0% found this document useful (0 votes)
242 views

CCIE DevNet Reference Guide v1.6

This document provides an overview of sections covered in the CCIE DevNet Technology Reference Guide. Section 1 focuses on infrastructure as code tools like Terraform and Docker, and network management platforms like Cisco NSO. It includes hands-on exercises to automate infrastructure provisioning and network configuration. Section 2 covers Python programming fundamentals and libraries for network automation. Later sections discuss continuous integration/deployment, APIs for network devices, network testing automation, and additional Python SDKs.

Uploaded by

Saleem Ahmad
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
242 views

CCIE DevNet Reference Guide v1.6

This document provides an overview of sections covered in the CCIE DevNet Technology Reference Guide. Section 1 focuses on infrastructure as code tools like Terraform and Docker, and network management platforms like Cisco NSO. It includes hands-on exercises to automate infrastructure provisioning and network configuration. Section 2 covers Python programming fundamentals and libraries for network automation. Later sections discuss continuous integration/deployment, APIs for network devices, network testing automation, and additional Python SDKs.

Uploaded by

Saleem Ahmad
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 58

CCIE DevNet Technology Reference Guide

CCIE DevNet Technology Reference Guide


Section Overview
Section 1.0: Setting up and Getting Started
What would you need?
Section 1.1 - Terraform - Docker and ACI
What is Terraform
Installation
How does Terraform work?
Terraform workflow
Resource Blocks
Resource Syntax
Providers
Terraform Docker Config file example
Hands-On 1 - Provision Docker Container using Terraform
Input Variables
Hands-On 2 - Parametrize Terraform config script using variables
Section 1.2 - YANG, Cisco NSO (Network Services Orchestrator)
Refer
What is Cisco NSO?
How does Cisco NSO Work?
Install Cisco NSO
Getting Started
Managing the devices
Configuring the network
Creating Services
Modifying a service
NETCONF
YANG
What is a data model?
YANG Structure
Modules
Containers and Leaves
YANG Data Types
Common Data Types
Enumeration
Section 2.0: Python Crash Course
What is Program?
What is Python?
Why Python?
Install Python
Getting started
Data Types
Variables
Operators
Arithmetic Operators
Assignment Operators
Comparison Operators
Logical Operators
Special Operators
Getting Input from the user
Data Types - Deep Dive
Strings
Lists
Tuples
Dictionaries
Conditional Statements
if Statement
if - else
Loops
for Loop
while Loop Example
Functions
A Few Built-in Functions
Writing a Function
Functions With Optional Arguments
Functions With Keyword Arguments
Functions With Arbitrary Arguments
Libraries
Built-in module - json
Working with files
Netmiko
Getting Started

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 would you need?


● DevNet VM Image with the following pieces set up
○ Python
○ Ansible
○ Docker
○ Terraform
● VMWare Workstation Player
● Code Editor/ IDE (Integrated Development Environment)
○ VS Code (Recommended)
○ Pycharm
○ Any other editor of your choice
● Download Links
○ VM Image
■ Octa Image:
https://drive.google.com/file/d/1a_xfj2wI3Ezbi0ElstYebPxpUC5cMulO/vie
w
● Username: student
● Password: devnetstudent
■ Cisco Image
https://learningcontent.cisco.com/images/2022-04-09_DevNetExpert_CW
S_Example.ova
■ If you download the Cisco image, what you get is a zip file, which you
need to unzip into some folder and then open the OVA file with any
virtualization software like VMWare Workstation
○ VMWare Player
■ https://www.vmware.com/go/getplayer-win
○ Visual Studio Code (VS Code)
■ https://code.visualstudio.com/download

Section 1.1 - Terraform - Docker and ACI

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

How does Terraform work?


● Providers enable Terraform to work with virtually any platform or service with an
accessible API.
● HashiCorp and the Terraform community have already written thousands of providers
to manage many different types of resources and services
● You can find all publicly available providers on the Terraform Registry
○ Explore the following providers on Terraform Registry
■ Docker
■ Cisco ACI
■ Azure
■ AWS

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

Terraform Docker Config file example

Unset
terraform {
required_providers {
docker = {
source = "kreuzwerker/docker"
version = "~> 3.0.2"
}
}
}

provider "docker" {}

resource "docker_image" "nginx_image" {


name = "nginx:latest"
keep_locally = false
}

resource "docker_container" "nginx_container" {


image = docker_image.nginx_image.image_id
name = "tutorial"
ports {
internal = 80
external = 8000
}
}

# initialize terraform
$ terraforrm init

# check what is terraform going to create by generating a plan


$ terraform plan

# apply the plan to create resources


$ terraform apply

# destroy the resources created by terraform


$ terraform destroy
Hands-On 1 - Provision Docker Container using Terraform
● Create an nginx docker container using terraform
● Expose port 80 on the container via port 8888 on host
● Ensure that default page comes up

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

Hands-On 2 - Parametrize Terraform config script using variables

● 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

Hands-On 3 - Create multiple ACI tenants using for-each construct

● Declare a list variable with a list of tenant names


● Using for each construct, create multiple tenants with each name given in the list
variable

Hands-On 4 - Create multiple ACI tenants using for-each construct

