Skip to content

Commit afd17c8

Browse files
Add L3 port-channel support with member interface layer management
Enable IPv4, VRF membership, and BFD on Aggregate (port-channel) interfaces in the CRD API and NX-OS provider. Handle the member interface layer problem where NX-OS requires Physical members of an L3 port-channel to be Layer3 even without their own IP address. - Relax CEL validation to allow L3 config on Aggregate interfaces - Add AggregateParent to EnsureInterfaceRequest for member awareness - Set Layer3/VRF/medium on Aggregate when IPv4 is configured (NX-OS) - Set Layer3 on Physical members of L3 Aggregates (NX-OS) - Add aggregateToMembers watch with field-scoped update predicate - Add golden files, provider fixtures, and controller integration tests - Add L3 port-channel sample manifests and Tiltfile entries
1 parent 631c4be commit afd17c8

16 files changed

Lines changed: 488 additions & 89 deletions

File tree

Tiltfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ k8s_resource(new_name='eth1-1', objects=['eth1-1:interface'], trigger_mode=TRIGG
4848
k8s_resource(new_name='eth1-2', objects=['eth1-2:interface'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
4949
k8s_resource(new_name='eth1-10', objects=['eth1-10:interface'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
5050
k8s_resource(new_name='po10', objects=['po-10:interface'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
51+
k8s_resource(new_name='eth1-3', objects=['eth1-3:interface'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
52+
k8s_resource(new_name='po20', objects=['po-20:interface'], resource_deps=['eth1-3'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
5153
k8s_resource(new_name='svi-10', objects=['svi-10:interface'], resource_deps=['vlan-10'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
5254

5355
k8s_yaml('./config/samples/v1alpha1_banner.yaml')

api/core/v1alpha1/interface_types.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@ import (
1616
// +kubebuilder:validation:XValidation:rule="self.type == 'Physical' || !has(self.ipv4) || !has(self.ipv4.unnumbered)", message="unnumbered ipv4 configuration can only be used for interfaces of type Physical"
1717
// +kubebuilder:validation:XValidation:rule="self.type != 'Aggregate' || has(self.aggregation)", message="aggregation must be specified for interfaces of type Aggregate"
1818
// +kubebuilder:validation:XValidation:rule="self.type == 'Aggregate' || !has(self.aggregation)", message="aggregation must only be specified on interfaces of type Aggregate"
19-
// +kubebuilder:validation:XValidation:rule="self.type != 'Aggregate' || !has(self.ipv4)", message="ipv4 must not be specified for interfaces of type Aggregate"
19+
// +kubebuilder:validation:XValidation:rule="self.type != 'Aggregate' || !has(self.switchport) || !has(self.ipv4)", message="ipv4 must not be specified for Aggregate interfaces with switchport configuration"
2020
// +kubebuilder:validation:XValidation:rule="self.type != 'RoutedVLAN' || has(self.vlanRef)", message="vlanRef must be specified for interfaces of type RoutedVLAN"
2121
// +kubebuilder:validation:XValidation:rule="self.type == 'RoutedVLAN' || !has(self.vlanRef)", message="vlanRef must only be specified on interfaces of type RoutedVLAN"
2222
// +kubebuilder:validation:XValidation:rule="self.type != 'RoutedVLAN' || !has(self.switchport)", message="switchport must not be specified for interfaces of type RoutedVLAN"
2323
// +kubebuilder:validation:XValidation:rule="self.type != 'RoutedVLAN' || !has(self.aggregation)", message="aggregation must not be specified for interfaces of type RoutedVLAN"
2424
// +kubebuilder:validation:XValidation:rule="self.type == 'RoutedVLAN' || !has(self.ipv4) || !self.ipv4.anycastGateway", message="anycastGateway can only be enabled for interfaces of type RoutedVLAN"
25-
// +kubebuilder:validation:XValidation:rule="self.type != 'Aggregate' || !has(self.vrfRef)", message="vrfRef must not be specified for interfaces of type Aggregate"
25+
// +kubebuilder:validation:XValidation:rule="self.type != 'Aggregate' || !has(self.switchport) || !has(self.vrfRef)", message="vrfRef must not be specified for Aggregate interfaces with switchport configuration"
2626
// +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"
27-
// +kubebuilder:validation:XValidation:rule="self.type != 'Aggregate' || !has(self.bfd)", message="bfd must not be specified for interfaces of type Aggregate"
27+
// +kubebuilder:validation:XValidation:rule="self.type != 'Aggregate' || !has(self.switchport) || !has(self.bfd)", message="bfd must not be specified for Aggregate interfaces with switchport configuration"
2828
// +kubebuilder:validation:XValidation:rule="!has(self.bfd) || !has(self.switchport)", message="bfd must not be specified for interfaces with switchport configuration"
2929
// +kubebuilder:validation:XValidation:rule="self.type == 'Physical' || !has(self.ethernet)", message="ethernet configuration must only be specified on interfaces of type Physical"
3030
type InterfaceSpec struct {
@@ -95,7 +95,7 @@ type InterfaceSpec struct {
9595
VrfRef *LocalObjectReference `json:"vrfRef,omitempty"`
9696

9797
// BFD defines the Bidirectional Forwarding Detection configuration for the interface.
98-
// BFD is only applicable for Layer 3 interfaces (Physical, Loopback, RoutedVLAN).
98+
// BFD is only applicable for Layer 3 interfaces.
9999
// +optional
100100
BFD *BFD `json:"bfd,omitempty"`
101101

api/core/v1alpha1/prefix_types.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ package v1alpha1
55
import (
66
"encoding/json"
77
"net/netip"
8+
9+
"k8s.io/apimachinery/pkg/api/equality"
810
)
911

1012
// IPPrefix represents an IP prefix in CIDR notation.
@@ -37,6 +39,12 @@ func (p IPPrefix) IsZero() bool {
3739
return !p.IsValid()
3840
}
3941

42+
// Equal reports whether p and q are the same prefix.
43+
// This method exists as a convenience for callers that need a direct comparison.
44+
func (p IPPrefix) Equal(q IPPrefix) bool {
45+
return p.Prefix == q.Prefix
46+
}
47+
4048
// MarshalJSON implements [json.Marshaler].
4149
func (p IPPrefix) MarshalJSON() ([]byte, error) {
4250
if !p.IsValid() {
@@ -63,6 +71,19 @@ func (p *IPPrefix) UnmarshalJSON(data []byte) error {
6371
return nil
6472
}
6573

74+
// IsPointToPoint reports whether the prefix indicates a point-to-point link.
75+
// For IPv4, this means a /31 subnet mask as defined in [RFC 3021].
76+
// For IPv6, this means a /127 subnet mask as defined in [RFC 6164].
77+
//
78+
// [RFC 3021]: https://datatracker.ietf.org/doc/html/rfc3021
79+
// [RFC 6164]: https://datatracker.ietf.org/doc/html/rfc6164
80+
func (p IPPrefix) IsPointToPoint() bool {
81+
if p.Addr().Is4() {
82+
return p.Bits() == 31
83+
}
84+
return p.Bits() == 127
85+
}
86+
6687
// DeepCopyInto copies all properties of this object into another object of the same type
6788
func (in *IPPrefix) DeepCopyInto(out *IPPrefix) {
6889
*out = *in
@@ -77,3 +98,15 @@ func (in *IPPrefix) DeepCopy() *IPPrefix {
7798
in.DeepCopyInto(out)
7899
return out
79100
}
101+
102+
func init() {
103+
// IPPrefix embeds [netip.Prefix] which contains unexported fields.
104+
// [equality.Semantic.DeepEqual] panics on unexported fields, so an
105+
// explicit equality function is registered in this package's init to
106+
// make any type containing IPPrefix safe to compare.
107+
if err := equality.Semantic.AddFunc(func(a, b IPPrefix) bool {
108+
return a.Equal(b)
109+
}); err != nil {
110+
panic(err)
111+
}
112+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package v1alpha1
5+
6+
import "testing"
7+
8+
func TestIPPrefix_IsPointToPoint(t *testing.T) {
9+
tests := []struct {
10+
name string
11+
prefix string
12+
want bool
13+
}{
14+
{name: "IPv4 /31 is p2p", prefix: "10.0.0.0/31", want: true},
15+
{name: "IPv4 /32 is not p2p", prefix: "10.0.0.1/32", want: false},
16+
{name: "IPv4 /30 is not p2p", prefix: "10.0.0.0/30", want: false},
17+
{name: "IPv4 /24 is not p2p", prefix: "192.168.1.0/24", want: false},
18+
{name: "IPv6 /127 is p2p", prefix: "2001:db8::/127", want: true},
19+
{name: "IPv6 /128 is not p2p", prefix: "2001:db8::1/128", want: false},
20+
{name: "IPv6 /126 is not p2p", prefix: "2001:db8::/126", want: false},
21+
{name: "IPv6 /64 is not p2p", prefix: "2001:db8::/64", want: false},
22+
}
23+
for _, tt := range tests {
24+
t.Run(tt.name, func(t *testing.T) {
25+
p := MustParsePrefix(tt.prefix)
26+
if got := p.IsPointToPoint(); got != tt.want {
27+
t.Errorf("IPPrefix(%q).IsPointToPoint() = %v, want %v", tt.prefix, got, tt.want)
28+
}
29+
})
30+
}
31+
}

charts/network-operator/templates/crd/interfaces.networking.metal.ironcore.dev.yaml

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ spec:
160160
bfd:
161161
description: |-
162162
BFD defines the Bidirectional Forwarding Detection configuration for the interface.
163-
BFD is only applicable for Layer 3 interfaces (Physical, Loopback, RoutedVLAN).
163+
BFD is only applicable for Layer 3 interfaces.
164164
properties:
165165
desiredMinimumTxInterval:
166166
description: |-
@@ -444,8 +444,9 @@ spec:
444444
rule: self.type != 'Aggregate' || has(self.aggregation)
445445
- message: aggregation must only be specified on interfaces of type Aggregate
446446
rule: self.type == 'Aggregate' || !has(self.aggregation)
447-
- message: ipv4 must not be specified for interfaces of type Aggregate
448-
rule: self.type != 'Aggregate' || !has(self.ipv4)
447+
- message: ipv4 must not be specified for Aggregate interfaces with switchport
448+
configuration
449+
rule: self.type != 'Aggregate' || !has(self.switchport) || !has(self.ipv4)
449450
- message: vlanRef must be specified for interfaces of type RoutedVLAN
450451
rule: self.type != 'RoutedVLAN' || has(self.vlanRef)
451452
- message: vlanRef must only be specified on interfaces of type RoutedVLAN
@@ -456,13 +457,15 @@ spec:
456457
rule: self.type != 'RoutedVLAN' || !has(self.aggregation)
457458
- message: anycastGateway can only be enabled for interfaces of type RoutedVLAN
458459
rule: self.type == 'RoutedVLAN' || !has(self.ipv4) || !self.ipv4.anycastGateway
459-
- message: vrfRef must not be specified for interfaces of type Aggregate
460-
rule: self.type != 'Aggregate' || !has(self.vrfRef)
460+
- message: vrfRef must not be specified for Aggregate interfaces with
461+
switchport configuration
462+
rule: self.type != 'Aggregate' || !has(self.switchport) || !has(self.vrfRef)
461463
- message: vrfRef must not be specified for Physical interfaces with switchport
462464
configuration
463465
rule: self.type != 'Physical' || !has(self.switchport) || !has(self.vrfRef)
464-
- message: bfd must not be specified for interfaces of type Aggregate
465-
rule: self.type != 'Aggregate' || !has(self.bfd)
466+
- message: bfd must not be specified for Aggregate interfaces with switchport
467+
configuration
468+
rule: self.type != 'Aggregate' || !has(self.switchport) || !has(self.bfd)
466469
- message: bfd must not be specified for interfaces with switchport configuration
467470
rule: '!has(self.bfd) || !has(self.switchport)'
468471
- message: ethernet configuration must only be specified on interfaces

config/crd/bases/networking.metal.ironcore.dev_interfaces.yaml

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ spec:
157157
bfd:
158158
description: |-
159159
BFD defines the Bidirectional Forwarding Detection configuration for the interface.
160-
BFD is only applicable for Layer 3 interfaces (Physical, Loopback, RoutedVLAN).
160+
BFD is only applicable for Layer 3 interfaces.
161161
properties:
162162
desiredMinimumTxInterval:
163163
description: |-
@@ -441,8 +441,9 @@ spec:
441441
rule: self.type != 'Aggregate' || has(self.aggregation)
442442
- message: aggregation must only be specified on interfaces of type Aggregate
443443
rule: self.type == 'Aggregate' || !has(self.aggregation)
444-
- message: ipv4 must not be specified for interfaces of type Aggregate
445-
rule: self.type != 'Aggregate' || !has(self.ipv4)
444+
- message: ipv4 must not be specified for Aggregate interfaces with switchport
445+
configuration
446+
rule: self.type != 'Aggregate' || !has(self.switchport) || !has(self.ipv4)
446447
- message: vlanRef must be specified for interfaces of type RoutedVLAN
447448
rule: self.type != 'RoutedVLAN' || has(self.vlanRef)
448449
- message: vlanRef must only be specified on interfaces of type RoutedVLAN
@@ -453,13 +454,15 @@ spec:
453454
rule: self.type != 'RoutedVLAN' || !has(self.aggregation)
454455
- message: anycastGateway can only be enabled for interfaces of type RoutedVLAN
455456
rule: self.type == 'RoutedVLAN' || !has(self.ipv4) || !self.ipv4.anycastGateway
456-
- message: vrfRef must not be specified for interfaces of type Aggregate
457-
rule: self.type != 'Aggregate' || !has(self.vrfRef)
457+
- message: vrfRef must not be specified for Aggregate interfaces with
458+
switchport configuration
459+
rule: self.type != 'Aggregate' || !has(self.switchport) || !has(self.vrfRef)
458460
- message: vrfRef must not be specified for Physical interfaces with switchport
459461
configuration
460462
rule: self.type != 'Physical' || !has(self.switchport) || !has(self.vrfRef)
461-
- message: bfd must not be specified for interfaces of type Aggregate
462-
rule: self.type != 'Aggregate' || !has(self.bfd)
463+
- message: bfd must not be specified for Aggregate interfaces with switchport
464+
configuration
465+
rule: self.type != 'Aggregate' || !has(self.switchport) || !has(self.bfd)
463466
- message: bfd must not be specified for interfaces with switchport configuration
464467
rule: '!has(self.bfd) || !has(self.switchport)'
465468
- message: ethernet configuration must only be specified on interfaces

config/samples/v1alpha1_interface.yaml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,53 @@ spec:
147147
---
148148
apiVersion: networking.metal.ironcore.dev/v1alpha1
149149
kind: Interface
150+
metadata:
151+
labels:
152+
app.kubernetes.io/name: network-operator
153+
app.kubernetes.io/managed-by: kustomize
154+
networking.metal.ironcore.dev/device-name: leaf1
155+
name: eth1-3
156+
spec:
157+
deviceRef:
158+
name: leaf1
159+
name: eth1/3
160+
description: L3 Port-Channel Member
161+
adminState: Up
162+
type: Physical
163+
mtu: 9216
164+
---
165+
apiVersion: networking.metal.ironcore.dev/v1alpha1
166+
kind: Interface
167+
metadata:
168+
labels:
169+
app.kubernetes.io/name: network-operator
170+
app.kubernetes.io/managed-by: kustomize
171+
networking.metal.ironcore.dev/device-name: leaf1
172+
name: po-20
173+
spec:
174+
deviceRef:
175+
name: leaf1
176+
name: po20
177+
description: L3 Port-Channel
178+
adminState: Up
179+
type: Aggregate
180+
mtu: 9216
181+
ipv4:
182+
addresses:
183+
- 10.0.100.0/31
184+
bfd:
185+
enabled: true
186+
desiredMinimumTxInterval: 300ms
187+
requiredMinimumReceive: 300ms
188+
detectionMultiplier: 3
189+
aggregation:
190+
controlProtocol:
191+
mode: Active
192+
memberInterfaceRefs:
193+
- name: eth1-3
194+
---
195+
apiVersion: networking.metal.ironcore.dev/v1alpha1
196+
kind: Interface
150197
metadata:
151198
labels:
152199
app.kubernetes.io/name: network-operator

docs/api-reference/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1350,7 +1350,7 @@ _Appears in:_
13501350
| `aggregation` _[Aggregation](#aggregation)_ | Aggregation defines the aggregation (bundle) configuration for the interface.<br />This is only applicable for interfaces of type Aggregate. | | Optional: \{\} <br /> |
13511351
| `vlanRef` _[LocalObjectReference](#localobjectreference)_ | VlanRef is a reference to the VLAN resource that this interface provides routing for.<br />This is only applicable for interfaces of type RoutedVLAN.<br />The referenced VLAN must exist in the same namespace. | | Optional: \{\} <br /> |
13521352
| `vrfRef` _[LocalObjectReference](#localobjectreference)_ | VrfRef is a reference to the VRF resource that this interface belongs to.<br />If not specified, the interface will be part of the default VRF.<br />This is only applicable for Layer 3 interfaces.<br />The referenced VRF must exist in the same namespace. | | Optional: \{\} <br /> |
1353-
| `bfd` _[BFD](#bfd)_ | BFD defines the Bidirectional Forwarding Detection configuration for the interface.<br />BFD is only applicable for Layer 3 interfaces (Physical, Loopback, RoutedVLAN). | | Optional: \{\} <br /> |
1353+
| `bfd` _[BFD](#bfd)_ | BFD defines the Bidirectional Forwarding Detection configuration for the interface.<br />BFD is only applicable for Layer 3 interfaces. | | Optional: \{\} <br /> |
13541354
| `ethernet` _[Ethernet](#ethernet)_ | Ethernet defines the ethernet-specific configuration for physical interfaces.<br />This configuration is only applicable to Physical interfaces.<br />When omitted, ethernet parameters use their default values (e.g., FEC mode defaults to auto). | | Optional: \{\} <br /> |
13551355

13561356

internal/controller/core/interface_controller.go

Lines changed: 75 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,33 @@ func (r *InterfaceReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Man
315315
},
316316
}),
317317
).
318+
// Watches enqueues member Physical Interfaces when their parent Aggregate changes.
319+
// Only triggers when Aggregate Spec fields change that affect member reconciliation.
320+
Watches(
321+
&v1alpha1.Interface{},
322+
handler.EnqueueRequestsFromMapFunc(r.aggregateToMembers),
323+
builder.WithPredicates(predicate.Funcs{
324+
CreateFunc: func(e event.CreateEvent) bool {
325+
return false
326+
},
327+
UpdateFunc: func(e event.UpdateEvent) bool {
328+
oldIntf := e.ObjectOld.(*v1alpha1.Interface)
329+
newIntf := e.ObjectNew.(*v1alpha1.Interface)
330+
// Only trigger when fields that affect member Physical interface
331+
// reconciliation change (e.g. layer, VRF membership, MTU).
332+
return !equality.Semantic.DeepEqual(oldIntf.Spec.IPv4, newIntf.Spec.IPv4) ||
333+
!equality.Semantic.DeepEqual(oldIntf.Spec.Switchport, newIntf.Spec.Switchport) ||
334+
!equality.Semantic.DeepEqual(oldIntf.Spec.VrfRef, newIntf.Spec.VrfRef) ||
335+
oldIntf.Spec.MTU != newIntf.Spec.MTU
336+
},
337+
DeleteFunc: func(e event.DeleteEvent) bool {
338+
return false
339+
},
340+
GenericFunc: func(e event.GenericEvent) bool {
341+
return false
342+
},
343+
}),
344+
).
318345
// Watches enqueues RoutedVLAN Interfaces for updates in referenced VLAN resources.
319346
// Only triggers on create and delete events since VLAN IDs are immutable.
320347
Watches(
@@ -399,6 +426,18 @@ func (r *InterfaceReconciler) reconcile(ctx context.Context, s *scope) (_ ctrl.R
399426
}
400427
}
401428

429+
var aggregateParent *v1alpha1.Interface
430+
if s.Interface.Spec.Type == v1alpha1.InterfaceTypePhysical && s.Interface.Status.MemberOf != nil {
431+
aggregateParent = new(v1alpha1.Interface)
432+
key := client.ObjectKey{Name: s.Interface.Status.MemberOf.Name, Namespace: s.Interface.Namespace}
433+
if err := r.Get(ctx, key, aggregateParent); err != nil {
434+
if !apierrors.IsNotFound(err) {
435+
return ctrl.Result{}, fmt.Errorf("failed to get aggregate parent %q: %w", s.Interface.Status.MemberOf.Name, err)
436+
}
437+
aggregateParent = nil
438+
}
439+
}
440+
402441
var multiChassisID *int16
403442
if s.Interface.Spec.Aggregation != nil && s.Interface.Spec.Aggregation.MultiChassis != nil {
404443
multiChassisID = &s.Interface.Spec.Aggregation.MultiChassis.ID
@@ -442,13 +481,14 @@ func (r *InterfaceReconciler) reconcile(ctx context.Context, s *scope) (_ ctrl.R
442481

443482
// Ensure the Interface is realized on the provider.
444483
err := s.Provider.EnsureInterface(ctx, &provider.EnsureInterfaceRequest{
445-
Interface: s.Interface,
446-
ProviderConfig: s.ProviderConfig,
447-
IPv4: ip,
448-
Members: members,
449-
MultiChassisID: multiChassisID,
450-
VLAN: vlan,
451-
VRF: vrf,
484+
Interface: s.Interface,
485+
ProviderConfig: s.ProviderConfig,
486+
IPv4: ip,
487+
Members: members,
488+
MultiChassisID: multiChassisID,
489+
AggregateParent: aggregateParent,
490+
VLAN: vlan,
491+
VRF: vrf,
452492
})
453493

454494
cond := conditions.FromError(err)
@@ -873,6 +913,34 @@ func (r *InterfaceReconciler) interfaceToAggregate(ctx context.Context, obj clie
873913
return requests
874914
}
875915

916+
// aggregateToMembers is a [handler.MapFunc] to be used to enqueue requests for reconciliation
917+
// for member Physical Interfaces when their parent Aggregate Interface gets updated.
918+
func (r *InterfaceReconciler) aggregateToMembers(ctx context.Context, obj client.Object) []ctrl.Request {
919+
intf, ok := obj.(*v1alpha1.Interface)
920+
if !ok {
921+
panic(fmt.Sprintf("Expected a Interface but got a %T", obj))
922+
}
923+
924+
if intf.Spec.Type != v1alpha1.InterfaceTypeAggregate {
925+
return nil
926+
}
927+
928+
log := ctrl.LoggerFrom(ctx, "Aggregate", klog.KObj(intf))
929+
930+
requests := make([]ctrl.Request, 0, len(intf.Spec.Aggregation.MemberInterfaceRefs))
931+
for _, ref := range intf.Spec.Aggregation.MemberInterfaceRefs {
932+
log.Info("Enqueuing member Interface for reconciliation", "Member", ref.Name)
933+
requests = append(requests, ctrl.Request{
934+
NamespacedName: client.ObjectKey{
935+
Name: ref.Name,
936+
Namespace: intf.Namespace,
937+
},
938+
})
939+
}
940+
941+
return requests
942+
}
943+
876944
// vlanToRoutedVLAN is a [handler.MapFunc] to be used to enqueue requests for reconciliation
877945
// for a RoutedVLAN Interface when its referenced VLAN changes.
878946
func (r *InterfaceReconciler) vlanToRoutedVLAN(ctx context.Context, obj client.Object) []ctrl.Request {

0 commit comments

Comments
 (0)