7 months, 3 weeks

Configuring Network Devices Using Netmiko




 

Netmiko, developed by Kirk Byers, is an open source library designed to simplify SSH management across a wide range of network devices from various vendors including Cisco, Arista, and Juniper Networks. Netmiko is very popular and similar to Paramiko with a few significant differences:device support, performance.
Netmiko abstracts many of these complexities for you, by providing a Python library with a set of easy to use methods. This purpose-built abstraction becomes particularly useful when dealing with devices from different vendors and not with a homogeneous single-vendor deployment. Additionally, netmiko provides a nice set of helper functions that allow you to carry out common workflows, such as connecting to a device and deploying a configuration from a file, with fewer lines of code.

Features
Structured parsing - Supports parsing via the TTP, TextFSM and Genie parsing libraries.
Multi-vendor - Supports a large set of multi-vendor devices.
Device configuration - Provides methods for applying configuration from a list of commands or a file of commands.
Device config - Supports various methods for reading configuration from devices.
Stability tuning - Supports various options for ensuring stability for slow devices or network transports

Please also install the netmiko package (python3 -m pip install netmiko).

When dealing with a network device and its connection in netmiko we will generally deal with ConnectHandler. This is our central interface to open a connection to a device and write commands to the device as well as reading the output back.

Follow these steps to establish a connection from your Python script to a network device using netmiko:

1. Import ConnectHandler from netmiko:
from netmiko import ConnectHandler

2. Create a dictionary that will contain our connection details. 

connection_info = {
 'device_type': 'cisco_ios',
 'host': '<insert your host here>',
 'port': <insert your port number here>,
 'username': '<insert your username here>',
 'password': '<insert your password here>'
}

3. Next, we are going to use a Python context manager to open a connection, which is similar to opening a file:
with ConnectHandler(**connection_info) as conn:
     print("Successfully connected!")

The following are the handlers for the most common (network) device vendors:
• cisco_ios, cisco_xe, and cisco_xr for connecting to Cisco IOS devices
• cisco_nxos for connecting to devices running the Cisco NX-OS operating system 
• linux for connecting to generic Linux devices such as servers
• juniper, juniper_junos, and juniper_screenos for connecting to devices from Juniper
• arista_eos for connecting to Arista devices
• hp_comware and hp_procurve for connecting to devices from HP
• huawei, huawei_smartax, huawei_olt, and huawei_vrpv8 for connecting to devices from Huawei

Netmiko also defines a context manager for its ConnectHandler so we can use the with statement to use it. With this, netmiko also takes care of closing the connection to the device once we have executed all our commands. 

We use the double asterisk operator to unpack the information specified in the dictionary and pass that information on as keyword arguments to ConnectHandler. Therefore, it is important to use the exact same spelling since it refers to the connection_info dictionary. 

The **syntax seen in the preceding section is often used in conjunction with a concept called variadic arguments. Variadic arguments allow a function to receive an arbitrary number of arguments from the user without specifying the arguments. 

Sending Commands Using Netmiko
Follow these steps to connect to a network device and send commands using netmiko:
1. Import ConnectHandler from netmiko:
from netmiko import ConnectHandler
2. Create a dictionary that will contain our connection details.
connection_info = {
 'device_type': 'cisco_ios',
 'host': '<insert your host here>',
 'port': <insert your port number here>,
 'username': '<insert your username here>',
 'password': '<insert your password here>'
}
3. Next, we are going to use a Python context manager to open a connection similar to how we can open a file. Inside of that context manager, we'll then use the send_command() method of ConnectHandler to send our show interfacescommand to the device and print back the output:
with ConnectHandler(**connection_info) as conn:
     out = conn.send_command("show interfaces")
     print(out)

We specify the connection details for our device, the host, port, username, and password, as well as the device type. Netmiko uses the device type internally to provide a common interface to send commands to devices and operating systems from different vendors.

Genie

