Предисловие
Всем доброго времени суток!
Работаю сетевым инженером у крупного оператора связи, и под моим управлением имеется целый зоопарк разного сетевого оборудования, но речь пойдет о коммутаторах доступа.
Данная статья это не руководство к действию, не единственное решение и явно не претендует на номинацию скрипт года, но я хочу поделиться этим творением, и может быть кому-нибудь он пригодится.
В статье будет приводиться блок кода под спойлером, а под ним будет описание с вырезками и объяснениями почему именно так и для чего это.
Задача
Использовать именно 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()