NSO – Part 3

Let’s continue playing around some more with NSO. In the last posts we’ve added our netsim-devices to NSO and managed to push out some simple config & tried rollbacks. Instead of specifying each single router everytime we want to manage them we can instead use device-groups for easier management.

Let’s start with creating two groups for our Core- & Edge-routers:

dmin@ncs# config 
Entering configuration mode terminal
admin@ncs(config)# devices device-group Core device-name [ r0 r1 ]


admin@ncs(config)# devices device-group Edge device-name [ r2 r
Possible completions:
  r0  r1  r3
admin@ncs(config)# devices device-group Edge device-name [ r2 r3 ]


admin@ncs(config)# show full-configuration devices device-group 
devices device-group Core
 device-name [ r0 r1 ]
!
devices device-group Edge
 device-name [ r2 r3 ]

admin@ncs(config)# do show devices device-group
NAME  MEMBER     INDETERMINATES  CRITICALS  MAJORS  MINORS  WARNINGS  
----------------------------------------------------------------------
Core  [ r0 r1 ]  0               0          0       0       0         
Edge  [ r2 r3 ]  0               0          0       0       0 

We can then use the group-name when we want to check if router are in sync f.ex.:

admin@ncs# devices device-group Core check-sync 
sync-result {
    device r0
    result in-sync
}
sync-result {
    device r1
    result in-sync
}

We can also have groups in a group:

devices device-group all
 device-group [ Core Edge ]
!

Let’s also take a look at creating a template that we can later call on to configure something generic on our device(s). By using “{$VARIABLENAME}” NSO gives us the ability to input our own variables when calling the specific template. Here’s an example of a simple VRF-template:

admin@ncs(config)# devices template NEW_VRF
admin@ncs(config-template-NEW_VRF)# config ios:vrf definition {$VRF}
admin@ncs(config-definition-{$VRF})# description {$CUSTOMER}
admin@ncs(config-definition-{$VRF})# rd 3301\:{$VRF}
admin@ncs(config-definition-{$VRF})# address-family ipv4 route-target import 3301\:{$VRF}
admin@ncs(config-import-3301:{$VRF})# exit
admin@ncs(config-definition-{$VRF})# address-family ipv4 route-target exoort 3301\:{$VRF}
admin@ncs(config-export-3301:{$VRF})# exit
admin@ncs(config-definition-{$VRF})# top
admin@ncs(config)# commit

admin@ncs(config)# show full-configuration devices template NEW_VRF 
devices template NEW_VRF
 config
  ios:vrf definition {$VRF}
   description {$CUSTOMER}
   rd          3301:{$VRF}
   address-family ipv4 route-target export 3301:{$VRF}
   !
   address-family ipv4 route-target import 3301:{$VRF}
   !
  !
 !

When we want to configure a new VRF we than only have to name our NEW_VRF-template and input the variables for CUSTOMER & VRF. NSO’s autocomplete/tab feature is excellent here and keeps track of available variables we can use.

admin@ncs(config)# devices device-group Edge apply-template template-name NEW_VRF variable { name CUSTOMER value 'GEKAS' } variable { name VRF value '22222' }
apply-template-result {
    device r2
    result ok
}
apply-template-result {
    device r3
    result ok
}
admin@ncs(config)# commit
Commit complete.
admin@ncs(config)# 

admin@ncs(config)# devices device-group all apply-template template-name NEW_VRF variable { name CUSTOMER value 'MGMT' } variable { name VRF value '999' } 
apply-template-result {
    device r0
    result ok
}
apply-template-result {
    device r1
    result ok
}
apply-template-result {
    device r2
    result ok
}
apply-template-result {
    device r3
    result ok
}

If we login and check our routers we can see that all routers now have the MGMT-vrf & Edge-routers (R2 & R3) also have a customer vrf GEKAS.

$ ncs-netsim cli-i r1

admin connected from 192.168.15.186 using ssh on labb-nso
r1> en
r1# show running-config vrf
vrf definition 999
 description MGMT
 rd          3301:999
 address-family ipv4
  route-target export 3301:999
  route-target import 3301:999
  exit-address-family
 !
!

$ ncs-netsim cli-i r2

admin connected from 192.168.15.186 using ssh on labb-nso
r2> en
r2# show running-config vrf
vrf definition 22222
 description GEKAS
 rd          3301:22222
 address-family ipv4
  route-target export 3301:22222
  route-target import 3301:22222
  exit-address-family
 !
!
vrf definition 999
 description MGMT
 rd          3301:999
 address-family ipv4
  route-target export 3301:999
  route-target import 3301:999
  exit-address-family
 !
!

Let’s see what happens when we do a manual change and R2 gets out of sync.

r2# conf
Enter configuration commands, one per line. End with CNTL/Z.
r2(config)# vrf definition 999
r2(config-vrf)# description CustomerA
r2(config-vrf)# exit

admin@ncs# devices device-group all check-sync 
sync-result {
    device r0
    result in-sync
}
sync-result {
    device r1
    result in-sync
}
sync-result {
    device r2
    result out-of-sync
    info got: 77727c766ca3b799f52c13a9cb76daa3 expected: 1d88fb85cf89d8f417701fac26e6df3d

}
sync-result {
    device r3
    result in-sync
}
admin@ncs# *** ALARM out-of-sync: got: 77727c766ca3b799f52c13a9cb76daa3 expected: 1d88fb85cf89d8f417701fac26e6df3d

That hash-value isn’t helping us very much, but we can easily check what the actual difference is with compare-config:

admin@ncs# devices device r2 compare-config 
diff 
 devices {
     device r2 {
         config {
             ios:vrf {
                 definition 999 {
-                    description MGMT;
+                    description CustomerA;
                 }
             }
         }
     }
 }


admin@ncs# devices device r2 sync-to       
result true

admin@ncs# devices device-group all check-sync
sync-result {
    device r0
    result in-sync
}
sync-result {
    device r1
    result in-sync
}
sync-result {
    device r2
    result in-sync
}
sync-result {
    device r3
    result in-sync
}

There’s also a pretty cool option to use policy-rules to f.ex warn the user before he/she tries to removes an important vrf. It was pretty tricky however how to figure out how it should be written so I had a lot of trial and error here, but ultimately this is what I ended up with:

admin@ncs(config)# policy rule MGMT foreach /devices/device expr config/ios:vrf/definition[name='999'] warning-message "Device {name} must have a Management-VRF"

A really good command to easier see what path we should check in our expr-string was by using the “| display xpath”:

admin@ncs(config)# show full-configuration devices device r2 config ios:vrf | display xpath
/devices/device[name='r2']/config/ios:vrf/definition[name='22222']/description GEKAS
/devices/device[name='r2']/config/ios:vrf/definition[name='22222']/rd 3301:22222
/devices/device[name='r2']/config/ios:vrf/definition[name='22222']/address-family/ipv4
/devices/device[name='r2']/config/ios:vrf/definition[name='22222']/address-family/ipv4/route-target/export[asn-ip='3301:22222']
/devices/device[name='r2']/config/ios:vrf/definition[name='22222']/address-family/ipv4/route-target/import[asn-ip='3301:22222']
/devices/device[name='r2']/config/ios:vrf/definition[name='999']/description MGMT
/devices/device[name='r2']/config/ios:vrf/definition[name='999']/rd 3301:999
/devices/device[name='r2']/config/ios:vrf/definition[name='999']/address-family/ipv4
/devices/device[name='r2']/config/ios:vrf/definition[name='999']/address-family/ipv4/route-target/export[asn-ip='3301:999']
/devices/device[name='r2']/config/ios:vrf/definition[name='999']/address-family/ipv4/route-target/import[asn-ip='3301:999']

When a user tries to remove the vrf 999 for management they should now get an error:

admin@ncs(config)# no devices device r2 config ios:vrf definition 999 
admin@ncs(config)# show configuration 
devices device r2
 config
  no ios:vrf definition 999
 !