"Genie is a Python library which contains all the tools needed for Networking Test Automation. Genie is the library solution for pyATS. The main goals of Genie is to facilitates rapid development, encourage re-usable libraries and simplify writing test automation." 
pyATS stands for Python Automation Test System.It was released to Devnet in 2017.pyATS is a foundation-layer test framework. It is designed to provide a reasonable end-to-end test environment for developers to write test cases in, featuring multiple packages and modules making writing networking-related tests a breeze.
We install these two additional packages by issuing python3 -m pip install pyats genie. Please note that Genie and pyATS are not supported on Windows. If you use Windows as your main operating system you may need to either install Linux in a virtual machine or use the Windows Subsystem for Linux 2 to use pyATS/Genie.

Retrieving command outputs as structured Python data using Netmiko and Genie

Using the following steps you can retrieve the output of a command as structured Python data instead of plaintext:
1. Import the ConnectHandler from netmiko. Additionally, we'll import the pretty print module that allows us to print a dictionary with better formatting:
from netmiko import ConnectHandler
import pprint

2. Create a dictionary that will contain our connection details. 
connection_info = {
 'device_type': 'cisco_ios',
 'host': '<insert your host here>',
 'port': <insert your port number here>,
 'username': '<insert your username here>',
 'password': '<insert your password here>'
}

3.Inside of that context manager, we'll then use the send_command() method of the ConnectHandler to send our show interfaces command to the device and print back the output. Notice how we have set the use_genie flag of send_command to True: 
with ConnectHandler(**connection_info) as conn: 
    out = conn.send_command("show interfaces", use_genie=True)
4. Next, we'll print out the entire dictionary we received using prettyprint.
    pprint.pprint(out)

5. We iterate over all the items within the dictionary. 
    for interface in out.keys():
         print(interface)
We can then use the built-in pretty print library to get a formatted version of the dictionary printed back to us.

Gathering Facts Using Netmiko
Using the following steps you can retrieve the output of our show interfaces command as structured Python data that will then be parsed further:
1. Import ConnectHandler from netmiko. Additionally, we'll import the pretty print module that allows us to print a dictionary with better formatting:
from netmiko import ConnectHandler
import pprint

2. Create a dictionary that will contain our connection details. 
connection_info = {
 'device_type': 'cisco_ios',
 'host': '<insert your host here>',
 'port': <insert your port number here>,
 'username': '<insert your username here>',
 'password': '<insert your password here>'
}

3. Inside of that context manager, we'll then use the send_command() method of the ConnectHandler to send our show interfaces command to 
the device and print back the output. Notice that we have set the use_genie flag of send_command to True: 
with ConnectHandler(**connection_info) as conn:
  out = conn.send_command("show interfaces", use_genie=True)

4. With our structured data retrieved we can now iterate over each of the interfaces and print the information we want.
  for name, details in out.items():
    print(f"{name}")
    print(f"- Status: {details.get('enabled', None)}")
    print(f"- Physical address: {details.get('phys_address', None)}")
    print(f"- Duplex mode: {details.get('duplex_mode', None)}")

5. With our basic information printed out, we can now iterate over all counters and their values to check if any of those counters is over 0.
   for counter, count in details.get('counters', {}).items():
       if isinstance(count, int):
          if count > 0:
             print(f"- {counter}: {count}")
       elif isinstance(count, dict):
          for sub_counter, sub_count in count.items():
              if sub_count > 0:
                 print(f"- {counter}::{sub_counter}: {sub_count}") 


Connecting to multiple devices

Using the following steps we can read connection details from a JSON file, connect to each specified device, execute a command, and save the output of the command per device:
1. Open the connections.json file in your code editor.
[
 {
 "name": "device-1",
 "connection": {
 "device_type": "<insert your device type here>",
 "host": "<insert your host here>",
 "port": <insert your port number here>,
 "username": "<insert your username here>",
 "password": "<insert your password here>"
 }
 },
{
 "name": "device-2",
 "connection": {
 "device_type": "<insert your device type here>",
 "host": "<insert your host here>",
 "port": <insert your port number here>,
 "username": "<insert your username here>",
 "password": "<insert your password here>"
 }
 }
]

