Python3. Automatisation de la configuration des équipements réseau multi-fournisseurs

Préface


Bonne journée à tous!

Je travaille en tant qu'ingénieur réseau pour un grand opérateur de télécommunications, et sous mon contrôle il y a tout un zoo de divers équipements réseau, mais nous parlerons de commutateurs d'accès.

Cet article n'est pas un guide d'action, ce n'est pas la seule solution et ne prétend clairement pas être un scénario de la nomination de l'année, mais je veux partager cette création, et peut-être qu'elle sera utile à quelqu'un.

L'article fournira un bloc de code sous le spoiler, et ci-dessous sera une description avec des coupures et des explications sur la raison pour laquelle il en est ainsi et à quoi il sert.

Défi


Utilisez python3 spécifiquement. Le script doit pouvoir accéder aux commutateurs à partir de la liste, déterminer le type de fournisseur, donner la commande requise, enregistrer.

En fait, comment je suis arrivé à cela


Face à un petit problème de changement de configuration sur un grand nombre de commutateurs, je vais tout de suite faire une réserve sur le système d'administration centralisée des équipements réseau dont nous disposons, de nombreux développements de mes collègues sous forme de scripts pour faciliter le travail manuel sont également disponibles, principalement bash, perl.

Mais pour moi, il était important de faire quelque chose de différent, car J'ai commencé à apprendre le python récemment et je dois améliorer mes compétences en programmation, et mon outil devrait ressembler à un marteau (facile à utiliser et facile à entretenir). Si l'intérêt persiste, je demande un chat.

Googler et ne pas trouver la bonne solution (comme si sur les forums cette question était posée, mais tout allait mal là-bas), j'ai décidé de commencer à écrire mon propre script.

Nous avons les commutateurs suivants:

  • Raisecom
  • Qtech rev. 1.0
  • Qtech rev. 2.0 (différence de syntaxe)
  • Eltex
  • Lien D
  • Spawn Frankenstein switch avec un museau de Qtech rev 1.0, et du fer de Raisecom nous l'appellerons ROS

Tout d'abord, importez les bibliothèques requises:

import telnetlib import time import os import sys import getpass import pexpect from telnetlib import Telnet import datetime import subprocess 

Dans un premier temps, nous décrivons la fonction de définition du fournisseur, car a décidé d'utiliser deux méthodes:

1) Supposons de quel type de fournisseur il s'agit (à l'invitation à entrer la connexion) car j'ai remarqué que c'est différent pour tout le monde: Qtech (connexion :), Raisecom et ROS (Connexion :), Eltex (Nom d'utilisateur :), D-link (Nom d'utilisateur).

2) après la connexion - assurez-vous que le premier élément a été effectué sans erreur et pour une meilleure identification de l'interrupteur sur lequel nous sommes.

Fonction
 def who_is(): #   global ver_status global sab_versus global versus sab_versus='' ver_status='' versus='' #  a = 'Serial No.:1405' # #qtech rev1 d = 'Command: show switch' # #d-link f = 'Raisecom Operating System Software' # #raisecom h = 'QOS' # #raisecom-qtech j = 'MES1124M' # #eltex k = 'eltex' # #eltex n = 'Build245' #qtech2.0 parser=open('servers_&_log/ver.txt', 'r') #  for line in parser.readlines(): line1 = line.find(a) line4 = line.find(d) line5 = line.find(f) line6 = line.find(h) line7 = line.find(j) line8 = line.find(k) line10 = line.find(n) if line1 != -1: ver_status='qtech_rev_1.0' if line4 != -1: ver_status='d-link' if line5 != -1: ver_status='raisecom' if line6 != -1: ver_status='raisecom-qtech' sab_versus=1 if line7 !=-1: ver_status='eltex' if line8 !=-1: ver_status='eltex' if line10 != -1: ver_status = 'qtech_rev_2.0' time.sleep(0.1) parser.close() os.remove('servers_&_log/ver.txt') return ver_status,sab_versus 


