Python3. Automação da configuração de equipamentos de rede de vários fornecedores

Prefácio


Bom dia a todos!

Trabalho como engenheiro de rede de uma grande operadora de telecomunicações e, sob meu controle, existe um zoológico inteiro de vários equipamentos de rede, mas falaremos sobre switches de acesso.

Este artigo não é um guia de ação, não é a única solução e claramente não pretende ser um roteiro da indicação do ano, mas quero compartilhar essa criação, e talvez seja útil para alguém.

O artigo fornecerá um bloco de código sob o spoiler e, a seguir, haverá uma descrição com recortes e explicações sobre por que é assim e para que serve.

Desafio


Use python3 especificamente. O script deve ser capaz de acessar os comutadores da lista, determinar que tipo de fornecedor, fornecer o comando necessário, log.

Na verdade, como cheguei a isso


Diante de um pequeno problema de alterar a configuração em um grande número de comutadores, farei imediatamente uma reserva sobre o sistema de administração centralizada para equipamentos de rede que temos, muitos desenvolvimentos de meus colegas na forma de scripts para facilitar o trabalho manual também estão disponíveis, principalmente o bash, perl.

Mas para mim era importante fazer algo diferente, porque Comecei a aprender python recentemente e preciso atualizar minhas habilidades em programação, e minha ferramenta deve parecer um martelo (fácil de usar e fácil de manter). Se ainda houver interesse, peço um gato.

Pesquisando no Google e não encontrando a solução adequada (como se nos fóruns essa questão tivesse sido levantada, mas estava tudo errado lá), decidi começar a escrever meu próprio script.

Temos as seguintes opções:

  • Raisecom
  • Qtech rev. 1.0
  • Qtech rev. 2.0 (diferença na sintaxe)
  • Eltex
  • D-link
  • Spawn Frankenstein switch com um focinho de Qtech rev 1.0, e ferro da Raisecom chamaremos ROS

Primeiro, importe as bibliotecas necessárias:

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

Inicialmente, descrevemos a função de definição de fornecedor, porque decidiu usar dois métodos:

1) Suponha que tipo de fornecedor (no convite para entrar no login), pois notei que é diferente para todos: Qtech (login :), Raisecom e ROS (login :), Eltex (nome do usuário :), D-link (nome de usuário).

2) após o login - verifique se o primeiro item foi concluído sem erros e para uma melhor identificação de qual opção estamos ativados.

Função
 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 


Toda a função está sob o spoiler. Declaramos variáveis ​​globais que serão visíveis para todo o script:

 global ver_status global sab_versus global versus 

as variáveis ​​a, d, f, h, j, k, n armazenam as palavras-chave pelas quais determinaremos subsequentemente o modelo do comutador.

 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 

Após abrir o arquivo ver.txt, lemos linha por linha e verificamos a entrada por palavras-chave, porque a função find () retorna -1 se esse resultado for negativo, e construiremos ramificações.

 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' 

...

Dou um exemplo de parte do código, o restante sob o spoiler acima.

O conteúdo deste arquivo será descrito abaixo. Após todas as manipulações e definição do fornecedor, exclua o arquivo ver.txt.

Declaração das principais variáveis, corpo do loop principal
 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() ... 


Decidi não me incomodar e definir as variáveis ​​com nome de usuário e senha no corpo, sei que não é segurança, estou trabalhando nisso.

Abra o arquivo para os logs e a lista de opções

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

Depois de usar o loop for, leia a linha com o endereço IP do switch

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

Verificamos a disponibilidade

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

Se estiver acessível usando a biblioteca pexpect, tentaremos conectar via telnet aqui nesta iteração e ocorrerá a primeira verificação no convite, sobre a qual escrevi no começo.

 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) 

A variável vend obterá um valor de 0 a 3, inclusive, e dependendo do prompt de login exibido, uma outra ramificação será formada.

