CCIE DevNet Reference Guide v1.3
CCIE DevNet Reference Guide v1.3
Section Overview
Section 1.0: Setting up and Getting Started
Section 1.1: Terraform - Docker, ACI
Section 1.2: YANG, NSO
Section 2.1: REST APIs with Python, Click API
Section 2.2: Python NCCLIENT, YANG, NETCONF, RESTCONF
Section 2.3: Python Libraries Jinja2, Scrapli, Netmiko
Section 3.1: Git, Gitlab, CI/CD Pipelines
Section 3.2: Cisco APIC APIs
Section 3.3: Network Test Automation with PyATS
Section 3.4: Secret Management with Vault, Ansible
Section 3.5: NX-API, , GitLab Authentication, Secure REST APIs
Section 4.1: Zammad Python SDK
Section 4.2: YANG Model Driven Telemetry
Section 4.3: Docker, Docker Compose
Section 1.0: Setting up and Getting Started
What is Terraform
● Terraform is an infrastructure as code tool
● It lets you build, change, and version infrastructure, both on cloud and on-prem, safely
and efficiently
● Resources are defined in human-readable configuration files that can be versioned,
reused, and shared
● Can manage both
○ Low level resources like compute, storage and networking
○ High level resources like DNS entries, SaaS features etc.
Installation
● If you have downloaded the DevNet VMImage mentioned above, Terraform is already
installed.
● If you are using any other system, you can follow the below instructions to install
Terraform.
○ https://developer.hashicorp.com/terraform/downloads
Terraform workflow
● Write
○ Create a config file in HCL (Hashciorp Config Language) which defines the
infrastructure in a declarative way
● Plan
○ Terraform creates an execution plan describing the infrastructure it will create,
update, or destroy based on the existing infrastructure and your configuration.
● Apply
○ On approval, Terraform performs the proposed operations in the correct order,
respecting any resource dependencies
Resource Blocks
● Resources are the most important element in the Terraform language
● Each resource block describes one or more infrastructure objects, such as virtual
networks, compute instances, or higher-level components such as DNS records
Resource Syntax
● A resource block declares a resource of a given type ("docker_container") with a
given local name ("nginx_container")
● The local name can only be used to refer this resource from elsewhere in the same
module but has no significance outside
Providers
● Each resource type is implemented by a provider, which is a terraform plugin
● Providers are distributed separately from Terraform itself
● Terraform can automatically install most providers when initializing a working directory
● In order to manage resources, a Terraform module must specify which providers it
requires
● Terraform usually automatically determines which provider to use based on a resource
type's name
● By convention, resource type names start with their provider's preferred local name
● Every Terraform provider has its own documentation, describing its resource types and
their arguments
● Most publicly available providers are distributed on the Terraform Registry, which also
hosts their documentation
Unset
terraform {
required_providers {
docker = {
source = "kreuzwerker/docker"
version = "~> 3.0.2"
}
}
}
provider "docker" {}
# initialize terraform
$ terraforrm init
Input Variables
● Lets us customize aspects of Terraform modules without altering the module's source
code
● This functionality allows you to share modules across different Terraform configurations
● If you're familiar with traditional programming languages, it can be useful to compare
Terraform modules to function definitions
○ Input variables are like function arguments
○ Output values are like function return values
○ Local values are like a function's temporary local variables
● Each input variable accepted by a module must be declared using a variable block
● The label after the variable keyword is a name for the variable, which must be unique
among all variables in the same module
● Using Cisco ACI provider, create an ACI tenant and an application profile
● Names of the tenant and the application profile should be taken from variables file set as
default values
Refer
● https://developer.cisco.com/docs/nso/guides/#!start/start
● https://developer.cisco.com/learning/tracks/get_started_with_nso
● https://developer.cisco.com/docs/nso/guides/#!basic-operations/setup
● Go to NSO installation folder and execute the following command to add NSO
executable to the PATH
● Alternately you can also add it to the end of bash profile so that NSO executables are
available on system boot up
○ cd nso
○ source ncsrc
● Now we are ready to start using NSO commands that start with its earlier name nsc
○ ncs-setup --help
● If we don't see a command not found error, we are good
Getting Started
● Change to nso installation dir and source ncsrc file to set nso commands in path
$ cd nso
$ source ncsrc
(main) expert@devnet-lab:~/nso$ cd
(main) expert@devnet-lab:~$ cd work
(main) expert@devnet-lab:~/work$ ls
my_nso my_nso_new
(main) expert@devnet-lab:~/work$ cd my_nso
(main) expert@devnet-lab:~/work/my_nso$
● In this directory create a simple network with a couple of devices using netsim by
choosing some ned available with the installation
(main) expert@devnet-lab:~/work/my_nso$ ls
logs ncs-cdb ncs.conf packages README.ncs README.netsim
scripts state
(main) expert@devnet-lab:~/work/my_nso$ ncs-netsim create-network
cisco-ios-cli-3.8 2 ios
DEVICE ios0 CREATED
DEVICE ios1 CREATED
(main) expert@devnet-lab:~/work/my_nso$ ls
logs ncs-cdb ncs.conf netsim packages README.ncs
README.netsim scripts state
(main) expert@devnet-lab:~/work/my_nso$ ls netsim/ios/
ios0 ios1
(main) expert@devnet-lab:~/work/my_nso$
● Make this as NSO runtime by setting nso in this dir by pointing to netsim directory as
inventory of devices
● NSO always keeps a copy of the device configuration in CDB and you can easily check if
devices are in sync or not
● At any point if you wish to connect to any device directly, you can do it as follows
(main) expert@devnet-lab:~/work/my_nso$ ncs-netsim cli-c ios0
Creating Services
● Service is a way of simplifying device configuration by automating service provisioning in
NSO
● To add a service, you need to create a service package, which will have some pre-built
files and directories
● We will use the NSO built in command ncs-make-package with the option
service-skeleton to create a blank service
(main) expert@devnet-lab:~/work/my_nso/packages/simple-service$ ls
src/
Makefile yang
(main) expert@devnet-lab:~/work/my_nso/packages/simple-service$ ls
src/yang/
simple-service.yang
(main) expert@devnet-lab:~/work/my_nso/packages/simple-service$
● Open simple-service.yang
module simple-service {
namespace "http://com/example/simpleservice";
prefix simple-service;
import ietf-inet-types {
prefix inet;
}
import tailf-ncs {
prefix ncs;
}
list simple-service {
key name;
uses ncs:service-data;
ncs:servicepoint "simple-service";
leaf name {
type string;
}
leaf secret {
type string;
tailf:info "Enable secret for this device";
}
● Basically we are trying to create a service to enable password on the devices
● Accordingly we are defining how the password should look in the yang data model
● We will now look at the xml template which specifies the configuration as defined in the
yang model
(main) expert@devnet-lab:~/work/my_nso/packages/simple-service$ ls
templates/
simple-service-template.xml
(main) expert@devnet-lab:~/work/my_nso/packages/simple-service$
nano templates/simple-service-template.xml
<config-template xmlns="http://tail-f.com/ns/config/1.0"
servicepoint="simple-service">
<devices xmlns="http://tail-f.com/ns/ncs">
<device>
<!--
Select the devices from some data structure in the
service
model. In this skeleton the devices are specified in a
leaf-list.
Select all devices in that leaf-list:
-->
<name>{/device}</name>
<config>
<!--
Add device-specific parameters here.
In this skeleton the service has a leaf "dummy"; use that
to set something on the device e.g.:
<ip-address-on-device>{/dummy}</ip-address-on-device>
-->
</config>
</device>
</devices>
</config-template>
<config-template xmlns="http://tail-f.com/ns/config/1.0"
servicepoint="simple-service">
<devices xmlns="http://tail-f.com/ns/ncs">
<device>
<name>{./device}</name>
<config>
<enable xmlns="urn:ios">
<password>
<secret>{./secret}</secret>
</password>
</enable>
</config>
</device>
</devices>
</config-template>
● After we change yang model, we have to invoke the command make to check for any
errors and compile it
● For instance we made some mistake in yang file which is shown like below
(main) expert@devnet-lab:~/work/my_nso/packages/simple-service$
make -C src
make: Entering directory
'/home/expert/work/my_nso/packages/simple-service/src'
mkdir -p ../load-dir
/home/expert/nso/bin/ncsc `ls simple-service-ann.yang > /dev/null
2>&1 && echo "-a simple-service-ann.yang"` \
--fail-on-warnings \
\
-c -o ../load-dir/simple-service.fxs yang/simple-service.yang
yang/simple-service.yang:34: error: premature end of file
make: *** [Makefile:26: ../load-dir/simple-service.fxs] Error 1
make: Leaving directory
'/home/expert/work/my_nso/packages/simple-service/src'
(main) expert@devnet-lab:~/work/my_nso/packages/simple-service$
make -C src
make: Entering directory
'/home/expert/work/my_nso/packages/simple-service/src'
/home/expert/nso/bin/ncsc `ls simple-service-ann.yang > /dev/null
2>&1 && echo "-a simple-service-ann.yang"` \
--fail-on-warnings \
\
-c -o ../load-dir/simple-service.fxs yang/simple-service.yang
make: Leaving directory
'/home/expert/work/my_nso/packages/simple-service/src'
● We can also validate yang file correctness using python module for yang called pyang
● We will use to display yang file in tree format to better understand the hierarchy and
relations
(main) expert@devnet-lab:~/work/my_nso/packages/simple-service$
pyang -f tree --tree-depth=2 src/yang/simple-service.yang
module: simple-service
+--rw simple-service* [name]
+---x check-sync
| ...
+---x deep-check-sync
| ...
+---x re-deploy
| ...
+---x reactive-re-deploy
| ...
+---x touch
| ...
+--ro modified
| ...
+--ro directly-modified
| ...
+---x get-modifications
| ...
+---x un-deploy
| ...
+--ro used-by-customer-service* ->
/ncs:services/customer-service/object-id
+--ro commit-queue
| ...
+--rw private
| ...
+--ro plan-location? instance-identifier
+--ro log
| ...
+--rw name string
+--rw device* -> /ncs:devices/device/name
+--rw secret? string
● We have now defined our service using yang data model and xml config template as a
package
● We now need to connect to nso cli and reload the packages for the changes to take
effect
(main) expert@devnet-lab:~/work/my_nso/packages/simple-service$
ncs_cli -u admin -C
admin@ncs(config)# si
Possible completions:
side-effect-queue simple-service
admin@ncs(config)# simple-service ?
% No entries found
Possible completions:
<name:string>
admin@ncs(config)# simple-service
● Use the below command to set the password on the device ios0 to mypasswd
● Verify the config change by moving to parent prompt and using the command show
configuration
● Commit the config changes using the commit command
Modifying a service
● Once we have a service in place, making config modifications is very easy
● Just re invoke the service with the changed config
● For instance if we want to change the password, we can do it as below
admin@ncs(config-simple-service-test1)# top
admin@ncs(config)# simple-service test1 get-modifications
cli {
local-node {
data
}
}
● It is showing that what has been changed is the actual password, which is nothing but
data
● We can now apply the changed configuration by redeploying the changed service
admin@ncs(config)# simple-service test1 re-deploy
admin@ncs(config)#
System message at 2023-06-09 17:13:31...
Commit performed by admin via ssh using cli.
admin@ncs(config)#
NETCONF
● NETCONF is a configuration management protocol that defines a mechanism through
which:
○ A network device can be managed
○ configuration data can be retrieved
○ new configuration data can be uploaded and manipulated
● NETCONF uses SSH for secure communication and data is encoded in XML
● This data should be consistent, standardized and as per certain agreed upon rules, if the
sender and receiver have to be on the same page
● So it can be modeled using YANG
YANG
leaf first-name {
tailf:info "Person's first name";
mandatory true;
type string;
}
leaf last-name {
tailf:info "Person's last name";
mandatory true;
type string;
}
leaf date-of-birth {
tailf:info "Date of birth as dd/mm/yyyy";
type string {
pattern "[0-9][0-9]/[0-9][0-9]/[0-9][0-9][0-9][0-9]";
}
}
leaf email {
tailf:info "Contact email";
mandatory true;
type string;
}
leaf job-role {
tailf:info "Persons job role within organization";
type enumeration {
enum admin;
enum developer;
enum manager;
}
}
}
● Data models form the foundation for model-driven APIs and network programmability
● They define the syntax, semantics, and constraints of the data interchanged
● They define data attributes and answer questions, such as the following:
○ What is the range of a valid VLAN ID?
○ Can a VLAN name have spaces in it?
○ Should the values be enumerated and only support "up" or "down" for an admin
state?
○ Should the value be a string or an integer?
● Yet Another Next Generation (YANG) has become a widely used data modeling
language in the networking industry
● Although it can describe any data model, it was originally designed for networking data
models
● It is used to create models of configuration and state data
● It has a structured format that is also human-readable
● The below example shows how YANG defines a model of a network interface
module ietf-interfaces {
import ietf-yang-types {
prefix yang;
}
container interfaces {
list interface {
key "name";
leaf name {
type string;
description
"The name of the interface";
}
leaf description {
type string;
description
"A textual description of the interface";
}
leaf type {
type identityref {
base interface-type;
}
mandatory true;
description
"The type of the interface";
}
leaf enabled {
type boolean;
default true;
description
"The configured state of the interface";
}
}
}
YANG Structure
Modules
● In YANG, all data modeling definitions reside inside YANG modules
● Modules organize related items and place them in groups
● Often a single module describes a complete part of a functionality, such as the Border
Gateway Protocol (BGP) or interface IP configuration
● Other times, a device, especially a simpler one, could implement everything in a single
module
● Module can be defines as shown below
Unset
module ip-access-list {
namespace "http://example.com/ns/yang/ip-access-list";
prefix acl;
// ...
}
Unset
organization
"Example, Inc.";
contact
"Example, Inc.
Customer Service
E-mail: cs-yang@example.org";
description
"Access Control List (ACL) YANG model.";
revision 2021-07-06 {
description
"Initial revision";
}
Unset
container acl {
description
Unset
container acl {
...
leaf acl-description {
type string {
length "0..64";
pattern "[0-9a-zA-Z]*";
● The leaf-list statement defines an array of values, which have the same, specific
type
Unset
container acl {
...
leaf acl-description {
...
leaf-list maintainers {
type string;
description "Maintainers working on the ACL";
● The list node specifies a sequence of list entries and is similar to Python's dictionary
data structure
Enumeration
● Used to define a finite set of values allowed
○ true/ false
○ enabled/ disabled
○ ipv4/ ipv6
What is Program?
● A program is a set of instructions given to a computer to perform a specific operation
using some data
● When the program is executed, raw data is processed into a desired output format
● These programs are written in high-level programming languages which are close to
human languages
● They are then converted to machine understandable low level languages and
executed
What is Python?
● Python is an interpreted, object oriented, high-level, scripting language used for
general-purpose programming
● It has wide range of applications ranging from Web Development, Scientific and
Mathematical Computing to Desktop Graphical User Interfaces
● It was created by Guido van Rossum and first released in 1991
Why Python?
● Python interpreters are available for many operating systems including network
operating systems
● Cisco puts Python on many of its devices and releases many tools using Python
● This makes Python a great choice for network engineers looking to add programming
to their skillset
● For network and IT infrastructure engineers, there is a well-established online community
of infrastructure engineers using Python to deploy and maintain IT systems
Install Python
● If you are using Cisco CWS, Python is already installed and ready for use the moment
you connect to CWS as user expert
● If You want to set it up on any other VM or your laptop you can follow the steps below
● On Windows
○ Download the installer from https://www.python.org/downloads/
○ Invoke the installer executable and follow the steps in the installation wizard
● On Ubuntu
○ By default Ubuntu comes with Python older version that is python2
○ Now a days certain Ubuntu distributions come preinstalled with python3
○ Check which version is already installed using the below command
■ python -–version
○ If you get a message like command not found or you get the result showing
python version as 2.x, you can proceed further
○ If the result shows the version as 3.x, you are good to go, unless you want to
update to the latest version
○ To install latest version follow the link
https://www.makeuseof.com/install-python-ubuntu/
○ Or any other link of your choice
Getting started
● Check which version of python are you using
Data Types
● All we are doing as part of our work is using and manipulating data
● Data comes in various types like:
○ Names of network devices: Group of alphabets - Strings (str)
○ Number of interfaces: Number (Integers) (int)
○ Information like whether certain interface is enabled or not: True or False
(Boolean) (bool)
○ OS version of certain router or switch: Number (Decimals or Floating point
numbers) (float)
○ List of spine and leaf switches: Collection of names (List)
○ A switch with all its attributes like: Name of the attribute mapped to its value
■ Make or manufacturer: Cisco/ Juniper etc.
■ Model: Cisco CSR 1000
■ OS Version: 16.9
■ Name: brch_rtr_01
■ And so on
● Looking at a piece of data it is easy for humans to understand its type, but for machines
it is not
● So we need to explicitly let the systems know what is the type of data we are dealing
with
● To do this every programming language uses some data types
● Python has the following basic data types
○ Text Type : Strings (str)
○ Numeric Types : Numbers (int, float, complex)
○ Sequence Types : Lists, Tuples, Set (list, tuple, set)
○ Mapping Type : Dictionaries (dict)
○ Boolean Type : bool
Variables
● Now that we are talking about different types of data, we need a way to store this data
during the program (or code as we call it) execution
● We use variables for this purpose
● Variables are named locations used to store data in memory
● Think of them like containers that holds data
● Data held by the variable can change later
● When we create a variable, we are telling the system to keep some storage aside
● How much storage to keep aside depends on the type of data that is going to be stored
in that variable
● Let us create few variables and display them on the console using an inbuilt utility or
function called print()
● By the way, we can use Python Interpreter CLI to give any python commands and
perform some basic operations
● In CLI mode you do not need to use print() function. Directly type the name of the
variable and the value stored in it will be displayed
● Use suggestive names for the variables based on what they are used to store
● We can check the type of data stored in a variable using another built in function called
type()
>>> type(model)
<class 'str'>
>>> type(os_version)
<class 'float'>
>>> type(num_intfcs)
<class 'int'>
>>>
Operators
● Data manipulation requires that we are able to perform certain operations on the data
● We will use operators for this purpose
● Accordingly every programming language, including python has certain operators
defined
● Depending on the types of operations they are classified as follows
○ Arithmetic Operators
○ Assignment Operators
○ Comparison Operators
○ Logical Operators
○ Special Operators
Arithmetic Operators
● Used to perform arithmetic operations on variables
+ Addition 5 + 2 = 7
- Subtraction 4 - 2 = 2
* Multiplication 2 * 3 = 6
/ Division 4 / 2 = 2
// Floor Division 10 // 3 = 3
% Modulo 5 % 2 = 1
** Power 4 ** 2 = 16
a = 7
b = 2
# addition
print ('Sum: ', a + b)
# subtraction
print ('Subtraction: ', a - b)
# multiplication
print ('Multiplication: ', a * b)
# division
print ('Division: ', a / b)
# floor division
print ('Floor Division: ', a // b)
# modulo
print ('Modulo: ', a % b)
# a to the power b
print ('Power: ', a ** b)
Assignment Operators
● Used to assign values to variables
= Assignment Operator a = 7
+= Addition Assignment a += 1 # a = a + 1
-= Subtraction Assignment a -= 3 # a = a - 3
*= Multiplication Assignment a *= 4 # a = a * 4
/= Division Assignment a /= 3 # a = a / 3
%= Remainder Assignment a %= 10 # a = a % 10
# assign 10 to a
a = 10
# assign 5 to b
b = 5
print(a)
# Output: 15
Comparison Operators
● Used to compare the value or variables
● Result of the comparison operators is always true or false (boolean)
a = 5
b = 2
# equal to operator
print('a == b =', a == b)
Logical Operators
● Used to check truthiness or falseness of expressions
and a and b Logical AND: True only if both the operands are True
or a or b Logical OR: True if at least one of the operands is True
# logical AND
print(True and True) # True
print(True and False) # False
# logical OR
print(True or False) # True
# logical NOT
print(not True) # False
Special Operators
● Used to verify identity or check membership
is True if the operands are identical (refer to the same object) x is True
is not True if the operands are not identical (do not refer to the same x is not
object) True
x = 'Hello world'
Strings
● Strings are sequence of characters enclosed by quotes
● Single or double quotes are accepted, but be consistent
● Create a variable called hostname and assign it the value of "ROUTER1"
● Strings are very useful in Python as most of the time we end up working with strings
● Some of the built in methods on strings are shown below
Methods Description
Lists
● Lists are sequence data type used to store multiple values or objects
● It's one of the most frequently used and versatile data type
● It is an ordered sequence, meaning elements of a list are indexed by integers starting at
0
● Similar to arrays in other programming languages
● But elements of a list can be of any data type
● An example is shown below
● List is an ordered collection, which means that the elements in the list are indexed
● We can retrieve elements of a list using an index
● Indexing in lists starts from 0
● Lists also support negative indexing which helps us to traverse the list in reverse
● Some useful methods on lists are shown below
Method Description
extend() add items of lists and other iterables to the end of the
list
Tuples
● A tuple in Python is similar to a list
● The difference between the two is that we cannot change the elements of a tuple once it
is assigned
● We call this property as immutability
● A tuple is created by placing all the items (elements) inside parentheses ‘(‘, ‘)’
● Similar to lists, tuples can have any number of elements and of any type
● From memory management point of view tuples are more efficient than lists
● Also tuples are used where we do not expect the variable to be changed accidentally
● There are not too many built in methods are available for Tuples as they cannot be
modified once created
● We just have count() and index() methods on tuples
Dictionaries
● Dictionaries are mapping data types where data is stored as combination of key, value
pairs
● We can think of them as unordered lists
● Unlike lists instead being indexed by a number, dictionaries are indexed by a name
known as key
● Also known with other names like hashes or associative arrays
● Let us create a simple dictionary to store the details of a network device
● But why do we need a new data type like a dictionary? Why can't we use one of the
existing ones like a list?
● Let us relook at a list which stores similar details
● Looking at the values stored in the list, we have to guess what they stand for
● Instead we can associate them with appropriate keys in the dictionary