-
Notifications
You must be signed in to change notification settings - Fork 3
feat: Implement DeviceProvider for IOS-XR #263
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
sven-rosenzweig
wants to merge
2
commits into
main
Choose a base branch
from
feat/iosxr_deviceprovider
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -6,21 +6,84 @@ package iosxr | |||||
| import ( | ||||||
| "fmt" | ||||||
| "regexp" | ||||||
| "strconv" | ||||||
| "strings" | ||||||
|
|
||||||
| "github.com/ironcore-dev/network-operator/internal/transport/gnmiext" | ||||||
| ) | ||||||
|
|
||||||
| type PhysIf struct { | ||||||
| var ( | ||||||
| bundleEtherRE = regexp.MustCompile(`^Bundle-Ether(\d+)(?:\.\d+)?$`) | ||||||
| physicalInterfaceRE = regexp.MustCompile(`^(TenGigE|TwentyFiveGigE|FortyGigE|HundredGigE|GigabitEthernet)(\d){1}(\/\d){2}(\/\d+){1}(.\d{1,5})?$`) | ||||||
| ) | ||||||
|
|
||||||
| type IFaceSpeed string | ||||||
|
|
||||||
| const ( | ||||||
| Speed10G IFaceSpeed = "TenGigE" | ||||||
| Speed25G IFaceSpeed = "TwentyFiveGigE" | ||||||
| Speed40G IFaceSpeed = "FortyGigE" | ||||||
| Speed100G IFaceSpeed = "HundredGigE" | ||||||
| EtherBundle IFaceSpeed = "etherbundle" | ||||||
| ) | ||||||
|
|
||||||
| type BundlePortActivity string | ||||||
|
|
||||||
| const ( | ||||||
| PortActivityOn BundlePortActivity = "on" | ||||||
| PortActivityActive BundlePortActivity = "active" | ||||||
| PortActivityPassive BundlePortActivity = "passive" | ||||||
| PortActivityInherit BundlePortActivity = "inherit" | ||||||
| ) | ||||||
|
|
||||||
| type PhysIfStateType string | ||||||
|
|
||||||
| const ( | ||||||
| StateUp PhysIfStateType = "im-state-up" | ||||||
| StateDown PhysIfStateType = "im-state-down" | ||||||
| StateNotReady PhysIfStateType = "im-state-not-ready" | ||||||
| StateAdminDown PhysIfStateType = "im-state-admin-down" | ||||||
| StateShutDown PhysIfStateType = "im-state-shutdown" | ||||||
| ) | ||||||
|
|
||||||
| type Ifaces struct { | ||||||
| PhysIfList []struct { | ||||||
| Name string `json:"interface-name"` | ||||||
| } `json:"interface-configuration"` | ||||||
| } | ||||||
|
|
||||||
| func (i *Ifaces) XPath() string { | ||||||
| return "Cisco-IOS-XR-ifmgr-cfg:interface-configurations" | ||||||
| } | ||||||
|
|
||||||
| // Iface represents physical and bundle interfaces as part of the same struct as they share a lot of common configuration | ||||||
| // and only differ in a few attributes like the interface name and the presence of bundle configuration or not. | ||||||
| type Iface 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"` | ||||||
| IPv4Network IPv4Network `json:"Cisco-IOS-XR-ipv4-io-cfg:ipv4-network"` | ||||||
| IPv6Network IPv6Network `json:"Cisco-IOS-XR-ipv6-ma-cfg:ipv6-network"` | ||||||
| IPv6Neighbor IPv6Neighbor `json:"Cisco-IOS-XR-ipv6-nd-cfg:ipv6-neighbor"` | ||||||
| MTUs MTUs `json:"mtus"` | ||||||
| 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"` | ||||||
|
|
||||||
| // Existence of this object causes the creation of the software subinterface | ||||||
| ModeNoPhysical string `json:"interface-mode-non-physical,omitzero"` | ||||||
|
|
||||||
| // BundleMember configuration for Physical interface as member of a Bundle-Ether | ||||||
| BundleMember BundleMember `json:"Cisco-IOS-XR-bundlemgr-cfg:bundle-member,omitzero"` | ||||||
|
|
||||||
| // Mode in which an interface is running (e.g., virtual for subinterfaces) | ||||||
| Mode gnmiext.Empty `json:"interface-virtual,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 BundleMember struct { | ||||||
| ID BundleID `json:"id"` | ||||||
| } | ||||||
|
|
||||||
| type Statistics struct { | ||||||
|
|
@@ -29,7 +92,7 @@ type Statistics struct { | |||||
|
|
||||||
| type IPv4Network struct { | ||||||
| Addresses AddressesIPv4 `json:"addresses"` | ||||||
| Mtu uint16 `json:"mtu"` | ||||||
| Mtu uint16 `json:"mtu,omitzero"` | ||||||
| } | ||||||
|
|
||||||
| type AddressesIPv4 struct { | ||||||
|
|
@@ -73,64 +136,188 @@ type MTU struct { | |||||
| Owner string `json:"owner"` | ||||||
| } | ||||||
|
|
||||||
| func (i *PhysIf) XPath() string { | ||||||
| type BundleID struct { | ||||||
| BundleID int32 `json:"bundle-id"` | ||||||
| PortActivity 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,omitzero"` | ||||||
| VlanType string `json:"vlan-type"` | ||||||
| } | ||||||
|
|
||||||
| func (i *Iface) 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) | ||||||
| func (i *Iface) String() string { | ||||||
| return fmt.Sprintf("Name: %s, Description=%s", i.Name, i.Description) | ||||||
| } | ||||||
|
|
||||||
| type IFaceSpeed string | ||||||
| type PhysIfState struct { | ||||||
| State string `json:"state"` | ||||||
| Name string `json:"-"` | ||||||
| } | ||||||
|
|
||||||
| const ( | ||||||
| Speed10G IFaceSpeed = "TenGigE" | ||||||
| Speed25G IFaceSpeed = "TwentyFiveGigE" | ||||||
| Speed40G IFaceSpeed = "FortyGigE" | ||||||
| Speed100G IFaceSpeed = "HundredGigE" | ||||||
| ) | ||||||
| func (phys *PhysIfState) XPath() string { | ||||||
| // (fixme): hardcoded route processor for the moment | ||||||
| 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) | ||||||
| } | ||||||
|
|
||||||
| func ExtractMTUOwnerFromIfaceName(ifaceName string) (IFaceSpeed, error) { | ||||||
| func ValidateInterfaceName(name string) error { | ||||||
| // Supported Interface name formats: | ||||||
| // Physical Interface <PortSpeed><rack><slot><port> e.g TwentyFiveGigE0/0/0/3 | ||||||
| // SubInterface <PotySpeed><rack><slot><port>.<vlan-id> e.g TwentyFiveGigE0/0/0/3 | ||||||
| // Bundle Interface/Port Channel Bundle-Ether<BundleID> | ||||||
| // Vlans over Bundle Bundle-Ether<BundleID>.<vlan-id> | ||||||
|
|
||||||
| beErr := CheckInterfaceNameTypeAggregate(name) | ||||||
| physErr := CheckInterfaceNameTypePhysical(name) | ||||||
|
|
||||||
| if beErr == nil || physErr == nil { | ||||||
| return nil | ||||||
| } | ||||||
| return fmt.Errorf("unsupported interface name format: %s", name) | ||||||
| } | ||||||
|
|
||||||
| func ExtractOwnerFromInterfaceName(ifaceName string) (IFaceSpeed, error) { | ||||||
| // Owner of bundle interfaces is 'etherbundle' | ||||||
| if bundleEtherRE.MatchString(ifaceName) { | ||||||
| return EtherBundle, nil | ||||||
| } | ||||||
| return ExtractInterfaceSpeedFromName(ifaceName) | ||||||
| } | ||||||
|
|
||||||
| func ExtractInterfaceSpeedFromName(ifaceName string) (IFaceSpeed, error) { | ||||||
| // 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) | ||||||
| speed := string(re.Find([]byte(ifaceName))) | ||||||
| if speed == "" { | ||||||
| return "", fmt.Errorf("failed to extract speed from interface name %s", ifaceName) | ||||||
| } | ||||||
|
|
||||||
| switch mtuOwner { | ||||||
| switch speed { | ||||||
| case string(Speed10G): | ||||||
| return Speed10G, nil | ||||||
| case string(Speed25G): | ||||||
| return Speed25G, nil | ||||||
| case string(Speed40G): | ||||||
| return Speed25G, nil | ||||||
| return Speed40G, nil | ||||||
| case string(Speed100G): | ||||||
| return Speed100G, nil | ||||||
| default: | ||||||
| return "", fmt.Errorf("unsupported interface type %s for MTU owner extraction", mtuOwner) | ||||||
| return "", fmt.Errorf("unsupported interface type %s", speed) | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| type PhysIfStateType string | ||||||
| func MapInterfaceSpeedToNumeric(speed IFaceSpeed) (int32, error) { | ||||||
| switch speed { | ||||||
| case Speed10G: | ||||||
| return 10000, nil | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
nit: you can format numbers like this. Makes it a bit easier to read perhaps. |
||||||
| case Speed25G: | ||||||
| return 25000, nil | ||||||
| case Speed40G: | ||||||
| return 40000, nil | ||||||
| case Speed100G: | ||||||
| return 100000, nil | ||||||
| default: | ||||||
| return 0, fmt.Errorf("unsupported interface speed %s", speed) | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| const ( | ||||||
| StateUp PhysIfStateType = "im-state-up" | ||||||
| StateDown PhysIfStateType = "im-state-down" | ||||||
| StateNotReady PhysIfStateType = "im-state-not-ready" | ||||||
| StateAdminDown PhysIfStateType = "im-state-admin-down" | ||||||
| StateShutDown PhysIfStateType = "im-state-shutdown" | ||||||
| ) | ||||||
| func CheckInterfaceNameTypeAggregate(name string) error { | ||||||
| matches := bundleEtherRE.FindStringSubmatch(name) | ||||||
|
|
||||||
| type PhysIfState struct { | ||||||
| State string `json:"state"` | ||||||
| Name string `json:"-"` | ||||||
| if matches == nil { | ||||||
| return fmt.Errorf("unsupported interface format %q, expected one of: %q", name, bundleEtherRE.String()) | ||||||
| } | ||||||
| // Fixme(sven-rosenzweig): check BundleId range | ||||||
| return nil | ||||||
| } | ||||||
|
|
||||||
| func (phys *PhysIfState) XPath() string { | ||||||
| // (fixme): hardcoded route processor for the moment | ||||||
| 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) | ||||||
| func CheckInterfaceNameTypePhysical(name string) error { | ||||||
| if !physicalInterfaceRE.MatchString(name) { | ||||||
| return fmt.Errorf("unsupported physical interface format %s", name) | ||||||
| } | ||||||
| return nil | ||||||
| } | ||||||
|
|
||||||
| func ExtractVlanTagFromName(name string) (vlanID int32, err error) { | ||||||
| res := strings.Split(name, ".") | ||||||
| switch len(res) { | ||||||
| case 1: | ||||||
| return 0, nil | ||||||
| case 2: | ||||||
| vlan, err := strconv.ParseInt(res[1], 10, 32) | ||||||
| if err != nil { | ||||||
| return 0, fmt.Errorf("failed to parse VLAN ID from interface name %q: %w", name, err) | ||||||
| } | ||||||
| return int32(vlan), nil | ||||||
| default: | ||||||
| return 0, fmt.Errorf("unexpected interface name format %q, expected <interface> or <interface>.<vlan>", name) | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| func ExtractBundleAndSubinterfaceID(name string) (bundleID, subinterfaceID int32, err error) { | ||||||
| // Extract bundle ID and optional subinterface ID from Bundle-Ether<id> or Bundle-Ether<id>.<subif_id> | ||||||
| // Examples: Bundle-Ether200 -> (200, 0), Bundle-Ether200.4095 -> (200, 4095) | ||||||
|
|
||||||
| // Remove the "Bundle-Ether" or "BE" prefix | ||||||
| var idPart string | ||||||
|
|
||||||
| if !bundleEtherRE.MatchString(name) { | ||||||
| return 0, 0, fmt.Errorf("interface name %q does not start with Bundle-Ether or bundle-ether", name) | ||||||
| } | ||||||
| idPart = strings.TrimPrefix(strings.TrimPrefix(name, "Bundle-Ether"), "bundle-ether") | ||||||
| parts := strings.Split(idPart, ".") | ||||||
|
|
||||||
| if len(parts) == 0 || parts[0] == "" { | ||||||
| return 0, 0, fmt.Errorf("failed to extract bundle ID from interface name %q", name) | ||||||
| } | ||||||
|
|
||||||
| // Parse bundle ID | ||||||
| id, err := strconv.ParseInt(parts[0], 10, 32) | ||||||
| if err != nil { | ||||||
| return 0, 0, fmt.Errorf("failed to parse bundle ID from interface name %q: %w", name, err) | ||||||
| } | ||||||
| bundleID = int32(id) | ||||||
|
|
||||||
| // Parse subinterface ID if present | ||||||
| if len(parts) == 2 { | ||||||
| subIfaceIDInt, err := strconv.ParseInt(parts[1], 10, 32) | ||||||
| if err != nil { | ||||||
| return 0, 0, fmt.Errorf("failed to parse subinterface ID from interface name %q: %w", name, err) | ||||||
| } | ||||||
| subinterfaceID = int32(subIfaceIDInt) | ||||||
| } else if len(parts) > 2 { | ||||||
| return 0, 0, fmt.Errorf("unexpected interface name format %q, expected Bundle-Ether<id> or Bundle-Ether<id>.<subif_id>", name) | ||||||
| } | ||||||
|
|
||||||
| return bundleID, subinterfaceID, nil | ||||||
| } | ||||||
|
|
||||||
| 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 | ||||||
| } | ||||||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.