Skip to content

Commit 274259e

Browse files
(feat): Cisco IOS XR implement bundle interface configuration
1 parent e274603 commit 274259e

4 files changed

Lines changed: 558 additions & 116 deletions

File tree

internal/provider/cisco/iosxr/intf.go

Lines changed: 204 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,74 @@ package iosxr
66
import (
77
"fmt"
88
"regexp"
9+
"strconv"
10+
"strings"
911

1012
"github.com/ironcore-dev/network-operator/internal/provider/cisco/gnmiext/v2"
1113
)
1214

13-
type PhysIf struct {
15+
var (
16+
bundleEtherRE = regexp.MustCompile(`^[bB]undle-[Ee]ther(\d+)(?:\.\d+)?$`)
17+
physicalInterfaceRE = regexp.MustCompile(`^(TenGigE|TwentyFiveGigE|FortyGigE|HundredGigE|GigabitEthernet)(\d){1}(\/\d){2}(\/\d+){1}(.\d{1,5})?$`)
18+
)
19+
20+
type IFaceSpeed string
21+
22+
const (
23+
Speed10G IFaceSpeed = "TenGigE"
24+
Speed25G IFaceSpeed = "TwentyFiveGigE"
25+
Speed40G IFaceSpeed = "FortyGigE"
26+
Speed100G IFaceSpeed = "HundredGigE"
27+
EtherBundle IFaceSpeed = "etherbundle"
28+
)
29+
30+
type BundlePortActivity string
31+
32+
const (
33+
PortActivityOn BundlePortActivity = "on"
34+
PortActivityActive BundlePortActivity = "active"
35+
PortActivityPassive BundlePortActivity = "passive"
36+
PortActivityInherit BundlePortActivity = "inherit"
37+
)
38+
39+
type PhysIfStateType string
40+
41+
const (
42+
StateUp PhysIfStateType = "im-state-up"
43+
StateDown PhysIfStateType = "im-state-down"
44+
StateNotReady PhysIfStateType = "im-state-not-ready"
45+
StateAdminDown PhysIfStateType = "im-state-admin-down"
46+
StateShutDown PhysIfStateType = "im-state-shutdown"
47+
)
48+
49+
// Iface represents physical and bundle interfaces as part of the same struct as they share a lot of common configuration
50+
// and only differ in a few attributes like the interface name and the presence of bundle configuration or not.
51+
type Iface struct {
1452
Name string `json:"-"`
15-
Description string `json:"description"`
16-
Active string `json:"active"`
17-
Vrf string `json:"Cisco-IOS-XR-infra-rsi-cfg:vrf,omitempty"`
18-
Statistics Statistics `json:"Cisco-IOS-XR-infra-statsd-cfg:statistics"`
19-
IPv4Network IPv4Network `json:"Cisco-IOS-XR-ipv4-io-cfg:ipv4-network"`
20-
IPv6Network IPv6Network `json:"Cisco-IOS-XR-ipv6-ma-cfg:ipv6-network"`
21-
IPv6Neighbor IPv6Neighbor `json:"Cisco-IOS-XR-ipv6-nd-cfg:ipv6-neighbor"`
22-
MTUs MTUs `json:"mtus"`
23-
Shutdown gnmiext.Empty `json:"shutdown,omitempty"`
53+
Description string `json:"description,omitzero"`
54+
Statistics Statistics `json:"Cisco-IOS-XR-infra-statsd-cfg:statistics,omitzero"`
55+
MTUs MTUs `json:"mtus,omitzero"`
56+
Active string `json:"active,omitzero"`
57+
Vrf string `json:"Cisco-IOS-XR-infra-rsi-cfg:vrf,omitzero"`
58+
IPv4Network IPv4Network `json:"Cisco-IOS-XR-ipv4-io-cfg:ipv4-network,omitzero"`
59+
IPv6Network IPv6Network `json:"Cisco-IOS-XR-ipv6-ma-cfg:ipv6-network,omitzero"`
60+
IPv6Neighbor IPv6Neighbor `json:"Cisco-IOS-XR-ipv6-nd-cfg:ipv6-neighbor,omitzero"`
61+
Shutdown gnmiext.Empty `json:"shutdown,omitzero"`
62+
63+
// Existence of this object causes the creation of the software subinterface
64+
ModeNoPhysical string `json:"interface-mode-non-physical,omitzero"`
65+
66+
// BundleMember configuration for Physical interface as member of a Bundle-Ether
67+
BundleMember BundleMember `json:"Cisco-IOS-XR-bundlemgr-cfg:bundle-member,omitzero"`
68+
69+
// Mode in which an interface is running (e.g., virtual for subinterfaces)
70+
Mode gnmiext.Empty `json:"interface-virtual,omitzero"`
71+
Bundle Bundle `json:"Cisco-IOS-XR-bundlemgr-cfg:bundle,omitzero"`
72+
SubInterface VlanSubInterface `json:"Cisco-IOS-XR-l2-eth-infra-cfg:vlan-sub-configuration,omitzero"`
73+
}
74+
75+
type BundleMember struct {
76+
ID BundleID `json:"id"`
2477
}
2578

2679
type Statistics struct {
@@ -29,7 +82,7 @@ type Statistics struct {
2982

3083
type IPv4Network struct {
3184
Addresses AddressesIPv4 `json:"addresses"`
32-
Mtu uint16 `json:"mtu"`
85+
Mtu uint16 `json:"mtu,omitzero"`
3386
}
3487

3588
type AddressesIPv4 struct {
@@ -73,64 +126,171 @@ type MTU struct {
73126
Owner string `json:"owner"`
74127
}
75128

76-
func (i *PhysIf) XPath() string {
129+
type BundleID struct {
130+
BundleID int32 `json:"bundle-id"`
131+
PortActivity string `json:"port-activity"`
132+
}
133+
134+
type Bundle struct {
135+
MinAct MinimumActive `json:"minimum-active"`
136+
}
137+
138+
type MinimumActive struct {
139+
Links int32 `json:"links"`
140+
}
141+
142+
type VlanSubInterface struct {
143+
VlanIdentifier VlanIdentifier `json:"vlan-identifier"`
144+
}
145+
146+
type VlanIdentifier struct {
147+
FirstTag int32 `json:"first-tag"`
148+
SecondTag int32 `json:"second-tag,omitzero"`
149+
VlanType string `json:"vlan-type"`
150+
}
151+
152+
func (i *Iface) XPath() string {
77153
return fmt.Sprintf("Cisco-IOS-XR-ifmgr-cfg:interface-configurations/interface-configuration[active=act][interface-name=%s]", i.Name)
78154
}
79155

80-
func (i *PhysIf) String() string {
81-
return fmt.Sprintf("Name: %s, Description=%s, ShutDown=%t", i.Name, i.Description, i.Shutdown)
156+
func (i *Iface) String() string {
157+
return fmt.Sprintf("Name: %s, Description=%s", i.Name, i.Description)
82158
}
83159

84-
type IFaceSpeed string
160+
type PhysIfState struct {
161+
State string `json:"state"`
162+
Name string `json:"-"`
163+
}
85164

86-
const (
87-
Speed10G IFaceSpeed = "TenGigE"
88-
Speed25G IFaceSpeed = "TwentyFiveGigE"
89-
Speed40G IFaceSpeed = "FortyGigE"
90-
Speed100G IFaceSpeed = "HundredGigE"
91-
)
165+
func (phys *PhysIfState) XPath() string {
166+
// (fixme): hardcoded route processor for the moment
167+
return fmt.Sprintf("Cisco-IOS-XR-ifmgr-oper:interface-properties/data-nodes/data-node[data-node-name=0/RP0/CPU0]/system-view/interfaces/interface[interface-name=%s]", phys.Name)
168+
}
169+
170+
func ValidateInterfaceName(name string) error {
171+
// Supported Interface name formats:
172+
// Physical Interface <PortSpeed><rack><slot><port> e.g TwentyFiveGigE0/0/0/3
173+
// SubInterface <PotySpeed><rack><slot><port>.<vlan-id> e.g TwentyFiveGigE0/0/0/3
174+
// Bundle Interface/Port Channel Bundle-Ether<BundleID>
175+
// Vlans over Bundle Bundle-Ether<BundleID>.<vlan-id>
176+
177+
beErr := CheckInterfaceNameTypeAggregate(name)
178+
physErr := CheckInterfaceNameTypePhysical(name)
179+
180+
if beErr == nil || physErr == nil {
181+
return nil
182+
}
183+
return fmt.Errorf("unsupported interface name format: %s", name)
184+
}
185+
186+
func ExtractInterfaceSpeedFromName(ifaceName string) (IFaceSpeed, error) {
187+
// Owner of bundle interfaces is 'etherbundle'
188+
if bundleEtherRE.MatchString(ifaceName) {
189+
return EtherBundle, nil
190+
}
92191

93-
func ExtractMTUOwnerFromIfaceName(ifaceName string) (IFaceSpeed, error) {
94192
// Match the port_type in an interface name <port_type>/<rack>/<slot/<module>/<port>
95193
// E.g. match TwentyFiveGigE of interface with name TwentyFiveGigE0/0/0/1
96194
re := regexp.MustCompile(`^\D*`)
97-
98-
mtuOwner := string(re.Find([]byte(ifaceName)))
99-
100-
if mtuOwner == "" {
101-
return "", fmt.Errorf("failed to extract MTU owner from interface name %s", ifaceName)
195+
speed := string(re.Find([]byte(ifaceName)))
196+
if speed == "" {
197+
return "", fmt.Errorf("failed to extract speed from interface name %s", ifaceName)
102198
}
103199

104-
switch mtuOwner {
200+
switch speed {
105201
case string(Speed10G):
106202
return Speed10G, nil
107203
case string(Speed25G):
108204
return Speed25G, nil
109205
case string(Speed40G):
110-
return Speed25G, nil
206+
return Speed40G, nil
111207
case string(Speed100G):
112208
return Speed100G, nil
113209
default:
114-
return "", fmt.Errorf("unsupported interface type %s for MTU owner extraction", mtuOwner)
210+
return "", fmt.Errorf("unsupported interface type %s", speed)
115211
}
116212
}
117213

118-
type PhysIfStateType string
214+
func CheckInterfaceNameTypeAggregate(name string) error {
215+
matches := bundleEtherRE.FindStringSubmatch(name)
119216

120-
const (
121-
StateUp PhysIfStateType = "im-state-up"
122-
StateDown PhysIfStateType = "im-state-down"
123-
StateNotReady PhysIfStateType = "im-state-not-ready"
124-
StateAdminDown PhysIfStateType = "im-state-admin-down"
125-
StateShutDown PhysIfStateType = "im-state-shutdown"
126-
)
217+
if matches == nil {
218+
return fmt.Errorf("unsupported interface format %q, expected one of: %q", name, bundleEtherRE.String())
219+
}
220+
// Fixme(sven-rosenzweig): check BundleId range
221+
return nil
222+
}
127223

128-
type PhysIfState struct {
129-
State string `json:"state"`
130-
Name string `json:"-"`
224+
func CheckInterfaceNameTypePhysical(name string) error {
225+
if !physicalInterfaceRE.MatchString(name) {
226+
return fmt.Errorf("unsupported physical interface format %s", name)
227+
}
228+
return nil
131229
}
132230

133-
func (phys *PhysIfState) XPath() string {
134-
// (fixme): hardcoded route processor for the moment
135-
return fmt.Sprintf("Cisco-IOS-XR-ifmgr-oper:interface-properties/data-nodes/data-node[data-node-name=0/RP0/CPU0]/system-view/interfaces/interface[interface-name=%s]", phys.Name)
231+
func ExtractVlanTagFromName(name string) (vlanID int32, err error) {
232+
res := strings.Split(name, ".")
233+
switch len(res) {
234+
case 1:
235+
return 0, nil
236+
case 2:
237+
vlan, err := strconv.ParseInt(res[1], 10, 32)
238+
if err != nil {
239+
return 0, fmt.Errorf("failed to parse VLAN ID from interface name %q: %w", name, err)
240+
}
241+
return int32(vlan), nil
242+
default:
243+
return 0, fmt.Errorf("unexpected interface name format %q, expected <interface> or <interface>.<vlan>", name)
244+
}
245+
}
246+
247+
func ExtractBundleAndSubinterfaceId(name string) (bundleId, subinterfaceId int32, err error) {
248+
// Extract bundle ID and optional subinterface ID from Bundle-Ether<id> or Bundle-Ether<id>.<subif_id>
249+
// Examples: Bundle-Ether200 -> (200, 0), Bundle-Ether200.4095 -> (200, 4095)
250+
251+
// Remove the "Bundle-Ether" or "BE" prefix
252+
var idPart string
253+
254+
if !bundleEtherRE.MatchString(name) {
255+
return 0, 0, fmt.Errorf("interface name %q does not start with Bundle-Ether or bundle-ether", name)
256+
}
257+
idPart = strings.TrimPrefix(strings.TrimPrefix(name, "Bundle-Ether"), "bundle-ether")
258+
parts := strings.Split(idPart, ".")
259+
260+
if len(parts) == 0 || parts[0] == "" {
261+
return 0, 0, fmt.Errorf("failed to extract bundle ID from interface name %q", name)
262+
}
263+
264+
// Parse bundle ID
265+
bundleIdInt, err := strconv.ParseInt(parts[0], 10, 32)
266+
if err != nil {
267+
return 0, 0, fmt.Errorf("failed to parse bundle ID from interface name %q: %w", name, err)
268+
}
269+
bundleId = int32(bundleIdInt)
270+
271+
// Parse subinterface ID if present
272+
if len(parts) == 2 {
273+
subIfaceIdInt, err := strconv.ParseInt(parts[1], 10, 32)
274+
if err != nil {
275+
return 0, 0, fmt.Errorf("failed to parse subinterface ID from interface name %q: %w", name, err)
276+
}
277+
subinterfaceId = int32(subIfaceIdInt)
278+
} else if len(parts) > 2 {
279+
return 0, 0, fmt.Errorf("unexpected interface name format %q, expected Bundle-Ether<id> or Bundle-Ether<id>.<subif_id>", name)
280+
}
281+
282+
return bundleId, subinterfaceId, nil
283+
}
284+
285+
func CheckVlanRange(vlan string) error {
286+
v, err := strconv.Atoi(vlan)
287+
288+
if err != nil {
289+
return fmt.Errorf("failed to parse VLAN %q: %w", vlan, err)
290+
}
291+
292+
if v < 1 || v > 4095 {
293+
return fmt.Errorf("VLAN %s is out of range, valid range is 1-4095", vlan)
294+
}
295+
return nil
136296
}

0 commit comments

Comments
 (0)