!
admin@ncs(config)# commit
The following warnings were generated:
  Device r2 must have a Management-VRF
Proceed? [yes,no] no
Aborted: by user

I’ve only scratched the surface yet but i’m really starting to dig NSO. 🙂

NSO – Part 2

As we now have NSO up and running on our local VM let’s try and do some basic stuff like actually push out some configuration changes to our network. 🙂

Cisco are nice enough to provide us with a tool (netsim) to simulate devices that we can use NSO on. These are not to be compared with virtual routers using CSRv etc however.

They use practically 0 memory and is more or less just a config-template that we can “login” to and view/change the current config in a CiscoIOS or Juniper CLI-view. There’s no actual topology we can build though and we can’t send any traffic between these devices.

Anyway, let’s start with creating 4 routers running IOS with the names r* in a new directory somewhere suitable.

joco02 at labb-nso in ~/nso-labs/ios
$ source $HOME/ncs-4.7/ncsrc
$ ncs-netsim create-network $NCS_DIR/packages/neds/cisco-ios 4 r
DEVICE r0 CREATED
DEVICE r1 CREATED
DEVICE r2 CREATED
DEVICE r3 CREATED

We then “boot them up” by using ncs-netsim start.

joco02 at labb-nso in ~/nso-labs/ios
$ ncs-netsim start
DEVICE r0 OK STARTED
DEVICE r1 OK STARTED
DEVICE r2 OK STARTED
DEVICE r3 OK STARTED

If we later want to check if all devices are still up and running we can use the command:

joco02 at labb-nso in ~/nso-labs/ios
$ ncs-netsim is-alive
DEVICE r0 OK
DEVICE r1 OK
DEVICE r2 OK
DEVICE r3 OK

We can login to these “routers” and a rather nifty function in netsim is that we can also choose how the CLI should be displayed (juniper or cisco-style) by adding the -i flag. Cisco is also nice enough to provide a base config for us to play around with when using CiscoIOS netsim-devices.

For juniper-style CLI:

$  ncs-netsim cli r0

admin connected from 192.168.15.188 using ssh on labb-nso
admin@r0> show configuration router
bgp 64512 {
    aggregate-address {
        address 10.10.10.1;
        mask    255.255.255.251;
    }
    neighbor 1.2.3.4 {
        remote-as 1;
        ebgp-multihop {
            max-hop 3;
        }
    }
    neighbor 2.3.4.5 {
        remote-as 1;
        activate;
        capability {
            orf {
                prefix-list both;
            }
        }
        weight    300;
    }
}
[ok]

For Cisco-style CLI:

$ ncs-netsim cli-i r0

admin connected from 192.168.15.188 using ssh on labb-nso
r0> enable
r0# show running-config router
router bgp 64512
 aggregate-address 10.10.10.1 255.255.255.251
 neighbor 1.2.3.4 remote-as 1
 neighbor 1.2.3.4 ebgp-multihop 3
 neighbor 2.3.4.5 remote-as 1
 neighbor 2.3.4.5 activate
 neighbor 2.3.4.5 capability orf prefix-list both
 neighbor 2.3.4.5 weight 300
!

There’s also options to create IOS XR, NX, Juniper, Dell ftos & a10 acos devices – but for at least IOS XR it seems there’s no default config included to play with so in this example i’ll just use the IOS for now.

We now have our devices, so let’s set up NSO to use our new netsim-routers it can find in our current folder.

$ ncs-setup --netsim-dir ./netsim --dest .
Using netsim dir ./netsim

If we check our current directory we can see that we got a whole bunch of new files suddenly:

joco02 at labb-nso in ~/nso-labs/ios
$ la
total 52
drwxrwxr-x 8 joco02 joco02 4096 May  4 18:41 ./
drwxrwxr-x 3 joco02 joco02 4096 May  4 18:19 ../
drwxrwxr-x 2 joco02 joco02 4096 May  4 18:41 logs/
drwxrwxr-x 2 joco02 joco02 4096 May  4 18:41 ncs-cdb/
-rw-rw-r-- 1 joco02 joco02 9357 May  4 18:41 ncs.conf
drwxrwxr-x 3 joco02 joco02 4096 May  4 18:41 netsim/
drwxrwxr-x 2 joco02 joco02 4096 May  4 18:41 packages/
-rw-rw-r-- 1 joco02 joco02  608 May  4 18:41 README.ncs
-rw-rw-r-- 1 joco02 joco02 1128 May  4 18:41 README.netsim
drwxrwxr-x 4 joco02 joco02 4096 May  4 18:41 scripts/
drwxrwxr-x 2 joco02 joco02 4096 May  4 18:41 state/

And finally we’re ready to start NSO.

joco02 at labb-nso in ~/nso-labs/ios
$ ncs

joco02 at labb-nso in ~/nso-labs/ios
$ ncs --status | grep status
status: started

When loggin in to the NSO CLI we can chose between using Cisco IOSXR or Juniper style CLI:
$ ncs_cli -C -u admin <- XR
$ ncs_cli -J -u admin <- Juniper

I feel way more at home in the XR so that’s what ill be using.

joco02 at labb-nso in ~/nso-labs/ios
$ ncs_cli -C -u admin

admin connected from 192.168.15.188 using ssh on labb-nso
admin@ncs# 

At this point the only thing NSO knows about our devices are how to connect to them (address/port/authentication) and what type of device it is (Cisco IOS). We can check NSO’s current view of the configuration of each device with:

admin@ncs# show running-config devices device r0
devices device r0
 address   127.0.0.1
 port      10022
 ssh host-key ssh-rsa
  key-data "..long key.."
 !
 authgroup default
 device-type cli ned-id cisco-ios
 state admin-state unlocked
 config
  no ios:service pad
  no ios:ip domain-lookup
  no ios:ip http secure-server
  ios:ip source-route

A nice feature is that we can also use tab-completion to see which devices we have in our database:

admin@ncs# show running-config devices device 
Possible completions:
  r0  r1  r2  r3  |  <cr>

But before we go any further we have to make sure NSO is up to sync with the actual configuration of our devices, so let’s start with checking that it can actually connect to our devices:

admin@ncs# devices connect                              
connect-result {
    device r0
    result true
    info (admin) Connected to r0 - 127.0.0.1:10022
}
connect-result {
    device r1
    result true
    info (admin) Connected to r1 - 127.0.0.1:10023
}
connect-result {
    device r2
    result true
    info (admin) Connected to r2 - 127.0.0.1:10024
}
connect-result {
    device r3
    result true
    info (admin) Connected to r3 - 127.0.0.1:10025
}

And finally lets sync data from the devices, NSO will save this data to its configuration database (CDB) and respond with true if it was successful.

admin@ncs# devices sync-from
sync-result {
    device r0
    result true
}
sync-result {
    device r1
    result true
}
sync-result {
    device r2
    result true
}
sync-result {
    device r3
    result true
}

We can now check NSOs view of R0 config again.

admin@ncs# show running-config devices device r0
devices device r0
 address   127.0.0.1
 port      10022
 ssh host-key ssh-rsa
  key-data "..long key.."
 !
 authgroup default
 device-type cli ned-id cisco-ios
 state admin-state unlocked
 config
  no ios:service pad
  ios:ip vrf my-forward
   bgp next-hop Loopback 1
  !
  ios:ip community-list 1 permit
  ios:ip community-list 2 deny
  ios:ip community-list standard s permit
  no ios:ip domain-lookup
  no ios:ip http server
  no ios:ip http secure-server
  ios:ip routing
  ios:ip source-route
  ios:interface FastEthernet1/0
  exit
  ios:interface Loopback0
  exit
  ios:class-map match-all a
  !
  ios:class-map match-all cmap1
   match mpls experimental topmost 1
   match packet length max 255
   match packet length min 2
   match qos-group 1
  !
  ios:policy-map a
  !
  ios:policy-map map1
   class c1
    drop
    estimate bandwidth delay-one-in 500 milliseconds 100
    priority percent 33
   !
  !
  no ios:spanning-tree optimize bpdu transmission
  ios:mpls ip propagate-ttl
  ios:router bgp 64512
   aggregate-address 10.10.10.1 255.255.255.251
   neighbor 1.2.3.4 remote-as 1
   neighbor 1.2.3.4 ebgp-multihop 3
   neighbor 2.3.4.5 remote-as 1
   neighbor 2.3.4.5 activate
   neighbor 2.3.4.5 capability orf prefix-list both
   neighbor 2.3.4.5 weight 300
  !
 !
