现在,我们已经知道如何在不同的操作系统中使用和安装Python以及如何使用EVE-NG搭建网络拓扑。在本章中,我们将学习如何使用目前常用的网络自动化库自动完成各种网络任务。Python可以在不同的网络层上与网络设备进行交互。

首先,Python可以通过套接字编程和socket模块操纵底层网络,从而为Python所在的操作系统和网络设备之间搭建一个低层次的网络接口。此外,Python模块还可以通过Telnet、SSH和API与网络设备进行更高级别的交互。本章将深入探讨如何在Python中使用Telnet与SSH模块在远程设备上建立连接和执行命令。

本章主要介绍以下内容:

  • 使用Python通过Telnet连接设备;
  • Python和SSH;
  • 使用netaddr处理IP地址和网络;
  • 网络自动化实战示例。

1 技术要求

应检查是否正确安装了下列工具并保证它们能够正常使用:

  • Python 2.7.1x;
  • PyCharm社区版或专业版;
  • EVE-NG,网络仿真器的安装和配置请参阅第3章。

本章中出现的所有脚本请参见GitHub网站。

1.1 Python和SSH

SSH和Telnet的不同之处在于客户端与服务器之间交换数据的通道不一样。SSH使用的是安全链路,在客户端和设备之间创建了一个使用不同的安全机制进行加密的隧道,通信内容很难被解密。因此在需要保证网络安全的时候,网络工程师会首先选择使用SSH协议。

Paramiko库遵循SSH2协议,支持身份验证,密钥处理(DSA、RSA、ECDSA和ED25519),以及其他SSH功能(如proxy命令和SFTP)。在Python中当需要使用SSH连接到网络设备时通常使用这个库。

1.2 Paramiko模块

Paramiko是Python中应用最广的SSH模块。模块本身使用Python语言编写和开发,只有像crypto这样的核心函数才会用到C语言。从其GitHub官方链接上能够看到代码的贡献者和模块历史等诸多信息。

1.安装模块

打开Windows cmd或Linux shell,运行下面的命令,从PyPI下载最新的Paramiko模块。如下图所示,同时,该命令会自动下载其他依赖包(如cyrptography、ipaddress和six),并将它们安装到计算机上。

pip install paramiko

Python自动化运维实战:使用Python管理网络设备_python

在命令行中输入Python,然后导入Paramiko模块,验证是否安装成功。如下图所示,正确安装之后,能够成功导入模块。也就是说,命令行上不会出现任何错误提示。

Python自动化运维实战:使用Python管理网络设备_python_02

2.用SSH连接网络设备

如前所述,要使用Paramiko模块,首先需要在Python脚本中导入它,然后通过继承SSHClient()来创建SSH客户端。然后,设置Paramiko的参数,使其能够自动添加任意未知的主机密钥并信任与服务器之间的连接。接下来,将远程主机的信息(IP地址、用户名和密码等)传递给connect函数。

#!/usr/bin/python
__author__ = "Bassim Aly"
__EMAIL__ = "basim.alyy@gmail.com"

import paramiko
import time
Channel = paramiko.SSHClient()
Channel.set_missing_host_key_policy(paramiko.AutoAddPolicy())
Channel.connect(hostname="10.10.88.112", username='admin',
password='access123', look_for_keys=False,allow_agent=False)

shell = Channel.invoke_shell()



AutoAddPolicy()是一种策略,可以作为函数set_missing_host_key_policy()的输入参数。在虚拟实验室环境中推荐使用这种策略,但在生产环境中应当使用更加严格的策略,如WarningPolicy()或RejectPolicy()。


最后,invoke_shell()将启动一个连接到SSH服务器的交互式shell会话。在调用该函数时可以传入一些其他参数(如终端类型、宽度、高度等)。

Paramiko的连接参数如下。

  • Look_For_Keys:默认为True,强制Paramiko使用密钥进行身份验证。也就是说,用户需要使用私钥和公钥对网络设备进行身份验证。在这里使用密码验证,因此将该参数设置为False。
  • allow_agent:表示是否允许连接到SSH代理,默认为True。在用密钥验证时可能需要使用这个选项。由于这里使用的是用户名/密码,因此禁用它。

最后一步,把各种命令(如show ip int b和show arp)发送到设备终端,并将返回结果输出到Python窗口中。

