Skip to content

Commit f6fb13c

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 fecc7d0 commit f6fb13c

30 files changed

+2320
-62
lines changed

PROJECT

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,4 +143,12 @@ resources:
143143
kind: BGPPeer
144144
path: github.com/ironcore-dev/network-operator/api/core/v1alpha1
145145
version: v1alpha1
146+
- api:
147+
crdVersion: v1
148+
namespaced: true
149+
controller: true
150+
domain: cisco.networking.metal.ironcore.dev
151+
group: nx
152+
kind: VPC
153+
path: github.com/ironcore-dev/network-operator/api/cisco/nx/v1alpha1
146154
version: "3"

Tiltfile

Lines changed: 47 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ local_resource('controller-gen', 'make generate', ignore=['**/*/zz_generated.dee
2222

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

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

2727
manager = kustomize('config/develop')
2828
manager = str(manager).replace('--provider=openconfig', '--provider={}'.format(provider))
@@ -39,59 +39,68 @@ def device_yaml():
3939
return encode_yaml_stream(decoded)
4040

4141
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)
42+
k8s_resource(new_name='leaf1', objects=['leaf1:device', 'secret-basic-auth:secret'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=True)
4343

4444
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)
49-
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)
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)
49+
#k8s_resource(new_name='eth1-10', objects=['eth1-10:interface'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
50+
k8s_resource(new_name='eth1-31', objects=['eth1-31:interface'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
51+
k8s_resource(new_name='eth1-32', objects=['eth1-32:interface'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
52+
k8s_resource(new_name='po1', objects=['po1:interface'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
53+
#resource_deps=['eth1-31', 'eth1-32'],
5154

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

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

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

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

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

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

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

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

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

79-
k8s_yaml('./config/samples/v1alpha1_isis.yaml')
80-
k8s_resource(new_name='underlay', objects=['underlay:isis'], resource_deps=['lo0', 'lo1', 'eth1-1', 'eth1-2'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
80+
# k8s_yaml('./config/samples/v1alpha1_managementaccess.yaml')
81+
# k8s_resource(new_name='managementaccess', objects=['managementaccess:managementaccess'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
8182

82-
k8s_yaml('./config/samples/v1alpha1_vrf.yaml')
83-
k8s_resource(new_name='vrf-admin', objects=['vrf-cc-admin:vrf'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
84-
k8s_resource(new_name='vrf-001', objects=['vrf-cc-prod-001:vrf'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
83+
# k8s_yaml('./config/samples/v1alpha1_isis.yaml')
84+
# k8s_resource(new_name='underlay', objects=['underlay:isis'], resource_deps=['lo0', 'lo1', 'eth1-1', 'eth1-2'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
8585

86-
k8s_yaml('./config/samples/v1alpha1_pim.yaml')
87-
k8s_resource(new_name='pim', objects=['pim:pim'], resource_deps=['lo0', 'lo1', 'eth1-1', 'eth1-2'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
86+
# k8s_yaml('./config/samples/v1alpha1_vrf.yaml')
87+
# k8s_resource(new_name='vrf-admin', objects=['vrf-cc-admin:vrf'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
88+
# k8s_resource(new_name='vrf-vpckeepalive', objects=['VPC_VPCKEEPALIVE:vrf'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
8889

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

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

96105
print('🚀 network-operator development environment')
97106
print('👉 Edit the code inside the api/, cmd/, or internal/ directories')

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)