Skip to content

Commit 5fede6d

Browse files
committed
feat: Cisco NXOS VPCs
Multi-Chassis Link Aggregation (MC-LAG) is quite vendor and platform specific. We don't see much intersection in their respective configuration to justify a common API type. Instead, we move forward with a platform specific API exclusive to Cisco NXOS devices. This commit adds new types, controller, and provider to configure virtual Port Channels (vPCs) via the operator. Implementation note: Consider the following information about the YANG model for configuring a vPC: * each vPC configured in the domain appears in the tree in this location: `vpc-items/inst-items/dom-items/if-items/If-list[id=30]` (where `30` is the vPC ID) * the peer-link interface is configured here: `vpc-items/inst-items/dom-items/keepalive-items/peerlink-items[id=po10]` The interfaces will be added to the vPC config by the LAG provider and not by this controller. Hence, if we apply a gNMI Replace operation on the xpath returned by VPC.XPath() we would remove any existing vPC interfaces. A gNMI Update operation will not modify the configuration introduced by the LAG provider.
1 parent d0ef8b3 commit 5fede6d

31 files changed

+2174
-93
lines changed

PROJECT

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,4 +176,12 @@ resources:
176176
kind: VLAN
177177
path: github.com/ironcore-dev/network-operator/api/core/v1alpha1
178178
version: v1alpha1
179+
- api:
180+
crdVersion: v1
181+
namespaced: true
182+
controller: true
183+
domain: cisco.networking.metal.ironcore.dev
184+
group: nx
185+
kind: VPC
186+
path: github.com/ironcore-dev/network-operator/api/cisco/nx/v1alpha1
179187
version: "3"

Tiltfile

Lines changed: 57 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ allow_k8s_contexts(['minikube', 'kind-network'])
1212
load('ext://cert_manager', 'deploy_cert_manager')
1313
deploy_cert_manager(version='v1.18.2')
1414

