Skip to content
Draft
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
51 changes: 51 additions & 0 deletions internal/provider/cisco/iosxr/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ var (
_ provider.Provider = &Provider{}
_ provider.DeviceProvider = &Provider{}
_ provider.InterfaceProvider = &Provider{}
_ provider.VRFProvider = &Provider{}
)

type Provider struct {
Expand Down Expand Up @@ -116,6 +117,8 @@ func (p *Provider) Reprovision(_ context.Context, conn *deviceutil.Connection) e
}

func (p *Provider) EnsureInterface(ctx context.Context, req *provider.EnsureInterfaceRequest) error {
// TODO(sven-rosenweig): Make use of the VRF information in the request to assign the interface to the correct VRF.
// FIXME(sven-rosenweig): Use the ExtractOwnerFromInterfaceName function in the ValidateInterfaceName function
if p.client == nil {
return errors.New("client is not connected")
}
Expand Down Expand Up @@ -368,6 +371,54 @@ func (p *Provider) InterfaceNameEqual(_ context.Context, a, b string) (bool, err
return a == b, nil
}

func (p *Provider) EnsureVRF(ctx context.Context, req *provider.VRFRequest) error {
if p.client == nil {
return errors.New("client is not connected")
}

vrf := &VRF{
Name: req.VRF.Spec.Name,
Descr: req.VRF.Spec.Description,
}

for _, routeTarget := range req.VRF.Spec.RouteTargets {
// Parse the route target value to extract ASN and index
// TODO(sven-rosenweig): Add support for two-byte (type 0) and IPv4 (type 1) route targets
// For now we assume that all route targets are in the format of four-byte ASNs with index: <asn>:<index>
localRT, err := NewRouteTarget(routeTarget.Value)
if err != nil {
return fmt.Errorf("failed to parse route target %q for VRF %q: %w", routeTarget.Value, req.VRF.Spec.Name, err)
}
for _, af := range routeTarget.AddressFamilies {
switch af {
case v1alpha1.IPv4:
AppendAddressFamily(&vrf.AddrFamily.IPv4.Unicast, &localRT, routeTarget.Action)
case v1alpha1.IPv6:
AppendAddressFamily(&vrf.AddrFamily.IPv6.Unicast, &localRT, routeTarget.Action)
default:
return fmt.Errorf("unsupported address family %q for VRF %q", af, req.VRF.Spec.Name)
}
}
}

return p.client.Update(ctx, vrf)
}

func (p *Provider) DeleteVRF(ctx context.Context, req *provider.VRFRequest) error {
if p.client == nil {
return errors.New("client is not connected")
}

vrf := &VRF{
Name: req.VRF.Spec.Name,
}

if err := p.client.Delete(ctx, vrf); err != nil {
return fmt.Errorf("failed to delete VRF %q: %w", req.VRF.Spec.Name, err)
}
return nil
}

func init() {
provider.Register("cisco-iosxr-gnmi", NewProvider)
}
68 changes: 68 additions & 0 deletions internal/provider/cisco/iosxr/testdata/vrf.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{
"vrf": {
"vrf-name": "vrf-name",
"description": "vrf-description",
"address-family": {
"ipv4": {
"unicast": {
"Cisco-IOS-XR-um-router-bgp-cfg:export": {
"route-target": {
"four-byte-as-route-targets": {
"four-byte-as-route-target": [
{
"four-byte-as-number": 4268359684,
"asn4-index": 1101,
"stitching": "disable"
}
]
}
}
},
"Cisco-IOS-XR-um-router-bgp-cfg:import": {
"route-target": {
"four-byte-as-route-targets": {
"four-byte-as-route-target": [
{
"four-byte-as-number": 4268359684,
"asn4-index": 1101,
"stitching": "disable"
}
]
}
}
}
}
},
"ipv6": {
"unicast": {
"Cisco-IOS-XR-um-router-bgp-cfg:export": {
"route-target": {
"four-byte-as-route-targets": {
"four-byte-as-route-target": [
{
"four-byte-as-number": 4268359684,
"asn4-index": 1101,
"stitching": "disable"
}
]
}
}
},
"Cisco-IOS-XR-um-router-bgp-cfg:import": {
"route-target": {
"four-byte-as-route-targets": {
"four-byte-as-route-target": [
{
"four-byte-as-number": 4268359684,
"asn4-index": 1101,
"stitching": "disable"
}
]
}
}
}
}
}
}
}
}
8 changes: 8 additions & 0 deletions internal/provider/cisco/iosxr/testdata/vrf.json.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
vrf vrf-name
description vrf-description
address-family ipv4 unicast
import route-target 4268359684:2012
export route-target 4268359684:2012
address-family ipv6 unicast
import route-target 4268359684:2012
export route-target 4268359684:2012
105 changes: 105 additions & 0 deletions internal/provider/cisco/iosxr/vrf.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors
// SPDX-License-Identifier: Apache-2.0
package iosxr

import (
"fmt"
"strconv"
"strings"

"github.com/ironcore-dev/network-operator/api/core/v1alpha1"
"github.com/ironcore-dev/network-operator/internal/transport/gnmiext"
)

type StitchingType string

const (
Enable StitchingType = "enable"
Disable StitchingType = "disable"
)

var _ gnmiext.DataElement = (*VRF)(nil)

type VRF struct {
Name string `json:"vrf-name"`
Descr string `json:"description"`
AddrFamily AddressFamily `json:"address-family"`
}

type AddressFamily struct {
IPv4 UnicastFamily `json:"ipv4,omitzero"`
IPv6 UnicastFamily `json:"ipv6,omitzero"`
}

type UnicastFamily struct {
Unicast Unicast `json:"unicast"`
}

type Unicast struct {
Export RouteTarget `json:"Cisco-IOS-XR-um-router-bgp-cfg:export,omitzero"`
Import RouteTarget `json:"Cisco-IOS-XR-um-router-bgp-cfg:import,omitzero"`
}

type RouteTarget struct {
RouteTargetFourByteAS RouteTargetFourByteAsRTS `json:"route-target"`
}

type RouteTargetFourByteAsRTS struct {
FourByteRT FourByteAsRT `json:"four-byte-as-route-targets"`
}

type FourByteAsRT struct {
Target []FourByteRT `json:"four-byte-as-route-target"`
}

type FourByteRT struct {
AsNumber uint32 `json:"four-byte-as-number"`
Index uint32 `json:"asn4-index"`
Stitching StitchingType `json:"stitching"`
}

func (v *VRF) XPath() string {
return "Cisco-IOS-XR-um-vrf-cfg:vrfs/vrf[vrf-name=" + v.Name + "]"
}

func NewRouteTarget(rd string) (FourByteRT, error) {
parts := strings.SplitN(rd, ":", 2)
if len(parts) != 2 {
return FourByteRT{}, fmt.Errorf("invalid rd: %q", rd)
}

asnInt, err := strconv.ParseUint(parts[0], 10, 32)
if err != nil {
return FourByteRT{}, fmt.Errorf("invalid ASN in rd %q: %w", rd, err)
}

indexInt, err := strconv.ParseUint(parts[1], 10, 32)
if err != nil {
return FourByteRT{}, fmt.Errorf("invalid index in rd %q: %w", rd, err)
}

return FourByteRT{
AsNumber: uint32(asnInt),
Index: uint32(indexInt),
Stitching: Disable,
}, nil
}

func AppendAddressFamily(unicast *Unicast, rt *FourByteRT, action v1alpha1.RouteTargetAction) {
switch action {
case v1alpha1.RouteTargetActionImport:
AppendRouteTarget(&unicast.Import.RouteTargetFourByteAS.FourByteRT, rt)
case v1alpha1.RouteTargetActionExport:
AppendRouteTarget(&unicast.Export.RouteTargetFourByteAS.FourByteRT, rt)
case v1alpha1.RouteTargetActionBoth:
AppendRouteTarget(&unicast.Import.RouteTargetFourByteAS.FourByteRT, rt)
AppendRouteTarget(&unicast.Export.RouteTargetFourByteAS.FourByteRT, rt)
default:
// Should not happen with validated input, but being defensive
panic(fmt.Sprintf("unexpected route target action: %v", action))
}
}

func AppendRouteTarget(targets *FourByteAsRT, rt *FourByteRT) {
targets.Target = append(targets.Target, *rt)
}
29 changes: 29 additions & 0 deletions internal/provider/cisco/iosxr/vrf_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors
// SPDX-License-Identifier: Apache-2.0

package iosxr

func init() {
rt := RouteTarget{
RouteTargetFourByteAS: RouteTargetFourByteAsRTS{
FourByteRT: FourByteAsRT{
Target: []FourByteRT{
{AsNumber: 4268359684, Index: 1101, Stitching: Disable},
},
},
},
}

Register("vrf", &VRF{
Name: "vrf-name",
Descr: "vrf-description",
AddrFamily: AddressFamily{
IPv4: UnicastFamily{
Unicast: Unicast{Import: rt, Export: rt},
},
IPv6: UnicastFamily{
Unicast: Unicast{Import: rt, Export: rt},
},
},
})
}
3 changes: 0 additions & 3 deletions internal/provider/cisco/nxos/vrf_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors
// SPDX-License-Identifier: Apache-2.0

package nxos

func init() {
Expand Down
Loading