Cisco NSO – Create Service

In NSO, service is defined in YANG model. And once YANG model is defined and compiled, it will then be encoded to XML. There are few variations to define encode, such as “template only” and “python and template”. As name suggests, template is the most basic pattern, and it directly map the YANG model to XML. While with python some arbitrary operation can be configured based on YANG model before passing any values for XML encode.


1. Generate skeleton

Service package is composed from many files, you can create one by one, but you can generate a service skeleton by calling “ncs-make-package”

$ ncs-make-package --service-skeleton python-and-template custom_snmp_community

2. Compile with YANG model

First, we need to define how the service look like. It’s basically to define what information you want to correct in order to create a service. Because we are going to define service to modify snmp community, we need to get following information:

  • snmp community name
  • snmp community access right

So here in “leaf”, we defined what kind of information user can pass to this service. Eventually, these leaf will be presented as argument when user configures this service.

k_shogo@ubuntu16-nso:~/ncs-run/packages$ cd custom_snmp_community/
k_shogo@ubuntu16-nso:~/ncs-run/packages/custom_snmp_community$ cd src
k_shogo@ubuntu16-nso:~/ncs-run/packages/custom_snmp_community/src$ vim 
Makefile  yang/     
k_shogo@ubuntu16-nso:~/ncs-run/packages/custom_snmp_community/src$ vim yang/custom_snmp_community.yang 
k_shogo@ubuntu16-nso:~/ncs-run/packages/custom_snmp_community/src$ cat yang/custom_snmp_community.yang 
module custom_snmp_community {

  namespace "http://example.com/custom_snmp_community";
  prefix custom_snmp_community;

  import ietf-inet-types {
    prefix inet;
  }
  import tailf-common {
    prefix tailf;
  }
  import tailf-ncs {
    prefix ncs;
  }

  revision 2016-01-01 {
    description
      "Initial revision.";
  }

  list custom_snmp_community {
    description "This is an RFS skeleton service";

    key community_string;
    leaf community_string {
      tailf:info "community string";
      type string;
    }

    uses ncs:service-data;
    ncs:servicepoint custom_snmp_community-servicepoint;

    // may replace this with other ways of refering to the devices.
    leaf-list device {
      type leafref {
        path "/ncs:devices/ncs:device/ncs:name";
      }
    }

    leaf access_right {
      tailf:info "read-only or read-write";
      type enumeration {
        enum read-only;
        enum read-write;
      }
    }
  }
}

One you configured the model, we need to compile it.

custom_snmp_community/src$ make clean all
rm -rf ../load-dir java/src//
mkdir -p ../load-dir
mkdir -p java/src//
/home/k_shogo/nso-4.7/bin/ncsc  `ls custom_snmp_community-ann.yang  > /dev/null 2>&1 && echo "-a custom_snmp_community-ann.yang"` \
              -c -o ../load-dir/custom_snmp_community.fxs yang/custom_snmp_community.yang

3. Create python function

Those variables are passed to python function once user configures the service. In this example, I just added current year after the user passed value to make a community name.

custom_snmp_community$ vim python/custom_snmp_community/main.py 
custom_snmp_community$ cat python/custom_snmp_community/main.py 
# -*- mode: python; python-indent: 4 -*-
from datetime import date

import ncs
from ncs.application import Service


# ------------------------
# SERVICE CALLBACK EXAMPLE
# ------------------------
class ServiceCallbacks(Service):

    @Service.create
    def cb_create(self, tctx, root, service, proplist):
        self.log.info('Service create(service=', service._path, ')')

        vars = ncs.template.Variables()
        # This COMMUNITY and ACCESS is passed to XML
        vars.add('COMMUNITY', service.community_string + str(date.today().year))
        vars.add('ACCESS', 'ro' if service.access_right == 'read-only' else 'rw')
        template = ncs.template.Template(service)
        template.apply('custom_snmp_community-template', vars)

# ---------------------------------------------
# COMPONENT THREAD THAT WILL BE STARTED BY NCS.
# ---------------------------------------------
class Main(ncs.application.Application):
    def setup(self):
        self.log.info('Main RUNNING')

        self.register_service('custom_snmp_community-servicepoint', ServiceCallbacks)

    def teardown(self):

        self.log.info('Main FINISHED')

4. Create XML template

