网路行者
首发于网路行者
网络工程师的Python之路---进阶篇(续)

网络工程师的Python之路---进阶篇(续)

最近知乎对专栏文章加入了字数限制,好像是7000字封顶,导致我的《网络工程师的Python之路---进阶篇》刚讲完案例1就必须拆开来写了,也不知是知乎的进步还是退步。

附上《初级篇》和《进阶篇》的连接:

弈心:网络工程师的Python之路---初级篇zhuanlan.zhihu.com图标弈心:网络工程师的Python之路---进阶篇zhuanlan.zhihu.com图标

本文《进阶篇(续)》将接着《进阶篇》里的案例1讲案例2, 如果字数没超过的话,后面的案例将会继续在本文内持续更新,敬请关注:

案例2

案例背景:

某公司有1000台思科2960,100台2960S, 300台2960X交换机,均为24口,它们的型号和IOS版本分别如下:

2960

型号:WS-C2960-24PC-L

IOS:c2960-lanbasek9-mz.122-55.SE5

2960S

型号: WS-C2960S-F24PS-L

IOS: c2960s-universalk9-mz.150-2.SE5

2960X:

型号:WS-C2960X-24PS-L

IOS: c2960x-universalk9-mz.152-2.E5

最近公司决定将所有上述交换机的IOS升级,修补漏洞,消除BUG。升级后的版本分别为:

2960: c2960-lanbasek9-mz.122-55.SE12

2960S:c2960s-universalk9-mz.150-2.SE11

2960X: c2960x-universalk9-mz.152-2.E8

案例需求:

所有交换机的新IOS已经手动完成上传,并且boot system的路径也改为了新的IOS版本,为确保IOS升级顺利完成,需要创建Python脚本检查所有交换机的配置是否正确,避免升级IOS的过程中人为出现的错误。

案例思路:

升级交换机IOS的验证部分大致分为两个步骤:

  1. 重启交换机前的验证步骤

将新版IOS上传到flash后到重启交换机前大致会遇到下面三种人为错误:

A. IOS版本和交换机型号货不对板,比如把2960的IOS上传给了2960X

B. boot system忘记修改或者修改错误

C. 重启交换机前,忘记write memory保存配置

因此,在给所有交换机重启前,需要检验交换机的三处配置:

a. show inventory | i PID: WS

b. show flash: | i c2960

上述两步目的为查看交换机型号和闪存下面的IOS版本,避免前者和后者货不对板的情况发生

c. show boot | i BOOT path

检查boot system配置是否修改正确

2. 重启交换机后的验证步骤

重启交换机后的验证步骤较为简单,只需要用show ver | b SW Version验证交换机的IOS是否已经成功升级到新版本。

综上所述,我们可以分别写两个脚本来分别对应上面两个验证步骤。 脚本1用作交换机重启前的验证,脚本2用作交换机重启后的验证。

案例代码:

案例2-脚本1:

import paramiko
import time
import getpass
import sys
import re
import socket

username = raw_input("Username: ")
password = raw_input("Password: ")
iplist = open('ip_list.txt','r+')

switch_upgraded = []
switch_not_upgraded = []
switch_with_tacacs_issue = []
switch_not_reachable = []

for line in iplist.readlines():
    try:
        ip_address = line.strip()
        ssh_client = paramiko.SSHClient()
        ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh_client.connect(hostname=ip_address,username=username,password=password,look_for_keys=False)
        print "Successfully connect to ", ip_address
        command = ssh_client.invoke_shell(width=300)
        command.send("show inventory | i PID: WS\n")
        time.sleep(0.5)
        command.send("show flash: | i c2960\n")
        time.sleep(0.5)
        command.send("show boot | i BOOT path\n")
        time.sleep(0.5)
        output = command.recv(65535)
        command.send("wr mem\n")
        switch_model = re.search(r'WS-C2960\w?-\w{4,5}-L', output)
        ios_version = re.search(r'c2960\w?-\w{8,10}\d?-mz.\d{3}-\d{1,2}.\w{2,4}(.bin)?', output)
        boot_system = re.search(r'flash:.+mz.\d{3}-\d{1,2}\.\w{2,4}\.bin', output)
        if switch_model.group() == "WS-C2960-24PC-L" and ios_version.group() == "c2960-lanbasek9-mz.122-55.SE12.bin" and boot_system.group() == 'flash:c2960-lanbasek9-mz.122-55.SE12.bin' or boot_system.group() == 'flash:/c2960-lanbasek9-mz.122-55.SE12.bin':
            switch_upgraded.append(ip_address)
        elif switch_model.group() == "WS-C2960S-F24PS-L" and ios_version.group() == "c2960s-universalk9-mz.150-2.SE11.bin" and boot_system.group() == 'flash:c2960s-universalk9-mz.150-2.SE11.bin' or boot_system.group() == 'flash:/c2960s-universalk9-mz.150-2.SE11.bin':
            switch_upgraded.append(ip_address)
        elif switch_model.group() == "WS-C2960X-24PS-L" and ios_version.group() == "c2960x-universalk9-mz.152-2.E8.bin" and boot_system.group() == 'flash:c2960x-universalk9-mz.152-2.E8.bin' or boot_system.group() == 'flash:/c2960x-universalk9-mz.152-2.E8.bin':
            switch_upgraded.append(ip_address)
        else:
            switch_not_upgraded.append(ip_address)
    except paramiko.ssh_exception.AuthenticationException:
        print "TACACS is not working for " + ip_address + "."
        switch_with_tacacs_issue.append(ip_address)
    except socket.error:
        print ip_address +  " is not reachable."
        switch_not_reachable.append(ip_address)
    
