Pull to refresh

Python3. Автоматизация конфигурации мультивендорного сетевого оборудования

Reading time 13 min
Views 13K

Предисловие


Всем доброго времени суток!

Работаю сетевым инженером у крупного оператора связи, и под моим управлением имеется целый зоопарк разного сетевого оборудования, но речь пойдет о коммутаторах доступа.

Данная статья это не руководство к действию, не единственное решение и явно не претендует на номинацию скрипт года, но я хочу поделиться этим творением, и может быть кому-нибудь он пригодится.

В статье будет приводиться блок кода под спойлером, а под ним будет описание с вырезками и объяснениями почему именно так и для чего это.

Задача


Использовать именно python3. Скрипт должен уметь попадать на коммутаторы из списка, определять что за вендор, давать требуемую команду, логировать.

Собственно как я к этому пришел


Столкнулся с небольшой проблемой по изменению конфигурации на большом количестве коммутаторов, сразу оговорюсь системы централизованного администрирования сетевого оборудования у нас есть, куча наработок моих коллег в виде скриптов для облегчение ручного труда тоже имеется, в основном bash, perl.

Но для меня было важно сделать что-то свое, т.к. изучать python я начал недавно и нужно прокачивать скил в программировании, и мой инструмент должен быть похож на молоток (прост в использовании, и легок в обслуживании). Если интерес до сих пор остался тогда прошу под кат.

Погуглив и не найдя должного решения (как бы на форумах этот вопрос поднимался, но там всё не то ), решил приступить к написанию собственного скрипта.

Имеем следующие коммутаторы:

  • Raisecom
  • Qtech rev. 1.0
  • Qtech rev. 2.0 (отличие в синтаксисе)
  • Eltex
  • D-link
  • Порождение Франкенштейна коммутатор с мордой от Qtech rev 1.0, а железо от Raisecom будем звать его ROS

Для начала импортируем требуемые библиотеки:

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

Первоначально опишем функцию определения вендора, т.к. решил использовать два метода:

1) предположить — что это за вендор (по приглашению ввода логина) как я заметил у всех он разный: Qtech (login:), Raisecom и ROS (Login:), Eltex(User Name:), D-link(Username).

2) после того как залогинился — убедиться, что первый пункт выполнился без ошибки, и для более лучшего выявления на каком коммутаторе мы находимся.

Функция

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


Вся функция под спойлером. Объявляем глобальные переменные, которые будут видны всему скрипту:

global ver_status
global sab_versus
global versus

переменные a,d,f,h,j,k,n хранят ключевые слова, по которым мы в последствии будем определять модель коммутатора.

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

После открытия файла ver.txt считываем построчно и проверяем на вхождение по ключевым словам, т.к. функция find() возвращает при отрицательном результате -1 по этой логике мы и будем строить ветвления.

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'



привожу в пример часть кода, остальное под спойлером выше.

Наполнение данного файла будет описано ниже. После всех манипуляций и определения вендора удаляем файл ver.txt.

Объявление основных переменных, тело основного цикла
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()
...


Решил не заморачиваться и в теле задать переменные с логином и паролем, знаю что не секьюрно, я работаю над этим.

Открываем файл для логов и список коммутаторов


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

После используем цикл for, считываем строку с ip-адресом коммутатора

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

Проверяем его на доступность

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

Если он доступен при помощи библиотеки pexpect, делаем попытку подключения по telnet вот на этой итерации и происходит первая проверка по приглашению, о которой между прочим я писал в начале.


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)

Переменная vend получит значение от 0 до 3 включительно, а в зависимости от того, какое приглашение входа увидит — будет формироваться дальнейшее ветвление.

Из данного куска кода внимательный читатель может заметить, что я делаю подключение к коммутатору и сразу закрываю соединение, и это не спроста. Я пытался использовать только библиотеку telnetlib, но на первой проверке скрипт периодически залипал и отваливался по таймауту, а данный костыль неплохо выручает.

После закрытия подключения мы делаем повторное подключение только используя уже библиотеку telnetlib.

Чтобы избежать вываливание ошибок, а выше мы уже проверили что коммутатор доступен, и исключить прерывание скрипта в процессе работы из-за одного неработающего коммутатора обернем все в блок try except.

Были неоднократные случаи когда из 100 коммутаторов один на себя не пускал.


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)


Если все хорошо и мы подключились, то нам требуется ввести логин и пароль,
мы точно знаем, что в любом приглашении для входа используется двоеточие.

Значит ждем его

tn.read_until(b':')

После вводим логин

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

Ждем двоеточие от password

tn.read_until(b':')

Вводим пароль


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

И ждем 3 секунды (передышка, а то столько работы проделали)

time.sleep(3)

После ждем приглашения

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

Вот на данном этапе мы переходим ко второму уровню по проверке вендора.


if vend == 0:# предположительно что это Qtech
    tn.write(('show ver' + '\n').encode('ascii'))
    print((tn.read_until(b'#').decode('ascii')), file=verinfo)
    verinfo.close()
    who_is()

Т.к. мы предположили, что попали на Qtech, а если Вы внимательно читали, то в нашем зоопарке есть две версии qtech, которые отличаются по синтаксису нам нужно еще провести сверку.

Следовательно мы даем команду show ver, заносим весь вывод в файл ver.txt
и вызываем процедуру who_is(), которую я описал выше. Команда show ver универсальна для всех Qtech, Raisecom, Eltex,

К сожалению, D-link не понимает и ему нужно сказать show swich, но мы же умные и не зря ввели итерацию с предположительным определением вендора.


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

Так сразу небольшая ремарка после ввода show swich коммутатор выводит не полную информацию и ждет от пользователя нажатие на любую клавишу для дальнейшего вывода и поэтому мы следом посылаем символ «a» для вывода полной информации.

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

Чтобы этим не заниматься, можно выключить clipadding

Вот на этом этапе и заканчивается проверка вендора и с 99% вероятностью можно предположить, что мы верно определили модель коммутатора и можно приступить к конфигурации.

Для каждого коммутатора мы имеем отдельный файл с набором команд.

Блок конфигурации
   
... 
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)
...


После того, как функция отработала и вернула переменную ver_status, мы можем продолжать дальнейшую работу с ветвлением, т.к. мы точно знаем какой коммутатор сейчас на линии.
В первую очередь даем коммутатору команду show ver и записываем вывод в лог(про d-link помним ему даем команду sh sw)


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

Обязательно порядок команд в файле должен быть таким, как бы их вводил администратор вручную

Пример:

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

Дальше всё по сценарию — читаем файл до тех пор, пока не закончатся строки и их выполняем

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

После выхода из цикла говорим коммутатору exit и читаем весь вывод в файл, и так делаем только с qtech1.0, т.к. порой бывает скрипт залипает в ожидании чего-то и именно с этим коммутатором, чтобы не городить лишнего данное решение показалось мне более изящным.

После того, как была произведена конфигурация коммутатора, общий счетчик увеличиваем на единицу.

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

Данный скрипт многократно перерабатывался и процесс на этом не будет остановлен.
Всем спасибо за внимание. Конструктивная критика приветствуется.

Весь код под спойлером.

Исходник
#!/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()

Tags:
Hubs:
+3
Comments 10
Comments Comments 10

Articles