This is the final piece of the service definition. This XML file will have an outlined XML, which is used to define the architecture of the service and eventually it is used to create the actual configuration on managed network devices.

custom_snmp_community$ vim templates/custom_snmp_community-template.xml 
custom_snmp_community$ cat templates/custom_snmp_community-template.xml 
<config-template xmlns="http://tail-f.com/ns/config/1.0">
  <devices xmlns="http://tail-f.com/ns/ncs">
    <device>
      <name>{/device}</name>
      <config>
      <!-- This part is specific to each platform -->
        <snmp-server xmlns="urn:ios">
          <community>
            <name>{$COMMUNITY}</name>
            <RW when="{starts-with($ACCESS, 'rw')}" />
            <RO when="{starts-with($ACCESS, 'ro')}" />
          </community>
        </snmp-server>
      <!-- End of platform specific section -->
      </config>
    </device>
  </devices>
</config-template>

5. Load the package into NSO

All the components in the package are ready, now you need to load the package into the NSO. You should see “oper-status up” if you configured correctly.

admin@ncs> request packages reload force

>>> System upgrade is starting.
>>> Sessions in configure mode must exit to operational mode.
>>> No configuration changes can be performed until upgrade has completed.
>>> System upgrade has completed successfully.
reload-result {
    package a10-acos
    result true
}
reload-result {
    package cisco-ios
    result true
}
reload-result {
    package cisco-iosxr
    result true
}
reload-result {
    package cisco-nx
    result true
}
reload-result {
    package custom_snmp_community
    result true
}
reload-result {
    package dell-ftos
    result true
}
reload-result {
    package juniper-junos
    result true
}
[ok]
admin@ncs> show packages package custom_snmp_community 
packages package custom_snmp_community
 package-version 1.0
 description     "Generated Python package"
 ncs-min-version [ 4.7 ]
 python-package vm-name custom_snmp_community
 directory       ./state/packages-in-use/1/custom_snmp_community
 templates       [ custom_snmp_community-template ]
 component main
  application python-class-name custom_snmp_community.main.Main
  application start-phase phase2
 oper-status up
[ok]

6. Test

Let’s test how we can use this newly created package to manage a service.

First, create a service. As you can see, leaf elemts are displayed as argument candidate.

admin@ncs> configure              
Entering configuration mode private
[ok]

[edit]
admin@ncs% set custom_snmp_community 
Possible completions:
  community string
admin@ncs% set custom_snmp_community testcomm 
Possible completions:
  access_right - read-only or read-write
  device       - 
admin@ncs% set custom_snmp_community testcomm access_right read-only device useast-csr01 
[ok]

Let’s see what command line will be sent to the managed devices.

[edit]
admin@ncs% commit dry-run outformat native                                              
native {
    device {
        name useast-csr01
        data snmp-server community testcomm2019 RO
    }
}
[ok]

We can see “snmp-server community testcomm2019 RO” will be sent to the device. Commit the change and see if it really changes the configuration.

[edit]
admin@ncs% commit
Commit complete.
[ok]

[edit]
admin@ncs% show custom_snmp_community 
custom_snmp_community testcomm {
    device       [ useast-csr01 ];
    access_right read-only;
}
[ok]

It looks good, and config in Cisco (csr1000v) is also changed correclty.

useast-csr01#sh run | i snmp
snmp-server community testcomm2019 RO

And changing the access right…

admin@ncs% set custom_snmp_community testcomm access_right read-write 
[ok]

[edit]
admin@ncs% commit
Commit complete.
[ok]

…is also propagated to the device correctly.

useast-csr01#sh run | i snmp      
snmp-server community testcomm2019 RW

Once you are done with your service, you can delete the service…

admin@ncs% delete custom_snmp_community testcomm
[ok]

[edit]
admin@ncs% commit dry-run outformat native
native {
    device {
        name useast-csr01
        data no snmp-server community testcomm2019 RW
    }
}
[ok]

[edit]
admin@ncs% 

[edit]
admin@ncs% commit
Commit complete.
[ok]

[edit]
admin@ncs% show custom_snmp_community 
No entries found.
[ok]

And that configuration is also deleted from the device.

useast-csr01#sh run | i snmp
useast-csr01#

The evaluation version of NSO comes with only a few NEDs, and obviously I have not tried much. It is quite different from other competitors in terms of how service is defined.