!

It’s also possible to filter the output and just check the routing specific config etc:

admin@ncs# show running-config devices device r0 config ios:router
devices device r0
 config
  ios:router bgp 64512
   aggregate-address 10.10.10.1 255.255.255.251
   neighbor 1.2.3.4 remote-as 1
   neighbor 1.2.3.4 ebgp-multihop 3
   neighbor 2.3.4.5 remote-as 1
   neighbor 2.3.4.5 activate
   neighbor 2.3.4.5 capability orf prefix-list both
   neighbor 2.3.4.5 weight 300
  !
 !
!

We can also change the output to f.ex json or xml for easier handling in scripts etc and/or save to a file.

admin@ncs# show running-config devices device r0 config ios:router | display json
{
  "data": {
    "tailf-ncs:devices": {
      "device": [
        {
          "name": "r0",
          "config": {
            "tailf-ned-cisco-ios:router": {
              "bgp": [
                {
                  "as-no": 64512,
                  "aggregate-address": {
                    "address": "10.10.10.1",
                    "mask": "255.255.255.251"
                  },
                  "neighbor": [
                    {
                      "id": "1.2.3.4",
                      "remote-as": 1,
                      "ebgp-multihop": {
                        "max-hop": 3
                      }
                    },
                    {
                      "id": "2.3.4.5",
                      "remote-as": 1,
                      "activate": [null],
                      "capability": {
                        "orf": {
                          "prefix-list": ["both"]
                        }
                      },
                      "weight": 300
                    }
                  ]
                }
              ]
            }
          }
        }
      ]
    }
  }
}

admin@ncs# show running-config devices device r0 config ios:router | display json | save output.json
admin@ncs#  

So how does NSO actually handle configuration changes we want to do in our network? In the ideal world all configuration changes would be performed only via NSO by either the CLI, WebGui, REST etc.

When committing a change NSO will compare the new config with its CDB and calculate the difference. It will then send the needed changes required over to the specific NED that in turn will translate this over to CLI-specific commands to make the change.

Only if all the changes succeeds on the actual device(s) it will be committed to the CDB, if it fails on any device NSO will roll back the changes on all devices, this even works on devices which doesn’t have native rollback like the Cisco IOS.

Here we have a problem though, if any changes are done outside of NSO it will not be known to the CDB and NSO will be out of sync and unable to do these steps correctly. We would then first have to sync the changes to the CDB or overwrite the current device config with what’s stored in NSOs CDB.

Any way, let’s try and make a configuration change! First we specify which device or range of devices we want to change, in this case r0, r1 & r2 and then the actual change, in this case lets add redistribution of static routes.

admin@ncs# config                
Entering configuration mode terminal
admin@ncs(config)# devices device r0..2 config ios:router bgp 64512 redistribute static 
admin@ncs(config-router)# top
admin@ncs(config)#

In this state no changes has actually been made and is local only. NSO will calculate the difference to its CDB, we can view what the actual changes are going to be with show configuration:

admin@ncs(config)# show configuration
devices device r0
 config
  ios:router bgp 64512
   redistribute static
  !
 !
!
devices device r1
 config
  ios:router bgp 64512
   redistribute static
  !
 !
!
devices device r2
 config
  ios:router bgp 64512
   redistribute static
  !
 !
!

We’re committing the change to both the CDB and the actual devices.

admin@ncs(config)# commit | details
entering validate phase for running...
 2019-05-04T19:32:13.672 validate: run pre-trans-lock service callbacks...
 2019-05-04T19:32:13.672 validate: run transforms and transaction hooks...
 2019-05-04T19:32:13.672 validate: run transforms and transaction hooks done [0.000 sec]
 2019-05-04T19:32:13.673 validate: pre-trans-lock service callbacks done [0.000 sec]
 2019-05-04T19:32:13.673 validate: grabbing transaction lock... ok [0.001 sec]
 2019-05-04T19:32:13.674 validate: creating rollback file... ok [0.003 sec]
 2019-05-04T19:32:13.678 validate: run transforms and transaction hooks...
 2019-05-04T19:32:13.678 validate: run transforms and transaction hooks done [0.000 sec]
 2019-05-04T19:32:13.678 validate: mark inactive... ok [0.000 sec]
 2019-05-04T19:32:13.679 validate: pre validate... ok [0.000 sec]
 2019-05-04T19:32:13.679 validate: run validation over the change set...
 2019-05-04T19:32:13.682 validate: validation over the change set done [0.002 sec]
 2019-05-04T19:32:13.682 validate: run dependency-triggered validation...
 2019-05-04T19:32:13.682 validate: dependency-triggered validation done [0.000 sec]
 2019-05-04T19:32:13.682 validate: check configuration policies...
 2019-05-04T19:32:13.682 validate: configuration policies done [0.000 sec]
entering write-start phase for running...
 2019-05-04T19:32:13.682 cdb: write-start
entering prepare phase for running...
 2019-05-04T19:32:13.684 cdb: prepare
 2019-05-04T19:32:13.684 ncs-internal-device-mgr: prepare
entering commit phase for running...
 2019-05-04T19:32:14.929 cdb: commit
 2019-05-04T19:32:14.931 ncs-internal-service-mux: commit
 2019-05-04T19:32:14.931 ncs-internal-device-mgr: commit
Commit complete.

NSO will also store a rollback-file for each commit so it can be rolled back manually if needed. We can find these files in ./logs/:

admin@ncs(config)# do file show logs/rollback10007
# Created by: admin
# Date: 2019-05-04 19:32:13
# Via: cli
# Type: delta
# Label: 
# Comment: 
# No: 10007

ncs:devices {
    ncs:device r0 {
        ncs:config {
            ios:router {
                ios:bgp 64512 {
                    ios:redistribute {
                        delete:
                        ios:static;
                    }
                }
            }
        }
    }
    ncs:device r1 {
        ncs:config {
            ios:router {
                ios:bgp 64512 {
                    ios:redistribute {
                        delete:
                        ios:static;
                    }
                }
            }
        }
    }
    ncs:device r2 {
        ncs:config {
            ios:router {
                ios:bgp 64512 {
                    ios:redistribute {
                        delete:
                        ios:static;
                    }
                 }
             }
         }
     }
 }

But before we do that let’s verify that one of our devices actually have redistribution configured now.

$ ncs-netsim cli-i r1

admin connected from 192.168.15.188 using ssh on labb-nso
r1> en
r1# show running-config router
router bgp 64512
 aggregate-address 10.10.10.1 255.255.255.251
 neighbor 1.2.3.4 remote-as 1
 neighbor 1.2.3.4 ebgp-multihop 3
 neighbor 2.3.4.5 remote-as 1
 neighbor 2.3.4.5 activate
 neighbor 2.3.4.5 capability orf prefix-list both
 neighbor 2.3.4.5 weight 300
 redistribute static

Cool! Let’s do the rollback and see what happens.

admin@ncs(config)# rollback configuration ?
Possible completions:
  10001   2019-05-04 18:44:00 by system via system
  10002   2019-05-04 18:44:03 by system via system
  10003   2019-05-04 18:56:56 by admin via cli
  10004   2019-05-04 18:56:56 by admin via cli
  10005   2019-05-04 18:56:56 by admin via cli
  10006   2019-05-04 18:56:56 by admin via cli
  10007   2019-05-04 19:32:13 by admin via cli
  <cr>    latest