● Declare a list variable with a list of tenant names


● Using for each construct, create multiple tenants with each name given in the list
variable
Section 1.2 - YANG, Cisco NSO (Network Services Orchestrator)

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

What is Cisco NSO?


● A Linux application which orchestrates the configuration life cycle of physical and virtual
network devices
● NSO gathers, parses, and stores the configuration state of the network devices it
manages in a configuration database (CDB)
● Users and applications can then "ask" NSO to create, read, update, or delete
configuration in a programmatic way either ad hoc or through customizable network
services

How does Cisco NSO Work?


● NSO uses software packages called Network Element Drivers (NEDs)
● Using NEDs NSO facilitates telnet, SSH, or API interactions with the devices that it
manages
● NED provides an abstraction layer that reads in the device's running configuration and
parses it into a data-model-validated snapshot in the CDB
● NEDs can also do the reverse
○ creating network configuration from CDB data inputs
○ sending the configurations to the network devices

Install Cisco NSO


● https://developer.cisco.com/docs/nso/#!getting-and-installing-nso/installation
● Prerequisites
○ Java
■ apt install default-jdk (already installed on CWS)
○ Apache ANT
■ apt install ant (already installed on CWS)
○ Python
■ apt-get install python3 python3-pip python3-setuptools
(already installed on CWS)
○ Development Tools
■ apt install libxml2-utils (already installed on CWS)
● Download NSO suitable for your OS
○ https://software.cisco.com/download/home/286331591/type/286283941/release/6
.0
○ Locate the installer for your OS (e.g.: Cisco Network Services Orchestrator Linux
Installer) and click download button on the right
○ Once downloaded,
■ cd /home/expert/Downloads
■ sh nso-6.0-freetrial.linux.x86_64.signed.bin
○ Files are unpacked
○ Locate your installer
■ E.g. nso-6.0.linux.x86_64.installer.bin
○ Use the following command to get help with the installation
■ sh nso-6.0.linux.x86_64.installer.bin --help
○ Use the following command to perform the local installation (recommended for
installing NSO for development purpose on laptops)
■ nso-6.0.linux.x86_64.installer.bin [--local-install]
LocalInstallDir

○ Ensure that installation is successful as shown in the above screenshot


● Look at the contents of the nso/packages/neds directory:

● 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

● NSO supports keeping the installation separate from the runtime


● This enables us to create a project specific nso setup to keep the list of devices and their
corresponding configuration separate
● Go to any directory created for working on a project

(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

(main) expert@devnet-lab:~/work/my_nso$ ncs-setup --dest .


--netsim-dir netsim
Using netsim dir netsim
(main) expert@devnet-lab:~/work/my_nso$

● Start the devices and then nso using the following commands

(main) expert@devnet-lab:~/work/my_nso$ ncs-netsim start


DEVICE ios0 OK STARTED
DEVICE ios1 OK STARTED
(main) expert@devnet-lab:~/work/my_nso$ ncs
(main) expert@devnet-lab:~/work/my_nso$ncs --status | grep status
status: started
db=running id=34 priority=1
path=/ncs:devices/device/live-status-protocol/device-type
(main) expert@devnet-lab:~/work/my_nso$

Managing the devices


● Connect to NSO CLI as admin using Cisco-Style
● Sync NSO with our simulated devices

(main) expert@devnet-lab:~/work/my_nso$ ncs_cli -u admin -C

admin connected from 192.168.110.1 using ssh on devnet-lab


admin@ncs# devices sync-from
sync-result {
device ios0
result true
}
sync-result {
device ios1
result true
}
admin@ncs#
● NSO always keeps a copy of the device configuration in CDB and you can easily check if
devices are in sync or not

admin@ncs# devices device ios1 check-sync


result in-sync
admin@ncs#

Configuring the network


● From NCS CLI, get into config mode using the below command

(main) expert@devnet-lab:~/work/my_nso$ ncs_cli -u admin -C

User admin last logged in 2023-06-09T12:37:09.88296+00:00, to


devnet-lab, from 192.168.110.1 using cli-ssh
admin connected from 192.168.110.1 using ssh on devnet-lab
admin@ncs# config
Entering configuration mode terminal
admin@ncs(config)#

● Examine the existing configuration as follows

admin@ncs(config)# show full-configuration devices device ios1


config
devices device ios1
config
tailfned device netsim
tailfned police cirmode
no service password-encryption
no cable admission-control preempt priority-voice
no cable qos permission create
no cable qos permission update
no cable qos permission modems
ip source-route
no ip cef
ip vrf my-forward
bgp next-hop Loopback1

● Configuration of a single device can be changes as follows


○ Get into device config mode using devices device ios0 config
○ Change hostname using ios:hostname nso.cisco.com
○ Move to parent prompt using top
○ Verify the config change using show configuration
○ Commit the configuration changes using commit
admin@ncs(config)# devices device ios0 config
admin@ncs(config-config)# ios:hostname nso.cisco.com
admin@ncs(config-config)# top
admin@ncs(config)# show configuration
devices device ios0
config
hostname nso.cisco.com
!
!
admin@ncs(config)# commit
Commit complete.
admin@ncs(config)#

● 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

User admin last logged in 2023-06-09T14:24:30.838836+00:00, to


devnet-lab, from 192.168.110.1 using cli-ssh
admin connected from 192.168.110.1 using ssh on devnet-lab
ios0# show running-config | include hostname
hostname nso.cisco.com
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$ cd packages/


(main) expert@devnet-lab:~/work/my_nso/packages$ ncs-make-package
--service-skeleton template simple-service
(main) expert@devnet-lab:~/work/my_nso/packages$ ls
cisco-ios-cli-3.8 simple-service
(main) expert@devnet-lab:~/work/my_nso/packages$ ls simple-service/
package-meta-data.xml src templates test

● You can see 2 folder created above src, templates


● Check their contents
(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;
}

// may replace this with other ways of referring to the


devices.
leaf-list device {
type leafref {
path "/ncs:devices/ncs:device/ncs:name";
}
}
// replace with your own stuff here
leaf dummy {
type inet:ipv4-address;
}
}
}

