Skip to content

Validate interfaces neighbors via LLDP - #292

Merged
nikatza merged 7 commits into
mainfrom
add-lldp-to-iface-status
May 5, 2026
Merged

nikatza merged 7 commits into
mainfrom
add-lldp-to-iface-status

Conversation

@nikatza

@nikatza nikatza commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

I am opening this PR even though there is still one action item missing: making the provider reject some of the naming schemes for the physical interfaces. I fear this would make this PR even longer than it is. So I will add this in a follow up PR. As for now, validation via labels and annotations work in the lab in both cases.

This is what an interface resource looks like with neighbor validation using an annotation (i skip the label as it is similar):

apiVersion: networking.metal.ironcore.dev/v1alpha1
kind: Interface
metadata:
  annotations:
[...]
    networking.metal.ironcore.dev/interface-neighbor-raw: router2::Ethernet1/10
[...]
spec:
  adminState: Up
  description: vPC to Host1
  deviceRef:
    name: leaf1
  mtu: 1500
  name: eth1/10
  type: Physical
status:
[...]
  neighbors:
  - chassisId: 0c3f.2300.010a
    chassisIdType: 4
    expirationTime: "2026-04-13T14:47:50Z"
    portDescription: Ethernet1/10
    portId: Ethernet1/10
    portIdType: 5
    systemDescription: |-
      Cisco Nexus Operating System (NX-OS) Software 10.6(2)
      TAC support: http://www.cisco.com/tac
      Copyright (c) 2002-2025, Cisco Systems, Inc. All rights reserved.
    systemName: router2
    validation: Verified

I have added context in the commit message, but please let me know if something is not clear.

@nikatza
nikatza force-pushed the add-lldp-to-iface-status branch 4 times, most recently from 1afee7c to 49d83c2 Compare April 9, 2026 14:34
@hardikdr hardikdr added the area/switch-automation Automation processes for network switch management and operations. label Apr 10, 2026
@hardikdr hardikdr added this to Roadmap Apr 10, 2026
@nikatza
nikatza force-pushed the add-lldp-to-iface-status branch 2 times, most recently from 5ac6e21 to ccc8336 Compare April 13, 2026 15:39
@nikatza
nikatza force-pushed the add-lldp-to-iface-status branch 7 times, most recently from fa7115c to 911f23b Compare April 13, 2026 22:08
@nikatza nikatza changed the title Validate interfaces neighbors with LLDP Validate interfaces neighbors via LLDP Apr 13, 2026
@nikatza
nikatza force-pushed the add-lldp-to-iface-status branch from 911f23b to c4b7087 Compare April 13, 2026 22:15
@nikatza
nikatza marked this pull request as ready for review April 13, 2026 22:27
@nikatza
nikatza requested a review from a team April 13, 2026 22:27
@nikatza
nikatza force-pushed the add-lldp-to-iface-status branch 6 times, most recently from 065f914 to d21b806 Compare April 20, 2026 18:46
@github-actions github-actions Bot added size/L and removed size/XXL labels Apr 20, 2026
@nikatza
nikatza force-pushed the add-lldp-to-iface-status branch 2 times, most recently from f3262aa to 1f391b0 Compare April 22, 2026 09:04
@github-actions github-actions Bot added size/XXL and removed size/L labels May 4, 2026
@nikatza
nikatza force-pushed the add-lldp-to-iface-status branch from cab65dc to af8dc8f Compare May 4, 2026 10:30
@github-actions github-actions Bot added size/L and removed size/XXL labels May 4, 2026
@nikatza
nikatza force-pushed the add-lldp-to-iface-status branch 6 times, most recently from 14f5cfd to 54616d1 Compare May 4, 2026 11:42
@github-actions github-actions Bot added size/XXL and removed size/L labels May 4, 2026
@nikatza
nikatza force-pushed the add-lldp-to-iface-status branch from 54616d1 to 0d7b708 Compare May 4, 2026 12:21
@nikatza
nikatza marked this pull request as ready for review May 4, 2026 12:33
nikatza added 2 commits May 4, 2026 15:33
Fetch the configured hostname from the device and populate it in the
Device status. The hostname is a configuration item (not state), so it
is fetched via GetConfig rather than GetState. This implies one
additional "unpacked" gNMI call to the switch.

