Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions internal/provider/cisco/nxos/isis/isis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company
// SPDX-License-Identifier: Apache-2.0

package isis

import (
"fmt"

"github.com/openconfig/ygot/ygot"

nxos "github.com/ironcore-dev/network-operator/internal/provider/cisco/nxos/genyang"
"github.com/ironcore-dev/network-operator/internal/provider/cisco/nxos/gnmiext"
)

var _ gnmiext.DeviceConf = (*ISIS)(nil)

type ISIS struct {
// name of the ISIS process, e.g., `router isis UNDERLAY`
Name string
// Network Entity Title, e.g., `net 49.0001.0001.0000.0001.00`
NET string
// Level is type. e.g., `is-type level-1`
Level ISISType
// overloadbit options
OverloadBit *OverloadBit
// supported families
AddressFamilies []ISISAFType
}

type OverloadBit struct {
OnStartup uint32
}

//go:generate stringer -type=ISISType
type ISISType int

const (
Level1 ISISType = iota + 1
Level2
Level12
)

type ISISAFType int

const (
IPv4Unicast = iota + 1
IPv6Unicast
)

func (i *ISIS) ToYGOT(_ gnmiext.Client) ([]gnmiext.Update, error) {
if i.Name == "" {
return nil, fmt.Errorf("isis: name must be set")
}
if i.NET == "" {
return nil, fmt.Errorf("isis: NET must be set")
}
instList := &nxos.Cisco_NX_OSDevice_System_IsisItems_InstItems_InstList{
Name: ygot.String(i.Name),
}

domList := instList.GetOrCreateDomItems().GetOrCreateDomList("default")
domList.Net = ygot.String(i.NET)
switch i.Level {
case Level1:
domList.IsType = nxos.Cisco_NX_OSDevice_Isis_IsT_l1
case Level2:
domList.IsType = nxos.Cisco_NX_OSDevice_Isis_IsT_l2
case Level12:
domList.IsType = nxos.Cisco_NX_OSDevice_Isis_IsT_l12
default:
return nil, fmt.Errorf("isis: invalid level type %d", i.Level)
}

if i.OverloadBit != nil {
olItems := domList.GetOrCreateOverloadItems()
olItems.AdminSt = nxos.Cisco_NX_OSDevice_Isis_OverloadAdminSt_bootup
olItems.StartupTime = ygot.Uint32(i.OverloadBit.OnStartup)
}

for af := range i.AddressFamilies {
switch i.AddressFamilies[af] {
case IPv4Unicast:
domList.GetOrCreateAfItems().GetOrCreateDomAfList(nxos.Cisco_NX_OSDevice_Isis_AfT_v4)
case IPv6Unicast:
domList.GetOrCreateAfItems().GetOrCreateDomAfList(nxos.Cisco_NX_OSDevice_Isis_AfT_v6)
default:
return nil, fmt.Errorf("isis: invalid address family type %d", i.AddressFamilies[af])
}
}

return []gnmiext.Update{
gnmiext.ReplacingUpdate{
XPath: "System/isis-items/inst-items/Inst-list[name=" + i.Name + "]",
Value: instList,
},
}, nil
}

// Reset removes the ISIS process with the given name from the device.
func (i *ISIS) Reset(_ gnmiext.Client) ([]gnmiext.Update, error) {
return []gnmiext.Update{
gnmiext.DeletingUpdate{
XPath: "System/isis-items/inst-items/Inst-list[name=" + i.Name + "]",
},
}, nil
}
168 changes: 168 additions & 0 deletions internal/provider/cisco/nxos/isis/isis_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company
// SPDX-License-Identifier: Apache-2.0
package isis

import (
"testing"

nxos "github.com/ironcore-dev/network-operator/internal/provider/cisco/nxos/genyang"
"github.com/ironcore-dev/network-operator/internal/provider/cisco/nxos/gnmiext"
)

