Skip to content

Commit 52078ca

Browse files
committed
feat: Add support for Cisco NXOS VPCs
* Add a new API type, controller, and provider `NXOSVPC`. This type is exclusive to Cisco NXOS devices and enables virtual Port Channels (vPCs).
1 parent a834714 commit 52078ca

25 files changed

+1272
-396
lines changed

PROJECT

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,4 +118,13 @@ resources:
118118
kind: ISIS
119119
path: github.com/ironcore-dev/network-operator/api/v1alpha1
120120
version: v1alpha1
121+
- api:
122+
crdVersion: v1
123+
namespaced: true
124+
controller: true
125+
domain: cloud.sap
126+
group: networking
127+
kind: NXOSVPC
128+
path: github.com/ironcore-dev/network-operator/api/v1alpha1
129+
version: v1alpha1
121130
version: "3"

Tiltfile

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ local_resource('controller-gen', 'make generate', ignore=['*/*/zz_generated.deep
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))
@@ -41,42 +41,47 @@ def device_yaml():
4141
k8s_yaml(device_yaml())
4242
k8s_resource(new_name='leaf1', objects=['leaf1:device', 'secret-basic-auth:secret'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
4343

44-
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)
44+
# 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)
5050

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

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

57-
k8s_yaml('./config/samples/v1alpha1_dns.yaml')
58-
k8s_resource(new_name='dns', objects=['dns:dns'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
57+
# k8s_yaml('./config/samples/v1alpha1_dns.yaml')
58+
# k8s_resource(new_name='dns', objects=['dns:dns'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
5959

60-
k8s_yaml('./config/samples/v1alpha1_ntp.yaml')
61-
k8s_resource(new_name='ntp', objects=['ntp:ntp'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
60+
# k8s_yaml('./config/samples/v1alpha1_ntp.yaml')
61+
# k8s_resource(new_name='ntp', objects=['ntp:ntp'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
6262

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

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

69-
k8s_yaml('./config/samples/v1alpha1_snmp.yaml')
70-
k8s_resource(new_name='snmp', objects=['snmp:snmp'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
69+
# k8s_yaml('./config/samples/v1alpha1_snmp.yaml')
70+
# k8s_resource(new_name='snmp', objects=['snmp:snmp'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
7171

72-
k8s_yaml('./config/samples/v1alpha1_syslog.yaml')
73-
k8s_resource(new_name='syslog', objects=['syslog:syslog'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
72+
# k8s_yaml('./config/samples/v1alpha1_syslog.yaml')
73+
# k8s_resource(new_name='syslog', objects=['syslog:syslog'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
7474

75-
k8s_yaml('./config/samples/v1alpha1_managementaccess.yaml')
76-
k8s_resource(new_name='managementaccess', objects=['managementaccess:managementaccess'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
75+
# k8s_yaml('./config/samples/v1alpha1_managementaccess.yaml')
76+
# k8s_resource(new_name='managementaccess', objects=['managementaccess:managementaccess'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
77+
78+
# k8s_yaml('./config/samples/v1alpha1_isis.yaml')
79+
# 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+
81+
# TODO: add resource_deps=['po1', 'po2'] when port-channel resource is implemented
82+
k8s_yaml('./config/samples/v1alpha1_nxosvpc.yaml')
83+
k8s_resource(new_name='vpc', objects=['nxosvpc:nxosvpc'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
7784

78-
k8s_yaml('./config/samples/v1alpha1_isis.yaml')
79-
k8s_resource(new_name='underlay', objects=['underlay:isis'], resource_deps=['lo0', 'lo1', 'eth1-1', 'eth1-2'], trigger_mode=TRIGGER_MODE_MANUAL, auto_init=False)
8085

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

api/v1alpha1/nxosvpc_types.go

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
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 (
7+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
8+
)
9+
10+
// NXOSVPCSpec defines the desired state of NXOSVPC (Cisco's NXOS Virtual Port Channel)
11+
type NXOSVPCSpec struct {
12+
// DeviceName is the name of the Device this object belongs to. The Device object must exist in the same namespace.
13+
// Immutable.
14+
// +required
15+
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="DeviceRef is immutable"
16+
DeviceRef LocalObjectReference `json:"deviceRef"`
17+
18+
// DomainID is the vPC domain ID.
19+
// +required
20+
// +kubebuilder:validation:Minimum=1
21+
// +kubebuilder:validation:Maximum=1000
22+
DomainID int32 `json:"domainId"`
23+
24+
// AdminState is the administrative state of the vPC domain.
25+
// +required
26+
// +kubebuilder:default="enabled"
27+
// +kubebuilder:validation:Enum=enabled;disabled
28+
AdminState string `json:"adminState,omitempty"`
29+
30+
// RolePriority is the role priority for this vPC domain.
31+
// +required
32+
// +kubebuilder:validation:Minimum=1
33+
// +kubebuilder:validation:Maximum=65535
34+
RolePriority uint16 `json:"rolePriority"`
35+
36+
// SystemPriority is the system priority for this vPC domain.
37+
// +required
38+
// +kubebuilder:validation:Minimum=1
39+
// +kubebuilder:validation:Maximum=65535
40+
SystemPriority uint16 `json:"systemPriority"`
41+
42+
// DelayRestoreSVI is the delay in bringing up bringing-up the interface-vlan
43+
// +required
44+
// +kubebuilder:validation:Minimum=1
45+
// +kubebuilder:validation:Maximum=3600
46+
DelayRestoreSVI uint16 `json:"delayRestoreSVI"`
47+
48+
// DelayRestoreVPC is the delay in bringing up the vPC links after restoring the peer-link
49+
// +required
50+
// +kubebuilder:validation:Minimum=1
51+
// +kubebuilder:validation:Maximum=3600
52+
DelayRestoreVPC uint16 `json:"delayRestoreVPC"`
53+
54+
// +required
55+
FastConvergence AdminSt `json:"fastConvergence"`
56+
57+
// +required
58+
Peer Peer `json:"peer"`
59+
}
60+
61+
// AdminSt represents administrative state (enabled/disabled)
62+
type AdminSt struct {
63+
// +required
64+
Enabled bool `json:"enabled"`
65+
}
66+
67+
type Peer struct {
68+
// +required
69+
KeepAlive KeepAlive `json:"keepalive"`
70+
71+
// +required
72+
AutoRecovery AutoRecovery `json:"autoRecovery"`
73+
74+
// +required
75+
Switch AdminSt `json:"switch"`
76+
77+
// +required
78+
Gateway AdminSt `json:"gateway"`
79+
80+
// Router defines layer3 peer-router settings
81+
// +required
82+
Router AdminSt `json:"router"`
83+
}
84+
85+
type KeepAlive struct {
86+
// Destination is the destination IP address
87+
// +required
88+
Destination string `json:"destination"`
89+
90+
// Source is the source IP address
91+
// +required
92+
Source string `json:"source"`
93+
94+
// +optional
95+
VRF string `json:"vrf,omitempty"`
96+
}
97+
98+
// AutoRecovery holds autorecovery settings.
99+
// +kubebuilder:validation:XValidation:rule="self.enabled ? has(self.reloadDelay) : !has(self.reloadDelay)",message="reloadDelay must be set when enabled and absent when disabled"
100+
type AutoRecovery struct {
101+
// +required
102+
Enabled bool `json:"enabled,omitempty"`
103+
104+
// +optional
105+
// +kubebuilder:validation:Minimum=60
106+
// +kubebuilder:validation:Maximum=3600
107+
ReloadDelay uint32 `json:"reloadDelay,omitempty"`
108+
}
109+
110+
// NXOSVPCStatus defines the observed state of NXOSVPC.
111+
type NXOSVPCStatus struct {
112+
// The conditions are a list of status objects that describe the state of the VPC.
113+
//+listType=map
114+
//+listMapKey=type
115+
//+patchStrategy=merge
116+
//+patchMergeKey=type
117+
//+optional
118+
Conditions []metav1.Condition `json:"conditions,omitempty"`
119+
}
120+
121+
// +kubebuilder:object:root=true
122+
// +kubebuilder:subresource:status
123+
// +kubebuilder:resource:path=nxosvpcs
124+
// +kubebuilder:resource:singular=nxosvpc
125+
// +kubebuilder:resource:shortName=vpc
126+
// +kubebuilder:printcolumn:name="Domain",type=string,JSONPath=`.spec.domainId`
127+
// +kubebuilder:printcolumn:name="AdminState",type=string,JSONPath=`.spec.adminState`
128+
// +kubebuilder:printcolumn:name="Device",type=string,JSONPath=`.spec.deviceRef.name`
129+
// +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].status`
130+
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
131+
132+
// NXOSVPC is the Schema for the nxosvpcs API
133+
type NXOSVPC struct {
134+
metav1.TypeMeta `json:",inline"`
135+
metav1.ObjectMeta `json:"metadata,omitempty"`
136+
137+
// spec defines the desired state of NXOSVPC
138+
// +required
139+
Spec NXOSVPCSpec `json:"spec,omitempty"`
140+
141+
// status defines the observed state of NXOSVPC
142+
// +optional
143+
Status NXOSVPCStatus `json:"status,omitempty,omitzero"`
144+
}
145+
146+
// +kubebuilder:object:root=true
147+
148+
// NXOSVPCList contains a list of NXOSVPC
149+
type NXOSVPCList struct {
150+
metav1.TypeMeta `json:",inline"`
151+
metav1.ListMeta `json:"metadata,omitempty"`
152+
Items []NXOSVPC `json:"items"`
153+
}
154+
155+
func init() {
156+
SchemeBuilder.Register(&NXOSVPC{}, &NXOSVPCList{})
157+
}

0 commit comments

Comments
 (0)