shell.send("enable\n")
shell.send("access123\n")
shell.send("terminal length 0\n")
shell.send("show ip int b\n")
shell.send("show arp \n")
time.sleep(2)
print shell.recv(5000)
Channel.close()

脚本运行结果如下图所示。

Python自动化运维实战:使用Python管理网络设备_程序员_03



如果需要在远程设备上执行耗时很长的命令,就要强制Python等待一段时间,直到设备生成输出并将结果返回给Python,因此最好使用time.sleep()。否则,Python可能得不到正确的输出结果。


1.3 netmiko模块

netmiko是Paramiko的增强版本,专门面向网络设备。虽然Paramiko能够处理与设备的SSH连接并判断设备类型是服务器、打印机还是网络设备,但netmiko在设计时针对网络设备做了优化,能够更有效地处理SSH连接。netmiko还支持各种不同的设备厂商和平台。

netmiko也是对Paramiko的封装,它使用许多其他增强功能扩展了Paramiko,比如使用启用的密码直接访问所支持的设备,从文件读取配置并将推送到设备,在登录期间禁用分页显示,以及默认在每条命令后面加上回车符"\n"。

1.支持的设备商

netmiko支持许多供应商的设备,并定期在支持列表中添加新的供应商。netmiko支持的供应商列表分为定期测试类、有限测试类和实验类。在该模块的GitHub页面上可以找到这个列表。

定期测试类中支持的供应商如下图所示。

Python自动化运维实战:使用Python管理网络设备_python_04

有限测试类中支持的供应商如下图所示。

Python自动化运维实战:使用Python管理网络设备_python_05

实验类中支持的供应商如下图所示。

Python自动化运维实战:使用Python管理网络设备_程序员_06

2.安装和验证

安装netmiko非常简单。打开Windows命令行窗口或Linux shell,执行下面的命令就可以从PyPI获取最新版本的netmiko包(见下图)。

pip install netmiko

Python自动化运维实战:使用Python管理网络设备_python_07

然后,在Python shell中导入netmiko,验证模块是否正确地安装到Python site-packages中。

$python
>>>import netmiko

3.使用netmiko建立SSH连接

现在该开始使用netmiko了,让我们来看看它强大的SSH功能。首先连接到网络设备并在上面执行命令。默认情况下,netmiko在建立会话(session)的过程中会在后台处理许多操作(如添加未知的SSH密钥主机,设置终端类型、宽度和高度),在需要的时候还可以进入特权(enable)模式,然后通过运行供应商提供的命令来禁用分页。

首先,以字典格式定义设备并提供下列5个必需的关键信息。

R1 = (
'device type ': 'cisco ios',
'ip': '10.10.88.110',
'username': 'admin',
'password': 'access123',
'secret': 'access123',
}

第一个参数是device_type,为了执行正确的命令,需要使用这个参数来定义平台供应商。然后,需要SSH的IP地址。如果已经使用DNS解析了IP地址,该参数可能是主机名;否则,该参数是IP地址。接下来,提供username、password以及以secret参数传递的特权模式的密码。注意,可以使用getpass()模块隐藏密码,并且只在脚本执行期间提示它们。



虽然变量中的密钥序列不重要,但是为了使netmiko能够正确解析字典并开始和设备建立连接,密钥的名称应该和之前示例中提供的密钥完全一样。


接下来,从netmiko模块导入ConnectHandler函数,并提供定义的字典来开始建立连接。因为所有的设备是通过特权模式的密码配置的,所以需要为创建的连接提供.enable(),以在特权模式下访问。使用.send_command()在路由器终端上执行命令,.send_command()将会执行命令并通过变量的值显示设备的输出。

from netmiko import ConnectHandler
connection = ConnectHandler(**R1)
connection.enable()
output = connection.send_command("show ip int b")
print output

脚本输出结果如下。

Python自动化运维实战:使用Python管理网络设备_程序员_08

注意,这里看到的输出结果去掉了命令行中的命令回显和设备提示符。默认情况下,netmiko会替换设备的返回结果,使输出更加整洁,替换过程通过正则表达式完成,这部分会在下一章中介绍。

如果不想使用这种方式,而是希望看到命令提示符,并在返回结果的后面执行命令,可以在.send_command()函数中加上以下参数。

