Skip to content

Commit 76cc0c8

Browse files
authored
cisco-nxos-provider: configure ISIS
Configure ISIS on a NXOS device allowing the parametrization of: 1) the instance name, 2) the Network Entitity Title, 3) the type ("Level1", "Level2", and "Level12"), 4) the number of seconds of the on-startup overload bit, and 5) two address families: IPv4 and IPv6 unicast. With this package we support the minimum configuration required to build the underlay as in this example: ``` router isis UNDERLAY net 49.0001.0001.0000.0001.00 is-type level-1 set-overload-bit on-startup 61 address-family ipv4 unicast address-family ipv6 unicast ```
1 parent 1d6e8ec commit 76cc0c8

File tree

3 files changed

+300
-0
lines changed

3 files changed

+300
-0
lines changed
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package isis
5+
6+
import (
7+
"fmt"
8+
9+
"github.com/openconfig/ygot/ygot"
10+
11+
nxos "github.com/ironcore-dev/network-operator/internal/provider/cisco/nxos/genyang"
12+
"github.com/ironcore-dev/network-operator/internal/provider/cisco/nxos/gnmiext"
13+
)
14+
15+
var _ gnmiext.DeviceConf = (*ISIS)(nil)
16+
17+
type ISIS struct {
18+
// name of the ISIS process, e.g., `router isis UNDERLAY`
19+
Name string
20+
// Network Entity Title, e.g., `net 49.0001.0001.0000.0001.00`
21+
NET string
22+
// Level is type. e.g., `is-type level-1`
23+
Level ISISType
24+
// overloadbit options
25+
OverloadBit *OverloadBit
26+
// supported families
27+
AddressFamilies []ISISAFType
28+
}
29+
30+
type OverloadBit struct {
31+
OnStartup uint32
32+
}
33+
34+
//go:generate stringer -type=ISISType
35+
type ISISType int
36+
37+
const (
38+
Level1 ISISType = iota + 1
39+
Level2
40+
Level12
41+
)
42+
43+
type ISISAFType int
44+
45+
const (
46+
IPv4Unicast = iota + 1
47+
IPv6Unicast
48+
)
49+
50+
func (i *ISIS) ToYGOT(_ gnmiext.Client) ([]gnmiext.Update, error) {
51+
if i.Name == "" {
52+
return nil, fmt.Errorf("isis: name must be set")
53+
}
54+
if i.NET == "" {
55+
return nil, fmt.Errorf("isis: NET must be set")
56+
}
57+
instList := &nxos.Cisco_NX_OSDevice_System_IsisItems_InstItems_InstList{
58+
Name: ygot.String(i.Name),
59+
}
60+
61+
domList := instList.GetOrCreateDomItems().GetOrCreateDomList("default")
62+
domList.Net = ygot.String(i.NET)
63+
switch i.Level {
64+
case Level1:
65+
domList.IsType = nxos.Cisco_NX_OSDevice_Isis_IsT_l1
66+
case Level2:
67+
domList.IsType = nxos.Cisco_NX_OSDevice_Isis_IsT_l2
68+
case Level12:
69+
domList.IsType = nxos.Cisco_NX_OSDevice_Isis_IsT_l12
70+
default:
71+
return nil, fmt.Errorf("isis: invalid level type %d", i.Level)
72+
}
73+
74+
if i.OverloadBit != nil {
75+
olItems := domList.GetOrCreateOverloadItems()
76+
olItems.AdminSt = nxos.Cisco_NX_OSDevice_Isis_OverloadAdminSt_bootup
77+
olItems.StartupTime = ygot.Uint32(i.OverloadBit.OnStartup)
78+
}
79+
80+
for af := range i.AddressFamilies {
81+
switch i.AddressFamilies[af] {
82+
case IPv4Unicast:
83+
domList.GetOrCreateAfItems().GetOrCreateDomAfList(nxos.Cisco_NX_OSDevice_Isis_AfT_v4)
84+
case IPv6Unicast:
85+
domList.GetOrCreateAfItems().GetOrCreateDomAfList(nxos.Cisco_NX_OSDevice_Isis_AfT_v6)
86+
default:
87+
return nil, fmt.Errorf("isis: invalid address family type %d", i.AddressFamilies[af])
88+
}
89+
}
90+
91+
return []gnmiext.Update{
92+
gnmiext.ReplacingUpdate{
93+
XPath: "System/isis-items/inst-items/Inst-list[name=" + i.Name + "]",
94+
Value: instList,
95+
},
96+
}, nil
97+
}
98+
99+
// Reset removes the ISIS process with the given name from the device.
100+
func (i *ISIS) Reset(_ gnmiext.Client) ([]gnmiext.Update, error) {
101+
return []gnmiext.Update{
102+
gnmiext.DeletingUpdate{
103+
XPath: "System/isis-items/inst-items/Inst-list[name=" + i.Name + "]",
104+
},
105+
}, nil
106+
}
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company
2+
// SPDX-License-Identifier: Apache-2.0
3+
package isis
4+
5+
import (
6+
"testing"
7+
8+
nxos "github.com/ironcore-dev/network-operator/internal/provider/cisco/nxos/genyang"
9+
"github.com/ironcore-dev/network-operator/internal/provider/cisco/nxos/gnmiext"
10+
)
11+
12+
// TestToYGOT tests a configuration with only ISIS for IPv6
13+
func TestToYGOT(t *testing.T) {
14+
isis := &ISIS{
15+
Name: "UNDERLAY",
16+
NET: "49.0001.0001.0000.0001.00",
17+
Level: Level12,
18+
OverloadBit: &OverloadBit{
19+
OnStartup: 61, // seconds
20+
},
21+
AddressFamilies: []ISISAFType{
22+
IPv6Unicast,
23+
},
24+
}
25+
got, err := isis.ToYGOT(nil)
26+
if err != nil {
27+
t.Fatalf("ToYGOT() error = %v", err)
28+
}
29+
if len(got) != 1 {
30+
t.Fatalf("ToYGOT() expected 1 update, got %d", len(got))
31+
}
32+
update, ok := got[0].(gnmiext.ReplacingUpdate)
33+
if !ok {
34+
t.Errorf("expected value to be of type ReplacingUpdate")
35+
}
36+
if update.XPath != "System/isis-items/inst-items/Inst-list[name=UNDERLAY]" {
37+
t.Errorf("expected XPath 'System/isis-items/inst-items/Inst-list[name=UNDERLAY]', got %s", update.XPath)
38+
}
39+
instList, ok := update.Value.(*nxos.Cisco_NX_OSDevice_System_IsisItems_InstItems_InstList)
40+
if !ok {
41+
t.Errorf("expected value to be of type *nxos.Cisco_NX_OSDevice_System_IsisItems_InstItems_InstList")
42+
}
43+
if instList.Name == nil || *instList.Name != "UNDERLAY" {
44+
t.Errorf("expected instList.Name to be 'UNDERLAY', got %v", instList.Name)
45+
}
46+
domList := instList.GetDomItems().GetDomList("default")
47+
if domList == nil {
48+
t.Fatalf("expected domList for default to be present")
49+
}
50+
if *domList.Net != isis.NET {
51+
t.Errorf("Net not set correctly")
52+
}
53+
if domList.IsType != nxos.Cisco_NX_OSDevice_Isis_IsT_l12 {
54+
t.Errorf("Level not set correctly")
55+
}
56+
if domList.GetOverloadItems().AdminSt != nxos.Cisco_NX_OSDevice_Isis_OverloadAdminSt_bootup {
57+
t.Errorf("OverloadBit AdminSt not set correctly")
58+
}
59+
if *domList.GetOverloadItems().StartupTime != isis.OverloadBit.OnStartup {
60+
t.Errorf("OverloadBit StartupTime not set correctly")
61+
}
62+
if len(domList.GetAfItems().DomAfList) != 1 {
63+
t.Errorf("expected 1 address family")
64+
}
65+
if domList.GetAfItems().GetDomAfList(nxos.Cisco_NX_OSDevice_Isis_AfT_v6) == nil {
66+
t.Errorf("expected IPv6 unicast to be enabled, but it is disabled")
67+
}
68+
}
69+
70+
func TestISIS_ToYGOT_InvalidLevel(t *testing.T) {
71+
isis := &ISIS{
72+
Name: "UNDERLAY",
73+
NET: "49.0001.0001.0000.0001.00",
74+
Level: ISISType(99),
75+
AddressFamilies: []ISISAFType{IPv4Unicast},
76+
}
77+
_, err := isis.ToYGOT(&gnmiext.ClientMock{})
78+
if err == nil {
79+
t.Error("expected error for invalid level, got nil")
80+
}
81+
}
82+
83+
func TestISIS_ToYGOT_InvalidAddressFamily(t *testing.T) {
84+
isis := &ISIS{
85+
Name: "UNDERLAY",
86+
NET: "49.0001.0001.0000.0001.00",
87+
Level: Level1,
88+
AddressFamilies: []ISISAFType{ISISAFType(99)},
89+
}
90+
_, err := isis.ToYGOT(&gnmiext.ClientMock{})
91+
if err == nil {
92+
t.Error("expected error for invalid address family, got nil")
93+
}
94+
}
95+
96+
func TestISIS_ToYGOT_NoOverloadBit(t *testing.T) {
97+
isis := &ISIS{
98+
Name: "UNDERLAY",
99+
NET: "49.0001.0001.0000.0001.00",
100+
Level: Level1,
101+
AddressFamilies: []ISISAFType{IPv4Unicast},
102+
OverloadBit: nil,
103+
}
104+
_, err := isis.ToYGOT(&gnmiext.ClientMock{})
105+
if err != nil {
106+
t.Errorf("unexpected error when OverloadBit is nil: %v", err)
107+
}
108+
}
109+
110+
func TestISIS_ToYGOT_EmptyAddressFamilies(t *testing.T) {
111+
isis := &ISIS{
112+
Name: "UNDERLAY",
113+
NET: "49.0001.0001.0000.0001.00",
114+
Level: Level1,
115+
AddressFamilies: []ISISAFType{},
116+
}
117+
updates, err := isis.ToYGOT(&gnmiext.ClientMock{})
118+
if err != nil {
119+
t.Errorf("unexpected error for empty address families: %v", err)
120+
}
121+
if len(updates) != 1 {
122+
t.Errorf("expected 1 update, got %d", len(updates))
123+
}
124+
}
125+
126+
func TestISIS_Reset(t *testing.T) {
127+
isis := &ISIS{Name: "UNDERLAY"}
128+
updates, err := isis.Reset(&gnmiext.ClientMock{})
129+
if err != nil {
130+
t.Fatalf("unexpected error: %v", err)
131+
}
132+
if len(updates) != 1 {
133+
t.Fatalf("expected 1 update, got %d", len(updates))
134+
}
135+
du, ok := updates[0].(gnmiext.DeletingUpdate)
136+
if !ok {
137+
t.Fatalf("expected DeletingUpdate, got %T", updates[0])
138+
}
139+
expectedXPath := "System/isis-items/inst-items/Inst-list[name=UNDERLAY]"
140+
if du.XPath != expectedXPath {
141+
t.Errorf("expected XPath %q, got %q", expectedXPath, du.XPath)
142+
}
143+
}
144+
145+
func TestISIS_MissingMandatoryFields(t *testing.T) {
146+
t.Run("missing name", func(t *testing.T) {
147+
isis := &ISIS{
148+
NET: "49.0001.0001.0000.0001.00",
149+
Level: Level1,
150+
AddressFamilies: []ISISAFType{IPv4Unicast},
151+
}
152+
_, err := isis.ToYGOT(&gnmiext.ClientMock{})
153+
if err == nil {
154+
t.Error("expected error for empty name, got nil")
155+
}
156+
})
157+
t.Run("missing NET", func(t *testing.T) {
158+
isis := &ISIS{
159+
Name: "UNDERLAY",
160+
Level: Level1,
161+
AddressFamilies: []ISISAFType{IPv4Unicast},
162+
}
163+
_, err := isis.ToYGOT(&gnmiext.ClientMock{})
164+
if err == nil {
165+
t.Error("expected error for empty NET, got nil")
166+
}
167+
})
168+
}

internal/provider/cisco/nxos/isis/isistype_string.go

Lines changed: 26 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)