Toute la fonction est sous le spoiler. Nous déclarons des variables globales qui seront visibles par l'ensemble du script:

 global ver_status global sab_versus global versus 

les variables a, d, f, h, j, k, n stockent les mots-clés par lesquels nous déterminerons par la suite le modèle du commutateur.

 a = 'Serial No.:1405' # #qtech rev1 d = 'Command: show switch' # #d-link f = 'Raisecom Operating System Software' # #raisecom h = 'QOS' # #raisecom-qtech j = 'MES1124M' # #eltex k = 'eltex' # #eltex n = 'Build245' #qtech2.0 

Après avoir ouvert le fichier ver.txt, nous le lisons ligne par ligne et vérifions l'entrée par mots-clés, car la fonction find () renvoie -1 si ce résultat est négatif, et nous allons construire des branches.

 parser=open('servers_&_log/ver.txt', 'r') #  for line in parser.readlines(): line1 = line.find(a) line4 = line.find(d) line5 = line.find(f) line6 = line.find(h) line7 = line.find(j) line8 = line.find(k) line10 = line.find(n) if line1 != -1: ver_status='qtech_rev_1.0' 

...

Je donne un exemple de partie du code, le reste sous le spoiler ci-dessus.

Le contenu de ce fichier sera décrit ci-dessous. Après toutes les manipulations et la définition du fournisseur, supprimez le fichier ver.txt.

Déclaration des variables principales, corps de la boucle principale
 user='user' password='password' komm=open('servers_&_log/komm.txt') log=open('servers_&_log/log.txt','a') ######### counter_komm=0 counter_dlink=0 counter_qtech=0 counter_eltex=0 counter_raisecom=0 counter_ROS=0 counter_qtech1_0=0 counter_qtech2_0=0 ########## for host in komm.readlines(): print('connect....',host) vend = '' response = os.system('ping -c 1 ' + host) verinfo = open('servers_&_log/ver.txt', 'w') ver_status = '' sab_versus = '' if response == 0: telnet = pexpect.spawn('telnet ' + host,timeout=40) vend = telnet.expect(['login:', 'Login:', 'User Name:', 'Username']) telnet.close() tn = Telnet(host.replace('\n', ''), 23,30) try: print('Ok'+'\n') tn.read_until(b':') tn.write((user +'\n').encode('ascii')) tn.read_until(b':') tn.write((password + '\n').encode('ascii')) time.sleep(3) tn.read_until(b'#',timeout=20) except: print('connection refused' + '\n') f = open('servers_&_log/log.txt', 'a') print(host, 'connection refused', file=log) print('#' * 100, host, file=log) ###   ######################################################### if vend == 0:#    Qtech tn.write(('show ver' + '\n').encode('ascii')) print((tn.read_until(b'#').decode('ascii')), file=verinfo) verinfo.close() who_is() ... 


J'ai décidé de ne pas déranger et de définir les variables avec le nom d'utilisateur et le mot de passe dans le corps, je sais que ce n'est pas de la sécurité, je travaille dessus.

Ouvrez le fichier pour les journaux et la liste des commutateurs

 komm=open('servers_&_log/komm.txt') log=open('servers_&_log/log.txt','a') 

Après avoir utilisé la boucle for, lisez la ligne avec l'adresse IP du commutateur

 for host in komm.readlines(): print('connect....',host) vend = '' 

Nous vérifions sa disponibilité

 response = os.system('ping -c 1 ' + host) 

S'il est accessible à l'aide de la bibliothèque pexpect, nous essayons de nous connecter via telnet ici à cette itération et la première vérification à l'invitation a lieu, dont j'ai parlé au début.

 if response == 0: telnet = pexpect.spawn('telnet ' + host,timeout=40) vend = telnet.expect(['login:', 'Login:', 'User Name:', 'Username']) telnet.close() tn = Telnet(host.replace('\n', ''), 23,30) 

