Advanced NSO Template Service #2
Load blank.ssh.enabled.cfg
#IOS-XE
config replace flash:blank.ssh.enabled.cfg
#IOS-XR
configure
load bootflash:blank.ssh.enabled.cfg
commit replace
Create a template service
Create a template service that configures a basic EVPN-VPWS. The template should take the following variables:
Instance #
Service # (source and target number)
Name
PE-CE link (two)
PE device name
interface type
interface number
vlan ID
Handle both IOS-XE and IOS-XR PEs
If you are starting this lab without doing the previous one, you can add all devices to NSO with this config:
Answer
Create the vpws service-skeleton:
ncs-make-package --service-skeleton template vpws
Create a YANG module as follows:
module vpws {
namespace "http://com/example/vpws";
prefix vpws;
import ietf-inet-types {
prefix inet;
}
import tailf-ncs {
prefix ncs;
}
list vpws {
key name;
uses ncs:service-data;
ncs:servicepoint "vpws";
leaf name {
type string;
}
leaf instance_id {
type uint16;
}
leaf service_id {
type uint16;
}
list link {
key id;
leaf device {
type leafref {
path "/ncs:devices/ncs:device/ncs:name";
}
}
leaf id {
type string;
}
leaf vlan_id {
type uint16;
}
leaf interface_type {
type enumeration {
enum GigabitEthernet;
enum TenGigabitEthernet;
}
}
leaf interface_number {
type string;
}
}
}
}
Above, the VPWS service is identified by the leaf “name” (using the key statement). The VPWS service has an instance_id, service_id, and a list of links. Each link is identified by the link ID, and contains a device, vlan_id, interface_type, and interface_number.
Notice that we can restrict the options for an interface_type by using an enumeration. In the CLI, we are only allowed to enter a type that was pre-specified as an enum:

Next, we create a template. I created this by generating the config in NCS and doing a dry-run commit outputted to XML.
<config-template xmlns="http://tail-f.com/ns/config/1.0"
servicepoint="vpws">
<devices xmlns="http://tail-f.com/ns/ncs">
<?foreach {link}?>
<device>
<name>{device}</name>
<config>
<interface xmlns="urn:ios">
<? if {interface_type='GigabitEthernet'}?>
<GigabitEthernet>
<name>{interface_number}</name>
<service>
<instance>
<id>{vlan_id}</id>
<ethernet/>
<encapsulation>
<dot1q>
<id>{vlan_id}</id>
</dot1q>
</encapsulation>
<rewrite>
<ingress>
<tag>
<pop>1</pop>
<mode>symmetric</mode>
</tag>
</ingress>
</rewrite>
</instance>
</service>
</GigabitEthernet>
<?end?>
</interface>
<interface xmlns="http://tail-f.com/ned/cisco-ios-xr">
<? if {interface_type='GigabitEthernet'}?>
<GigabitEthernet-subinterface>
<GigabitEthernet>
<id>{interface_number}.{vlan_id}</id>
<mode>l2transport</mode>
<encapsulation>
<dot1q>
<vlan-id>{vlan_id}</vlan-id>
</dot1q>
</encapsulation>
<rewrite>
<ingress>
<tag>
<pop>1</pop>
<mode>symmetric</mode>
</tag>
</ingress>
</rewrite>
</GigabitEthernet>
</GigabitEthernet-subinterface>
<?end?>
</interface>
<l2vpn-evpn xmlns="urn:ios">
<l2vpn>
<evpn>
<instance>
<id>{string(/instance_id)}</id>
<point-to-point/>
<vpws>
<context>
<name>{string(/name)}</name>
<service>
<target>{string(/service_id)}</target>
<source>{string(/service_id)}</source>
</service>
<member>
<interface>{interface_type}{interface_number}</interface>
<service-instance>{vlan_id}</service-instance>
</member>
</context>
</vpws>
</instance>
</evpn>
</l2vpn>
</l2vpn-evpn>
<l2vpn xmlns="http://tail-f.com/ned/cisco-ios-xr">
<xconnect>
<group>
<name>VPWS</name>
<p2p>
<name>{string(/name)}</name>
<interface>
<name>{interface_type}{interface_number}.{vlan_id}</name>
</interface>
<neighbor-evpn>
<neighbor>
<evpn>
<evi>{string(/instance_id)}</evi>
<target>{string(/service_id)}</target>
<source>{string(/service_id)}</source>
</evpn>
</neighbor>
</neighbor-evpn>
</p2p>
</group>
</xconnect>
</l2vpn>
</config>
</device>
<?end?>
</devices>
</config-template>
Explanation
To accomplish this service, we must handle several things:
Multiple links
IOS-XE and IOS-XR config
Multiple interface types (i.e. GigabitEthernet and TenGigabitEthernet)
To handle multiple links, we simply iterate over every link in the list called “link.” This is done using NSO XML template processing instructions:
<config-template xmlns="http://tail-f.com/ns/config/1.0"
servicepoint="vpws">
<devices xmlns="http://tail-f.com/ns/ncs">
<?foreach {link}?>
<....>
<?end?>
</devices>
</config-template>
To iterate over each item in a list, you use <?foreach {Xpath}?>.
We can also use processing instructions such as if/else statements. This can be used to handle multiple interface types. The issue is that we can’t use an XPath for the XML tag. For example, we cannot do this:
<config>
<interface xmlns="urn:ios">
<{interface_type}>
<name>{interface_number}</name>
Instead, we can do an if statement. In this template, I do not add TenGig support for brevity. But it can easily be added using another corresponding if statement.
<config>
<interface xmlns="urn:ios">
<? if {interface_type='GigabitEthernet'}?>
<GigabitEthernet>
<name>{interface_number}</name>
In general, XPath statements are always placed in curly braces {}. There is an important thing to realize about XPath statements though. When these evaluate to a node, the XPath context for the template will change to that node. This can break your template in unexpected ways.
Notice that the l2vpn config uses {string(path)} syntax instead of just {path}. This is used because when the XPath statement resolves to a value, the XPath context does not change. If we don’t use this, the XPath context will change from root/link to just root/ when we get the /instance_id value. Then when we go to reference the interface_type later, we cannot find it under the root. We have lost our root/link context.
<l2vpn-evpn xmlns="urn:ios">
<l2vpn>
<evpn>
<instance>
<id>{string(/instance_id)}</id>
<point-to-point/>
<vpws>
<context>
<name>{string(/name)}</name>
<service>
<target>{string(/service_id)}</target>
<source>{string(/service_id)}</source>
</service>
<member>
<interface>{interface_type}{interface_number}</interface>
<service-instance>{vlan_id}</service-instance>
</member>
</context>
</vpws>
</instance>
</evpn>
</l2vpn>
Luckily we can find these issues out by debugging during a commit.
Demonstrating Debugging
Let’s change these XPath statements back to the absolute nodes:
<l2vpn-evpn xmlns="urn:ios">
<l2vpn>
<evpn>
<instance>
<id>{/instance_id}</id>
<point-to-point/>
<vpws>
<context>
<name>{/name}</name>
<service>
<target>{/service_id}</target>
<source>{/service_id}</source>
</service>
<member>
<interface>{interface_type}{interface_number}</interface>
<service-instance>{vlan_id}</service-instance>
</member>
</context>
</vpws>
</instance>
</evpn>
</l2vpn>
</l2vpn-evpn>
We make the package again and reload packages in NCS.
I then apply a new VPWS service:
vpws ONE
instance_id 100
service_id 101
link 1
device r1
vlan_id 100
interface_type GigabitEthernet
interface_number 1
However, it doesn’t look like it is working as expected. Where’s my interface as a member of the VPWS?

If I pipe the commit dry-run to debug template, I can see this context change happening, which is breaking my template.
First we start in the root context node, which is /vpws[name=’ONE’]. Then we iterate over the foreach, which puts our contex as the first link:

The context stays as-is, until we process a root node. We process /instance_id, which successfully finds the value. But notice that the next evaluation is now in the root context node. We’ve left our link context.

Then when we go to process the interface_type, it cannot be found at the root context:

As a side note, using this debug we can see how the template works for both XE and XR. NCS only generates config for the namespace that a device supports. IOS-XE does not support the ios-xr namespace, so NCS knows not to generate that config:

