L'automatisation des réseaux avec Netmiko (Python)

Netmiko est une librairie python développée par Kirk Byers, basée sur Paramiko elle facilite la connexion aux différents équipements réseaux (Cisco, JunOS, Aruba etc.). Nous étudierons différents exemples et scripts permettant de faciliter l'administration réseau au quotidien.

L'automatisation des réseaux avec Netmiko (Python)

Netmiko est une librairie python développée par Kirk Byers, basée sur Paramiko elle facilite la connexion aux différents équipements réseaux (Cisco, JunOS, Aruba etc.).

Nous étudierons différents exemples et scripts permettant de faciliter l'administration réseau au quotidien.

Pré-requis :

  • Serveur Linux avec un accès aux différents routeurs
  • Python 2.7/3.5/3.6
  • pip
La version de python utilisée durant cette présentation est la 2.7

Installation de la librairie netmiko :

pip install netmiko

Connexion sur une liste de routeurs :

Le script va boucler sur une liste de routeurs et effectuer la commande "show run int fa0" :

Voici la liste des routeurs routeur.list :

172.31.1.182	routeur_1
172.31.2.153	routeur_2
172.31.1.209	routeur_3
172.31.90.119	routeur_10

Voici le script sh_run_int_fa0.py :

#!/usr/bin/python
# Importation des librairies nécessaires
import getpass
import netmiko
# Affiche un prompt pour stocker le mot de passe dans la variable "passwd"
passwd = getpass.getpass()
injoignable = []

# On ouvre la liste des routeurs et on créer une liste des routeurs dans la variable fh2
with open("routeur.list") as fh:
        fh2=fh.readlines()

# Pour chacune des lignes dans fh2 on split le nom du routeur et l'adresse IP
for line in fh2:
	ip = (line.split()[0])
	id = (line.split()[1])
# On définit le dictionnaire cisco nécessaire à netmiko
 	cisco = {
	'device_type': 'cisco_ios',
	'ip': ip,
	'username': 'user',
	'password': passwd,
	}
# On tente de se connecter sur l'équipement et de lancer la commande "sh run int fa0"
	try:
		net_connect = netmiko.ConnectHandler(**cisco)
		result=net_connect.send_command("show run int fa0")
# Sinon on ajoute le nom de l'équipement dans la liste injoignable
    except :
		injoignable.append(id)
		continue
# On affiche ensuite le résultat de sh run int fa0 pour chacun des routeurs
    print result
# On se déconnecte de l'équipement
	net_connect.disconnect()
# On affiche la liste des sites injoignables
print "sites injoignables :"
print "\n".join(injoignable) 

La commande net_connect.send_command("command") permet donc de lancer une commande en mode enable.

On lance ensuite le script :

Création d'un nouvel utilisateur sur un ensemble de routeurs :

Nous allons maintenant voir comment entrer des commandes en mode configuration sur les équipements.

Le script va se connecter sur l'ensemble des routeurs de la liste routeur.list, si l'utilisateur "matthieu" existe il ne fait rien, sinon il créer l'utilisateur en question :

#!/usr/bin/python
import getpass
import netmiko
passwd = getpass.getpass()
injoignable = []

with open("routeur.list") as fh:
        fh2=fh.readlines()

for line in fh2:
	ip = (line.split()[0])
	id = (line.split()[1])
	cisco = {
	'device_type': 'cisco_ios',
	'ip': ip,
	'username': 'user',
	'password': passwd,
	}
	try:
		net_connect = netmiko.ConnectHandler(**cisco)
		result=net_connect.send_command("show run | inc username ")
	except :
		injoignable.append(id)
		continue
 	if "matthieu" in result:
		print "l'utilisateur matthieu existe deja sur", id
	else:
		print "creation de l'utilisateur matthieu sur", id, ".."
# On rentre en mode config avant de créer l'utilisateur
		net_connect.config_mode()
		net_connect.send_command("username matthieu password ueihttam")
# On sort du mode config puis on sauvegarde
		net_connect.exit_config_mode()
		net_connect.send_command("wr")
    net_connect.disconnect()
# On affiche la liste des sites injoignables
print "sites injoignables :"
print "\n".join(injoignable) 

Résultat du script :


Prenons l'exemple de la commande ci-dessous :