La variable vend obtiendra une valeur de 0 à 3 inclus, et en fonction de l'invite de connexion qu'elle voit, une autre branche sera formée.

À partir de ce morceau de code, un lecteur attentif peut remarquer que je fais une connexion au commutateur et ferme immédiatement la connexion, et ce n'est pas sans raison. J'ai essayé d'utiliser uniquement la bibliothèque telnetlib, mais lors du premier test, le script se bloquait et tombait périodiquement par timeout, et cette béquille m'aide beaucoup.

Après avoir fermé la connexion, nous ne nous reconnectons qu'en utilisant déjà la bibliothèque telnetlib.

Afin d'éviter de tomber des erreurs, nous avons déjà vérifié ci-dessus que le commutateur est accessible, et excluons l'interruption du script pendant le fonctionnement en raison d'un commutateur inactif, enveloppez tout dans un essai sauf le bloc.

Il y a eu des cas répétés où sur 100 commutateurs, un ne s'est pas laissé entrer.

 try: print('Ok'+'\n') tn.read_until(b':') tn.write((user +'\n').encode('ascii')) tn.read_until(b':') tn.write((password + '\n').encode('ascii')) time.sleep(3) tn.read_until(b'#',timeout=20) except: print('connection refused' + '\n') f = open('servers_&_log/log.txt', 'a') print(host, 'connection refused', file=log) print('#' * 100, host, file=log) 

...
Si tout va bien et que nous sommes connectés, nous devons saisir un nom d'utilisateur et un mot de passe,
nous savons avec certitude que toute invite de deux points utilise un deux-points.

Nous l'attendons donc

 tn.read_until(b':') 

Après avoir entré la connexion

 tn.write((user +'\n').encode('ascii')) 

En attente de deux points du mot de passe

 tn.read_until(b':') 

Entrez le mot de passe

 tn.write((password + '\n').encode('ascii')) 

Et nous attendons 3 secondes (répit, sinon nous avons fait tellement de travail)

 time.sleep(3) 

Après avoir attendu l'invitation

 tn.read_until(b'#',timeout=20) 

Ici, à ce stade, nous passons au deuxième niveau pour vérifier le fournisseur.

 if vend == 0:#    Qtech tn.write(('show ver' + '\n').encode('ascii')) print((tn.read_until(b'#').decode('ascii')), file=verinfo) verinfo.close() who_is() 

Parce que nous avons supposé que nous sommes arrivés à Qtech, et si vous lisez attentivement, alors dans notre zoo, il existe deux versions de qtech qui diffèrent dans la syntaxe, nous devons encore nous réconcilier.

Par conséquent, nous donnons la commande show ver, mettons toute la sortie dans le fichier ver.txt
et appelez la procédure who_is (), que j'ai décrite ci-dessus. L'équipe show ver est universelle pour tous les Qtech, Raisecom, Eltex,

Malheureusement, D-link ne comprend pas et il doit dire montrer swich, mais nous sommes intelligents et n'avons pas vainement introduit une itération avec la définition supposée d'un fournisseur.

 if vend == 3:#   D-link tn.write(('show sw' + '\n').encode('ascii')) tn.write(('a' + '\n').encode('ascii')) print((tn.read_until(b'#').decode('ascii')), file=verinfo) verinfo.close() who_is() 

Donc, immédiatement, une petite remarque après avoir entré show swich, le commutateur affiche des informations incomplètes et attend que l'utilisateur appuie sur n'importe quelle touche pour une sortie supplémentaire, et donc nous envoyons ensuite le caractère "a" pour afficher des informations complètes.

 tn.write(('a' + '\n').encode('ascii')) 

Pour éviter cela, vous pouvez désactiver le clipadding

C'est là que la vérification du fournisseur se termine et avec une probabilité de 99%, nous pouvons supposer que nous avons correctement identifié le modèle du commutateur et que nous pouvons procéder à la configuration.

