04-24-2020 08:16 AM - edited 04-24-2020 08:19 AM
I'm trying to implement IOS-XR based route-policy configuration service in NSO and frankly said I'm stuck. It appears that content of the route-policy doesn't have the model in IOS-XR NED (possibly I can imagine why) and the body of it is visible as just one parameter "value" (marked green in attached outputs). Is there some decent way (other than one-liner paste) to configure such a route-policy from the NSO perspective?
Based on the example below, I want to create a simple route-policy (it does nothing, logic is not important, it's just for example purposes).
Here is the router native configuration as created from the router CLI:
route-policy set-comm($prefix_set, $as_set) if destination in $prefix_set then set community comm-prefix endif if destination in $as_set then set community comm-as endif pass end-policy
Here is how the router see above configuration displayed in XML:
</rpl-route-policy> </route-policy> <route-policy> <route-policy-name>set-comm</route-policy-name> <rpl-route-policy>route-policy set-comm($prefix_set, $as_set) if destination in $prefix_set then set community comm-prefix endif if destination in $as_set then set community comm-as endif pass end-policy
Now the same configuration from the NSO perspective. Native one:
show configuration devices device r13 config route-policy set-comm($prefix_set,\ $as_set) value " if destination in $prefix_set then\r\n set community comm-prefix\r\n endif\r\n if destination in $as_set then\r\n set community comm-as\r\n endif\r\n pass\r\n";
and in XML:
... <config> <route-policy xmlns="http://tail-f.com/ned/cisco-ios-xr"> <name>set-comm($prefix_set, $as_set)</name> <value> if destination in $prefix_set then set community comm-prefix endif if destination in $as_set then set community comm-as endif pass </value> </route-policy> </config> ...
I would rather prefer to make this configuration a bit more simplified from the end user perspective.
Solved! Go to Solution.
04-26-2020 12:48 AM
You're right. The entire content of the route-policy is modeled as a single string.
To handle that in a service, you need a single service instance to be the "owner" of the entire route-policy.
i.e. one service instance needs to be configure the entire route-policy. It cannot own just one line, while other lines are manually configured or maintained by another service instance (there are ways to allow multiple service instances to contribute content to the same route-policy, but not directly).
Anyhow, the way to handle this in a service is somewhat dependent on the complexity of the route-policy you want to create. In many cases, you'd have to move most of the logic to Python/Java. In that case, your code will constract the content of the 'value' leaf, and pass it to the template which will just apply it.
It is possible to handle route-policy logic in XML to some extent, but I still think it will be much better to have it in Python.
Something like this:
if this is the service model:
list foo { key name; leaf name { type string; } uses ncs:service-data; ncs:servicepoint foo-servicepoint; leaf device { type leafref { path "/ncs:devices/ncs:device/ncs:name"; } } leaf rpl-name { type string; } list destination { key prefix-set; leaf prefix-set { type string; } leaf community { type string; } } }
This will be the XML template:
<config-template xmlns="http://tail-f.com/ns/config/1.0"> <devices xmlns="http://tail-f.com/ns/ncs"> <device> <name>{/device}</name> <config> <route-policy xmlns="http://tail-f.com/ned/cisco-ios-xr"> <name>{/rpl-name}</name> <value>{$VAL}</value> </route-policy> </config> </device> </devices> </config-template>
and this will be the Python code to apply the template with the appropriate route-policy content:
class ServiceCallbacks(Service): # The create() callback is invoked inside NCS FASTMAP and # must always exist. @Service.create def cb_create(self, tctx, root, service, proplist): self.log.info('Service create(service=', service._path, ')') # starting with empty value val = '' # looping the list of destinations for dest in service.destination: # adding if-statement per each destination val += (' if destination in ' + dest.prefix_set + '\n set community ' + dest.community + '\n endif\n') val += ' pass\n' vars = ncs.template.Variables() vars.add('VAL', val) template = ncs.template.Template(service) template.apply('foo-template', vars)
Usage example:
admin@ncs(config)# show configuration foo bar device xr-00 rpl-name bar destination foo community bar ! destination foo2 community bar2 ! destination foo3 community bar3 ! ! admin@ncs(config)# commit dry-run outformat native native { device { name xr-00 data route-policy bar " if destination in foo\n set community bar\n endif\n if destination in foo2\n set community bar2\n endif\n if destination in foo3\n set community bar3\n endif\n pass\n" end-policy ! } }
04-26-2020 12:48 AM
You're right. The entire content of the route-policy is modeled as a single string.
To handle that in a service, you need a single service instance to be the "owner" of the entire route-policy.
i.e. one service instance needs to be configure the entire route-policy. It cannot own just one line, while other lines are manually configured or maintained by another service instance (there are ways to allow multiple service instances to contribute content to the same route-policy, but not directly).
Anyhow, the way to handle this in a service is somewhat dependent on the complexity of the route-policy you want to create. In many cases, you'd have to move most of the logic to Python/Java. In that case, your code will constract the content of the 'value' leaf, and pass it to the template which will just apply it.
It is possible to handle route-policy logic in XML to some extent, but I still think it will be much better to have it in Python.
Something like this:
if this is the service model:
list foo { key name; leaf name { type string; } uses ncs:service-data; ncs:servicepoint foo-servicepoint; leaf device { type leafref { path "/ncs:devices/ncs:device/ncs:name"; } } leaf rpl-name { type string; } list destination { key prefix-set; leaf prefix-set { type string; } leaf community { type string; } } }
This will be the XML template:
<config-template xmlns="http://tail-f.com/ns/config/1.0"> <devices xmlns="http://tail-f.com/ns/ncs"> <device> <name>{/device}</name> <config> <route-policy xmlns="http://tail-f.com/ned/cisco-ios-xr"> <name>{/rpl-name}</name> <value>{$VAL}</value> </route-policy> </config> </device> </devices> </config-template>
and this will be the Python code to apply the template with the appropriate route-policy content:
class ServiceCallbacks(Service): # The create() callback is invoked inside NCS FASTMAP and # must always exist. @Service.create def cb_create(self, tctx, root, service, proplist): self.log.info('Service create(service=', service._path, ')') # starting with empty value val = '' # looping the list of destinations for dest in service.destination: # adding if-statement per each destination val += (' if destination in ' + dest.prefix_set + '\n set community ' + dest.community + '\n endif\n') val += ' pass\n' vars = ncs.template.Variables() vars.add('VAL', val) template = ncs.template.Template(service) template.apply('foo-template', vars)
Usage example:
admin@ncs(config)# show configuration foo bar device xr-00 rpl-name bar destination foo community bar ! destination foo2 community bar2 ! destination foo3 community bar3 ! ! admin@ncs(config)# commit dry-run outformat native native { device { name xr-00 data route-policy bar " if destination in foo\n set community bar\n endif\n if destination in foo2\n set community bar2\n endif\n if destination in foo3\n set community bar3\n endif\n pass\n" end-policy ! } }
04-27-2020 01:43 AM
Thank You very much for Your answer. I was counting on some statement like "You should do it in Python/Java" and instead I've got complete example here. This is awesome, thank You once again.
I understood that because of route-policy variety of configurations it is impossible to model the content in some reasonable way that's why current approach is the best possible how to handle this.
I'll have to change my approach to this topic too. I was keen to create policy service which allows end user to create route-policy as he/she needs. Instead I've to prepare few policy templates to address most of the needs required by end users and simply program all of them. Each of this template will be a single service (RFS service). Then I'll create CFS service "route-policies" for example and based on the selected policy type within CFS service (I'll model such object), proper RFS service will be instantiated. With this approach within CFS service I'll be also able to "attach" other RFS services like "communities", "prefix-sets", "access-lists", etc. This way I'll have a single CFS service responsible for all of the above pieces of configuration in one place. Such a service created per customer should significantly simplified deployment or modification of single prefix-set or ACL on every single device where it is configured on. It'll be like a "helper" service for L3VPN service for example.
04-27-2020 02:15 AM
Stacked services approach is definitely the way to go with route policies.
There are multiple ways to go about it though.
There's a sense in simplifying the RFS service so each type will handle only specific type of policy.
Another aspect to have in mind is this ownership issue.
To allow a single owner for each route policy, but still allow multiple services to contribute to the route policy, you can follow more of a "gather" kind of stacked services design rather than "scatter".
In your suggestion, a single CFS will select a specializing RFS which knows how to create specific route policies.
What may come handy is to have a single RFS which will own a route-policy on a device.
This service will have as inputs: device-name, route-policy-name, list of values.
Each of the values is a route-policy block that a CFS might require.
The RFS mapping logic will concatenate all entries on the list of values into a single string, and will apply it to the device.
This way, multiple CFS instances can parent a single instance of this RFS, contributing blocks, while the route-policy only has one owner (the RFS instance).
Might be that you can even combine the two design approaches in some way:
CFS creates instances of different types of route-policy service for the same device and route-policy.
Those specializing route-policy service instances can write to the same route-policy RFS, adding different blocks to it (on the list of values), and the RFS service will combine the blocks and apply a single value to the device (might require service keys on the mediation layer to be less trivial).
Many ways to go about it. Just have in mind that the route-policy device configs need to have a single parent.
The rest is up to you.
Discover and save your favorite ideas. Come back to expert answers, step-by-step guides, recent topics, and more.
New here? Get started with these tips. How to use Community New member guide