net_connect.send_command("interface vlan1") 
net_connect.send_command("ip address 1.1.1.1 255.255.255.0")

Celle-ci ne fonctionnera pas car une fois l'interface vlan 1 créée, le routeur se rend directement dans la configuration de l'interface routeur(config-if) or netmiko n'arrive pas à interpréter cette situation.

Il faudra alors utiliser la commande net_connect.send_config_set("cmd1","cmd2") pour envoyer au routeur toute la configuration sans attendre le prompt du "conf t" en retour.

net_connect.send_config_set("interface vlan1","ip address 1.1.1.1 255.255.255.0") 

Accélerer la connexion aux équipements

L'utilisation de ce script peut s'avérer fastidieuse dès lors que l'on souhaite se connecter sur un grand nombre d'équipements, il faut presque 10 secondes par équipement entre l'établissement de la session, l'envoi de la commande et l'affichage du résultat.

Voici une solution permettant de paralléliser les processus et les connexions en utilisant la librairie thread de Python.

Ce script se base sur celui-ci, et permet de lancer X processus en simultané.

import os
from os import listdir
from os.path import isfile,join,expanduser
import getpass
import threading
import logging
import time

from netmiko import ConnectHandler
from netmiko.ssh_exception import NetMikoTimeoutException
from netmiko.ssh_exception import NetMikoAuthenticationException



#-----------------------------------------------------------
def get_wd():
    """Create directory for file output"""
    wd = os.path.expanduser('temp/')
    if not os.path.exists(wd):
            os.makedirs(wd)
    return wd
#-----------------------------------------------------------
def del_temp_files():
    """Delete temporary files that were created on the previous run"""

    # temp_files_dir = (wd)
    list_temp_dir = os.listdir(wd)
    ext = (".json",".csv",".txt",".log")
    for item in list_temp_dir:
        if item.endswith(ext):
    		os.remove(os.path.join(wd, item))

#--------------------------------------------------------------
def ssh_connection(ip, ref, username, password):
    """open SSH connection to device"""
    try:
        return ConnectHandler(device_type='cisco_ios',
                                ip=ip,
                                username=username,
                                password=password,
                                )

    except Exception as error :
        logger.error('. %&%&%&%&%& {} {}   \t   {}'.format(ref, ip, error))
        with open ("{}conn_error.txt".format(wd), "a") as efile:
		efile.write('{} {} \n'.format(ref, ip))
#----------------------------------------------------------------
def get_worker(ip, ref, device):
    """Send the command to the device and write the result in a file"""
    try: 
	
	result = device.send_command("show run")
	with open ("{}result.txt".format(wd), "a") as file1:
        file1.write('{} {} {}\n'.format(ref, ip, result))
    except Exception as error:
	logger.error(". Get Error  {}  {}   \t    {}".format(ref, ip, error))
#------------------------------------------------------------------
def main(ip, ref, username, password):
    """Connect to device using netmiko"""
    device = ssh_connection(ip, ref, username, password)
    #if we can't connect, release the lock and move on
    if device == None:
        sema.release()
        return

    # call get_data function to run commands
    output = get_worker(ip, ref, device)
    # Disconnect the SSH session
    device.disconnect()
    sema.release()

if __name__ == '__main__':
    
    wd = get_wd()
    del_temp_files()
    #set up threading to utilize (max_threads count) concurrent SSH connections
    threads = []
    max_threads = 20
    sema = threading.BoundedSemaphore(value=max_threads)

    # Prompt for user credentials
    user = raw_input("Enter Username: ")
    passwd = getpass.getpass("Enter Password: ")
    
    start_time = time.time()
    #set up logging
    logger = logging.getLogger("LOG")
    handler = logging.FileHandler("{}main.log".format(wd))
    logger.setLevel(logging.DEBUG)
    logger.addHandler(handler) 
    # read /etc/hosts in list
    with open("/etc/hosts") as fh:
	devices=fh.readlines()
    for host in devices:
        sema.acquire()
        ip = host.split()[0]
        ref = host.split()[1]
        thread = threading.Thread(target=main, args=(ip, ref, user, passwd))
        threads.append(thread)
        thread.start()
    
    elapsed_time = time.time() - start_time
    print("Script run time = " + time.strftime("%H:%M:%S", time.gmtime(elapsed_time)))