Skip to content

Commit fdf5acc

Browse files
committed
Add provider implementation for NXOS
1 parent 4de54ce commit fdf5acc

14 files changed

Lines changed: 239 additions & 64 deletions

File tree

api/cisco/nx/v1alpha1/lldpconfig_types.go

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,36 +18,34 @@ type LLDPConfigSpec struct {
1818
// +kubebuilder:validation:Minimum=1
1919
// +kubebuilder:validation:Maximum=10
2020
// +kubebuilder:default=2
21-
InitDelay *int16 `json:"initDelay,omitempty"`
21+
InitDelay int16 `json:"initDelay,omitempty"`
2222

2323
// HoldTime defines the time in seconds that the receiving device should hold the LLDP information before discarding it.
2424
// +optional
2525
// +kubebuilder:validation:Minimum=1
2626
// +kubebuilder:validation:Maximum=255
2727
// +kubebuilder:default=120
28-
HoldTime *int16 `json:"holdTime,omitempty"`
28+
HoldTime int16 `json:"holdTime,omitempty"`
2929

30-
// Interfaces defines the LLDP configuration per interface.
30+
// InterfaceConfigurations defines the LLDP configuration per interface.
3131
// +optional
32-
Interfaces []LLDPInterfaceConfig `json:"interfaces,omitempty"`
32+
InterfaceConfigurations []LLDPInterfaceConfig `json:"interfaceConfigurations,omitempty"`
3333
}
3434

3535
type LLDPInterfaceConfig struct {
3636
// InterfaceRef references the interface to configure LLDP on.
3737
// +required
3838
InterfaceRef v1alpha1.LocalObjectReference `json:"interfaceRef"`
3939

40-
// AdminSt defines the administrative state of LLDP on the interface
41-
// +required
42-
AdminSt v1alpha1.AdminState `json:"adminSt"`
43-
4440
// AdminRxState defines the administrative state for receiving LLDP PDUs on the interface.
4541
// +optional
46-
AdminRxState *v1alpha1.AdminState `json:"adminRxState,omitempty"`
42+
// +kubebuilder:default=Up
43+
AdminRxState v1alpha1.AdminState `json:"adminRxState,omitempty"`
4744

4845
// AdminTxState defines the administrative state for transmitting LLDP PDUs on the interface.
4946
// +optional
50-
AdminTxState *v1alpha1.AdminState `json:"adminTxState,omitempty"`
47+
// +kubebuilder:default=Up
48+
AdminTxState v1alpha1.AdminState `json:"adminTxState,omitempty"`
5149
}
5250

5351
// +kubebuilder:object:root=true

api/cisco/nx/v1alpha1/zz_generated.deepcopy.go

Lines changed: 3 additions & 25 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/core/v1alpha1/lldp_types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ type LLDPStatus struct {
5353
// +kubebuilder:resource:path=lldps
5454
// +kubebuilder:resource:singular=lldp
5555
// +kubebuilder:printcolumn:name="Device",type=string,JSONPath=`.spec.deviceRef.name`
56+
// +kubebuilder:printcolumn:name="Admin State",type=string,JSONPath=`.spec.adminState`
5657
// +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].status`
5758
// +kubebuilder:printcolumn:name="Configured",type=string,JSONPath=`.status.conditions[?(@.type=="Configured")].status`,priority=1
5859
// +kubebuilder:printcolumn:name="Operational",type=string,JSONPath=`.status.conditions[?(@.type=="Operational")].status`,priority=1

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ spec:
1818
- jsonPath: .spec.deviceRef.name
1919
name: Device
2020
type: string
21+
- jsonPath: .spec.adminState
22+
name: Admin State
23+
type: string
2124
- jsonPath: .status.conditions[?(@.type=="Ready")].status
2225
name: Ready
2326
type: string

config/crd/bases/nx.cisco.networking.metal.ironcore.dev_lldpconfigs.yaml

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -55,25 +55,21 @@ spec:
5555
maximum: 10
5656
minimum: 1
5757
type: integer
58-
interfaces:
59-
description: Interfaces defines the LLDP configuration per interface.
58+
interfaceConfigurations:
59+
description: InterfaceConfigurations defines the LLDP configuration
60+
per interface.
6061
items:
6162
properties:
6263
adminRxState:
64+
default: Up
6365
description: AdminRxState defines the administrative state for
6466
receiving LLDP PDUs on the interface.
6567
enum:
6668
- Up
6769
- Down
6870
type: string
69-
adminSt:
70-
description: AdminSt defines the administrative state of LLDP
71-
on the interface
72-
enum:
73-
- Up
74-
- Down
75-
type: string
7671
adminTxState:
72+
default: Up
7773
description: AdminTxState defines the administrative state for
7874
transmitting LLDP PDUs on the interface.
7975
enum:
@@ -96,7 +92,6 @@ spec:
9692
type: object
9793
x-kubernetes-map-type: atomic
9894
required:
99-
- adminSt
10095
- interfaceRef
10196
type: object
10297
type: array

config/samples/cisco/nx/v1alpha1_lldpconfig.yaml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@ metadata:
66
app.kubernetes.io/managed-by: kustomize
77
name: leaf1-lldpconfig
88
spec:
9-
initDelay: 5
9+
initDelay: 10
1010
holdTime: 120
11-
interfaceRef:
12-
- name: leaf1-physif-eth1/1
11+
interfaceConfigurations:
12+
- interfaceRef:
13+
name: eth1-1
14+
- interfaceRef:
15+
name: eth1-2
16+
adminRxState: Down
17+
adminTxState: Down

internal/controller/core/lldp_controller.go

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"sigs.k8s.io/controller-runtime/pkg/predicate"
3030
"sigs.k8s.io/controller-runtime/pkg/reconcile"
3131

32+
nxv1alpha1 "github.com/ironcore-dev/network-operator/api/cisco/nx/v1alpha1"
3233
"github.com/ironcore-dev/network-operator/api/core/v1alpha1"
3334
"github.com/ironcore-dev/network-operator/internal/conditions"
3435
"github.com/ironcore-dev/network-operator/internal/deviceutil"
@@ -204,6 +205,9 @@ type lldpScope struct {
204205
Connection *deviceutil.Connection
205206
ProviderConfig *provider.ProviderConfig
206207
Provider provider.LLDPProvider
208+
// Interfaces maps k8s object names to Interface objects associated with the LLDP, required by
209+
// - cisco/nx
210+
Interfaces map[string]*v1alpha1.Interface
207211
}
208212

209213
func (r *LLDPReconciler) reconcile(ctx context.Context, s *lldpScope) (_ ctrl.Result, reterr error) {
@@ -244,6 +248,7 @@ func (r *LLDPReconciler) reconcile(ctx context.Context, s *lldpScope) (_ ctrl.Re
244248
err := s.Provider.EnsureLLDP(ctx, &provider.LLDPRequest{
245249
LLDP: s.LLDP,
246250
ProviderConfig: s.ProviderConfig,
251+
Interfaces: s.Interfaces,
247252
})
248253

249254
cond := conditions.FromError(err)
@@ -278,7 +283,7 @@ func (r *LLDPReconciler) reconcile(ctx context.Context, s *lldpScope) (_ ctrl.Re
278283
}
279284

280285
// validateProviderConfigRef checks if the referenced provider configuration is compatible with the target platform.
281-
func (r *LLDPReconciler) validateProviderConfigRef(_ context.Context, s *lldpScope) error {
286+
func (r *LLDPReconciler) validateProviderConfigRef(ctx context.Context, s *lldpScope) error {
282287
if s.LLDP.Spec.ProviderConfigRef == nil {
283288
return nil
284289
}
@@ -302,9 +307,26 @@ func (r *LLDPReconciler) validateProviderConfigRef(_ context.Context, s *lldpSco
302307
Type: v1alpha1.ConfiguredCondition,
303308
Status: metav1.ConditionFalse,
304309
Reason: v1alpha1.IncompatibleProviderConfigRef,
305-
Message: fmt.Sprintf("ProviderConfigRef is not compatible with Device: %v", err),
310+
Message: fmt.Sprintf("ProviderConfigRef kind '%s' with API version '%s' is not compatible with this device type", s.LLDP.Spec.ProviderConfigRef.Kind, s.LLDP.Spec.ProviderConfigRef.APIVersion),
306311
})
307-
return reconcile.TerminalError(fmt.Errorf("unsupported provider config ref kind %q on this provider", gv))
312+
return reconcile.TerminalError(fmt.Errorf("unsupported pProviderConfigRef Kind %q on this provider", gv))
313+
}
314+
315+
gvk := schema.GroupVersionKind{
316+
Group: gv.Group,
317+
Version: gv.Version,
318+
Kind: s.LLDP.Spec.ProviderConfigRef.Kind,
319+
}
320+
321+
// only cisco/nx for the moment
322+
if gvk == nxv1alpha1.GroupVersion.WithKind("LLDPConfig") {
323+
lc := new(nxv1alpha1.LLDPConfig)
324+
if err := s.ProviderConfig.Into(lc); err != nil {
325+
return reconcile.TerminalError(fmt.Errorf("failed to parse provider config into LLDPConfig: %w", err))
326+
}
327+
if err := r.validateProviderConfigNXOS(ctx, lc, s); err != nil {
328+
return reconcile.TerminalError(fmt.Errorf("configuration error in provider config ref %w", err))
329+
}
308330
}
309331
return nil
310332
}
@@ -325,8 +347,50 @@ func (r *LLDPReconciler) validateUniqueLLDPPerDevice(ctx context.Context, s *lld
325347
Reason: v1alpha1.DuplicateResourceOnDevice,
326348
Message: fmt.Sprintf("Another LLDP (%s) already exists for device %s", lldp.Name, s.LLDP.Spec.DeviceRef.Name),
327349
})
328-
return reconcile.TerminalError(fmt.Errorf("only one lldp resource allowed per device (%s)", s.LLDP.Spec.DeviceRef.Name))
350+
return reconcile.TerminalError(fmt.Errorf("only one LLDP resource allowed per device (%s)", s.LLDP.Spec.DeviceRef.Name))
351+
}
352+
}
353+
return nil
354+
}
355+
356+
// validateProviderConfigNXOS validates the provider configuration for the NXOS platform.
357+
// Updates scope and adds interfaces referenced by the LLDP configuration to the scope.
358+
func (r *LLDPReconciler) validateProviderConfigNXOS(ctx context.Context, cfg *nxv1alpha1.LLDPConfig, s *lldpScope) error {
359+
// Ensure all referenced interfaces exist and belong to she same device as the LLDP.
360+
s.Interfaces = make(map[string]*v1alpha1.Interface)
361+
for _, ifCfg := range cfg.Spec.InterfaceConfigurations {
362+
intf := new(v1alpha1.Interface)
363+
if err := r.Get(ctx, client.ObjectKey{Name: ifCfg.InterfaceRef.Name, Namespace: s.LLDP.Namespace}, intf); err != nil {
364+
if apierrors.IsNotFound(err) {
365+
conditions.Set(s.LLDP, metav1.Condition{
366+
Type: v1alpha1.ConfiguredCondition,
367+
Status: metav1.ConditionFalse,
368+
Reason: v1alpha1.InterfaceNotFoundReason,
369+
Message: fmt.Sprintf("Referenced interface %q not found", ifCfg.InterfaceRef.Name),
370+
})
371+
return fmt.Errorf("referenced interface %q not found: %w", ifCfg.InterfaceRef.Name, err)
372+
}
373+
return fmt.Errorf("failed to get referenced interface %q: %w", ifCfg.InterfaceRef.Name, err)
374+
}
375+
if intf.Spec.DeviceRef.Name != s.Device.Name {
376+
conditions.Set(s.LLDP, metav1.Condition{
377+
Type: v1alpha1.ConfiguredCondition,
378+
Status: metav1.ConditionFalse,
379+
Reason: v1alpha1.CrossDeviceReferenceReason,
380+
Message: fmt.Sprintf("Referenced interface %q belongs to device %q, expected %q", ifCfg.InterfaceRef.Name, intf.Spec.DeviceRef.Name, s.Device.Name),
381+
})
382+
return fmt.Errorf("referenced interface %q belongs to device %q, expected %q", ifCfg.InterfaceRef.Name, intf.Spec.DeviceRef.Name, s.Device.Name)
383+
}
384+
if intf.Spec.Type != v1alpha1.InterfaceTypePhysical && intf.Spec.Type != v1alpha1.InterfaceTypeAggregate {
385+
conditions.Set(s.LLDP, metav1.Condition{
386+
Type: v1alpha1.ConfiguredCondition,
387+
Status: metav1.ConditionFalse,
388+
Reason: v1alpha1.InvalidInterfaceTypeReason,
389+
Message: fmt.Sprintf("Referenced interface %q is of type %q, expected %q or %q", ifCfg.InterfaceRef.Name, intf.Spec.Type, v1alpha1.InterfaceTypePhysical, v1alpha1.InterfaceTypeAggregate),
390+
})
391+
return fmt.Errorf("referenced interface %q is of type %q, expected %q or %q", ifCfg.InterfaceRef.Name, intf.Spec.Type, v1alpha1.InterfaceTypePhysical, v1alpha1.InterfaceTypeAggregate)
329392
}
393+
s.Interfaces[ifCfg.InterfaceRef.Name] = intf
330394
}
331395
return nil
332396
}
@@ -376,7 +440,7 @@ func (r *LLDPReconciler) mapProviderConfigToLLDP(ctx context.Context, obj client
376440

377441
list := &v1alpha1.LLDPList{}
378442
if err := r.List(ctx, list, client.InNamespace(obj.GetNamespace())); err != nil {
379-
log.Error(err, "Failed to list LLDPs")
443+
log.Error(err, "failed to list LLDPs")
380444
return nil
381445
}
382446

@@ -388,7 +452,7 @@ func (r *LLDPReconciler) mapProviderConfigToLLDP(ctx context.Context, obj client
388452
m.Spec.ProviderConfigRef.Name == obj.GetName() &&
389453
m.Spec.ProviderConfigRef.Kind == gkv.Kind &&
390454
m.Spec.ProviderConfigRef.APIVersion == gkv.GroupVersion().Identifier() {
391-
log.Info("Enqueuing LLDP for reconciliation", "LLDP", klog.KObj(&m))
455+
log.Info("Found matching LLDP for provider config change, enqueuing for reconciliation", "LLDP", klog.KObj(&m))
392456
requests = append(requests, reconcile.Request{
393457
NamespacedName: types.NamespacedName{
394458
Name: m.Name,

internal/controller/core/lldp_controller_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ var _ = Describe("LLDP Controller", func() {
108108
g.Expect(lldp.OwnerReferences[0].Name).To(Equal(deviceName))
109109
}).Should(Succeed())
110110

111-
By("Veryfing the controller updates the status conditions")
111+
By("Verifying the controller updates the status conditions")
112112
Eventually(func(g Gomega) {
113113
lldp = &v1alpha1.LLDP{}
114114
g.Expect(k8sClient.Get(ctx, resourceKey, lldp)).To(Succeed())
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package nxos
5+
6+
import "github.com/ironcore-dev/network-operator/internal/provider/cisco/gnmiext/v2"
7+
8+
var (
9+
_ gnmiext.Configurable = (*LLDP)(nil)
10+
)
11+
12+
type LLDP struct {
13+
// HoldTime is the number of seconds that a receiving device should hold the information sent by another device before discarding it.
14+
HoldTime Option[uint16] `json:"holdTime"`
15+
// InitDelay is the number of seconds for LLDP to initialize on any interface.
16+
InitDelay Option[uint16] `json:"initDelayTime"`
17+
// IfItems contains the per-interface LLDP configuration.
18+
IfItems struct {
19+
IfList gnmiext.List[string, *LLDPIfItem] `json:"If-list,omitzero"`
20+
} `json:"if-items,omitzero"`
21+
}
22+
23+
func (*LLDP) XPath() string {
24+
return "System/lldp-items/inst-items"
25+
}
26+
27+
func (*LLDP) IsListItem() {}
28+
29+
type LLDPIfItem struct {
30+
InterfaceName string `json:"id"`
31+
AdminRxSt Option[AdminSt] `json:"adminRxSt"`
32+
AdminTxSt Option[AdminSt] `json:"adminTxSt"`
33+
}
34+
35+
func (i *LLDPIfItem) Key() string { return i.InterfaceName }
36+
37+
type LLDPOper struct {
38+
OperSt OperSt `json:"operSt"`
39+
}
40+
41+
func (*LLDPOper) IsListItem() {}
42+
43+
func (o *LLDPOper) XPath() string {
44+
return "System/fm-items/lldp-items"
45+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package nxos
5+
6+
func init() {
7+
lldp := &LLDP{
8+
HoldTime: NewOption(uint16(200)),
9+
InitDelay: NewOption(uint16(5)),
10+
}
11+
12+
lldp.IfItems.IfList.Set(&LLDPIfItem{
13+
InterfaceName: "eth7/1",
14+
AdminRxSt: NewOption(AdminStDisabled),
15+
AdminTxSt: NewOption(AdminStDisabled),
16+
})
17+
18+
lldp.IfItems.IfList.Set(&LLDPIfItem{
19+
InterfaceName: "eth8/1",
20+
AdminTxSt: NewOption(AdminStDisabled),
21+
})
22+
23+
Register("lldp", lldp)
24+
}

0 commit comments

Comments
 (0)