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
1 change: 1 addition & 0 deletions Tiltfile
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ k8s_yaml('./config/samples/v1alpha1_bgppeer.yaml')
k8s_resource(new_name='peer-spine1', objects=['leaf1-spine1:bgppeer'], resource_deps=['bgp', 'lo0'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False, labels=['samples'])
k8s_resource(new_name='peer-spine2', objects=['leaf1-spine2:bgppeer'], resource_deps=['bgp', 'lo0'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False, labels=['samples'])
k8s_resource(new_name='peer-spine1-filtered', objects=['leaf1-spine1-filtered:bgppeer'], resource_deps=['bgp', 'lo0', 'bgp-import-policy'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False, labels=['samples'])
k8s_resource(new_name='peer-spine1-unnumbered', objects=['leaf1-spine1-unnumbered:bgppeer'], resource_deps=['bgp', 'eth1-4'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False, labels=['samples'])

k8s_yaml('./config/samples/v1alpha1_ospf.yaml')
k8s_resource(new_name='ospf-underlay', objects=['underlay:ospf'], resource_deps=['lo0', 'lo1', 'eth1-1', 'eth1-2'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False, labels=['samples'])
Expand Down
42 changes: 40 additions & 2 deletions api/core/v1alpha1/bgp_peer_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ import (
)

// BGPPeerSpec defines the desired state of BGPPeer
// +kubebuilder:validation:XValidation:rule="has(self.address) != has(self.interfaceRef)", message="exactly one of address or interfaceRef must be specified"
// +kubebuilder:validation:XValidation:rule="!has(self.interfaceRef) || !has(self.localAddress)", message="localAddress must not be specified for interface-based peers"
// +kubebuilder:validation:XValidation:rule="type(self.asNumber) != string || self.asNumber != 'external' || has(self.interfaceRef)", message="asNumber external requires interfaceRef"
// +kubebuilder:validation:XValidation:rule="(!has(self.address) && !has(oldSelf.address)) || (has(self.address) && has(oldSelf.address) && self.address == oldSelf.address)",message="Address is immutable"
// +kubebuilder:validation:XValidation:rule="(!has(self.interfaceRef) && !has(oldSelf.interfaceRef)) || (has(self.interfaceRef) && has(oldSelf.interfaceRef) && self.interfaceRef == oldSelf.interfaceRef)",message="InterfaceRef is immutable"
type BGPPeerSpec struct {
// DeviceName is the name of the Device this object belongs to. The Device object must exist in the same namespace.
// Immutable.
Expand All @@ -27,7 +32,9 @@ type BGPPeerSpec struct {

// BgpRef is a reference to the BGP instance this peer belongs to.
// The BGP object must exist in the same namespace.
// Immutable.
// +required
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="BgpRef is immutable"
BgpRef LocalObjectReference `json:"bgpRef"`

// AdminState indicates whether this BGP peer is administratively up or down.
Expand All @@ -37,12 +44,26 @@ type BGPPeerSpec struct {
AdminState AdminState `json:"adminState,omitempty"`

// Address is the IPv4 address of the BGP peer.
// +required
// Mutually exclusive with InterfaceRef: exactly one of both must be specified.
// Immutable.
// +optional
// +kubebuilder:validation:Format=ipv4
Address string `json:"address"`
Address string `json:"address,omitempty"`

// InterfaceRef is a reference to an Interface resource over which an unnumbered
// (interface-based) BGP session is established. The peers discover each other over
// their IPv6 link-local addresses, so the link needs no addressing of its own.
// The referenced Interface must belong to the same Device, exist in the same namespace,
// and be configured for link-local operation (spec.ipv6.useLinkLocalOnly).
// Mutually exclusive with Address: exactly one of both must be specified.
// Immutable.
// +optional
InterfaceRef *LocalObjectReference `json:"interfaceRef,omitempty"`

// ASNumber is the autonomous system number (ASN) of the BGP peer.
// Supports both plain format (1-4294967295) and dotted notation (0-65535.0-65535) as per RFC 5396.
// The special value "external" configures a dynamic AS number, accepting any AS number
// that differs from the local one. It is only valid together with InterfaceRef.
// +required
ASNumber intstr.IntOrString `json:"asNumber"`

Expand All @@ -66,6 +87,16 @@ type BGPPeerSpec struct {
LocalAS *LocalAS `json:"localAS,omitempty"`
}

// BGPPeerASNumberExternal is the value of BGPPeerSpec.ASNumber that requests a dynamic
// AS number for the peer. The session is established with any AS number that differs from
// the local one, which is the common setup for unnumbered eBGP peerings.
const BGPPeerASNumberExternal = "external"

// IsExternalASNumber reports whether the peer is configured with a dynamic AS number.
func (s *BGPPeerSpec) IsExternalASNumber() bool {
return s.ASNumber.Type == intstr.String && s.ASNumber.StrVal == BGPPeerASNumberExternal
}

// LocalAS defines the local AS configuration and how it factors in BGP announcements.
type LocalAS struct {
// ASNumber specifies a local AS number to present in BGP sessions with this peer.
Expand Down Expand Up @@ -178,6 +209,12 @@ type BGPPeerStatus struct {
// +patchMergeKey=afiSafi
AddressFamilies []AddressFamilyStatus `json:"addressFamilies,omitempty"`

// PeerInterface is the device-level name of the interface an unnumbered peer is
// configured over. It is recorded so that the peer can still be removed from the
// device after the referenced Interface has been deleted.
// +optional
PeerInterface string `json:"peerInterface,omitempty"`

// ObservedGeneration reflects the .metadata.generation that was last processed by the controller.
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
Expand Down Expand Up @@ -254,6 +291,7 @@ const (
// +kubebuilder:resource:singular=bgppeer
// +kubebuilder:resource:shortName=peer;bgpneighbor
// +kubebuilder:printcolumn:name="Peer Address",type=string,JSONPath=`.spec.address`
// +kubebuilder:printcolumn:name="Peer Interface",type=string,JSONPath=`.spec.interfaceRef.name`
// +kubebuilder:printcolumn:name="Device",type=string,JSONPath=`.spec.deviceRef.name`
// +kubebuilder:printcolumn:name="Admin State",type=string,JSONPath=`.spec.adminState`
// +kubebuilder:printcolumn:name="AS Number",type=string,JSONPath=`.spec.asNumber`
Expand Down
5 changes: 5 additions & 0 deletions api/core/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

56 changes: 54 additions & 2 deletions config/crd/bases/networking.metal.ironcore.dev_bgppeers.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions config/samples/v1alpha1_bgppeer.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,25 @@ spec:
name: bgp-import-policy
outboundRoutingPolicyRef:
name: bgp-import-policy
---
apiVersion: networking.metal.ironcore.dev/v1alpha1
kind: BGPPeer
metadata:
labels:
app.kubernetes.io/name: network-operator
app.kubernetes.io/managed-by: kustomize
name: leaf1-spine1-unnumbered
spec:
deviceRef:
name: leaf1
bgpRef:
name: bgp
# Unnumbered peering: the session runs over the interface's IPv6 link-local
# address, and "external" accepts any AS number that differs from the local one.
interfaceRef:
name: eth1-4
asNumber: external
description: Unnumbered eBGP to spine1
addressFamilies:
ipv4Unicast:
enabled: true
Loading