admin@ncs(config)# rollback configuration 10007
admin@ncs(config)# show configuration 
devices device r0
 config
  ios:router bgp 64512
   no redistribute static
  !
 !
!
devices device r1
 config
  ios:router bgp 64512
   no redistribute static
  !
 !
!
devices device r2
 config
  ios:router bgp 64512
   no redistribute static
  !
 !
!
admin@ncs(config)# commit
Commit complete.

Checking back in R1 we can see that we no longer have any static redist configured.

r1# show running-config router
router bgp 64512
 aggregate-address 10.10.10.1 255.255.255.251
 neighbor 1.2.3.4 remote-as 1
 neighbor 1.2.3.4 ebgp-multihop 3
 neighbor 2.3.4.5 remote-as 1
 neighbor 2.3.4.5 activate
 neighbor 2.3.4.5 capability orf prefix-list both
 neighbor 2.3.4.5 weight 300
!

All gone! I think that will be all for tonight, must say i’m impressed with how easy it was to get started! And that not only is NSO free to use for us nerds to play around with, they also provide tools like Netsim and great documentation etc for everyone.

I haven’t had time to check them out myself yet but they also provide labs you can do over at developer.cisco.com/learning/labs/tags/NSO/page/1

Installing NSO

I’ve been wanting to test Cisco’s NSO for a long time now and finally had some spare time to try and get it up and running on a local VM. It’s free for non-production use now so go ahead and grab it over at
https://developer.cisco.com/site/nso/ (available for both MacOS & Linux).

I installed Ubuntu 18.04 LTS on a fresh vmbox (I didn’t manage to get it working on a raspberry), the only other pre-requirements NSO has is:

  • Java JDK-7.0 or higher
  • Ant
  • python2 or python3
  • python-paramiko

To make life simpler I also installed python3-pip

$ sudo apt-get install python3-pip
$ sudo apt-get install default-jdk
$ sudo apt-get install ant
$ pip3 install paramiko
$ java -version
openjdk version "11.0.2" 2019-01-15
OpenJDK Runtime Environment (build 11.0.2+9-Ubuntu-3ubuntu118.04.3)
OpenJDK 64-Bit Server VM (build 11.0.2+9-Ubuntu-3ubuntu118.04.3, mixed mode, sharing)

I then downloaded the file nso-4.7.linux.x86_64.signed.bin from Cisco, installation was actually much more simpler than I had imagined.

joco02 at labb-nso in ~/Downloads
$ ls
nso-4.7.linux.x86_64.signed.bin

$ sh nso-4.7.linux.x86_64.signed.bin --skip-verification
Unpacking...

joco02 at labb-nso in ~/Downloads
$ ls
cisco_x509_verify_release.py  nso-4.7.linux.x86_64.installer.bin  nso-4.7.linux.x86_64.installer.bin.signature  nso-4.7.linux.x86_64.signed.bin  README.signature  tailf.cer

joco02 at labb-nso in ~/Downloads
$ sh nso-4.7.linux.x86_64.installer.bin $HOME/ncs-4.7 --local-install
INFO  Using temporary directory /tmp/ncs_installer.41189 to stage NCS installation bundle
INFO  Unpacked ncs-4.7 in /home/joco02/ncs-4.7
INFO  Found and unpacked corresponding DOCUMENTATION_PACKAGE
INFO  Found and unpacked corresponding EXAMPLE_PACKAGE
INFO  Generating default SSH hostkey (this may take some time)
INFO  SSH hostkey generated
INFO  Environment set-up generated in /home/joco02/ncs-4.7/ncsrc
INFO  NCS installation script finished
INFO  Found and unpacked corresponding NETSIM_PACKAGE
INFO  NCS installation complete

When then have to source our new folder to enable built-in variables and then setup our environment.

joco02 at labb-nso in ~/Downloads
$ source $HOME/ncs-4.7/ncsrc
$ ncs-setup --dest $HOME/ncs-run

That should be everything, we can now start NSO:

$ ncs

To check status we can use:

joco02 at labb-nso in ~/ncs-run
$ ncs --status | grep status
status: started

joco02 at labb-nso in ~/ncs-run
$ ncs --version
4.7

We should now also be able to reach NSO’s GUI/Webpage for administration at http://serverip:8080/login.html

Login credentials are admin / admin

If your like me and allergic to GUIs we can also connect to the CLI instead.

joco02 at labb-nso in ~/ncs-4.7
$ ncs_cli -u admin

admin connected from 192.168.15.188 using ssh on labb-nso
admin@ncs> ?
Possible completions:
  clear      - Clear parameter
  compare    - Compare running configuration to another configuration or a file
  configure  - Manipulate software configuration information
  describe   - Display transparent command information
  exit       - Exit the management session
  file       - Perform file operations
  help       - Provide help information
  id         - Show user id information
  monitor    - Real-time debugging
  ping       - Ping a host
  ping6      - Ping an ipv6 host
  quit       - Exit the management session
  request    - Make system-level requests
  script     - Script actions
  set        - Set CLI properties
  set-path   - Set relative show path
  show       - Show information about the system
  source     - File to source
  switch     - Change CLI style
  templatize - Find patterns in subtree.
  top        - Exit to top level and optionally run command
  traceroute - Trace the route to a remote host
  up         - Exit one level of configuration

We should now be able to do some labs on NSO! 🙂 Cisco luckily provides a few “Network Elements Drivers” for their netsim-devices for lab purposes, if you want to manage your own devices (real/virtual) you will have to buy a license for it as I understand it.

joco02 at labb-nso in ~/ncs-4.7/packages/neds
$ ls
 a10-acos  cisco-ios  cisco-iosxr  cisco-nx  dell-ftos  juniper-junos

Netmiko part 3 – YAML

The last few examples of using netmiko hasn’t really been very scalable as the devices was provided as a list directly in the code. In this example I thought we could try out and use YAML for an inventory-file instead.

My very small network could look like this (but preferably you would use getpass() instead of writing it directly in your script):

inventory.yml

all:
  vars:
    username: joco02
    password: cisco
  sites:
    - name: gns3
      hosts:
        - hostname: R1
          host: 192.168.15.10
          device_type: cisco_ios
        - hostname: R2
          host: 192.168.15.11
          device_type: cisco_ios
        - hostname: R3
          host: 192.168.15.12
          device_type: cisco_ios

We then import “yaml” in our python-script, read the file and we will have a very nice dictionary to work with! If we wanted to we could divide our topology in different sites and decide for ourselves on which devices we want to run the script instead of all like I do here.

yaml_ouput.py

import yaml

def read_yaml(path="inventory.yml"):
    with open(path) as f:
        yaml_content = yaml.safe_load(f.read())
    return yaml_content

def main():
    parsed_yaml = read_yaml()
    print(parsed_yaml)

if __name__ == "__main__":
    main()

Output:

$ python login.py
{'all': {'vars': {'username': 'joco02', 'password': 'cisco'}, 'sites': [{'name': 'gns3', 'hosts': [{'hostname': 'R1', 'host': '192.168.15.10', 'device_type': 'cisco_ios'}, {'hostname': 'R2', 'host': '192.168.15.11', 'device_type': 'cisco_ios'}, {'hostname': 'R3', 'host': '192.168.15.12', 'device_type': 'cisco_ios'}]}]}}

So let’s rework our previous script so we only use your yaml-file as input to netmiko. It’s a little tricky at first if your as me, not super confident with using dictionaries in python.

We create an empty dict for every device and insert the login credentials, device_type and ip-address from the parsed_yaml dict which we then send over to netmiko with the standard “ConnectHandler(**device)”.

from netmiko import ConnectHandler
from datetime import datetime
from copy import deepcopy
import yaml

SH_COMMANDS = [
    "show ip route | beg Gate", "show ip interface brief | excl admin"
]

