diff --git a/api/core/v1alpha1/interface_types.go b/api/core/v1alpha1/interface_types.go index 6a247597..336240fb 100644 --- a/api/core/v1alpha1/interface_types.go +++ b/api/core/v1alpha1/interface_types.go @@ -21,6 +21,8 @@ import ( // +kubebuilder:validation:XValidation:rule="self.type == 'RoutedVLAN' || !has(self.ipv4) || !self.ipv4.anycastGateway", message="anycastGateway can only be enabled for interfaces of type RoutedVLAN" // +kubebuilder:validation:XValidation:rule="self.type != 'Aggregate' || !has(self.vrfRef)", message="vrfRef must not be specified for interfaces of type Aggregate" // +kubebuilder:validation:XValidation:rule="self.type != 'Physical' || !has(self.switchport) || !has(self.vrfRef)", message="vrfRef must not be specified for Physical interfaces with switchport configuration" +// +kubebuilder:validation:XValidation:rule="self.type != 'Aggregate' || !has(self.bfd)", message="bfd must not be specified for interfaces of type Aggregate" +// +kubebuilder:validation:XValidation:rule="!has(self.bfd) || !has(self.switchport)", message="bfd must not be specified for interfaces with switchport configuration" type InterfaceSpec struct { // DeviceName is the name of the Device this object belongs to. The Device object must exist in the same namespace. // Immutable. @@ -86,6 +88,11 @@ type InterfaceSpec struct { // The referenced VRF must exist in the same namespace. // +optional VrfRef *LocalObjectReference `json:"vrfRef,omitempty"` + + // BFD defines the Bidirectional Forwarding Detection configuration for the interface. + // BFD is only applicable for Layer 3 interfaces (Physical, Loopback, RoutedVLAN). + // +optional + BFD *BFD `json:"bfd,omitempty"` } // AdminState represents the administrative state of the interface. @@ -192,6 +199,39 @@ type InterfaceIPv4Unnumbered struct { InterfaceRef LocalObjectReference `json:"interfaceRef"` } +// BFD defines the Bidirectional Forwarding Detection configuration for an interface. +type BFD struct { + // Enabled indicates whether BFD is enabled on the interface. + // +required + Enabled bool `json:"enabled"` + + // DesiredMinimumTxInterval is the minimum interval between transmission of BFD control + // packets that the operator desires. This value is advertised to the peer. + // The actual interval used is the maximum of this value and the remote + // required-minimum-receive interval value. + // +optional + // +kubebuilder:validation:Type=string + // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$" + DesiredMinimumTxInterval *metav1.Duration `json:"desiredMinimumTxInterval,omitempty"` + + // RequiredMinimumReceive is the minimum interval between received BFD control packets + // that this system should support. This value is advertised to the remote peer to + // indicate the maximum frequency between BFD control packets that is acceptable + // to the local system. + // +optional + // +kubebuilder:validation:Type=string + // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$" + RequiredMinimumReceive *metav1.Duration `json:"requiredMinimumReceive,omitempty"` + + // DetectionMultiplier is the number of packets that must be missed to declare + // this session as down. The detection interval for the BFD session is calculated + // by multiplying the value of the negotiated transmission interval by this value. + // +optional + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Maximum=255 + DetectionMultiplier *int32 `json:"detectionMultiplier,omitempty"` +} + type Aggregation struct { // MemberInterfaceRefs is a list of interface references that are part of the aggregate interface. // +required diff --git a/api/core/v1alpha1/isis_types.go b/api/core/v1alpha1/isis_types.go index 13a9ab8e..0399f0ec 100644 --- a/api/core/v1alpha1/isis_types.go +++ b/api/core/v1alpha1/isis_types.go @@ -48,10 +48,10 @@ type ISISSpec struct { // +kubebuilder:validation:MaxItems=2 AddressFamilies []AddressFamily `json:"addressFamilies"` - // Interfaces is a list of interfaces that are part of the ISIS instance. + // InterfaceRefs is a list of interfaces that are part of the ISIS instance. // +optional // +listType=atomic - Interfaces []ISISInterface `json:"interfaces,omitempty"` + InterfaceRefs []LocalObjectReference `json:"interfaceRefs,omitempty"` } // ISISLevel represents the level of an ISIS instance. @@ -83,25 +83,6 @@ const ( AddressFamilyIPv6Unicast AddressFamily = "IPv6Unicast" ) -type ISISInterface struct { - // Ref is a reference to the interface object. - // The interface object must exist in the same namespace. - // +required - Ref LocalObjectReference `json:"ref"` - - // BFD contains BFD configuration for the interface. - // +optional - // +kubebuilder:default={} - BFD ISISBFD `json:"bfd,omitzero"` -} - -type ISISBFD struct { - // Enabled indicates whether BFD is enabled on the interface. - // +optional - // +kubebuilder:default=false - Enabled bool `json:"enabled"` -} - // ISISStatus defines the observed state of ISIS. type ISISStatus struct { // The conditions are a list of status objects that describe the state of the ISIS. diff --git a/api/core/v1alpha1/zz_generated.deepcopy.go b/api/core/v1alpha1/zz_generated.deepcopy.go index 8e643614..f4fb2326 100644 --- a/api/core/v1alpha1/zz_generated.deepcopy.go +++ b/api/core/v1alpha1/zz_generated.deepcopy.go @@ -179,6 +179,36 @@ func (in *Aggregation) DeepCopy() *Aggregation { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BFD) DeepCopyInto(out *BFD) { + *out = *in + if in.DesiredMinimumTxInterval != nil { + in, out := &in.DesiredMinimumTxInterval, &out.DesiredMinimumTxInterval + *out = new(v1.Duration) + **out = **in + } + if in.RequiredMinimumReceive != nil { + in, out := &in.RequiredMinimumReceive, &out.RequiredMinimumReceive + *out = new(v1.Duration) + **out = **in + } + if in.DetectionMultiplier != nil { + in, out := &in.DetectionMultiplier, &out.DetectionMultiplier + *out = new(int32) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BFD. +func (in *BFD) DeepCopy() *BFD { + if in == nil { + return nil + } + out := new(BFD) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BGP) DeepCopyInto(out *BGP) { *out = *in @@ -1363,38 +1393,6 @@ func (in *ISIS) DeepCopyObject() runtime.Object { return nil } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ISISBFD) DeepCopyInto(out *ISISBFD) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ISISBFD. -func (in *ISISBFD) DeepCopy() *ISISBFD { - if in == nil { - return nil - } - out := new(ISISBFD) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ISISInterface) DeepCopyInto(out *ISISInterface) { - *out = *in - out.Ref = in.Ref - out.BFD = in.BFD -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ISISInterface. -func (in *ISISInterface) DeepCopy() *ISISInterface { - if in == nil { - return nil - } - out := new(ISISInterface) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ISISList) DeepCopyInto(out *ISISList) { *out = *in @@ -1441,9 +1439,9 @@ func (in *ISISSpec) DeepCopyInto(out *ISISSpec) { *out = make([]AddressFamily, len(*in)) copy(*out, *in) } - if in.Interfaces != nil { - in, out := &in.Interfaces, &out.Interfaces - *out = make([]ISISInterface, len(*in)) + if in.InterfaceRefs != nil { + in, out := &in.InterfaceRefs, &out.InterfaceRefs + *out = make([]LocalObjectReference, len(*in)) copy(*out, *in) } } @@ -1631,6 +1629,11 @@ func (in *InterfaceSpec) DeepCopyInto(out *InterfaceSpec) { *out = new(LocalObjectReference) **out = **in } + if in.BFD != nil { + in, out := &in.BFD, &out.BFD + *out = new(BFD) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InterfaceSpec. diff --git a/charts/network-operator/templates/crd/networking.metal.ironcore.dev_interfaces.yaml b/charts/network-operator/templates/crd/networking.metal.ironcore.dev_interfaces.yaml index 677f5ae2..77274cf4 100644 --- a/charts/network-operator/templates/crd/networking.metal.ironcore.dev_interfaces.yaml +++ b/charts/network-operator/templates/crd/networking.metal.ironcore.dev_interfaces.yaml @@ -153,6 +153,42 @@ spec: required: - memberInterfaceRefs type: object + bfd: + description: |- + BFD defines the Bidirectional Forwarding Detection configuration for the interface. + BFD is only applicable for Layer 3 interfaces (Physical, Loopback, RoutedVLAN). + properties: + desiredMinimumTxInterval: + description: |- + DesiredMinimumTxInterval is the minimum interval between transmission of BFD control + packets that the operator desires. This value is advertised to the peer. + The actual interval used is the maximum of this value and the remote + required-minimum-receive interval value. + pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ + type: string + detectionMultiplier: + description: |- + DetectionMultiplier is the number of packets that must be missed to declare + this session as down. The detection interval for the BFD session is calculated + by multiplying the value of the negotiated transmission interval by this value. + format: int32 + maximum: 255 + minimum: 1 + type: integer + enabled: + description: Enabled indicates whether BFD is enabled on the interface. + type: boolean + requiredMinimumReceive: + description: |- + RequiredMinimumReceive is the minimum interval between received BFD control packets + that this system should support. This value is advertised to the remote peer to + indicate the maximum frequency between BFD control packets that is acceptable + to the local system. + pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ + type: string + required: + - enabled + type: object description: description: Description provides a human-readable description of the interface. @@ -405,6 +441,10 @@ spec: - message: vrfRef must not be specified for Physical interfaces with switchport configuration rule: self.type != 'Physical' || !has(self.switchport) || !has(self.vrfRef) + - message: bfd must not be specified for interfaces of type Aggregate + rule: self.type != 'Aggregate' || !has(self.bfd) + - message: bfd must not be specified for interfaces with switchport configuration + rule: '!has(self.bfd) || !has(self.switchport)' status: description: |- Status of the resource. This is set and updated automatically. diff --git a/charts/network-operator/templates/crd/networking.metal.ironcore.dev_isis.yaml b/charts/network-operator/templates/crd/networking.metal.ironcore.dev_isis.yaml index 74419bdd..984282fd 100644 --- a/charts/network-operator/templates/crd/networking.metal.ironcore.dev_isis.yaml +++ b/charts/network-operator/templates/crd/networking.metal.ironcore.dev_isis.yaml @@ -98,40 +98,25 @@ spec: x-kubernetes-validations: - message: Instance is immutable rule: self == oldSelf - interfaces: - description: Interfaces is a list of interfaces that are part of the - ISIS instance. + interfaceRefs: + description: InterfaceRefs is a list of interfaces that are part of + the ISIS instance. items: + description: |- + LocalObjectReference contains enough information to locate a + referenced object inside the same namespace. properties: - bfd: - default: {} - description: BFD contains BFD configuration for the interface. - properties: - enabled: - default: false - description: Enabled indicates whether BFD is enabled on - the interface. - type: boolean - type: object - ref: + name: description: |- - Ref is a reference to the interface object. - The interface object must exist in the same namespace. - properties: - name: - description: |- - Name of the referent. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - maxLength: 63 - minLength: 1 - type: string - required: - - name - type: object - x-kubernetes-map-type: atomic + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + maxLength: 63 + minLength: 1 + type: string required: - - ref + - name type: object + x-kubernetes-map-type: atomic type: array x-kubernetes-list-type: atomic networkEntityTitle: diff --git a/config/crd/bases/networking.metal.ironcore.dev_interfaces.yaml b/config/crd/bases/networking.metal.ironcore.dev_interfaces.yaml index 9d56c81d..4292b3e2 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_interfaces.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_interfaces.yaml @@ -147,6 +147,42 @@ spec: required: - memberInterfaceRefs type: object + bfd: + description: |- + BFD defines the Bidirectional Forwarding Detection configuration for the interface. + BFD is only applicable for Layer 3 interfaces (Physical, Loopback, RoutedVLAN). + properties: + desiredMinimumTxInterval: + description: |- + DesiredMinimumTxInterval is the minimum interval between transmission of BFD control + packets that the operator desires. This value is advertised to the peer. + The actual interval used is the maximum of this value and the remote + required-minimum-receive interval value. + pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ + type: string + detectionMultiplier: + description: |- + DetectionMultiplier is the number of packets that must be missed to declare + this session as down. The detection interval for the BFD session is calculated + by multiplying the value of the negotiated transmission interval by this value. + format: int32 + maximum: 255 + minimum: 1 + type: integer + enabled: + description: Enabled indicates whether BFD is enabled on the interface. + type: boolean + requiredMinimumReceive: + description: |- + RequiredMinimumReceive is the minimum interval between received BFD control packets + that this system should support. This value is advertised to the remote peer to + indicate the maximum frequency between BFD control packets that is acceptable + to the local system. + pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$ + type: string + required: + - enabled + type: object description: description: Description provides a human-readable description of the interface. @@ -399,6 +435,10 @@ spec: - message: vrfRef must not be specified for Physical interfaces with switchport configuration rule: self.type != 'Physical' || !has(self.switchport) || !has(self.vrfRef) + - message: bfd must not be specified for interfaces of type Aggregate + rule: self.type != 'Aggregate' || !has(self.bfd) + - message: bfd must not be specified for interfaces with switchport configuration + rule: '!has(self.bfd) || !has(self.switchport)' status: description: |- Status of the resource. This is set and updated automatically. diff --git a/config/crd/bases/networking.metal.ironcore.dev_isis.yaml b/config/crd/bases/networking.metal.ironcore.dev_isis.yaml index ad9e3c56..cc7b42f3 100644 --- a/config/crd/bases/networking.metal.ironcore.dev_isis.yaml +++ b/config/crd/bases/networking.metal.ironcore.dev_isis.yaml @@ -92,40 +92,25 @@ spec: x-kubernetes-validations: - message: Instance is immutable rule: self == oldSelf - interfaces: - description: Interfaces is a list of interfaces that are part of the - ISIS instance. + interfaceRefs: + description: InterfaceRefs is a list of interfaces that are part of + the ISIS instance. items: + description: |- + LocalObjectReference contains enough information to locate a + referenced object inside the same namespace. properties: - bfd: - default: {} - description: BFD contains BFD configuration for the interface. - properties: - enabled: - default: false - description: Enabled indicates whether BFD is enabled on - the interface. - type: boolean - type: object - ref: + name: description: |- - Ref is a reference to the interface object. - The interface object must exist in the same namespace. - properties: - name: - description: |- - Name of the referent. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - maxLength: 63 - minLength: 1 - type: string - required: - - name - type: object - x-kubernetes-map-type: atomic + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + maxLength: 63 + minLength: 1 + type: string required: - - ref + - name type: object + x-kubernetes-map-type: atomic type: array x-kubernetes-list-type: atomic networkEntityTitle: diff --git a/config/samples/v1alpha1_interface.yaml b/config/samples/v1alpha1_interface.yaml index 1bd062cf..4852ea7d 100644 --- a/config/samples/v1alpha1_interface.yaml +++ b/config/samples/v1alpha1_interface.yaml @@ -63,6 +63,11 @@ spec: unnumbered: interfaceRef: name: lo0 + bfd: + enabled: true + desiredMinimumTxInterval: 300ms + requiredMinimumReceive: 300ms + detectionMultiplier: 3 --- apiVersion: networking.metal.ironcore.dev/v1alpha1 kind: Interface @@ -84,6 +89,11 @@ spec: unnumbered: interfaceRef: name: lo0 + bfd: + enabled: true + desiredMinimumTxInterval: 500ms + requiredMinimumReceive: 500ms + detectionMultiplier: 5 --- apiVersion: networking.metal.ironcore.dev/v1alpha1 kind: Interface diff --git a/config/samples/v1alpha1_isis.yaml b/config/samples/v1alpha1_isis.yaml index 4cd11598..2334f3ff 100644 --- a/config/samples/v1alpha1_isis.yaml +++ b/config/samples/v1alpha1_isis.yaml @@ -17,15 +17,7 @@ spec: - IPv4Unicast - IPv6Unicast interfaces: - - ref: - name: eth1-1 - bfd: - enabled: true - - ref: - name: eth1-2 - bfd: - enabled: true - - ref: - name: lo0 - - ref: - name: lo1 + - name: eth1-1 + - name: eth1-2 + - name: lo0 + - name: lo1 diff --git a/internal/controller/core/isis_controller.go b/internal/controller/core/isis_controller.go index d49f1a35..f8006407 100644 --- a/internal/controller/core/isis_controller.go +++ b/internal/controller/core/isis_controller.go @@ -222,14 +222,14 @@ func (r *ISISReconciler) reconcile(ctx context.Context, s *isisScope) (_ ctrl.Re } } - var interfaces []provider.ISISInterface - for _, iface := range s.ISIS.Spec.Interfaces { - res := new(v1alpha1.Interface) - if err := r.Get(ctx, client.ObjectKey{Name: iface.Ref.Name, Namespace: s.ISIS.Namespace}, res); err != nil { + var interfaces []*v1alpha1.Interface + for _, iface := range s.ISIS.Spec.InterfaceRefs { + intf := new(v1alpha1.Interface) + if err := r.Get(ctx, client.ObjectKey{Name: iface.Name, Namespace: s.ISIS.Namespace}, intf); err != nil { return ctrl.Result{}, err } - if !conditions.IsReady(res) { + if !conditions.IsReady(intf) { conditions.Set(s.ISIS, metav1.Condition{ Type: v1alpha1.ReadyCondition, Status: metav1.ConditionFalse, @@ -239,10 +239,7 @@ func (r *ISISReconciler) reconcile(ctx context.Context, s *isisScope) (_ ctrl.Re return ctrl.Result{RequeueAfter: r.RequeueInterval}, nil } - interfaces = append(interfaces, provider.ISISInterface{ - Interface: res, - BFD: iface.BFD.Enabled, - }) + interfaces = append(interfaces, intf) } if err := s.Provider.Connect(ctx, s.Connection); err != nil { diff --git a/internal/provider/cisco/nxos/intf.go b/internal/provider/cisco/nxos/intf.go index e5a198e4..3a9c6055 100644 --- a/internal/provider/cisco/nxos/intf.go +++ b/internal/provider/cisco/nxos/intf.go @@ -24,6 +24,9 @@ var ( _ gnmiext.Configurable = (*PhysIfOperItems)(nil) _ gnmiext.Configurable = (*VrfMember)(nil) _ gnmiext.Configurable = (*SpanningTree)(nil) + _ gnmiext.Configurable = (*MultisiteIfTracking)(nil) + _ gnmiext.Configurable = (*BFD)(nil) + _ gnmiext.Configurable = (*ICMPIf)(nil) _ gnmiext.Configurable = (*PortChannel)(nil) _ gnmiext.Configurable = (*PortChannelOperItems)(nil) _ gnmiext.Configurable = (*SwitchVirtualInterface)(nil) @@ -168,6 +171,46 @@ func (m *MultisiteIfTracking) XPath() string { return "System/intf-items/phys-items/PhysIf-list[id=" + m.IfName + "]/multisiteiftracking-items" } +type BFD struct { + ID string `json:"id"` + AdminSt AdminSt `json:"adminSt"` + IfkaItems struct { + DetectMult int32 `json:"detectMult"` + MinRxIntvlMs int64 `json:"minRxIntvl"` + MinTxIntvlMs int64 `json:"minTxIntvl"` + } `json:"ifka-items,omitzero"` +} + +func (*BFD) IsListItem() {} + +func (b *BFD) XPath() string { + return "System/bfd-items/inst-items/if-items/If-list[id=" + b.ID + "]" +} + +func (b *BFD) Validate() error { + if b.IfkaItems.DetectMult < 1 || b.IfkaItems.DetectMult > 50 { + return fmt.Errorf("bfd: invalid detect-mult %d: must be between 1 and 50", b.IfkaItems.DetectMult) + } + if b.IfkaItems.MinRxIntvlMs < 100 || b.IfkaItems.MinRxIntvlMs > 999 { + return fmt.Errorf("bfd: invalid min-rx-intvl %d: must be between 100 and 999", b.IfkaItems.MinRxIntvlMs) + } + if b.IfkaItems.MinTxIntvlMs < 100 || b.IfkaItems.MinTxIntvlMs > 999 { + return fmt.Errorf("bfd: invalid min-tx-intvl %d: must be between 100 and 999", b.IfkaItems.MinTxIntvlMs) + } + return nil +} + +type ICMPIf struct { + ID string `json:"id"` + Ctrl string `json:"ctrl"` +} + +func (*ICMPIf) IsListItem() {} + +func (i *ICMPIf) XPath() string { + return "System/icmpv4-items/inst-items/dom-items/Dom-list[name=default]/if-items/If-list[id=" + i.ID + "]" +} + // PortChannel represents a port-channel (LAG) interface on a NX-OS device. type PortChannel struct { AccessVlan string `json:"accessVlan"` diff --git a/internal/provider/cisco/nxos/intf_test.go b/internal/provider/cisco/nxos/intf_test.go index cc361ee4..46137672 100644 --- a/internal/provider/cisco/nxos/intf_test.go +++ b/internal/provider/cisco/nxos/intf_test.go @@ -81,4 +81,13 @@ func init() { dci := &MultisiteIfTracking{IfName: "eth1/1", Tracking: MultisiteIfTrackingModeDCI} Register("bgw_tracking", dci) + + bfd := &BFD{AdminSt: AdminStEnabled, ID: "eth1/1"} + bfd.IfkaItems.DetectMult = 15 + bfd.IfkaItems.MinRxIntvlMs = 100 + bfd.IfkaItems.MinTxIntvlMs = 150 + Register("bfd", bfd) + + icmp := &ICMPIf{ID: "eth1/1", Ctrl: "port-unreachable"} + Register("rdr", icmp) } diff --git a/internal/provider/cisco/nxos/ospf.go b/internal/provider/cisco/nxos/ospf.go index fdcffc18..db4a1225 100644 --- a/internal/provider/cisco/nxos/ospf.go +++ b/internal/provider/cisco/nxos/ospf.go @@ -81,6 +81,7 @@ type OSPFInterface struct { ID string `json:"id"` NwT NtwType `json:"nwT"` PassiveCtrl PassiveControl `json:"passiveCtrl"` + BFDCtrl OspfBfdCtrl `json:"bfdCtrl"` } func (i *OSPFInterface) Key() string { return i.ID } @@ -112,7 +113,7 @@ type OSPFIfAdjEpGroup struct { OperSt AdjOperSt `json:"operSt"` // Adjacency neighbor state Prio uint8 `json:"prio"` // Priority, used in determining the designated router on this network AdjStatsItems struct { - LastStChgTs time.Time `json:"lastStChgTs"` // Timestamp of the last state change + LastStChgTS time.Time `json:"lastStChgTs"` // Timestamp of the last state change } `json:"adjstats-items,omitzero"` } @@ -202,3 +203,11 @@ const ( PassiveControlEnabled PassiveControl = "enabled" PassiveControlDisabled PassiveControl = "disabled" ) + +type OspfBfdCtrl string + +const ( + OspfBfdCtrlUnspecified OspfBfdCtrl = "unspecified" + OspfBfdCtrlEnabled OspfBfdCtrl = "enabled" + OspfBfdCtrlDisabled OspfBfdCtrl = "disabled" +) diff --git a/internal/provider/cisco/nxos/ospf_test.go b/internal/provider/cisco/nxos/ospf_test.go index e7cf4367..467bf367 100644 --- a/internal/provider/cisco/nxos/ospf_test.go +++ b/internal/provider/cisco/nxos/ospf_test.go @@ -23,6 +23,7 @@ func init() { Area: "0.0.0.0", NwT: NtwTypeUnspecified, PassiveCtrl: PassiveControlUnspecified, + BFDCtrl: OspfBfdCtrlUnspecified, } if strings.HasPrefix(name, "eth") { intf.NwT = NtwTypePointToPoint diff --git a/internal/provider/cisco/nxos/provider.go b/internal/provider/cisco/nxos/provider.go index 8106ff9c..aff42599 100644 --- a/internal/provider/cisco/nxos/provider.go +++ b/internal/provider/cisco/nxos/provider.go @@ -897,6 +897,57 @@ func (p *Provider) EnsureInterface(ctx context.Context, req *provider.EnsureInte if addr != nil { conf = append(conf, addr) } + + bfd := new(BFD) + bfd.ID = name + if req.Interface.Spec.BFD != nil { + f := new(Feature) + f.Name = "bfd" + f.AdminSt = AdminStEnabled + conf = append(conf, f) + + icmp := new(ICMPIf) + icmp.ID = name + icmp.Ctrl = "port-unreachable" + conf = append(conf, icmp) + + bfd.AdminSt = AdminStDisabled + if req.Interface.Spec.BFD.Enabled { + bfd.AdminSt = AdminStEnabled + bfd.IfkaItems.MinTxIntvlMs = 50 + if req.Interface.Spec.BFD.DesiredMinimumTxInterval != nil { + bfd.IfkaItems.MinTxIntvlMs = req.Interface.Spec.BFD.DesiredMinimumTxInterval.Milliseconds() + } + bfd.IfkaItems.MinRxIntvlMs = 50 + if req.Interface.Spec.BFD.RequiredMinimumReceive != nil { + bfd.IfkaItems.MinRxIntvlMs = req.Interface.Spec.BFD.RequiredMinimumReceive.Milliseconds() + } + bfd.IfkaItems.DetectMult = 3 + if req.Interface.Spec.BFD.DetectionMultiplier != nil { + bfd.IfkaItems.DetectMult = *req.Interface.Spec.BFD.DetectionMultiplier + } + if err := bfd.Validate(); err != nil { + return err + } + } + conf = append(conf, bfd) + } else { + icmp := new(ICMPIf) + icmp.ID = name + switch req.Interface.Spec.Type { + case v1alpha1.InterfaceTypePhysical: + if err := p.client.Delete(ctx, icmp); err != nil { + return err + } + case v1alpha1.InterfaceTypeLoopback: + icmp.Ctrl = "port-unreachable,redirect" + conf = append(conf, icmp) + case v1alpha1.InterfaceTypeRoutedVLAN: + icmp.Ctrl = "port-unreachable" + conf = append(conf, icmp) + } + } + return p.client.Update(ctx, conf...) } @@ -917,6 +968,10 @@ func (p *Provider) DeleteInterface(ctx context.Context, req *provider.InterfaceR } } + bfd := new(BFD) + bfd.ID = name + conf = append(conf, bfd) + switch req.Interface.Spec.Type { case v1alpha1.InterfaceTypePhysical: i := new(PhysIf) @@ -930,6 +985,10 @@ func (p *Provider) DeleteInterface(ctx context.Context, req *provider.InterfaceR conf = append(conf, stp) } + icmp := new(ICMPIf) + icmp.ID = name + conf = append(conf, icmp) + case v1alpha1.InterfaceTypeLoopback: lb := new(Loopback) lb.ID = name @@ -1045,8 +1104,8 @@ func (p *Provider) EnsureISIS(ctx context.Context, req *provider.EnsureISISReque conf := append(make([]gnmiext.Configurable, 0, 3), f) - if slices.ContainsFunc(req.Interfaces, func(intf provider.ISISInterface) bool { - return intf.BFD + if slices.ContainsFunc(req.Interfaces, func(intf *v1alpha1.Interface) bool { + return intf.Spec.BFD.Enabled }) { f := new(Feature) f.Name = "bfd" @@ -1089,12 +1148,7 @@ func (p *Provider) EnsureISIS(ctx context.Context, req *provider.EnsureISISReque dom.AfItems.DomAfList.Set(item) } - interfaces := make([]*v1alpha1.Interface, 0, len(req.Interfaces)) - for _, iface := range req.Interfaces { - interfaces = append(interfaces, iface.Interface) - } - - interfaceNames, err := p.EnsureInterfacesExist(ctx, interfaces) + interfaceNames, err := p.EnsureInterfacesExist(ctx, req.Interfaces) if err != nil { return err } @@ -1107,20 +1161,20 @@ func (p *Provider) EnsureISIS(ctx context.Context, req *provider.EnsureISISReque intf := new(ISISInterface) intf.ID = interfaceNames[i] intf.NetworkTypeP2P = AdminStOff - if iface.Interface.Spec.Type == v1alpha1.InterfaceTypePhysical { + if iface.Spec.Type == v1alpha1.InterfaceTypePhysical { intf.NetworkTypeP2P = AdminStOn } if ipv4 { intf.V4Enable = true intf.V4Bfd = "inheritVrf" - if iface.BFD { + if iface.Spec.BFD.Enabled { intf.V4Bfd = "enabled" } } if ipv6 { intf.V6Enable = true intf.V6Bfd = "inheritVrf" - if iface.BFD { + if iface.Spec.BFD.Enabled { intf.V6Bfd = "enabled" } } @@ -1385,13 +1439,17 @@ func (p *Provider) EnsureOSPF(ctx context.Context, req *provider.EnsureOSPFReque } } + conf := make([]gnmiext.Configurable, 0, 3) + f := new(Feature) f.Name = "ospf" f.AdminSt = AdminStEnabled + conf = append(conf, f) o := new(OSPF) o.AdminSt = AdminStEnabled o.Name = req.OSPF.Spec.Instance + conf = append(conf, o) dom := new(OSPFDom) dom.Name = DefaultVRFName @@ -1447,6 +1505,18 @@ func (p *Provider) EnsureOSPF(ctx context.Context, req *provider.EnsureOSPFReque if iface.Passive == nil || !*iface.Passive { intf.PassiveCtrl = PassiveControlDisabled } + intf.BFDCtrl = OspfBfdCtrlUnspecified + if iface.Interface.Spec.BFD != nil { + fb := new(Feature) + fb.Name = "bfd" + fb.AdminSt = AdminStEnabled + conf = slices.Insert(conf, 1, gnmiext.Configurable(fb)) // insert before OSPF + + intf.BFDCtrl = OspfBfdCtrlDisabled + if !iface.Interface.Spec.BFD.Enabled { + intf.BFDCtrl = OspfBfdCtrlEnabled + } + } dom.IfItems.IfList.Set(intf) } @@ -1474,7 +1544,7 @@ func (p *Provider) EnsureOSPF(ctx context.Context, req *provider.EnsureOSPFReque dom.MaxlsapItems.MaxLsa = cfg.MaxLSA } - return p.client.Update(ctx, f, o) + return p.client.Update(ctx, conf...) } func (p *Provider) DeleteOSPF(ctx context.Context, req *provider.DeleteOSPFRequest) error { @@ -1512,7 +1582,7 @@ func (p *Provider) GetOSPFStatus(ctx context.Context, req *provider.OSPFStatusRe Address: adj.PeerIP, Interface: i, Priority: adj.Prio, - LastEstablishedTime: adj.AdjStatsItems.LastStChgTs, + LastEstablishedTime: adj.AdjStatsItems.LastStChgTS, AdjacencyState: adj.OperSt.ToNeighborState(), }) } diff --git a/internal/provider/cisco/nxos/testdata/bfd.json b/internal/provider/cisco/nxos/testdata/bfd.json new file mode 100644 index 00000000..18018694 --- /dev/null +++ b/internal/provider/cisco/nxos/testdata/bfd.json @@ -0,0 +1,19 @@ +{ + "bfd-items": { + "inst-items": { + "if-items": { + "If-list": [ + { + "id": "eth1/1", + "adminSt": "enabled", + "ifka-items": { + "detectMult": 15, + "minRxIntvl": 100, + "minTxIntvl": 150 + } + } + ] + } + } + } +} diff --git a/internal/provider/cisco/nxos/testdata/bfd.json.txt b/internal/provider/cisco/nxos/testdata/bfd.json.txt new file mode 100644 index 00000000..92b7be8d --- /dev/null +++ b/internal/provider/cisco/nxos/testdata/bfd.json.txt @@ -0,0 +1,2 @@ +interface Ethernet1/1 + bfd interval 150 min_rx 100 multiplier 15 diff --git a/internal/provider/cisco/nxos/testdata/ospf.json b/internal/provider/cisco/nxos/testdata/ospf.json index 954a387e..ea95822e 100644 --- a/internal/provider/cisco/nxos/testdata/ospf.json +++ b/internal/provider/cisco/nxos/testdata/ospf.json @@ -23,7 +23,8 @@ "area": "0.0.0.0", "id": "eth1/1", "nwT": "p2p", - "passiveCtrl": "unspecified" + "passiveCtrl": "unspecified", + "bfdCtrl": "unspecified" } ] }, diff --git a/internal/provider/cisco/nxos/testdata/rdr.json b/internal/provider/cisco/nxos/testdata/rdr.json new file mode 100644 index 00000000..7bae6eca --- /dev/null +++ b/internal/provider/cisco/nxos/testdata/rdr.json @@ -0,0 +1,21 @@ +{ + "icmpv4-items": { + "inst-items": { + "dom-items": { + "Dom-list": [ + { + "name": "default", + "if-items": { + "If-list": [ + { + "id": "eth1/1", + "ctrl": "port-unreachable" + } + ] + } + } + ] + } + } + } +} diff --git a/internal/provider/cisco/nxos/testdata/rdr.json.txt b/internal/provider/cisco/nxos/testdata/rdr.json.txt new file mode 100644 index 00000000..b38824f6 --- /dev/null +++ b/internal/provider/cisco/nxos/testdata/rdr.json.txt @@ -0,0 +1,2 @@ +interface Ethernet1/1 + no ip redirects diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 37d567de..ad7765ae 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -299,15 +299,10 @@ type ISISProvider interface { type EnsureISISRequest struct { ISIS *v1alpha1.ISIS - Interfaces []ISISInterface + Interfaces []*v1alpha1.Interface ProviderConfig *ProviderConfig } -type ISISInterface struct { - Interface *v1alpha1.Interface - BFD bool -} - type DeleteISISRequest struct { ISIS *v1alpha1.ISIS ProviderConfig *ProviderConfig