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