Skip to content

Commit d455306

Browse files
Add cisco nx-os configuration implementation
The packages provide a variety of configurations for the Cisco Nexus 9000 Platform. Co-authored-by: Pujol <enric.pujol@sap.com>
1 parent 91de13f commit d455306

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+6194
-0
lines changed
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package acl
5+
6+
import (
7+
"fmt"
8+
"net/netip"
9+
10+
"github.com/openconfig/ygot/ygot"
11+
12+
nxos "github.com/ironcore-dev/network-operator/internal/provider/cisco/nxos/genyang"
13+
"github.com/ironcore-dev/network-operator/internal/provider/cisco/nxos/gnmiext"
14+
)
15+
16+
var _ gnmiext.DeviceConf = (*ACL)(nil)
17+
18+
// ACL represents an access control list.
19+
type ACL struct {
20+
// The items of the access control list.
21+
Items []*Item
22+
}
23+
24+
type Item struct {
25+
// The name of the access list.
26+
Name string
27+
// A list of rules to apply.
28+
Rules []*Rule
29+
}
30+
31+
type Rule struct {
32+
// The sequence number of the rule.
33+
Seq uint32
34+
// The action type of ACL Rule. Either 'permit' or 'deny'.
35+
Action Action
36+
// Source IP address or network. Use 'any' to match any source.
37+
Source string
38+
// Destination IP address or network. Use 'any' to match any target.
39+
Destination string
40+
}
41+
42+
//go:generate go tool stringer -type=Action
43+
type Action uint8
44+
45+
const (
46+
Permit Action = iota
47+
Deny
48+
)
49+
50+
// returns a single update forcing the removal of existing ACLs on the target device. Differential updates
51+
// are currently not applicable to ACLs; the entire tree must be replaced.
52+
func (a *ACL) ToYGOT(_ gnmiext.Client) ([]gnmiext.Update, error) {
53+
items := &nxos.Cisco_NX_OSDevice_System_AclItems_Ipv4Items_NameItems{}
54+
items.PopulateDefaults()
55+
for _, i := range a.Items {
56+
aclList := items.GetOrCreateACLList(i.Name)
57+
aclList.PopulateDefaults()
58+
aclListItems := aclList.GetOrCreateSeqItems()
59+
aclListItems.PopulateDefaults()
60+
61+
for _, r := range i.Rules {
62+
aceList := aclListItems.GetOrCreateACEList(r.Seq)
63+
aceList.PopulateDefaults()
64+
// seq
65+
aceList.SeqNum = ygot.Uint32(r.Seq)
66+
// action
67+
var action nxos.E_Cisco_NX_OSDevice_Acl_ActionType
68+
switch r.Action {
69+
case Permit:
70+
action = nxos.Cisco_NX_OSDevice_Acl_ActionType_permit
71+
case Deny:
72+
action = nxos.Cisco_NX_OSDevice_Acl_ActionType_deny
73+
default:
74+
return nil, fmt.Errorf("acl: invalid action type: %s", r.Action)
75+
}
76+
aceList.Action = action
77+
// src
78+
src, err := parse(r.Source)
79+
if err != nil {
80+
return nil, err
81+
}
82+
aceList.SrcPrefix = ygot.String(src.Addr().String())
83+
aceList.SrcPrefixLength = ygot.Uint8(uint8(src.Bits())) //nolint:gosec
84+
// dst
85+
if r.Destination == "" {
86+
r.Destination = "any"
87+
}
88+
dst, err := parse(r.Destination)
89+
if err != nil {
90+
return nil, err
91+
}
92+
aceList.DstPrefix = ygot.String(dst.Addr().String())
93+
aceList.DstPrefixLength = ygot.Uint8(uint8(dst.Bits())) //nolint:gosec
94+
}
95+
}
96+
return []gnmiext.Update{
97+
gnmiext.ReplacingUpdate{
98+
XPath: "System/acl-items/ipv4-items/name-items",
99+
Value: items,
100+
},
101+
}, nil
102+
}
103+
104+
// parse a CIDR string and returns it's [netip.Prefix].
105+
// If the CIDR is "any", it returns '0.0.0.0/0' as the default.
106+
func parse(cidr string) (p netip.Prefix, err error) {
107+
p = netip.PrefixFrom(netip.AddrFrom4([4]byte{0, 0, 0, 0}), 0)
108+
if cidr != "any" {
109+
p, err = netip.ParsePrefix(cidr)
110+
if err == nil && !p.IsValid() {
111+
err = fmt.Errorf("acl: invalid network CIDR: %s", cidr)
112+
}
113+
}
114+
return
115+
}
116+
117+
func (v *ACL) Reset(_ gnmiext.Client) ([]gnmiext.Update, error) {
118+
items := &nxos.Cisco_NX_OSDevice_System_AclItems_Ipv4Items_NameItems{}
119+
items.PopulateDefaults()
120+
121+
return []gnmiext.Update{
122+
gnmiext.ReplacingUpdate{
123+
XPath: "System/acl-items/ipv4-items/name-items",
124+
Value: items,
125+
},
126+
}, nil
127+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package acl
5+
6+
import (
7+
"testing"
8+
9+
nxos "github.com/ironcore-dev/network-operator/internal/provider/cisco/nxos/genyang"
10+
"github.com/ironcore-dev/network-operator/internal/provider/cisco/nxos/gnmiext"
11+
)
12+
13+
func Test_ACL(t *testing.T) {
14+
a := &ACL{
15+
Items: []*Item{
16+
{
17+
Name: "ACL-SNMP-VTY",
18+
Rules: []*Rule{
19+
{
20+
Seq: 10,
21+
Action: Permit,
22+
Source: "10.0.0.0/8",
23+
},
24+
},
25+
},
26+
},
27+
}
28+
29+
got, err := a.ToYGOT(&gnmiext.ClientMock{})
30+
if err != nil {
31+
t.Errorf("unexpected error: %v", err)
32+
}
33+
34+
if len(got) != 1 {
35+
t.Errorf("expected 1 key, got %d", len(got))
36+
}
37+
38+
update, ok := got[0].(gnmiext.ReplacingUpdate)
39+
if !ok {
40+
t.Errorf("expected value to be of type ReplacingUpdate")
41+
}
42+
43+
if update.XPath != "System/acl-items/ipv4-items/name-items" {
44+
t.Errorf("expected key 'System/acl-items/ipv4-items/name-items' to be present")
45+
}
46+
47+
items, ok := update.Value.(*nxos.Cisco_NX_OSDevice_System_AclItems_Ipv4Items_NameItems)
48+
if !ok {
49+
t.Errorf("expected value to be of type *nxos.Cisco_NX_OSDevice_System_AclItems_Ipv4Items_NameItems")
50+
}
51+
52+
if len(items.ACLList) != 1 {
53+
t.Errorf("expected 1 ACL, got %d", len(items.ACLList))
54+
}
55+
56+
if len(items.ACLList["ACL-SNMP-VTY"].SeqItems.ACEList) != 1 {
57+
t.Errorf("expected 1 rule, got %d", len(items.ACLList["ACL-SNMP-VTY"].SeqItems.ACEList))
58+
}
59+
60+
if items.ACLList["ACL-SNMP-VTY"].SeqItems.ACEList[10].Action != nxos.Cisco_NX_OSDevice_Acl_ActionType_permit {
61+
t.Errorf("expected action to be 'permit', got %v", items.ACLList["ACL-SNMP-VTY"].SeqItems.ACEList[10].Action)
62+
}
63+
64+
if *items.ACLList["ACL-SNMP-VTY"].SeqItems.ACEList[10].SrcPrefix != "10.0.0.0" {
65+
t.Errorf("expected source prefix to be '10.0.0.0', got %v", *items.ACLList["ACL-SNMP-VTY"].SeqItems.ACEList[10].SrcPrefix)
66+
}
67+
68+
if *items.ACLList["ACL-SNMP-VTY"].SeqItems.ACEList[10].SrcPrefixLength != 8 {
69+
t.Errorf("expected source mask to be '8', got %d", *items.ACLList["ACL-SNMP-VTY"].SeqItems.ACEList[10].SrcPrefixLength)
70+
}
71+
72+
if *items.ACLList["ACL-SNMP-VTY"].SeqItems.ACEList[10].DstPrefix != "0.0.0.0" {
73+
t.Errorf("expected source prefix to be '0.0.0.0', got %v", *items.ACLList["ACL-SNMP-VTY"].SeqItems.ACEList[10].DstPrefix)
74+
}
75+
76+
if *items.ACLList["ACL-SNMP-VTY"].SeqItems.ACEList[10].DstPrefixLength != 0 {
77+
t.Errorf("expected source mask to be '0', got %d", *items.ACLList["ACL-SNMP-VTY"].SeqItems.ACEList[10].DstPrefixLength)
78+
}
79+
}

internal/provider/cisco/nxos/acl/action_string.go

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package api
5+
6+
import (
7+
"errors"
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 = (*GRPC)(nil)
16+
17+
type GRPC struct {
18+
// Enable the gRPC agent on the switch. The default is false.
19+
Enable bool
20+
// The port number for the grpc server. The range of port-id is from 1024 to 65535. 50051 is the default.
21+
Port uint32
22+
// Enable the gRPC agent to accept incoming (dial-in) RPC requests from a given VRF.
23+
// If left empty, the management VRF processes incoming RPC requests when the gRPC feature is enabled.
24+
Vrf string
25+
// The certificate trustpoint ID.
26+
Trustpoint string
27+
// GNMI configuration.
28+
GNMI *GNMI `json:"gnmi,omitempty"`
29+
}
30+
31+
type GNMI struct {
32+
// The maximum number of concurrent gNMI calls that can be made to the gRPC server on the switch for each VRF.
33+
// Configure a limit from 1 through 16. The default limit is 8.
34+
MaxConcurrentCall uint16
35+
// Configure the keepalive timeout for inactive or unauthorized connections.
36+
// The gRPC agent periodically sends an empty response to the client. If this response fails to deliver to the client, then the connection stops.
37+
// The default interval value is 600 seconds. You can configure to change the keepalive interval with the interval range of 600-86400 seconds.
38+
KeepAliveTimeout uint32
39+
// Configure the minimum sample interval for the gNMI telemetry stream.
40+
// Once per stream sample interval, the switch sends the current values for all specified paths. The supported sample interval range is from 1 through 604,800 second.
41+
// The default sample interval is 10 seconds.
42+
MinSampleInterval uint32
43+
}
44+
45+
// ToYGOT converts the GRPC configuration to a slice of gNMI updates.
46+
func (g *GRPC) ToYGOT(_ gnmiext.Client) ([]gnmiext.Update, error) {
47+
if !g.Enable {
48+
return []gnmiext.Update{
49+
gnmiext.EditingUpdate{
50+
XPath: "System/fm-items/grpc-items",
51+
Value: &nxos.Cisco_NX_OSDevice_System_FmItems_GrpcItems{AdminSt: nxos.Cisco_NX_OSDevice_Fm_AdminState_disabled},
52+
},
53+
}, nil
54+
}
55+
56+
if g.Port == 0 {
57+
g.Port = 50051
58+
}
59+
60+
if g.GNMI == nil {
61+
g.GNMI = &GNMI{}
62+
}
63+
64+
if g.GNMI.MaxConcurrentCall == 0 {
65+
g.GNMI.MaxConcurrentCall = 8
66+
}
67+
68+
if g.GNMI.KeepAliveTimeout == 0 {
69+
g.GNMI.KeepAliveTimeout = 600
70+
}
71+
72+
if g.GNMI.MinSampleInterval == 0 {
73+
g.GNMI.MinSampleInterval = 10
74+
}
75+
76+
var vrf *string
77+
if g.Vrf != "" {
78+
vrf = ygot.String(g.Vrf)
79+
}
80+
81+
return []gnmiext.Update{
82+
gnmiext.EditingUpdate{
83+
XPath: "System/fm-items/grpc-items",
84+
Value: &nxos.Cisco_NX_OSDevice_System_FmItems_GrpcItems{AdminSt: nxos.Cisco_NX_OSDevice_Fm_AdminState_enabled},
85+
},
86+
gnmiext.EditingUpdate{
87+
XPath: "System/grpc-items",
88+
Value: &nxos.Cisco_NX_OSDevice_System_GrpcItems{
89+
Cert: ygot.String(g.Trustpoint),
90+
Port: ygot.Uint32(g.Port),
91+
UseVrf: vrf,
92+
GnmiItems: &nxos.Cisco_NX_OSDevice_System_GrpcItems_GnmiItems{
93+
MaxCalls: ygot.Uint16(g.GNMI.MaxConcurrentCall),
94+
KeepAliveTimeout: ygot.Uint32(g.GNMI.KeepAliveTimeout),
95+
MinSampleInterval: ygot.Uint32(g.GNMI.MinSampleInterval),
96+
},
97+
},
98+
},
99+
}, nil
100+
}
101+
102+
// returns an empty update and an error indicating that the reset is not implemented
103+
func (v *GRPC) Reset(_ gnmiext.Client) ([]gnmiext.Update, error) {
104+
return []gnmiext.Update{}, errors.New("grpc: reset not implemented as it effectively disables management over gNMI")
105+
}

0 commit comments

Comments
 (0)