// TestToYGOT tests a configuration with only ISIS for IPv6
func TestToYGOT(t *testing.T) {
isis := &ISIS{
Name: "UNDERLAY",
NET: "49.0001.0001.0000.0001.00",
Level: Level12,
OverloadBit: &OverloadBit{
OnStartup: 61, // seconds
},
AddressFamilies: []ISISAFType{
IPv6Unicast,
},
}
got, err := isis.ToYGOT(nil)
if err != nil {
t.Fatalf("ToYGOT() error = %v", err)
}
if len(got) != 1 {
t.Fatalf("ToYGOT() expected 1 update, got %d", len(got))
}
update, ok := got[0].(gnmiext.ReplacingUpdate)
if !ok {
t.Errorf("expected value to be of type ReplacingUpdate")
}
if update.XPath != "System/isis-items/inst-items/Inst-list[name=UNDERLAY]" {
t.Errorf("expected XPath 'System/isis-items/inst-items/Inst-list[name=UNDERLAY]', got %s", update.XPath)
}
instList, ok := update.Value.(*nxos.Cisco_NX_OSDevice_System_IsisItems_InstItems_InstList)
if !ok {
t.Errorf("expected value to be of type *nxos.Cisco_NX_OSDevice_System_IsisItems_InstItems_InstList")
}
if instList.Name == nil || *instList.Name != "UNDERLAY" {
t.Errorf("expected instList.Name to be 'UNDERLAY', got %v", instList.Name)
}
domList := instList.GetDomItems().GetDomList("default")
if domList == nil {
t.Fatalf("expected domList for default to be present")
}
if *domList.Net != isis.NET {
t.Errorf("Net not set correctly")
}
if domList.IsType != nxos.Cisco_NX_OSDevice_Isis_IsT_l12 {
t.Errorf("Level not set correctly")
}
if domList.GetOverloadItems().AdminSt != nxos.Cisco_NX_OSDevice_Isis_OverloadAdminSt_bootup {
t.Errorf("OverloadBit AdminSt not set correctly")
}
if *domList.GetOverloadItems().StartupTime != isis.OverloadBit.OnStartup {
t.Errorf("OverloadBit StartupTime not set correctly")
}
if len(domList.GetAfItems().DomAfList) != 1 {
t.Errorf("expected 1 address family")
}
if domList.GetAfItems().GetDomAfList(nxos.Cisco_NX_OSDevice_Isis_AfT_v6) == nil {
t.Errorf("expected IPv6 unicast to be enabled, but it is disabled")
}
}

func TestISIS_ToYGOT_InvalidLevel(t *testing.T) {
isis := &ISIS{
Name: "UNDERLAY",
NET: "49.0001.0001.0000.0001.00",
Level: ISISType(99),
AddressFamilies: []ISISAFType{IPv4Unicast},
}
_, err := isis.ToYGOT(&gnmiext.ClientMock{})
if err == nil {
t.Error("expected error for invalid level, got nil")
}
}

func TestISIS_ToYGOT_InvalidAddressFamily(t *testing.T) {
isis := &ISIS{
Name: "UNDERLAY",
NET: "49.0001.0001.0000.0001.00",
Level: Level1,
AddressFamilies: []ISISAFType{ISISAFType(99)},
}
_, err := isis.ToYGOT(&gnmiext.ClientMock{})
if err == nil {
t.Error("expected error for invalid address family, got nil")
}
}

func TestISIS_ToYGOT_NoOverloadBit(t *testing.T) {
isis := &ISIS{
Name: "UNDERLAY",
NET: "49.0001.0001.0000.0001.00",
Level: Level1,
AddressFamilies: []ISISAFType{IPv4Unicast},
OverloadBit: nil,
}
_, err := isis.ToYGOT(&gnmiext.ClientMock{})
if err != nil {
t.Errorf("unexpected error when OverloadBit is nil: %v", err)
}
}

func TestISIS_ToYGOT_EmptyAddressFamilies(t *testing.T) {
isis := &ISIS{
Name: "UNDERLAY",
NET: "49.0001.0001.0000.0001.00",
Level: Level1,
AddressFamilies: []ISISAFType{},
}
updates, err := isis.ToYGOT(&gnmiext.ClientMock{})
if err != nil {
t.Errorf("unexpected error for empty address families: %v", err)
}
if len(updates) != 1 {
t.Errorf("expected 1 update, got %d", len(updates))
}
}

func TestISIS_Reset(t *testing.T) {
isis := &ISIS{Name: "UNDERLAY"}
updates, err := isis.Reset(&gnmiext.ClientMock{})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(updates) != 1 {
t.Fatalf("expected 1 update, got %d", len(updates))
}
du, ok := updates[0].(gnmiext.DeletingUpdate)
if !ok {
t.Fatalf("expected DeletingUpdate, got %T", updates[0])
}
expectedXPath := "System/isis-items/inst-items/Inst-list[name=UNDERLAY]"
if du.XPath != expectedXPath {
t.Errorf("expected XPath %q, got %q", expectedXPath, du.XPath)
}
}

func TestISIS_MissingMandatoryFields(t *testing.T) {
t.Run("missing name", func(t *testing.T) {
isis := &ISIS{
NET: "49.0001.0001.0000.0001.00",
Level: Level1,
AddressFamilies: []ISISAFType{IPv4Unicast},
}
_, err := isis.ToYGOT(&gnmiext.ClientMock{})
if err == nil {
t.Error("expected error for empty name, got nil")
}
})
t.Run("missing NET", func(t *testing.T) {
isis := &ISIS{
Name: "UNDERLAY",
Level: Level1,
AddressFamilies: []ISISAFType{IPv4Unicast},
}
_, err := isis.ToYGOT(&gnmiext.ClientMock{})
if err == nil {
t.Error("expected error for empty NET, got nil")
}
})
}
26 changes: 26 additions & 0 deletions internal/provider/cisco/nxos/isis/isistype_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading