In this post we configure five brand new CSR routers, using the Cisco NSO PnP Server, with IP addresses for interfaces and EIGRP between them.
The NSO PnP Server is a NSO Component which enables management of Cisco PnP clients. The NSO PnP Server provides the Cisco PnP clients with configuration, typically configuration needed at the first boot of the client (Day-0 configuration).
The PnP Server enables NSO Services to communicate with Cisco PnP enabled devices. Each PnP Client will attempt to collect an initial configuration upon first boot, this is where the PnP Server comes in. The PnP Server delivers the initial configuration to the client and uses Reactive Fastmap to redeploy services.
For this post i created in EVE-NG Pro a simple topology which includes 5 CSR routers, one switch and a Docker node.
Docker node is used to run Cisco NSO and bind9 DNS server. If you don’t have EVE-NG Pro you can replace Docker with a Linux node, for example Ubuntu. Switch 1 is preconfigured as a DHCP server.
PnP Server Discovery
The typical PnP Client has a day -1 (day minus one) configuration with not much more than instructions to contact a PnP Server for the day-0 configuration. To be able to find the PnP Server the client uses PnP Discovery.
Typically DNS is used for PnP Discovery, therefore the DNS server needs to be configured with an entry for the NSO PnP Server. The exact configuration of the DNS server depends on the type of the DNS server. If no DNS server is available, you can use DHCP option 43 for Cisco Network Plug and Play.
NSO and PnP Package installation
I installed NSO as i described in my previous post, Connect Cisco NSO and CSRv in EVE-NG environment, so i will skip this part here.
NSO Cisco PnP Server 2.3.0 is delivered as a single binary (ncs-4.6.4-cisco-pnp-project-2.3.0.signed.bin) and can be used in a NSO project or in an existing runtime environment. I use my existing run-time environment which is located in /opt/nso-run/ and i saved the package binary in /opt/nso-run/packages.
root@DockerNSO:/opt/nso-run/packages# ls cisco-ios cisco-iosxr ncs-4.6.4-cisco-pnp-project-2.3.0.signed.bin
Then i run the binary with the option –skip-verification to skip package signature verification.
root@DockerNSO:/opt/nso-run/packages# sh ncs-4.6.4-cisco-pnp-project-2.3.0.signed.bin --skip-verification Unpacking... root@DockerNSO:/opt/nso-run/packages#
Let’s see what i got after the package was unpacked.
root@DockerNSO:/opt/nso-run/packages# ls -l total 3452 -rw-r--r-- 1 root root 1842 May 16 22:00 README.signature drwxr-xr-x 7 root root 4096 May 6 10:06 cisco-ios drwxr-xr-x 8 root root 4096 May 6 10:06 cisco-iosxr -rw-r--r-- 1 root root 10696 May 16 22:00 cisco_x509_verify_release.py -rw-r--r-- 1 root root 1752275 Jul 31 10:54 ncs-4.6.4-cisco-pnp-project-2.3.0.signed.bin -rw-r--r-- 1 root root 1742719 May 16 22:00 ncs-4.6.4-cisco-pnp-project-2.3.0.tar.gz -rw-r--r-- 1 root root 256 May 16 22:00 ncs-4.6.4-cisco-pnp-project-2.3.0.tar.gz.signature -rw-r--r-- 1 root root 1383 May 16 22:00 tailf.cer
Among other files i have a tarball called ncs-4.6.4-cisco-pnp-project-2.3.0.tar.gz that must be unpacked using tar.
root@DockerNSO:/opt/nso-run/packages# tar -zxvf ncs-4.6.4-cisco-pnp-project-2.3.0.tar.gz ncs-4.6.4-cisco-pnp-project-2.3.0/project-meta-data.xml ncs-4.6.4-cisco-pnp-project-2.3.0/packages/ncs-4.6.4-cisco-pnp-2.3.0.tar.gz ncs-4.6.4-cisco-pnp-project-2.3.0/init_data ncs-4.6.4-cisco-pnp-project-2.3.0/doc/cisco_pnp.pdf
As you can see above, all files has been unpacked in directory ncs-4.6.4-cisco-pnp-project-2.3.0. I will rename directory ncs-4.6.4-cisco-pnp-project-2.3.0 to make it a little shorter.
root@DockerNSO:/opt/nso-run/packages# mv ncs-4.6.4-cisco-pnp-project-2.3.0 cisco-pnp
Now the PnP package is ready to be loaded in NSO. Let’s start it and check the loaded packages.
root@DockerNSO:/opt/nso-run# ncs root@DockerNSO:/opt/nso-run# ncs_cli -C -u admin admin connected from 127.0.0.1 using console on DockerNSO admin@ncs# show packages package package-version PACKAGE NAME VERSION ---------------------- cisco-ios 184.108.40.206 cisco-iosxr 3.0.3 cisco-pnp 2.3.0
From the above output i can see that all packages that i need (cisco-ios and cisco-pnp) are loaded, now we can move on to configure DHCP on SW1.
DHCP Server configuration
To be able to reach PnP server, each client needs a IP address. In this lab SW1 is configured as DHCP server for subnet 10.0.1.0/24.
ip dhcp excluded-address 10.0.1.1 10.0.1.100 ! ip dhcp pool NSO network 10.0.1.0 255.255.255.0 domain-name pocvlab.local dns-server 10.0.1.1
As we discussed earlier, the preferred mode for PnP Discovery is DNS, which means client will try to connect to server pnpserver.pocvlab.local port 80 to get configuration. In my lab i configured Docker node to be DNS server because the software image that runs on the virtual switch don’t support local DNS feature.
If you don’t want to use a DNS server, then you have to tweak a little the DHCP configuration of the switch, and add the following line.
option 43 ascii 5A1N;B2;K4;I10.0.1.1;J80
If the string looks a little bit encrypted, don’t worry, it’s simple to understand when you see his components explained:
- 5A1N; Specifies the DHCP suboption for Plug and Play, active operation, version 1, no debug information.
- B2; is IP address type:
- B1 = hostname;
- B2 = IPv4 (default).
- K4; Transport protocol to be used between the Cisco Plug and Play IOS Agent and the server:
- K4 = HTTP (default);
- K5 = HTTPS.
- Ixxx.xxx.xxx.xxx; IP address or hostname of the PnP server controller (with prefix capital letter i). In out case, the IP address is 10.0.1.1.
- Jxxxx—Port number to use to connect to the PnP server. The default is port 80 for HTTP and port 443 for HTTPS.
Finding routers serial number
Because NSO PnP identifies each device by its serial number we have to get them from all lab routers. The serial number of a device can be seen on the sticker on the under side (or back of) a box. Because we run virtual routers, we have to start each of them, get the serial number from the output of the
show version command and after we power off them.
Router#show version | i ID Processor board ID 9AGQ9XSHEV7
Let’s go back to NSO CLI (
ncs_cli -C -u admin) to start defining PnP server properties. In NSO config mode enter the following lines.
pnp server ip-address 10.0.1.1 pnp server port 80 pnp server use-ssl false pnp trace enable pnp logging serial all
I defined PnP server address, port 80, disabled HTTPS protocol and in last two lines i enabled tracing and logging for all devices (with “any” serial).
Next i have to set the passwords (user pass and enable pass) for user admin (i will use the default authgroup) used by NSO to connect to devices.
devices authgroups group default umap admin remote-password cisco remote-secondary-password cisco
Note. You can enter configuration line by line, similar to cisco IOS, or in a single line, i prefer single line. For example:
devices authgroups group default umap admin remote-password cisco remote-secondary-password cisco
CSR-1 Day-0 Configuration
Now i can add CSR-1 to PnP configuration using below commnds, <device-serial-number> needs to be changed with the serial found with
show version in previous step.
pnp map CSR-1 serial <enter-serial-number> username admin password cisco sec-password cisco device-name CSR-1 device-type cli ned-id cisco-ios port 22 day0-file CSR-1-PnP.txt commit and-quit
Next step is to create IOS configuration file for CSR-1. The configuration files for devices should be placed in packages/cisco-pnp/cfg/ directory, in my case /opt/nso-run/packages/cisco-pnp/cfg/CSR-1-PnP.txt.
hostname CSR-1-PnP ! ip domain-name pocvlab.local ! username admin privilege 15 password 0 cisco enable password cisco ! interface Loopback1 ip address 220.127.116.11 255.255.255.255 ! interface GigabitEthernet1 description to Management Network ip address 10.0.1.11 255.255.255.0 no shutdown ! interface GigabitEthernet2 ip address 172.17.12.11 255.255.255.0 no shutdown ! interface GigabitEthernet3 ip address 172.17.14.11 255.255.255.0 no shutdown ! interface GigabitEthernet4 ip address 172.17.13.11 255.255.255.0 no shutdown ! router eigrp 100 network 18.104.22.168 0.0.0.0 network 172.17.0.0 0.0.255.255 ! crypto key generate rsa modulus 2048 ! ip ssh version 2 ! no ip http server no ip http secure-server ! line vty 0 4 login local transport input ssh
Note ! For username and enable i used password keyword, this is by purpose to have unencrypted passwords in lab demonstration. Please use secret keyword in real life scenarios.
After i saved the file, i go in NSO CLI end used
packages reload command to force NSO to re-read the configuration files. After that, i go in EVE-NG web interface and i start CSR-1. On the Docker node you can open a new terminal and use
tail -f /opt/nso-run/logs/ec_cisco_pnp.log to display the content of the PnP log file.
When the router is starting up, something similar with the output from bellow should be displayed on the CLI console.
Cisco IOS Software [Fuji], Virtual XE Software (X86_64_LINUX_IOSD-UNIVERSALK9-M), Version 16.9.1, RELEASE SOFTWARE (fc2) Technical Support: http://www.cisco.com/techsupport Copyright (c) 1986-2018 by Cisco Systems, Inc. Compiled Tue 17-Jul-18 16:57 by mcpre %LINEPROTO-5-UPDOWN: Line protocol on Interface GigabitEthernet1, changed state to up %CRYPTO-6-ISAKMP_ON_OFF: ISAKMP is OFF %CRYPTO-6-GDOI_ON_OFF: GDOI is OFF %PNP-6-HTTP_CONNECTING: PnP Discovery trying to connect to PnP server http://pnpserver.pocvlab.local:80/pnp/HELLO %PNP-6-HTTP_CONNECTED: PnP Discovery connected to PnP server http://pnpserver.pocvlab.local:80/pnp/HELLO %AN-6-AN_ABORTED_BY_MANUAL_CONFIG_DETECTED: Autonomic disabled due to detection of new configuration. %AN-6-AN_ABORTED_BY_CONSOLE_INPUT: Autonomic disabled due to User intervention on console. configure 'autonomic'. %SYS-6-BOOTTIME: Time taken to reboot after reload = 158 seconds %PNP-6-PROFILE_CONFIG: PnP Discovery profile pnp-zero-touch configured %PKI-2-NON_AUTHORITATIVE_CLOCK: PKI functions can not be initialized until an authoritative time source, like NT. %PNP-6-PNP_DISCOVERY_DONE: PnP Discovery done successfully AUTOINSTALL: Tftp script execution not successful for Gi1. %CRYPTO_ENGINE-5-KEY_ADDITION: A key named TP-self-signed-504877870 has been generated or imported %SSH-5-ENABLED: SSH 1.99 has been enabled %PKI-4-NOCONFIGAUTOSAVE: Configuration was modified. Issue "write memory" to save new IOS PKI configuration %CRYPTO_ENGINE-5-KEY_ADDITION: A key named TP-self-signed-504877870.server has been generated or imported %CRYPTO_ENGINE-5-KEY_ADDITION: A key named CSR-1-PnP.pocvlab.local has been generated or imported %SSH-4-SSH2_UNEXPECTED_MSG: Unexpected message type has arrived. Terminating the connection from 10.0.1.1 %SEC_LOGIN-5-LOGIN_SUCCESS: Login Success [user: admin] [Source: 10.0.1.1] [localport: 22]
Now let’s check the status of CSR-1 interfaces.
CSR-1-PnP#show ip interface brief Interface IP-Address OK? Method Status Protocol GigabitEthernet1 10.0.1.11 YES TFTP up up GigabitEthernet2 172.17.12.11 YES TFTP up up GigabitEthernet3 172.17.14.11 YES TFTP up up GigabitEthernet4 172.17.13.11 YES TFTP up up Loopback1 22.214.171.124 YES TFTP up up
CSR-2 Day-0 Configuration
The configuration file we used for CSR-1 is a template file and can contain variables. When sending configuration to the device any template variables can be replaced with their corresponding values. There are three types of template variables in NSO: device specific, shared and default.
For CSR-2 i will use device specific and shared variables, in a slightly changed router configuration template file. The base pnp map config for CSR-2 is identical with the previous one used for CSR-1, i have to change only the filename used as parameter for day0-file.
Shared template variables can be used by multiple devices and must be placed in a named list. These named lists are created in the pnp container and then referenced from the devices using cfg-common. I our case we have the same domain name used for all routers so we can use that as a shared template. Let’s see how will look our named list.
pnp day0-common VLAB variable RTR_DOMAIN_NAME value pocvlab.local
Device specific variables can be setup in the pnp configuration section of the device. These variables are available only for the device for which they have been configured. Following is the NSO pnp config for CSR-2 which includes device and shared variables.
pnp day0-common VLAB variable RTR_DOMAIN_NAME value pocvlab.local ! pnp map CSR-2 serial 96RBY7QG9TI device-name CSR-2 username admin password cisco sec-password cisco device-type cli ned-id cisco-ios port 22 day0-file CSR-any-PnP.txt cfg-common VLAB cfg-properties variable GI1_ADDRESS value 10.0.1.12 ! cfg-properties variable GI2_ADDRESS value 172.17.12.12 ! cfg-properties variable GI3_ADDRESS value 172.17.25.12 ! cfg-properties variable LOOP1_ADDRESS value 126.96.36.199 ! cfg-properties variable MASK value 255.255.255.0 ! cfg-properties variable RTR_HOSTNAME value CSR-2-PnP commit and-quit
A new IOS configuration template file which includes the new defined variables must be created in /opt/nso-run/packages/cisco-pnp/cfg/, this time called CSR-any-PnP.txt, with the follwing content.
hostname $RTR_HOSTNAME ! ip domain-name $RTR_DOMAIN_NAME ! username admin privilege 15 password 0 cisco enable password cisco ! interface Loopback1 ip address $LOOP1_ADDRESS 255.255.255.255 ! interface GigabitEthernet1 description to Management Network ip address $GI1_ADDRESS $MASK no shutdown ! interface GigabitEthernet2 ip address $GI2_ADDRESS $MASK no shutdown ! interface GigabitEthernet3 ip address $GI3_ADDRESS $MASK no shutdown ! interface GigabitEthernet4 ip address $GI4_ADDRESS $MASK no shutdown ! router eigrp 100 network $LOOP1_ADDRESS 0.0.0.0 network 172.17.0.0 0.0.255.255 ! crypto key generate rsa modulus 2048 ! ip ssh version 2 ! no ip http server no ip http secure-server ! line vty 0 4 login local transport input ssh
Save the file, reload packages in NSO (
packages reload)and then start CSR-2. In few minutes CSR-2 should be up, configured and ready to operate.
CSR-2-PnP# show ip interface brief Interface IP-Address OK? Method Status Protocol GigabitEthernet1 10.0.1.12 YES TFTP up up GigabitEthernet2 172.17.12.12 YES TFTP up up GigabitEthernet3 172.17.25.12 YES TFTP up up GigabitEthernet4 unassigned YES unset up up Loopback1 188.8.131.52 YES TFTP up up CSR-2-PnP# show ip route eigrp Gateway of last resort is not set 184.108.40.206/32 is subnetted, 1 subnets D 220.127.116.11 [90/130816] via 172.17.12.11, 00:10:48, GigabitEthernet2 172.17.0.0/16 is variably subnetted, 6 subnets, 2 masks D 172.17.13.0/24 [90/3072] via 172.17.12.11, 00:10:48, GigabitEthernet2 D 172.17.14.0/24 [90/3072] via 172.17.12.11, 00:10:48, GigabitEthernet2 CSR-2-PnP#ping 18.104.22.168 Type escape sequence to abort. Sending 5, 100-byte ICMP Echos to 22.214.171.124, timeout is 2 seconds: !!!!! Success rate is 100 percent (5/5), round-trip min/avg/max = 1/2/6 ms
If you go on the web interface of NSO, under Device Manager, you will see two devices listed. For each device click on the N/A message (under found, connected and in sysnc columns) to update the status of the devices on the web page.
CSR-3, 4 and 5 Day-0 Configuration
To provision NSO with the configuration needed for the other three routers left, i will use Bulk Upload (in my previous NSO post you can find all possible modes to add a device) because is useful for initial definition of many devices.
For Bulk Upload, i need to generate an initial pnp configuration file for all the devices that i will add in NSO.
To load the initial config file in NSO, i have at disposal ncs_load command which provides a convenient way of loading and saving configurations in different formats (XML, json, etc.). Running ncs_load in a Bash terminal, without any options, displays current NSO configuration in XML format (use –help option to see all supported parameters).
In our scenario we use -l (Load) option which uploads XML content to the CDB (Configuration Database, see more about this in NSO Core Concepts) and -m (Merge) which updates the existing device list. Be sure to use -m, otherwise the entire CDB will be replaced with the XML contents.
From NSO CLI i can get XML schema used for CSR-2 using
show running-config pnp map CSR-2 | display xml (Juniper guys know it) and based on that i can start building the XML file for CSR-3, 4 and 5.
admin@ncs# show running-config pnp map CSR-2 | display xml <config xmlns="http://tail-f.com/ns/config/1.0"> <pnp xmlns="http://tail-f.com/ned/cisco/pnp"> <map> <id>CSR-3</id> <serial>96RBY7QG9TI</serial> <device-name>CSR-3</device-name> <username>admin</username> <password>$8$beKVLTqyLSJmBWLu58tKerTd5XiR2nPBoGJtwK8Ush8=</password> <sec-password>$8$Xk1ZWGyxlKZYztMKlSHheV1ESP9aUWx5RHhhhvXoOgU=</sec-password> <device-type>cli</device-type> <ned-id>cisco-ios</ned-id> <port>22</port> <day0-file>CSR-any-PnP.txt</day0-file> <cfg-properties> <variable><name>GI1_ADDRESS</name><value>10.0.1.12</value></variable> <variable><name>GI2_ADDRESS</name><value>172.17.12.12</value></variable> <variable><name>GI3_ADDRESS</name><value>172.17.25.12</value></variable> <variable><name>LOOP1_ADDRESS</name><value>126.96.36.199</value></variable> <variable><name>MASK</name><value>255.255.255.0</value></variable> <variable><name>RTR_HOSTNAME</name><value>CSR-2-PnP</value></variable> </cfg-properties> <cfg-common>VLAB</cfg-common> </map> </pnp> </config>
The interesting part of output is located between <pnp></pnp> tags where we can see the values that must be replaced for other routers. This kind of file can be easy generated with a python script if we have a big deployment. In our case it will take only few minutes to fill by hand <id>, <serial>, <device-name>,<cfg-properties> for all three routers we have left.
At the bottom of the page you can find a link to the XML file i used.
I saved the XML file as CSR-3-4-5.xml in /opt/ncs-run/ directory and from the Bash prompt i loaded the file to NSO CDB using ncs_load.
root@DockerNSO:/opt/nso-run# ncs_load -m -l CSR-3-4-5.xml
Now i can check from NSO CLI what i have in pnp map section of configuration starting from CSR-3.
admin@ncs# show running-config pnp map | begin CSR-3 pnp map CSR-3 serial 9AGQ9XSHEV7 device-name CSR-3 username admin password $8$beKVLTqyLSJmBWLu58tKerTd5XiR2nPBoGJtwK8Ush8= sec-password $8$Xk1ZWGyxlKZYztMKlSHheV1ESP9aUWx5RHhhhvXoOgU= device-type cli ned-id cisco-ios port 22 day0-file CSR-any-PnP.txt cfg-properties variable GI1_ADDRESS value 10.0.1.13 ! cfg-properties variable GI2_ADDRESS value 172.17.13.13 ! cfg-properties variable GI3_ADDRESS value 172.17.34.13 ! cfg-properties variable LOOP1_ADDRESS value 188.8.131.52 ! cfg-properties variable MASK value 255.255.255.0 ! cfg-properties variable RTR_HOSTNAME value CSR-3-PnP ! cfg-common VLAB ! pnp map CSR-4 serial 92H5CBPXMGF device-name CSR-4 username admin password $8$beKVLTqyLSJmBWLu58tKerTd5XiR2nPBoGJtwK8Ush8= sec-password $8$Xk1ZWGyxlKZYztMKlSHheV1ESP9aUWx5RHhhhvXoOgU= device-type cli ned-id cisco-ios port 22 day0-file CSR-any-PnP.txt cfg-properties variable GI1_ADDRESS value 10.0.1.14 ! cfg-properties variable GI2_ADDRESS value 172.17.45.14 ! cfg-properties variable GI3_ADDRESS value 172.17.14.14 ! cfg-properties variable GI4_ADDRESS value 172.17.34.14 ! cfg-properties variable LOOP1_ADDRESS value 184.108.40.206 ! cfg-properties variable MASK value 255.255.255.0 ! cfg-properties variable RTR_HOSTNAME value CSR-4-PnP ! cfg-common VLAB ! pnp map CSR-5 serial 99XNJIWYN0Z device-name CSR-5 username admin password $8$beKVLTqyLSJmBWLu58tKerTd5XiR2nPBoGJtwK8Ush8= sec-password $8$Xk1ZWGyxlKZYztMKlSHheV1ESP9aUWx5RHhhhvXoOgU= device-type cli ned-id cisco-ios port 22 day0-file CSR-any-PnP.txt cfg-properties variable GI1_ADDRESS value 10.0.1.15 ! cfg-properties variable GI2_ADDRESS value 172.17.45.15 ! cfg-properties variable GI3_ADDRESS value 172.17.25.15 ! cfg-properties variable LOOP1_ADDRESS value 220.127.116.11 ! cfg-properties variable MASK value 255.255.255.0 ! cfg-properties variable RTR_HOSTNAME value CSR-5-PnP ! cfg-common VLAB !
If your output looks like output from above you can go ahead and start CSR 3, 4 and 5. Don’t forget to enable pnp trace (
pnp trace enable) from NSO CLI and
tail -f /opt/nso-run/logs/ec_cisco_pnp.log.
When all process in done check the output of
show pnp list in NSO CLI.
admin@ncs# show pnp list IP SERIAL ADDRESS CONFIGURED ADDED SYNCED LAST CONTACT ------------------------------------------------------------------------ 92H5CBPXMGF 10.0.1.14 true true true 2019-08-29 19:38:32 96RBY7QG9TI 10.0.1.12 true true true 2019-08-29 19:36:56 99XNJIWYN0Z 10.0.1.15 true true true 2019-08-29 19:38:33 9AGQ9XSHEV7 10.0.1.13 true true true 2019-08-29 19:37:13 9HYCWVAWR9B 10.0.1.11 true true true 2019-08-29 19:36:53
When all devices are up and pnp process has ended, we can check routing table of any router, to see what EIGRP routes we have installed in RIB.
CSR-5-PnP#show ip route eigrp Gateway of last resort is not set 18.104.22.168/32 is subnetted, 1 subnets D 22.214.171.124 [90/131072] via 172.17.45.14, 00:09:19, GigabitEthernet2 [90/131072] via 172.17.25.12, 00:09:19, GigabitEthernet3 126.96.36.199/32 is subnetted, 1 subnets D 188.8.131.52 [90/130816] via 172.17.25.12, 00:09:19, GigabitEthernet3 184.108.40.206/32 is subnetted, 1 subnets D 220.127.116.11 [90/131072] via 172.17.45.14, 00:09:19, GigabitEthernet2 18.104.22.168/32 is subnetted, 1 subnets D 22.214.171.124 [90/130816] via 172.17.45.14, 00:09:19, GigabitEthernet2 172.17.0.0/16 is variably subnetted, 8 subnets, 2 masks D 172.17.12.0/24 [90/3072] via 172.17.25.12, 00:09:19, GigabitEthernet3 D 172.17.13.0/24 [90/3328] via 172.17.45.14, 00:09:19, GigabitEthernet2 [90/3328] via 172.17.25.12, 00:09:19, GigabitEthernet3 D 172.17.14.0/24 [90/3072] via 172.17.45.14, 00:09:19, GigabitEthernet2 D 172.17.34.0/24 [90/3072] via 172.17.45.14, 00:09:19, GigabitEthernet2
As you can see, we have all prefixes we expected shown.
In the links below you can find a link (all routers xml) which contains all the configuration needed for NSO to full deploly all routers. It can be loaded in NSO using next steps:
- stop NSO (
- erase NSO config (
- start NSO (
- load xml in ncs (
ncs_load -m -l CSR-1-2-3-4-5.xml).
Probably, in a future post, we will look on how to make a Cisco SD-WAN deployment using NSO.