2.We are going to import the required libraries:
import json
from netmiko import ConnectHandler

3. Next, we need to read the connection details from our JSON file:
devices = []
with open("connections.json", "r") as fh:
   devices = json.load(fh)

4. We can now iterate over each device in the devices list using the connection information stored in the JSON file, issue the defined command, and then save the output to a new file based on the name we gave the device in the JSON file:
CMD = "show running-config"
for device in devices:
  file_name = f"{device['name']}.out"
  print(f"Retrieving config for {device['name']}")
  with ConnectHandler(**device['connection']) as conn:
     out = conn.send_command(CMD)
     with open(file_name) as f:
         f.write(out)

Creating and applying a configuration template with Jinja2 and netmiko

 You can install the newest version of Jinja2 using python3 -m pip install jinja2. 

1. Open the acl.conf.tpl file. This is the template that is being rendered. Here, we are going to first specify the interface for our ACL and then loop over the two lists we have provided as an argument to create the necessary commands to allow or deny the host from the network:
interface {{ intf }}
ip access-group 1 in
{% for host in disallowed %}
access-list 1 deny host {{ host }}
{% endfor %}
{% for host in allowed %}
access-list 1 permit host {{ host }}
{% endfor %}

2. Open the apply_config.py file. We'll start by importing the required libraries (netmiko and Jinja2):
from netmiko import ConnectHandler
from jinja2 import Environment, FileSystemLoader

3. With our libraries imported we can define the connection details of our device:
connection_info = {
 'device_type': 'cisco_ios',
 'host': '<insert your host here>',
 'port': <insert your port number here>,
 'username': '<insert your username here>',
 'password': '<insert your password here>'
}

4. Next, we need to set up our Jinja2 environment to render the template:
loader = FileSystemLoader("templates")
environment = Environment(loader=loader)
tpl = environment.get_template("acl.conf.tpl")

5. Now that we have our template, we need to define two lists that hold the IPs we want to deny and those we want to allow. Additionally, we are going to specify the interface that we want this ACL to be configured on in the following manner:
allowed = [
 "10.10.0.10",
 "10.10.0.11",
 "10.10.0.12"
]
disallowed = [
 "10.10.0.50",
 "10.10.0.62"]
intf = "ethernet0"

6. With the information needed to render our template in place, we can create a configuration from our template and write the output to a file:
out = tpl.render(allowed=allowed, disallowed=disallowed, intf=intf)
with open("configuration.conf", "w") as f:
   f.write(out)

7. Now that our template is rendered and written to a file called configuration.conf, we can establish a connection using ConnectHandler from netmiko.

with ConnectHandler(**connection_info) as conn:
   out = conn.send_config_from_file("configuration.conf")

If you have a list of configuration commands that you would like to issue and you already have that list as a Python list, you can make this easier in the following way: 
commands = [
 'interface ethernet0',
 ' ip access-group 1 in',
 'access-list 1 deny host 10.10.10.10'
]
with ConnectHandler(**connection_info) as conn:
 out = conn.send_config_set(commands)

Copying files to a device using netmiko
We are going to  see how netmiko has integrated the secure copy (or scp) protocol that works on top of SSH to allow us to easily transfer files to and from a device.

Follow these steps to establish a connection to the specified device and transfer a file to it:
1. Import the ConnectHandler from netmiko as well as the file_transfer function:
from netmiko import ConnectHandler, file_transfer

2. We need to specify the required connection information:
connection_info = { 
 'device_type': 'cisco_ios',
 'host': '<insert your host here>',
 'port': <insert your port number here>,
 'username': '<insert your username here>',
 'password': '<insert your password here>',
}