As per [1], it is not possible to issue a `GetRequest` message with
different types in the set of requested paths. Note that it is possible
to leave the `type` empty and thereby get all data (`CONFIG`, `STATE`,
and `OPERATIONAL`). We could implement a `client.GetAny()` method for
this case and retrieve all data at once since the models referenced
`DeviceInfo` do not have excessive data. However, as this is the only
case for such an optimization for now, and the `Device` is infrequently
reconciled, this does not seem necessary for now.

[1]
https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#331-the-getrequest-message

Signed-off-by: Pujol <enric.pujol@sap.com>
The `dhcprelay_controller` was not aligned with the design of the
other controllers. When the device is locked it should requeue using jitter
and also with priority `LockWaitPriorityDefault`.

Adds a test to verify that reconciliation is triggered when an interface
gets configured (after it reconciles once a pending `vrf` resource is
created).

Signed-off-by: Pujol <enric.pujol@sap.com>
@nikatza
nikatza force-pushed the add-lldp-to-iface-status branch from 0d7b708 to 3641530 Compare May 4, 2026 13:34
@nikatza
nikatza requested a review from swagner-de May 4, 2026 14:34
@nikatza
nikatza force-pushed the add-lldp-to-iface-status branch from 3641530 to 9dfe5cf Compare May 5, 2026 08:45
Comment thread internal/controller/core/interface_controller.go Outdated
nikatza added 5 commits May 5, 2026 16:13
Add a `neighbors` field to the Interface status containing LLDP neighbor
information derived from TLVs: chassis ID, port ID, system name, and
expiration time based on TTL.

As per 52eae24, users can annotate or label an interface resource with
expected neighbor information. This can be cross-validated against the
LLDP data that is now stored in the status.