To fix the XPath context issue, we have a few options:
Use string() to evaluate a value instead of a node. This prevents a context switch.
Saving and switching context.
Using a string() is probably the easiest method. But let’s demonstrate the use switching contexts. First we save the context under the current link:
<config-template xmlns="http://tail-f.com/ns/config/1.0"
servicepoint="vpws">
<devices xmlns="http://tail-f.com/ns/ncs">
<?foreach {link}?>
<?save-context current-link-context?>
<device>
Then we set the context back to this when we move from the root-level leafs to the link-level leafs again:
<l2vpn-evpn xmlns="urn:ios">
<l2vpn>
<evpn>
<instance>
<id>{/instance_id}</id>
<point-to-point/>
<vpws>
<context>
<name>{/name}</name>
<service>
<target>{/service_id}</target>
<source>{/service_id}</source>
</service>
<member>
<---- Here ----> <?switch-context current-link-context?>
<interface>{interface_type}{interface_number}</interface>
<service-instance>{vlan_id}</service-instance>
</member>
In the debug, we can see that this time the context switches back to the link context before we evaluate the link-specific leafs.

Improving the service
I’ve re-written this and made some improvements. First, the YANG module requires that the VPWS has two PEs. This breaks for a VPWS that has two interfaces on the same PE though. However, in that case, we don’t use an EVPN EVI anyways. So this service is only used for a VPWS between two separate PEs.
There is also a restriction on the VLAN ID (0-4094) and interface number (0/0/0/X).
module vpws-new {
namespace "http://com/example/vpwsnew";
prefix vpws-new;
import ietf-inet-types {
prefix inet;
}
import tailf-ncs {
prefix ncs;
}
list vpws-new {
must "count(pe-device) = 2" {
error-message "Must have two PEs for the service";
}
key name;
uses ncs:service-data;
ncs:servicepoint "vpws-new";
leaf name {
type string;
}
list pe-device {
key device-name;
leaf device-name {
type leafref {
path "/ncs:devices/ncs:device/ncs:name";
}
}
container access-circuit {
leaf interface-type {
type enumeration {
enum GigabitEthernet;
}
}
leaf interface-num {
type string {
pattern "0/0/0/[0-9]*";
}
}
leaf vlan {
type uint16 {
range "0..4094";
}
}
}
}
leaf evi-num {
type uint16;
}
}
}
The XML template sets variables at the beginning when we are still at the root node context, to prevent the need to worry about switching contexts.
<config-template xmlns="http://tail-f.com/ns/config/1.0"
servicepoint="vpws-new">
<?set EVPN_NAME = {name} ?>
<?set EVPN_EVI = {evi-num} ?>
<devices xmlns="http://tail-f.com/ns/ncs">
<?foreach {pe-device} ?>
<device>
<name>{device-name}</name>
<config>
<interface xmlns="http://tail-f.com/ned/cisco-ios-xr">
<GigabitEthernet-subinterface>
<GigabitEthernet>
<id>{access-circuit/interface-num}.{access-circuit/vlan}</id>
<mode>l2transport</mode>
<encapsulation>
<dot1q>
<vlan-id>{vlan}</vlan-id>
</dot1q>
</encapsulation>
<rewrite>
<ingress>
<tag>
<pop>1</pop>
<mode>symmetric</mode>
</tag>
</ingress>
</rewrite>
</GigabitEthernet>
</GigabitEthernet-subinterface>
</interface>
<l2vpn xmlns="http://tail-f.com/ned/cisco-ios-xr">
<xconnect>
<group>
<name>VPWS</name>
<p2p>
<name>{$EVPN_NAME}</name>
<interface>
<name>GigabitEthernet{access-circuit/interface-num}.{access-circuit/vlan}</name>
</interface>
<neighbor-evpn>
<neighbor>
<evpn>
<evi>{$EVPN_EVI}</evi>
<target>{$EVPN_EVI}</target>
<source>{$EVPN_EVI}</source>
</evpn>
</neighbor>
</neighbor-evpn>
</p2p>
</group>
</xconnect>
</l2vpn>
</config>
</device>
<?end?>
</devices>
</config-template>
Last updated