Cisco NSO300 Lab 2: Create SVI Service Using Pre_Modification Service Callback

Create SVI Service Using Pre_Modification Service Callback

In Lab 2: Create SVI Service Using pre_modification Service Callback, participants typically delve into the practical implementation of creating a Switched Virtual Interface (SVI) service within the Network Services Orchestrator (NSO) environment. This lab involves tasks such as defining the SVI service parameters, leveraging pre-modification service callbacks to validate and modify configurations before deployment, and ensuring the consistent provisioning of SVI services across network devices. Participants may explore the customization capabilities offered by pre-modification service callbacks to enforce specific business rules or policies. The lab aims to provide hands-on experience in utilizing NSO service callbacks for advanced service customization, contributing to the efficient and controlled deployment of SVI services in a network environment. Successful completion of Cisco NSO300 Lab 2 equips participants with practical skills in leveraging NSO's capabilities for customized service orchestration.

Lab:

In this lab, you will use advanced YANG modeling techniques and implement a pre_modification service callback.

The service will need a YANG data model, XML templates, and some Python code. Your new service package will be based on the existing model, which is provided to you in a separate folder.

Because your service is intended for switches, you will put a constraint in your data model. The YANG modeling language allows creating constraints with a must statement.

The pre_modification callback will be implemented in Python and will provide the next available vlan-id to the upgraded svi service. The callback will be executed when you commit the configuration. Therefore, your service can keep track of every vlan-id in use without the need for a separate database.

Task 1: Upgrade the YANG Service Model

In this task, you will upgrade the svi YANG model. Files from the old service have been provided for you in the $HOME/packages/svi directory.

The must YANG statement requires an argument in the form of a string. The string must be a valid XPath expression that evaluates to true for data to be valid.

The feature interface-vlan must be enabled if the ned-id is cisco-nx. This configuration will be a part of the service template.

Note: The final solutions for all labs, including this one, are located in the ~/solutions directory. You can use them for copying longer pieces of code and as a reference point for troubleshooting your packages.

Step 1: Connect to the VM server by clicking the NSO icon.

Step 2: Open the terminal window; click the Terminal icon on the bottom bar.

rst@rst:~$ 

Step 3: Create and change directory to nso300 inside ~/NSO directory.

rst@rst:~$ **cd NSO**
rst@rst:~/NSO$ **mkdir nso300**
rst@rst:~/NSO$ **cd nso300**
rst@rst:~/NSO/nso300$ 

Step 4: Enter the development NSO Docker container shell with the make dev-shell command and enter the /src/packages directory.

rst@rst:~/NSO/nso300$ **make dev-shell** 
docker run -it -v $(pwd):/src nso300.gitlab.local/cisco-nso-dev:5.3
root@46a3fd8d7374:/# **cd src/packages/**
root@46a3fd8d7374:/src/packages# 

Step 5: Create a new svi package with the ncs-make-package command.

root@46a3fd8d7374:/src/packages# **ncs-make-package --service-skeleton python-and-template --component-class svi.Svi svi**

Step 6: Change the ownership of the package and exit the Docker container.

root@46a3fd8d7374:/src/packages# **chown -Rv 1000:1000 svi**
changed ownership of 'svi/README' from root:root to 1000:1000
changed ownership of 'svi/templates/svi-template.xml' from root:root to 1000:1000
changed ownership of 'svi/templates' from root:root to 1000:1000
changed ownership of 'svi/python/svi/\_\_init\_\_.py' from root:root to 1000:1000
changed ownership of 'svi/python/svi/svi.py' from root:root to 1000:1000
changed ownership of 'svi/python/svi' from root:root to 1000:1000
changed ownership of 'svi/python' from root:root to 1000:1000
changed ownership of 'svi/src/yang/svi.yang' from root:root to 1000:1000
changed ownership of 'svi/src/yang' from root:root to 1000:1000
changed ownership of 'svi/src/Makefile' from root:root to 1000:1000
changed ownership of 'svi/src' from root:root to 1000:1000
changed ownership of 'svi/test/internal/lux/service/dummy-device.xml' from root:root to 1000:1000
changed ownership of 'svi/test/internal/lux/service/dummy-service.xml' from root:root to 1000:1000
changed ownership of 'svi/test/internal/lux/service/pyvm.xml' from root:root to 1000:1000
changed ownership of 'svi/test/internal/lux/service/run.lux' from root:root to 1000:1000
changed ownership of 'svi/test/internal/lux/service/Makefile' from root:root to 1000:1000
changed ownership of 'svi/test/internal/lux/service' from root:root to 1000:1000
changed ownership of 'svi/test/internal/lux/Makefile' from root:root to 1000:1000
changed ownership of 'svi/test/internal/lux' from root:root to 1000:1000
changed ownership of 'svi/test/internal/Makefile' from root:root to 1000:1000
changed ownership of 'svi/test/internal' from root:root to 1000:1000
changed ownership of 'svi/test/Makefile' from root:root to 1000:1000
changed ownership of 'svi/test' from root:root to 1000:1000
changed ownership of 'svi/package-meta-data.xml' from root:root to 1000:1000
changed ownership of 'svi' from root:root to 1000:1000
root@46a3fd8d7374:/src/packages# 
root@46a3fd8d7374:/src/packages# **exit**    
logout
rst@rst:~/NSO/nso300$ 

Step 7: List the contents of the package.

The package you created exists on your Student-VM machine and is mounted as a volume to the development Docker container.