def read_yaml(path="inventory.yml"):
    with open(path) as f:
        yaml_content = yaml.safe_load(f.read())
    return yaml_content

def get_connection_parameters(parsed_yaml):
    parsed_yaml = deepcopy(parsed_yaml)
    login_credentials = parsed_yaml["all"]["vars"]
    for site_dict in parsed_yaml["all"]["sites"]:
        for host in site_dict["hosts"]:
            host_dict = {}
            host_dict.update(login_credentials)
            host_dict.update(host)
            yield host_dict

def show_commands(devices, commands):
    for device in devices:
        start_time = datetime.now()
        hostname = device.pop("hostname")
        connection = ConnectHandler(**device)
        device_result = ["{0} {1} {0}".format("=" * 15, hostname)]

        for command in commands:
            command_result = connection.send_command(command)
            device_result.append("{0} {1} {0}".format("=" * 10, command))
            device_result.append(command_result)

        device_result_string = "\n\n".join(device_result)
        connection.disconnect()
        device_result_string += "\nElapsed time: " + str(datetime.now() - start_time)
        yield device_result_string

def main():
    parsed_yaml = read_yaml()
    print(parsed_yaml)
    connection_parameters = get_connection_parameters(parsed_yaml, site_name=SITE_NAME)

    for device_result in show_commands(connection_parameters, SH_COMMANDS):
        print(device_result)


if __name__ == "__main__":
    main()

Output:

$ python login.py
=============== R1 ===============

========== show ip route | beg Gate ==========

Gateway of last resort is 192.168.15.254 to network 0.0.0.0

S*    0.0.0.0/0 [1/0] via 192.168.15.254
      192.168.15.0/24 is variably subnetted, 2 subnets, 2 masks
C        192.168.15.0/24 is directly connected, Ethernet0/0
L        192.168.15.10/32 is directly connected, Ethernet0/0

========== show ip interface brief | excl admin ==========

Interface                  IP-Address      OK? Method Status                Protocol
Ethernet0/0                192.168.15.10   YES manual up                    up

Elapsed time: 0:00:05.841709
=============== R2 ===============

========== show ip route | beg Gate ==========

Gateway of last resort is 192.168.15.254 to network 0.0.0.0

S*    0.0.0.0/0 [1/0] via 192.168.15.254
      192.168.15.0/24 is variably subnetted, 2 subnets, 2 masks
C        192.168.15.0/24 is directly connected, Ethernet0/0
L        192.168.15.11/32 is directly connected, Ethernet0/0

========== show ip interface brief | excl admin ==========

Interface                  IP-Address      OK? Method Status                Protocol
Ethernet0/0                192.168.15.11   YES manual up                    up

Elapsed time: 0:00:05.805187
=============== R3 ===============

========== show ip route | beg Gate ==========

Gateway of last resort is 192.168.15.254 to network 0.0.0.0

S*    0.0.0.0/0 [1/0] via 192.168.15.254
      192.168.15.0/24 is variably subnetted, 2 subnets, 2 masks
C        192.168.15.0/24 is directly connected, Ethernet0/0
L        192.168.15.12/32 is directly connected, Ethernet0/0

========== show ip interface brief | excl admin ==========

Interface                  IP-Address      OK? Method Status                Protocol
Ethernet0/0                192.168.15.12   YES manual up                    up

Elapsed time: 0:00:05.808079

Netmiko part 2

A follow-up on the latest post about Netmiko where we logged in to our devices and did some show commands, let’s continue and do a very common thing in production networks with many devices – pushing out new config from a template.

I’ll be using our latest login_simple.py from last post as a base and just modify it slightly. First we create our config-file that we want in all our devices., let’s say we want to rollout a new route-map in our network.

config_template.txt

ip prefix-list DEFAULT-ROUTE seq 5 permit 0.0.0.0/0
!
route-map DEFAULT-INJECT permit 10
 match ip address prefix-list DEFAULT-ROUTE
 set extcommunity rt 300:301
!
route-map DEFAULT-INJECT permit 20
 set extcommunity rt 300:300

In netmiko we can then use “send_config_from_file” when we’ve logged in to our device(s), super simple! Notice that we don’t have to manage the context in our router (enable/conf t etc), netmiko does it for us.

conf_from_file.py

from netmiko import ConnectHandler
from datetime import datetime
from getpass import getpass

DEVICES = ["192.168.15.10", "192.168.15.11", "192.168.15.12"]
USERNAME = "joco02"
PASSWORD = getpass()
FILE = 'config_template.txt'

def config_commands(devices, file):
    for device in devices:
        start_time = datetime.now()
        connection = ConnectHandler(
            device_type="cisco_ios", host=device,
            username=USERNAME, password=PASSWORD)
        hostname = connection.find_prompt()
        device_result = ["{0} {1} {0}".format("=" * 20, hostname)]

        command_result = connection.send_config_from_file(FILE)
        device_result.append(command_result)

        device_result_string = "\n\n".join(device_result)
        connection.disconnect()
        device_result_string += "\nElapsed time: " + str(datetime.now() - start_time)
        yield device_result_string

def main():

    results = config_commands(DEVICES, FILE)
    for result in results:
        print(result)

if __name__ == "__main__":
    main()

Results:

$ python conf_from_file.py
Password:
==================== R1# ====================

config term
Enter configuration commands, one per line.  End with CNTL/Z.
R1(config)#ip prefix-list DEFAULT-ROUTE seq 5 permit 0.0.0.0/0
R1(config)#!
R1(config)#route-map DEFAULT-INJECT permit 10
R1(config-route-map)# match ip address prefix-list DEFAULT-ROUTE
R1(config-route-map)# set extcommunity rt 300:301
R1(config-route-map)#!
R1(config-route-map)#route-map DEFAULT-INJECT permit 20
R1(config-route-map)# set extcommunity rt 300:300
R1(config-route-map)#end
R1#
Elapsed time: 0:00:08.875642
==================== R2# ====================

config term
Enter configuration commands, one per line.  End with CNTL/Z.
R2(config)#ip prefix-list DEFAULT-ROUTE seq 5 permit 0.0.0.0/0
R2(config)#!
R2(config)#route-map DEFAULT-INJECT permit 10
R2(config-route-map)# match ip address prefix-list DEFAULT-ROUTE
R2(config-route-map)# set extcommunity rt 300:301
R2(config-route-map)#!
R2(config-route-map)#route-map DEFAULT-INJECT permit 20
R2(config-route-map)# set extcommunity rt 300:300
R2(config-route-map)#end
R2#
Elapsed time: 0:00:08.799664
==================== R3# ====================

config term
Enter configuration commands, one per line.  End with CNTL/Z.
R3(config)#ip prefix-list DEFAULT-ROUTE seq 5 permit 0.0.0.0/0
R3(config)#!
R3(config)#route-map DEFAULT-INJECT permit 10
R3(config-route-map)# match ip address prefix-list DEFAULT-ROUTE
R3(config-route-map)# set extcommunity rt 300:301
R3(config-route-map)#!
R3(config-route-map)#route-map DEFAULT-INJECT permit 20
R3(config-route-map)# set extcommunity rt 300:300
R3(config-route-map)#end
R3#
Elapsed time: 0:00:09.067742

We can now easily just change the config in our template and run the script again when it’s time for another rollout.

Netmiko

I’ve been playing around with Netmiko this weekend as I would like to try and rewrite all my old bash- & expect-scripts at work to python instead (if I ever will find the time)… 🙂

Just starting out I felt like it was incredibly slow however and was pretty disappointed when for ex. pushing out config from a file to multiple devices compared to expect. Perhaps I did something wrong but it also looks like there’s several ways to speed things up so I thought I should give it another go.

I’m using simple topology with three routers and some basic config allowing ssh-login:

  • R1 192.168.15.10
  • R2 192.168.15.11
  • R3 192.168.15.12

login_supersimple.py

We start by importing netmiko’s ConnectHandler, specify some “show commands” we’d like to run and lastly we need to specify what type of device we’re going to connect to and login credentials.