Pour chaque commutateur, nous avons un fichier séparé avec un ensemble de commandes.

Bloc de configuration
 ... elif ver_status == 'qtech_rev_1.0': tn.write(('show ver' + '\n').encode('ascii')) print((tn.read_until(b'#').decode('ascii')), file=log) counter_qtech1_0+=1 komand_qtech1_0=open('servers_&_log/komand_qtech_ver1.0.txt') for kommand in komand_qtech1_0.readlines(): tn.write((kommand.replace('\n', '') + '\n').encode('ascii')) tn.write(('exit' + '\n').encode('ascii')) print(tn.read_all().decode('ascii'), file=log) print('   qtech1.0') print('Qtech rev1.0 ' + host, file=log) print('#' * 100, file=log) ################################################################################ elif ver_status == 'qtech_rev_2.0': tn.write(('show ver' + '\n').encode('ascii')) print((tn.read_until(b'#').decode('ascii')), file=log) counter_qtech2_0+=1 komand_qtech2_0=open('servers_&_log/komand_qtech_ver2.0.txt') print('   qtech2.0') for kommand in komand_qtech2_0.readlines(): tn.write((kommand.replace('\n', '') + '\n').encode('ascii')) print((tn.read_until(b'#').decode('ascii')), file=log) print('Qtech rev2.0 ' + host, file=log) print('#' * 100, file=log) ... 


Une fois que la fonction a fonctionné et renvoyé la variable ver_status, nous pouvons continuer à travailler avec la branche, car nous savons exactement quel commutateur est actuellement en ligne.
Tout d'abord, nous donnons au commutateur la commande show ver et écrivons la sortie dans le journal (à propos de d-link, rappelez-vous que nous lui donnons la commande sh sw)

 tn.write(('show ver' + '\n').encode('ascii')) print((tn.read_until(b'#').decode('ascii')), file=log) 

Assurez-vous de garder des compteurs pour identifier les non-conformités.
 counter_qtech1_0+=1 

Nous ouvrons un fichier avec des commandes
 komand_qtech1_0=open('servers_&_log/komand_qtech_ver1.0.txt') 

L'ordre des commandes dans le fichier doit être le même que l'administrateur les saisirait manuellement

Un exemple:

conf
vlan 2525
name SPD
int ethe 1/1
sw mode access
sw acc vl 2525
exit
exit
save
y

Ensuite, tout est conforme au scénario - nous lisons le fichier jusqu'à la fin des lignes et nous les exécutons

 for kommand in komand_qtech1_0.readlines(): tn.write((kommand.replace('\n', '') + '\n').encode('ascii')) 

Après avoir quitté la boucle, nous indiquons la sortie du commutateur et lisons toutes les sorties dans un fichier, et donc nous ne le faisons qu'avec qtech1.0, car parfois un script colle en prévision de quelque chose et c'est avec ce switch, pour ne pas faire d'histoires, cette solution me paraissait plus élégante.

Une fois le commutateur configuré, nous augmentons le compteur total d'une unité.

 counter_komm+=1 

Et nous recommençons, lisons la ligne suivante du fichier avec les commutateurs, déterminons le modèle et réalisons la configuration.

Après avoir quitté le cycle principal, vous devez faire un résumé du travail effectué,
à la fin du journal, nous entrons tous les modèles que nous avons traités et la date est obligatoire, eh bien, comment pourrait-il en être sans.

 print('\n\n\n : ', counter_komm,file=log) print('\n\nD-link:', counter_dlink,'\nQtech ver1.0:', counter_qtech1_0,'\nROS:', counter_ROS,'\nRaisecom:',counter_raisecom,'\nEltex:', counter_eltex,'\nQtech ver2.0 :', counter_qtech2_0,file=log) print('\n: ', datetime.datetime.now().isoformat(),'\n', '#'*100,'\n\n\n\n\n',file=log) verinfo.close() log.close() 

