Skip to content

Commit 6789276

Browse files
(feat): Cisco IOS XR implement bundle interface configuration
1 parent ac17f51 commit 6789276

5 files changed

Lines changed: 394 additions & 106 deletions

File tree

internal/provider/cisco/gnmiext/v2/client.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,6 @@ func (c *client) set(ctx context.Context, patch bool, conf ...Configurable) erro
280280
// If the current configuration does not exist, continue to set the desired configuration.
281281
if status.Code(err) != codes.NotFound {
282282
if err != nil && !errors.Is(err, ErrNil) {
283-
284283
return fmt.Errorf("gnmiext: failed to retrieve current config for %s: %w", cf.XPath(), err)
285284
}
286285
// If the current configuration is equal to the desired configuration, skip the update.

internal/provider/cisco/iosxr/intf.go

Lines changed: 186 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,72 @@
44
package iosxr
55

66
import (
7+
"errors"
78
"fmt"
89
"regexp"
10+
"strconv"
11+
"strings"
912

1013
"github.com/ironcore-dev/network-operator/internal/provider/cisco/gnmiext/v2"
1114
)
1215

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

2675
type Statistics struct {
@@ -29,7 +78,7 @@ type Statistics struct {
2978

3079
type IPv4Network struct {
3180
Addresses AddressesIPv4 `json:"addresses"`
32-
Mtu uint16 `json:"mtu"`
81+
Mtu uint16 `json:"mtu,omitzero"`
3382
}
3483

3584
type AddressesIPv4 struct {
@@ -73,64 +122,157 @@ type MTU struct {
73122
Owner string `json:"owner"`
74123
}
75124

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

80-
func (i *PhysIf) String() string {
81-
return fmt.Sprintf("Name: %s, Description=%s, ShutDown=%t", i.Name, i.Description, i.Shutdown)
152+
func (i *Iface) String() string {
153+
return fmt.Sprintf("Name: %s, Description=%s", i.Name, i.Description)
82154
}
83155

84-
type IFaceSpeed string
156+
type PhysIfState struct {
157+
State string `json:"state"`
158+
Name string `json:"-"`
159+
}
85160

86-
const (
87-
Speed10G IFaceSpeed = "TenGigE"
88-
Speed25G IFaceSpeed = "TwentyFiveGigE"
89-
Speed40G IFaceSpeed = "FortyGigE"
90-
Speed100G IFaceSpeed = "HundredGigE"
91-
)
161+
func (phys *PhysIfState) XPath() string {
162+
// (fixme): hardcoded route processor for the moment
163+
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)
164+
}
165+
166+
func ExractInterfaceSpeedFromName(ifaceName string) (IFaceSpeed, error) {
167+
// Owner of bundle interfaces is 'etherbundle'
168+
bundleEtherRE := regexp.MustCompile(`^Bundle-Ether*`)
169+
if bundleEtherRE.MatchString(ifaceName) {
170+
// For Bundle-Ether interfaces
171+
return EtherBundle, nil
172+
}
92173

93-
func ExtractMTUOwnerFromIfaceName(ifaceName string) (IFaceSpeed, error) {
94174
// Match the port_type in an interface name <port_type>/<rack>/<slot/<module>/<port>
95175
// E.g. match TwentyFiveGigE of interface with name TwentyFiveGigE0/0/0/1
96176
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)
177+
speed := string(re.Find([]byte(ifaceName)))
178+
if speed == "" {
179+
return "", fmt.Errorf("failed to extract speed from interface name %s", ifaceName)
102180
}
103181

104-
switch mtuOwner {
182+
switch speed {
105183
case string(Speed10G):
106184
return Speed10G, nil
107185
case string(Speed25G):
108186
return Speed25G, nil
109187
case string(Speed40G):
110-
return Speed25G, nil
188+
return Speed40G, nil
111189
case string(Speed100G):
112190
return Speed100G, nil
113191
default:
114-
return "", fmt.Errorf("unsupported interface type %s for MTU owner extraction", mtuOwner)
192+
return "", fmt.Errorf("unsupported interface type %s§§", speed)
115193
}
116194
}
117195

118-
type PhysIfStateType string
196+
func CheckInterfaceNameTypeAggregate(name string) error {
197+
if name == "" {
198+
return errors.New("interface name must not be empty")
199+
}
200+
// Matches Bundle-Ether<VLAN>[.<VLAN>] or BE<VLAN>[.<VLAN>]
201+
re := regexp.MustCompile(`^(Bundle-Ether|BE)(\d+)(\.(\d+))?$`)
202+
matches := re.FindStringSubmatch(name)
119203

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-
)
204+
if matches == nil {
205+
return fmt.Errorf("unsupported interface format %q, expected one of: %q", name, re.String())
206+
}
127207

128-
type PhysIfState struct {
129-
State string `json:"state"`
130-
Name string `json:"-"`
208+
// Vlan is part of the name
209+
if matches[2] == "" {
210+
return fmt.Errorf("unsupported interface format %q, expected one of: %q", name, re.String())
211+
}
212+
// Check outer vlan
213+
// fixme: check range up to 65000
214+
// err := CheckVlanRange(matches[2])
215+
216+
// Check inner vlan if we have a subinterface
217+
if matches[4] != "" {
218+
return CheckVlanRange(matches[4])
219+
}
220+
return nil
131221
}
132222

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)
223+
func ExtractVlanTagFromName(name string) (vlanID int32, err error) {
224+
// TF0/0/0/3.2021 or Te0/0/0/3.2021 or Hun0/0/0/3.2021
225+
res := strings.Split(name, ".")
226+
switch len(res) {
227+
case 1:
228+
return 0, nil
229+
case 2:
230+
vlan, err := strconv.ParseInt(res[1], 10, 32)
231+
if err != nil {
232+
return 0, fmt.Errorf("failed to parse VLAN ID from interface name %q: %w", name, err)
233+
}
234+
return int32(vlan), nil
235+
default:
236+
return 0, fmt.Errorf("unexpected interface name format %q, expected <interface> or <interface>.<vlan>", name)
237+
}
238+
}
239+
240+
func ExtractBundleIdAndVlanTagsFromName(name string) (bundleID, outerVlan int32, err error) {
241+
// Matches BE1.1 or Bundle-Ether1.1
242+
re := regexp.MustCompile(`^(Bundle-Ether|BE)(\d+)(?:\.(\d+))?$`)
243+
matches := re.FindStringSubmatch(name)
244+
245+
switch len(matches) {
246+
case 3:
247+
o, err := strconv.ParseInt(matches[2], 10, 32)
248+
if err != nil {
249+
return 0, 0, fmt.Errorf("failed to parse bundle ID from interface name %q: %w", name, err)
250+
}
251+
bundleID = int32(o)
252+
case 4:
253+
o, err := strconv.ParseInt(matches[2], 10, 32)
254+
if err != nil {
255+
return 0, 0, fmt.Errorf("failed to parse bundle ID from interface name %q: %w", name, err)
256+
}
257+
i, err := strconv.ParseInt(matches[3], 10, 32)
258+
if err != nil {
259+
return 0, 0, fmt.Errorf("failed to parse outer VLAN from interface name %q: %w", name, err)
260+
}
261+
bundleID = int32(o)
262+
outerVlan = int32(i)
263+
}
264+
return bundleID, outerVlan, nil
265+
}
266+
267+
func CheckVlanRange(vlan string) error {
268+
v, err := strconv.Atoi(vlan)
269+
270+
if err != nil {
271+
return fmt.Errorf("failed to parse VLAN %q: %w", vlan, err)
272+
}
273+
274+
if v < 1 || v > 4095 {
275+
return fmt.Errorf("VLAN %s is out of range, valid range is 1-4095", vlan)
276+
}
277+
return nil
136278
}

internal/provider/cisco/iosxr/intf_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ func init() {
1111
Owner: "TwentyFiveGigE",
1212
}
1313

14-
Register("intf", &PhysIf{
14+
Register("intf", &Iface{
1515
Name: name,
1616
Description: "random interface test",
1717
Active: "act",

0 commit comments

Comments
 (0)