Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions api/core/v1alpha1/interface_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
// +kubebuilder:validation:XValidation:rule="self.type == 'RoutedVLAN' || !has(self.vlanRef)", message="vlanRef must only be specified on interfaces of type RoutedVLAN"
// +kubebuilder:validation:XValidation:rule="self.type != 'RoutedVLAN' || !has(self.switchport)", message="switchport must not be specified for interfaces of type RoutedVLAN"
// +kubebuilder:validation:XValidation:rule="self.type != 'RoutedVLAN' || !has(self.aggregation)", message="aggregation must not be specified for interfaces of type RoutedVLAN"
// +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"
type InterfaceSpec struct {
Expand Down Expand Up @@ -158,6 +159,7 @@ const (

// InterfaceIPv4 defines the IPv4 configuration for an interface.
// +kubebuilder:validation:XValidation:rule="!has(self.addresses) || !has(self.unnumbered)", message="addresses and unnumbered are mutually exclusive"
// +kubebuilder:validation:XValidation:rule="!has(self.unnumbered) || !self.anycastGateway", message="anycastGateway and unnumbered are mutually exclusive"
type InterfaceIPv4 struct {
// Addresses defines the list of IPv4 addresses assigned to the interface.
// The first address in the list is considered the primary address,
Expand All @@ -171,6 +173,14 @@ type InterfaceIPv4 struct {
// When specified, the interface borrows the IP address from another interface.
// +optional
Unnumbered *InterfaceIPv4Unnumbered `json:"unnumbered,omitempty"`

// AnycastGateway enables distributed anycast gateway functionality.
// When enabled, this interface uses the virtual MAC configured in the
// device's NVE resource for active-active default gateway redundancy.
// Only applicable for RoutedVLAN interfaces in EVPN/VXLAN fabrics.
// +optional
// +kubebuilder:default=false
AnycastGateway bool `json:"anycastGateway,omitempty"`
}

// InterfaceIPv4Unnumbered defines the unnumbered interface configuration.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,14 @@ spec:
minItems: 1
type: array
x-kubernetes-list-type: atomic
anycastGateway:
default: false
description: |-
AnycastGateway enables distributed anycast gateway functionality.
When enabled, this interface uses the virtual MAC configured in the
device's NVE resource for active-active default gateway redundancy.
Only applicable for RoutedVLAN interfaces in EVPN/VXLAN fabrics.
type: boolean
unnumbered:
description: |-
Unnumbered defines the unnumbered interface configuration.
Expand Down Expand Up @@ -219,6 +227,8 @@ spec:
x-kubernetes-validations:
- message: addresses and unnumbered are mutually exclusive
rule: '!has(self.addresses) || !has(self.unnumbered)'
- message: anycastGateway and unnumbered are mutually exclusive
rule: '!has(self.unnumbered) || !self.anycastGateway'
mtu:
description: MTU (Maximum Transmission Unit) specifies the size of
the largest packet that can be sent over the interface.
Expand Down Expand Up @@ -391,6 +401,8 @@ spec:
rule: self.type != 'RoutedVLAN' || !has(self.switchport)
- message: aggregation must not be specified for interfaces of type RoutedVLAN
rule: self.type != 'RoutedVLAN' || !has(self.aggregation)
- message: anycastGateway can only be enabled for interfaces of type RoutedVLAN
rule: self.type == 'RoutedVLAN' || !has(self.ipv4) || !self.ipv4.anycastGateway
- message: vrfRef must not be specified for interfaces of type Aggregate
rule: self.type != 'Aggregate' || !has(self.vrfRef)
- message: vrfRef must not be specified for Physical interfaces with switchport
Expand Down
12 changes: 12 additions & 0 deletions config/crd/bases/networking.metal.ironcore.dev_interfaces.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,14 @@ spec:
minItems: 1
type: array
x-kubernetes-list-type: atomic
anycastGateway:
default: false
description: |-
AnycastGateway enables distributed anycast gateway functionality.
When enabled, this interface uses the virtual MAC configured in the
device's NVE resource for active-active default gateway redundancy.
Only applicable for RoutedVLAN interfaces in EVPN/VXLAN fabrics.
type: boolean
unnumbered:
description: |-
Unnumbered defines the unnumbered interface configuration.
Expand Down Expand Up @@ -213,6 +221,8 @@ spec:
x-kubernetes-validations:
- message: addresses and unnumbered are mutually exclusive
rule: '!has(self.addresses) || !has(self.unnumbered)'
- message: anycastGateway and unnumbered are mutually exclusive
rule: '!has(self.unnumbered) || !self.anycastGateway'
mtu:
description: MTU (Maximum Transmission Unit) specifies the size of
the largest packet that can be sent over the interface.
Expand Down Expand Up @@ -385,6 +395,8 @@ spec:
rule: self.type != 'RoutedVLAN' || !has(self.switchport)
- message: aggregation must not be specified for interfaces of type RoutedVLAN
rule: self.type != 'RoutedVLAN' || !has(self.aggregation)
- message: anycastGateway can only be enabled for interfaces of type RoutedVLAN
rule: self.type == 'RoutedVLAN' || !has(self.ipv4) || !self.ipv4.anycastGateway
- message: vrfRef must not be specified for interfaces of type Aggregate
rule: self.type != 'Aggregate' || !has(self.vrfRef)
- message: vrfRef must not be specified for Physical interfaces with switchport
Expand Down
1 change: 1 addition & 0 deletions config/samples/v1alpha1_interface.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ spec:
ipv4:
addresses:
- 192.168.10.254/24
anycastGateway: true
---
apiVersion: networking.metal.ironcore.dev/v1alpha1
kind: Interface
Expand Down
7 changes: 7 additions & 0 deletions internal/controller/core/interface_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ var _ = Describe("Interface Controller", func() {
By("Cleaning up the test Device resource")
Expect(k8sClient.Delete(ctx, device, client.PropagationPolicy(metav1.DeletePropagationForeground))).To(Succeed())

By("Verifying all Interfaces are deleted")
Eventually(func(g Gomega) {
intfList := &v1alpha1.InterfaceList{}
g.Expect(k8sClient.List(ctx, intfList, client.InNamespace(metav1.NamespaceDefault))).To(Succeed())
g.Expect(intfList.Items).To(BeEmpty())
}).Should(Succeed())

By("Verifying the Interface is removed from the provider")
Eventually(func(g Gomega) {
g.Expect(testProvider.Ports.Has(name)).To(BeFalse(), "Provider shouldn't have Interface configured anymore")
Expand Down
7 changes: 7 additions & 0 deletions internal/provider/cisco/nxos/intf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,11 @@ func init() {
VlanID: 10,
}
Register("svi", svi)

fwif := &FabricFwdIf{
AdminSt: AdminStEnabled,
ID: "vlan10",
Mode: FwdModeAnycastGateway,
}
Register("fwif", fwif)
}
14 changes: 14 additions & 0 deletions internal/provider/cisco/nxos/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,20 @@ func (p *Provider) EnsureInterface(ctx context.Context, req *provider.EnsureInte
svi.VlanID = req.VLAN.Spec.ID
svi.RtvrfMbrItems = NewVrfMember(name, vrf)

fwif := new(FabricFwdIf)
fwif.ID = name

switch {
case req.Interface.Spec.IPv4 != nil && req.Interface.Spec.IPv4.AnycastGateway:
fwif.AdminSt = AdminStEnabled
fwif.Mode = FwdModeAnycastGateway
conf = append(conf, fwif)
default:
if err := p.client.Delete(ctx, fwif); err != nil {
return err
}
}

conf = append(conf, svi)

default:
Expand Down
15 changes: 15 additions & 0 deletions internal/provider/cisco/nxos/testdata/fwif.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"hmm-items": {
"fwdinst-items": {
"if-items": {
"FwdIf-list": [
{
"adminSt": "enabled",
"id": "vlan10",
"mode": "anycastGW"
}
]
}
}
}
}
2 changes: 2 additions & 0 deletions internal/provider/cisco/nxos/testdata/fwif.json.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
interface Vlan10
fabric forwarding mode anycast-gateway
Loading