Modules

This file is based on the Youtube video https://www.youtube.com/watch?v=AdIcYrz3AjU&t=1854s

Below is a sample YANG file generated by the ncs-make-package --service-skeleton template ospf_deploy . We will use this for our learnign and understanding for the YANG file and so the same based on real life YANG file examples.

Notes are added as comments in the file below


module ospf_deploy {
  namespace "http://com/example/ospf_deploy";
  prefix ospf_deploy;

  import ietf-inet-types {
    prefix inet;
  }
  import tailf-ncs {
    prefix ncs;
  }
  organisation "ACME Inc";
  revision 2007-01-02 {
    description "Second version v2";
  }
}

Note above that each module begins with the module declaration , the filename should equal the module name.Each module is uniqiely identified in the system with the namespace prefix below is how the namespace will be refrenced in the file goign forward. Something like ospf_deploy: followed by something.

Note that YANG is XML Definition langauage and maps one to one with XML.

The import and include add other modules to the YANG model.

Using the revision ifnormation in , when workign with NETCONF ,it will advertise the version wehich will make the managment device know waht s the device caapable of .

import A yang imports is similar to including a Header file in a C Code.

include Include statement is used to pull submodules into a main. A module doees not have to be contained within one file. You can decompose it for ease of maintenance design.

submodules

A submodule is a written in a separate acme-system.yang file and does not have a namespace of its own. (Notice the same in the picture below)

THe submodule is included in the parent module ,

so paremt module can refer –> to the submodule but submodule cannot refere to items in the parent module .

YANG Base Types

Typedef

Below is the a Typedef defined for percent . The leaf completed inherits the typedef percent.

typedef percent {
  type unit16 {
    range "0 .. 100"
  }
  description "Percentage";
}

leaf completed {
  type percent;
}
Type Restrictions

Notice below how Restrictions are applied on derived-int32 .

typedef my-base-int32-type {
  type int32 {
    range "1..4 | 10..20"  # 1 to 4 and 10 to 20
  }
}

typedef derived-int32 {
  type my-base-int32-type {
    range "11..max";   # Derived from the typedef above but is only limited to 11 to 20 .
  }
}

Union Statement
typedef threshold {
  type union {
    type uint16 {
      range "0 .. 100";
    }
    type enumeration {
      enum disabled {
        description "No threshold";
      }
    }
  }
}

Youtube 12:33

Common YANG types

Common Networking Data types are stores in ietf-yang-types (RFC 6021) like the following. These can be added to the your YANG file using the following.

import `ietf-yang-types` {
  perfix yang
}

leaf remote-ip {
  type yang:ipv4-address {  # Here we refer the ietf yang type for IPv4 address.
    pattern "10\\.0\\.0\\.[0-9]+";
  }

Grouping Statement

Grouping can contain any YANG structure (leafs or containers etc) . In this example we group

module ospf_deploy {
  namespace "http://com/example/ospf_deploy";
  prefix ospf_deploy;

  import "ietf-inet-types" {
    prefix inet;
  }

  grouping target {
    leaf address {
      type inet:ip-address;
      description "Target IP";
    }
    leaf port {
      type inet:port-number;
      description "Target port";
    }
  }

    container peer {
      container destination {
        uses target;
      }
    }

  }

Notice that when you view the actual output of the code above , it lists the container structure starting with peer , destination and then adds the target group to it .

#pyang -f tree ospf_deploy.yang module: ospf_deploy
      +--rw peer
         +--rw destination
            +--rw address?   inet:ip-address
            +--rw port?      inet:port-number

Grouping Statement with Refine

So with refine we further take the above example and refine the grouping with some contstraints or defaults. In the example below we set the default value to 80.

grouping target {
  leaf address {
    type inet:ip-address;
    description "Target IP";
  }
  leaf port {
    type inet:port-number;
    description "Target port";
  }
}

  container peer {
    container destination {
      uses target {
        refine port {
          default 80;
        }
      }
    }
  }

YANG Data Definitions

Leaf Statement

A leaf is a single item and can have multiple attributes.

leaf host-name {
  type string;
  mandatory true;
  config true;

}

Container Statement

A container is used to organise the leafs in a structure. It does not have type of its own.

container system {
  containers services {
    container ssh {
      presence "Enables SSH"
      description "SSH Service Specific configuration"
    }
  }
}

Leaf-list Statement

Its is a list of items . Do not see this as an array .

leaf-list domains-search {
  type string;
  ordered-by user; # How the list is ordered.
  description "List of domain names to search";
}

List Statements

Think of Lists as a Table of Items , key is the key of the data table.

Attributes of list an leaf-lists

Keys

The key field is used to specific which row are we reffering to .

Multiple Keys

Notice in the example below we have the key "ip prefix" allowing us to select based on two keys , IP and Prefix.

Leafref

A Leafref can refer to another leaf . So basically what it means is , the only calues can be selected are the values the Leafref is poiting to .

Multiple Key Leafref

In the example below , a give set of IP and Port is to be selected from the client table. Now selecting the Ip Address is easy , but there are duplicate IP Addresses .

Having the Xpath of ip=current() helps us go back in the tree and ensure integrity by limiting the scope to the current v-ip in question .

Deref() XPATH Operator

Now looking at the example above of Leafref , if the number of keys increases (v-ip , v-port …. and v-stream) it will get convuluted in the nesting of the current pointer .

This is made easy by the deref() operator.

http://www.yang-central.org/twiki/pub/Main/YangTools/pyang.1.html

The deref function follows the reference defined by the first node in document order in the argument node-set, and returns the nodes it refers to.

If the first argument node is an instance-identifier, the function returns a node-set that contains the single node that the instance identifier refers to, if it exists. If no such node exists, an empty node-set is returned.

If the first argument node is a leafref, the function returns a node-set that contains the nodes that the leafref refers to.

If the first argument node is of any other type, an empty node-set is returned.

The following example shows how a leafref can be written with and without the deref function:

Without Deref



leaf my-ip {
  type leafref {
    path "/server/ip";
  }
}
leaf my-port {
  type leafref {
    path "/server[ip = current()/../my-ip]/port";
  }
}

After Deref



leaf my-ip {
  type leafref {
    path "/server/ip";
  }
}
leaf my-port {
  type leafref {
    path "deref(../my-ip)/../port";
  }
}

Another DEREF() Example

Without Deref

container video {
    leaf v-ip {
      type leafref {
        path "/client/ip";
      }
    }
    leaf v-port {
      type leafref {
        path
          "/client[ip=current()/../v-ip]/port";
      }
    }
    leaf v-stream {
      type leafref {
        path
          "/client[ip=current()/../v-ip][port=current()/../v-port]/stream";
      }
    }
  }`

With Deref

container video {
    leaf v-ip {
      type leafref {
        path "/client/ip";
      }
    }
    leaf v-port {
      type leafref {
        path "deref(../v-ip)/../port";
      }
    }
    leaf v-stream {
      type leafref {
        path "deref(../v-port)/../stream";
      }
    }
  }

Understanding Path Expansions

It is always better to name your leafs and variables different to the system bases names and variables ensuring that their is no confusion when they are used in long XPATHS. For example the use of device or devices in naming your leafs can cause a lot of confusion with the native systems ncs:device and ncs:devices

Notice in the below example how the ../ and ../../ expresssions expand.

  • For example in the container ios , the line ../var1_router_name goes up one level to the leaf var1_router_name .
  • Also , in the leaf intf-number notice how the ../../ goes two levels up just like a unix directory to var1_router_name
  • Finally notice the expansion of current() , notice how it starts from the top level hierarchy of /ncs:service/learning_deref/......

module learning_deref {
  namespace "http://com/example/learning_deref";
  prefix learning_deref;

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


  augment "/ncs:services"  // AUGMENT is used to add to another data model , OR augment it. So in the example we are adding/augumenting the learning_deref to it.
  {

    list learning_deref
    {
      key "name";
      uses ncs:service-data;
      ncs:servicepoint "learning_deref";

      leaf name
      {
        mandatory true;
        type string;
      }

      list link
      {

        min-elements 2;
        max-elements 2;
        key "var1_router_name";

            leaf var1_router_name
            {
              mandatory true;
              type leafref
              {
                path "/ncs:devices/ncs:device/ncs:name";  // This path points to a list of routers , not a sepcific router
              }
            } //routername

            container ios
            {

              //  name=current()/../var1_router_name = PE11
              //  ncs:name=current()/../var1_router_name  EXPANDS to
              //  ncs:name=/ncs:services/learning_deref/link/var1_router_name
              when "/ncs:devices/ncs:device[ncs:name=current()/../var1_router_name]/ncs:device-type/ncs:cli/ncs:ned-id='ios-id:cisco-ios'"
              {

                //tailf:dependency "../device";
                //tailf:dependency "/ncs:devices/ncs:device/ncs:device-type";
              }



              leaf intf-number
              {
                mandatory true;
                type leafref
                {
                  path "deref(../../var1_router_name)/../ncs:config/ios:interface/ios:GigabitEthernet/ios:name";
                }
              } //intf-number

            } //ios

      }
    }
  }
}






















YANG Part 2

This is the beginning of Youtube tutorial on YANG Part 2

In Part 1 , we looked at basic YANG types , modules and datatype . In thit moduel we will look more advance data models constrains unique ness and other .

MUST Statement

An Example of a must statement below . There is a acces-timeout and a retry-timer .

In the below example we are setting by must that the value of retry-time should be less that the access-timeout .

In the past we had to simply put this in the code or programmign level , but here in this example we can have the same defined at the data model level.

The current() function (from XPATH) in the code below refers to the value of the current node (which is retry-timer).

Next The path ../ means that we go up one level to timeout and then reach access-path in the tree

The above constraint will be validated and enforced.

As a best practice you should add comment near must to describe what it is doing.

Some Good YANG Examples

unique Ensure uniqueness of values

range restricts the range

error-message Define the error for the model

In the example below we ensure the the value of vpn-id is unique . The range restricts the value and error-message is for the error for incorrect input .

list l3mplsvpn-ce-config
{
  tailf:info "Used to Configure the CE Side of the MPLSL3VPN";
  key "vpn-name";
  unique vpn-id;


  leaf vpn-id
  {
    tailf:info "Name of the VPN ID";
    type unint32
    {
      range 1..10;
      error-message "Invalid VPN ID"
    }
  }
}

count To count all occurrences of a xpath

Example YANG

augment "/ncs:services"
{
  list l3mplsvpn
  {
    tailf:info "Layer-3 MPLS VPN Service";
    key "vpn-name";
    unique interface;

    leaf vpn-name
    {..}

    leaf device
    {..}


    leaf interface
    {
      tailf:info "Customer Facing Interface";
      type string;

      # What the below expression expands to :
      # "In NO other vpn configuration (vpn-name !=)   the current device AND its current
      # interface should be configured" which results in the expression to be zero .
      must "count(../../l3mplsvpn[vpn-name != current()/../vpn-name][device = current()/../device][interface=current()]) = 0"

      # // must "count(../../l3mplsvpn[vpn-name != current()/../vpn-name]"+ "[device = current()/../device][interface=current()]) = 0"
      # Notice the + in the above command , it is nothing but to ensure continuration of the entire path . It is not doign any arithmentic SUMMMATION

      {
        error-message "Interface is already used for another link.";
      }
    }
  }
}

Example Code execution

# services l3mplsvpn vpn1 device SP1 interface 0/1
# services l3mplsvpn vpn2 device SP1 interface 0/2

admin@ncs(config-l3mplsvpn-vpn2)# commit dry-run | debug xpath

# Statement to be validated
Evaluating XPath for: /services/l3mplsvpn:l3mplsvpn[vpn-name='vpn2']/interface:
  count(../../l3mplsvpn[vpn-name != current()/../vpn-name][device = current()/../device][interface=current()]) = 0

get_next(/ncs:services/l3mplsvpn) = {vpn1}
get_elem("/ncs:services/l3mplsvpn{vpn1}/device") = SP1
get_elem("/ncs:services/l3mplsvpn{vpn2}/device") = SP1
get_elem("/ncs:services/l3mplsvpn{vpn1}/interface") = 0/1
get_elem("/ncs:services/l3mplsvpn{vpn2}/interface") = 0/2
get_next(/ncs:services/l3mplsvpn{vpn1}) = {vpn2}
get_next(/ncs:services/l3mplsvpn{vpn2}) = false
2018-05-25T18:40:13.758 XPath for: /services/l3mplsvpn:l3mplsvpn[vpn-name='vpn2']/interface returns true
2018-05-25T18:40:13.759
Evaluating XPath for: /services/l3mplsvpn:l3mplsvpn[vpn-name='vpn2']/device:
  /ncs:devices/ncs:device/ncs:name
get_elem("/ncs:services/l3mplsvpn{vpn2}/device") = SP1
exists("/ncs:devices/device{SP1}") = true
get_elem("/ncs:services/l3mplsvpn{vpn2}/device") = SP1
2018-05-25T18:40:13.763 XPath for: /services/l3mplsvpn:l3mplsvpn[vpn-name='vpn2']/device returns true
2018-05-25T18:40:13.766
Evaluating XPath for: /services/l3mplsvpn:l3mplsvpn[vpn-name='vpn1']/interface:
  count(../../l3mplsvpn[vpn-name != current()/../vpn-name][device = current()/../device][interface=current()]) = 0
get_next(/ncs:services/l3mplsvpn) = {vpn1}
get_next(/ncs:services/l3mplsvpn{vpn1}) = {vpn2}
get_elem("/ncs:services/l3mplsvpn{vpn2}/device") = SP1
get_elem("/ncs:services/l3mplsvpn{vpn1}/device") = SP1
get_elem("/ncs:services/l3mplsvpn{vpn2}/interface") = 0/2
get_elem("/ncs:services/l3mplsvpn{vpn1}/interface") = 0/1
get_next(/ncs:services/l3mplsvpn{vpn2}) = false
2018-05-25T18:40:13.768 XPath for: /services/l3mplsvpn:l3mplsvpn[vpn-name='vpn1']/interface returns true
cli {
    local-node {
        data  services {
             +    l3mplsvpn vpn2 {
             +    }
              }
    }
}

pattern To define a pattern

The below construct define an IP Address patterns and also the error which should be returned if its now followe.

leaf pe-ip {
  tailf:info "PE Interface IP Address";
  mandatory false;
  type inet:ipv4-address {
    pattern "172\\.([1][6-9]|[2][0-9]|3[0-1])\\..*"
    {
      error-message
        "Invalid IP address. IP address should be in the 172.16.0.0/12 range.";
    }
  }
}

starts-with To define a pattern

In the below example on the devices whose name starts with PE will be displayed in the options or be validated for.

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

min-elements statement to define the minimum number of entries in a list.

list link
{
  tailf:info "PE-CE Attachment Point";
  key "link-name";
  unique "link-id";
  unique "device interface";
  min-elements 1;
}

Using when to controll a leaf visibility

In the example below the leaf ce-ip will only be visible when the routing-protocol is set to bgp

leaf ce-ip
{
  tailf:info "CE Interface IP Address";
  when "../routing-protocol='bgp'";
  mandatory false;
  type inet:ipv4-address
  {
    pattern "172\\.([1][6-9]|[2][0-9]|3[0-1])\\..*"
    {
      error-message
        "Invalid IP address. IP address should be in the 172.16.0.0/12 range.";
    }
  }
}

Use the tailf:hidden statement to hide the leaf from the northbound interfaces.

This will remove the leaf from appreaing in CLI / Web UI . This is a precautionary step since we want to apply it programmaticaly.

augment "/ncs:services"
{
  leaf l3mplsvpn-id-cnt
  {
    description "Provides a unique 32-bit number used as VPN instance identifier";
    tailf:hidden "Counter";
    type uint32;
    default "1";
  }
}