iplist.close()
ssh_client.close

print '\nTACACS is not working for below switches: '
for i in switch_with_tacacs_issue:
    print i

print '\nBelow switches are not reachable: '
for i in switch_not_reachable:
    print i

print '\nBelow switches IOS version are up-to-date: '
for i in switch_upgraded:
    print i

print '\nBelow switches IOS version are not updated yet: '
for i in switch_not_upgraded:
    print i

案例2-脚本1代码讲解:

  • 这里我们将所有要验证的交换机IP地址放入名为ip_list.txt的文本文件中,用iplist = open('ip_list.txt','r+')将该文件打开,在后面可以配合for loop和paramiko依次循环SSH登录所有交换机,这是老生常谈的话题了。
  • switch_upgraded = [] switch_not_upgraded = [] : 创建两个空列表,一个取名为switch_upgraded一个取名为switch_not_upgraded,顾名思义,前者用来保存各项IOS升级配置均没有问题的交换机管理IP地址,一个用来保存配置仍然有误的交换机管理IP地址。
  • command = ssh_client.invoke_shell(width=300), 后面的(width=300)用来调整paramiko输出内容的宽度(默认为100),因为我们后面要用show flash: | i c2960查看闪存下面的IOS文件,而IOS文件名通常很长,如果不调整宽度,则会导致后面截取的output不完整,从而影响后面正则表达式对关键词的匹配。
  • command.send("show inventory | i PID: WS\n")command.send("show flash: | i c2960\n")command.send("show boot | i BOOT path\n") 这三个命令的作用前面已经讲到了,这里给大家看下在生产网络下的2960, 2960S,2960X交换机中执行这三条命令得到的输出结果(均为IOS上传完毕,交换机重启前的输出结果):

2960

2960S


2960X


  • 从上面的show inventory | i PID: WS的输出结果可以看到,公司里的3种29XX交换机的型号分别为WS-C2960-24PC-L, WS-C2960S-F24PS-L和WS-C2960X-24PS-L,这里可以用正则表达式switch_model = re.search(r'WS-C2960\w?-\w{4,5}-L', output)来匹配它们,关于正则表达式在Python中的用法,我将会在下一篇python基础文章中重点讲解。
  • command.send("wr mem\n"), 这里我们用wr mem命令保存了配置,解决了前面提到的“C. 重启交换机前,忘记write memory保存配置”这个有可能发生的人为错误。
  • 再来看show flash: | i c2960的输出结果,这里我们得到了对应三种交换机型号的IOS的.bin文件名: c2960-lanbasek9-mz.122-55.SE12.bin, c2960s-universalk9-mz.150-2.SE11.bin以及c2960x-universalk9-mz.152-2.E8.bin, 可以用正则表达式ios_version = re.search(r'c2960\w?-\w{8,10}\d?-mz.\d{3}-\d{1,2}.\w{2,4}(.bin)?', output)来匹配它们。
  • 同理,最后的show boot | i BOOT path分别得到了flash:c2960-lanbasek9-mz.122-55.SE12.bin, flash:c2960s-universalk9-mz.150-2.SE11.bin以及flash:/c2960x-universalk9-mz.152-2.E8.bin三种不同的boot system引导路径,这里我们用正则表达式boot_system = re.search(r'flash:/?c2960\w?-\w{9,11}-mz.\d{3}-\d{1,2}.\w{2,4}.bin', output) 来匹配它们这里如果你足够细心,会发现boot system路径可以写成flash:,也可以写成flash:/,有些网工喜欢加“/”号,有些不加,所以在正则表达式中我们用flash:/?将两种情况都匹配到了。
  • 用正则表达式匹配了所有可能出现的输出结果后,接下来就可以用if配合and和or两个布尔逻辑运算来做判断了,先说第一条匹配2960交换机的if语句
if switch_model.group() == "WS-C2960-24PC-L" and ios_version.group() == "c2960-lanbasek9-mz.122-55.SE12.bin" and boot_system.group() == 'flash:c2960-lanbasek9-mz.122-55.SE12.bin' or boot_system.group() == 'flash:/c2960-lanbasek9-mz.122-55.SE12.bin':
    switch_upgraded.append(ip_address)

整个语句可以这样翻译:如果交换机型号为WS-2960-24PC-L交换机的IOS版本为c2960-lanbasek9-mz.122-55.SE12.bin,并且boot system的引导路径为flash:c2960-lanbasek9-mz.122-55.SE12.bin(不加"/"号)或者flash:/c2960-lanbasek9-mz.122-55.SE12.bin(加了“/”号),那么将该交换机的IP地址加入switch_upgraded这个列表中。

  • 同理再来看第二条匹配2960S交换机的elif
