Learning YANG
by Vikas Srivastava
Opinions expressed are solely my own and do not express the views or opinions of my employer.
- Modules
- Another DEREF() Example
- YANG Part 2
- MUST Statement
- Some Good YANG Examples
unique
Ensure uniqueness of valuesrange
restricts the rangeerror-message
Define the error for the modelcount
To count all occurrences of a xpathpattern
To define a patternstarts-with
To define a patternmin-elements
statement to define the minimum number of entries in a list.- Using
when
to controll a leaf visibility - Use the
tailf:hidden
statement to hide the leaf from the northbound interfaces.
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
ordevices
in naming your leafs can cause a lot of confusion with the native systemsncs:device
andncs: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 leafvar1_router_name
. - Also , in the leaf intf-number notice how the
../../
goes two levels up just like a unix directory tovar1_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";
}
}
Subscribe via RSS