15+
# # Add webhook configuration
16+
k8s_yaml(kustomize('./config/webhook'))
17+
k8s_yaml(kustomize('./config/certmanager'))
18+
19+
1520
docker_build('controller:latest', '.', ignore=['**/*/zz_generated.deepcopy.go', 'config/crd/bases/*'], only=[
1621
'api/', 'cmd/', 'hack/', 'internal/', 'go.mod', 'go.sum', 'Makefile',
1722
])
@@ -22,7 +27,7 @@ local_resource('controller-gen', 'make generate', ignore=['**/*/zz_generated.dee
2227

2328
docker_build('ghcr.io/ironcore-dev/gnmi-test-server:latest', './test/gnmi')
2429

25-
provider = os.getenv('PROVIDER', 'openconfig')
30+
provider = os.getenv('PROVIDER', 'cisco-nxos-gnmi')
2631

2732
manager = kustomize('config/develop')
2833
manager = str(manager).replace('--provider=openconfig', '--provider={}'.format(provider))
@@ -39,65 +44,77 @@ def device_yaml():
3944
return encode_yaml_stream(decoded)
4045

4146
k8s_yaml(device_yaml())
42-
k8s_resource(new_name='leaf1', objects=['leaf1:device', 'secret-basic-auth:secret'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
47+
k8s_resource(new_name='leaf1', objects=['leaf1:device', 'secret-basic-auth:secret'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=True)
4348

4449
k8s_yaml('./config/samples/v1alpha1_interface.yaml')
45-
k8s_resource(new_name='lo0', objects=['lo0:interface'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
46-
k8s_resource(new_name='lo1', objects=['lo1:interface'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
47-
k8s_resource(new_name='eth1-1', objects=['eth1-1:interface'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
48-
k8s_resource(new_name='eth1-2', objects=['eth1-2:interface'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
50+
# k8s_resource(new_name='lo0', objects=['lo0:interface'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
51+
# k8s_resource(new_name='lo1', objects=['lo1:interface'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
52+
# k8s_resource(new_name='eth1-1', objects=['eth1-1:interface'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
53+
# k8s_resource(new_name='eth1-2', objects=['eth1-2:interface'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
4954
k8s_resource(new_name='eth1-10', objects=['eth1-10:interface'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
50-
k8s_resource(new_name='po10', objects=['po-10:interface'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
55+
k8s_resource(new_name='eth1-30', objects=['eth1-30:interface'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
56+
k8s_resource(new_name='eth1-31', objects=['eth1-31:interface'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
57+
k8s_resource(new_name='eth1-32', objects=['eth1-32:interface'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
5158
k8s_resource(new_name='svi-10', objects=['svi-10:interface'], resource_deps=['vlan-10'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
52-
k8s_resource(new_name='eth1-30', objects=['eth1-30:interface'], resource_deps=['vrf-vpc-keepalive'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
59+
k8s_resource(new_name='po1', objects=['po1:interface'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
60+
k8s_resource(new_name='po2', objects=['po2:interface'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
61+
62+
#resource_deps=['eth1-31', 'eth1-32'],
5363

54-
k8s_yaml('./config/samples/v1alpha1_banner.yaml')
55-
k8s_resource(new_name='banner', objects=['banner:banner'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
5664

57-
k8s_yaml('./config/samples/v1alpha1_user.yaml')
58-
k8s_resource(new_name='user', objects=['user:user', 'user-password:secret', 'user-ssh-key:secret'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
65+
# k8s_yaml('./config/samples/v1alpha1_banner.yaml')
66+
# k8s_resource(new_name='banner', objects=['banner:banner'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
5967

60-
k8s_yaml('./config/samples/v1alpha1_dns.yaml')
61-
k8s_resource(new_name='dns', objects=['dns:dns'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
68+
# k8s_yaml('./config/samples/v1alpha1_user.yaml')
69+
# k8s_resource(new_name='user', objects=['user:user', 'user-password:secret', 'user-ssh-key:secret'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
6270

63-
k8s_yaml('./config/samples/v1alpha1_ntp.yaml')
64-
k8s_resource(new_name='ntp', objects=['ntp:ntp'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
71+
# k8s_yaml('./config/samples/v1alpha1_dns.yaml')
72+
# k8s_resource(new_name='dns', objects=['dns:dns'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
6573

66-
k8s_yaml('./config/samples/v1alpha1_acl.yaml')
67-
k8s_resource(new_name='acl', objects=['acl:accesscontrollist'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
74+
# k8s_yaml('./config/samples/v1alpha1_ntp.yaml')
75+
# k8s_resource(new_name='ntp', objects=['ntp:ntp'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
6876

69-
k8s_yaml('./config/samples/v1alpha1_certificate.yaml')
70-
k8s_resource(new_name='trustpoint', objects=['network-operator:issuer', 'network-operator-ca:certificate', 'trustpoint:certificate'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
77+
# k8s_yaml('./config/samples/v1alpha1_acl.yaml')
78+
# k8s_resource(new_name='acl', objects=['acl:accesscontrollist'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
7179

72-
k8s_yaml('./config/samples/v1alpha1_snmp.yaml')
73-
k8s_resource(new_name='snmp', objects=['snmp:snmp'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
80+
# k8s_yaml('./config/samples/v1alpha1_certificate.yaml')
81+
# k8s_resource(new_name='trustpoint', objects=['network-operator:issuer', 'network-operator-ca:certificate', 'trustpoint:certificate'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
7482

75-
k8s_yaml('./config/samples/v1alpha1_syslog.yaml')
76-
k8s_resource(new_name='syslog', objects=['syslog:syslog'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
83+
# k8s_yaml('./config/samples/v1alpha1_snmp.yaml')
84+
# k8s_resource(new_name='snmp', objects=['snmp:snmp'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
7785

78-
k8s_yaml('./config/samples/v1alpha1_managementaccess.yaml')
79-
k8s_resource(new_name='managementaccess', objects=['managementaccess:managementaccess'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
86+
# k8s_yaml('./config/samples/v1alpha1_syslog.yaml')
87+
# k8s_resource(new_name='syslog', objects=['syslog:syslog'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
8088

81-
k8s_yaml('./config/samples/v1alpha1_isis.yaml')
82-
k8s_resource(new_name='isis-underlay', objects=['underlay:isis'], resource_deps=['lo0', 'lo1', 'eth1-1', 'eth1-2'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
89+
# k8s_yaml('./config/samples/v1alpha1_isis.yaml')
90+
# k8s_resource(new_name='isis-underlay', objects=['underlay:isis'], resource_deps=['lo0', 'lo1', 'eth1-1', 'eth1-2'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
91+
# k8s_yaml('./config/samples/v1alpha1_managementaccess.yaml')
92+
# k8s_resource(new_name='managementaccess', objects=['managementaccess:managementaccess'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
93+
94+
# k8s_yaml('./config/samples/v1alpha1_isis.yaml')
95+
# k8s_resource(new_name='underlay', objects=['underlay:isis'], resource_deps=['lo0', 'lo1', 'eth1-1', 'eth1-2'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
8396

8497
k8s_yaml('./config/samples/v1alpha1_vrf.yaml')
85-
k8s_resource(new_name='vrf-admin', objects=['vrf-cc-admin:vrf'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
86-
k8s_resource(new_name='vrf-001', objects=['vrf-cc-prod-001:vrf'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
87-
k8s_resource(new_name='vrf-vpc-keepalive', objects=['vpc-keepalive:vrf'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
98+
# k8s_resource(new_name='vrf-admin', objects=['vrf-cc-admin:vrf'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
99+
k8s_resource(new_name='vrf-vpckeepalive', objects=['vrf-vpckeepalive:vrf'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
100+
101+
# k8s_yaml('./config/samples/v1alpha1_pim.yaml')
102+
# k8s_resource(new_name='pim', objects=['pim:pim'], resource_deps=['lo0', 'lo1', 'eth1-1', 'eth1-2'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
88103

89-
k8s_yaml('./config/samples/v1alpha1_pim.yaml')
90-
k8s_resource(new_name='pim', objects=['pim:pim'], resource_deps=['lo0', 'lo1', 'eth1-1', 'eth1-2'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
104+
#k8s_yaml('./config/samples/v1alpha1_bgp.yaml')
105+
#k8s_resource(new_name='bgp', objects=['bgp:bgp'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
91106

92-
k8s_yaml('./config/samples/v1alpha1_bgp.yaml')
93-
k8s_resource(new_name='bgp', objects=['bgp:bgp'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
107+
#k8s_yaml('./config/samples/v1alpha1_bgppeer.yaml')
108+
# k8s_resource(new_name='peer-spine1', objects=['leaf1-spine1:bgppeer'], resource_deps=['bgp', 'lo0'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
109+
# k8s_resource(new_name='peer-spine2', objects=['leaf1-spine2:bgppeer'], resource_deps=['bgp', 'lo0'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
94110

95-
k8s_yaml('./config/samples/v1alpha1_bgppeer.yaml')
96-
k8s_resource(new_name='peer-spine1', objects=['leaf1-spine1:bgppeer'], resource_deps=['bgp', 'lo0'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
97-
k8s_resource(new_name='peer-spine2', objects=['leaf1-spine2:bgppeer'], resource_deps=['bgp', 'lo0'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
111+
k8s_yaml('./config/samples/cisco/nx/v1alpha1_vpc.yaml')
112+
# we cannot add a resource dependency to the interfaces here, otherwise we create a deadlock as the multichassis ID depends
113+
# on the vPC being created first, and the vPC requires the interfaces to be present.
114+
k8s_resource(new_name='vpc', objects=['leaf1-vpc:vpc'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
98115

99-
k8s_yaml('./config/samples/v1alpha1_ospf.yaml')
100-
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)
116+
#k8s_yaml('./config/samples/v1alpha1_ospf.yaml')
117+
#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)
101118

102119
k8s_yaml('./config/samples/v1alpha1_vlan.yaml')
103120
k8s_resource(new_name='vlan-10', objects=['vlan-10:vlan'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)

api/cisco/nx/v1alpha1/groupversion_info.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,14 @@ var (
2222
// AddToScheme adds the types in this group-version to the given scheme.
2323
AddToScheme = SchemeBuilder.AddToScheme
2424
)
25+
26+
// WatchLabel is a label that can be applied to any Network API object.
27+
//
28+
// Controllers which allow for selective reconciliation may check this label and proceed
29+
// with reconciliation of the object only if this label and a configured value is present.
30+
const WatchLabel = "nx.cisco.networking.metal.ironcore.dev/watch-filter"
31+
32+
// FinalizerName is the identifier used by the controllers to perform cleanup before a resource is deleted.
33+
// It is added when the resource is created and ensures that the controller can handle teardown logic
34+
// (e.g., deleting external dependencies) before Kubernetes finalizes the deletion.
35+
const FinalizerName = "nx.cisco.networking.metal.ironcore.dev/finalizer"

0 commit comments

Comments
 (0)