rst@rst:~/NSO/nso300$ **ls packages/svi**
package-meta-data.xml  python  README  src  templates  test
rst@rst:~/NSO/nso300$ **code packages/svi/package-meta-data.xml**

Step 8: Open and study the package-meta-data.xml of your new service package.

rst@rst:~/NSO/nso300$ code packages/svi/package-meta-data.xml

This is how the `package-meta-data.xml` file appears initially.

    <ncs-package xmlns="http://tail-f.com/ns/ncs-packages">
    <name>svi</name>
    <package-version>1.0</package-version>
    <description>Generated Python package</description>
    <ncs-min-version>5.3</ncs-min-version>
  
    <component>
      <name>svi</name>
      <application>
        <python-class-name>svi.svi.Svi</python-class-name>
      </application>
    </component>
  </ncs-package> 

Step 9: Change the package description and component name to better represent service purpose.

<ncs-package xmlns="http://tail-f.com/ns/ncs-packages">
  <name>svi</name>
  <package-version>1.0</package-version>
  <description>SVI Python and Template Service</description>
  <ncs-min-version>5.3</ncs-min-version>

  <component>
    <name>Switch Virtual Interface</name>
    <application>
      <python-class-name>svi.svi.Svi</python-class-name>
    </application>
  </component>
</ncs-package>

Step 10: Save the file and exit the file editor.

Step 11: Delete the generated XML template file.

rst@rst:~/NSO/nso300$ **rm packages/svi/templates/svi-template.xml**

Step 12: Copy the existing templates from ../packages/svi/templates to packages/svi/templates and verify that you have the correct files.

The files are separated by functionality. One template is for interface addressing, the other for vlan database and switch port configuration.