● Replace the part that says dummy as follows


// replace with your own stuff here
leaf dummy {
type inet:ipv4-address;
}
}

Replace above section with the below section

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>

● We need to modify the above sample template as shown below

<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'

● After rectifying errors it should look 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'
/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

User admin last logged in 2023-06-09T13:51:51.918629+00:00, to


devnet-lab, from 192.168.110.1 using cli-ssh
admin connected from 192.168.110.1 using ssh on devnet-lab
admin@ncs# packages reload

>>> System upgrade is starting.


>>> Sessions in configure mode must exit to operational mode.
>>> No configuration changes can be performed until upgrade has
completed.
>>> System upgrade has completed successfully.
reload-result {
package cisco-ios-cli-3.8
result true
}
reload-result {
package simple-service
result true
}
admin@ncs#
System message at 2023-06-09 16:58:29...
Subsystem stopped: ncs-dp-1-cisco-ios-cli-3.8:IOSDp2
admin@ncs#
System message at 2023-06-09 16:58:29...
Subsystem stopped: ncs-dp-2-cisco-ios-cli-3.8:IOSDp
admin@ncs#
System message at 2023-06-09 16:58:29...
Subsystem started: ncs-dp-3-cisco-ios-cli-3.8:IOSDp2
admin@ncs#
System message at 2023-06-09 16:58:29...
Subsystem started: ncs-dp-4-cisco-ios-cli-3.8:IOSDp

● We can verify the status of packages using the below command


admin@ncs# show packages package oper-status
packages package cisco-ios-cli-3.8
oper-status up
packages package simple-service
oper-status up
admin@ncs#

● Our service is now ready to start configuring the system


● NSO CLI now understand our service and starts offering auto complete hints

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

admin@ncs(config)# simple-service pwdchange device ios0 secret


mypasswd
admin@ncs(config-simple-service-pwdchange)# top
admin@ncs(config)# show configuration
simple-service pwdchange
device [ ios0 ]
secret mypasswd
!
admin@ncs(config)# commit
Commit complete.
admin@ncs(config)#

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 device ios0 secret
securepasswd
admin@ncs(config-simple-service-test1)# commit
Commit complete.

● Check the modification using the below command

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)#

● If the service is no longer needed, we can delete is as below

admin@ncs(config)# no simple-service test1


admin@ncs(config)# commit
Commit complete.
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

What is a data model?

● A data model is a well-understood and agreed-upon method to describe "something".


● E.g. Person:
○ First name: the name of the person.
○ Last name: the surname.
○ Date of birth: the day the person was born.
○ Job Role: such as admin, developer, or manager.
○ Email: the email address of the person.
● Sample YANG model to define a person would look like below

list person {
key email;

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";
}
}
}

● This YANG model describes ethernet interfaces


● Each individual attribute of the interface is represented by a leaf statement
● Every interface that is modeled with this YANG model has a name, type and description,
and can be enabled or disabled
● Interfaces are uniquely identified with the name. This is defined with the key attribute on
the leaf named "name".

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;
// ...
}

● We can add some additional information to this module as shown below

Unset
organization
"Example, Inc.";

contact
"Example, Inc.
Customer Service
E-mail: [email protected]";

description
"Access Control List (ACL) YANG model.";

revision 2021-07-06 {
description
"Initial revision";
}

● organization: Organization identifies the entity that created the module.


● contact: Contact identifies the contact responsible for the module.
● description: Description statement serves as an overview of the purpose of the
module.
● revision: Revision statement is used for version control, with sub statements detailing
the changes.

Containers and Leaves

● YANG uses different nodes to hold the actual data


● Think of a node as a data structure in a programming language
● The data inside a single node is also described using different data types
● To group several related nodes inside a single node, use a container node
● If you need a simple node for some basic data types, use a leaf node

