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
9 changes: 9 additions & 0 deletions PROJECT
Original file line number Diff line number Diff line change
Expand Up @@ -352,4 +352,13 @@ resources:
kind: IPPrefix
path: github.com/ironcore-dev/network-operator/api/pool/v1alpha1
version: v1alpha1
- api:
crdVersion: v1
namespaced: true
controller: true
domain: networking.metal.ironcore.dev
group: evpn
kind: Fabric
path: github.com/ironcore-dev/network-operator/api/evpn/v1alpha1
version: v1alpha1
version: "3"
3 changes: 3 additions & 0 deletions Tiltfile
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@ k8s_resource(new_name='claim-index', objects=['claim-index:claim'], resource_dep
k8s_resource(new_name='claim-ipaddress', objects=['claim-ipaddress:claim'], resource_deps=['ipaddresspool'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
k8s_resource(new_name='claim-prefix', objects=['claim-prefix:claim'], resource_deps=['ipprefixpool'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)

k8s_yaml('./config/samples/v1alpha1_fabric.yaml')
k8s_resource(new_name='fabric', objects=['fabric:fabric', 'loopback-pool:ipaddresspool', 'underlay-p2p-pool:ipprefixpool'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)

print('🚀 network-operator development environment')
print('👉 Edit the code inside the api/, cmd/, or internal/ directories')
print('👉 Tilt will automatically rebuild and redeploy when changes are detected')
Expand Down
8 changes: 8 additions & 0 deletions api/evpn/v1alpha1/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors
// SPDX-License-Identifier: Apache-2.0

// Package v1alpha1 contains API Schema definitions for the evpn.networking.metal.ironcore.dev v1alpha1 API group.
// +kubebuilder:validation:Required
// +kubebuilder:object:generate=true
// +groupName=evpn.networking.metal.ironcore.dev
package v1alpha1
296 changes: 296 additions & 0 deletions api/evpn/v1alpha1/fabric_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,296 @@
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors
// SPDX-License-Identifier: Apache-2.0

package v1alpha1

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"

corev1alpha1 "github.com/ironcore-dev/network-operator/api/core/v1alpha1"
)

// FabricSpec defines the desired state of Fabric.
type FabricSpec struct {
// deviceSelector identifies which devices are members of this fabric.
// All devices whose labels match this selector will be enrolled.
// +required
DeviceSelector metav1.LabelSelector `json:"deviceSelector"`

// loopbacks configures IP address allocation for loopback interfaces on
// fabric devices.
// +required
Loopbacks FabricLoopbacksSpec `json:"loopbacks"`

// underlay defines the underlay routing configuration for the fabric.
// +required
Underlay FabricUnderlaySpec `json:"underlay"`

// overlay defines the overlay control-plane configuration for the fabric.
// +required
Overlay FabricOverlaySpec `json:"overlay"`

// bum defines how BUM traffic is forwarded across the fabric.
// +required
BUM FabricBUMSpec `json:"bum"`

// vtep identifies the VTEP devices and configures their anycast gateway.
// +required
VTEP FabricVTEPSpec `json:"vtep"`
}

// FabricLoopbacksSpec configures IP address allocation for loopback interfaces.
type FabricLoopbacksSpec struct {
// ipAddressPoolRef references the IPAddressPool from which loopback addresses
// are allocated for devices in the fabric.
// +required
IPAddressPoolRef corev1alpha1.LocalObjectReference `json:"ipAddressPoolRef"`
}

// FabricUnderlaySpec defines the underlay network configuration.
type FabricUnderlaySpec struct {
// protocol is the routing protocol used to build IP reachability across the
// fabric underlay.
// +required
Protocol UnderlayProtocol `json:"protocol"`

// interfaceSelector identifies which interfaces participate in the underlay.
// Interfaces on fabric devices matching these labels will be enrolled in the
// underlay routing process.
// +required
InterfaceSelector metav1.LabelSelector `json:"interfaceSelector"`

// addressing configures how IP addresses are assigned to underlay interfaces.
// +required
Addressing FabricUnderlayAddressingSpec `json:"addressing"`
}

// UnderlayProtocol is the routing protocol used for the underlay network.
// +kubebuilder:validation:Enum=OSPF;ISIS
type UnderlayProtocol string

const (
// UnderlayProtocolOSPF uses OSPF for underlay routing.
UnderlayProtocolOSPF UnderlayProtocol = "OSPF"
// UnderlayProtocolISIS uses IS-IS for underlay routing.
UnderlayProtocolISIS UnderlayProtocol = "ISIS"
)

// FabricUnderlayAddressingSpec configures how IP addresses are assigned to
// underlay point-to-point links.
// +kubebuilder:validation:XValidation:rule="has(self.ipPrefixPoolRef) != self.unnumbered",message="exactly one of ipPrefixPoolRef or unnumbered must be set"
type FabricUnderlayAddressingSpec struct {
// ipPrefixPoolRef references the IPPrefixPool from which point-to-point
// prefixes are allocated for underlay interfaces.
// +optional
IPPrefixPoolRef *corev1alpha1.LocalObjectReference `json:"ipPrefixPoolRef,omitempty"`

// unnumbered controls whether underlay interfaces use unnumbered addressing
// (borrowing from loopback0) instead of dedicated point-to-point addresses.
// +optional
// +kubebuilder:default=false
Unnumbered bool `json:"unnumbered,omitempty"`
}

// OverlayProtocol is the control-plane protocol used for the overlay network.
// +kubebuilder:validation:Enum=IBGP
type OverlayProtocol string

const (
// OverlayProtocolIBGP uses iBGP EVPN for the overlay control plane.
OverlayProtocolIBGP OverlayProtocol = "IBGP"
)

// FabricOverlaySpec defines the overlay control-plane configuration.
// +kubebuilder:validation:XValidation:rule="self.protocol != 'IBGP' || has(self.ibgp)",message="ibgp must be set when protocol is IBGP"
type FabricOverlaySpec struct {
// protocol is the control-plane protocol used for the EVPN overlay.
// +required
Protocol OverlayProtocol `json:"protocol"`

// ibgp configures the iBGP overlay when protocol is IBGP.
// +optional
IBGP *FabricIBGPSpec `json:"ibgp,omitempty"`
}

// FabricIBGPSpec configures the iBGP overlay control plane.
// +kubebuilder:validation:XValidation:rule="size(self.routeReflectors) > 0",message="at least one route reflector group must be specified"
type FabricIBGPSpec struct {
// asNumber is the BGP autonomous system number shared by all devices in the
// iBGP fabric. Supports both plain format (1-4294967295) and dotted notation
// (1-65535.0-65535) as per RFC 5396.
// +required
ASNumber intstr.IntOrString `json:"asNumber"`

// routeReflectors lists the route reflector groups that provide iBGP scalability.
// Each group designates a set of reflectors and their client devices.
// +required
// +listType=map
// +listMapKey=name
// +kubebuilder:validation:MinItems=1
RouteReflectors []RouteReflectorGroup `json:"routeReflectors"`
}

// RouteReflectorGroup defines a set of BGP route reflectors and their clients.
type RouteReflectorGroup struct {
// name is a unique identifier for this route reflector group within the fabric.
// +required
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=63
Name string `json:"name"`

// deviceSelector identifies which devices act as route reflectors in this group.
// +required
DeviceSelector metav1.LabelSelector `json:"deviceSelector"`

// clientDeviceSelector identifies the devices that are route reflector clients
// for this group.
// +required
ClientDeviceSelector metav1.LabelSelector `json:"clientDeviceSelector"`
}

// BUMType is the mechanism used to handle BUM (Broadcast, Unknown unicast,
// Multicast) traffic in the fabric.
// +kubebuilder:validation:Enum=Multicast
type BUMType string

const (
// BUMTypeMulticast uses PIM sparse mode for BUM traffic forwarding.
BUMTypeMulticast BUMType = "Multicast"
)

// FabricBUMSpec defines how BUM (Broadcast, Unknown unicast, Multicast) traffic
// is forwarded across the fabric.
// +kubebuilder:validation:XValidation:rule="self.type != 'Multicast' || has(self.pim)",message="pim must be set when type is Multicast"
type FabricBUMSpec struct {
// type selects the BUM forwarding mechanism.
// +required
Type BUMType `json:"type"`

// pim configures PIM sparse mode when type is Multicast.
// +optional
PIM *FabricPIMSpec `json:"pim,omitempty"`
}

// FabricPIMSpec configures PIM sparse mode for BUM traffic.
type FabricPIMSpec struct {
// anycastRendezvousPoints lists the anycast rendezvous point groups used for
// PIM sparse mode. Anycast RPs share the same IP address across multiple
// devices for redundancy.
// +required
// +listType=map
// +listMapKey=name
// +kubebuilder:validation:MinItems=1
AnycastRendezvousPoints []AnycastRendezvousPoint `json:"anycastRendezvousPoints"`
}

// AnycastRendezvousPoint defines an anycast PIM rendezvous point group.
type AnycastRendezvousPoint struct {
// name is a unique identifier for this rendezvous point group within the fabric.
// +required
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=63
Name string `json:"name"`

// multicastGroups lists the multicast group address ranges served by this
// rendezvous point. Each entry must be a valid IPv4 multicast CIDR prefix.
// +required
// +kubebuilder:validation:MinItems=1
MulticastGroups []corev1alpha1.IPPrefix `json:"multicastGroups"`

// deviceSelector identifies which devices are configured as rendezvous points
// in this group.
// +required
DeviceSelector metav1.LabelSelector `json:"deviceSelector"`

// clientDeviceSelector identifies the devices that register with the rendezvous
// points in this group.
// +required
ClientDeviceSelector metav1.LabelSelector `json:"clientDeviceSelector"`
}

// FabricVTEPSpec identifies which devices act as VXLAN Tunnel Endpoints (VTEPs)
// and optionally configures their shared anycast gateway.
type FabricVTEPSpec struct {
// deviceSelector identifies which devices are configured as VTEPs.
// +required
DeviceSelector metav1.LabelSelector `json:"deviceSelector"`

// anycastGateway configures the anycast gateway shared across all VTEP devices.
// +optional
AnycastGateway *FabricAnycastGatewaySpec `json:"anycastGateway,omitempty"`
}

// FabricAnycastGatewaySpec configures the anycast gateway on VTEP devices.
type FabricAnycastGatewaySpec struct {
// virtualMAC is the shared MAC address used by all anycast gateway instances
// across the fabric. Must be a valid IEEE 802 MAC address in colon-separated
// hexadecimal notation (e.g. f0:0c:c1:5c:00:00).
// +required
// +kubebuilder:validation:Pattern=`^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$`
VirtualMAC string `json:"virtualMAC"`
}

// FabricStatus defines the observed state of Fabric.
type FabricStatus struct {
// conditions represent the current state of the Fabric resource.
// Each condition has a unique type and reflects the status of a specific aspect of the resource.
// The status of each condition is one of True, False, or Unknown.
// +listType=map
// +listMapKey=type
// +patchStrategy=merge
// +patchMergeKey=type
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`
}

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:path=fabrics
// +kubebuilder:resource:singular=fabric
// +kubebuilder:printcolumn:name="Underlay",type=string,JSONPath=`.spec.underlay.protocol`
// +kubebuilder:printcolumn:name="Overlay",type=string,JSONPath=`.spec.overlay.protocol`
// +kubebuilder:printcolumn:name="AS Number",type=string,JSONPath=`.spec.overlay.ibgp.asNumber`
// +kubebuilder:printcolumn:name="BUM",type=string,JSONPath=`.spec.bum.type`,priority=1
// +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].status`
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"

// Fabric is the Schema for the fabrics API
type Fabric struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

// Specification of the desired state of the resource.
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
// +required
Spec FabricSpec `json:"spec"`

// Status of the resource. This is set and updated automatically.
// Read-only.
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
// +optional
Status FabricStatus `json:"status,omitzero"`
}

// GetConditions implements conditions.Getter.
func (f *Fabric) GetConditions() []metav1.Condition {
return f.Status.Conditions
}

// SetConditions implements conditions.Setter.
func (f *Fabric) SetConditions(conditions []metav1.Condition) {
f.Status.Conditions = conditions
}

// +kubebuilder:object:root=true

// FabricList contains a list of Fabric
type FabricList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitzero"`
Items []Fabric `json:"items"`
}

func init() {
SchemeBuilder.Register(&Fabric{}, &FabricList{})
}
25 changes: 25 additions & 0 deletions api/evpn/v1alpha1/groupversion_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors
// SPDX-License-Identifier: Apache-2.0

package v1alpha1

import (
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/scheme"
)

var (
// GroupVersion is group version used to register these objects.
GroupVersion = schema.GroupVersion{Group: "evpn.networking.metal.ironcore.dev", Version: "v1alpha1"}

// SchemeBuilder is used to add go types to the GroupVersionKind scheme.
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}

// AddToScheme adds the types in this group-version to the given scheme.
AddToScheme = SchemeBuilder.AddToScheme
)

const (
// FinalizerName is the identifier used by evpn controllers to perform cleanup before a resource is deleted.
FinalizerName = "evpn.networking.metal.ironcore.dev/finalizer"
)
Loading