rst@rst:~/NSO/nso300$ **cp ../packages/svi/templates/svi-\* packages/svi/templates/**
rst@rst:~/NSO/nso300$ **ls packages/svi/templates/**
svi-intf-template.xml  svi-vlan-template.xml

Step 13: Open the svi-intf-template.xml template and add the configuration for enabling the feature interface-vlan.

rst@rst:~/NSO/nso300$ code packages/svi/templates/svi-intf-template.xml 

Step 14: Save the file.

Step 15: Your svi-intf-template-xml should now appear as follows:

<config-template xmlns="http://tail-f.com/ns/config/1.0">
  <devices xmlns="http://tail-f.com/ns/ncs">

    <device>
      <name>{/device/name}</name>
      <config>
        <!-- IOS -->
        <interface xmlns="urn:ios">
          <?if {string(name)=$SVI-DEVICE}?>
          <Vlan>
              <name>{$VLAN-ID}</name>
              <ip>
                <address>
                  <primary>
                    <address>{$IP-ADDR}</address>
                    <mask>{$NETMASK}</mask>
                  </primary>
                </address>
              </ip>
            </Vlan>
            <?end?>
        </interface>
        <!-- NX-OS -->
        **<feature xmlns="http://tail-f.com/ned/cisco-nx">**
        **  <interface-vlan/>**
        **</feature>**
        <interface xmlns="http://tail-f.com/ned/cisco-nx">
          <?if {string(name)=$SVI-DEVICE}?>
          <Vlan>
            <name>{$VLAN-ID}</name>
            <ip>
              <address>
                <ipaddr>{$IP-PREFIX}</ipaddr>
              </address>
            </ip>
          </Vlan>
          <?end?>
        </interface>
      </config>
    </device>
  </devices>
</config-template>

This feature is already enabled if you use an older version of the cisco-nx NED

Step 16: Copy the existing service YANG model to your new service package from the ../packages/svi folder.

This data model has already been created and serves you as base for your new service.

rst@rst:~/NSO/nso300$ **cp ../packages/svi/src/yang/svi.yang packages/svi/src/yang/svi.yang** 

Step 17: Open the service YANG model. This is how the YANG module should initially appear.

Try to reuse old packages as much as you can. Reusing saves you time, but it also provides you with a well-tested and production-proven source.

rst@rst:~/NSO/nso300$ **code packages/svi/src/yang/svi.yang**

module svi {
  namespace "http://example.com/svi";
  prefix svi;

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

  description
    "YANG model for SVI service";

  revision 2020-07-21 {
    description
      "Initial revision.";
  }

  augment "/ncs:services" {

    list svi {
      key "name";

      leaf name {
        tailf:info "Unique service id";
        tailf:cli-allow-range;
        type string;
      }
      uses ncs:service-data;
      ncs:servicepoint "svi-servicepoint";

      leaf vlan-id {
        tailf:info "Unique VLAN ID";
        mandatory true;
        type uint32 {
          range "1..4096";
        }
      }

      list device {
        tailf:info "L3 switch";
        key "name";

        leaf name {
          tailf:info "Device name";
          type leafref {
            path "/ncs:devices/ncs:device/ncs:name";
          }
        }

        leaf ip-prefix {
          tailf:info "Unique IPv4 prefix for VLAN";
          type inet:ip-prefix;
        }

        list interface {
          tailf:info "Ethernet interface";
          key "intf-type intf-id";

          leaf intf-type {
            tailf:info "Ethernet interface type";
            type enumeration {
              enum Ethernet;
              enum FastEthernet;
              enum GigabitEthernet;
            }
          }

          leaf intf-id {
            tailf:info "Ethernet interface ID";
            type string;
          }
        }
      }
    }
  }
}   

Step 18: Restrict device selection to switches only.

This constraint makes sure that your service can only be used on devices with specific names. The device name comes from the device as registered in NSO and is not to beconfused with the device hostname configuration setting.

module svi {
  namespace "http://example.com/svi";
  prefix svi;

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

  description
    "YANG model for SVI service";

  revision 2020-07-21 {
    description
      "Initial revision.";
  }

  augment "/ncs:services" {

    list svi {
      key "name";

      leaf name {
        tailf:info "Unique service id";
        tailf:cli-allow-range;
        type string;
      }
      uses ncs:service-data;
      ncs:servicepoint "svi-servicepoint";

      leaf vlan-id {
        tailf:info "Unique VLAN ID";
        mandatory true;
        type uint32 {
          range "1..4096";
        }
      }

      list device {
        tailf:info "L3 switch";
        key "name";

        leaf name {
          tailf:info "Device name";
          type leafref {
            path "/ncs:devices/ncs:device/ncs:name";
          }
          **must "starts-with(current(),'SW')" {**
          **  error-message "Only SW devices can be selected.";**
          **}**
        }

        leaf ip-prefix {
          tailf:info "Unique IPv4 prefix for VLAN";
          type inet:ip-prefix;
        }

        list interface {
          tailf:info "Ethernet interface";
          key "intf-type intf-id";

          leaf intf-type {
            tailf:info "Ethernet interface type";
            type enumeration {
              enum Ethernet;
              enum FastEthernet;
              enum GigabitEthernet;
            }
          }

          leaf intf-id {
            tailf:info "Ethernet interface ID";
            type string;
          }
        }
      }
    }
  }
}     

Step 19: Create a vlan-id-cnt leaf at the end of the YANG module.

This leaf serves as a counter and is updated only by your Python code inside the pre_modification callback. It always holds the vlan-id number for your next service instance.

module svi {
  namespace "http://example.com/svi";
  prefix svi;

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

  description
    "YANG model for SVI service";

  revision 2020-07-21 {
    description
      "Initial revision.";
  }

  augment "/ncs:services" {

    list svi {
      key "name";

      leaf name {
        tailf:info "Unique service id";
        tailf:cli-allow-range;
        type string;
      }
      uses ncs:service-data;
      ncs:servicepoint "svi-servicepoint";

      leaf vlan-id {
        tailf:info "Unique VLAN ID";
        mandatory true;
        type uint32 {
          range "1..4096";
        }
      }

      list device {
        tailf:info "L3 switch";
        key "name";

        leaf name {
          tailf:info "Device name";
          type leafref {
            path "/ncs:devices/ncs:device/ncs:name";
          }
          must "starts-with(current(),'SW')" {
            error-message "Only SW devices can be selected.";
          }
        }

        leaf ip-prefix {
          tailf:info "Unique IPv4 prefix for VLAN";
          type inet:ip-prefix;
        }

        list interface {
          tailf:info "Ethernet interface";
          key "intf-type intf-id";

          leaf intf-type {
            tailf:info "Ethernet interface type";
            type enumeration {
              enum Ethernet;
              enum FastEthernet;
              enum GigabitEthernet;
            }
          }

          leaf intf-id {
            tailf:info "Ethernet interface ID";
            type string;
          }
        }
      }
    }
  }
  **augment "/ncs:services" {**
  **  leaf vlan-id-cnt {**
  **    description "Provides a unique number used as VLAN identifier";**
  **    tailf:hidden "Counter";**
  **    type uint32 {**
  **      range "2..4096";**
  **    }**
  **    default "2";**
  **  }**
  **}**
}   

Step 20: Look for the leaf node vlan-id and delete it. This leaf is no longer needed since the vlan-id-cnt counter is used as source.

leaf vlan-id {
  tailf:info "Unique VLAN ID";
  mandatory true;
  type uint32 {
    range "1..4096";
  }
}

Step 21: Restrict the leaf ip-prefix to be configured on one of the devices only.

module svi {
  namespace "http://example.com/svi";
  prefix svi;

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

  description
    "YANG model for SVI service";

  revision 2020-07-21 {
    description
      "Initial revision.";
  }

  augment "/ncs:services" {

    list svi {
      key "name";

      leaf name {
        tailf:info "Unique service id";
        tailf:cli-allow-range;
        type string;
      }
      uses ncs:service-data;
      ncs:servicepoint "svi-servicepoint";

      list device {
        tailf:info "L3 switch";
        key "name";

        leaf name {
          tailf:info "Device name";
          type leafref {
            path "/ncs:devices/ncs:device/ncs:name";
          }
          must "starts-with(current(),'SW')" {
            error-message "Only SW devices can be selected.";
          }
        }

        **leaf ip-prefix {**
        **  tailf:info "Unique IPv4 prefix for VLAN. Device with ip-prefix configured will serve as gateway.";**
        **  type inet:ipv4-prefix;**
        **  // Only one device can have ip-prefix configured**
        **  when "count(../../device\[name != current()/../name\]/ip-prefix)=0";**
        **}**

        list interface {
          tailf:info "Ethernet interface";
          key "intf-type intf-id";

          leaf intf-type {
            tailf:info "Ethernet interface type";
            type enumeration {
              enum Ethernet;
              enum FastEthernet;
              enum GigabitEthernet;
            }
          }

          leaf intf-id {
            tailf:info "Ethernet interface ID";
            type string;
          }
        }
      }
    }
  }
  augment "/ncs:services" {
    leaf vlan-id-cnt {
      description "Provides a unique number used as VLAN identifier";
      tailf:hidden "Counter";
      type uint32 {
        range "2..4096";
      }
      default "2";
    }
  }
}   

Step 22: Save the file when finished.

You have completed this task when you attain this result: You successfully modified the YANG service model.

Task 2: Implement the Python Mapping Code

In this task, you will implement some Python code for configuration mapping. Files from an existing service have been provided for you in the $HOME/NSO/packages/svi directory.

NSO will call your pre_modification method before it calls the cb_create method. Therefore, you can prepare and format data.

When the pre_modification returns, your data can be passed to cb_create through the proplist object. proplist is a Python list of tuples, and each tuple consists of two members—a name and a value.

The old service package used the netaddr Python module, which is not a part of the standard library and therefore is not present in any of the containers. You will replace this module with the ipaddress module and make minor adjustments.

Step 1: Copy the existing Python-mapping code.

rst@rst:~/NSO/nso300$ **cp ../packages/svi/python/svi/svi.py packages/svi/python/svi/svi.py**

Step 2: Open Python mapping code for the svi service. You can use an IDE tool or text editor of your choice.

rst@rst:~/NSO/nso300$ **code packages/svi/python/svi/svi.py** 

# -\*- mode: python; python-indent: 4 -\*-
import ncs
from ncs.application import Service
import netaddr


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

    # The create() callback is invoked inside NCS FASTMAP and
    # must always exist.
    @Service.create
    def cb\_create(self, tctx, root, service, proplist):
        self.log.info(f'Service create(service={service.\_path})')
        svi = {'vlan-id': "",
               'svi-device': "",
               'ip-prefix': "",
               'ip-addr': "",
               'netmask': ""}

        svi\['vlan-id'\] = service.vlan\_id

        for device in service.device:
            self.log.info(f'Entering /device list = {device.name}')
            if device.ip\_prefix:
                self.log.info(f'SVI device = {device.name}')

                ip\_net = netaddr.IPNetwork(device.ip\_prefix)
                svi\['svi-device'\] = device.name
                svi\['ip-prefix'\] = f'{ip\_net\[2\]}/{ip\_net.prefixlen}'
                svi\['ip-addr'\] = str(ip\_net\[1\])
                svi\['netmask'\] = str(ip\_net.netmask)

                svi\_tvars = ncs.template.Variables()
                svi\_tvars.add('VLAN-ID', svi\['vlan-id'\])
                svi\_tvars.add('SVI-DEVICE', svi\['svi-device'\])
                svi\_tvars.add('IP-PREFIX', svi\['ip-prefix'\])
                svi\_tvars.add('IP-ADDR', svi\['ip-addr'\])
                svi\_tvars.add('NETMASK', svi\['netmask'\])
                svi\_template = ncs.template.Template(service)
                svi\_template.apply('svi-intf-template', svi\_tvars)

        vlan\_tvars = ncs.template.Variables()
        vlan\_tvars.add('VLAN-ID', svi\['vlan-id'\])
        vlan\_template = ncs.template.Template(service)
        vlan\_template.apply('svi-vlan-template', vlan\_tvars)

    # The pre\_modification() and post\_modification() callbacks are optional,
    # and are invoked outside FASTMAP. pre\_modification() is invoked before
    # create, update, or delete of the service, as indicated by the enum
    # ncs\_service\_operation op parameter. Conversely
    # post\_modification() is invoked after create, update, or delete
    # of the service. These functions can be useful e.g. for
    # allocations that should be stored and existing also when the
    # service instance is removed.

    # @Service.pre\_lock\_create
    # def cb\_pre\_lock\_create(self, tctx, root, service, proplist):
    #     self.log.info('Service plcreate(service=', service.\_path, ')')

    # @Service.pre\_modification
    # def cb\_pre\_modification(self, tctx, op, kp, root, proplist):
    #     self.log.info('Service premod(service=', kp, ')')

    # @Service.post\_modification
    # def cb\_post\_modification(self, tctx, op, kp, root, proplist):
    #     self.log.info('Service premod(service=', kp, ')')


# ---------------------------------------------
# COMPONENT THREAD THAT WILL BE STARTED BY NCS.
# ---------------------------------------------
class Svi(ncs.application.Application):
    def setup(self):
        # The application class sets up logging for us. It is accessible
        # through 'self.log' and is a ncs.log.Log instance.
        self.log.info('Main RUNNING')

        # Service callbacks require a registration for a 'service point',
        # as specified in the corresponding data model.
        #
        self.register\_service('svi-servicepoint', ServiceCallbacks)

        # If we registered any callback(s) above, the Application class
        # took care of creating a daemon (related to the service/action point).

        # When this setup method is finished, all registrations are
        # considered done and the application is 'started'.

    def teardown(self):
        # When the application is finished (which would happen if NCS went
        # down, packages were reloaded or some error occurred) this teardown
        # method will be called.

        self.log.info('Main FINISHED')

Step 3: Delete all the comments to improve readability. You can keep the pre_modification definition and decorator.

\# -\*- mode: python; python-indent: 4 -\*-
import ncs
from ncs.application import Service
import netaddr


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

    @Service.create
    def cb\_create(self, tctx, root, service, proplist):
        self.log.info(f'Service create(service={service.\_path})')
        svi = {'vlan-id': "",
               'svi-device': "",
               'ip-prefix': "",
               'ip-addr': "",
               'netmask': ""}

        svi\['vlan-id'\] = service.vlan\_id

        for device in service.device:
            self.log.info(f'Entering /device list = {device.name}')
            if device.ip\_prefix:
                self.log.info(f'SVI device = {device.name}')

                ip\_net = netaddr.IPNetwork(device.ip\_prefix)
                svi\['svi-device'\] = device.name
                svi\['ip-prefix'\] = f'{ip\_net\[2\]}/{ip\_net.prefixlen}'
                svi\['ip-addr'\] = str(ip\_net\[1\])
                svi\['netmask'\] = str(ip\_net.netmask)

                svi\_tvars = ncs.template.Variables()
                svi\_tvars.add('VLAN-ID', svi\['vlan-id'\])
                svi\_tvars.add('SVI-DEVICE', svi\['svi-device'\])
                svi\_tvars.add('IP-PREFIX', svi\['ip-prefix'\])
                svi\_tvars.add('IP-ADDR', svi\['ip-addr'\])
                svi\_tvars.add('NETMASK', svi\['netmask'\])
                svi\_template = ncs.template.Template(service)
                svi\_template.apply('svi-intf-template', svi\_tvars)

        vlan\_tvars = ncs.template.Variables()
        vlan\_tvars.add('VLAN-ID', svi\['vlan-id'\])
        vlan\_template = ncs.template.Template(service)
        vlan\_template.apply('svi-vlan-template', vlan\_tvars)

    # @Service.pre\_modification
    # def cb\_pre\_modification(self, tctx, op, kp, root, proplist):
    #     self.log.info('Service premod(service=', kp, ')')

# ---------------------------------------------
# COMPONENT THREAD THAT WILL BE STARTED BY NCS.
# ---------------------------------------------
class Svi(ncs.application.Application):
    def setup(self):
        self.log.info('Main RUNNING')
        self.register\_service('svi-servicepoint', ServiceCallbacks)

    def teardown(self):self.log.info('Main FINISHED')

Step 4: Implement the pre_modification callback method. If this is a service creation, then assign the value vlan-id-cnt to a local variable vlan_id, increment the counter by one, and append it to the proplist variable before returning.

\# -\*- mode: python; python-indent: 4 -\*-
import ncs
from ncs.application import Service
import netaddr


# ------------------------
# SERVICE CALLBACK EXAMPLE
# ------------------------
class ServiceCallbacks(Service):
    @Service.create
    def cb\_create(self, tctx, root, service, proplist):
        self.log.info(f"Service create(service={service.\_path})")
        svi = {
            "vlan-id": "",
            "svi-device": "",
            "ip-prefix": "",
            "ip-addr": "",
            "netmask": "",
        }

        svi\["vlan-id"\] = service.vlan\_id

        for device in service.device:
            self.log.info(f"Entering /device list = {device.name}")
            if device.ip\_prefix:
                self.log.info(f"SVI device = {device.name}")

                ip\_net = netaddr.IPNetwork(device.ip\_prefix)
                svi\["svi-device"\] = device.name
                svi\["ip-prefix"\] = f"{ip\_net\[2\]}/{ip\_net.prefixlen}"
                svi\["ip-addr"\] = str(ip\_net\[1\])
                svi\["netmask"\] = str(ip\_net.netmask)

                svi\_tvars = ncs.template.Variables()
                svi\_tvars.add("VLAN-ID", svi\["vlan-id"\])
                svi\_tvars.add("SVI-DEVICE", svi\["svi-device"\])
                svi\_tvars.add("IP-PREFIX", svi\["ip-prefix"\])
                svi\_tvars.add("IP-ADDR", svi\["ip-addr"\])
                svi\_tvars.add("NETMASK", svi\["netmask"\])
                svi\_template = ncs.template.Template(service)
                svi\_template.apply("svi-intf-template", svi\_tvars)

        vlan\_tvars = ncs.template.Variables()
        vlan\_tvars.add("VLAN-ID", svi\["vlan-id"\])
        vlan\_template = ncs.template.Template(service)
        vlan\_template.apply("svi-vlan-template", vlan\_tvars)

    **@Service.pre\_modification**
    **def cb\_pre\_modification(self, tctx, op, kp, root, proplist):**
    **    self.log.info("Service premod(service=", kp, ")")**
    **    if op == ncs.dp.NCS\_SERVICE\_CREATE:**
    **        self.log.info("Service premod(operation=NCS\_SERVICE\_CREATE, allocate)")**
    **        vlan\_id = root.services.vlan\_id\_cnt**
    **        proplist.append(("vlan-id", str(vlan\_id)))**
    **        self.log.info(f"Service premod(allocated vlan-id: {vlan\_id})")**
    **        root.services.vlan\_id\_cnt = vlan\_id + 1**
    **    elif op == ncs.dp.NCS\_SERVICE\_DELETE:**
    **        self.log.info("Service premod(operation=NCS\_SERVICE\_DELETE, skil)")**

    **    return proplist**

# ---------------------------------------------
# COMPONENT THREAD THAT WILL BE STARTED BY NCS.
# ---------------------------------------------
class Svi(ncs.application.Application):
    def setup(self):
        self.log.info("Main RUNNING")
        self.register\_service("svi-servicepoint", ServiceCallbacks)

    def teardown(self):
        self.log.info("Main FINISHED")

Step 5: Remove the following line from the cb_create method because the vlan-id will be passed through the proplist object.

svi\['vlan-id'\] = service.vlan\_id

Step 6: Update the cb_create code. Retrieve the vlan-id as returned from the pre_modification callback inside the proplist object.

\# -\*- mode: python; python-indent: 4 -\*-
import ncs
from ncs.application import Service
import netaddr


# ------------------------
# SERVICE CALLBACK EXAMPLE
# ------------------------
class ServiceCallbacks(Service):
    @Service.create
    def cb\_create(self, tctx, root, service, proplist):
        self.log.info(f"Service create(service={service.\_path})")
        svi = {
            "vlan-id": "",
            "svi-device": "",
            "ip-prefix": "",
            "ip-addr": "",
            "netmask": "",
        }

        **\# proplist object list(tuple(str,str)) to pass information between invocations. We set**
        **\# value for vlan\_id in cb\_pre\_modification and use it here in cb\_create.**
        **svi\["vlan-id"\] = \[x\[1\] for x in proplist if x\[0\] == "vlan-id"\]\[0\]**

        for device in service.device:
            self.log.info(f"Entering /device list = {device.name}")
            if device.ip\_prefix:
                self.log.info(f"SVI device = {device.name}")

                ip\_net = netaddr.IPNetwork(device.ip\_prefix)
                svi\["svi-device"\] = device.name
                svi\["ip-prefix"\] = f"{ip\_net\[2\]}/{ip\_net.prefixlen}"
                svi\["ip-addr"\] = str(ip\_net\[1\])
                svi\["netmask"\] = str(ip\_net.netmask)

                svi\_tvars = ncs.template.Variables()
                svi\_tvars.add("VLAN-ID", svi\["vlan-id"\])
                svi\_tvars.add("SVI-DEVICE", svi\["svi-device"\])
                svi\_tvars.add("IP-PREFIX", svi\["ip-prefix"\])
                svi\_tvars.add("IP-ADDR", svi\["ip-addr"\])
                svi\_tvars.add("NETMASK", svi\["netmask"\])
                svi\_template = ncs.template.Template(service)
                svi\_template.apply("svi-intf-template", svi\_tvars)

        vlan\_tvars = ncs.template.Variables()
        vlan\_tvars.add("VLAN-ID", svi\["vlan-id"\])
        vlan\_template = ncs.template.Template(service)
        vlan\_template.apply("svi-vlan-template", vlan\_tvars)

    @Service.pre\_modification
    def cb\_pre\_modification(self, tctx, op, kp, root, proplist):
        self.log.info("Service premod(service=", kp, ")")
        if op == ncs.dp.NCS\_SERVICE\_CREATE:
            self.log.info("Service premod(operation=NCS\_SERVICE\_CREATE, allocate)")
            vlan\_id = root.services.vlan\_id\_cnt
            proplist.append(("vlan-id", str(vlan\_id)))
            self.log.info(f"Service premod(allocated vlan-id: {vlan\_id})")
            root.services.vlan\_id\_cnt = vlan\_id + 1
        elif op == ncs.dp.NCS\_SERVICE\_DELETE:
            self.log.info("Service premod(operation=NCS\_SERVICE\_DELETE, skil)")

        return proplist


# ---------------------------------------------
# COMPONENT THREAD THAT WILL BE STARTED BY NCS.
# ---------------------------------------------
class Svi(ncs.application.Application):
    def setup(self):
        self.log.info("Main RUNNING")
        self.register\_service("svi-servicepoint", ServiceCallbacks)

    def teardown(self):
        self.log.info("Main FINISHED")

Step 7: Replace the Python netaddr module with the ipaddress module, and IPNetwork call with ip_network. The Python netaddr module is not a part of Python’s standard library and does not exist inside the container.

\# -\*- mode: python; python-indent: 4 -\*-
import ncs
from ncs.application import Service
**import ipaddress**

# ------------------------
# SERVICE CALLBACK EXAMPLE
# ------------------------
class ServiceCallbacks(Service):
    @Service.create
    def cb\_create(self, tctx, root, service, proplist):
        self.log.info(f"Service create(service={service.\_path})")
        svi = {
            "vlan-id": "",
            "svi-device": "",
            "ip-prefix": "",
            "ip-addr": "",
            "netmask": "",
        }

        # proplist object list(tuple(str,str)) to pass information between invocations. We set
        # value for vlan\_id in cb\_pre\_modification and use it here in cb\_create.
        svi\["vlan-id"\] = \[x\[1\] for x in proplist if x\[0\] == "vlan-id"\]\[0\]

        for device in service.device:
            self.log.info(f"Entering /device list = {device.name}")
            if device.ip\_prefix:
                self.log.info(f"SVI device = {device.name}")

                **ip\_net = ipaddress.ip\_network(device.ip\_prefix)**
                svi\["svi-device"\] = device.name
                svi\["ip-prefix"\] = f"{ip\_net\[2\]}/{ip\_net.prefixlen}"
                svi\["ip-addr"\] = str(ip\_net\[1\])
                svi\["netmask"\] = str(ip\_net.netmask)

                svi\_tvars = ncs.template.Variables()
                svi\_tvars.add("VLAN-ID", svi\["vlan-id"\])
                svi\_tvars.add("SVI-DEVICE", svi\["svi-device"\])
                svi\_tvars.add("IP-PREFIX", svi\["ip-prefix"\])
                svi\_tvars.add("IP-ADDR", svi\["ip-addr"\])
                svi\_tvars.add("NETMASK", svi\["netmask"\])
                svi\_template = ncs.template.Template(service)
                svi\_template.apply("svi-intf-template", svi\_tvars)

        vlan\_tvars = ncs.template.Variables()
        vlan\_tvars.add("VLAN-ID", svi\["vlan-id"\])
        vlan\_template = ncs.template.Template(service)
        vlan\_template.apply("svi-vlan-template", vlan\_tvars)

    @Service.pre\_modification
    def cb\_pre\_modification(self, tctx, op, kp, root, proplist):
        self.log.info("Service premod(service=", kp, ")")
        if op == ncs.dp.NCS\_SERVICE\_CREATE:
            self.log.info("Service premod(operation=NCS\_SERVICE\_CREATE, allocate)")
            vlan\_id = root.services.vlan\_id\_cnt
            proplist.append(("vlan-id", str(vlan\_id)))
            self.log.info(f"Service premod(allocated vlan-id: {vlan\_id})")
            root.services.vlan\_id\_cnt = vlan\_id + 1
        elif op == ncs.dp.NCS\_SERVICE\_DELETE:
            self.log.info("Service premod(operation=NCS\_SERVICE\_DELETE, skil)")

        return proplist


# ---------------------------------------------
# COMPONENT THREAD THAT WILL BE STARTED BY NCS.
# ---------------------------------------------
class Svi(ncs.application.Application):
    def setup(self):
        self.log.info("Main RUNNING")
        self.register\_service("svi-servicepoint", ServiceCallbacks)

    def teardown(self):
        self.log.info("Main FINISHED")

Step 8: Save the file when finished.

You have successfully modified the Python mapping code.

Task 3: Compile and Deploy the Service

In this task, you will compile and deploy the service package created in the previous task. But before doing so, you will create a new target for pylint.

Before data model compilation, your Python code should be checked for errors. You do this action by running pylint on every .py file found in your package.

After a successful pylint run, your data model must be checked and compiled into binary form. This is achieved by running the NSO compiler. For every .yang file found in your package, the compiler will output a corresponding .fxs file. These files are compiled versions of your text data models

All this work is abstracted from you in the form of Makefile targets. Running the make testenv-build command will progress to the next target only if there are no errors in the current target.

Only after a successful compilation will the package be complete and ready for deployment. This step is also the last Makefile target.

If there is an error in any of the targets, simply identify the cause, make corrections, and try to build again.

Step 1: Open the Makefile with a text editor of your choice.

rst@rst:~/NSO/nso300$ **code packages/svi/src/Makefile**

Step 2: Edit the first line at the very top and add pylint at the end.

**all: fxs pylint**
.PHONY: all

Step 3: Scroll down and add a pylint target declaration. Make sure you use TAB for indentation.

NCSCPATH   = $(YANGPATH:%=--yangpath %)
YANGERPATH = $(YANGPATH:%=--path %)

pylint:
	pylint --disable=R,C --reports=n ../python/svi/\*.py || (test $$? -ge 4)

fxs: $(DIRS) $(FXS)

### OUTPUT OMITTED ###

Step 4: Save changes and exit the text editor.

Step 5: Compile the package. Use the make testenv-build command. This command recompiles and reloads or redeploys the packages, used by the Docker NSO containers.

Inspect the output and make sure that no errors are present.

Notice the pylint lines in the output. Linter checks your code for errors and provides a score, which at this point should be of no concern.

rst@rst:~/NSO/nso300$ **make testenv-build** 
for NSO in $(docker ps --format '{{.Names}}' --filter label=com.cisco.nso.testenv.name=testenv-nso300-5.3-rst --filter label=com.cisco.nso.testenv.type=nso); do \\
	echo "-- Rebuilding for NSO: ${NSO}"; \\
	docker run -it --rm -v /home/rst/NSO/nso300:/src --volumes-from ${NSO} --network=container:${NSO} -e NSO=${NSO} -e PACKAGE\_RELOAD= -e SKIP\_LINT= -e PKG\_FILE=nso300.gitlab.local/nso300/package:5.3-rst nso300.gitlab.local/cisco-nso-dev:5.3 /src/nid/testenv-build; \\
done
-- Rebuilding for NSO: testenv-nso300-5.3-rst-nso
(^package-meta-data.xml$|\\.cli$|\\.yang$)
make: Entering directory '/var/opt/ncs/packages/svi/src'
/opt/ncs/ncs-5.3/bin/ncsc  \`ls svi-ann.yang  > /dev/null 2>&1 && echo "-a svi-ann.yang"\` \\
              -c -o ../load-dir/svi.fxs yang/svi.yang
**pylint --disable=R,C --reports=n ../python/svi/\*.py || (test $? -ge 4)**
\*\*\*\*\*\*\*\*\*\*\*\*\* Module svi.svi
/var/opt/ncs/packages/svi/python/svi/svi.py:17:48: W0212: Access to a protected member \_path of a client class (protected-access)

-----------------------------------
Your code has been rated at 9.78/10

make: Leaving directory '/var/opt/ncs/packages/svi/src'
make: Entering directory '/src/packages/svi'
if \[ ! -f build-meta-data.xml \]; then \\
	export PKG\_NAME=$(xmlstarlet sel -N x=http://tail-f.com/ns/ncs-packages -t -v '/x:ncs-package/x:name' $(ls package-meta-data.xml src/package-meta-data.xml.in 2>/dev/null | head -n 1)); \\
	export PKG\_VERSION=$(xmlstarlet sel -N x=http://tail-f.com/ns/ncs-packages -t -v '/x:ncs-package/x:package-version' $(ls package-meta-data.xml src/package-meta-data.xml.in 2>/dev/null | head -n 1)); \\
	eval "cat <<< \\"$(</src/nid/build-meta-data.xml)\\"" > /var/opt/ncs/packages/svi//build-meta-data.xml; fi
fatal: not a git repository (or any parent up to mount point /)
Stopping at filesystem boundary (GIT\_DISCOVERY\_ACROSS\_FILESYSTEM not set).
fatal: not a git repository (or any parent up to mount point /)
Stopping at filesystem boundary (GIT\_DISCOVERY\_ACROSS\_FILESYSTEM not set).
make: Leaving directory '/src/packages/svi'
make: Entering directory '/var/opt/ncs/packages/hostname/src'
make: Nothing to be done for 'all'.
make: Leaving directory '/var/opt/ncs/packages/hostname/src'
make: Entering directory '/src/packages/hostname'
if \[ ! -f build-meta-data.xml \]; then \\
	export PKG\_NAME=$(xmlstarlet sel -N x=http://tail-f.com/ns/ncs-packages -t -v '/x:ncs-package/x:name' $(ls package-meta-data.xml src/package-meta-data.xml.in 2>/dev/null | head -n 1)); \\
	export PKG\_VERSION=$(xmlstarlet sel -N x=http://tail-f.com/ns/ncs-packages -t -v '/x:ncs-package/x:package-version' $(ls package-meta-data.xml src/package-meta-data.xml.in 2>/dev/null | head -n 1)); \\
	eval "cat <<< \\"$(</src/nid/build-meta-data.xml)\\"" > /var/opt/ncs/packages/hostname//build-meta-data.xml; fi
fatal: not a git repository (or any parent up to mount point /)
Stopping at filesystem boundary (GIT\_DISCOVERY\_ACROSS\_FILESYSTEM not set).
fatal: not a git repository (or any parent up to mount point /)
Stopping at filesystem boundary (GIT\_DISCOVERY\_ACROSS\_FILESYSTEM not set).
make: Leaving directory '/src/packages/hostname'
**\-- Reloading packages for NSO testenv-nso300-5.3-rst-nso**

>>> 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 cisco-asa-cli-6.7
    result true
}
reload-result {
    package cisco-ios-cli-6.42
    result true
}
reload-result {
    package cisco-iosxr-cli-7.18
    result true
}
reload-result {
    package cisco-nx-cli-5.13
    result true
}
reload-result {
    package hostname
    result true
}
**reload-result {**
    **package svi**
    **result true**
**}**
rst@rst:~/NSO/nso300$ 

Step 6: Connect to the NSO CLI and switch to the Cisco mode.

rst@rst:~/NSO/nso300$ **make testenv-cli**
docker exec -it testenv-nso300-5.3-rst-nso bash -lc 'ncs\_cli -u admin'

admin connected from 127.0.0.1 using console on 83dc28b9733d
admin@ncs> 
admin@ncs> **switch cli**
admin@ncs# 

Step 7: Configure the service instance.

admin@ncs# **config**
Entering configuration mode terminal
admin@ncs(config)# **services svi ACME**
admin@ncs(config-svi-ACME)# **device SW31** 
admin@ncs(config-device-SW31)# **ip-prefix 10.10.0.0/16**
admin@ncs(config-device-SW31)# **interface GigabitEthernet 0/1**
admin@ncs(config-interface-GigabitEthernet/0/1)# **exit**
admin@ncs(config-device-SW31)# **exit**
admin@ncs(config-svi-ACME)# **device SW32** 
admin@ncs(config-device-SW32)# **interface Ethernet 0/2**
admin@ncs(config-interface-Ethernet/0/2)# **top**
admin@ncs(config)# **show configuration** 
services svi ACME
 device SW31
  ip-prefix 10.10.0.0/16
  interface GigabitEthernet 0/1
  !
 !
 device SW32
  interface Ethernet 0/2
  !
 !
!
admin@ncs(config)#    

Step 8: Review the configuration that will be created using the **`commit dry-run`** command.

admin@ncs(config)# **commit dry-run**
cli {
    local-node {
        data  devices {
                  device SW31 {
                      config {
                          vlan {
             +                vlan-list 2 {
             +                }
                          }
                          interface {
                              GigabitEthernet 0/1 {
             +                    switchport {
             +                        mode {
             +                            access {
             +                            }
             +                        }
             +                        access {
             +                            vlan 2;
             +                        }
             +                    }
                                  ip {
                                      no-address {
             -                            address false;
                                      }
                                  }
                              }
             +                Vlan 2 {
             +                    ip {
             +                        address {
             +                            primary {
             +                                address 10.10.0.1;
             +                                mask 255.255.0.0;
             +                            }
             +                        }
             +                    }
             +                }
                          }
                      }
                  }
                  device SW32 {
                      config {
                          feature {
             +                interface-vlan;
                          }
                          vlan {
             +                vlan-list 2 {
             +                }
                          }
                          interface {
             +                Ethernet 0/2 {
             +                    switchport {
             +                        mode access;
             +                        access {
             +                            vlan 2;
             +                        }
             +                    }
             +                }
                          }
                      }
                  }
              }
              services {
             +    svi ACME {
             +        device SW31 {
             +            ip-prefix 10.10.0.0/16;
             +            interface GigabitEthernet 0/1;
             +        }
             +        device SW32 {
             +            interface Ethernet 0/2;
             +        }
             +    }
              }
    }
}

Step 9: Commit the configuration to deploy the service. Your service should be configured with vlan-id 2.

admin@ncs(config)# **commit**
Commit complete.

Step 10: Exit the privileged mode and exit the NSO CLI.

admin@ncs(config)# **exit**
admin@ncs# **exit**
rst@rst:~/NSO/nso300$ 

You have successfully deployed a service instance.