Skip to content

Commit 0895369

Browse files
feat: Add Cisco IOS-XR Provider and implement interface stubs
1 parent 50ae38f commit 0895369

File tree

6 files changed

+458
-23
lines changed

6 files changed

+458
-23
lines changed
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
package iosxr
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"regexp"
7+
)
8+
9+
// YANG has an empty type that is represented as a JSON array with a single null value. Whenever the key is present in YANG, it should evaluate to true.
10+
type Empty bool
11+
12+
func (e *Empty) UnmarshalJSON(b []byte) error {
13+
var elements []any
14+
if err := json.Unmarshal(b, &elements); err != nil {
15+
return err
16+
}
17+
18+
// Check if the data is a JSON array with a single null value
19+
if len(elements) == 1 && elements[0] == nil {
20+
*e = true
21+
return nil
22+
}
23+
*e = false
24+
return nil
25+
}
26+
27+
func (e Empty) MarshalJSON() ([]byte, error) {
28+
// If e is true, marshal as a JSON array with a single null value
29+
if e {
30+
return json.Marshal([]any{nil})
31+
}
32+
return nil, nil
33+
}
34+
35+
type PhisIf struct {
36+
Name string `json:"-"`
37+
Description string `json:"description,omitempty"`
38+
Active string `json:"active,omitempty"`
39+
Vrf string `json:"Cisco-IOS-XR-infra-rsi-cfg:vrf,omitempty"`
40+
Statistics Statistics `json:"Cisco-IOS-XR-infra-statsd-cfg:statistics,omitzero"`
41+
IPv4Network IPv4Network `json:"Cisco-IOS-XR-ipv4-io-cfg:ipv4-network,omitzero"`
42+
IPv6Network IPv6Network `json:"Cisco-IOS-XR-ipv6-ma-cfg:ipv6-network,omitzero"`
43+
IPv6Neighbor IPv6Neighbor `json:"Cisco-IOS-XR-ipv6-nd-cfg:ipv6-neighbor,omitzero"`
44+
MTUs MTUs `json:"mtus,omitzero"`
45+
Shutdown Empty `json:"shutdown,omitzero"`
46+
}
47+
48+
type Statistics struct {
49+
LoadInterval uint8 `json:"load-interval,omitzero"`
50+
}
51+
52+
type IPv4Network struct {
53+
Addresses AddressesIPv4 `json:"addresses,omitzero"`
54+
Mtu uint16 `json:"mtu,omitzero"`
55+
}
56+
57+
type AddressesIPv4 struct {
58+
Primary Primary `json:"primary,omitzero"`
59+
}
60+
61+
type Primary struct {
62+
Address string `json:"address,omitempty"`
63+
Netmask string `json:"netmask,omitempty"`
64+
}
65+
66+
type IPv6Network struct {
67+
Mtu uint16 `json:"mtu,omitzero"`
68+
Addresses AddressesIPv6 `json:"addresses,omitzero"`
69+
}
70+
71+
type AddressesIPv6 struct {
72+
RegularAddresses RegularAddresses `json:"regular-addresses,omitzero"`
73+
}
74+
75+
type RegularAddresses struct {
76+
RegularAddress []RegularAddress `json:"regular-address,omitempty"`
77+
}
78+
79+
type RegularAddress struct {
80+
Address string `json:"address,omitempty"`
81+
PrefixLength uint8 `json:"prefix-length,omitzero"`
82+
Zone string `json:"zone,omitempty"`
83+
}
84+
85+
type IPv6Neighbor struct {
86+
RASuppress bool `json:"ra-suppress,omitempty"`
87+
}
88+
89+
type MTUs struct {
90+
MTU []MTU `json:"mtu,omitempty"`
91+
}
92+
93+
type MTU struct {
94+
MTU int32 `json:"mtu,omitzero"`
95+
Owner string `json:"owner,omitempty"`
96+
}
97+
98+
func (i *PhisIf) XPath() string {
99+
return fmt.Sprintf("Cisco-IOS-XR-ifmgr-cfg:interface-configurations/interface-configuration[active=act][interface-name=%s]", i.Name)
100+
}
101+
102+
func NewIface(name string) *PhisIf {
103+
return &PhisIf{
104+
Name: name,
105+
Statistics: Statistics{},
106+
IPv4Network: IPv4Network{},
107+
IPv6Network: IPv6Network{},
108+
MTUs: MTUs{},
109+
}
110+
}
111+
112+
func (i *PhisIf) String() string {
113+
return fmt.Sprintf("Name: %s, Description=%s, ShutDown=%t", i.Name, i.Description, i.Shutdown)
114+
}
115+
116+
type IFaceSpeed string
117+
118+
const (
119+
TenGig IFaceSpeed = "TenGigE"
120+
TwentyFiveGig IFaceSpeed = "TwentyFiveGigE"
121+
FortyGig IFaceSpeed = "FortyGigE"
122+
HundredGig IFaceSpeed = "HundredGigE"
123+
)
124+
125+
func ExractMTUOwnerFromIfaceName(ifaceName string) (IFaceSpeed, error) {
126+
re := regexp.MustCompile(`^\D*`)
127+
128+
mtuOwner := string(re.Find([]byte(ifaceName)))
129+
130+
if mtuOwner == "" {
131+
return "", fmt.Errorf("failed to extract MTU owner from interface name %s", ifaceName)
132+
}
133+
134+
switch mtuOwner {
135+
case string(TenGig):
136+
return TenGig, nil
137+
case string(TwentyFiveGig):
138+
return TwentyFiveGig, nil
139+
case string(FortyGig):
140+
return FortyGig, nil
141+
case string(HundredGig):
142+
return HundredGig, nil
143+
default:
144+
return "", fmt.Errorf("unsupported interface type %s for MTU owner extraction", mtuOwner)
145+
}
146+
}
147+
148+
type PhysIfStates int
149+
150+
const (
151+
StateUp PhysIfStates = iota
152+
StateDown
153+
StateNotReady
154+
StateAdminDown
155+
StateShutDown
156+
)
157+
158+
var stateMapping = map[string]PhysIfStates{
159+
"im-state-not-ready": StateNotReady,
160+
"im-state-down": StateDown,
161+
"im-state-up": StateUp,
162+
"im-state-shutdown": StateShutDown,
163+
}
164+
165+
type PhysIfState struct {
166+
State string `json:"state,omitempty"`
167+
Name string `json:"-,omitempty"`
168+
}
169+
170+
func (phys *PhysIfState) XPath() string {
171+
// (fixme): hardcoded route processor for the moment
172+
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)
173+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package iosxr
2+
3+
func init() {
4+
name := "TwentyFiveGigE0/0/0/14"
5+
6+
mtu := MTU{
7+
MTU: 9026,
8+
Owner: "TwentyFiveGigE",
9+
}
10+
11+
Register("intf", &PhisIf{
12+
Name: name,
13+
Description: "test",
14+
Active: "act",
15+
MTUs: MTUs{
16+
[]MTU{mtu},
17+
},
18+
IPv4Network: IPv4Network{
19+
Addresses: AddressesIPv4{
20+
Primary: Primary{
21+
Address: "192.168.1.2",
22+
Netmask: "255.255.255.0",
23+
},
24+
},
25+
},
26+
})
27+
}

internal/provider/cisco/iosxr/provider.go

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ package iosxr
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
7+
"strconv"
68

79
"github.com/ironcore-dev/network-operator/internal/deviceutil"
810
"github.com/ironcore-dev/network-operator/internal/provider"
911
"github.com/ironcore-dev/network-operator/internal/provider/cisco/gnmiext/v2"
12+
1013
"google.golang.org/grpc"
1114
)
1215

@@ -43,21 +46,22 @@ func (p *Provider) Disconnect(ctx context.Context, conn *deviceutil.Connection)
4346

4447
func (p *Provider) EnsureInterface(ctx context.Context, req *provider.InterfaceRequest) error {
4548
if p.client == nil {
46-
return fmt.Errorf("client is not connected")
49+
return errors.New("client is not connected")
4750
}
48-
var name string = req.Interface.Spec.Name
51+
var name = req.Interface.Spec.Name
4952

50-
var physif *PhisIf = NewIface(name)
53+
var physif = NewIface(name)
5154

5255
physif.Name = req.Interface.Spec.Name
5356
physif.Description = req.Interface.Spec.Description
5457

5558
physif.Statistics.LoadInterval = 30
5659
owner, err := ExractMTUOwnerFromIfaceName(name)
5760
if err != nil {
58-
return fmt.Errorf("failed to extract MTU owner from interface name %s: %w", name, err)
61+
message := "failed to extract MTU owner from interface name" + name
62+
return errors.New(message)
5963
}
60-
physif.MTUs = MTUs{MTU: []MTU{{MTU: uint16(req.Interface.Spec.MTU), Owner: string(owner)}}}
64+
physif.MTUs = MTUs{MTU: []MTU{{MTU: req.Interface.Spec.MTU, Owner: string(owner)}}}
6165

6266
// (fixme): for the moment it is enought to keep this static
6367
// option1: extend existing interface spec
@@ -66,31 +70,30 @@ func (p *Provider) EnsureInterface(ctx context.Context, req *provider.InterfaceR
6670
physif.Statistics.LoadInterval = uint8(30)
6771

6872
if len(req.Interface.Spec.IPv4.Addresses) == 0 {
69-
return fmt.Errorf("no IPv4 address configured for interface %s", name)
73+
message := "no IPv4 address configured for interface " + name
74+
return errors.New(message)
7075
}
7176

7277
if len(req.Interface.Spec.IPv4.Addresses) > 1 {
73-
return fmt.Errorf("only a single primary IPv4 address is supported for interface %s", name)
78+
message := "multiple IPv4 addresses configured for interface " + name
79+
return errors.New(message)
7480
}
7581

7682
// (fixme): support IPv6 addresses, IPv6 neighbor config
77-
ip := req.Interface.Spec.IPv4.Addresses[0].Prefix.Addr().String()
78-
ipNet := req.Interface.Spec.IPv4.Addresses[0].Prefix.Bits()
79-
if err != nil {
80-
return fmt.Errorf("failed to parse IPv4 address %s: %w", req.Interface.Spec.IPv4.Addresses[0], err)
81-
}
83+
ip := req.Interface.Spec.IPv4.Addresses[0].Addr().String()
84+
ipNet := req.Interface.Spec.IPv4.Addresses[0].Bits()
8285

8386
physif.IPv4Network = IPv4Network{
8487
Addresses: AddressesIPv4{
8588
Primary: Primary{
8689
Address: ip,
87-
Netmask: string(ipNet),
90+
Netmask: strconv.Itoa(ipNet),
8891
},
8992
},
9093
}
9194

9295
// Check if interface exists otherwise patch will fail
93-
var tmpiFace *PhisIf = NewIface(name)
96+
var tmpiFace = NewIface(name)
9497
err = p.client.GetConfig(ctx, tmpiFace)
9598
if err != nil {
9699
// Interface does not exist, create it
@@ -114,7 +117,7 @@ func (p *Provider) DeleteInterface(ctx context.Context, req *provider.InterfaceR
114117
var iFace = NewIface(req.Interface.Spec.Name)
115118

116119
if p.client == nil {
117-
return fmt.Errorf("client is not connected")
120+
return errors.New("client is not connected")
118121
}
119122

120123
err := p.client.Delete(ctx, iFace)
@@ -129,10 +132,10 @@ func (p *Provider) GetInterfaceStatus(ctx context.Context, req *provider.Interfa
129132
state.Name = req.Interface.Spec.Name
130133

131134
if p.client == nil {
132-
return provider.InterfaceStatus{}, fmt.Errorf("client is not connected")
135+
return provider.InterfaceStatus{}, errors.New("client is not connected")
133136
}
134137

135-
states, err := p.client.GetStateWithMultipleUpdates(ctx, state)
138+
err := p.client.GetState(ctx, state)
136139

137140
if err != nil {
138141
return provider.InterfaceStatus{}, fmt.Errorf("failed to get interface status for %s: %w", req.Interface.Spec.Name, err)
@@ -141,13 +144,11 @@ func (p *Provider) GetInterfaceStatus(ctx context.Context, req *provider.Interfa
141144
providerStatus := provider.InterfaceStatus{
142145
OperStatus: true,
143146
}
144-
for _, s := range *states {
145-
currState := s.(*PhysIfState)
146-
if stateMapping[currState.State] != StateUp {
147-
providerStatus.OperStatus = false
148-
break
149-
}
147+
148+
if stateMapping[state.State] != StateUp {
149+
providerStatus.OperStatus = false
150150
}
151+
151152
return providerStatus, nil
152153
}
153154

0 commit comments

Comments
 (0)