3. With our connection information defined, we can open a ConnectHandler to our remote device and use the file_transfer function imported before to 
upload our local file:
with ConnectHandler(**connection_info) as conn:
   ret = file_transfer(conn, source_file="test_upload.txt", dest_file="test_on_device.txt",
       file_system="<insert target filesystem here>", direction="put")
   for k, v in ret.items():
       print(f"{k}: {v}")

The file_transfer function takes the following arguments:
• source_file is the path to the local file we want to upload.
• dest_file is the name of the file in our destination.
• file_system allows us to specify which file system we want to write to on the target device, such as bootflash, flash, or disk0.
• direction specifies whether we are transferring to or from a device. To transfer files from the local machine to a device, we use put, and to transfer a file from a remote device to our local device, we use the get direction.

The file_transfer function returns a dictionary that contains three Boolean flags related to our remote file:
• file_exists indicates that the file does now exist on the target device.
• file_transfered indicates whether the file actually had to be transferred or whether it was already present on the target device.
• file_verified indicates whether the MD5 checksum of the transferred file matches the MD5 signature of the local file.

By default, the transferred file will be verified by comparing the MD5 hash. This verification can take a long time and thus you can disable it by setting the disable_md5 flag to True.We can have the file_transfer function overwrite files by setting the overwrite_file flag to True.

Escalating privileges with netmiko

Follow these steps to programmatically enter enable mode before issuing a command:
1. Import ConnectHandler from netmiko:
from netmiko import ConnectHandler
2. Create a dictionary that will contain our connection details.
connection_info = {
 'device_type': 'cisco_ios',
 'host': '<insert your host here>',
 'port': <insert your port number here>,
 'username': '<insert your username here>',
 'password': '<insert your password here>',
 'secret': '<insert enable secret here>'
}
3. Next, we are going to use a Python context manager to open a connection, similar to opening a file. Inside of that context manager, we'll then use the send_command() method of the ConnectHandler to send our configuration commands as follows:
with ConnectHandler(**connection_info) as conn:
   conn.enable()
   out = conn.send_command("show running-config")
   print(out)


Authenticating using public-private keys with netmiko

Follow these steps to authenticate using a specific key file:
1. Import ConnectHandler from netmiko:
from netmiko import ConnectHandler

2. Create a dictionary that will contain our connection details.
connection_info = {
 'device_type': 'cisco_ios',
 'host': '<insert your host here>',
 'port': <insert your port number here>,
 'username': '<insert your username>',
 'use_keys': True,
 'key_file': '<insert path to private key here>'
}

3. Next, we are going to use a Python context manager for opening a connection, similar to how we open a file. Inside of that context manager, we'll then use the send_command() method of the ConnectHandler to send our show interfaces command to the device and print back the output:
with ConnectHandler(**connection_info) as conn:
   out = conn.send_command("show interfaces")
   print(out)

Handling commands that prompt for information using netmiko

Follow these steps to answer the prompt of a command:
1. Import ConnectHandler from netmiko:
from netmiko import ConnectHandler
2. Create a dictionary that will contain our connection details.
connection_info = {
 'device_type': 'cisco_ios',
 'host': '<insert your host here>',
 'port': <insert your port number here>,
 'username': '<insert your username>',
 'password': '<insert your password here>',
}
3. Next, we are going to use a Python context manager for opening a connection similar to opening a file. Inside of that context manager, we'll then use the send_command() method of the ConnectHandler to send our delete command to the device and expect and answer the prompts:
with ConnectHandler(**connection_info) as conn:
 conn.send_command("delete flash:/test_upload.txt", expect_string=r"Delete filename",
 strip_prompt=False, strip_command=False)
 conn.send_command("\n",  expect_string=r"confirm", strip_prompt=False, strip_command=False)
 conn.send_command("y", expect_string=r"#", strip_prompt=False, strip_command=False)


 


Responses(0)







Related