diff --git a/api/core/v1alpha1/routingpolicy_types.go b/api/core/v1alpha1/routingpolicy_types.go
index 4583854e..cf48825c 100644
--- a/api/core/v1alpha1/routingpolicy_types.go
+++ b/api/core/v1alpha1/routingpolicy_types.go
@@ -8,6 +8,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
+ "k8s.io/apimachinery/pkg/util/intstr"
)
// RoutingPolicySpec defines the desired state of RoutingPolicy
@@ -96,7 +97,7 @@ const (
)
// BgpActions defines BGP-specific actions for a policy statement.
-// +kubebuilder:validation:XValidation:rule="has(self.setCommunity) || has(self.setExtCommunity)",message="at least one BGP action must be specified"
+// +kubebuilder:validation:XValidation:rule="has(self.setCommunity) || has(self.setExtCommunity) || has(self.setASPath)",message="at least one BGP action must be specified"
type BgpActions struct {
// SetCommunity configures BGP standard community attributes.
// +optional
@@ -105,6 +106,67 @@ type BgpActions struct {
// SetExtCommunity configures BGP extended community attributes.
// +optional
SetExtCommunity *SetExtCommunityAction `json:"setExtCommunity,omitempty"`
+
+ // SetASPath configures modifications to the BGP AS path attribute.
+ // Not all providers may support this action.
+ // +optional
+ SetASPath *SetASPathAction `json:"setASPath,omitempty"`
+}
+
+// SetASPathAction defines actions to modify the BGP AS path attribute.
+// +kubebuilder:validation:XValidation:rule="has(self.prepend) || has(self.replace) || has(self.asNumber)",message="at least one AS path action must be specified"
+type SetASPathAction struct {
+ // Prepend configures prepending to the AS path.
+ // +optional
+ Prepend *SetASPathPrepend `json:"prepend,omitempty"`
+
+ // Replace configures replacement of AS numbers in the AS path.
+ // +optional
+ Replace *SetASPathReplace `json:"replace,omitempty"`
+
+ // ASNumber sets the AS path to the specified AS number.
+ // Supports both plain format (1-4294967295) and dotted notation (1-65535.0-65535) as per RFC 5396.
+ // +optional
+ ASNumber *intstr.IntOrString `json:"asNumber,omitempty"`
+}
+
+// SetASPathPrepend configures prepending to the BGP AS path.
+// Either asNumber or useLastAS must be specified, but not both.
+// +kubebuilder:validation:XValidation:rule="has(self.asNumber) != has(self.useLastAS)",message="exactly one of asNumber or useLastAS must be specified"
+type SetASPathPrepend struct {
+ // ASNumber is the autonomous system number to prepend to the AS path.
+ // Supports both plain format (1-4294967295) and dotted notation (1-65535.0-65535) as per RFC 5396.
+ // Mutually exclusive with useLastAS.
+ // +optional
+ ASNumber *intstr.IntOrString `json:"asNumber,omitempty"`
+
+ // UseLastAS prepends the last AS number in the existing AS path the specified number of times.
+ // Mutually exclusive with asNumber.
+ // +optional
+ // +kubebuilder:validation:Minimum=1
+ // +kubebuilder:validation:Maximum=10
+ UseLastAS *int32 `json:"useLastAS,omitempty"`
+}
+
+// SetASPathReplace configures replacement of AS numbers in the BGP AS path.
+// Either privateAS or asNumber must be specified, but not both.
+// +kubebuilder:validation:XValidation:rule="has(self.privateAS) != has(self.asNumber)",message="exactly one of privateAS or asNumber must be specified"
+type SetASPathReplace struct {
+ // PrivateAS, when set to true, targets all private AS numbers in the path for replacement.
+ // Mutually exclusive with asNumber.
+ // +optional
+ PrivateAS bool `json:"privateAS,omitempty"`
+
+ // ASNumber targets a specific AS number in the path for replacement.
+ // Supports both plain format (1-4294967295) and dotted notation (1-65535.0-65535) as per RFC 5396.
+ // Mutually exclusive with privateAS.
+ // +optional
+ ASNumber *intstr.IntOrString `json:"asNumber,omitempty"`
+
+ // Replacement is the AS number to substitute in place of matched AS numbers.
+ // Supports both plain format (1-4294967295) and dotted notation (1-65535.0-65535) as per RFC 5396.
+ // +required
+ Replacement intstr.IntOrString `json:"replacement"`
}
// SetCommunityAction defines the action to set BGP standard communities.
diff --git a/api/core/v1alpha1/zz_generated.deepcopy.go b/api/core/v1alpha1/zz_generated.deepcopy.go
index cde89b5c..bff93d77 100644
--- a/api/core/v1alpha1/zz_generated.deepcopy.go
+++ b/api/core/v1alpha1/zz_generated.deepcopy.go
@@ -10,6 +10,7 @@ package v1alpha1
import (
"k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/util/intstr"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
@@ -772,6 +773,11 @@ func (in *BgpActions) DeepCopyInto(out *BgpActions) {
*out = new(SetExtCommunityAction)
(*in).DeepCopyInto(*out)
}
+ if in.SetASPath != nil {
+ in, out := &in.SetASPath, &out.SetASPath
+ *out = new(SetASPathAction)
+ (*in).DeepCopyInto(*out)
+ }
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BgpActions.
@@ -3209,6 +3215,82 @@ func (in *SecretReference) DeepCopy() *SecretReference {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SetASPathAction) DeepCopyInto(out *SetASPathAction) {
+ *out = *in
+ if in.Prepend != nil {
+ in, out := &in.Prepend, &out.Prepend
+ *out = new(SetASPathPrepend)
+ (*in).DeepCopyInto(*out)
+ }
+ if in.Replace != nil {
+ in, out := &in.Replace, &out.Replace
+ *out = new(SetASPathReplace)
+ (*in).DeepCopyInto(*out)
+ }
+ if in.ASNumber != nil {
+ in, out := &in.ASNumber, &out.ASNumber
+ *out = new(intstr.IntOrString)
+ **out = **in
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SetASPathAction.
+func (in *SetASPathAction) DeepCopy() *SetASPathAction {
+ if in == nil {
+ return nil
+ }
+ out := new(SetASPathAction)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SetASPathPrepend) DeepCopyInto(out *SetASPathPrepend) {
+ *out = *in
+ if in.ASNumber != nil {
+ in, out := &in.ASNumber, &out.ASNumber
+ *out = new(intstr.IntOrString)
+ **out = **in
+ }
+ if in.UseLastAS != nil {
+ in, out := &in.UseLastAS, &out.UseLastAS
+ *out = new(int32)
+ **out = **in
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SetASPathPrepend.
+func (in *SetASPathPrepend) DeepCopy() *SetASPathPrepend {
+ if in == nil {
+ return nil
+ }
+ out := new(SetASPathPrepend)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SetASPathReplace) DeepCopyInto(out *SetASPathReplace) {
+ *out = *in
+ if in.ASNumber != nil {
+ in, out := &in.ASNumber, &out.ASNumber
+ *out = new(intstr.IntOrString)
+ **out = **in
+ }
+ out.Replacement = in.Replacement
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SetASPathReplace.
+func (in *SetASPathReplace) DeepCopy() *SetASPathReplace {
+ if in == nil {
+ return nil
+ }
+ out := new(SetASPathReplace)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SetCommunityAction) DeepCopyInto(out *SetCommunityAction) {
*out = *in
diff --git a/charts/network-operator/templates/crd/routingpolicies.networking.metal.ironcore.dev.yaml b/charts/network-operator/templates/crd/routingpolicies.networking.metal.ironcore.dev.yaml
index c072d158..c68fa6c0 100644
--- a/charts/network-operator/templates/crd/routingpolicies.networking.metal.ironcore.dev.yaml
+++ b/charts/network-operator/templates/crd/routingpolicies.networking.metal.ironcore.dev.yaml
@@ -138,6 +138,82 @@ spec:
BgpActions specifies BGP-specific actions to apply when the route is accepted.
Only applicable when RouteDisposition is AcceptRoute.
properties:
+ setASPath:
+ description: |-
+ SetASPath configures modifications to the BGP AS path attribute.
+ Not all providers may support this action.
+ properties:
+ asNumber:
+ anyOf:
+ - type: integer
+ - type: string
+ description: |-
+ ASNumber sets the AS path to the specified AS number.
+ Supports both plain format (1-4294967295) and dotted notation (1-65535.0-65535) as per RFC 5396.
+ x-kubernetes-int-or-string: true
+ prepend:
+ description: Prepend configures prepending to the
+ AS path.
+ properties:
+ asNumber:
+ anyOf:
+ - type: integer
+ - type: string
+ description: |-
+ ASNumber is the autonomous system number to prepend to the AS path.
+ Supports both plain format (1-4294967295) and dotted notation (1-65535.0-65535) as per RFC 5396.
+ Mutually exclusive with useLastAS.
+ x-kubernetes-int-or-string: true
+ useLastAS:
+ description: |-
+ UseLastAS prepends the last AS number in the existing AS path the specified number of times.
+ Mutually exclusive with asNumber.
+ format: int32
+ maximum: 10
+ minimum: 1
+ type: integer
+ type: object
+ x-kubernetes-validations:
+ - message: exactly one of asNumber or useLastAS
+ must be specified
+ rule: has(self.asNumber) != has(self.useLastAS)
+ replace:
+ description: Replace configures replacement of AS
+ numbers in the AS path.
+ properties:
+ asNumber:
+ anyOf:
+ - type: integer
+ - type: string
+ description: |-
+ ASNumber targets a specific AS number in the path for replacement.
+ Supports both plain format (1-4294967295) and dotted notation (1-65535.0-65535) as per RFC 5396.
+ Mutually exclusive with privateAS.
+ x-kubernetes-int-or-string: true
+ privateAS:
+ description: |-
+ PrivateAS, when set to true, targets all private AS numbers in the path for replacement.
+ Mutually exclusive with asNumber.
+ type: boolean
+ replacement:
+ anyOf:
+ - type: integer
+ - type: string
+ description: |-
+ Replacement is the AS number to substitute in place of matched AS numbers.
+ Supports both plain format (1-4294967295) and dotted notation (1-65535.0-65535) as per RFC 5396.
+ x-kubernetes-int-or-string: true
+ required:
+ - replacement
+ type: object
+ x-kubernetes-validations:
+ - message: exactly one of privateAS or asNumber
+ must be specified
+ rule: has(self.privateAS) != has(self.asNumber)
+ type: object
+ x-kubernetes-validations:
+ - message: at least one AS path action must be specified
+ rule: has(self.prepend) || has(self.replace) || has(self.asNumber)
setCommunity:
description: SetCommunity configures BGP standard community
attributes.
@@ -174,6 +250,7 @@ spec:
x-kubernetes-validations:
- message: at least one BGP action must be specified
rule: has(self.setCommunity) || has(self.setExtCommunity)
+ || has(self.setASPath)
routeDisposition:
description: RouteDisposition specifies whether to accept
or reject the route.
diff --git a/config/crd/bases/networking.metal.ironcore.dev_routingpolicies.yaml b/config/crd/bases/networking.metal.ironcore.dev_routingpolicies.yaml
index 608bc64c..cf3212ad 100644
--- a/config/crd/bases/networking.metal.ironcore.dev_routingpolicies.yaml
+++ b/config/crd/bases/networking.metal.ironcore.dev_routingpolicies.yaml
@@ -135,6 +135,82 @@ spec:
BgpActions specifies BGP-specific actions to apply when the route is accepted.
Only applicable when RouteDisposition is AcceptRoute.
properties:
+ setASPath:
+ description: |-
+ SetASPath configures modifications to the BGP AS path attribute.
+ Not all providers may support this action.
+ properties:
+ asNumber:
+ anyOf:
+ - type: integer
+ - type: string
+ description: |-
+ ASNumber sets the AS path to the specified AS number.
+ Supports both plain format (1-4294967295) and dotted notation (1-65535.0-65535) as per RFC 5396.
+ x-kubernetes-int-or-string: true
+ prepend:
+ description: Prepend configures prepending to the
+ AS path.
+ properties:
+ asNumber:
+ anyOf:
+ - type: integer
+ - type: string
+ description: |-
+ ASNumber is the autonomous system number to prepend to the AS path.
+ Supports both plain format (1-4294967295) and dotted notation (1-65535.0-65535) as per RFC 5396.
+ Mutually exclusive with useLastAS.
+ x-kubernetes-int-or-string: true
+ useLastAS:
+ description: |-
+ UseLastAS prepends the last AS number in the existing AS path the specified number of times.
+ Mutually exclusive with asNumber.
+ format: int32
+ maximum: 10
+ minimum: 1
+ type: integer
+ type: object
+ x-kubernetes-validations:
+ - message: exactly one of asNumber or useLastAS
+ must be specified
+ rule: has(self.asNumber) != has(self.useLastAS)
+ replace:
+ description: Replace configures replacement of AS
+ numbers in the AS path.
+ properties:
+ asNumber:
+ anyOf:
+ - type: integer
+ - type: string
+ description: |-
+ ASNumber targets a specific AS number in the path for replacement.
+ Supports both plain format (1-4294967295) and dotted notation (1-65535.0-65535) as per RFC 5396.
+ Mutually exclusive with privateAS.
+ x-kubernetes-int-or-string: true
+ privateAS:
+ description: |-
+ PrivateAS, when set to true, targets all private AS numbers in the path for replacement.
+ Mutually exclusive with asNumber.
+ type: boolean
+ replacement:
+ anyOf:
+ - type: integer
+ - type: string
+ description: |-
+ Replacement is the AS number to substitute in place of matched AS numbers.
+ Supports both plain format (1-4294967295) and dotted notation (1-65535.0-65535) as per RFC 5396.
+ x-kubernetes-int-or-string: true
+ required:
+ - replacement
+ type: object
+ x-kubernetes-validations:
+ - message: exactly one of privateAS or asNumber
+ must be specified
+ rule: has(self.privateAS) != has(self.asNumber)
+ type: object
+ x-kubernetes-validations:
+ - message: at least one AS path action must be specified
+ rule: has(self.prepend) || has(self.replace) || has(self.asNumber)
setCommunity:
description: SetCommunity configures BGP standard community
attributes.
@@ -171,6 +247,7 @@ spec:
x-kubernetes-validations:
- message: at least one BGP action must be specified
rule: has(self.setCommunity) || has(self.setExtCommunity)
+ || has(self.setASPath)
routeDisposition:
description: RouteDisposition specifies whether to accept
or reject the route.
diff --git a/config/samples/v1alpha1_routingpolicy.yaml b/config/samples/v1alpha1_routingpolicy.yaml
index 567891c4..174c139d 100644
--- a/config/samples/v1alpha1_routingpolicy.yaml
+++ b/config/samples/v1alpha1_routingpolicy.yaml
@@ -44,6 +44,34 @@ spec:
name: blocked-networks
actions:
routeDisposition: RejectRoute
+ - sequence: 40
+ actions:
+ routeDisposition: AcceptRoute
+ bgpActions:
+ setASPath:
+ prepend:
+ asNumber: 65000
+ - sequence: 50
+ actions:
+ routeDisposition: AcceptRoute
+ bgpActions:
+ setASPath:
+ prepend:
+ useLastAS: 5
+ - sequence: 60
+ actions:
+ routeDisposition: AcceptRoute
+ bgpActions:
+ setASPath:
+ replace:
+ privateAS: true
+ replacement: 65100
+ - sequence: 70
+ actions:
+ routeDisposition: AcceptRoute
+ bgpActions:
+ setASPath:
+ asNumber: 65000
- sequence: 100
actions:
routeDisposition: AcceptRoute
diff --git a/docs/api-reference/index.md b/docs/api-reference/index.md
index 06e61d0d..ce43405f 100644
--- a/docs/api-reference/index.md
+++ b/docs/api-reference/index.md
@@ -649,6 +649,7 @@ _Appears in:_
| --- | --- | --- | --- |
| `setCommunity` _[SetCommunityAction](#setcommunityaction)_ | SetCommunity configures BGP standard community attributes. | | Optional: \{\}
|
| `setExtCommunity` _[SetExtCommunityAction](#setextcommunityaction)_ | SetExtCommunity configures BGP extended community attributes. | | Optional: \{\}
|
+| `setASPath` _[SetASPathAction](#setaspathaction)_ | SetASPath configures modifications to the BGP AS path attribute.
Not all providers may support this action. | | Optional: \{\}
|
#### Certificate
@@ -920,7 +921,7 @@ _Appears in:_
| Field | Description | Default | Validation |
| --- | --- | --- | --- |
-| `paused` _boolean_ | Paused can be used to prevent controllers from processing the Device and its associated objects. | | Optional: \{\}
|
+| `paused` _boolean_ | Paused can be used to prevent controllers from processing the Device and its associated objects. | false | Optional: \{\}
|
| `endpoint` _[Endpoint](#endpoint)_ | Endpoint contains the connection information for the device. | | Required: \{\}
|
| `provisioning` _[Provisioning](#provisioning)_ | Provisioning is an optional configuration for the device provisioning process.
It can be used to provide initial configuration templates or scripts that are applied during the device provisioning. | | Optional: \{\}
|
@@ -2602,6 +2603,61 @@ _Appears in:_
| `namespace` _string_ | Namespace defines the space within which the secret name must be unique.
If omitted, the namespace of the object being reconciled will be used. | | MaxLength: 63
MinLength: 1
Optional: \{\}
|
+#### SetASPathAction
+
+
+
+SetASPathAction defines actions to modify the BGP AS path attribute.
+
+
+
+_Appears in:_
+- [BgpActions](#bgpactions)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `prepend` _[SetASPathPrepend](#setaspathprepend)_ | Prepend configures prepending to the AS path. | | Optional: \{\}
|
+| `replace` _[SetASPathReplace](#setaspathreplace)_ | Replace configures replacement of AS numbers in the AS path. | | Optional: \{\}
|
+| `asNumber` _[IntOrString](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.35/#intorstring-intstr-util)_ | ASNumber sets the AS path to the specified AS number.
Supports both plain format (1-4294967295) and dotted notation (1-65535.0-65535) as per RFC 5396. | | Optional: \{\}
|
+
+
+#### SetASPathPrepend
+
+
+
+SetASPathPrepend configures prepending to the BGP AS path.
+Either asNumber or useLastAS must be specified, but not both.
+
+
+
+_Appears in:_
+- [SetASPathAction](#setaspathaction)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `asNumber` _[IntOrString](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.35/#intorstring-intstr-util)_ | ASNumber is the autonomous system number to prepend to the AS path.
Supports both plain format (1-4294967295) and dotted notation (1-65535.0-65535) as per RFC 5396.
Mutually exclusive with useLastAS. | | Optional: \{\}
|
+| `useLastAS` _integer_ | UseLastAS prepends the last AS number in the existing AS path the specified number of times.
Mutually exclusive with asNumber. | | Maximum: 10
Minimum: 1
Optional: \{\}
|
+
+
+#### SetASPathReplace
+
+
+
+SetASPathReplace configures replacement of AS numbers in the BGP AS path.
+Either privateAS or asNumber must be specified, but not both.
+
+
+
+_Appears in:_
+- [SetASPathAction](#setaspathaction)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `privateAS` _boolean_ | PrivateAS, when set to true, targets all private AS numbers in the path for replacement.
Mutually exclusive with asNumber. | | Optional: \{\}
|
+| `asNumber` _[IntOrString](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.35/#intorstring-intstr-util)_ | ASNumber targets a specific AS number in the path for replacement.
Supports both plain format (1-4294967295) and dotted notation (1-65535.0-65535) as per RFC 5396.
Mutually exclusive with privateAS. | | Optional: \{\}
|
+| `replacement` _[IntOrString](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.35/#intorstring-intstr-util)_ | Replacement is the AS number to substitute in place of matched AS numbers.
Supports both plain format (1-4294967295) and dotted notation (1-65535.0-65535) as per RFC 5396. | | Required: \{\}
|
+
+
#### SetCommunityAction
diff --git a/internal/controller/core/routingpolicy_controller_test.go b/internal/controller/core/routingpolicy_controller_test.go
index e138bbb7..b7c99ac7 100644
--- a/internal/controller/core/routingpolicy_controller_test.go
+++ b/internal/controller/core/routingpolicy_controller_test.go
@@ -10,6 +10,7 @@ import (
. "github.com/onsi/gomega"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/util/intstr"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
@@ -233,6 +234,113 @@ var _ = Describe("RoutingPolicy Controller", func() {
}).Should(Succeed())
})
+ It("Should successfully reconcile a RoutingPolicy with AS path actions", func() {
+ By("Creating a RoutingPolicy with various AS path actions")
+ asn65000 := intstr.FromInt32(65000)
+ asn65001 := intstr.FromInt32(65001)
+ asn65100 := intstr.FromInt32(65100)
+ asnDotted := intstr.FromString("1.100")
+ useLastAS := int32(5)
+
+ rp := &v1alpha1.RoutingPolicy{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: name,
+ Namespace: metav1.NamespaceDefault,
+ },
+ Spec: v1alpha1.RoutingPolicySpec{
+ DeviceRef: v1alpha1.LocalObjectReference{Name: name},
+ Name: name,
+ Statements: []v1alpha1.PolicyStatement{
+ {
+ // Prepend a specific ASN
+ Sequence: 10,
+ Actions: v1alpha1.PolicyActions{
+ RouteDisposition: v1alpha1.AcceptRoute,
+ BgpActions: &v1alpha1.BgpActions{
+ SetASPath: &v1alpha1.SetASPathAction{
+ Prepend: &v1alpha1.SetASPathPrepend{
+ ASNumber: &asn65000,
+ },
+ },
+ },
+ },
+ },
+ {
+ // Prepend using last-as
+ Sequence: 20,
+ Actions: v1alpha1.PolicyActions{
+ RouteDisposition: v1alpha1.AcceptRoute,
+ BgpActions: &v1alpha1.BgpActions{
+ SetASPath: &v1alpha1.SetASPathAction{
+ Prepend: &v1alpha1.SetASPathPrepend{
+ UseLastAS: &useLastAS,
+ },
+ },
+ },
+ },
+ },
+ {
+ // Replace private AS with a specific ASN
+ Sequence: 30,
+ Actions: v1alpha1.PolicyActions{
+ RouteDisposition: v1alpha1.AcceptRoute,
+ BgpActions: &v1alpha1.BgpActions{
+ SetASPath: &v1alpha1.SetASPathAction{
+ Replace: &v1alpha1.SetASPathReplace{
+ PrivateAS: true,
+ Replacement: asn65100,
+ },
+ },
+ },
+ },
+ },
+ {
+ // Replace a specific ASN with another (using dotted notation)
+ Sequence: 40,
+ Actions: v1alpha1.PolicyActions{
+ RouteDisposition: v1alpha1.AcceptRoute,
+ BgpActions: &v1alpha1.BgpActions{
+ SetASPath: &v1alpha1.SetASPathAction{
+ Replace: &v1alpha1.SetASPathReplace{
+ ASNumber: &asn65001,
+ Replacement: asnDotted,
+ },
+ },
+ },
+ },
+ },
+ {
+ // Set explicit AS path
+ Sequence: 50,
+ Actions: v1alpha1.PolicyActions{
+ RouteDisposition: v1alpha1.AcceptRoute,
+ BgpActions: &v1alpha1.BgpActions{
+ SetASPath: &v1alpha1.SetASPathAction{
+ ASNumber: &asn65000,
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+ Expect(k8sClient.Create(ctx, rp)).To(Succeed())
+
+ By("Verifying the controller sets successful status conditions")
+ Eventually(func(g Gomega) {
+ resource := &v1alpha1.RoutingPolicy{}
+ g.Expect(k8sClient.Get(ctx, key, resource)).To(Succeed())
+ g.Expect(resource.Status.Conditions).To(HaveLen(1))
+ g.Expect(resource.Status.Conditions[0].Type).To(Equal(v1alpha1.ReadyCondition))
+ g.Expect(resource.Status.Conditions[0].Status).To(Equal(metav1.ConditionTrue))
+ }).Should(Succeed())
+
+ By("Verifying the RoutingPolicy is configured in the provider")
+ Eventually(func(g Gomega) {
+ g.Expect(testProvider.RoutingPolicies.Has(name)).To(BeTrue(), "Provider should have RoutingPolicy configured")
+ }).Should(Succeed())
+ })
+
It("Should handle PrefixSet on different device", func() {
By("Creating a PrefixSet on a different device")
ps := &v1alpha1.PrefixSet{
diff --git a/internal/provider/cisco/nxos/provider.go b/internal/provider/cisco/nxos/provider.go
index c12fe772..1ef0ae93 100644
--- a/internal/provider/cisco/nxos/provider.go
+++ b/internal/provider/cisco/nxos/provider.go
@@ -1768,6 +1768,32 @@ func (p *Provider) EnsureRoutingPolicy(ctx context.Context, req *provider.Ensure
return err
}
}
+ if stmt.Actions.BgpActions.SetASPath != nil {
+ asp := stmt.Actions.BgpActions.SetASPath
+ if asp.Prepend != nil {
+ if asp.Prepend.ASNumber != nil {
+ e.SetASPathPrependItems.AS = asp.Prepend.ASNumber.String()
+ }
+ if asp.Prepend.UseLastAS != nil {
+ e.SetASPathLastASItems.LastAS = *asp.Prepend.UseLastAS
+ }
+ }
+ if asp.Replace != nil {
+ if asp.Replace.PrivateAS {
+ e.SetASPathReplaceItems.MatchPrivateAS = true
+ e.SetASPathReplaceItems.ReplaceAsn = asp.Replace.Replacement.String()
+ e.SetASPathReplaceItems.ReplaceType = "asn"
+ } else if asp.Replace.ASNumber != nil {
+ e.SetASPathReplaceItems.MatchAsnList = asp.Replace.ASNumber.String()
+ e.SetASPathReplaceItems.MatchPrivateAS = false
+ e.SetASPathReplaceItems.ReplaceAsn = asp.Replace.Replacement.String()
+ e.SetASPathReplaceItems.ReplaceType = "asn"
+ }
+ }
+ if asp.ASNumber != nil {
+ e.SetASPathItems.AsnList = asp.ASNumber.String()
+ }
+ }
}
rm.EntItems.EntryList.Set(e)
diff --git a/internal/provider/cisco/nxos/routemap.go b/internal/provider/cisco/nxos/routemap.go
index 04f1e4d0..70b14080 100644
--- a/internal/provider/cisco/nxos/routemap.go
+++ b/internal/provider/cisco/nxos/routemap.go
@@ -42,6 +42,21 @@ type RouteMapEntry struct {
RsRtDstAttList gnmiext.List[string, *RsRtDstAtt] `json:"RsRtDstAtt-list,omitzero"`
} `json:"rsrtDstAtt-items,omitzero"`
} `json:"mrtdst-items,omitzero"`
+ SetASPathPrependItems struct {
+ AS string `json:"as"`
+ } `json:"setaspathprepend-items,omitzero"`
+ SetASPathLastASItems struct {
+ LastAS int32 `json:"lastas"`
+ } `json:"setaspathlastas-items,omitzero"`
+ SetASPathReplaceItems struct {
+ MatchAsnList string `json:"matchAsnList,omitempty"`
+ MatchPrivateAS bool `json:"matchPrivateAs"`
+ ReplaceAsn string `json:"replaceAsn"`
+ ReplaceType string `json:"replaceType"`
+ } `json:"setaspathreplace-items,omitzero"`
+ SetASPathItems struct {
+ AsnList string `json:"asnList"`
+ } `json:"setaspath-items,omitzero"`
}
func (e *RouteMapEntry) Key() int32 { return e.Order }
diff --git a/internal/provider/cisco/nxos/routemap_test.go b/internal/provider/cisco/nxos/routemap_test.go
index fdfc7610..f743fa27 100644
--- a/internal/provider/cisco/nxos/routemap_test.go
+++ b/internal/provider/cisco/nxos/routemap_test.go
@@ -16,4 +16,55 @@ func init() {
rm.Name = "RM-REDIST"
rm.EntItems.EntryList.Set(e)
Register("route_map", rm)
+
+ prependEntry := &RouteMapEntry{}
+ prependEntry.Order = 10
+ prependEntry.Action = ActionPermit
+ prependEntry.SetASPathPrependItems.AS = "65000"
+
+ prependRM := &RouteMap{}
+ prependRM.Name = "RM-ASPATH-PREPEND"
+ prependRM.EntItems.EntryList.Set(prependEntry)
+ Register("route_map_aspath_prepend", prependRM)
+
+ lastASEntry := &RouteMapEntry{}
+ lastASEntry.Order = 10
+ lastASEntry.Action = ActionPermit
+ lastASEntry.SetASPathLastASItems.LastAS = 10
+
+ lastASRM := &RouteMap{}
+ lastASRM.Name = "RM-ASPATH-LASTAS"
+ lastASRM.EntItems.EntryList.Set(lastASEntry)
+ Register("route_map_aspath_lastas", lastASRM)
+
+ replaceEntry1 := &RouteMapEntry{}
+ replaceEntry1.Order = 10
+ replaceEntry1.Action = ActionPermit
+ replaceEntry1.SetASPathReplaceItems.MatchPrivateAS = true
+ replaceEntry1.SetASPathReplaceItems.ReplaceAsn = "65000"
+ replaceEntry1.SetASPathReplaceItems.ReplaceType = "asn"
+
+ replaceEntry2 := &RouteMapEntry{}
+ replaceEntry2.Order = 20
+ replaceEntry2.Action = ActionPermit
+ replaceEntry2.SetASPathReplaceItems.MatchAsnList = "65001"
+ replaceEntry2.SetASPathReplaceItems.MatchPrivateAS = false
+ replaceEntry2.SetASPathReplaceItems.ReplaceAsn = "65100"
+ replaceEntry2.SetASPathReplaceItems.ReplaceType = "asn"
+
+ replaceRM := &RouteMap{}
+ replaceRM.Name = "RM-ASPATH-REPLACE"
+ replaceRM.EntItems.EntryList.Set(replaceEntry1)
+ replaceRM.EntItems.EntryList.Set(replaceEntry2)
+ Register("route_map_aspath_replace", replaceRM)
+
+ setEntry := &RouteMapEntry{}
+ setEntry.Order = 10
+ setEntry.Action = ActionPermit
+ setEntry.SetASPathItems.AsnList = "65000"
+
+ setRM := &RouteMap{}
+ setRM.Name = "RM-ASPATH-SET"
+ setRM.EntItems.EntryList.Set(setEntry)
+ Register("route_map_aspath_set", setRM)
}
diff --git a/internal/provider/cisco/nxos/testdata/route_map_aspath_lastas.json b/internal/provider/cisco/nxos/testdata/route_map_aspath_lastas.json
new file mode 100644
index 00000000..4da78be9
--- /dev/null
+++ b/internal/provider/cisco/nxos/testdata/route_map_aspath_lastas.json
@@ -0,0 +1,22 @@
+{
+ "rpm-items": {
+ "rtmap-items": {
+ "Rule-list": [
+ {
+ "name": "RM-ASPATH-LASTAS",
+ "ent-items": {
+ "Entry-list": [
+ {
+ "action": "permit",
+ "order": 10,
+ "setaspathlastas-items": {
+ "lastas": 10
+ }
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+}
diff --git a/internal/provider/cisco/nxos/testdata/route_map_aspath_lastas.json.txt b/internal/provider/cisco/nxos/testdata/route_map_aspath_lastas.json.txt
new file mode 100644
index 00000000..1c044434
--- /dev/null
+++ b/internal/provider/cisco/nxos/testdata/route_map_aspath_lastas.json.txt
@@ -0,0 +1,2 @@
+route-map RM-ASPATH-LASTAS permit 10
+ set as-path prepend last-as 10
diff --git a/internal/provider/cisco/nxos/testdata/route_map_aspath_prepend.json b/internal/provider/cisco/nxos/testdata/route_map_aspath_prepend.json
new file mode 100644
index 00000000..3b4ed7ab
--- /dev/null
+++ b/internal/provider/cisco/nxos/testdata/route_map_aspath_prepend.json
@@ -0,0 +1,22 @@
+{
+ "rpm-items": {
+ "rtmap-items": {
+ "Rule-list": [
+ {
+ "name": "RM-ASPATH-PREPEND",
+ "ent-items": {
+ "Entry-list": [
+ {
+ "action": "permit",
+ "order": 10,
+ "setaspathprepend-items": {
+ "as": "65000"
+ }
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+}
diff --git a/internal/provider/cisco/nxos/testdata/route_map_aspath_prepend.json.txt b/internal/provider/cisco/nxos/testdata/route_map_aspath_prepend.json.txt
new file mode 100644
index 00000000..1b6a6151
--- /dev/null
+++ b/internal/provider/cisco/nxos/testdata/route_map_aspath_prepend.json.txt
@@ -0,0 +1,2 @@
+route-map RM-ASPATH-PREPEND permit 10
+ set as-path prepend 65000
diff --git a/internal/provider/cisco/nxos/testdata/route_map_aspath_replace.json b/internal/provider/cisco/nxos/testdata/route_map_aspath_replace.json
new file mode 100644
index 00000000..68379e9e
--- /dev/null
+++ b/internal/provider/cisco/nxos/testdata/route_map_aspath_replace.json
@@ -0,0 +1,34 @@
+{
+ "rpm-items": {
+ "rtmap-items": {
+ "Rule-list": [
+ {
+ "name": "RM-ASPATH-REPLACE",
+ "ent-items": {
+ "Entry-list": [
+ {
+ "action": "permit",
+ "order": 10,
+ "setaspathreplace-items": {
+ "matchPrivateAs": true,
+ "replaceAsn": "65000",
+ "replaceType": "asn"
+ }
+ },
+ {
+ "action": "permit",
+ "order": 20,
+ "setaspathreplace-items": {
+ "matchAsnList": "65001",
+ "matchPrivateAs": false,
+ "replaceAsn": "65100",
+ "replaceType": "asn"
+ }
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+}
diff --git a/internal/provider/cisco/nxos/testdata/route_map_aspath_replace.json.txt b/internal/provider/cisco/nxos/testdata/route_map_aspath_replace.json.txt
new file mode 100644
index 00000000..9615a351
--- /dev/null
+++ b/internal/provider/cisco/nxos/testdata/route_map_aspath_replace.json.txt
@@ -0,0 +1,4 @@
+route-map RM-ASPATH-REPLACE permit 10
+ set as-path replace private-as with 65000
+route-map RM-ASPATH-REPLACE permit 20
+ set as-path replace 65001 with 65100
diff --git a/internal/provider/cisco/nxos/testdata/route_map_aspath_set.json b/internal/provider/cisco/nxos/testdata/route_map_aspath_set.json
new file mode 100644
index 00000000..2920076c
--- /dev/null
+++ b/internal/provider/cisco/nxos/testdata/route_map_aspath_set.json
@@ -0,0 +1,22 @@
+{
+ "rpm-items": {
+ "rtmap-items": {
+ "Rule-list": [
+ {
+ "name": "RM-ASPATH-SET",
+ "ent-items": {
+ "Entry-list": [
+ {
+ "action": "permit",
+ "order": 10,
+ "setaspath-items": {
+ "asnList": "65000"
+ }
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+}
diff --git a/internal/provider/cisco/nxos/testdata/route_map_aspath_set.json.txt b/internal/provider/cisco/nxos/testdata/route_map_aspath_set.json.txt
new file mode 100644
index 00000000..7d11f01e
--- /dev/null
+++ b/internal/provider/cisco/nxos/testdata/route_map_aspath_set.json.txt
@@ -0,0 +1,2 @@
+route-map RM-ASPATH-SET permit 10
+ set as-path 65000