From a8eda25cc269a10a05c24adc024dee667d8febe4 Mon Sep 17 00:00:00 2001 From: Sven Rosenzweig Date: Mon, 13 Jul 2026 14:51:15 +0200 Subject: [PATCH] feat(iosxr): Implement Route Policies Compared with OpenConfig and Cisco NX-OS, IOS-XR-based route policies are more powerful and feature-rich. However, we currently do not need most of these advanced features. Instead, we adapt to the simpler route-map configuration model (sequence, condition, action, and disposition). Route maps work by iterating over each entry in order of their sequence number. If a condition matches, the configured action is executed and processing stops after a match is found. The default action when no match is found is to pass. We assume the same behavior with route policies: after each condition check, we set the policy result to "done" (which stops processing and accepts the route) or "drop" (which stops processing and rejects the route). At the end, if no match is found, the route is accepted by default. Some key aspects that make Route Policies richer than route maps: * Support for if/else control flow * Referencing other RPLs within each other, creating nested blocks of policy logic * Boolean operators (and, not, or) * Parameterized route policy configuration * Wildcard support in setting values Yang support for Cisco IOS XR route policy support also differs from other OS models. It simply accepts one single policy string, being correctly formatted in the way mentioned above: ``` route-policy if then set done endif end-policy ``` https://github.com/YangModels/yang/blob/ad7aa9d483c592661c1df3d38bdfc99dc01cba56/vendor/cisco/xr/2522/Cisco-IOS-XR-um-route-policy-cfg.yang#L81-L94 Signed-off-by: Sven Rosenzweig --- internal/provider/cisco/iosxr/provider.go | 39 ++- .../provider/cisco/iosxr/route_policies.go | 318 +++++++++++++++++- .../cisco/iosxr/route_policies_test.go | 272 +++++++++++++++ .../route_policies/bgp_all_actions_policy.txt | 8 + .../bgp_multi_action_policy.txt | 7 + .../route_policies/multi_statement_policy.txt | 7 + .../testdata/route_policies/simple_policy.txt | 6 + .../cisco/iosxr/testdata/routepolicy.json | 10 + .../cisco/iosxr/testdata/routepolicy.json.txt | 6 + 9 files changed, 664 insertions(+), 9 deletions(-) create mode 100644 internal/provider/cisco/iosxr/route_policies_test.go create mode 100644 internal/provider/cisco/iosxr/testdata/route_policies/bgp_all_actions_policy.txt create mode 100644 internal/provider/cisco/iosxr/testdata/route_policies/bgp_multi_action_policy.txt create mode 100644 internal/provider/cisco/iosxr/testdata/route_policies/multi_statement_policy.txt create mode 100644 internal/provider/cisco/iosxr/testdata/route_policies/simple_policy.txt create mode 100644 internal/provider/cisco/iosxr/testdata/routepolicy.json create mode 100644 internal/provider/cisco/iosxr/testdata/routepolicy.json.txt diff --git a/internal/provider/cisco/iosxr/provider.go b/internal/provider/cisco/iosxr/provider.go index c05255a09..f3c2592ec 100644 --- a/internal/provider/cisco/iosxr/provider.go +++ b/internal/provider/cisco/iosxr/provider.go @@ -20,12 +20,13 @@ import ( ) var ( - _ provider.Provider = &Provider{} - _ provider.DeviceProvider = &Provider{} - _ provider.InterfaceProvider = &Provider{} - _ provider.VRFProvider = &Provider{} - _ provider.BGPProvider = &Provider{} - _ provider.BGPPeerProvider = &Provider{} + _ provider.Provider = &Provider{} + _ provider.DeviceProvider = &Provider{} + _ provider.InterfaceProvider = &Provider{} + _ provider.VRFProvider = &Provider{} + _ provider.BGPProvider = &Provider{} + _ provider.BGPPeerProvider = &Provider{} + _ provider.RoutingPolicyProvider = &Provider{} ) type Provider struct { @@ -457,7 +458,7 @@ func (p *Provider) EnsureBGPPeer(ctx context.Context, req *provider.EnsureBGPPee routerID := bgp.AS[0].ASNumber // Create Default Route Policies for the peer - defaultRpl := NewRoutePolicy(req.VRF.Spec.Name) + defaultRpl := NewEmptyAcceptRoutePolicy(req.VRF.Spec.Name) err := p.client.Update(ctx, &defaultRpl) if err != nil { @@ -546,7 +547,7 @@ func (p *Provider) DeleteBGPPeer(ctx context.Context, req *provider.DeleteBGPPee return fmt.Errorf("bgp peer: failed to get bgp instance 'default': %w", err) } - defaultRpl := NewRoutePolicy(req.VRF.Spec.Name) + defaultRpl := NewEmptyAcceptRoutePolicy(req.VRF.Spec.Name) peer := BGPPeer{ RouterID: bgp.AS[0].ASNumber, @@ -574,6 +575,28 @@ func (p *Provider) GetPeerStatus(ctx context.Context, req *provider.BGPPeerStatu return state, nil } +func (p *Provider) EnsureRoutingPolicy(ctx context.Context, req *provider.EnsureRoutingPolicyRequest) error { + policy, err := NewPolicyString(req.Name, req.Statements) + if err != nil { + return err + } + + rp := &RoutePolicy{ + Name: req.Name, + Body: policy.String(), + } + + return p.client.Update(ctx, rp) +} + +func (p *Provider) DeleteRoutingPolicy(ctx context.Context, req *provider.DeleteRoutingPolicyRequest) error { + rp := &RoutePolicy{ + Name: req.Name, + } + + return p.client.Delete(ctx, rp) +} + func init() { provider.Register("cisco-iosxr-gnmi", NewProvider) } diff --git a/internal/provider/cisco/iosxr/route_policies.go b/internal/provider/cisco/iosxr/route_policies.go index 13e3b3cc9..930ec3473 100644 --- a/internal/provider/cisco/iosxr/route_policies.go +++ b/internal/provider/cisco/iosxr/route_policies.go @@ -4,13 +4,22 @@ package iosxr import ( + "errors" "fmt" + "slices" + "strconv" + "strings" + "k8s.io/apimachinery/pkg/util/intstr" + + "github.com/ironcore-dev/network-operator/api/core/v1alpha1" + "github.com/ironcore-dev/network-operator/internal/provider" "github.com/ironcore-dev/network-operator/internal/transport/gnmiext" ) var _ gnmiext.DataElement = (*RoutePolicy)(nil) +// RoutePolicy represents an IOS-XR route policy configuration element. type RoutePolicy struct { Name string `json:"route-policy-name"` Body string `json:"rpl-route-policy"` @@ -20,10 +29,317 @@ func (rp *RoutePolicy) XPath() string { return "Cisco-IOS-XR-policy-repository-cfg:routing-policy/route-policies/route-policy[route-policy-name=" + rp.Name + "]" } -func NewRoutePolicy(vrf string) RoutePolicy { +// NewEmptyAcceptRoutePolicy creates a pass-through route policy for the specified VRF. +func NewEmptyAcceptRoutePolicy(vrf string) RoutePolicy { name := fmt.Sprintf("RPL_%s_IN", vrf) return RoutePolicy{ Name: name, Body: fmt.Sprintf("route-policy %s\n pass\nend-policy\n", name), } } + +// PolicyString generates IOS-XR RPL syntax from ordered policy statements. +// Statements are evaluated sequentially until a match is found. +type PolicyString struct { + Statement PolicyStatement + Name string +} + +// NewPolicyString creates a PolicyString from provider-agnostic policy statements. +// Statements are sorted by sequence number and linked in evaluation order. +func NewPolicyString(name string, providerStatements []provider.PolicyStatement) (*PolicyString, error) { + // Sort statements by sequence number (lowest first) + statements := make([]provider.PolicyStatement, len(providerStatements)) + copy(statements, providerStatements) + slices.SortFunc(statements, func(a, b provider.PolicyStatement) int { + if a.Sequence < b.Sequence { + return -1 + } + if a.Sequence > b.Sequence { + return 1 + } + return 0 + }) + + // Build condition string and actions for each statement + var iosxrStatements []*Statement + for _, stmt := range statements { + conditions, err := NewConditions(stmt.Conditions) + if err != nil { + return nil, fmt.Errorf("failed to build condition for statement %d: %w", stmt.Sequence, err) + } + + actions, err := NewActions(stmt.Actions) + if err != nil { + return nil, fmt.Errorf("failed to build actions for statement %d: %w", stmt.Sequence, err) + } + iosxrStmt := NewStatement(conditions, actions) + iosxrStatements = append(iosxrStatements, iosxrStmt) + } + + // Link statements together (Next/Back pointers) + for i := range iosxrStatements { + if i > 0 { + iosxrStatements[i].previous = iosxrStatements[i-1] + } + if i < len(iosxrStatements)-1 { + iosxrStatements[i].next = iosxrStatements[i+1] + } + } + + return &PolicyString{ + Name: name, + Statement: iosxrStatements[0], + }, nil +} + +func (p *PolicyString) String() string { + var sb strings.Builder + fmt.Fprintf(&sb, "route-policy %s\n ", p.Name) + sb.WriteString(p.Statement.String()) + sb.WriteString("\nend-policy\n") + return sb.String() +} + +// PolicyStatement represents a single statement in a route policy. +type PolicyStatement interface { + String() string + Next() PolicyStatement + Previous() PolicyStatement +} + +// Statement is a policy statement with conditions, actions, and route disposition. +// Statements are linked to form if-elseif-endif chains in IOS-XR RPL syntax. +type Statement struct { + Action Actions + Condition Conditions + Disposition string + next PolicyStatement + previous PolicyStatement +} + +// NewStatement creates a Statement with the specified conditions and actions. +func NewStatement(condition Conditions, actions Actions) *Statement { + return &Statement{ + Condition: condition, + Action: actions, + Disposition: "done", + } +} + +func (s *Statement) Next() PolicyStatement { + return s.next +} + +func (s *Statement) Previous() PolicyStatement { + return s.previous +} + +func (s *Statement) String() string { + var sb strings.Builder + + // first element starts if + if s.previous == nil { + fmt.Fprintf(&sb, "if %s then\n", s.Condition.String()) + } else { + fmt.Fprintf(&sb, "elseif %s then\n", s.Condition.String()) + } + + sb.WriteString(s.Action.String()) + + // no other element, close action + if s.next == nil { + sb.WriteString("endif") + } + + res := sb.String() + if s.next != nil { + res = res + s.next.String() + } + return res +} + +// PolicyCondition represents a single route matching condition. +type PolicyCondition interface { + String() string +} + +// Conditions represents a set of route matching conditions combined with logical AND. +type Conditions struct { + Conditions []PolicyCondition +} + +func (cl *Conditions) String() string { + condStrings := make([]string, 0, len(cl.Conditions)) + for _, cond := range cl.Conditions { + condStrings = append(condStrings, cond.String()) + } + return strings.Join(condStrings, " and ") +} + +// MatchPrefixCondition matches routes against a prefix set. +type MatchPrefixCondition struct { + PrefixSet *v1alpha1.PrefixSet +} + +func (c *MatchPrefixCondition) String() string { + prefixes := make([]string, 0, len(c.PrefixSet.Spec.Entries)) + for _, entry := range c.PrefixSet.Spec.Entries { + prefixes = append(prefixes, entry.Prefix.String()) + } + return fmt.Sprintf("destination in (%s)", strings.Join(prefixes, ", ")) +} + +// Action represents a single route modification operation. +type Action interface { + Action() string +} + +// Actions represents a set of route modification actions and the final route disposition. +type Actions struct { + Actions []Action + RouteDisposition v1alpha1.RouteDisposition +} + +func (al *Actions) String() string { + var sb strings.Builder + for _, action := range al.Actions { + sb.WriteString(" ") + sb.WriteString(action.Action()) + } + + // Route disposition: RejectRoute -> "drop", AcceptRoute -> "done". + // IOS-XR "pass" is not used as it would continue policy evaluation. + if al.RouteDisposition == v1alpha1.RejectRoute { + sb.WriteString(" drop\n ") + } else { + sb.WriteString(" done\n ") + } + return sb.String() +} + +func NewActions(actions v1alpha1.PolicyActions) (Actions, error) { + var result Actions + + if actions.BgpActions != nil && actions.BgpActions.SetCommunity != nil { + result.Actions = append(result.Actions, &ComAction{ + Values: actions.BgpActions.SetCommunity.Communities, + }) + } + + if actions.BgpActions != nil && actions.BgpActions.SetExtCommunity != nil { + result.Actions = append(result.Actions, &ExtCommAction{ + Values: actions.BgpActions.SetExtCommunity.Communities, + }) + } + + if actions.BgpActions != nil && actions.BgpActions.SetASPath != nil { + result.Actions = append(result.Actions, &ASPathAction{ + PathAction: *actions.BgpActions.SetASPath, + }) + } + + result.RouteDisposition = actions.RouteDisposition + + return result, nil +} + +// ExtCommAction sets BGP extended community attributes. +type ExtCommAction struct { + Values []string +} + +func (pa *ExtCommAction) Action() string { + communities := strings.Join(pa.Values, ", ") + return fmt.Sprintf("set extcommunity rt (%s)\n", communities) +} + +// ComAction sets BGP community attributes. +type ComAction struct { + Values []string +} + +func (pa *ComAction) Action() string { + communities := strings.Join(pa.Values, ", ") + return fmt.Sprintf("set community (%s)\n", communities) +} + +// ASPathAction modifies BGP AS path attributes. +type ASPathAction struct { + PathAction v1alpha1.SetASPathAction +} + +func (pa *ASPathAction) Action() string { + var sb strings.Builder + + // Handle Prepend action + if pa.PathAction.Prepend != nil { + if pa.PathAction.Prepend.ASNumber != nil { + asNum := formatASNumber(pa.PathAction.Prepend.ASNumber) + fmt.Fprintf(&sb, "prepend as-path %s\n", asNum) + } else if pa.PathAction.Prepend.UseLastAS != nil { + fmt.Fprintf(&sb, "prepend as-path most-recent %d\n", *pa.PathAction.Prepend.UseLastAS) + } + return sb.String() + } + + // Handle Replace action + if pa.PathAction.Replace != nil { + replacement := formatASNumber(&pa.PathAction.Replace.Replacement) + + if pa.PathAction.Replace.PrivateAS { + sb.WriteString("replace as-path private-as\n") + return sb.String() + } + if pa.PathAction.Replace.ASNumber != nil { + // Replace specific AS number + // todo implement replacement logic for specific AS number if needed + // targetAS := formatASNumber(pa.PathAction.Replace.ASNumber) + fmt.Fprintf(&sb, "replace as-path all '%s'\n", replacement) + return sb.String() + } + fmt.Fprintf(&sb, "replace as-path all %s\n", replacement) + return sb.String() + } + + // Handle direct ASNumber set (sets the AS path to a single AS number) + if pa.PathAction.ASNumber != nil { + asNum := formatASNumber(pa.PathAction.ASNumber) + sb.WriteString("set as-path ") + sb.WriteString(asNum) + return sb.String() + } + + return sb.String() +} + +// formatASNumber formats an IntOrString AS number for IOS XR RPL syntax. +// Handles both plain format (integer) and dotted notation (string). +func formatASNumber(asNum *intstr.IntOrString) string { + if asNum.Type == intstr.Int { + return strconv.Itoa(asNum.IntValue()) + } + return asNum.StrVal +} + +// NewConditions converts provider-agnostic conditions to IOS-XR conditions. +func NewConditions(conditions []provider.PolicyCondition) (Conditions, error) { + var condList Conditions + + for _, cond := range conditions { + switch c := cond.(type) { + case provider.MatchPrefixSetCondition: + if len(c.PrefixSet.Spec.Entries) == 0 { + return Conditions{}, errors.New("prefix set has no entries") + } + matchPrefix := &MatchPrefixCondition{ + PrefixSet: c.PrefixSet, + } + condList.Conditions = append(condList.Conditions, matchPrefix) + default: + return Conditions{}, fmt.Errorf("unsupported condition type: %T", cond) + } + } + + return Conditions{Conditions: condList.Conditions}, nil +} diff --git a/internal/provider/cisco/iosxr/route_policies_test.go b/internal/provider/cisco/iosxr/route_policies_test.go new file mode 100644 index 000000000..eda596740 --- /dev/null +++ b/internal/provider/cisco/iosxr/route_policies_test.go @@ -0,0 +1,272 @@ +// SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +package iosxr + +import ( + "net/netip" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/util/intstr" + + "github.com/ironcore-dev/network-operator/api/core/v1alpha1" + "github.com/ironcore-dev/network-operator/internal/provider" +) + +func init() { + pl, err := NewPolicyString("TEST-POLICY", []provider.PolicyStatement{ + { + Sequence: 10, + Conditions: []provider.PolicyCondition{ + provider.MatchPrefixSetCondition{ + PrefixSet: &v1alpha1.PrefixSet{ + Spec: v1alpha1.PrefixSetSpec{ + Entries: []v1alpha1.PrefixEntry{ + { + Prefix: v1alpha1.IPPrefix{ + Prefix: netip.MustParsePrefix("10.0.0.0/8"), + }, + }, + }, + }, + }, + }, + }, + Actions: v1alpha1.PolicyActions{ + RouteDisposition: v1alpha1.AcceptRoute, + BgpActions: &v1alpha1.BgpActions{ + SetCommunity: &v1alpha1.SetCommunityAction{ + Communities: []string{"65000:100"}, + }, + }, + }, + }, + }) + if err != nil { + panic(err) + } + + rpl := RoutePolicy{ + Name: "TEST-POLICY", + Body: pl.String(), + } + Register("routepolicy", &rpl) +} + +// readGoldenFile reads a golden file from testdata/route_policies +func readGoldenFile(t *testing.T, filename string) string { + t.Helper() + + path := filepath.Join("testdata", "route_policies", filename) + content, err := os.ReadFile(path) + require.NoError(t, err, "failed to read golden file %s", filename) + + return string(content) +} + +// ptr returns a pointer to the given value +// +//nolint:modernize // ptr helper is needed for value-returning functions like intstr.FromInt32 +func ptr[T any](v T) *T { + return &v +} + +func TestRoutePolicyString(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + policyName string + statements []provider.PolicyStatement + goldenFile string + wantErr bool + }{ + { + name: "simple policy with prefix match and community", + policyName: "TEST-POLICY", + statements: []provider.PolicyStatement{ + { + Sequence: 10, + Conditions: []provider.PolicyCondition{ + provider.MatchPrefixSetCondition{ + PrefixSet: &v1alpha1.PrefixSet{ + Spec: v1alpha1.PrefixSetSpec{ + Entries: []v1alpha1.PrefixEntry{ + { + Prefix: v1alpha1.IPPrefix{ + Prefix: netip.MustParsePrefix("10.0.0.0/8"), + }, + }, + }, + }, + }, + }, + }, + Actions: v1alpha1.PolicyActions{ + RouteDisposition: v1alpha1.AcceptRoute, + BgpActions: &v1alpha1.BgpActions{ + SetCommunity: &v1alpha1.SetCommunityAction{ + Communities: []string{"65000:100"}, + }, + }, + }, + }, + }, + goldenFile: "simple_policy.txt", + wantErr: false, + }, + { + name: "multiple statements with different actions", + policyName: "MULTI-STATEMENT-POLICY", + statements: []provider.PolicyStatement{ + { + Sequence: 10, + Conditions: []provider.PolicyCondition{ + provider.MatchPrefixSetCondition{ + PrefixSet: &v1alpha1.PrefixSet{ + Spec: v1alpha1.PrefixSetSpec{ + Entries: []v1alpha1.PrefixEntry{ + { + Prefix: v1alpha1.IPPrefix{ + Prefix: netip.MustParsePrefix("192.168.0.0/16"), + }, + }, + }, + }, + }, + }, + }, + Actions: v1alpha1.PolicyActions{ + RouteDisposition: v1alpha1.RejectRoute, + }, + }, + { + Sequence: 20, + Conditions: []provider.PolicyCondition{ + provider.MatchPrefixSetCondition{ + PrefixSet: &v1alpha1.PrefixSet{ + Spec: v1alpha1.PrefixSetSpec{ + Entries: []v1alpha1.PrefixEntry{ + { + Prefix: v1alpha1.IPPrefix{ + Prefix: netip.MustParsePrefix("10.0.0.0/8"), + }, + }, + }, + }, + }, + }, + }, + Actions: v1alpha1.PolicyActions{ + RouteDisposition: v1alpha1.AcceptRoute, + }, + }, + }, + goldenFile: "multi_statement_policy.txt", + wantErr: false, + }, + { + name: "policy with multiple BGP actions", + policyName: "BGP-MULTI-ACTION", + statements: []provider.PolicyStatement{ + { + Sequence: 10, + Conditions: []provider.PolicyCondition{ + provider.MatchPrefixSetCondition{ + PrefixSet: &v1alpha1.PrefixSet{ + Spec: v1alpha1.PrefixSetSpec{ + Entries: []v1alpha1.PrefixEntry{ + { + Prefix: v1alpha1.IPPrefix{ + Prefix: netip.MustParsePrefix("10.0.0.0/8"), + }, + }, + }, + }, + }, + }, + }, + Actions: v1alpha1.PolicyActions{ + RouteDisposition: v1alpha1.AcceptRoute, + BgpActions: &v1alpha1.BgpActions{ + SetCommunity: &v1alpha1.SetCommunityAction{ + Communities: []string{"65000:100", "65000:200"}, + }, + SetExtCommunity: &v1alpha1.SetExtCommunityAction{ + Communities: []string{"65000:100"}, + }, + }, + }, + }, + }, + goldenFile: "bgp_multi_action_policy.txt", + wantErr: false, + }, + { + name: "policy with all BGP actions combined", + policyName: "BGP-ALL-ACTIONS", + statements: []provider.PolicyStatement{ + { + Sequence: 10, + Conditions: []provider.PolicyCondition{ + provider.MatchPrefixSetCondition{ + PrefixSet: &v1alpha1.PrefixSet{ + Spec: v1alpha1.PrefixSetSpec{ + Entries: []v1alpha1.PrefixEntry{ + { + Prefix: v1alpha1.IPPrefix{ + Prefix: netip.MustParsePrefix("172.16.0.0/12"), + }, + }, + }, + }, + }, + }, + }, + Actions: v1alpha1.PolicyActions{ + RouteDisposition: v1alpha1.AcceptRoute, + BgpActions: &v1alpha1.BgpActions{ + SetCommunity: &v1alpha1.SetCommunityAction{ + Communities: []string{"65000:100", "65000:200", "65000:300"}, + }, + SetExtCommunity: &v1alpha1.SetExtCommunityAction{ + Communities: []string{"rt:65000:100", "rt:65000:200"}, + }, + SetASPath: &v1alpha1.SetASPathAction{ + Prepend: &v1alpha1.SetASPathPrepend{ + ASNumber: ptr(intstr.FromInt32(65200)), //nolint:modernize // ptr needed for value-returning function + }, + }, + }, + }, + }, + }, + goldenFile: "bgp_all_actions_policy.txt", + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + policy, err := NewPolicyString(tt.policyName, tt.statements) + + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err, "NewPolicy failed") + + expected := readGoldenFile(t, tt.goldenFile) + got := policy.String() + + assert.Equal(t, expected, got, "policy configuration mismatch") + }) + } +} diff --git a/internal/provider/cisco/iosxr/testdata/route_policies/bgp_all_actions_policy.txt b/internal/provider/cisco/iosxr/testdata/route_policies/bgp_all_actions_policy.txt new file mode 100644 index 000000000..9ef433357 --- /dev/null +++ b/internal/provider/cisco/iosxr/testdata/route_policies/bgp_all_actions_policy.txt @@ -0,0 +1,8 @@ +route-policy BGP-ALL-ACTIONS + if destination in (172.16.0.0/12) then + set community (65000:100, 65000:200, 65000:300) + set extcommunity rt (rt:65000:100, rt:65000:200) + prepend as-path 65200 + done + endif +end-policy diff --git a/internal/provider/cisco/iosxr/testdata/route_policies/bgp_multi_action_policy.txt b/internal/provider/cisco/iosxr/testdata/route_policies/bgp_multi_action_policy.txt new file mode 100644 index 000000000..8c59469e7 --- /dev/null +++ b/internal/provider/cisco/iosxr/testdata/route_policies/bgp_multi_action_policy.txt @@ -0,0 +1,7 @@ +route-policy BGP-MULTI-ACTION + if destination in (10.0.0.0/8) then + set community (65000:100, 65000:200) + set extcommunity rt (65000:100) + done + endif +end-policy diff --git a/internal/provider/cisco/iosxr/testdata/route_policies/multi_statement_policy.txt b/internal/provider/cisco/iosxr/testdata/route_policies/multi_statement_policy.txt new file mode 100644 index 000000000..fb1fe6277 --- /dev/null +++ b/internal/provider/cisco/iosxr/testdata/route_policies/multi_statement_policy.txt @@ -0,0 +1,7 @@ +route-policy MULTI-STATEMENT-POLICY + if destination in (192.168.0.0/16) then + drop + elseif destination in (10.0.0.0/8) then + done + endif +end-policy diff --git a/internal/provider/cisco/iosxr/testdata/route_policies/simple_policy.txt b/internal/provider/cisco/iosxr/testdata/route_policies/simple_policy.txt new file mode 100644 index 000000000..6fbab17f2 --- /dev/null +++ b/internal/provider/cisco/iosxr/testdata/route_policies/simple_policy.txt @@ -0,0 +1,6 @@ +route-policy TEST-POLICY + if destination in (10.0.0.0/8) then + set community (65000:100) + done + endif +end-policy diff --git a/internal/provider/cisco/iosxr/testdata/routepolicy.json b/internal/provider/cisco/iosxr/testdata/routepolicy.json new file mode 100644 index 000000000..5b72e1472 --- /dev/null +++ b/internal/provider/cisco/iosxr/testdata/routepolicy.json @@ -0,0 +1,10 @@ + { + "routing-policy": { + "route-policies": { + "route-policy": { + "route-policy-name": "TEST-POLICY", + "rpl-route-policy": "route-policy TEST-POLICY\n if destination in (10.0.0.0/8) then\n set community (65000:100)\n done\n endif\nend-policy\n" + } + } + } + } diff --git a/internal/provider/cisco/iosxr/testdata/routepolicy.json.txt b/internal/provider/cisco/iosxr/testdata/routepolicy.json.txt new file mode 100644 index 000000000..6fbab17f2 --- /dev/null +++ b/internal/provider/cisco/iosxr/testdata/routepolicy.json.txt @@ -0,0 +1,6 @@ +route-policy TEST-POLICY + if destination in (10.0.0.0/8) then + set community (65000:100) + done + endif +end-policy