A partir desse código, um leitor atento pode perceber que estou fazendo uma conexão com o switch e fechando imediatamente a conexão, e isso não é sem razão. Tentei usar apenas a biblioteca telnetlib, mas no primeiro teste, o script travou e caiu periodicamente com o tempo limite, e essa muleta ajuda muito.

Depois de fechar a conexão, fazemos a reconexão apenas usando a biblioteca telnetlib.

Para evitar erros, já verificamos acima se o comutador está acessível e excluímos a interrupção do script durante a operação devido a um comutador inativo, envolva tudo em uma tentativa, exceto no bloco.

Houve casos repetidos em que de 100 interruptores, um não se deixava entrar

 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) 

...
Se está tudo bem e estamos conectados, precisamos inserir um nome de usuário e senha,
sabemos com certeza que qualquer prompt de dois pontos usa dois pontos.

Então, estamos esperando por ele

 tn.read_until(b':') 

Depois de entrar no login

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

Aguardando dois pontos da senha

 tn.read_until(b':') 

Digite a senha

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

E esperamos 3 segundos (pausa, caso contrário, fizemos muito trabalho)

 time.sleep(3) 

Depois de esperar pelo convite

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

Aqui, nesta etapa, passamos para o segundo nível para verificar o fornecedor.

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

Porque assumimos que chegamos ao Qtech e, se você ler atentamente, em nosso zoológico existem duas versões do qtech que diferem na sintaxe, ainda precisamos reconciliar.

Portanto, damos o comando show ver, colocamos toda a saída no arquivo ver.txt
e chame o procedimento who_is (), que eu descrevi acima. A equipe show ver é universal para todas as empresas Qtech, Raisecom, Eltex,

Infelizmente, o D-link não entende e ele precisa dizer show swich, mas somos inteligentes e não em vão introduzimos uma iteração com a suposta definição de fornecedor.

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

Então, imediatamente, uma pequena observação depois de entrar no show swich, o comutador exibe informações incompletas e aguarda o usuário pressionar qualquer tecla para obter mais informações e, portanto, enviamos o caractere "a" para exibir informações completas.

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

Para evitar isso, você pode desativar o recorte

É aqui que a verificação do fornecedor termina e, com uma probabilidade de 99%, podemos assumir que identificamos corretamente o modelo do comutador e podemos prosseguir com a configuração.

Para cada opção, temos um arquivo separado com um conjunto de comandos.

Bloco de configuração
 ... 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) ... 


Depois que a função funcionou e retornou a variável ver_status, podemos continuar trabalhando com a ramificação, porque sabemos exatamente qual opção está atualmente em jogo.
Primeiro, damos ao switch o comando show ver e escrevemos a saída no log (sobre o d-link, lembre-se de dar o comando sh sw)

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

Certifique-se de manter contadores para identificar não-conformidades.
 counter_qtech1_0+=1 

Abrimos um arquivo com comandos
 komand_qtech1_0=open('servers_&_log/komand_qtech_ver1.0.txt') 

A ordem dos comandos no arquivo deve ser a mesma que o administrador os digitaria manualmente

Um exemplo:

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

Então tudo está de acordo com o cenário - lemos o arquivo até o final das linhas e os executamos

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

Depois de sair do loop, informamos à saída do switch e lemos toda a saída em um arquivo, e o fazemos apenas com qtech1.0, porque Às vezes, um script se antecipa a algo e é com essa opção, para não causar confusão, essa solução me pareceu mais elegante.

Após a configuração do switch, aumentamos o contador total em um.

 counter_komm+=1 

E começamos tudo de novo, lemos a próxima linha do arquivo com os comutadores, determinamos o modelo e fazemos a configuração.

Depois de sair do ciclo principal, você precisa fazer um resumo do trabalho realizado,
no final do log, inserimos todos os modelos que processamos e a data é obrigatória, bem, como poderia ser sem ele.

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

Este script foi processado repetidamente e o processo não para por aí.
Obrigado a todos pela atenção. Críticas construtivas são bem-vindas.

Todo o código sob o spoiler.

Código fonte
 #!/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/pt415453/


All Articles