One very simple way to do this is using a dictionary for each device:

from netmiko import ConnectHandler
from getpass import getpass

R1 = {
    "host": "192.168.15.10",
    "username": "joco02",
    "password": getpass(),
    "device_type": "cisco_ios",
}

We then use the dictionary when calling our ConnectHandler to provide all necessary info for logging in and setting the correct device type.

connection = ConnectHandler(**R1)
print(connection.find_prompt())
connection.disconnect()

Results:

$ python login_supersimple.py
Password:
R1#

login_simple.py

We can also skip using the dictionary and specify connection parameters manually instead. Here i’m sending multiple commands to multiple devices using only lists. In a later post I thought we could try and use a YAML-file for our devices and connection parameters.

from netmiko import ConnectHandler
from datetime import datetime
from getpass import getpass

SH_COMMANDS = [
"show ip route", "show ip interface brief"
]

DEVICES = ["192.168.15.10", "192.168.15.11", "192.168.15.12"]
USERNAME = "joco02"
PASSWORD = getpass()

def show_commands(devices, commands):
for device in devices:
start_time = datetime.now()
connection = ConnectHandler(
device_type="cisco_ios", host=device,
username=USERNAME, password=PASSWORD)
hostname = connection.find_prompt()
device_result = ["{0} {1} {0}".format("=" * 20, hostname)]

for command in commands:
command_result = connection.send_command(command)
device_result.append("{0} {1} {0}".format("=" * 20, command))
device_result.append(command_result)

device_result_string = "\n\n".join(device_result)
connection.disconnect()
device_result_string += "\nElapsed time: " + str(datetime.now() - start_time)
yield device_result_string

def main():

results = show_commands(DEVICES, SH_COMMANDS)
for result in results:
print(result)

if __name__ == "__main__":
main()

Results:

$ python login_simple.py
Password:
==================== R1# ====================

==================== show ip route ====================

Codes: L - local, C - connected, S - static, R - RIP, M - mobile, B - BGP
       D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area
       N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2
       E1 - OSPF external type 1, E2 - OSPF external type 2
       i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2
       ia - IS-IS inter area, * - candidate default, U - per-user static route
       o - ODR, P - periodic downloaded static route, H - NHRP, l - LISP
       a - application route
       + - replicated route, % - next hop override

Gateway of last resort is 192.168.15.254 to network 0.0.0.0

S*    0.0.0.0/0 [1/0] via 192.168.15.254
      10.0.0.0/8 is variably subnetted, 16 subnets, 2 masks
C        10.10.0.0/24 is directly connected, Loopback0
L        10.10.0.1/32 is directly connected, Loopback0
C        10.10.1.0/24 is directly connected, Loopback1
L        10.10.1.1/32 is directly connected, Loopback1
C        10.10.2.0/24 is directly connected, Loopback2
L        10.10.2.1/32 is directly connected, Loopback2
C        10.10.3.0/24 is directly connected, Loopback3
L        10.10.3.1/32 is directly connected, Loopback3
O        10.11.0.0/24 [110/11] via 192.168.15.11, 23:58:37, Ethernet0/0
O        10.11.1.0/24 [110/11] via 192.168.15.11, 23:58:27, Ethernet0/0
O        10.11.2.0/24 [110/11] via 192.168.15.11, 23:58:27, Ethernet0/0
O        10.11.3.0/24 [110/11] via 192.168.15.11, 23:58:27, Ethernet0/0
O        10.12.0.0/24 [110/11] via 192.168.15.12, 23:58:15, Ethernet0/0
O        10.12.1.0/24 [110/11] via 192.168.15.12, 23:58:05, Ethernet0/0
O        10.12.2.0/24 [110/11] via 192.168.15.12, 23:58:05, Ethernet0/0
O        10.12.3.0/24 [110/11] via 192.168.15.12, 23:58:05, Ethernet0/0
      192.168.15.0/24 is variably subnetted, 2 subnets, 2 masks
C        192.168.15.0/24 is directly connected, Ethernet0/0
L        192.168.15.10/32 is directly connected, Ethernet0/0

==================== show ip interface brief ====================

Interface                  IP-Address      OK? Method Status                Protocol
Ethernet0/0                192.168.15.10   YES manual up                    up
Ethernet0/1                unassigned      YES NVRAM  administratively down down
Ethernet0/2                unassigned      YES NVRAM  administratively down down
Ethernet0/3                unassigned      YES NVRAM  administratively down down
Ethernet1/0                unassigned      YES NVRAM  administratively down down
Ethernet1/1                unassigned      YES NVRAM  administratively down down
Ethernet1/2                unassigned      YES NVRAM  administratively down down
Ethernet1/3                unassigned      YES NVRAM  administratively down down
Serial2/0                  unassigned      YES NVRAM  administratively down down
Serial2/1                  unassigned      YES NVRAM  administratively down down
Serial2/2                  unassigned      YES NVRAM  administratively down down
Serial2/3                  unassigned      YES NVRAM  administratively down down
Serial3/0                  unassigned      YES NVRAM  administratively down down
Serial3/1                  unassigned      YES NVRAM  administratively down down
Serial3/2                  unassigned      YES NVRAM  administratively down down
Serial3/3                  unassigned      YES NVRAM  administratively down down
Loopback0                  10.10.0.1       YES manual up                    up
Loopback1                  10.10.1.1       YES manual up                    up
Loopback2                  10.10.2.1       YES manual up                    up
Loopback3                  10.10.3.1       YES manual up                    up
Elapsed time: 0:00:06.128156
==================== R2# ====================

==================== show ip route ====================

Codes: L - local, C - connected, S - static, R - RIP, M - mobile, B - BGP
       D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area
       N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2
       E1 - OSPF external type 1, E2 - OSPF external type 2
       i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2
       ia - IS-IS inter area, * - candidate default, U - per-user static route
       o - ODR, P - periodic downloaded static route, H - NHRP, l - LISP
       a - application route
       + - replicated route, % - next hop override

Gateway of last resort is 192.168.15.254 to network 0.0.0.0

S*    0.0.0.0/0 [1/0] via 192.168.15.254
      10.0.0.0/8 is variably subnetted, 16 subnets, 2 masks
O        10.10.0.0/24 [110/11] via 192.168.15.10, 1d00h, Ethernet0/0
O        10.10.1.0/24 [110/11] via 192.168.15.10, 23:58:53, Ethernet0/0
O        10.10.2.0/24 [110/11] via 192.168.15.10, 23:58:53, Ethernet0/0
O        10.10.3.0/24 [110/11] via 192.168.15.10, 23:58:43, Ethernet0/0
C        10.11.0.0/24 is directly connected, Loopback0
L        10.11.0.1/32 is directly connected, Loopback0
C        10.11.1.0/24 is directly connected, Loopback1
L        10.11.1.1/32 is directly connected, Loopback1
C        10.11.2.0/24 is directly connected, Loopback2
L        10.11.2.1/32 is directly connected, Loopback2
C        10.11.3.0/24 is directly connected, Loopback3
L        10.11.3.1/32 is directly connected, Loopback3
O        10.12.0.0/24 [110/11] via 192.168.15.12, 23:58:21, Ethernet0/0
O        10.12.1.0/24 [110/11] via 192.168.15.12, 23:58:11, Ethernet0/0
O        10.12.2.0/24 [110/11] via 192.168.15.12, 23:58:11, Ethernet0/0
O        10.12.3.0/24 [110/11] via 192.168.15.12, 23:58:11, Ethernet0/0
      192.168.15.0/24 is variably subnetted, 2 subnets, 2 masks
C        192.168.15.0/24 is directly connected, Ethernet0/0
L        192.168.15.11/32 is directly connected, Ethernet0/0

==================== show ip interface brief ====================

