Python3. Automation of multi-vendor network equipment configuration

Published on June 27, 2018

Python3. Automation of multi-vendor network equipment configuration

Foreword


Good day to all!

I work as a network engineer for a large telecom operator, and under my management there is a zoo of different network equipment, but it will be about access switches.

This article is not a guide to action, is not the only solution, and obviously does not pretend to be nominated for the script of the year, but I want to share this creation, and maybe someone will find it useful.

The article will provide a block of code under the spoiler, and under it will be a description with clippings and explanations of why and for what reason.

Task


Use exactly python3. The script should be able to get on the switches from the list, determine what kind of vendor, give the required command, log.

Actually, how did I come to this?


Faced with a small problem of changing the configuration on a large number of switches, I’ll just make a reservation about the system of centralized administration of network equipment, we have a lot of practical work of my colleagues in the form of scripts for facilitating manual labor, mostly bash, perl.

But for me it was important to do something of my own, because I started learning python recently and need to pump skills in programming, and my tool should look like a hammer (easy to use and easy to maintain). If interest still remains then I ask under cat.

Googling and not finding the proper solution (as if on forums this question was raised, but everything is not right there), I decided to start writing my own script.

We have the following switches:

  • Raisecom
  • Qtech rev. 1.0
  • Qtech rev. 2.0 (syntax difference)
  • Eltex
  • D-link
  • Frankenstein spawned a switch with a muzzle from Qtech rev 1.0, and iron from Raisecom will call it ROS

First, import the required libraries:

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

Initially, we will describe the vendor definition function, since I decided to use two methods:

1) to assume - what kind of vendor (at the invitation of the login), as I noticed at all, it is different: Qtech (login :), Raisecom and ROS (Login :), Eltex (User Name :), D- link (Username).

2) after logging in - to make sure that the first item was completed without error, and to better identify which switch we are on.

Function

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


All function under the spoiler. We declare global variables that will be visible to the whole script:

global ver_status
global sab_versus
global versus

The variables a, d, f, h, j, k, n store keywords, by which we will later determine the switch model.

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

After opening the file ver.txt, we read line by line and check for occurrences by keywords, since The find () function returns -1 for this negative result, and we will build branching.

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'

... I

cite as an example a part of the code, the rest is under the spoiler above.

The content of this file will be described below. After all the manipulations and definitions of the vendor, we delete the ver.txt file.

Main variable declaration, main loop body
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()
...


I decided not to bother and in the body to set variables with a login and password, I know that it is not security, I am working on it.

Open the file for logs and the list of switches


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

After we use the for loop, we read the line with the switch ip-address

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

We check it for availability

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

If it is available using the pexpect library, we make an attempt to connect via telnet here at this iteration and the first verification by invitation occurs, which I, by the way, wrote at the beginning.


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)

The variable vend will receive a value from 0 to 3 inclusive, and depending on which login prompt it sees, further branching will be formed.

From this piece of code, an attentive reader may notice that I am connecting to the switch and immediately close the connection, and this is not casual. I tried to use only the telnetlib library, but at the first check the script periodically stuck and fell off after a timeout, and this crutch helps a lot.

After closing the connection, we re-connect using only the telnetlib library.

To avoid falling out of errors, we have already checked above that the switch is available, and exclude script interruption in the process because of one non-working switch, we will wrap everything into the try except block.

There were repeated cases when out of 100 switches one did not allow oneself.


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 everything is good and we are connected, then we need to enter a login and password,
we know for sure that in any invitation you use a colon to enter.

So we are waiting for him

tn.read_until(b':')

After we enter the login

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

Waiting for a colon from password

tn.read_until(b':')

Enter the password


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

And wait for 3 seconds (a break, and then so much work has been done)

time.sleep(3)

After waiting for an invitation

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

Here at this stage we move to the second level to verify the vendor.


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

Since we assumed that we got to Qtech, and if you read carefully, then in our zoo there are two versions of qtech, which differ in syntax we still need to reconcile.

Therefore, we give the show ver command, put all the output into the ver.txt file
and call the who_is () procedure, which I described above. The show ver command is universal for all Qtech, Raisecom, Eltex,

Unfortunately, D-link does not understand and he needs to say show swich, but we are smart and have not introduced the iteration with the tentative definition of a vendor.


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

So immediately a small remark after entering show swich, the switch displays incomplete information and waits for the user to press any key for further output and therefore we send the “a” symbol to display the full information.

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

In order not to do this, you can turn off clipadding.

At this stage, the vendor check ends and with 99% probability we can assume that we have correctly defined the switch model and can proceed to the configuration.

For each switch we have a separate file with a set of commands.

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


After the function has completed and returned the variable ver_status, we can continue further work with branching, since we know exactly which switch is on the line now.
First of all, we give the switch the show ver command and we write the output to the log (about d-link we remember we give the command sh sw)


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

Be sure to keep counters to identify inconsistencies.
counter_qtech1_0+=1

Open the file with the commands
komand_qtech1_0=open('servers_&_log/komand_qtech_ver1.0.txt')

The order of commands in the file must be the same as if the administrator had entered them manually.

Example: Then everything is done according to the script - we read the file until the lines run out and execute them

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

After exiting the loop, we tell the switch exit and read the entire output to the file, and so we do only with qtech1.0, since sometimes the script sticks while waiting for something, and it is with this switch that it doesn’t seem to be more graceful, so as not to make unnecessary trouble.

After the switch configuration has been made, we increase the total counter by one.

counter_komm+=1

And we start all over again, read the next line from the file with the switches, determine the model, produce the configuration.

After exiting the main cycle, you need to produce a summary of the work done,
at the end of the log we enter all the models that we have processed and the date must be, well, what about without it.


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

This script has been repeatedly processed and the process will not stop there.
Thank you all for your attention. Constructive criticism is welcome.

All code under the spoiler.

Source code
#!/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()