elif switch_model.group() == "WS-C2960S-F24PS-L" and ios_version.group() == "c2960s-universalk9-mz.150-2.SE11.bin" and boot_system.group() == 'flash:c2960s-universalk9-mz.150-2.SE11.bin' or boot_system.group() == 'flash:/c2960s-universalk9-mz.150-2.SE11.bin':
    switch_upgraded.append(ip_address)

如果交换机型号为WS-C2960S-F24PS-L交换机的IOS版本为c2960s-universalk9-mz.150-2.SE11.bin,并且flash:c2960s-universalk9-mz.150-2.SE11.bin(不加"/"号)或者flash:/c2960s-universalk9-mz.150-2.SE11.bin(加了“/”号),那么将该交换机的IP地址加入switch_upgraded这个列表中。

  • 第三条匹配2960X的同理,这里就略过不讲了。
  • 如果有任何交换机不满足以上3条匹配条件,则将它的IP地址加入switch_not_upgraded这个列表中。
else:
     switch_not_upgraded.append(ip_address)
  • 最后将 switch_upgraded和 switch_not_upgraded两个列表中的所有元素打印出来,这样你就能清楚地看到哪些交换机已经成功升级,哪些还没有升级,以及他们的地址。

执行案例2-脚本1看效果:

运行脚本前我将案例2-脚本1(advance_2_1.py)和存放所有29XX交换机管理地址的ip_list.txt文件保存在同一个文件夹下,由于只是起演示作用,这里我只放了96个交换机的IP。

然后用IDLE执行脚本,这时可以起身去泡杯咖啡,等脚本自动运行

约莫3分钟后,脚本执行完毕,你能清楚地看到哪些交换机的IOS已经升级成功升级完成,哪些尚待排错、修改。

接下来我们再来看脚本2,也就是交换机重启后,验证其IOS是否成功升级的脚本,先来看代码部分

案例2-脚本2:

import paramiko
import time
import getpass
import sys
import re
import socket

username = raw_input("Username: ")
password = raw_input("Password: ")
iplist = open('ip_list.txt','r+')

switch_upgraded = []
switch_not_upgraded = []
switch_with_tacacs_issue = []
switch_not_reachable = []

for line in iplist.readlines():
    try:
        ip_address = line.strip()
        ssh_client = paramiko.SSHClient()
        ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh_client.connect(hostname=ip_address,username=username,password=password,look_for_keys=False)
        print "Successfully connect to ", ip_address
        command = ssh_client.invoke_shell(width=300)
        command.send("show ver | b SW Version\n")
        time.sleep(0.5)
        output = command.recv(65535)
        print output
        ios_version = re.search(r'\d{2}.\d\(\d{1,2}\)\w{2,4}', output)
        print boot_system.group()
        print ios_version.group()
        if ios_version.group() == '12.2(55)SE12':
            switch_upgraded.append(ip_address)
        elif ios_version.group() == '15.2(2)E8':
            switch_upgraded.append(ip_address)
        elif ios_version.group() == '15.0(2)SE11':
            switch_upgraded.append(ip_address)
        else:
            switch_not_upgraded.append(ip_address)
    except paramiko.ssh_exception.AuthenticationException:
        print "TACACS is not working for " + ip_address + "."
        switch_with_tacacs_issue.append(ip_address)
    except socket.error:
        print ip_address +  " is not reachable."
        switch_not_reachable.append(ip_address)   

iplist.close()
ssh_client.close

print '\nTACACS is not working for below switches: '
for i in switch_with_tacacs_issue:
    print i

print '\nBelow switches are not reachable: '
for i in switch_not_reachable:
    print i

print '\nBelow switches IOS version are up-to-date: '
for i in switch_upgraded:
    print i

print '\nBelow switches IOS version are not updated yet: '
for i in switch_not_upgraded:
    print i

案例2-脚本2代码讲解:

  • 脚本2基本没有什么可讲的,交换机重启后直接show ver | b SW Version查看当前IOS的版本后,再用正则表达式 ios_version = re.search(r'\d{2}.\d\(\d{1,2}\)\w{2,4}', output)匹配,只要能匹配到12.2(55)SE12, 15.2(2)E8, 15.0(2)SE11这三种IOS中的任意一个,就将交换机IP添加到switch_upgraded这个列表里面,反之如果上述三种IOS都匹配不到,则将交换机IP添加到switch_not_reachable这个列表里面,最后用for循环将switch_upgraded和switch_not_reachable中的元素一一打印出来,输出内容的格式和脚本1并无二致。

文章被以下专栏收录

    笔者从业计算机网络工作近十年(新加坡7年,沙特2年),2013年考取CCIE(R&S),先后在新加坡任职于AT&T, 苹果,Equinix,苏格兰皇家银行等大型企业。 目前在沙特工作,供职于“世界第一土豪大学“阿卜杜拉国王科技大学(KAUST),担任Senior Network Engineer。本专栏只谈计算机网络技术,专栏下所有文章均为笔者原创。