Interface                  IP-Address      OK? Method Status                Protocol
Ethernet0/0                192.168.15.11   YES manual up                    up
Ethernet0/1                unassigned      YES NVRAM  administratively down down
Ethernet0/2                unassigned      YES NVRAM  administratively down down
Ethernet0/3                unassigned      YES NVRAM  administratively down down
Ethernet1/0                unassigned      YES NVRAM  administratively down down
Ethernet1/1                unassigned      YES NVRAM  administratively down down
Ethernet1/2                unassigned      YES NVRAM  administratively down down
Ethernet1/3                unassigned      YES NVRAM  administratively down down
Serial2/0                  unassigned      YES NVRAM  administratively down down
Serial2/1                  unassigned      YES NVRAM  administratively down down
Serial2/2                  unassigned      YES NVRAM  administratively down down
Serial2/3                  unassigned      YES NVRAM  administratively down down
Serial3/0                  unassigned      YES NVRAM  administratively down down
Serial3/1                  unassigned      YES NVRAM  administratively down down
Serial3/2                  unassigned      YES NVRAM  administratively down down
Serial3/3                  unassigned      YES NVRAM  administratively down down
Loopback0                  10.11.0.1       YES manual up                    up
Loopback1                  10.11.1.1       YES manual up                    up
Loopback2                  10.11.2.1       YES manual up                    up
Loopback3                  10.11.3.1       YES manual up                    up
Elapsed time: 0:00:06.205272
==================== R3# ====================

==================== show ip route ====================

Codes: L - local, C - connected, S - static, R - RIP, M - mobile, B - BGP
       D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area
       N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2
       E1 - OSPF external type 1, E2 - OSPF external type 2
       i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2
       ia - IS-IS inter area, * - candidate default, U - per-user static route
       o - ODR, P - periodic downloaded static route, H - NHRP, l - LISP
       a - application route
       + - replicated route, % - next hop override

Gateway of last resort is 192.168.15.254 to network 0.0.0.0

S*    0.0.0.0/0 [1/0] via 192.168.15.254
      10.0.0.0/8 is variably subnetted, 16 subnets, 2 masks
O        10.10.0.0/24 [110/11] via 192.168.15.10, 1d00h, Ethernet0/0
O        10.10.1.0/24 [110/11] via 192.168.15.10, 23:58:59, Ethernet0/0
O        10.10.2.0/24 [110/11] via 192.168.15.10, 23:58:49, Ethernet0/0
O        10.10.3.0/24 [110/11] via 192.168.15.10, 23:58:49, Ethernet0/0
O        10.11.0.0/24 [110/11] via 192.168.15.11, 23:58:49, Ethernet0/0
O        10.11.1.0/24 [110/11] via 192.168.15.11, 23:58:39, Ethernet0/0
O        10.11.2.0/24 [110/11] via 192.168.15.11, 23:58:39, Ethernet0/0
O        10.11.3.0/24 [110/11] via 192.168.15.11, 23:58:39, Ethernet0/0
C        10.12.0.0/24 is directly connected, Loopback0
L        10.12.0.1/32 is directly connected, Loopback0
C        10.12.1.0/24 is directly connected, Loopback1
L        10.12.1.1/32 is directly connected, Loopback1
C        10.12.2.0/24 is directly connected, Loopback2
L        10.12.2.1/32 is directly connected, Loopback2
C        10.12.3.0/24 is directly connected, Loopback3
L        10.12.3.1/32 is directly connected, Loopback3
      192.168.15.0/24 is variably subnetted, 2 subnets, 2 masks
C        192.168.15.0/24 is directly connected, Ethernet0/0
L        192.168.15.12/32 is directly connected, Ethernet0/0

==================== show ip interface brief ====================

Interface                  IP-Address      OK? Method Status                Protocol
Ethernet0/0                192.168.15.12   YES manual up                    up
Ethernet0/1                unassigned      YES NVRAM  administratively down down
Ethernet0/2                unassigned      YES NVRAM  administratively down down
Ethernet0/3                unassigned      YES NVRAM  administratively down down
Ethernet1/0                unassigned      YES NVRAM  administratively down down
Ethernet1/1                unassigned      YES NVRAM  administratively down down
Ethernet1/2                unassigned      YES NVRAM  administratively down down
Ethernet1/3                unassigned      YES NVRAM  administratively down down
Serial2/0                  unassigned      YES NVRAM  administratively down down
Serial2/1                  unassigned      YES NVRAM  administratively down down
Serial2/2                  unassigned      YES NVRAM  administratively down down
Serial2/3                  unassigned      YES NVRAM  administratively down down
Serial3/0                  unassigned      YES NVRAM  administratively down down
Serial3/1                  unassigned      YES NVRAM  administratively down down
Serial3/2                  unassigned      YES NVRAM  administratively down down
Serial3/3                  unassigned      YES NVRAM  administratively down down
Loopback0                  10.12.0.1       YES manual up                    up
Loopback1                  10.12.1.1       YES manual up                    up
Loopback2                  10.12.2.1       YES manual up                    up
Loopback3                  10.12.3.1       YES manual up                    up
Elapsed time: 0:00:05.982471

Around 6 seconds per device isn’t that bad when we’re not even using threading or asyncio to speed things up. There is probably other parameters that can be tuned inside netmiko as well, i’ve seen delay-values that defaults to 1 second for example but we’ll save that for a later post.

Finally finished my project!

With just a one day to go before vacation I finally finished the webportal i’ve been working on last few weeks/months and it’s now in production at the company I work at. Time to celebrate with a new whisky I think… 🙂

Purpose of the website is to semi-automate the building process and simplify config-generation (+ verification & deployment steps) for new links, nodes & services dynamically with just a few clicks. It will also rebuild existing solutions to newer architecture by just copy/paste’ing the current config. It’s written entirely in Python & Flask with some simple shellscripts running in the background to fetch statistics and some other minor stuff.

I’ve hosted a demo-page with very limited functionality and redacted config-templates if anyone is interested at https://www.deployment-tools.se/home. Spent the evening redesigning my own personal site as well which was very overdue.

Had a blast working on this project,  it also feels like most things in our field is slowly moving in the direction of more software-based networks and automation so i’m looking forward to dive deeper and learn more.

Intro to Github, Linux on Win10 & dotfiles

I’ve been buried with work trying to deploy the web portal I mentioned earlier before I go on vacation, only 2 weeks left! Also finally gotten my feet wet trying out Github for the first time and it’s been real eye opener, how I managed without it earlier I don’t know… I’ll try and get around posting some repos next time I write about Python/automation again. 🙂

Really recommend watching Corey Schafers tutorials/intro to Github here if you’re interested, he does an amazing job explaining most of the features you’ll ever need.

Spending so much time in a linux prompt and writing some shell scripts now and then it’s been a bit annoying not being able to test them without having to SSH in to a linux-distro every time. Today I stumbled upon another pretty cool video from Corey showing how you now can run a Linux subsystem within Windows 10!

It was very easy getting started, just open up your start menu and search for “Turn Windows features on or off” and enable “Windows Subsystem for Linux”. After installation/reboot is done just search for whatever distro you want to run in the Windows store and install!

Another cool thing he mentions is https://dotfiles.github.io/ which is full of neat little scripts to make every day life easier. I recommend checking out Mathias Bynens repo at https://github.com/mathiasbynens/dotfiles to start with and look around, feels like there’s something for everyone.

Deployment-tools

I’ve been busy these last few days working on migrating the website I created over at deployment-tools.se to Telias datacenter as it soon will be deployed for official use in the company, fun stuff! 🙂 It’s like an NSO-light which creates configuration (initial, full config & verification) for deployment of aggregation nodes, new links, services etc based on Python & Flask.

I’ve written a few posts showing how to do things like automate config for DHCP, firewalls & IOS etc that can be found here, here, here, here & here (in swedish though).  My weakest point is for sure design & CSS so i’m having a pretty rough time renewing the design (& HTML5) making it more dynamic but it’s slowly coming together now. I’ll host a demo-page the next few days when i’m closer to finishing it.

