diff --git a/api/models/vpp/interfaces/keys.go b/api/models/vpp/interfaces/keys.go index e13ac15520..6365fbb829 100644 --- a/api/models/vpp/interfaces/keys.go +++ b/api/models/vpp/interfaces/keys.go @@ -135,6 +135,13 @@ const ( rxModesKeyTemplate = "vpp/interface/{iface}/rx-modes" ) +/* Interface with IP address (derived, property) */ +const ( + // interfaceWithIPKeyTemplate is a template for keys derived from all interfaces + // but created only after at least one IP address is assigned. + interfaceWithIPKeyTemplate = "vpp/interface/{iface}/has-IP-address" +) + const ( // InvalidKeyPart is used in key for parts which are invalid InvalidKeyPart = "" @@ -477,6 +484,31 @@ func ParseLinkStateKey(key string) (ifaceName string, isLinkUp bool, isLinkState return } +/* Interface with IP address (derived property) */ + +// InterfaceWithIPKey returns key derived from every VPP interface but created only +// after at least one IP address was assigned to it. +func InterfaceWithIPKey(ifaceName string) string { + if ifaceName == "" { + ifaceName = InvalidKeyPart + } + return strings.Replace(interfaceWithIPKeyTemplate, "{iface}", ifaceName, 1) +} + +// ParseInterfaceWithIPKey parses key derived from every VPP interface but created only +// after at least one IP address was assigned to it +func ParseInterfaceWithIPKey(key string) (ifaceName string, isInterfaceWithIPKey bool) { + if suffix := strings.TrimPrefix(key, "vpp/interface/"); suffix != key { + if prefix := strings.TrimSuffix(suffix, "/has-IP-address"); prefix != suffix { + if prefix != InvalidKeyPart { + ifaceName = prefix + isInterfaceWithIPKey = true + } + } + } + return +} + /* Rx placement (derived) */ // RxPlacementKey returns a key representing rx-placement configured for a given diff --git a/api/models/vpp/interfaces/keys_test.go b/api/models/vpp/interfaces/keys_test.go index e166f65aad..172205552c 100644 --- a/api/models/vpp/interfaces/keys_test.go +++ b/api/models/vpp/interfaces/keys_test.go @@ -1152,3 +1152,86 @@ func TestParseRxModesKey(t *testing.T) { }) } } + +func TestInterfaceWithIPKey(t *testing.T) { + tests := []struct { + name string + iface string + expectedKey string + }{ + { + name: "memif", + iface: "memif0", + expectedKey: "vpp/interface/memif0/has-IP-address", + }, + { + name: "invalid interface name", + iface: "", + expectedKey: "vpp/interface//has-IP-address", + }, + { + name: "Gbe interface", + iface: "GigabitEthernet0/8/0", + expectedKey: "vpp/interface/GigabitEthernet0/8/0/has-IP-address", + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + key := InterfaceWithIPKey(test.iface) + if key != test.expectedKey { + t.Errorf("failed for: iface=%s\n"+ + "expected key:\n\t%q\ngot key:\n\t%q", + test.iface, test.expectedKey, key) + } + }) + } +} + +func TestParseInterfaceWithIPKey(t *testing.T) { + tests := []struct { + name string + key string + expectedIface string + expectedIsIfaceWithIPKey bool + }{ + { + name: "memif", + key: "vpp/interface/memif0/has-IP-address", + expectedIface: "memif0", + expectedIsIfaceWithIPKey: true, + }, + { + name: "Gbe", + key: "vpp/interface/GigabitEthernet0/8/0/has-IP-address", + expectedIface: "GigabitEthernet0/8/0", + expectedIsIfaceWithIPKey: true, + }, + { + name: "invalid interface name", + key: "vpp/interface//has-IP-address", + expectedIsIfaceWithIPKey: false, + }, + { + name: "missing has-IP-address suffix", + key: "vpp/interface/", + expectedIsIfaceWithIPKey: false, + }, + { + name: "not has-IP-address key", + key: "vpp/interface/memif0/address/192.168.1.12/24", + expectedIsIfaceWithIPKey: false, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + iface, isInterfaceWithIPKey := ParseInterfaceWithIPKey(test.key) + if isInterfaceWithIPKey != test.expectedIsIfaceWithIPKey { + t.Errorf("expected isInterfaceWithIPKey: %v\tgot: %v", + test.expectedIsIfaceWithIPKey, isInterfaceWithIPKey) + } + if iface != test.expectedIface { + t.Errorf("expected iface: %s\tgot: %s", test.expectedIface, iface) + } + }) + } +} diff --git a/plugins/vpp/ifplugin/descriptor/interface.go b/plugins/vpp/ifplugin/descriptor/interface.go index 7e7978a5d9..6fc00d59d0 100644 --- a/plugins/vpp/ifplugin/descriptor/interface.go +++ b/plugins/vpp/ifplugin/descriptor/interface.go @@ -536,7 +536,9 @@ func (d *InterfaceDescriptor) Dependencies(key string, intf *interfaces.Interfac // - one empty value for every IP address to be assigned to the interface // - one empty value for VRF table to put the interface into // - one value with interface configuration reduced to RxMode if set -// - one Interface_RxPlacement for every queue with configured Rx placement. +// - one Interface_RxPlacement for every queue with configured Rx placement +// - one empty value which will be created once at least one IP address is +// assigned to the interface. func (d *InterfaceDescriptor) DerivedValues(key string, intf *interfaces.Interface) (derValues []kvs.KeyValuePair) { // unnumbered interface if intf.GetUnnumbered() != nil { @@ -622,6 +624,14 @@ func (d *InterfaceDescriptor) DerivedValues(key string, intf *interfaces.Interfa }) } + // with-IP address (property) + if len(intf.GetIpAddresses()) > 0 { + derValues = append(derValues, kvs.KeyValuePair{ + Key: interfaces.InterfaceWithIPKey(intf.GetName()), + Value: &prototypes.Empty{}, + }) + } + // TODO: define derived value for UP/DOWN state (needed for subinterfaces) return derValues diff --git a/plugins/vpp/ifplugin/descriptor/interface_with_address.go b/plugins/vpp/ifplugin/descriptor/interface_with_address.go new file mode 100644 index 0000000000..ceac826f81 --- /dev/null +++ b/plugins/vpp/ifplugin/descriptor/interface_with_address.go @@ -0,0 +1,85 @@ +// Copyright (c) 2019 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package descriptor + +import ( + "github.com/gogo/protobuf/proto" + "github.com/ligato/cn-infra/logging" + + interfaces "github.com/ligato/vpp-agent/api/models/vpp/interfaces" + kvs "github.com/ligato/vpp-agent/plugins/kvscheduler/api" +) + +const ( + // InterfaceWithAddressDescriptorName is the name of the descriptor for marking + // interfaces with at least one IP address assigned. + InterfaceWithAddressDescriptorName = "vpp-interface-has-address" + + // dependency labels + interfaceHasIPDep = "interface-has-IP" +) + +// InterfaceWithAddrDescriptor assigns property key-value pairs to interfaces +// with at least one IP address. +type InterfaceWithAddrDescriptor struct { + log logging.Logger +} + +// NewInterfaceWithAddrDescriptor creates a new instance of InterfaceWithAddrDescriptor. +func NewInterfaceWithAddrDescriptor(log logging.PluginLogger) *kvs.KVDescriptor { + + descrCtx := &InterfaceWithAddrDescriptor{ + log: log.NewLogger("interface-has-address-descriptor"), + } + return &kvs.KVDescriptor{ + Name: InterfaceWithAddressDescriptorName, + KeySelector: descrCtx.IsInterfaceWithAddressKey, + Create: descrCtx.Create, + Delete: descrCtx.Delete, + Dependencies: descrCtx.Dependencies, + } +} + +// IsInterfaceWithAddressKey returns true if the key is a property assigned to interface +// with at least one IP address. +func (d *InterfaceWithAddrDescriptor) IsInterfaceWithAddressKey(key string) bool { + _, isIfaceWithIPKey := interfaces.ParseInterfaceWithIPKey(key) + return isIfaceWithIPKey +} + +// Create is NOOP (the key-value pair is a property). +func (d *InterfaceWithAddrDescriptor) Create(key string, emptyVal proto.Message) (metadata kvs.Metadata, err error) { + return nil, nil +} + +// Delete is NOOP (the key-value pair is a property) +func (d *InterfaceWithAddrDescriptor) Delete(key string, emptyVal proto.Message, metadata kvs.Metadata) (err error) { + return nil +} + +// Dependencies ensures that the property is created only after at least one IP +// address is successfully assigned to the interface. +func (d *InterfaceWithAddrDescriptor) Dependencies(key string, emptyVal proto.Message) (deps []kvs.Dependency) { + ifaceName, _ := interfaces.ParseInterfaceWithIPKey(key) + return []kvs.Dependency{ + { + Label: interfaceHasIPDep, + AnyOf: kvs.AnyOfDependency{ + KeyPrefixes: []string{interfaces.InterfaceAddressPrefix(ifaceName)}, + }, + }, + } +} + diff --git a/plugins/vpp/ifplugin/descriptor/unnumbered.go b/plugins/vpp/ifplugin/descriptor/unnumbered.go index ae33e13762..c6af5f3853 100644 --- a/plugins/vpp/ifplugin/descriptor/unnumbered.go +++ b/plugins/vpp/ifplugin/descriptor/unnumbered.go @@ -125,9 +125,7 @@ func (d *UnnumberedIfDescriptor) Dependencies(key string, unIntf *interfaces.Int deps = []kvs.Dependency{ { Label: unnumberedInterfaceHasIPDep, - AnyOf: kvs.AnyOfDependency{ - KeyPrefixes: []string{interfaces.InterfaceAddressPrefix(unIntf.InterfaceWithIp)}, - }, + Key: interfaces.InterfaceWithIPKey(unIntf.InterfaceWithIp), }, } diff --git a/plugins/vpp/ifplugin/ifplugin.go b/plugins/vpp/ifplugin/ifplugin.go index 43974abb95..08dbcf2343 100644 --- a/plugins/vpp/ifplugin/ifplugin.go +++ b/plugins/vpp/ifplugin/ifplugin.go @@ -180,12 +180,14 @@ func (p *IfPlugin) Init() error { p.ifHandler, p.intfIndex, p.Log) linkStateDescriptor, p.linkStateDescriptor = descriptor.NewLinkStateDescriptor( p.KVScheduler, p.ifHandler, p.intfIndex, p.Log) + rxModeDescriptor := descriptor.NewRxModeDescriptor(p.ifHandler, p.intfIndex, p.Log) rxPlacementDescriptor := descriptor.NewRxPlacementDescriptor(p.ifHandler, p.intfIndex, p.Log) addrDescriptor := descriptor.NewInterfaceAddressDescriptor(p.ifHandler, p.intfIndex, p.Log) unIfDescriptor := descriptor.NewUnnumberedIfDescriptor(p.ifHandler, p.intfIndex, p.Log) bondIfDescriptor, _ := descriptor.NewBondedInterfaceDescriptor(p.ifHandler, p.intfIndex, p.Log) vrfDescriptor := descriptor.NewInterfaceVrfDescriptor(p.ifHandler, p.intfIndex, p.Log) + withAddrDescriptor := descriptor.NewInterfaceWithAddrDescriptor(p.Log) err = p.KVScheduler.RegisterKVDescriptor( dhcpDescriptor, @@ -196,6 +198,7 @@ func (p *IfPlugin) Init() error { unIfDescriptor, bondIfDescriptor, vrfDescriptor, + withAddrDescriptor, ) if err != nil { return err