Cisco Network Services Orchestrator

Recently, I worked on a design task that heavily focused on automated service orchestration in service provider networks. During this, I dived into the Cisco Network Services Orchestrator (NSO), which originally was developed by a company called Tail-f and later acquired by Cisco.

NSO is a beast of a tool, or rather a platform, through which many tasks can be automated. This could be tasks such as service provisioning, compliance checking and configuration migration just to mention a few.

The platform builds around YANG modelling and uses NETCONF or CLI – through specific network element drivers (NEDs) – as interfaces towards the network. Services, or templates, are built using the XML-framework and XPATH for referencing elements defined in the service YANG model. General programming logic (foreach, if/else, etc) can be used in the services template, alongside built-in XPATH functions, JAVA or Python elements.

The NSO comes with a pre-built webUI, but custom-built UIs can be developed for the services defined, making the platform very versatile but also a massive task to start up with.

Although I do have some experience with programming (C++, Java, Perl/PHP, Python, VB and Javascripting), I’m still very much a novice in this field and the learning curve when starting out with NSO is very steep.

First thing to understand was the whole purpose of YANG. YANG is a modelling language used to define and validate ‘things’ – In the case of NSO, YANG is vastly used for defining the input model for a service, which eventually is expressed by a range of input fields in the webIU (this was the point where I began to find NSO and YANG being quite interesting).

I’m not going to explain YANG – many people understand this better than I and have explained it in an understandable manner. Instead I’ll show an example of a service model (in this case and l3vpn), which was one of the things I found hard to find initially:

module l3vpn {
 namespace "http://com/example/l3vpn";
 prefix l3vpn;
 
 import ietf-inet-types {
  prefix inet;
 }
 import tailf-ncs {
  prefix ncs;
 }
 import tailf-common {
  prefix tailf;
 }
 import tailf-ned-cisco-ios {
  prefix ios;
 }
 import tailf-ned-cisco-ios-xr {
  prefix cisco-ios-xr;
 }
 augment "/ncs:services" {
  list l3vpn {
   key "name";
   unique "vpn-id";
    
   uses ncs:service-data;
   ncs:servicepoint "l3vpn";
 
   leaf name {
    tailf:info "VRF Name";
    mandatory true;
    type string;
   }
   leaf customer {
    tailf:info "Customer name";
    type leafref {
     path "/ncs:customers/ncs:customer/ncs:id";
    }
   }
   leaf vpn-id {
    tailf:info "Unique VPN ID, used for RD/RT";
    mandatory true;
    type uint32 {
     range "1..65535";
    }
   }
   list link {
    tailf:info "Attachment Circuits to VPN";
 
    key "connection-name";
    unique "connection-name";
 
    leaf connection-name {
     tailf:info "MPLS connection ID - ie. CONNECTION_ID_1024";
     mandatory true;
     type string;
    }
    leaf device {
     tailf:info "PE Router";
     mandatory true;
     type leafref {
      path "/ncs:devices/ncs:device/ncs:name";
     }
    }
    leaf link-vlan {
     tailf:info "Service VLAN";
     mandatory true;
     type int32 {
      range "1..4000";
     }
    }
    leaf link-ipv4 {
     tailf:info "PE IPv4 link address";
     mandatory true;
     type inet:ipv4-address;
    }
    leaf link-ipv4-subnetmask {
     tailf:info "PE IPv4 link subnetmask";
     mandatory true;
     type inet:ipv4-address;
    }
    container ios {
     when "/ncs:devices/ncs:device[ncs:name=current()/../device]/ncs:device-type/ncs:cli/ncs:ned-id='ios-id:cisco-ios'"      {
      tailf:dependency "../device";
      tailf:dependency "/ncs:devices/ncs:device/ncs:device-type";
     }
     choice interface-type {
      case GigabitEthernet {
       leaf ios-GigabitEthernet {
        type leafref {
         path "deref(../../device)/../ncs:config/ios:interface/ios:GigabitEthernet/ios:name";
        }
         must "(not (starts-with(deref(current())/../ios:description,'CORE')))" {
          tailf:dependency "/ncs:devices/ncs:device/ncs:config/ios:interface/ios:GigabitEthernet/ios:description";
         }
        }
       }
      }
     }
    container iosxr {
     when "/ncs:devices/ncs:device[ncs:name=current()/../device]/ncs:device-type/ncs:cli/ncs:ned-id='cisco-ios-xr-id:cisco-ios-xr'" {
      tailf:dependency "../device";
      tailf:dependency "/ncs:devices/ncs:device/ncs:device-type";
     }
     choice interface-type {
      case GigabitEthernet {
       leaf xr-GigabitEthernet {
        type leafref {
         path "deref(../../device)/../ncs:config/cisco-ios-xr:interface/cisco-ios-xr:GigabitEthernet/cisco-ios-xr:id";
        }
        must "(not (starts-with(deref(current())/../cisco-ios-xr:description,'CORE')))" {
         tailf:dependency "/ncs:devices/ncs:device/ncs:config/cisco-ios-xr:interface/cisco-ios-xr:GigabitEthernet/cisco-ios-xr:description";
        }
       }
      }
      case TenGigabitEthernet {
       leaf xr-TenGigabitEthernet {
        type leafref {
         path "deref(../../device)/../ncs:config/cisco-ios-xr:interface/cisco-ios-xr:TenGigE/cisco-ios-xr:id";
        }
        must "(not (starts-with(deref(current())/../cisco-ios-xr:description,'CORE')))" {
         tailf:dependency "/ncs:devices/ncs:device/ncs:config/cisco-ios-xr:interface/cisco-ios-xr:TenGigE/cisco-ios-xr:description";
        }
       }
      }
     }
    }
    leaf routing-protocol {
     tailf:info "Routing option for the PE-CE link";
     type enumeration {
      enum "bgp";
      enum "static";
      enum "none";
     }
    }
    container bgp {
     when "../routing-protocol = 'bgp'";
     leaf customer_as {
      tailf:info "Customer BGP AS number (range 1 - 65534)";
      type uint32;
      mandatory true;
     }
     leaf customer_ip {
      tailf:info "Customer IP address";
      type inet:ipv4-address;
      mandatory true;
     }
     leaf bgp_password {
      tailf:info "Password for BGP session";
      type string;
     }
     leaf max_prefix {
      tailf:info "Maximum number of BGP prefixes allowed";
      type enumeration {
       enum 10;
       enum 50;
       enum 100;
       enum 500;
       enum 1000;
      }
     }
    }
    container static {
     when "../routing-protocol = 'static'";
     list statics {
      key "prefix";
      leaf prefix {
       type string;
      }
      leaf next-hop {
       type inet:ipv4-address;
      }
     }
    }
   }
  }
 }
}

Most valuable part of the example above, that took some time to dig up, was the leafref that allows you to reference ie. routers (path “/ncs:devices/ncs:device/ncs:name”;) in the NCS inventory. Also conditions was quite cools; ie. the ‘when’ statement that evaluates others leafs (when “/ncs:devices/ncs:device[ncs:name=current()/../device]/ncs:device-type/ncs:cli/ncs:ned-id=’ios-id:cisco-ios'” or when “../routing-protocol = ‘bgp'”). Important points to note is that the references uses a directory-style approach for referencing elements in the in the YANG tree.

Constraints can also be set in the YANG model. In the example above, I used a constraint that doesn’t allow the service to be deployed on interfaces on which the description starts with the word “CORE” (must “(not (starts-with(deref(current())/../cisco-ios-xr:description,’CORE’)))”