Moving from my own Raspberry to a very secured and locked down release of RHEL7 was a challenge as well hehe.. I’ve been trying to keep up with the reading in the mornings at least and i’m soon done with Internet Routing Architectures, it’s an old book but a very good read, highly recommended as a good intro to BGP!

Python & Flask – Chatterbot

Tänkte skriva ett litet inlägg om hur jag implementerade Chatterbot på min Flask/Python-webbsida, krävdes en hel del trial & error innan jag väl fick det att fungera så kanske kan vara intressant för någon mer. Ordförrådet fick dock hållas väldigt begränsat då Raspberryn inte riktigt orkar med om det blev för stora databaser att jobba med (svarstider på +20 sek).

Python

Börja med att installera Chatterbot-paketet, rekommenderar även att läsa igenom den officiella dokumentationen först för att förstå hur allt hänger ihop.

pip install chatterbot

Vi importerar sedan biblioteket i vår python-fil och skapar vår egen Chatbot-instans, här pekar vi även ut vilken databas vi ska använda, i mitt fall en Postgres-databas som ligger lokalt på servern:

from chatterbot import ChatBot
from chatterbot.trainers import ChatterBotCorpusTrainer

english_bot = ChatBot("jcAI", 
  storage_adapter="chatterbot.storage.SQLStorageAdapter",
  database_uri="postgres://user:password@localhost:5432/chatbot")

Nästa steg blir att “träna” vår chatbot, antingen via fördefinierade Corpus-filer som följer med vid installationen eller så skapar vi egna, i mitt fall gjorde jag både och. Filerna laddas härifrån:

/www/flaskenv/lib/python3.5/site-packages/chatterbot_corpus/data$ ls -l
totalt 68
drwxr-xr-x 2 joco02 joco02 4096 mar 7 13:29 bangla
drwxr-xr-x 2 joco02 joco02 4096 mar 7 13:29 chinese
drwxr-xr-x 2 joco02 joco02 4096 mar 9 22:58 custom
drwxr-xr-x 2 joco02 joco02 4096 mar 9 23:31 english
drwxr-xr-x 2 joco02 joco02 4096 mar 7 13:29 french
drwxr-xr-x 2 joco02 joco02 4096 mar 7 13:29 german
drwxr-xr-x 2 joco02 joco02 4096 mar 7 13:29 hebrew
drwxr-xr-x 2 joco02 joco02 4096 mar 7 13:29 hindi
drwxr-xr-x 2 joco02 joco02 4096 mar 7 13:29 indonesia
drwxr-xr-x 2 joco02 joco02 4096 mar 7 13:29 italian
drwxr-xr-x 2 joco02 joco02 4096 mar 7 13:29 marathi
drwxr-xr-x 2 joco02 joco02 4096 mar 7 13:29 portuguese
drwxr-xr-x 2 joco02 joco02 4096 mar 7 13:29 russian
drwxr-xr-x 2 joco02 joco02 4096 mar 7 13:29 spanish
drwxr-xr-x 2 joco02 joco02 4096 mar 21 15:00 swedish
drwxr-xr-x 2 joco02 joco02 4096 mar 7 13:29 tchinese
drwxr-xr-x 2 joco02 joco02 4096 mar 7 13:29 telugu

Några exempel från min egna Corpus-fil (sparad i custom), den inledande meningen/frågan börjar alltid med “- -” och efterföljande svar med “-“:

- - Dagens lunch
  - Please use ex. !lunch KG13 or !lunch pannbiff
- - DHCP
  - DCHP-documents is found <a href="http://link.link/DHCP/">here.</a>
- - Edge
  - Edge & Core Workroom is found <a href="http://link.link/core_agg/">here.</a>

Vi pekar sedan på önskade Corpus-filer i scriptet (detta behövs endast köras en gång och kan sedan kommenteras bort, verifiera via apache2-loggen att allt gick bra).

english_bot.set_trainer(ChatterBotCorpusTrainer)
english_bot.train("chatterbot.corpus.swedish", "chatterbot.corpus.english", "chatterbot.corpus.custom")

Sedan återstår endast att presentera användarinput & vår Chatbots svar på en webbsida:

@app.route("/chatbot")
def chatbot():
  return render_template("chatbot.html")

@app.route("/get")
def get_bot_response():
  userText = request.args.get('msg')

  return str(english_bot.get_response(userText))

HTML

Först skapar vi en “Chatbox”, använder Boostrap för att höger/vänsterjustera texten mellan bot/användare samt färgval.

<div class="row">
 <div class="col-md-6">
  <div class="panel panel-default">
   <div class="panel-heading"><span>AI Chat</span></div>
   <div class="panel-body">
    <p>Ask me something!<br></p>
    <div id="chatbox"></div>
   </div>
 <div class="panel-footer">
  <div id="userInput">
  <input id="textInput" type="text" name="msg" placeholder="">
  <input id="buttonInput" type="submit" value="Send" class="btn btn-success btn-xs">
  </div>
 </div>
 </div>
</div>

Sedan använder vi oss av ett script för att pusha/hämta info och presentera i ovanstående chatbox:

<script>
 function getBotResponse() {
 var rawText = $("#textInput").val();
 var userHtml = '<p class="text-success text-right"><span>' + rawText + '</span></p>';
 $("#textInput").val("");
 $("#chatbox").append(userHtml);
 document.getElementById('userInput').scrollIntoView({block: 'start', behavior: 'smooth'});
 $.get("/get", { msg: rawText }).done(function(data) {
 var botHtml = '<p>' + data + '</p>';
 $("#chatbox").append(botHtml);
 document.getElementById('userInput').scrollIntoView({block: 'start', behavior: 'smooth'});
 });
 }
 $("#textInput").keypress(function(e) {
 if(e.which == 13) {
 getBotResponse();
 }
 });
 $("#buttonInput").click(function() {
 getBotResponse();
 })
 </script>
</div>

Kompletterande även med en liten “infobox” (tips på användbara kommandon etc):

<div class="col-md-4">
 <div class="panel panel-default">
 <div class="panel-body">
  <h4>Some helpful commands:</h4><br>
  <small><strong>Jönköping:</strong></small><br>
  <small>!lunch <em>restaurant</em> (ex: <em><strong>!lunch KG13 or !lunch Vy</strong>)</em></small><br>
  <small>!lunch <em>food</em> (ex: <em><strong>!lunch kyckling</strong>)</em></small><br><br>

  <small><strong>Solna:</strong></small><br>
  <small>!solna <em>(shows todays menu at Modis)</em></small><br><br>

  <small><strong>Göteborg:</strong></small><br>
  <small>!gbg <em>(shows todays menu at Vällagat)</em></small><br><br>

  <small><strong>Other commands:</strong></small><br> 
  <small>!callguide (ex: <em><strong>!callguide or !callguide v12)</strong>)</em></small><br>
  <small>!sdp <em>node</em> (ex: <em><strong>!sdp j-sec1 or !sdp get-hsr1)</strong>)</em></small><br>
  <small>DHCP</small><br>
  <small>Common Services</small><br>
  <small>Standardporch</small><br>
  <small>etc..</small><br><br> 
  <small>I'm programmed with a few keywords so feel free to try anything, i'll try my best to respond. Let joco02 know if something else should be added for easy access.</small>
 </div>
 </div>
</div>

Som synes har jag byggt in ytterligare några funktioner här som använder samma chatbox men kringgår Chatterbot och presenterar resultat från andra scriptkörningar, exempelvis:

  • !lunch / !solna / !gbg – Använder Beautifulsoup4 för webscraping av menyer från lunchrestauranger i Jönköping/Solna/Göteborg (i närheten av Telia)
  • !callguide – Använder en schemafil och presenterar ansvarig person för aktuell/angiven vecka

Kanske återkommer med ett inlägg eller två om hur jag löste ovanstående framöver när det finns tid över.. 🙂