Unset
container acl {

description

"Access Control Lists";

● A leaf node contains simple data such as an integer or a character string


● You can further restrict values of a particular type with the usage of the pattern,
range, and length restriction statements
● A pattern is defined with the regular expression (regex), range, and length are both
defined with integers

Unset
container acl {

...

leaf acl-description {

type string {

length "0..64";

pattern "[0-9a-zA-Z]*";

description "Purpose of ACL";

}
● 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

YANG Data Types

Common Data Types

Type Name Type Description

binary Text Any binary data

bits Text/Number A set of bits or flags

boolean Text "true" or "false"

decimal64 Number 64-bit fixed point real number

empty Empty A leaf that does not have any value


enumeration Text/Number Enumerated strings with associated numeric
values

identityref Text A reference to an abstract identity

instance-identifier Text References a data tree node

int8 Number 8-bit signed integer

int16 Number 16-bit signed integer

int32 Number 32-bit signed integer

int64 Number 64-bit signed integer

leafref Text/Number A reference to a leaf instance

string Text Human readable string

uint8 Number 8-bit unsigned integer

uint16 Number 16-bit unsigned integer

uint32 Number 32-bit unsigned integer

uint64 Number 64-bit unsigned integer

union Text/Number Choice of member types

Enumeration
● Used to define a finite set of values allowed
○ true/ false
○ enabled/ disabled
○ ipv4/ ipv6

Section 2.0: Python Crash Course

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
(main) expert@devnet-lab:~$ python -V
Python 3.9.10
(main) expert@devnet-lab:~$

● Python is an interpreted language (as opposed to being a compiled language)


● Check which interpreter you are using

(main) expert@devnet-lab:~$ which python


/home/expert/venvs/main/bin/python
(main) expert@devnet-lab:~$

● Start using interpreter in CLI mode

(main) expert@devnet-lab:~$ python


Python 3.9.10 (main, Jan 15 2022, 18:17:56)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more
information.
>>>

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

(main) expert@devnet-lab:~$ python


Python 3.9.10 (main, Jan 15 2022, 18:17:56)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more
information.
>>> hostname = "brch_rtr_01"
>>> make = "Nexus"
>>> model = "9300v"
>>> os_version = 9.3
>>> num_intfcs = 4
>>> print(hostname)
brch_rtr_01
>>> print(make)
Nexus
>>> num_intfcs
4
>>>

● 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

Operator Operation Example

+ 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

Operator Name Example

= 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

**= Exponent Assignment a **= 10 # a = a ** 10


# assign 10 to a
a = 10

# assign 5 to b
b = 5

# assign the sum of a and b to a


a += b # a = a + b

print(a)

# Output: 15

Comparison Operators
● Used to compare the value or variables
● Result of the comparison operators is always true or false (boolean)

Operator Meaning Example

== Is Equal To 3 == 5 gives us False

!= Not Equal To 3 != 5 gives us True

> Greater Than 3 > 5 gives us False

< Less Than 3 < 5 gives us True

>= Greater Than or Equal To 3 >= 5 give us False

<= Less Than or Equal To 3 <= 5 gives us True

a = 5

b = 2

# equal to operator
print('a == b =', a == b)

# not equal to operator


print('a != b =', a != b)

# greater than operator


print('a > b =', a > b)
# less than operator
print('a < b =', a < b)

# greater than or equal to operator


print('a >= b =', a >= b)

# less than or equal to operator


print('a <= b =', a <= b)

Logical Operators
● Used to check truthiness or falseness of expressions

Operator Example Meaning

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

not not a Logical NOT: True if the operand is False and


vice-versa.

# 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

Operator Meaning Example

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

in True if value/variable is found in the sequence 5 in x

not in True if value/variable is not found in the 5 not in x


sequence

x = 'Hello world'

# check if 'H' is present in x string


print('H' in x) # prints True

# check if 'hello' is present in x string


print('hello' not in x) # prints True

Getting Input from the user


● We have seen how to print something to the console, which we normally call output
● We can also take something from the console as input
● We use a built in function called input() for this
● This is useful in taking input from the console and assigning it to variables using the
assignment operator
● By default the input from the console will be read as a string
● Let us read the following input from the console and store them in appropriate variables
○ Number of edge routers
○ Names of those edge routers
○ Their IP Addresses

>>> num_routers = input('Enter the number of edge routers:')


Enter the number of edge routers:2
>>> router1_name = input('Enter the name of the first router:')
Enter the name of the first router:edge_rtr_01
>>> router2_name = input('Enter the name of the second router:')
Enter the name of the second router:edge_rtr_02
>>> router1_ip = input('Enter the IP Address of the first router:')
Enter the IP Address of the first router:192.168.10.1
>>> router2_ip = input('Enter the IP Address of the second
router:')
Enter the IP Address of the second router:192.168.10.2
>>> print(router2_name)
edge_rtr_02
>>>

Data Types - Deep Dive

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"

>>> hostname = 'edge_rtr_01'


>>> type(hostname)
<class 'str'>
>>>

● 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

upper() converts the string to uppercase

lower() converts the string to lowercase

partition() returns a tuple

replace() replaces substring inside

find() returns the index of first occurrence of substring

rstrip() removes trailing characters

split() splits string from left

startswith() checks if string starts with the specified string

isnumeric() checks numeric characters

index() returns index of substring

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

>>> commands = ["interface Etherent1/1", "switchport access vlan


10"]
>>> commands
['interface Etherent1/1', 'switchport access vlan 10']
>>> type(commands)
<class 'list'>
>>>

● 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

append() add an item to the end of the list

extend() add items of lists and other iterables to the end of the
list

insert() inserts an item at the specified index

remove() removes item present at the given index

pop() returns and removes item present at the given index

clear() removes all items from the list

index() returns the index of the first matched item

count() returns the count of the specified item in the list

sort() sort the list in ascending/descending order

reverse() reverses the item of the list

copy() returns the shallow copy 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

>>> dev_dict = {'vendor': 'cisco', 'hostname': 'csr2', 'ip':


'10.10.10.2'}
>>> dev_sub_dict = dev_dict["hostname"]
>>> dev_sub_dict
'csr2'
>>>

● 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

>>> dev = ['sw1', '10.1.100.1/24', '00:00:00:00:00:01']


>>> # hostname is stored at index 0
>>> # mac address is stored at index 2

● 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

>>> dev = {'hostname':'sw1', 'mgmt_ip':'10.1.100.1/24',


'mac':'00:00:00:00:00:01'}
>>> type(dev)
<class 'dict'>
>>> dev
{'hostname': 'sw1', 'mgmt_ip': '10.1.100.1/24', 'mac':
'00:00:00:00:00:01'}
>>>

Conditional Statements
● Decision making is a crucial component of programming for building logic
● We can alter the course the program using this decision making feature
● if...elif...else statements are used in Python for decision making
● Syntax is
if test expression:
statement(s)
● The statement(s) are executed only if the test expression is evaluated to True (Python
Boolean value)

if Statement
● As per the syntax seen above, the if statement should end with a colon ( : ) symbol
● The statements that follow must be indented (usually 4 by spaces)
● The combination of the if keyword and the statements forms a block
● TODO:
○ Check the os version of a device and add it to the list only if the version is equal
to a predefined value

>>> facts = {'vendor': 'arista', 'mgmt_ip': '10.1.1.1', 'chipset':


't2', 'hostname': 'NYC301', 'os': '6.1.2'}
>>> devices = []
>>> devices
[]
>>> if facts['os'] == '6.1.2':
... devices.append(facts['hostname'])
...
>>> devices
['NYC301']
>>>

if - else
● We have instructed the code what to do when the condition is satisfied
● We have seen that when the condition is not satisfied, the code does not do anything
● We will use if - else to rectify this
>>> command = ''
>>> if command:
... print('Command to send', command)
... else:
... print('No command to send')
...
No command to send
>>> #note that 0, empty string, None all evaluate to False

Loops
● In programing loops are used to repeat the execution of a block of code
● Two types of loops are used in Python
○ for loop
○ while loop
● For loop is used to iterate over a sequence or through a given set of objects
● While loop is used to iterate a block of code as long a test expression evaluates to True
● In that sense while is similar to if except that if stops execution after once whereas while
continues execution till the condition continues to be True

for Loop
● Used to iterate through a given set of objects
● Uses a user defined variable to hold each object temporarily
● Syntax
for item in sequence:
statement(s)
● TODO:
○ Create a list of routers and iterate through the list to print the name of each router

>>> routers = ['r1', 'r2', 'r3']


>>> for router in routers:
... print(router)
...
r1
r2
r3
>>>

● Let us see another example

>>> interfaces = ['Eth1/1', 'vlan20', 'Eth4/4', 'loop10']


>>> for interface in interfaces:
... if interface.lower().startswith('et'):
... itype = 'ethernet'
... elif interface.lower().startswith('vl'):
... itype = 'svi'
... elif interface.lower().startswith('lo'):
... itype = 'loopback'
... print(itype)
...
ethernet
svi
ethernet
loopback
>>>

while Loop Example


● Let us say we want to create 10 or 100 vlans
● We can use for loop for this
● But using a while loop would be much better

>>> vlan_count = 0
>>> while vlan_count < 10:
... print(f"Creating vlan_{vlan_count}")
... vlan_count += 1
...
Creating 0
Creating vlan_1
Creating vlan_2
Creating vlan_3
Creating vlan_4
Creating vlan_5
Creating vlan_6
Creating vlan_7
Creating vlan_8
Creating vlan_9
>>>

Functions
● Helps to break down large programs into small modular chunks
● Helps keep code organized and manageable
● More importantly helps us follow DRY (Don’t Repeat Yourself) principle
● We write function once and call it multiple times
● Syntax
def function_name(parameters):
"""docstring"""
statement(s)
● Functions are not new for us, as we have used quite a few of them already
A Few Built-in Functions
● print(), len()
● We can also chain the functions together
● len() returns the length which in turn can be passed to print for printing it to the console

>>> command = 'sh ip int br'


>>> print(command)
sh ip int br
>>> print(len(command))
12
>>>

Writing a Function
● Functions that we will create become user defined functions
● Follow the syntax shown above
● Starts with keyword def followed by a name to identify the function
● () for passing optional arguments followed by :
● Statements within the function will be indented uniformly
● Function definition ends with an optional return statement which returns some data to the
caller
● Defining a function will not execute the code in it, unless it is called elsewhere

>>> def get_vlans(device):


... if device == 'R1':
... vlans = [1, 5, 10, 11, 14, 15]
... else device == 'R2':
... vlans = [5, 10, 14, 15]
... return vlans
...
>>> r1_vlans = get_vlans('R1')
>>> print(r1_vlans)
[1, 5, 10, 11, 14, 15]
>>> r2_vlans = get_vlans('R2')
>>> print(r2_vlans)
[5, 10, 14, 15]

Functions With Optional Arguments


● While defining a function, we can define it with some arguments
● But while calling the function, we can omit them
● In such case we must provide a default value for them

>>> def ip_mask(ip, mask = 24):


... print("IP is {}/{}".format(ip, mask))
...
>>> ip_mask('10.250.206.21',32)
IP is 10.250.206.21/32
>>> ip_mask('10.250.206.21')
IP is 10.250.206.21/24
>>>

Functions With Keyword Arguments


● When we call a function along with values, the values get assigned to arguments
according to their position, as seen in the previous slide
● This allows us to change the order in which we pass the values
● But then, positional arguments have to come first, followed by keyword arguments

>>> def ip_mask(ip, mask):


... print("IP is {}/{}".format(ip, mask))
...
>>> ip_mask('10.250.206.21',32)
IP is 10.250.206.21/32
>>> ip_mask(32, '10.250.206.21')
IP is 32/10.250.206.21
>>> ip_mask(mask=32, ip='10.250.206.21')
IP is 10.250.206.21/32
>>>

Functions With Arbitrary Arguments


● Sometimes the number of arguments to be passed is not known in advance
● In such cases we define the function with arbitrary or variable number of arguments
● We can mix variable length and fixed arguments, in which case fixed arguments must be
keyword arguments
● The arbitrary argument defined to hold variable number of values is treated as an
iterable item
● We can extract values from it using a for loop
>>> def router_vlan(*routers, vlan):
... for router in routers:
... print("Router {} belongs to VLAN {}.".format(router,
vlan))
...
>>> router_vlan('r1', vlan=10)
Router r1 belongs to VLAN 10.
>>> router_vlan('r1', 'r2', 'r3', vlan=10)
Router r1 belongs to VLAN 10.
Router r2 belongs to VLAN 10.
Router r3 belongs to VLAN 10.
>>>

Libraries
● Modules
○ Collection of functions and global variables stored as a file with .py extension
○ It is an executable file
● Package
○ Simple directory containing collection of modules and sub packages (sub
directories)
○ It has a __init__.py file which is used by Python interpreter to identify this
directory as Python package

Built-in module - json


● We need to import json package before we can use it
● Once imported we can check the available methods in it using dir()

>>> import json


>>> dir(json)
['JSONDecodeError', 'JSONDecoder', 'JSONEncoder', '__all__',
'__author__', '__builtins__', '__cached__', '__doc__', '__file__',
'__loader__', '__name__', '__package__', '__path__', '__spec__',
'__version__', '_default_decoder', '_default_encoder', 'codecs',
'decoder', 'detect_encoding', 'dump', 'dumps', 'encoder', 'load',
'loads', 'scanner']
>>>

● As we are going to see further, json format is quite similar to Python dictionaries
● One use of json module is to pretty print Python dictionaries
● json objects are quite similar to Python dictionaries
● We “dump” Python objects into json string using dumps() method
● This process is called serialization

>>> device_facts = {'chipset': 't2', 'hostname': 'NYC301',


'vendor': 'arista'}
>>> device_facts
{'chipset': 't2', 'hostname': 'NYC301', 'vendor': 'arista'}
>>> type(device_facts)
<class 'dict'>
>>> json_str = json.dumps(device_facts)
>>> json_str
'{"chipset": "t2", "hostname": "NYC301", "vendor": "arista"}'
>>> type(json_str)
<class 'str'>
>>> json_str = json.dumps(device_facts, indent=4)
>>> print(json_str)
{
"chipset": "t2",
"hostname": "NYC301",
"vendor": "arista"
}
>>> json_str = json.dumps(device_facts)
>>> print(json_str)
{"chipset": "t2", "hostname": "NYC301", "vendor": "arista"}
>>>

● We can also deserialize data and load them into the program during the run time
● We will use loads() method for this
>>> json_str = '{"chipset": "t2", "hostname": "NYC301", "vendor":
"arista"}'
>>> json_obj= json.loads(json_str)
>>> json_obj
{'chipset': 't2', 'hostname': 'NYC301', 'vendor': 'arista'}
>>> type(json_obj)
<class 'dict'>
>>> json_obj['chipset']
't2'
>>>

Working with files


● So far we have either hard coded the data or read the data from the user as input
● What is more useful in network automation is reading the data from files
● Accordingly Python supports both reading data from and writing data to files
● We can read data from text files, XML files,JSON files, CSV files and so on
● All the above files are plain text format files, also termed as flat files
● We can also read data from Excel files using some special libraries

Working with text files


● Create a file with config commands as strings

$ cat conf.txt
en
conf t
hostname CR2
int g 0/1
ip address dhcp
no shut

● Open the file using the read() command and check the details
>>> conf_file = open('conf.txt')
>>> conf_file
<_io.TextIOWrapper name='conf.txt' mode='r' encoding='UTF-8'>
>>> dir(conf_file)
['_CHUNK_SIZE', '__class__', '__del__', '__delattr__', '__dict__',
'__dir__', '__doc__', '__enter__', '__eq__', '__exit__',
'__format__', '__ge__', '__getattribute__', '__gt__', '__hash__',
'__init__', '__init_subclass__', '__iter__', '__le__', '__lt__',
'__ne__', '__new__', '__next__', '__reduce__', '__reduce_ex__',
'__repr__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__', '_checkClosed', '_checkReadable',
'_checkSeekable', '_checkWritable', '_finalizing', 'buffer',
'close', 'closed', 'detach', 'encoding', 'errors', 'fileno',
'flush', 'isatty', 'line_buffering', 'mode', 'name', 'newlines',
'read', 'readable', 'readline', 'readlines', 'reconfigure', 'seek',
'seekable', 'tell', 'truncate', 'writable', 'write',
'write_through', 'writelines']
>>>

● We have methods like read(), readline(), readlines()


● Let us try them and understand

>>> print(conf_data)
en
conf t
hostname CR2
int g 0/1
ip address dhcp
no shut

>>> type(conf_data)
<class 'str'>
>>>
● Try readline() now

>>> conf_line = conf_file.readline()


>>> conf_line
''
>>>

● We are getting blank line because we already read the data using read() method
● So the file pointer is at the bottom of the file now
● We can move the pointer back to the top or anywhere within the file using seek() method

>>> conf_file.seek(0)
0
>>> conf_line = conf_file.readline()
>>> print(conf_line)
en

>>> conf_line = conf_file.readline()


>>> print(conf_line)
conf t

>>>

● Let us go back to the top of the file and use readlines()

>>> conf_file.seek(0)
0
>>> conf_lines = conf_file.readlines()
>>> print(conf_lines)
['en\n', 'conf t\n', 'hostname CR2\n', 'int g 0/1\n', 'ip address
dhcp\n', 'no shut\n']
>>> type(conf_lines)
<class 'list'>
>>>
● We can also write data to files using the write mode
● Open a file for writing using the "w" value instead "r" for reading

>>> out_file = open('interface.cfg', 'w')


>>> out_file.write("interface Eth1\n")
15
>>> out_file.write(" speed 100\n")
11
>>> out_file.write(" duplex full\n")
13
>>> out_file.close()

● Opened the file in write mode, wrote data and closed the file
● Closing the file is not mandatory but is recommended as a coding best practice
● Without using the context manager working with files opening them for read write
operations and forgetting to close them can leave them in inconsistent state
● The with statement ensures that files is closed as soon as we are done working with it

>>> out_file = open('interface.cfg', 'w')


>>> out_file.write("interface Eth1\n")
15
>>> out_file.write(" speed 100\n")
11
>>> out_file.write(" duplex full\n")
13
>>> out_file.close()

Working with CSV files


● Create a CSV file “devices.csv” as follows
Device,IP
Router1,192.168.1.1
Switch1,192.168.1.2

● We can read the data from the CSV file using a built in library called csv as follows

# Read devices from CSV file


with open('devices.csv', 'r') as csv_file:
reader = csv.reader(csv_file)
header = next(reader) # Skip the header row
device_idx = header.index('Device')
ip_idx = header.index('IP')

for row in reader:


device_name = row[device_idx]
device_ip = row[ip_idx]

● The csv library also offers a better method to read csv data directly as Python
dictionaries as shown in the below example

# Read devices from CSV file


with open('devices.csv', 'r') as csv_file:
reader = csv.DictReader(csv_file)
for row in reader:
device_name = row['Device']
device_ip = row['IP']

● We can also write data to csv files using writer method


● However we must first arrange the data in CSV suitable format, like list of lists where
each list in the outer list corresponds to a row in CSV file
# Data to be written
data = [
['John Doe', '[email protected]'],
['Jane Smith', '[email protected]'],
['Bob Johnson', '[email protected]']
]

# Open the CSV file in write mode


with open('contacts.csv', 'w', newline='') as csv_file:
writer = csv.writer(csv_file)

# Write the header


writer.writerow(['Name', 'Email'])

# Write the data rows


writer.writerows(data)

● If the data is available as dictionaries, we can also use DictWriter method to write data to
CSV files

# Data to be written
data = [
{'Name': 'John Doe', 'Email': '[email protected]'},
{'Name': 'Jane Smith', 'Email': '[email protected]'},
{'Name': 'Bob Johnson', 'Email': '[email protected]'}
]

# Field names/columns
fieldnames = ['Name', 'Email']

# Open the CSV file in write mode


with open('contacts.csv', 'w', newline='') as csv_file:
writer = csv.DictWriter(csv_file, fieldnames=fieldnames)

# Write the header


writer.writeheader()

# Write the data rows


writer.writerows(data)

Working with JSON files


● We have already seen how to manipulate JSON data using the builtin json library
● Here we will see how to work with JSON files
● Create a sample json file called network_devices.json with the following content

{
"devices": [
{
"name": "Router1",
"ip": "192.168.1.1"
},
{
"name": "Switch1",
"ip": "192.168.1.2"
},
{
"name": "Firewall1",
"ip": "192.168.1.3"
}
]
}
● We can read the data from json files as shown below

import json

# Read JSON file


with open('network_devices.json') as f:
data = json.load(f)

# Access the devices


devices = data['devices']
for device in devices:
print(f"Name: {device['name']}, IP: {device['ip']}")

● Writing new data to json file can be down as shown below

import json
# Add a new device
new_device = {
"name": "Switch2",
"ip": "192.168.1.4"
}
devices.append(new_device)

# Write updated data to JSON file


with open('network_devices.json', 'w') as f:
json.dump(data, f, indent=4)

Working with YAML files


● YAML files have become de facto choice for representing configuration in the networking
as well as devops world
● Python provides a library yaml to work with yaml files
● Create a yaml file called config.yaml with the content shown below
network:
interface: eth0
ip_address: 192.168.1.10
subnet_mask: 255.255.255.0
hostname: router1

● We can read data from yaml files as shown below

import yaml

with open('config.yaml', 'r') as file:


config_data = yaml.safe_load(file)

# Accessing specific values


interface = config_data['network']['interface']
ip_address = config_data['network']['ip_address']

● Writing new data to yaml file can be done as shown below

import yaml

# Create YAML data


config_data = {
'network': {
'interface': 'eth0',
'ip_address': '192.168.1.10',
'subnet_mask': '255.255.255.0',
},
'hostname': 'router1',
}
# Save the data to a YAML file
with open('config.yaml', 'w') as file:
yaml.dump(config_data, file)

Netmiko
● Paramiko is a Python implementation of SSHv2 protocol
● Netmiko is a multi-vendor library to simplify Paramiko SSH connections to network
devices
● Netmiko simplifies:
○ Establishing ssh connection to various devices
○ Execution of show commands used to retrieve config data
○ Execution of configuration commands sent to the device
○ Working with a broad range of devices from different vendors

Getting Started
● Install netmiko using the following command
○ python -m pip install netmiko

● Import "ConnectHandler" from the Netmiko library


● ConnectHandler as your main entry point into the library
● It picks the right class for you, creates a Netmiko object based on that class, and
establishes an SSH connection to the remote device.
(main) expert@devnet-lab:~$ python
Python 3.9.10 (main, Jan 15 2022, 18:17:56)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more
information.
>>> from netmiko import ConnectHandler
>>>

● You would then use ConnectHandler to pick the class and establish the SSH connection
● ConnectHandler needs the following details to connect to the device
○ device_type
○ host (hostname or IP)
○ username
○ password

(main) expert@expert-cws:~/work$ python


Python 3.9.10 (main, Jan 15 2022, 18:17:56)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more
information.
>>> from netmiko import ConnectHandler
>>>

>>> conn = ConnectHandler(


...
... device_type="cisco_ios",
...
... host = "192.168.5.101",
...
... username = "expert",
...
... password = "1234QWer!"
...
... )
>>> conn
<netmiko.cisco.cisco_ios.CiscoIosSSH object at 0x7f41a718b3a0>
>>>

● We are now ready to start issuing commands to the device from our code
● Let us see which prompt we are currently at

>>> conn.find_prompt()
'brch-rtr-01#'
>>>

● Executing show commands

>>> output = conn.send_command("show ip arp")


>>> print(output)
Protocol Address Age (min) Hardware Addr Type
Interface
Internet 172.16.54.0 - 5000.0003.0001 ARPA
GigabitEthernet2
Internet 172.16.54.2 - 5000.0003.0002 ARPA
GigabitEthernet3
Internet 172.16.54.6 - 5000.0003.0003 ARPA
GigabitEthernet4
>>>

● Let us now try to send config commands to the device

>>> cfg_list = [
...
... "ip access-list extended TEST1",
...
... "permit ip any host 1.1.1.1",
...
... "permit ip any host 1.1.1.2",
...
... "permit ip any host 1.1.1.3",
...
... "permit ip any host 1.1.1.4",
...
... "permit ip any host 1.1.1.5",
...
... ]
>>> cfg_list
['ip access-list extended TEST1', 'permit ip any host 1.1.1.1',
'permit ip any host 1.1.1.2', 'permit ip any host 1.1.1.3', 'permit
ip any host 1.1.1.4', 'permit ip any host 1.1.1.5']
>>> cfg_output = conn.send_config_set(cfg_list)
>>> print(cfg_output)
configure terminal
Enter configuration commands, one per line. End with CNTL/Z.
brch-rtr-01(config)#ip access-list extended TEST1
brch-rtr-01(config-ext-nacl)#permit ip any host 1.1.1.1
brch-rtr-01(config-ext-nacl)#permit ip any host 1.1.1.2
brch-rtr-01(config-ext-nacl)#permit ip any host 1.1.1.3
brch-rtr-01(config-ext-nacl)#permit ip any host 1.1.1.4
brch-rtr-01(config-ext-nacl)#permit ip any host 1.1.1.5
brch-rtr-01(config-ext-nacl)#end
brch-rtr-01#
>>> conn.save_config()
'write mem\n\nBuilding configuration...\n[OK]\nbrch-rtr-01#'
>>>

You might also like