output = connection.send_command("show ip int
b",strip_command=False,strip_prompt=False)

strip_command=False和strip_prompt=False告诉netmiko保留而不是替换命令行回显和提示符。默认情况下它为True,可以根据需要进行设置。

Python自动化运维实战:使用Python管理网络设备_ip地址_09

4.使用netmiko配置设备

netmiko可以通过SSH配置远程设备,通过.config方法进入设备的配置模式,然后按照list格式中的信息(配置列表)配置设备。配置列表可以直接写在Python脚本中,也可以从文件中读取,然后用readlines()方法转换为列表。

from netmiko import ConnectHandler

SW2 = {
'device_type': 'cisco_ios',
'ip': '10.10.88.112',
'username': 'admin',
'password': 'access123',
'secret': 'access123',
}

core_sw_config = ["int range gig0/1 - 2","switchport trunk encapsulation
dot1q",
"switchport mode trunk","switchport trunk allowed vlan
1,2"]

print "########## Connecting to Device {0} ############".format(SW2['ip'])
net_connect = ConnectHandler(**SW2)
net_connect.enable()
print "***** Sending Configuration to Device *****"
net_connect.send_config_set(core_sw_config)

上面的脚本以另外一种形式连接到SW2并进入特权模式。但这次使用的是另一个netmiko方法——send_config_set(),该方法需要使用列表形式的配置文件,同时进入设备的配置模式并根据列表对设备进行配置。这里测试了一个简单的配置,即修改gig0/1和gig0/2,并将这两个端口配成trunk模式。在设备上执行show run命令时,如果命令执行成功,会出现类似下面的输出。

Python自动化运维实战:使用Python管理网络设备_运维_10

5.netmiko中的异常处理

在设计Python脚本时,我们可能会假设设备已启动并运行,并且用户已提供了正确的登录信息,但实际情况并非总是如此。有时Python和远程设备之间的网络连接可能存在问题,或者用户输入了错误的登录信息。如果发生这种情况,Python通常会抛出异常并退出,但这种解决方案显然不够完美。

netmiko中的异常处理模块netmiko.ssh_exception提供的一些异常处理类可以处理上面所说的那些情况。第一个类AuthenticationException能够捕获远程设备中的身份验证错误。第二个类NetMikoTimeoutException能够捕获netmiko和设备之间的超时或任何连接问题。下面的例子中使用try-except子句包含了ConnectHandler()方法,用来捕获超时和身份验证异常。

from netmiko import ConnectHandler
from netmiko.ssh_exception import AuthenticationException,
NetMikoTimeoutException
device = {
'device_type': 'cisco_ios',
'ip': '10.10.88.112',
'username': 'admin',
'password': 'access123',
'secret': 'access123',
}

print "########## Connecting to Device {0}
############".format(device['ip'])
try:
net_connect = ConnectHandler(**device)
net_connect.enable()

print "***** show ip configuration of Device *****"
output = net_connect.send_command("show ip int b")
print output

net_connect.disconnect()

except NetMikoTimeoutException:
print "=========== SOMETHING WRONG HAPPEN WITH {0}
============".format(device['ip'])

except AuthenticationException:
print "========= Authentication Failed with {0}
============".format(device['ip'])

except Exception as unknown_error:
print "============ SOMETHING UNKNOWN HAPPEN WITH {0} ============"

6.设备自动发现

netmiko提供了一种可以“猜测”设备类型和发现设备的机制。通过组合使用SNMP发现OIDS和在远程控制台上执行多个show命令这两种方式,根据输出字符串检测路由器的操作系统和类型。然后,netmiko将相应的驱动程序加载到ConnectHandler()类中。

#!/usr/local/bin/python
__author__ = "Bassim Aly"
__EMAIL__ = "basim.alyy@gmail.com"

from netmiko import SSHDetect, Netmiko
device = {
'device_type': 'autodetect',
'host': '10.10.88.110',
'username': 'admin',
'password': "access123",
}

detect_device = SSHDetect(**device)
device_type = detect_device.autodetect()
print(device_type)
print(detect_device.potential_matches)

device['device_type'] = device_type
connection = Netmiko(**device)

在上面的脚本中,应注意以下几点。

首先,设备字典中的device_type等于autodetect,也就是告诉netmiko在检测到设备类型之前不要加载驱动程序。

然后,使用netmiko的SSHDetect()类发现设备。它使用SSH连接到设备,并执行一些命令以找出操作系统的类型,结果以字典形式返回。

接着,使用autodetect()函数将匹配度最高的结果赋给device_type变量。

接下来,输出potential_matches,查看字典内的全部返回结果。

最后,可以更新设备字典并为其分配新的device_type。

2 在Python中使用Telnet协议

Telnet是TCP/IP协议栈中最早可用的协议之一,主要用来在服务器和客户端之间建立连接、交换数据。服务器端监听TCP端口23,等待客户端的连接请求。

在下面的例子中,我们将创建一个Python脚本作为Telnet客户端,拓扑中的其他路由器和交换机则作为Telnet服务器。Python原生的telnetlib库已经支持Telnet,所以不需要另外安装。

客户端对象可以通过telnetlib模块中的Telnet()类实例化创建。通过这个对象,我们能够使用telnetlib中的两个重要函数——read_until()(用于读取输出结果)和write()(用于向远程设备写入内容)。这两个函数用来和Telnet连接交互,从Telnet连接读取或向Telnet连接写入数据。

还有一点非常关键,使用read_until()读取Telnet连接的内容之后缓冲区会被清空,无法再次读取。因此,如果在后面的处理中还会用到之前读取的重要数据,需要在脚本里将其另存为变量。



Telnet数据以明文形式发送,因此通过“中间人攻击”可以捕获并查看到Telnet数据,如用户信息和密码。即便如此,一些服务提供商和企业仍然在使用它,只是他们会集成VPN和radius/tacacs协议,以提供轻量级和安全的访问方式。


让我们一步步分析这个脚本。

(1)在Python脚本中导入telnetlib模块,在变量中定义用户名和密码。代码如下。

import telnetlib
username = "admin"
password = "access123"
enable_password = "access123"

(2)定义一个变量,用来和远程主机建立连接。注意,只需要提供远程主机的IP地址,不用在连接建立过程中提供用户名或密码。

cnx = telnetlib.Telnet(host="10.10.88.110") #here we're telnet to
Gateway

(3)通过读取Telnet连接返回的输出并搜索“Username:”关键字来提供Telnet连接的用户名。然后写入管理员用户名。如果需要,用同样的方法输入Telnet密码。

cnx.read_until("Username:")
cnx.write(username + "\n")
cnx.read_until("Password:")
cnx.write(password + "\n")
cnx.read_until(">")
cnx.write("en" + "\n")
cnx.read_until("Password:")
cnx.write(enable_password + "\n")



Telnet连接建好之后,在脚本中加上控制台提示符非常重要;否则,连接将陷入死循环。接着Python脚本就会超时并出现错误。


(4)向Telnet连接写入show ip interface brief命令并开始读取返回内容,直到出现路由器提示符(#)为止。通过以下命令可以得到路由器的接口配置。

cnx.read_until("#")
cnx.write("show ip int b" + "\n")
output = cnx.read_until("#")
print output

完整的脚本如下所示。

Python自动化运维实战:使用Python管理网络设备_ip地址_11

脚本运行结果如下所示。

Python自动化运维实战:使用Python管理网络设备_Python_12

注意,在输出中包含了执行的命令show ip int b,并且在stdout中输出和返回了路由器提示符"R1#"。可以使用内置的字符串函数(如replace())从输出中清除它们。

cleaned_output = output.replace("show ip int b","").replace("R1#","")
print cleaned_output

Python自动化运维实战:使用Python管理网络设备_python_13

你可能已经注意到脚本中使用了密码并将密码以明文形式写下来,这样做显然是不安全的。同时,在Python脚本中使用硬编码也不是好习惯。在下一节中,我们将学习如何隐藏密码并设计一种机制,从而在脚本运行时要求用户输入密码。

此外,如果要执行那些输出结果可能跨越多个页面的命令(如show running config),则需要在连接到设备之后和发送命令之前,先通过发送termindl length 0来禁用分页。

使用telnetlib推送配置

在上一节中,我们通过执行show ip int brief简单介绍了telnetlib的操作过程。现在我们要用telnetlib将VLAN配置推送到实验室网络拓扑中的4台交换机。使用Python的range()函数创建一个VLAN列表,遍历列表将VLAN ID推送到当前交换机。注意,我们将交换机的IP地址放到了另一个列表中,使用外部for循环来遍历这个列表。同时使用内置模块getpass隐藏控制台中的密码,在脚本运行时提示用户输入密码。

#!/usr/bin/python
import telnetlib
import getpass
import time

switch_ips = ["10.10.88.111", "10.10.88.112", "10.10.88.113",
"10.10.88.114"]
username = raw_input("Please Enter your username:")
password = getpass.getpass("Please Enter your Password:")
enable_password = getpass.getpass("Please Enter your Enable Password:")

for sw_ip in switch_ips:
print "\n#################### Working on Device " + sw_ip + "
####################"
connection = telnetlib.Telnet(host=sw_ip.strip())
connection.read_until("Username:")
connection.write(username + "\n")
connection.read_until("Password:")
connection.write(password + "\n")
connection.read_until(">")
connection.write("enable" + "\n")
connection.read_until("Password:")
connection.write(enable_password + "\n")
connection.read_until("#")
connection.write("config terminal" + "\n") # now i'm in config mode
vlans = range(300,400)
for vlan_id in vlans:
print "\n********* Adding VLAN " + str(vlan_id) + "**********"
connection.read_until("#")
connection.write("vlan " + str(vlan_id) + "\n")
time.sleep(1)
connection.write("exit" + "\n")
connection.read_until("#")
connection.close()

最外层的for循环用来遍历设备列表,然后在每次循环(每个设备)中生成范围为300~400的VLAN ID并将它们推送到当前设备。

脚本运行结果如下。

Python自动化运维实战:使用Python管理网络设备_python_14

当然,也可以通过交换机控制台检查运行结果(仅显示部分结果)。

Python自动化运维实战:使用Python管理网络设备_运维_15

3 使用netaddr处理IP地址和网络

管理和操作IP地址是网络工程师最重要的任务之一。Python开发人员提供了一个令人惊叹的库—— netaddr,它可以识别IP地址并对其进行处理。假设你开发了一个应用程序,其中需要获取129.183.1.55/21的网络地址和广播地址,通过模块内的内置方法network和broadcast可以轻松地获取到相应的地址。

net.network
129.183.0.
net.broadcast
129.183.0.0

netaddr支持很多功能。

在第3层的地址中,netaddr支持下列功能。

  • 识别IPv4和IPv6地址、子网、掩码和前缀。
  • 对IP网络进行迭代、切片、排序、汇总和分类。
  • 处理各种格式(CIDR、任意子网长度、nmap)。
  • 对IP地址和子网进行集合操作(联合、交叉等)。
  • 解析各种不同的格式和符号。
  • 查找IANA IP块信息。
  • 生成DNS反向查找结果。
  • 检索超网和生成子网。

在第2层的地址中,netaddr支持下列功能。

  • 展示和操作Mac地址与EUI-64标识符。
  • 查找IEEE组织信息(OUI、IAB)。
  • 生成链路本地的IPv6地址。

3.1 安装netaddr

使用pip安装netaddr模块,命令如下。

pip install netaddr

安装完成之后打开PyCharm或Python控制台并导入模块,验证模块是否安装成功。如果没有出现错误信息,说明模块安装成功。

python
>>>import netaddr

3.2 使用netaddr的方法

netaddr模块提供了两种重要的方法来定义IP地址并对其进行处理。第一种方法是IPAddress(),它用来定义具有默认子网掩码的单个有类IP地址。第二种方法是IPNetwork(),它使用CIDR定义无类IP地址。

两种方法都将IP地址作为字符串来处理,根据字符串返回IP地址或IP网络对象。返回的对象还可以继续执行许多方法,比如判断IP地址是单播地址、多播地址、环回地址、私有地址还是公有地址,以及地址有效还是无效地址。这些操作的结果是True或False。在Python的if条件中可以直接使用这些方法。

另外,该模块支持使用==、<和>等比较运算符比较两个 IP 地址,从而生成子网。它还可以检索一个给定IP地址或者子网术语的超网列表。最终,netaddr模块可以生成有效主机的一个完整列表(不包括网络IP地址和网络广播地址)。

#!/usr/bin/python
__author__ = "Bassim Aly"
__EMAIL__ = "basim.alyy@gmail.com"
from netaddr import IPNetwork,IPAddress
def check_ip_address(ipaddr):
ip_attributes = []
ipaddress = IPAddress(ipaddr)

if ipaddress.is_private():
ip_attributes.append("IP Address is Private")
else:
ip_attributes.append("IP Address is public")
if ipaddress.is_unicast():
ip_attributes.append("IP Address is unicast")
elif ipaddress.is_multicast():
ip_attributes.append("IP Address is multicast")
if ipaddress.is_loopback():
ip_attributes.append("IP Address is loopback")

return "\n".join(ip_attributes)

def operate_on_ip_network(ipnet):

net_attributes = []
net = IPNetwork(ipnet)
net_attributes.append("Network IP Address is " + str(net.network) + "
and Netowrk Mask is " + str(net.netmask))

net_attributes.append("The Broadcast is " + str(net.broadcast) )
net_attributes.append("IP Version is " + str(net.version) )
net_attributes.append("Information known about this network is " +
str(net.info) )
net_attributes.append("The IPv6 representation is " + str(net.ipv6()))
net_attributes.append("The Network size is " + str(net.size))
net_attributes.append("Generating a list of ip addresses inside the
subnet")

for ip in net:
net_attributes.append("\t" + str(ip))
return "\n".join(net_attributes)

ipaddr = raw_input("Please Enter the IP Address: ")
print check_ip_address(ipaddr)

ipnet = raw_input("Please Enter the IP Network: ")
print operate_on_ip_network(ipnet)

在上面的脚本中,首先使用raw_input()函数请求用户输入IP地址和IP网络,然后将输入的值作为参数传递给两个用户方法check_ip_address()和operate_on_ip_network()并调用它们。第一个函数check_ip_address()会检查输入的IP地址,同时尝试生成有关IP地址属性的报告(例如,IP地址是单播、多播、私有还是环回地址),并将输出返回给用户。

第二个函数operate_on_ip_network()用来完成和网络相关的操作,即生成网络ID、掩码、广播、版本、网络上的已知信息、IPv6地址的显示方式,最后生成该子网内的所有IP地址。

注意,net.info只能对公共IP地址生成可用信息,对私有IP地址不起作用。

同样,在使用之前需要先从netaddr模块导入IP Network和IP Address。

脚本运行结果如下所示。

Python自动化运维实战:使用Python管理网络设备_python_16

4 简单的用例

随着网络变得越来越大,其中包含更多来自各种不同供应商的设备,这就需要创建模块化的Python脚本来自动执行各种任务。接下来的几节将分析3个用例,这些用例可以从网络中收集不同信息,缩短解决问题所需的时间,或者至少将网络配置恢复到其上次已知的良好状态。使用自动化工作流来处理网络故障、修复网络环境,网络工程师能够更关心工作完成情况,提高业务水平。

4.1 备份设备配置

备份设备配置对于任何一名网络工程师来说都是最重要的任务之一。在这个用例中,我们将使用netmiko库设计一个示例Python脚本。该脚本用来备份设备配置,它适用于不同的供应商和平台。

为方便日后访问或引用,我们将根据设备IP地址格式化输出文件名,例如,SW1备份操作的输出文件保存在dev_10.10.88.111_.cfg中。

创建Python脚本

从定义交换机开始,我们希望将其配置备份为文本文件(设备文件),用逗号分隔访问设备的用户名、密码等详细信息。这样就可以在Python脚本中使用split()函数来获取这些数据,方便在ConnectHandler函数中使用这些数据。此外,还可以从Microsoft Excel工作表或任何数据库中轻松导出该文件以及把该文件导入其中。

文件结构如下。

<device_ipaddress>,<username>,<password>,<enable_password>,<vendor>

Python自动化运维实战:使用Python管理网络设备_运维_17

创建Python脚本,使用with open子句在脚本中导入该文件。在导入的文件对象上使用readlines()方法将文件中的每一行组成列表,然后用for循环逐行遍历文件,用split()函数获取每一行中用逗号隔开的设备信息,并将它们赋予相应的变量。

from netmiko import ConnectHandler
from datetime import datetime

with
open("/media/bassim/DATA/GoogleDrive/Packt/EnterpriseAutomationProject/Chap
ter5_Using_Python_to_manage_network_devices/UC1_devices.txt") as
devices_file:
devices = devices_file.readlines()
for line in devices:
line = line.strip("\n")
ipaddr = line.split(",")[0]
username = line.split(",")[1]
password = line.split(",")[2]
enable_password = line.split(",")[3]

vendor = line.split(",")[4]

if vendor.lower() == "cisco":
device_type = "cisco_ios"
backup_command = "show running-config"

elif vendor.lower() == "juniper":
device_type = "juniper"
backup_command = "show configuration | display set"

由于我们的目标是创建模块化的、支持多种设备供应商的脚本,因此在if子句中需要检查设备供应商,并为该设备分配正确的device_type和backup_command。

接下来,建立与设备的SSH连接,使用netmiko模块中的.send_command()方法执行备份命令。

print str(datetime.now()) + " Connecting to device {}" .format(ipaddr)

net_connect = ConnectHandler(device_type=device_type,
ip=ipaddr,
username=username,
password=password,
secret=enable_password)
net_connect.enable()
running_config = net_connect.send_command(backup_command)

print str(datetime.now()) + " Saving config from device {}" .format(ipaddr)

f = open( "dev_" + ipaddr + "_.cfg", "w")
f.write(running_config)
f.close()
print "=============================================="

在最后的几行中,以写入方式打开一个文件(文件不存在时将自动创建),文件名中包含了前面从设备文件中读取的ipaddr变量。

脚本运行结果如下。

Python自动化运维实战:使用Python管理网络设备_python_18

需要注意的是,备份的配置文件存储在项目的主目录中,文件名称包含每个设备的IP地址(见下图)。

Python自动化运维实战:使用Python管理网络设备_运维_19



使用Linux服务器上的cron任务,或Windows服务器上的计划任务,可让服务器在指定时间运行上面的Python脚本。例如,每天凌晨运行一次,将配置信息存储在latest目录中,以方便运维团队使用。


4.2 创建访问终端

在Python或其他编程活动中,你就是自己的设备供应商。为了满足自己的需求,你可以创建任何喜欢的代码组合和程序。在第二个例子中我们创建自己的终端(terminal),通过telnetlib访问路由器。只需要在终端写几个单词,就会在网络设备中执行很多命令并返回输出结果。输出结果将会显示在标准输出或保存在文件中。

#!/usr/bin/python
__author__ = "Bassim Aly"
__EMAIL__ = "basim.alyy@gmail.com"

import telnetlib

connection = telnetlib.Telnet(host="10.10.88.110")
connection.read_until("Username:")
connection.write("admin" + "\n")
connection.read_until("Password:")
connection.write("access123" + "\n")
connection.read_until(">")
connection.write("en" + "\n")
connection.read_until("Password:")
connection.write("access123" + "\n")
connection.read_until("#")
connection.write("terminal length 0" + "\n")
connection.read_until("#")
while True:
command = raw_input("#:")
if "health" in command.lower():
commands = ["show ip int b",
"show ip route",
"show clock",
"show banner motd"
]

elif "discover" in command.lower():
commands = ["show arp",
"show version | i uptime",
"show inventory",
]
else:
commands = [command]
for cmd in commands:
connection.write(cmd + "\n")
output = connection.read_until("#")
print output
print "==================="

首先,建立到路由器的Telnet连接并输入相应的用户信息,一直到打开特权(enable)模式。然后,创建一个始终为true的无限while循环,使用内置的raw_input()函数捕捉用户输入的命令。脚本捕获到用户输入之后,在网络设备上执行这些命令。

如果用户输入关键字health或discover,终端将自动执行一系列命令以反映期望的操作。这些关键字在排除网络故障时非常有用,可以根据常用的操作自由扩展它们。想象一下,在需要解决两个路由器之间的开放式最短路径优先(Open Shortest Path First,OSPF)邻居问题时,只要打开自己的Python终端脚本(这个脚本中已经写好了几个排除故障常用的命令),并将这些命令打包到诸如tshoot_ospf之类的if条件之后。一旦脚本看到这个关键字,它就会执行这些命令,输出OSPF邻居状态、MTU的接口、OSPF的广播网络等,简化定位问题的过程。

通过在提示符中输入health尝试脚本中的第一条命令。脚本输出结果如下。

Python自动化运维实战:使用Python管理网络设备_运维_20

可以看到,脚本将返回在设备上执行多条命令后的结果。

接着试一下第二个命令discover。脚本输出结果如下。

Python自动化运维实战:使用Python管理网络设备_python_21

这次脚本返回discover命令的输出。在后面的章节中,我们将会解析返回的输出结果并从中提取有用的信息。

4.3 从Excel工作表中读取数据

网络和IT工程师始终使用Excel工作表来存储基础设施的相关信息,如IP地址、设备供应商和登录凭证。Python支持从Excel工作表中读取数据并对其进行处理,以便我们在脚本中使用数据。

在这个用例中,我们将使用Excel Read(xlrd)模块读取UC3_devices.xlsx文件。该文件保存了基础设施的主机名、IP地址、用户名、普通密码、特权模式下的密码和供应商名称。然后将读到的数据用作netmiko模块的输入。

Excel工作表中的内容如下图所示。

Python自动化运维实战:使用Python管理网络设备_程序员_22

首先,用pip安装xlrd模块,因为需要用它来读取Microsoft Excel工作表。

pip install xlrd

xlrd模块能够读取Excel工作表并将行和列转换为矩阵。比如,row[0][0]代表第一行第一列的单元格,右边紧接着它的单元格是row[0][1](见下图),以此类推。

Python自动化运维实战:使用Python管理网络设备_python_23

xlrd在读取工作表时,每次读取一行,同时特殊计数器nrows(行数)自动加1。同样,每次读取一列,ncols(列数)自动加1,这样我们就能够通过这两个参数知道矩阵的大小。

然后,在xlrd的open_workbook()函数中输入文件路径,并用sheet_by_index()或sheet_by_name()函数访问工作表。在本例中,数据存储在第一个工作表(index = 0)中,工作表文件存储在以章为名的文件夹下。接着,遍历工作表的每一行,或者使用row()函数来访问指定行。返回的输出结果是一个列表,使用索引可以访问列表中的元素。

Python脚本如下。

__author__ = "Bassim Aly"
__EMAIL__ = "basim.alyy@gmail.com"

from netmiko import ConnectHandler
from netmiko.ssh_exception import AuthenticationException,
NetMikoTimeoutException
import xlrd
from pprint import pprint

workbook =
xlrd.open_workbook(r"/media/bassim/DATA/GoogleDrive/Packt/EnterpriseAutomat
ionProject/Chapter4_Using_Python_to_manage_network_devices/UC3_devices.xlsx
")

sheet = workbook.sheet_by_index(0)

for index in range(1, sheet.nrows):
hostname = sheet.row(index)[0].value
ipaddr = sheet.row(index)[1].value
username = sheet.row(index)[2].value
password = sheet.row(index)[3].value
enable_password = sheet.row(index)[4].value
vendor = sheet.row(index)[5].value

device = {
'device_type': vendor,
'ip': ipaddr,
'username': username,
'password': password,
'secret': enable_password,

}
# pprint(device)

print "########## Connecting to Device {0}
############".format(device['ip'])
try:
net_connect = ConnectHandler(**device)
net_connect.enable()

print "***** show ip configuration of Device *****"
output = net_connect.send_command("show ip int b")
print output

net_connect.disconnect()

except NetMikoTimeoutException:
print "=======SOMETHING WRONG HAPPEN WITH
{0}=======".format(device['ip'])

except AuthenticationException:
print "=======Authentication Failed with
{0}=======".format(device['ip'])
except Exception as unknown_error:
print "=======SOMETHING UNKNOWN HAPPEN WITH {0}======="

4.4 其他用例

使用netmiko可以实现很多网络自动化用例。例如,在升级期间从远程设备上传/下载文件,利用Jinja2模板加载配置,访问终端服务器,访问终端设备等。要了解更多用例,请参见GitHub官网(见下图)。

Python自动化运维实战:使用Python管理网络设备_程序员_24

5 小结

在本章中,我们开始使用Python进入网络自动化世界。本章讨论了Python中的一些工具,通过Telnet和SSH建立到远程节点的连接,并在远程设备上执行命令。此外,本章还讲述了如何在netaddr模块的帮助下处理IP地址和网络子网。最后通过两个实际用例巩固了这些知识。