This is the second part of the story porting an existing service to use a NETCONF NED. In the previous post, we prepared the environment and made sure we have both a CLI NED and a NETCONF NED ready to go for our IOS-XE device. You don't have to go back to read the previous post if you don't want to, but if you'd like to, it's here Porting an existing service to use a NETCONF NED, part 1
As noted in the root post Porting an existing service to use a NETCONF NED, the next step would be:
2) Extend service with (additional) support for the NETCONF variant of the device
- Switch NSO to talk to the device over CLI
- Configure a service instance, commit
- Switch NSO to talk to the device over NETCONF
- View the configuration changes in XML
- Take this XML as input to new sections in the service templates
- Fill in variables as given by the CLI version of the template
Ok, so now that I know I have a working NETCONF NED for the device, I actually want to switch back to the CLI NED for a while. After all, that's all my service code can relate to at this point. It is mapping the service intent to the CLI NED model right now. I'll be switching ce0 back to NETCONF later.
JLINDBLA-M-W0J2# con
Entering configuration mode terminal
JLINDBLA-M-W0J2(config)# devices device ce0
JLINDBLA-M-W0J2(config-device-ce0)# device-type cli ned-id cisco-ios
JLINDBLA-M-W0J2(config-device-ce0)# no port
JLINDBLA-M-W0J2(config-device-ce0)# comm
Commit complete.
It will be useful to also have a NETCONF interface view of the ce0 at all times, so I will play the little party trick to add the same ce0 device once more into the NSO device list, but this time as a NETCONF device. I'll call this view of the device ce0-nc. The XE device will hence be listed _twice_ in NSO. This is absolutely not normal and would lead to out of sync situations in normal operations, but today that's exactly what we're after. You'll see in a minute.
JLINDBLA-M-W0J2(config)# devices device ce0-nc address 192.168.50.50 port 830 authgroup vagrant device-type netconf
JLINDBLA-M-W0J2(config-device-ce0-nc)# state admin-state unlocked
JLINDBLA-M-W0J2(config-device-ce0-nc)# comm
Commit complete.
JLINDBLA-M-W0J2(config-device-ce0-nc)# ssh fetch-host-keys
result updated
fingerprint {
algorithm ssh-rsa
value b0:d4:d4:f8:01:b6:c9:07:6f:ad:a1:82:bc:b3:d0:3d
}
JLINDBLA-M-W0J2(config-device-ce0-nc)# top
And then I'd like to ensure we have an up to date config from all devices.
JLINDBLA-M-W0J2(config)# devices sync-f
sync-result {
device ce0
result true
}
sync-result {
device ce0-nc
result true
}
sync-result {
device ce1
result true
}
Now, I'll configure a service instance and push to all the devices. I made a file that I can load (over and over), but you could of course type this in manually just as well.
JLINDBLA-M-W0J2(config)# load merge vpn_skoda.xml
Loading.
923 bytes parsed in 0.00 sec (185.00 KiB/sec)
JLINDBLA-M-W0J2(config)# show c
vpn l3vpn skoda
as-number 61000
endpoint newyork-office
ce-device ce3
ce-interface GigabitEthernet0/2
ip-network 10.1.0.0/24
bandwidth 2000000
!
endpoint riodejaneiro-office
ce-device ce6
ce-interface GigabitEthernet0/2
ip-network 10.0.0.0/24
bandwidth 10000000
!
endpoint tokyo-office
ce-device ce0
ce-interface GigabitEthernet2
ip-network 10.0.0.0/24
bandwidth 10000000
!
!
The important piece here is that we configured some basic service that _involves the ce0 device_. See how the tokyo-office is using ce0. Commit dry-run to see what's about to happen. I just pasted the part that relates to ce0 here, there's much more.
JLINDBLA-M-W0J2(config)# commit dry-run
cli {
local-node {
data devices {
device ce0 {
config {
ios:policy-map skoda {
+ # first
+ class class-default {
+ shape {
+ average {
+ bit-rate 10000000;
+ }
+ }
+ }
}
ios:interface {
GigabitEthernet 2 {
ip {
no-address {
- address false;
}
address {
primary {
+ address 10.0.0.1;
+ mask 255.255.255.0;
}
}
}
}
GigabitEthernet 3.88 {
}
}
ios:router {
bgp 61000 {
+ network 10.0.0.0;
neighbor 192.168.1.2 {
+ activate;
}
}
}
}
}
device ce3 {
config {
+ ios:policy-map skoda {
+ class class-default {
Let's commit this. The commit updates the NSO view of the ce0 device, it also updates the actual device. It does not, however, update NSO's view of the ce0-nc device, which is now out of sync. If we ask the ce0-nc device for the configuration (get-config) over NETCONF, we will get a translation of the service we just sent over CLI into NETCONF. The only problem is that there will be loads of other configuration on the device that has nothing to do with our service.
This is where the ce0-nc device comes in. Before we added the service to the device, we did a sync-from towards all devices, which included ce0-nc. So if we compare the configuration of the actual ce0-nc device over NETCONF with the saved state in NSO of ce0-nc, we can get a pretty good diff. We can get this diff in a variety of formats, but for my purposes, I will want it in XML.
JLINDBLA-M-W0J2(config)# commit
Commit complete.
JLINDBLA-M-W0J2(config)# devices device ce0-nc compare-config outformat xml
diff
<devices xmlns="http://tail-f.com/ns/ncs">
<device>
<name>ce0-nc</name>
<config>
<native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
<policy>
<policy-map xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-policy">
<name>skoda</name>
<class>
<name>class-default</name>
<action-list>
<action-type>shape</action-type>
<shape>
<average>
<bit-rate>10000000</bit-rate>
</average>
</shape>
</action-list>
</class>
</policy-map>
</policy>
<interface>
<GigabitEthernet>
<name>2</name>
<ip>
<address>
<primary>
<address>10.0.0.1</address>
<mask>255.255.255.0</mask>
</primary>
</address>
</ip>
</GigabitEthernet>
</interface>
<router>
<bgp xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-bgp">
<id>61000</id>
<neighbor>
<id>192.168.1.2</id>
<activate/>
</neighbor>
<network>
<number>10.0.0.0</number>
</network>
</bgp>
</router>
</native>
</config>
</device>
</devices>
This XML is essentially what the service needs to push to the device over NETCONF. Since the service is based on an XML template towards the CLI NED, all I need to do is add this XML to that template, and parametrize it using the same variables that the CLI template is using. Let's do it.
If we look in the L3VPN template directory (packages/l3vpn/templates/) you'll see a number of template files.
$ ls ~/nso/4.4.2/examples.ncs/service-provider/vvpn-xe/packages/l3vpn/templates
l3vpn-acl.xml l3vpn-pe.xml l3vpn-qos-pe-class.xml l3vpn-qos-pe.xml l3vpn-qos.xml
l3vpn-ce.xml l3vpn-qos-class.xml l3vpn-qos-pe-prio.xml l3vpn-qos-prio.xml l3vpn-vm-manager.xml
Only some of these are configure XE devices, though. To quickly figure out which ones would touch an XE device, we can grep for the XE CLI NED namespace ID. You can find this in the XE CLI NED YANG files, but I can tell you it's "urn:ios". So let's grep for that.
$ grep -rils urn:ios packages/l3vpn/templates
packages/l3vpn/templates/l3vpn-acl.xml
packages/l3vpn/templates/l3vpn-ce.xml
packages/l3vpn/templates/l3vpn-pe.xml
packages/l3vpn/templates/l3vpn-qos-class.xml
packages/l3vpn/templates/l3vpn-qos-prio.xml
packages/l3vpn/templates/l3vpn-qos.xml
Looking at those files, I quickly realize that the only one I need to care about for the simple service I defined (with no QoS settings etc) is the l3vpn-ce.xml . And the l3vpn-pe.xml would not be relevant for an XE device in the CE role.
Looking at the l3vpn-ce.xml, this is what it contains right now:
<config-template xmlns="http://tail-f.com/ns/config/1.0">
<devices xmlns="http://tail-f.com/ns/ncs">
<device tags="nocreate">
<name>{$CE}</name>
<config>
<interface xmlns="urn:ios" tags="merge">
<GigabitEthernet
when="{starts-with($CE_INT_NAME,'GigabitEthernet')}">
<name>{substring($CE_INT_NAME,16)}.{$VLAN_ID}</name>
<description>Link to PE / {$PE} - {$PE_INT_NAME}</description>
<encapsulation>
<dot1Q>
<vlan-id>{$VLAN_ID}</vlan-id>
</dot1Q>
</encapsulation>
<ip>
<address>
<primary>
<address>{$LINK_CE_ADR}</address>
<mask>{$LINK_MASK}</mask>
</primary>
</address>
</ip>
<service-policy>
<output>{/name}</output>
</service-policy>
</GigabitEthernet>
<GigabitEthernet
when="{starts-with($CE_LOCAL_INT_NAME,'GigabitEthernet')}">
<name>{substring($CE_LOCAL_INT_NAME,16)}</name>
<description>{/name} local network</description>
<ip>
<address>
<primary>
<address>{$LOCAL_CE_ADR}</address>
<mask>{$CE_MASK}</mask>
</primary>
</address>
</ip>
</GigabitEthernet>
</interface>
<policy-map xmlns="urn:ios" tags="merge">
<name>{/name}</name>
<class>
<name>class-default</name>
<shape>
<average>
<bit-rate>{$BW}</bit-rate>
</average>
</shape>
</class>
</policy-map>
<router xmlns="urn:ios" tags="merge">
<bgp>
<as-no>{/as-number}</as-no>
<neighbor>
<id>{$LINK_PE_ADR}</id>
<remote-as>100</remote-as>
<activate/>
</neighbor>
<network>
<number>{$LOCAL_CE_NET}</number>
</network>
</bgp>
</router>
</config>
</device>
</devices>
</config-template>
That's somewhat similar to the XML diff we saw earlier. The { ... } are XPATH expressions evaluated by the NSO template applier, and the $VAR expressions are variables set by the L3VPN service when it applies the template.
The next step now is to simply paste the XML diff we got earlier into the template file inside the <config></config> tags, but not outside the previous content in between there. So in effect, insert the diff right after the top <config> tag. We don't want every line of the diff, only the part that's inside the diff's <config>...</config> tags. Also, remember to add tags="merge" on each top level node.
The resulting template becomes something like this:
<config-template xmlns="http://tail-f.com/ns/config/1.0">
<devices xmlns="http://tail-f.com/ns/ncs">
<device tags="nocreate">
<name>{$CE}</name>
<config>
<native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native" tags="merge">
<policy>
<policy-map xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-policy">
<name>skoda</name>
<class>
...
</native>
<interface xmlns="urn:ios" tags="merge">
<GigabitEthernet
when="{starts-with($CE_INT_NAME,'GigabitEthernet')}">
<name>{substring($CE_INT_NAME,16)}.{$VLAN_ID}</name>
...
The pasted template contains a lot of hard coded names, e.g. "skoda", that we need to replace by one of the variables computed by the service. We can look at the CLI template part for inspiration.
Looking at the pasted template, I find <name>skoda</name>. What could that be? It sits on policy/policy-map. Looking at the CLI side of the model, under policy-map I find <name>{/name}</name>. I.e. the template is picking up the service name and inserts here. Since I named my service instance "skoda", this makes a lot of sense. So let's change the pasted template line to the same: <name>{/name}</name>
Next in the pasted template, I find <name>class-default</name>. Looking for something similar in the CLI model, I find <name>class-default</name>. A hard-coded constant. Fine, I'll leave it alone then.
Next, <action-type>shape</action-type>. I don't find any value that corresponds to this in the CLI model, but I do find a tag called <shape>, so this is presumably just a small modeling difference. I'll leave it alone like this.
<bit-rate>10000000</bit-rate> seems to correspond with <bit-rate>{$BW}</bit-rate>, so I'll replace the number by {$BW}.
Going through the entire pasted XML diff like this, the template I come up with looks like this (plus the unmodified CLI variant, which I'm not showing).
<config-template xmlns="http://tail-f.com/ns/config/1.0">
<devices xmlns="http://tail-f.com/ns/ncs">
<device tags="nocreate">
<name>{$CE}</name>
<config>
<!-- XE over NETCONF -->
<native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native" tags="merge">
<policy>
<policy-map xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-policy">
<name>{/name}</name>
<class>
<name>class-default</name>
<action-list>
<action-type>shape</action-type>
<shape>
<average>
<bit-rate>{$BW}</bit-rate>
</average>
</shape>
</action-list>
</class>
</policy-map>
</policy>
<interface>
<GigabitEthernet when="{starts-with($CE_INT_NAME,'GigabitEthernet')}">
<name>{substring($CE_LOCAL_INT_NAME,16)}</name>
<description>{/name} local network</description>
<ip>
<address>
<primary>
<address>{$LOCAL_CE_ADR}</address>
<mask>{$CE_MASK}</mask>
</primary>
</address>
</ip>
</GigabitEthernet>
</interface>
<interface>
<GigabitEthernet
when="{starts-with($CE_INT_NAME,'GigabitEthernet')}">
<name>{substring($CE_INT_NAME,16)}.{$VLAN_ID}</name>
<description>Link to PE / {$PE} - {$PE_INT_NAME}</description>
<encapsulation>
<dot1Q>
<vlan-id>{$VLAN_ID}</vlan-id>
</dot1Q>
</encapsulation>
<ip>
<address>
<primary>
<address>{$LINK_CE_ADR}</address>
<mask>{$LINK_MASK}</mask>
</primary>
</address>
</ip>
<service-policy>
<output>{/name}</output>
</service-policy>
</GigabitEthernet>
</interface>
<router>
<bgp xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-bgp">
<id>{/as-number}</id>
<neighbor>
<id>{$LINK_PE_ADR}</id>
<activate/>
</neighbor>
<network>
<number>{$LOCAL_CE_NET}</number>
</network>
</bgp>
</router>
</native>
<!-- End of XE over NETCONF -->
<!-- XE over CLI -->
<interface xmlns="urn:ios" tags="merge">
<GigabitEthernet
when="{starts-with($CE_INT_NAME,'GigabitEthernet')}">
<name>{substring($CE_INT_NAME,16)}.{$VLAN_ID}</name>
If I typed everything in correctly, I should now be able to reload packages to get the changes in. If I messed up the template, the package loader will typically notice and tell me about it.
JLINDBLA-M-W0J2(config)# exit
JLINDBLA-M-W0J2# packages reload
reload-result {
package ce0
result true
}
...
reload-result {
package l3vpn
result false
info [l3vpn-ce.xml:57 Unknown element: 'service-policy']
}
Oh well. Seems I forgot the xmlns namespace on the service-policy element. Correcting that and reloading packages again works fine. I have the habit of checking that everything is fine with a show packages package oper-status
JLINDBLA-M-W0J2# show packages package oper-status
PACKAGE
PROGRAM META FILE
CODE JAVA BAD NCS PACKAGE PACKAGE CIRCULAR DATA LOAD ERROR
NAME UP ERROR UNINITIALIZED VERSION NAME VERSION DEPENDENCY ERROR ERROR INFO
---------------------------------------------------------------------------------------------------------------
ce0 X - - - - - - - - -
cisco-ios X - - - - - - - - -
cisco-iosxr X - - - - - - - - -
esc X - - - - - - - - -
id-allocator X - - - - - - - - -
ipaddress-allocator X - - - - - - - - -
juniper-junos X - - - - - - - - -
l3vpn X - - - - - - - - -
l3vpnui X - - - - - - - - -
pioneer X - - - - - - - - -
resource-manager X - - - - - - - - -
vm-manager X - - - - - - - - -
vm-manager-esc X - - - - - - - - -
weblog X - - - - - - - - -
Now it's time to switch the ce0 device back to NETCONF, so that we can verify that the service translation was ok.
JLINDBLA-M-W0J2# con
Entering configuration mode terminal
JLINDBLA-M-W0J2(config)# devices device ce0
Possible completions:
ce0 ce0-nc
JLINDBLA-M-W0J2(config)# devices device ce0 device-type netconf
JLINDBLA-M-W0J2(config-device-ce0)# no port
JLINDBLA-M-W0J2(config-device-ce0)# comm
Commit complete.
JLINDBLA-M-W0J2(config)# sync-from
result true
If we did the template work correctly, we should be able to re-deploy the service without anything being sent to the device.
JLINDBLA-M-W0J2(config-device-ce0)# top
JLINDBLA-M-W0J2(config)# vpn l3vpn skoda re-deploy dry-run
cli {
}
Let's also see if we can un-deploy and re-deploy for real.
JLINDBLA-M-W0J2(config)# vpn l3vpn skoda un-deploy
JLINDBLA-M-W0J2(config)# vpn l3vpn skoda re-deploy
JLINDBLA-M-W0J2(config)# devices device ce0 compare-config
JLINDBLA-M-W0J2(config)# vpn l3vpn skoda get-modifications
cli {
local-node {
data devices {
device ce0 {
config {
ios-native:native {
policy {
+ policy-map skoda {
+ class class-default {
+ action-list shape {
+ shape {
+ average {
+ bit-rate 10000000;
+ }
+ }
+ }
+ }
+ }
}
interface {
+ GigabitEthernet 2 {
+ description "skoda local network";
+ ip {
+ address {
+ primary {
+ address 10.0.0.1;
+ mask 255.255.255.0;
+ }
+ }
+ }
+ }
+ GigabitEthernet 3.88 {
+ description "Link to PE / pe0 - GigabitEthernet0/0/0/3";
+ encapsulation {
+ dot1Q {
+ vlan-id 88;
+ }
+ }
+ ip {
+ address {
+ primary {
+ address 192.168.1.1;
+ mask 255.255.255.252;
+ }
+ }
+ }
+ service-policy {
+ output skoda;
+ }
+ }
}
router {
+ bgp 61000 {
+ neighbor 192.168.1.2 {
+ activate;
+ }
+ network 10.0.0.0;
+ }
}
}
}
}
device ce3 {
config {
+ ios:policy-map skoda {
Cool!
That was the first step of porting the service. As noted earlier, there are a few more templates to look at. The next post will complete that work, and also show how to do some testing of the result.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Find answers to your questions by entering keywords or phrases in the Search bar above. New here? Use these resources to familiarize yourself with the NSO Developer community: