Using OpenConfig YANG Models with IOS-XR and JunOS

karim okasha
9 min readMay 24, 2017

--

In the previous article , we discussed the importance of YANG Modules and how it can provide a clear method to describe both the configuration and operational state of a network device. In this article, we will explore how to use OpenConfig YANG model and how it will be used to drive the BGP configuration on both Cisco IOS-XR and JunOS nodes. This will outline the benefits from adopting this vendor neutral YANG models and how it will simplify the OSS layer. In order to make use of the OpenConfig YANG models we will use a python library developed by Cisco called YDK.

What is YDK?

YDK stands for YANG Development Kit, it is a tool developed by Cisco to accelerate the adoption of YANG models and makes working with YANG models easier especially for developers. As we discussed in a previous post, YANG models can be mapped directly to any programming language data structures and this is what YDK deliver.

YDK aims at providing the following key features.

  • Dynamic API binding between the YANG Models and the data structures of multiple Programming languages.
  • Provides and abstraction layer that hides all the NETCONF, RESTCONF, XML and JSON details from the Developer point of View, thus the developer can only focus on developing application that can consume the YANG models.

The following diagram outline a high-level architecture of the YDK Development kit and how it fits into the new Model Driven network approach.

The heart of the YDK platform is YDK-GEN this is the transformation engine that takes the YANG files and transfer them to consumable python scripts, the below diagram outlines this conversion.

The YDK framework is segregated into three layer as follows

- Models, these hold the YANG files after being transferred to python.

- Services, these are the different services that can utilize the python libraries imported by the models. This can be NETCONF services to provide NETCONF operations on the models or can be Codec service which translate (serialize) this model into JSON or XML.

- Providers, these are the workers which take the model after the service was performed on it and send it over the wire like NETCONF provider or display it via Codec provider.

With this brief introduction into YDK we are ready to use it to render the OpenConfig BGP YANG file and use it to build the BGP configuration on both Juniper and Cisco IOS-XR nodes.

Lab Setup

The Nodes used in the topology are the following

  • Cisco IOS-XR 6.1.2 (running on XRv VM)
  • JunOS 17.1R1.8 (running on vMX VMs)

The below diagram outline the Lab setup that will be used to demonstrate the use of OpenConfig YANG models with IOS-XR and JunOS.

Pre-requisites

  • Enable NETCONF on IOS-XR and JunOS

IOS-XR

ssh server v2
ssh server netconf vrf default
ssh server logging
netconf agent tty
xml agent
iteration off

JunOS

netconf@vmx-r7# show system services 
ssh;
netconf {
ssh;
rfc-compliant;
}
  • Compile the OpenConfig YANG Models on JunOS since by default JunOS doesn’t integrate OpenConfig YANG Models as part of the JunOS release yet. Thus we need to install these modules on JunOS, in order to do so the please follow the steps in the below link

https://www.juniper.net/documentation/en_US/junos/topics/task/installation/openconfig-installing.html

  • On the Development VM you should be running Python 2.7 (I tested it with python 2.7, however it should run fine on Python 3.4+). To install the YDK packages please follow the instructions outlined in the below link

https://github.com/CiscoDevNet/ydk-py

In order to better understand the YANG structure and how it will converted to python, below is the snippet of the BGP OpenConfig YANG model (this is not the complete model, i have removed many parts for clarity) that we will work with after displaying it using pyang to be more clear.

module: openconfig-bgp
+--rw bgp
+--rw global
| +--rw config
| | +--rw as inet:as-number
| | +--rw router-id? yang:dotted-quad
| +--rw default-route-distance
| | +--rw config
| | | +--rw external-route-distance? uint8
| | | +--rw internal-route-distance? uint8
| +--rw afi-safis
| | +--rw afi-safi* [afi-safi-name]
| | +--rw afi-safi-name -> ../config/afi-safi-name
| | +--rw config
| | | +--rw afi-safi-name? identityref
| | | +--rw enabled? boolean
| | +--rw ipv4-unicast
| | | +--rw prefix-limit
| | | | +--rw config
| | | | | +--rw max-prefixes? uint32
| | | | | +--rw shutdown-threshold-pct? oc-types:percentage
| | | | | +--rw restart-timer? decimal64
| | | +--rw config
| | | | +--rw send-default-route? boolean
| | | +--ro state
| | | +--ro send-default-route? boolean
+--rw neighbors
| +--rw neighbor* [neighbor-address]
| +--rw neighbor-address -> ../config/neighbor-address
| +--rw config
| | +--rw peer-group? -> ../../../../peer-groups/peer-group/peer-group-name
| | +--rw neighbor-address? inet:ip-address
| | +--rw enabled? boolean
| | +--rw peer-as? inet:as-number
| | +--rw local-as? inet:as-number
| | +--rw peer-type? oc-bgp-types:peer-type
| | +--rw auth-password? string
| | +--rw remove-private-as? oc-bgp-types:remove-private-as-option
| | +--rw route-flap-damping? boolean
| | +--rw send-community? oc-bgp-types:community-type
| | +--rw description? string
| +--rw timers
| | +--rw config
| | | +--rw connect-retry? decimal64
| | | +--rw hold-time? decimal64
| | | +--rw keepalive-interval? decimal64
| | | +--rw minimum-advertisement-interval? decimal64
| +--rw transport
| | +--rw config
| | | +--rw tcp-mss? uint16
| | | +--rw mtu-discovery? boolean
| | | +--rw passive-mode? boolean
| | | +--rw local-address? union
| +--rw afi-safis
| +--rw afi-safi* [afi-safi-name]
| +--rw afi-safi-name -> ../config/afi-safi-name
| +--rw config
| | +--rw afi-safi-name? identityref
| | +--rw enabled? boolean
| +--rw ipv4-unicast
| | +--rw prefix-limit
| | | +--rw config
| | | | +--rw max-prefixes? uint32
| | | | +--rw shutdown-threshold-pct? oc-types:percentage
| | | | +--rw restart-timer? decimal64
| | | +--ro state
| | | +--ro max-prefixes? uint32
| | | +--ro shutdown-threshold-pct? oc-types:percentage
| | | +--ro restart-timer? decimal64
| | +--rw config
| | | +--rw send-default-route? boolean
| | +--ro state
| | +--ro send-default-route? boolean
+--rw peer-groups
+--rw peer-group* [peer-group-name]
+--rw peer-group-name -> ../config/peer-group-name
+--rw config
| +--rw peer-group-name? string
| +--rw peer-as? inet:as-number
| +--rw local-as? inet:as-number
| +--rw peer-type? oc-bgp-types:peer-type
| +--rw auth-password? string
| +--rw remove-private-as? oc-bgp-types:remove-private-as-option
| +--rw route-flap-damping? boolean
| +--rw send-community? oc-bgp-types:community-type
| +--rw description? string
+--rw timers
| +--rw config
| | +--rw connect-retry? decimal64
| | +--rw hold-time? decimal64
| | +--rw keepalive-interval? decimal64
| | +--rw minimum-advertisement-interval? decimal64
+--rw afi-safis
+--rw afi-safi* [afi-safi-name]
+--rw afi-safi-name -> ../config/afi-safi-name
+--rw config
| +--rw afi-safi-name? identityref
| +--rw enabled? boolean
+--rw ipv4-unicast
| +--rw prefix-limit
| | +--rw config
| | | +--rw max-prefixes? uint32
| | | +--rw shutdown-threshold-pct? oc-types:percentage
| | | +--rw restart-timer? decimal64
| | +--ro state
| | +--ro max-prefixes? uint32
| | +--ro shutdown-threshold-pct? oc-types:percentage
| | +--ro restart-timer? decimal64
| +--rw config
| | +--rw send-default-route? boolean
| +--ro state
| +--ro send-default-route? boolean

With this we should have a working environment to start the lab. The below is the code used to generate the configuration for both the IOS-XR and the JunOS nodes and push this configuration via NETCONF to both nodes and then commit.

from ydk.services import  NetconfService,Datastore
from ydk.providers import NetconfServiceProvider
from ydk.models.openconfig import openconfig_bgp as oc_bgp
from ydk.models.openconfig import openconfig_bgp_types as oc_bgp_types
import time
username = 'netconf'
password = 'netconf123'
netconf_port = 22
iosxr_host = 'https://www.linkedin.com/redir/invalid-link-page?url=172%2e16%2e0%2e13'
junos_host = 'https://www.linkedin.com/redir/invalid-link-page?url=172%2e16%2e0%2e12'
# Function used to generate the BGP configuration
def config_bgp(bgp,global_as,router_id,peer_group_name,remote_as,remote_peer):

"""Add config data to bgp object."""
# global configuration
bgp.global_.config.as_ = global_as
bgp.global_.config.router_id = router_id
afi_safi = bgp.global_.afi_safis.AfiSafi()
afi_safi.afi_safi_name = oc_bgp_types.Ipv4UnicastIdentity()
afi_safi.config.enabled = True
bgp.global_.afi_safis.afi_safi.append(afi_safi)
afi_safi.config.enabled = True
# configure IBGP peer group
peer_group = bgp.peer_groups.PeerGroup()
peer_group.peer_group_name = peer_group_name
peer_group.config.peer_group_name = peer_group_name
peer_group.config.peer_as = remote_as
afi_safi = peer_group.afi_safis.AfiSafi()
afi_safi.afi_safi_name = oc_bgp_types.Ipv4UnicastIdentity()
afi_safi.config.enabled = True
peer_group.afi_safis.afi_safi.append(afi_safi)
bgp.peer_groups.peer_group.append(peer_group)
# configure IBGP neighbor
neighbor = bgp.neighbors.Neighbor()
neighbor.neighbor_address = remote_peer
neighbor.config.neighbor_address = remote_peer
neighbor.config.peer_group = peer_group_name
bgp.neighbors.neighbor.append(neighbor)
if __name__ == "__main__":
"""Execute main program."""
# create IOS-XR NETCONF provider
xr_provider = NetconfServiceProvider(address=iosxr_host,
port=netconf_port,
username=username,
password=password,
timeout=10)
# create JunOS NETCONF provider
junos_provider = NetconfServiceProvider(address=junos_host,
port=netconf_port,
username=username,
password=password,
timeout=10)
#create an instance of the netconf service
netconf = NetconfService()
# Create an IOSXR instance from the OpenConfig BGP Model
xr_bgp = oc_bgp.Bgp()
# Create an JunOS instance from the OpenConfig BGP Model
junos_bgp = oc_bgp.Bgp()
# Populate the BGP object with xr Configuration
config_bgp(xr_bgp,global_as=100,router_id='https://www.linkedin.com/redir/invalid-link-page?url=10%2e1%2e3%2e1',peer_group_name='EBGP',remote_as=101,remote_peer='https://www.linkedin.com/redir/invalid-link-page?url=10%2e1%2e3%2e2')
# Populate the BGP object with JunOS Configuration
config_bgp(junos_bgp,global_as=101,router_id='https://www.linkedin.com/redir/invalid-link-page?url=10%2e1%2e3%2e2',peer_group_name='EBGP',remote_as=100,remote_peer='https://www.linkedin.com/redir/invalid-link-page?url=10%2e1%2e3%2e1')
# Send the XR bgp object via NETCONF to the Candidate DataStore on the XR router
netconf.edit_config(xr_provider,Datastore.candidate,xr_bgp)
# commit the configuration
netconf.commit(xr_provider)
# close the NETCONF session towards the XR router
xr_provider.close()
# Wait for 2 second
time.sleep(2)
# Send the JUnOS bgp object via NETCONF to the Candidate DataStore on the JUnOS router
netconf.edit_config(junos_provider,Datastore.candidate,junos_bgp)
# commit the configuration
netconf.commit(junos_provider)
# close the NETCONF session towards the JUnOS router
junos_provider.close()
exit()

We verify that the configuration was correctly pushed towards both nodes

IOS-XR

RP/0/0/CPU0:xrv3#sh running-config router bgp 
Wed May 10 15:38:42.119 UTC
router bgp 101
address-family ipv4 unicast
!
neighbor-group EBGP
remote-as 100
address-family ipv4 unicast
!
!
neighbor https://www.linkedin.com/redir/invalid-link-page?url=10%2e1%2e3%2e2
use neighbor-group EBGP
!
!

JunOS

netconf@vmx-r7# show openconfig-bgp:bgp 
neighbors {
neighbor https://www.linkedin.com/redir/invalid-link-page?url=10%2e1%2e3%2e1 {
config {
peer-group EBGP;
neighbor-address https://www.linkedin.com/redir/invalid-link-page?url=10%2e1%2e3%2e1;
}
}
}
peer-groups {
peer-group EBGP {
config {
peer-group-name EBGP;
peer-as 101;
}
}
}

We verify that the BGP Session is now operational

netconf@vmx-r7# run show bgp summary 
Groups: 1 Peers: 1 Down peers: 0
Table Tot Paths Act Paths Suppressed History Damp State Pending
inet.0
0 0 0 0 0 0
Peer AS InPkt OutPkt OutQ Flaps Last Up/Dwn State|#Active/Received/Accepted/Damped...
https://www.linkedin.com/redir/invalid-link-page?url=10%2e1%2e3%2e1 100 2012 2236 0 0 16:45:33 0/0/0/0 0/0/0/0

Summary

In this article, we illustrate the use of YDK library and how it can be used to simplify working with YANG modules. Further we outlined how to use OpenConfig YANG models to drive the BGP configuration on both IOS-XR and JunOS nodes. Adopting these vendor neutral YANG models clearly simplifies the NMS and provides and consistent method to configure multiple vendor equipment with the same template/configuration structure.

References

Simplifying Network Automation Using Python Model-Driven APIs

NANOG 68 Ok We Got YANG Data Models Now What — YouTube

--

--