Skip to content

Commit 04ed737

Browse files
fixup! (feat): Cisco IOS XR implement bundle interface configuration
1 parent fe04117 commit 04ed737

3 files changed

Lines changed: 151 additions & 56 deletions

File tree

internal/provider/cisco/iosxr/intf.go

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ import (
1313
)
1414

1515
var (
16-
bundleEtherRE = regexp.MustCompile(`^Bundle-Ether*`)
17-
physicalInterfaceRE = regexp.MustCompile(`(TenGigE|TwentyFiveGigE|FortyGigE|HundredGigE|GigabitEthernet|Te|TF|Fo|Hu)(\d{0,4})(\.(\d+))?$`)
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}`)
1818
)
1919

2020
type IFaceSpeed string
@@ -185,7 +185,7 @@ func ValidateInterfaceName(name string) error {
185185
return fmt.Errorf("unsupported interface name format: %s", name)
186186
}
187187

188-
func ExtractOwnerFromInterfaceName(ifaceName string) (IFaceSpeed, error) {
188+
func ExtractInterfaceSpeedFromName(ifaceName string) (IFaceSpeed, error) {
189189
// Owner of bundle interfaces is 'etherbundle'
190190
if bundleEtherRE.MatchString(ifaceName) {
191191
// For Bundle-Ether interfaces
@@ -210,7 +210,8 @@ func ExtractOwnerFromInterfaceName(ifaceName string) (IFaceSpeed, error) {
210210
case string(Speed100G):
211211
return Speed100G, nil
212212
default:
213-
return "", fmt.Errorf("unsupported interface type %s§§", speed)
213+
fmt.Println("speed: ", "value", speed, " random")
214+
return "", fmt.Errorf("unsupported interface type %s", speed)
214215
}
215216
}
216217

@@ -265,31 +266,42 @@ func ExtractVlanTagFromName(name string) (vlanID int32, err error) {
265266
}
266267
}
267268

268-
func ExtractBundleIdAndVlanTagsFromName(name string) (bundleID, outerVlan int32, err error) {
269-
// Matches BE1.1 or Bundle-Ether1.1
270-
re := regexp.MustCompile(`^(Bundle-Ether|BE)(\d+)(?:\.(\d+))?$`)
271-
matches := re.FindStringSubmatch(name)
269+
func ExtractBundleAndSubinterfaceId(name string) (bundleId, subinterfaceId int32, err error) {
270+
// Extract bundle ID and optional subinterface ID from Bundle-Ether<id> or Bundle-Ether<id>.<subif_id>
271+
// Examples: Bundle-Ether200 -> (200, 0), Bundle-Ether200.4095 -> (200, 4095)
272272

273-
switch len(matches) {
274-
case 3:
275-
o, err := strconv.ParseInt(matches[2], 10, 32)
276-
if err != nil {
277-
return 0, 0, fmt.Errorf("failed to parse bundle ID from interface name %q: %w", name, err)
278-
}
279-
bundleID = int32(o)
280-
case 4:
281-
o, err := strconv.ParseInt(matches[2], 10, 32)
282-
if err != nil {
283-
return 0, 0, fmt.Errorf("failed to parse bundle ID from interface name %q: %w", name, err)
284-
}
285-
i, err := strconv.ParseInt(matches[3], 10, 32)
273+
// Remove the "Bundle-Ether" or "BE" prefix
274+
var idPart string
275+
276+
if !bundleEtherRE.MatchString(name) {
277+
return 0, 0, fmt.Errorf("interface name %q does not start with Bundle-Ether or bundle-ether", name)
278+
}
279+
idPart = strings.TrimPrefix(strings.TrimPrefix(name, "Bundle-Ether"), "bundle-ether")
280+
parts := strings.Split(idPart, ".")
281+
282+
if len(parts) == 0 || parts[0] == "" {
283+
return 0, 0, fmt.Errorf("failed to extract bundle ID from interface name %q", name)
284+
}
285+
286+
// Parse bundle ID
287+
bundleIdInt, err := strconv.ParseInt(parts[0], 10, 32)
288+
if err != nil {
289+
return 0, 0, fmt.Errorf("failed to parse bundle ID from interface name %q: %w", name, err)
290+
}
291+
bundleId = int32(bundleIdInt)
292+
293+
// Parse subinterface ID if present
294+
if len(parts) == 2 {
295+
subIfaceIdInt, err := strconv.ParseInt(parts[1], 10, 32)
286296
if err != nil {
287-
return 0, 0, fmt.Errorf("failed to parse outer VLAN from interface name %q: %w", name, err)
297+
return 0, 0, fmt.Errorf("failed to parse subinterface ID from interface name %q: %w", name, err)
288298
}
289-
bundleID = int32(o)
290-
outerVlan = int32(i)
299+
subinterfaceId = int32(subIfaceIdInt)
300+
} else if len(parts) > 2 {
301+
return 0, 0, fmt.Errorf("unexpected interface name format %q, expected Bundle-Ether<id> or Bundle-Ether<id>.<subif_id>", name)
291302
}
292-
return bundleID, outerVlan, nil
303+
304+
return bundleId, subinterfaceId, nil
293305
}
294306

295307
func CheckVlanRange(vlan string) error {

internal/provider/cisco/iosxr/intf_test.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,59 @@ func init() {
5454
})
5555
}
5656

57+
func TestExtractBundleAndSubinterfaceId(t *testing.T) {
58+
tests := []struct {
59+
name string
60+
input string
61+
expectedBundleId int32
62+
expectedSubIfaceId int32
63+
wantErr bool
64+
}{
65+
{
66+
name: "Bundle-Ether with subinterface",
67+
input: "Bundle-Ether200.4095",
68+
expectedBundleId: 200,
69+
expectedSubIfaceId: 4095,
70+
wantErr: false,
71+
},
72+
{
73+
name: "Bundle-Ether with subinterface",
74+
input: "Bundle-Ether200",
75+
expectedBundleId: 200,
76+
expectedSubIfaceId: 0,
77+
wantErr: false,
78+
},
79+
{
80+
name: "Bundle-Ether with subinterface",
81+
input: "Bundle-Ether200.100.100",
82+
expectedBundleId: 0,
83+
expectedSubIfaceId: 0,
84+
wantErr: true,
85+
},
86+
}
87+
88+
for _, tt := range tests {
89+
t.Run(tt.name, func(t *testing.T) {
90+
bundleId, subIfaceId, err := ExtractBundleAndSubinterfaceId(tt.input)
91+
if tt.wantErr {
92+
if err == nil {
93+
t.Errorf("ExtractBundleAndSubinterfaceId(%s) expected error, got nil", tt.input)
94+
}
95+
} else {
96+
if err != nil {
97+
t.Errorf("ExtractBundleAndSubinterfaceId(%s) unexpected error: %v", tt.input, err)
98+
}
99+
if bundleId != tt.expectedBundleId {
100+
t.Errorf("ExtractBundleAndSubinterfaceId(%s) bundleId = %v, want %v", tt.input, bundleId, tt.expectedBundleId)
101+
}
102+
if subIfaceId != tt.expectedSubIfaceId {
103+
t.Errorf("ExtractBundleAndSubinterfaceId(%s) subIfaceId = %v, want %v", tt.input, subIfaceId, tt.expectedSubIfaceId)
104+
}
105+
}
106+
})
107+
}
108+
}
109+
57110
func TestValidateInterfaceName(t *testing.T) {
58111
tests := []struct {
59112
name string
@@ -108,3 +161,37 @@ func TestValidateInterfaceName(t *testing.T) {
108161
})
109162
}
110163
}
164+
165+
func TestExtractInterfaceSpeedFromName(t *testing.T) {
166+
tests := []struct {
167+
name string
168+
ifaceName string
169+
expectedSpeed IFaceSpeed
170+
wantErr bool
171+
}{
172+
{
173+
name: "TF short form for TwentyFiveGigE",
174+
ifaceName: "TF0/0/0/33",
175+
expectedSpeed: Speed25G,
176+
wantErr: false,
177+
},
178+
}
179+
180+
for _, tt := range tests {
181+
t.Run(tt.name, func(t *testing.T) {
182+
speed, err := ExtractInterfaceSpeedFromName(tt.ifaceName)
183+
if tt.wantErr {
184+
if err == nil {
185+
t.Errorf("ExtractInterfaceSpeedFromName(%s) expected error, got nil", tt.ifaceName)
186+
}
187+
} else {
188+
if err != nil {
189+
t.Errorf("ExtractInterfaceSpeedFromName(%s) unexpected error: %v", tt.ifaceName, err)
190+
}
191+
if speed != tt.expectedSpeed {
192+
t.Errorf("ExtractInterfaceSpeedFromName(%s) = %v, want %v", tt.ifaceName, speed, tt.expectedSpeed)
193+
}
194+
}
195+
})
196+
}
197+
}

internal/provider/cisco/iosxr/provider.go

Lines changed: 27 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ func (p *Provider) EnsureInterface(ctx context.Context, req *provider.EnsureInte
8181

8282
// Check if interface is part of a bundle
8383
// Bundle configuration needs to happen in a separate gnmi call
84-
bundle_name := req.Interface.GetAnnotations()[v1alpha1.AggregateLabel]
84+
bundle_name := req.Interface.GetLabels()[v1alpha1.AggregateLabel]
8585
if bundle_name == "" {
8686
iface.Statistics.LoadInterval = uint8(30)
8787

@@ -130,7 +130,7 @@ func (p *Provider) EnsureInterface(ctx context.Context, req *provider.EnsureInte
130130
if bundle_name != "" {
131131
ifaceBundeConf := &Iface{}
132132
ifaceBundeConf.Name = name
133-
bundle_id, _, err := ExtractBundleIdAndVlanTagsFromName(bundle_name)
133+
bundle_id, _, err := ExtractBundleAndSubinterfaceId(bundle_name)
134134
if err != nil {
135135
return err
136136
}
@@ -161,59 +161,55 @@ func (p *Provider) EnsureInterface(ctx context.Context, req *provider.EnsureInte
161161

162162
iface := NewBundleInterface(req.Interface)
163163

164-
// Presence of an outerVlan Tag in the interface name indicates a subinterface
165-
// BE<id>.<VLAN_ID>
166-
_, outerVlan, err := ExtractBundleIdAndVlanTagsFromName(name)
164+
bundleId, subinterfaceId, err := ExtractBundleAndSubinterfaceId(name)
167165
if err != nil {
168166
return err
169167
}
170168

171-
if outerVlan != 0 {
172-
if req.Interface.Spec.Switchport != nil && outerVlan != req.Interface.Spec.Switchport.AccessVlan {
173-
message := fmt.Sprintf("AccessVlan must match bundle-ether name pattern BE<id>.<ACCESS_VLAN>. %d != %d",
174-
outerVlan, req.Interface.Spec.Switchport.AccessVlan)
175-
return errors.New(message)
176-
}
169+
if bundleId == 0 {
170+
return fmt.Errorf("failed to extract bundle ID from interface name %q", name)
171+
}
177172

178-
// Unset for bundle subinterfaces
179-
iface.Mode = gnmiext.Empty(false)
173+
//Bundle interface configuration
174+
if subinterfaceId == 0 {
175+
iface.Statistics.LoadInterval = uint8(30)
180176

177+
mtu, err := NewMTU(name, req.Interface.Spec.MTU)
178+
if err != nil {
179+
return err
180+
}
181+
iface.MTUs = mtu
182+
183+
iface.Bundle = Bundle{
184+
MinAct: MinimumActive{
185+
Links: 1,
186+
},
187+
}
188+
conf = append(conf, &iface)
189+
} else { //Bundle subinterface configuration
181190
// make sure the parent bundle-ether interface bundle-ether<id> exits
182191
parentBunndle := strings.Split(name, ".")[0]
183192
tmp := cp.Deep(req.Interface)
184193
tmp.Spec.Name = parentBunndle
185194
bundle := NewBundleInterface(tmp)
186195
conf = append(conf, &bundle)
187196

197+
// Unset for bundle subinterfaces
198+
iface.Mode = gnmiext.Empty(false)
188199
iface.ModeNoPhysical = "default"
189200
iface.SubInterface = VlanSubInterface{
190201
VlanIdentifier: VlanIdentifier{
191-
FirstTag: outerVlan,
202+
FirstTag: req.Interface.Spec.Switchport.AccessVlan,
192203
VlanType: "vlan-type-dot1q",
193204
},
194205
}
195206

196207
// Subinterface configures QAndQ vlan
197-
if req.Interface.Spec.Switchport != nil && req.Interface.Spec.Switchport.AccessVlan != 0 {
198-
iface.SubInterface.VlanIdentifier.SecondTag = req.Interface.Spec.Switchport.AccessVlan
208+
if req.Interface.Spec.Switchport.InnerVlan != 0 {
209+
iface.SubInterface.VlanIdentifier.SecondTag = req.Interface.Spec.Switchport.InnerVlan
199210
iface.SubInterface.VlanIdentifier.VlanType = "vlan-type-dot1ad"
200211
}
201212
conf = append(conf, &iface)
202-
} else {
203-
iface.Statistics.LoadInterval = uint8(30)
204-
205-
mtu, err := NewMTU(name, req.Interface.Spec.MTU)
206-
if err != nil {
207-
return err
208-
}
209-
iface.MTUs = mtu
210-
211-
iface.Bundle = Bundle{
212-
MinAct: MinimumActive{
213-
Links: 1,
214-
},
215-
}
216-
conf = append(conf, &iface)
217213
}
218214
return updateInterface(ctx, p.client, conf...)
219215
}

0 commit comments

Comments
 (0)