Ce script a été traité à plusieurs reprises et le processus ne s'arrêtera pas là.
Merci à tous pour votre attention. La critique constructive est la bienvenue.

Tout le code sous le spoiler.

Code source
 #!/usr/bin/python3 #22/06/2017 17:17 import telnetlib import time import os import sys import getpass import pexpect from telnetlib import Telnet import datetime import subprocess def who_is(): #     ,      . global ver_status global sab_versus global versus sab_versus='' ver_status='' versus='' a = 'Serial No.:1405' # #qtech rev1 #b = 'Serial No.:3922' # #qtech rev2 #c = 'Serial No.:5436' # #qtech rev2 #l = 'Serial No.:16101' # #qtech rev2 d = 'Command: show switch' # #d-link f = 'Raisecom Operating System Software' # #raisecom h = 'QOS' # #raisecom-qtech j = 'MES1124M' # #eltex k = 'eltex' # #eltex n = 'Build245' #qtech2.0 parser=open('servers_&_log/ver.txt', 'r') for line in parser.readlines(): line1 = line.find(a) #line2 = line.find(b) #line3 = line.find(c) line4 = line.find(d) line5 = line.find(f) line6 = line.find(h) line7 = line.find(j) line8 = line.find(k) #line9 = line.find(l) line10 = line.find(n) if line1 != -1: ver_status='qtech_rev_1.0' #if line2 != -1 or line3 != -1 or line9 !=-1: #ver_status='qtech_rev_2.0' if line4 != -1: ver_status='d-link' if line5 != -1: ver_status='raisecom' if line6 != -1: ver_status='raisecom-qtech' sab_versus=1 if line7 !=-1: ver_status='eltex' if line8 !=-1: ver_status='eltex' if line10 != -1: ver_status = 'qtech_rev_2.0' time.sleep(0.1) parser.close() os.remove('servers_&_log/ver.txt') return ver_status,sab_versus user='user' password='password' komm=open('servers_&_log/komm.txt') log=open('servers_&_log/log.txt','a') counter_komm=0 counter_dlink=0 counter_qtech=0 counter_eltex=0 counter_raisecom=0 counter_ROS=0 # counter_qtech1_0=0 counter_qtech2_0=0 for host in komm.readlines(): print('connect....',host) vend = '' #tn = Telnet(host.replace('\n', ''), 23, 20) response = os.system('ping -c 1 ' + host) verinfo = open('servers_&_log/ver.txt', 'w') ver_status = '' sab_versus = '' if response == 0: telnet = pexpect.spawn('telnet ' + host,timeout=40) vend = telnet.expect(['login:', 'Login:', 'User Name:', 'Username']) telnet.close() tn = Telnet(host.replace('\n', ''), 23,30) try: print('Ok'+'\n') tn.read_until(b':') tn.write((user +'\n').encode('ascii')) tn.read_until(b':') tn.write((password + '\n').encode('ascii')) time.sleep(3) tn.read_until(b'#',timeout=20) except: print('connection refused' + '\n') f = open('servers_&_log/log.txt', 'a') print(host, 'connection refused', file=log) print('#' * 100, host, file=log) ###   ################################################################################ if vend == 0:#    Qtech tn.write(('show ver' + '\n').encode('ascii')) print((tn.read_until(b'#').decode('ascii')), file=verinfo) verinfo.close() who_is() ################################################################################ if vend == 1: #   Raisecom,Raisecom-Qtech tn.write(('show ver' + '\n').encode('ascii')) print((tn.read_until(b'#').decode('ascii')), file=verinfo) verinfo.close() who_is() ################################################################################ if vend == 2:#   Eltex tn.write(('show system' + '\n').encode('ascii')) tn.write(('show ver' + '\n').encode('ascii')) print((tn.read_until(b'#').decode('ascii')), file=verinfo) verinfo.close() who_is() ################################################################################ if vend == 3:#   D-link tn.write(('show sw' + '\n').encode('ascii')) tn.write(('a' + '\n').encode('ascii')) print((tn.read_until(b'#').decode('ascii')), file=verinfo) verinfo.close() who_is() # ###  # ################################################################################ if ver_status == 'd-link': tn.read_until(b'#', timeout=20) tn.write(('show sw' + '\n').encode('ascii')) tn.write(('a' + '\n').encode('ascii')) print((tn.read_until(b'#').decode('ascii')), file=log) print('D-link ' + host,file=log) print('#'*100,file=log) counter_dlink+=1 ################################################################################ elif ver_status == 'qtech_rev_1.0': tn.write(('show ver' + '\n').encode('ascii')) print((tn.read_until(b'#').decode('ascii')), file=log) counter_qtech1_0+=1 komand_qtech1_0=open('servers_&_log/komand_qtech_ver1.0.txt') for kommand in komand_qtech1_0.readlines(): tn.write((kommand.replace('\n', '') + '\n').encode('ascii')) #print((tn.read_until(b'#').decode('ascii')), file=log) tn.write(('exit' + '\n').encode('ascii')) print(tn.read_all().decode('ascii'), file=log) print('Qtech rev1.0 ' + host, file=log) print('#' * 100, file=log) ################################################################################ elif ver_status == 'qtech_rev_2.0': tn.write(('show ver' + '\n').encode('ascii')) print((tn.read_until(b'#').decode('ascii')), file=log) counter_qtech2_0+=1 komand_qtech2_0=open('servers_&_log/komand_qtech_ver2.0.txt') for kommand in komand_qtech2_0.readlines(): tn.write((kommand.replace('\n', '') + '\n').encode('ascii')) print((tn.read_until(b'#').decode('ascii')), file=log) time.sleep(1) print('Qtech rev2.0 ' + host, file=log) print('#' * 100, file=log) ################################################################################ elif ver_status == 'raisecom-qtech': tn.write(('show ver' + '\n').encode('ascii')) print((tn.read_until(b'#').decode('ascii')), file=log) raisecom_command = open('servers_&_log/komand_raisecom.txt') for komand in raisecom_command.readlines(): tn.write((komand.replace('\n', '') + '\n').encode('ascii')) print((tn.read_until(b'#').decode('ascii')), file=log) print('ROS '+ host,file=log) print('#'*100,file=log) counter_ROS+=1 ################################################################################ elif ver_status == 'raisecom': tn.write(('show ver' + '\n').encode('ascii')) print((tn.read_until(b'#').decode('ascii')), file=log) counter_raisecom+=1 raisecom_command = open('servers_&_log/komand_raisecom.txt') for komand in raisecom_command.readlines(): tn.write((komand.replace('\n', '') + '\n').encode('ascii')) print((tn.read_until(b'#').decode('ascii')), file=log) print(host, ':', 'RAISECOM', file=log) print('#' * 100, host, file=log) ################################################################################ elif ver_status == 'eltex': tn.write(('show ver' + '\n').encode('ascii')) print((tn.read_until(b'#').decode('ascii')), file=log) eltex_command=open('servers_&_log/komand_eltex.txt') for komand in eltex_command.readlines(): tn.write((komand.replace('\n', '') + '\n').encode('ascii')) print((tn.read_until(b'#').decode('ascii')), file=log) print('Eltex ' + host, file=log) print('#' * 100, file=log) counter_eltex+=1 else: print('no ping') counter_komm+=1 print('\n\n\n : ', counter_komm,file=log) print('\n\nD-link:', counter_dlink,'\nQtech ver1.0:', counter_qtech1_0,'\nROS:', counter_ROS,'\nRaisecom:',counter_raisecom,'\nEltex:', counter_eltex,'\nQtech ver2.0 :', counter_qtech2_0,file=log) print('\n: ', datetime.datetime.now().isoformat(),'\n', '#'*100,'\n\n\n\n\n',file=log) verinfo.close() log.close() 

Source: https://habr.com/ru/post/fr415453/


All Articles