We change the annotation format to accept either the chassis ID field or
the system name. According to the standard [1], the chassis ID itself
can represent different types, like the MAC address, the interface name
or the chassis component among others (see Sec. "8.5.2.2 Chassis ID
subtype" for details).

As information like MAC is not always immediately available, we opt for
also allowing the user to use the sysName instead. The rationale is
that the hostname is typically configured by the operator, the user, or
a known provisioning process. As a result, controller will check the
value in the annotation for both cases. The exact mechanism is detailed
in the next commit.

The status includes a validation field summarizing whether the neighbor
could be validated and its result. While TTL is a mandatory field in
LLDPDUs, we exclude it from the status because its value decreases
continuously on the device. Instead, we compute an ExpirationTime
(current time + TTL) which is more meaningful for users. Note that this
value is recomputed on each reconcile - a predicate is added later to
prevent this from causing infinite reconciliation loops.

This design slightly deviates from OpenConfig [2]. In there all LLDP
information is contained in the `lldp` subtree. We have decided that
configuration is provided by the `lldp` resource. However, the adjacency
data is put into the `interface` status and retrieved by its controller.
This simplifies the design by removing a dependency towards the `lldp`
resource. Notice that if a user enables LLDP by means other than the
operator, the data will appear in the interface even if the `lldp`
resource does not exist.

[1] https://ieeexplore.ieee.org/document/7433915
[2] https://openconfig.net/projects/models/schemadocs/yangdoc/openconfig-lldp.html#lldp-interfaces-interface-neighbors-neighbor-id

Signed-off-by: Pujol <enric.pujol@sap.com>
Instruct the interface controller to retrieve the LLDP adjacency
information via the provider and populate the status accordingly.

The neighbor validation is implemented as a non-blocking operation.
Errors are logged but they don't prevent reconciliation of the
interface. The validation checks first if the resource has a label. The
label is used to perform a validation against an interface resource. If
this fails, then the controller falls back to check the annotation. The
annotation is used to validate neighbors that are not a kubernetes
resource (see 52eae24).

The operator does not actively track TTL expiration. Instead, it relies
on the device to remove expired neighbors and the periodic requeue
interval to sync the status. The ExpirationTime field is informational
for users to know when a neighbor will disappear. Note that neighbor
entries may become stale if the interface is not requeued in time -
the status reflects the last fetched state, not real-time data.

This commit also adds a missing watch for the LLDP controller. Now, if
the LLDP resource references an interface resource that has not been
created, the LLDP resource will be reconciled once the missing
dependency is created. This was problematic during bootstraps, as the
lldp feature was not installed if a single interface was missing. We
also add tests for LLDP operational status degradation, verifying that
the controller correctly sets OperationalCondition to False when the
device reports LLDP is down, and recovers when it comes back up.

Signed-off-by: Pujol <enric.pujol@sap.com>
Retrieve LLDP neighbor information while fetching interface status. As
of now, this is only performed on physical interfaces with the use case
of cabling validation in mind. The GetStatus response now includes a
slice of adjacencies with a subset of the Cisco model for NXOS [1].

Also adds `InterfaceNameEqual` to the InterfaceProvider interface,
allowing provider-specific logic to compare interface names. This is
needed because of interface naming conventions differ across vendors (e.g.,
NX-OS uses forms like "eth1/1", "Ethernet1/1").

[1]
https://pubhub.devnetcloud.com/media/dme-docs-10-4-3/docs/Discovery%20Protocols/lldp%3AAdjEp/

Signed-off-by: Pujol <enric.pujol@sap.com>
Each reconcile computes ExpirationTime from current time plus TTL. The
status patch triggers another reconcile, causing an infinite loop.

This predicate filters Update events on the Interface watch, skipping
reconciliation when the only change is ExpirationTime. Includes a
conditions-count check to ensure initial condition setup completes
before filtering kicks in.

Signed-off-by: Pujol <enric.pujol@sap.com>
Signed-off-by: Pujol <enric.pujol@sap.com>
@nikatza
nikatza force-pushed the add-lldp-to-iface-status branch from 337e48a to c3f333c Compare May 5, 2026 14:15
@github-actions

github-actions Bot commented May 5, 2026

Copy link
Copy Markdown

Merging this branch will decrease overall coverage

Impacted Packages Coverage Δ 🤖
github.com/ironcore-dev/network-operator/api/core/v1alpha1 0.00% (ø)
github.com/ironcore-dev/network-operator/internal/controller/cisco/nx 64.16% (-0.18%) 👎
github.com/ironcore-dev/network-operator/internal/controller/core 62.93% (-0.35%) 👎
github.com/ironcore-dev/network-operator/internal/provider 52.00% (ø)
github.com/ironcore-dev/network-operator/internal/provider/cisco/iosxr 48.55% (-0.28%) 👎
github.com/ironcore-dev/network-operator/internal/provider/cisco/nxos 9.94% (-0.08%) 👎
github.com/ironcore-dev/network-operator/internal/provider/openconfig 0.00% (ø)
github.com/ironcore-dev/network-operator/test/e2e 0.00% (ø)
github.com/ironcore-dev/network-operator/test/lab 0.00% (ø)

Coverage by file

Changed files (no unit tests)

Changed File Coverage Δ Total Covered Missed 🤖
github.com/ironcore-dev/network-operator/api/core/v1alpha1/device_types.go 0.00% (ø) 0 0 0
github.com/ironcore-dev/network-operator/api/core/v1alpha1/groupversion_info.go 0.00% (ø) 0 0 0
github.com/ironcore-dev/network-operator/api/core/v1alpha1/interface_types.go 0.00% (ø) 0 0 0
github.com/ironcore-dev/network-operator/api/core/v1alpha1/zz_generated.deepcopy.go 0.00% (ø) 0 0 0
github.com/ironcore-dev/network-operator/internal/controller/cisco/nx/bordergateway_controller.go 52.97% (ø) 236 125 111
github.com/ironcore-dev/network-operator/internal/controller/cisco/nx/system_controller.go 64.66% (-0.86%) 116 75 (-1) 41 (+1) 👎
github.com/ironcore-dev/network-operator/internal/controller/cisco/nx/vpcdomain_controller.go 76.70% (ø) 206 158 48
github.com/ironcore-dev/network-operator/internal/controller/core/acl_controller.go 58.16% (ø) 141 82 59
github.com/ironcore-dev/network-operator/internal/controller/core/banner_controller.go 57.99% (ø) 169 98 71
github.com/ironcore-dev/network-operator/internal/controller/core/bgp_controller.go 69.40% (+0.86%) 232 161 (+2) 71 (-2) 👍
github.com/ironcore-dev/network-operator/internal/controller/core/bgp_peer_controller.go 57.01% (ø) 314 179 135
github.com/ironcore-dev/network-operator/internal/controller/core/certificate_controller.go 58.71% (ø) 155 91 64
github.com/ironcore-dev/network-operator/internal/controller/core/device_controller.go 70.91% (+0.13%) 220 (+1) 156 (+1) 64 👍
github.com/ironcore-dev/network-operator/internal/controller/core/dhcprelay_controller.go 65.89% (+3.49%) 258 170 (+9) 88 (-9) 👍
github.com/ironcore-dev/network-operator/internal/controller/core/dns_controller.go 57.97% (ø) 138 80 58
github.com/ironcore-dev/network-operator/internal/controller/core/evpninstance_controller.go 67.31% (ø) 208 140 68
github.com/ironcore-dev/network-operator/internal/controller/core/interface_controller.go 66.06% (-7.66%) 551 (+83) 364 (+19) 187 (+64) 👎
github.com/ironcore-dev/network-operator/internal/controller/core/isis_controller.go 60.12% (ø) 173 104 69
github.com/ironcore-dev/network-operator/internal/controller/core/lldp_controller.go 69.59% (+2.25%) 217 (+18) 151 (+17) 66 (+1) 👍
github.com/ironcore-dev/network-operator/internal/controller/core/managementaccess_controller.go 57.97% (-1.45%) 138 80 (-2) 58 (+2) 👎
github.com/ironcore-dev/network-operator/internal/controller/core/ntp_controller.go 57.97% (ø) 138 80 58
github.com/ironcore-dev/network-operator/internal/controller/core/nve_controller.go 67.59% (-0.93%) 216 146 (-2) 70 (+2) 👎
github.com/ironcore-dev/network-operator/internal/controller/core/ospf_controller.go 59.80% (-0.98%) 204 122 (-2) 82 (+2) 👎
github.com/ironcore-dev/network-operator/internal/controller/core/pim_controller.go 61.85% (+1.73%) 173 107 (+3) 66 (-3) 👍
github.com/ironcore-dev/network-operator/internal/controller/core/prefixset_controller.go 61.70% (ø) 141 87 54
github.com/ironcore-dev/network-operator/internal/controller/core/routingpolicy_controller.go 67.54% (+1.57%) 191 129 (+3) 62 (-3) 👍
github.com/ironcore-dev/network-operator/internal/controller/core/snmp_controller.go 57.97% (-1.45%) 138 80 (-2) 58 (+2) 👎
github.com/ironcore-dev/network-operator/internal/controller/core/syslog_controller.go 58.16% (ø) 141 82 59
github.com/ironcore-dev/network-operator/internal/controller/core/user_controller.go 56.80% (ø) 169 96 73
github.com/ironcore-dev/network-operator/internal/controller/core/vlan_controller.go 63.33% (+1.33%) 150 95 (+2) 55 (-2) 👍
github.com/ironcore-dev/network-operator/internal/controller/core/vrf_controller.go 62.32% (ø) 138 86 52
github.com/ironcore-dev/network-operator/internal/provider/cisco/iosxr/provider.go 40.71% (-0.36%) 113 (+1) 46 67 (+1) 👎
github.com/ironcore-dev/network-operator/internal/provider/cisco/nxos/lldp.go 50.00% (-16.67%) 4 (+1) 2 2 (+1) 💀
github.com/ironcore-dev/network-operator/internal/provider/cisco/nxos/provider.go 0.06% (-0.00%) 1779 (+17) 1 1778 (+17) 👎
github.com/ironcore-dev/network-operator/internal/provider/cisco/nxos/system.go 3.12% (-0.10%) 32 (+1) 1 31 (+1) 👎
github.com/ironcore-dev/network-operator/internal/provider/openconfig/provider.go 0.00% (ø) 0 0 0
github.com/ironcore-dev/network-operator/internal/provider/provider.go 52.00% (ø) 25 13 12

Please note that the "Total", "Covered", and "Missed" counts above refer to code statements instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code.

Changed unit test files

  • github.com/ironcore-dev/network-operator/internal/controller/core/dhcprelay_controller_test.go
  • github.com/ironcore-dev/network-operator/internal/controller/core/interface_controller_test.go
  • github.com/ironcore-dev/network-operator/internal/controller/core/lldp_controller_test.go
  • github.com/ironcore-dev/network-operator/internal/controller/core/suite_test.go
  • github.com/ironcore-dev/network-operator/test/e2e/e2e_test.go
  • github.com/ironcore-dev/network-operator/test/e2e/util_test.go
  • github.com/ironcore-dev/network-operator/test/lab/main_test.go

@nikatza
nikatza merged commit 8ab57ad into main May 5, 2026
20 checks passed
@nikatza
nikatza deleted the add-lldp-to-iface-status branch May 5, 2026 15:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/switch-automation Automation processes for network switch management and operations. size/XXL

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants