Skip to content
Open
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
6 changes: 6 additions & 0 deletions api/core/v1alpha1/interface_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,12 @@ type Switchport struct {
// +kubebuilder:validation:Maximum=4094
AccessVlan int32 `json:"accessVlan,omitempty"`

// InnerVlan specifies the VLAN id for QinQ access mode switchports.
// +optional
// +kubebuilder:validation:Minimum=1
// +kubebuilder:validation:Maximum=4094
InnerVlan int32 `json:"innerVlan,omitempty"`

// NativeVlan specifies the native VLAN ID for trunk mode switchports.
// Only applicable when Mode is set to "Trunk".
// +optional
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,13 @@ spec:
type: integer
minItems: 1
type: array
innerVlan:
description: InnerVlan specifies the VLAN id for QinQ access mode
switchports.
format: int32
maximum: 4094
minimum: 1
type: integer
mode:
description: Mode defines the switchport mode, such as access
or trunk.
Expand Down
33 changes: 21 additions & 12 deletions internal/provider/cisco/gnmiext/v2/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
Patch(ctx context.Context, conf ...Configurable) error
Update(ctx context.Context, conf ...Configurable) error
Delete(ctx context.Context, conf ...Configurable) error
Create(ctx context.Context, conf ...Configurable) error
}

// Client is a gNMI client offering convenience methods for device configuration
Expand Down Expand Up @@ -148,14 +149,18 @@
// If the current configuration equals the desired configuration, the operation is skipped.
// For partial updates that merge changes, use [Client.Patch] instead.
func (c *client) Update(ctx context.Context, conf ...Configurable) error {
return c.set(ctx, false, conf...)
return c.set(ctx, false, true, conf...)
}

// Patch merges the configuration for the given set of items.
// If the current configuration equals the desired configuration, the operation is skipped.
// For full replacement of configuration, use [Client.Update] instead.
func (c *client) Patch(ctx context.Context, conf ...Configurable) error {
return c.set(ctx, true, conf...)
return c.set(ctx, true, true, conf...)
}

func (c *client) Create(ctx context.Context, conf ...Configurable) error {
return c.set(ctx, false, false, conf...)
}

// Delete resets the configuration for the given set of items.
Expand Down Expand Up @@ -261,7 +266,7 @@
// configuration. Otherwise, a full replacement is done.
// If the current configuration equals the desired configuration, the operation
// is skipped.
func (c *client) set(ctx context.Context, patch bool, conf ...Configurable) error {
func (c *client) set(ctx context.Context, patch bool, retrieve bool, conf ...Configurable) error {

Check failure on line 269 in internal/provider/cisco/gnmiext/v2/client.go

View workflow job for this annotation

GitHub Actions / Checks

paramTypeCombine: func(ctx context.Context, patch bool, retrieve bool, conf ...Configurable) error could be replaced with func(ctx context.Context, patch, retrieve bool, conf ...Configurable) error (gocritic)
if len(conf) == 0 {
return nil
}
Expand All @@ -272,16 +277,20 @@
return err
}
got := cp.Deep(cf)
err = c.GetConfig(ctx, got)
if err != nil && !errors.Is(err, ErrNil) {
return fmt.Errorf("gnmiext: failed to retrieve current config for %s: %w", cf.XPath(), err)
}
// If the current configuration is equal to the desired configuration, skip the update.
// This avoids unnecessary updates and potential disruptions.
if err == nil && reflect.DeepEqual(cf, got) {
c.logger.V(1).Info("Configuration is already up-to-date", "path", cf.XPath())
continue

if retrieve {
err = c.GetConfig(ctx, got)
if err != nil && !errors.Is(err, ErrNil) {
return fmt.Errorf("gnmiext: failed to retrieve current config for %s: %w", cf.XPath(), err)
}
// If the current configuration is equal to the desired configuration, skip the update.
// This avoids unnecessary updates and potential disruptions.
if err == nil && reflect.DeepEqual(cf, got) {
c.logger.V(1).Info("Configuration is already up-to-date", "path", cf.XPath())
continue
}
}

b, err := c.Marshal(cf)
if err != nil {
return err
Expand Down
6 changes: 5 additions & 1 deletion internal/provider/cisco/gnmiext/v2/empty.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import (
"encoding/json"
"fmt"
"regexp"
)

// NOTE: Use json.Marshaler and json.Unmarshaler interfaces instead of the
Expand Down Expand Up @@ -39,7 +40,10 @@
*e = false
return nil
}
if string(b) != "[null]" {

//Due to some Cisco IOSX ouptut we also match [ \n null \n]

Check failure on line 44 in internal/provider/cisco/gnmiext/v2/empty.go

View workflow job for this annotation

GitHub Actions / Checks

commentFormatting: put a space between `//` and comment text (gocritic)
nullTypeRe := regexp.MustCompile(`^\[\s*null\s*]$`)
if !nullTypeRe.MatchString(string(b)) {
return fmt.Errorf("gnmiext: invalid empty value: %s", string(b))
}
*e = true
Expand Down
164 changes: 147 additions & 17 deletions internal/provider/cisco/iosxr/intf.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,32 @@
package iosxr

import (
"errors"
"fmt"
"regexp"
"strconv"

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

type PhysIf struct {
Name string `json:"-"`
Description string `json:"description"`
Active string `json:"active"`
Vrf string `json:"Cisco-IOS-XR-infra-rsi-cfg:vrf,omitempty"`
Statistics Statistics `json:"Cisco-IOS-XR-infra-statsd-cfg:statistics,omitempty"`
IPv4Network IPv4Network `json:"Cisco-IOS-XR-ipv4-io-cfg:ipv4-network,omitempty"`
IPv6Network IPv6Network `json:"Cisco-IOS-XR-ipv6-ma-cfg:ipv6-network,omitempty"`
IPv6Neighbor IPv6Neighbor `json:"Cisco-IOS-XR-ipv6-nd-cfg:ipv6-neighbor,omitempty"`
MTUs MTUs `json:"mtus,omitempty"`
Shutdown gnmiext.Empty `json:"shutdown,omitempty"`
Description string `json:"description,omitzero"`
Statistics Statistics `json:"Cisco-IOS-XR-infra-statsd-cfg:statistics,omitzero"`
MTUs MTUs `json:"mtus,omitzero"`
Active string `json:"active,omitzero"`
Vrf string `json:"Cisco-IOS-XR-infra-rsi-cfg:vrf,omitzero"`
IPv4Network IPv4Network `json:"Cisco-IOS-XR-ipv4-io-cfg:ipv4-network,omitzero"`
IPv6Network IPv6Network `json:"Cisco-IOS-XR-ipv6-ma-cfg:ipv6-network,omitzero"`
IPv6Neighbor IPv6Neighbor `json:"Cisco-IOS-XR-ipv6-nd-cfg:ipv6-neighbor,omitzero"`
Shutdown gnmiext.Empty `json:"shutdown,omitzero"`

//BundleMember configuration for Physical interface as member of a Bundle-Ether

Check failure on line 27 in internal/provider/cisco/iosxr/intf.go

View workflow job for this annotation

GitHub Actions / Checks

commentFormatting: put a space between `//` and comment text (gocritic)
BundleMember BundleMember `json:"Cisco-IOS-XR-bundlemgr-cfg:bundle-member,omitzero"`
}

type BundleMember struct {
ID BundleID `json:"id"`
}

type Statistics struct {
Expand Down Expand Up @@ -73,30 +82,91 @@
Owner string `json:"owner"`
}

type BunldePortActivity string

const (
PortActivityOn BunldePortActivity = "on"
PortActivityActive BunldePortActivity = "active"
PortActivityPassive BunldePortActivity = "passive"
PortActivityInherit BunldePortActivity = "inherit"
)

// BundleInterface represents a port-channel (LAG) interface on IOS-XR devices
type BundleInterface struct {
Name string `json:"-"`
Description string `json:"description,omitzero"`
Statistics Statistics `json:"Cisco-IOS-XR-infra-statsd-cfg:statistics,omitzero"`
MTUs MTUs `json:"mtus,omitzero"`
//mode in which an interface is running (e.g., virtual for subinterfaces)

Check failure on line 100 in internal/provider/cisco/iosxr/intf.go

View workflow job for this annotation

GitHub Actions / Checks

commentFormatting: put a space between `//` and comment text (gocritic)
Mode gnmiext.Empty `json:"interface-virtual,omitzero"`

//existence of this object causes the creation of the software subinterface

Check failure on line 103 in internal/provider/cisco/iosxr/intf.go

View workflow job for this annotation

GitHub Actions / Checks

commentFormatting: put a space between `//` and comment text (gocritic)
ModeNoPhysical string `json:"interface-mode-non-physical,omitzero"`
Bundle Bundle `json:"Cisco-IOS-XR-bundlemgr-cfg:bundle,omitzero"`
SubInterface VlanSubInterface `json:"Cisco-IOS-XR-l2-eth-infra-cfg:vlan-sub-configuration,omitzero"`
}

type BundleID struct {
BundleID int32 `json:"bundle-id"`
PortAcivity string `json:"port-activity"`
}

type Bundle struct {
MinAct MinimumActive `json:"minimum-active"`
}

type MinimumActive struct {
Links int32 `json:"links"`
}

type VlanSubInterface struct {
VlanIdentifier VlanIdentifier `json:"vlan-identifier"`
}

type VlanIdentifier struct {
FirstTag int32 `json:"first-tag"`
SecondTag int32 `json:"second-tag"`
VlanType string `json:"vlan-type"`
}

func (i *PhysIf) XPath() string {
return fmt.Sprintf("Cisco-IOS-XR-ifmgr-cfg:interface-configurations/interface-configuration[active=act][interface-name=%s]", i.Name)
}

func (i *PhysIf) String() string {
return fmt.Sprintf("Name: %s, Description=%s, ShutDown=%t", i.Name, i.Description, i.Shutdown)
return fmt.Sprintf("Name: %s, Description=%s", i.Name, i.Description)
}

func (i *BundleInterface) XPath() string {
return fmt.Sprintf("Cisco-IOS-XR-ifmgr-cfg:interface-configurations/interface-configuration[active=act][interface-name=%s]", i.Name)
}

func (i *BundleInterface) String() string {
return fmt.Sprintf("Name: %s, Description=%s", i.Name, i.Description)
}

type IFaceSpeed string

const (
Speed10G IFaceSpeed = "TenGigE"
Speed25G IFaceSpeed = "TwentyFiveGigE"
Speed40G IFaceSpeed = "FortyGigE"
Speed100G IFaceSpeed = "HundredGigE"
Speed10G IFaceSpeed = "TenGigE"
Speed25G IFaceSpeed = "TwentyFiveGigE"
Speed40G IFaceSpeed = "FortyGigE"
Speed100G IFaceSpeed = "HundredGigE"
EtherBundle IFaceSpeed = "etherbundle"
)

func ExtractMTUOwnerFromIfaceName(ifaceName string) (IFaceSpeed, error) {
func ExractMTUOwnerFromIfaceName(ifaceName string) (IFaceSpeed, error) {
//MTU owner of bundle interfaces is 'etherbundle'

Check failure on line 159 in internal/provider/cisco/iosxr/intf.go

View workflow job for this annotation

GitHub Actions / Checks

commentFormatting: put a space between `//` and comment text (gocritic)
bundleEtherRE := regexp.MustCompile(`^Bundle-Ether*`)
if bundleEtherRE.MatchString(ifaceName) {
// For Bundle-Ether interfaces
return EtherBundle, nil
}

// Match the port_type in an interface name <port_type>/<rack>/<slot/<module>/<port>
// E.g. match TwentyFiveGigE of interface with name TwentyFiveGigE0/0/0/1
re := regexp.MustCompile(`^\D*`)

mtuOwner := string(re.Find([]byte(ifaceName)))

if mtuOwner == "" {
return "", fmt.Errorf("failed to extract MTU owner from interface name %s", ifaceName)
}
Expand All @@ -115,6 +185,66 @@
}
}

func CheckInterfaceNameTypeAggregate(name string) error {
if name == "" {
return errors.New("interface name must not be empty")
}
//Matches Bundle-Ether<VLAN>[.<VLAN>] or BE<VLAN>[.<VLAN>]

Check failure on line 192 in internal/provider/cisco/iosxr/intf.go

View workflow job for this annotation

GitHub Actions / Checks

commentFormatting: put a space between `//` and comment text (gocritic)
re := regexp.MustCompile(`^(Bundle-Ether|BE)(\d+)(\.(\d+))?$`)
matches := re.FindStringSubmatch(name)

if matches == nil {
return fmt.Errorf("unsupported interface format %q, expected one of: %q", name, re.String())
}

//Vlan is part of the name
if matches[2] == "" {
return fmt.Errorf("unsupported interface format %q, expected one of: %q", name, re.String())
}
//Check outer vlan
//fixme: check range up to 65000
//err := CheckVlanRange(matches[2])

//Check inner vlan if we have a subinterface
if matches[4] != "" {
return CheckVlanRange(matches[4])
}
return nil
}

func ExtractBundleIdAndVlanTagsFromName(name string) (int32, int32) {
//Matches BE1.1 or Bundle-Ether1.1
re := regexp.MustCompile(`^(Bundle-Ether|BE)(\d+)(?:\.(\d+))?$`)
matches := re.FindStringSubmatch(name)

bundleID := int32(0)
outerVlan := int32(0)
switch len(matches) {
case 4:
o, _ := strconv.Atoi(matches[2])

Check failure on line 224 in internal/provider/cisco/iosxr/intf.go

View workflow job for this annotation

GitHub Actions / Checks

Error return value of `strconv.Atoi` is not checked (errcheck)
bundleID = int32(o)
case 5:
o, _ := strconv.Atoi(matches[2])

Check failure on line 227 in internal/provider/cisco/iosxr/intf.go

View workflow job for this annotation

GitHub Actions / Checks

Error return value of `strconv.Atoi` is not checked (errcheck)
i, _ := strconv.Atoi(matches[3])

Check failure on line 228 in internal/provider/cisco/iosxr/intf.go

View workflow job for this annotation

GitHub Actions / Checks

Error return value of `strconv.Atoi` is not checked (errcheck)
bundleID = int32(o)
outerVlan = int32(i)
}
return bundleID, outerVlan
}

func CheckVlanRange(vlan string) error {
v, err := strconv.Atoi(vlan)

if err != nil {
return fmt.Errorf("failed to parse VLAN %q: %w", vlan, err)
}

if v < 1 || v > 4095 {
return fmt.Errorf("VLAN %s is out of range, valid range is 1-4095", vlan)
}
return nil
}

type PhysIfStateType string

const (
Expand Down
Loading
Loading