中文编程
首发于中文编程
用python编写控制网络设备的自动化脚本2:显示

用python编写控制网络设备的自动化脚本2:显示

显示命令介绍(前言)

几乎所有的带网络管理的网络设备都拥有大量的显示命令。显示命令可以直接获取网络设备的详细信息,比如:配置、接口、等等。在思科中显示命令以“show”开头,华为/华三中以“display”开头,意思就是把什么什么东西“秀”出来。在网络故障排错中,敲得最多的就是显示命令,根据设备显示出来的东西,判断出故障在哪,然后排除故障。
显示命令全部在用户模式下敲,因为没有复杂交互逻辑,网络上已经有人实现导出配置的功能,原理就是很傻瓜式地把提前准备好的命令输入进去,然后保存输出信息。而我的脚本除了获取信息以外,还要对信息进行解析,整理成统一格式。
这篇文章的内容就是介绍如何执行显示命令,以及如何处理设备显示出来的内容,以便于用户使用。

简单的网络终端程序

为了方便观察网络设备的输入输出行为,先写一个简单的网络终端。

import time
import cflw网络连接 as 连接
def main():
    v地址 = input("地址: ")
    v端口号 = input("端口号: ")
    v连接 = 连接.C网络终端(v地址, int(v端口号))
    print(v连接.f读_最新())
    while True:
        v内容 = input(">")
        v连接.f写(v内容 + "\r")
        time.sleep(1)
        print(v连接.f读_最新())
if __name__ == "__main__":
    main()

程序很简单,就是连接到网络设备,然后不断地输入命令,看看网络设备会输出什么。

设备执行命令的过程

先定义2个词:输入是通过连接写字符串的过程,输出是从连接读字符串的过程。有了这2个词,才能编写设备的输入输出函数:

class I设备:
    def f输入(self, a文本):
        self.m连接.f写(a文本)
    def f输入_回车(self):
        self.m连接.f写("\r")
    def f输出(self):
        return self.m连接.f读_最新()

设备要执行命令的话,就是输入文本,按回车,然后输出文本。

class I设备:
    def f执行命令(self, a命令):
        "输入一段字符按回车, 并返回输出结果"
        self.f刷新()
        self.f输入(str(a命令))
        self.f输入_回车()
        v输出 = self.f输出()
        return v输出

处理输出

前面的简单网络终端程序已经写好了。这里我演示如何连接到设备随便输入一些命令。

随便敲敲

图中可以看到,连接对象不仅读到了命令、显示的内容,还有提示输入命令的提示符。我们只需要中间那一串信息,在处理输出时需要把头尾两行删除掉。因为这是纯粹的字符串处理,这里就不给出代码了。

处理超长输出

像显示配置这种命令会输出一大堆内容,而且还会因为内容太多,要求按空格、按回车继续输出。

如果还有剩余内容会显示“More”

检测方法很简单。如果设备没显示完全还有剩余内容时,最底下会显示“More”。只要处理输出时检测到有换页提示文本,就按空格继续输出。并把所有输出内容连接起来。

class I设备:
    def f执行显示命令(self, a命令, a自动换页 = False):
        "有自动换页功能"
        self.f刷新()
        self.f输入(str(a命令))
        self.f输入_回车()
        v输出 = ''
        if a自动换页:
            while True:
                v读 = self.f输出()
                v输出 += v读
                if self.m自动换页文本 in v读:  #还有更多
                    self.f输入_空格()
                    continue
                else:
                    break
            v输出 = self.f自动换页替换(v输出)
        else:
            v输出 = self.f输出()
        return v输出

在按空格键换页时,设备会输出退格或回车等特殊字符,这些特殊字符会留在字符串变量中,这个在调试中能直接看到。

图中画横线的文字就是提示还有更多的“More”和用来删掉“More”的特殊字符


由于这些特殊字符影响处理,需要把特殊字符和原本提示换页的文本都删掉。上面的 f自动换页替换 就是把这些字符删掉,因为这也是纯粹的字符串处理,所以我还是不给代码。

在配置模式显示

虽然显示命令位于用户模式,实际上在配置模式也可以不用切换模式直接执行显示命令,比如华为设备就可以。

class C华为设备(I设备):
    def f执行显示命令(self, a命令, a自动换页 = False):
        v命令 = str(a命令)
        v输出 = I设备.f执行显示命令(self, v命令, a自动换页)
        v输出 = f去头尾行(v输出)
        return v输出

思科的显示命令有一点不好:在配置模式敲用户模式的命令必须在命令前加“do”,所以思科还要判断当前模式以确定是否在前面加“do”。

c做 = "do"
class C思科设备(I设备):
    def f执行显示命令(self, a命令):
        v命令 = str(a命令)
        if isinstance(self.ma模式[-1], C用户模式):
            v输出 = I设备.f执行显示命令(self, a命令 = v命令, a自动换页 = True)
        else:   #在配置模式,命令前要加个do
            v输出 = I设备.f执行显示命令(self, a命令 = c做 + v命令, a自动换页 = True)
        v输出 = v输出.replace("\r\n", "\n")
        v输出 = f去头尾行(v输出)
        return v输出

显示当前模式配置

华为/华三有一个很方便的命令“display this”,这个命令的功能是显示当前模式配置。在哪个模式敲这个命令,它就显示那个模式的配置。因为这个显示函数是在模式中调用的,而且每个模式的命令都一样,只需要把执行命令的代码放在 I设备 中,在 I模式.f显示_当前模式配置 调用 I设备.f显示_当前模式配置 就行。

class I设备:
    def f显示_当前模式配置(self):
        raise NotImplementError()
class I模式:
    def f显示_当前模式配置(self):
        self.f切换到当前模式()
        return self.m设备.f显示_当前模式配置()
class C华为设备(I设备):
    def f显示_当前模式配置(self):
        return self.f执行显示命令("display this")

虽然思科没有一个统一的命令来显示不同模式的配置,但是可以用不同的命令来显示不同模式的配置。例如:显示接口配置可以用“show running-config interface 具体接口”,显示用户配置可以用“show running-config | section username 用户名”。只需要在具体模式重写 f显示_当前模式配置 就行。

返回类型统一

因为不同的设备有不同的输出内容,如果直接返回字符串,对于用户而言不好对信息进一步加工处理,所以有必要对显示命令得到的字符串进行解析。

以显示时间为例,虽然不同设备显示出来的时间格式不一样,但是可以把它转换成python的time.struct_time类型,方便处理。

#↓这是思科用户模式的 f显示_时间
    def f显示_时间(self):
        #由于时区名可以设置成奇怪的名字,为了避免奇怪的问题,解析时过滤掉时区
        v输出 = self.m设备.f执行显示命令("show clock")    #*09:09:36.935 UTC Thu Sep 29 2016
        v空格位置 = 字符串.f全部搜索(v输出, " ")
        v行结束 = v输出.find("\n")
        if v行结束 > 0:    #如果有换行符,截取到行结束
            v输出 = v输出[0 : v空格位置[0]] + v输出[v空格位置[1] : v行结束]
        else:   #如果没有换行符,截取到字符串结束
            v输出 = v输出[0 : v空格位置[0]] + v输出[v空格位置[1]:]    #*09:09:36.935 Thu Sep 29 2016
        v时间 = time.strptime(v输出, "*%H:%M:%S.%f %a %b %d %Y")
        return v时间

#↓这是华为用户视图的 f显示_时间
    def f显示_时间(self):
        v命令 = "display clock"
        v输出 = self.m设备.f执行显示命令(v命令)
        #2017-02-19 14:09:32
        #Sunday
        #Time Zone(China-Standard-Time) : UTC-08:00
        v输出 = v输出.split("\n")[0]
        v时间 = time.strptime(v输出, "%Y-%m-%d %H:%M:%S")
        return v时间

然而像显示时间这种只显示几行信息的命令只是少数,更多的是显示一堆表格。

这点信息量算少的,还有一个接口可以输出一页信息的命令,显示所有接口信息完全是刷屏。

要保证返回类型统一,需要写个表格类把类型格式定死,以便于用户使用。

class C接口表:
    """敲"display interface brief"所显示的信息"""
    c标题行 = "Interface                         IP Address/Mask      Physical   Protocol  "
    c接口开始 = 0
    c物理开始 = 28
    c协议开始 = 34
    c输入率 = 43
    c输出率 = 49
    c输入错误 = 58
    c输出错误 = 68
    ca列开始 = (c接口开始, c物理开始, c协议开始, c输入率, c输出率, c输入错误, c输出错误)
    def __init__(self, a):
        v位置 = 字符串.f连续找最后(a, C物理接口表.c标题行, "\n")
        self.m文本 = a[v位置+1:]
    def fe行(self):
        for v行 in self.m文本.split("\n"):
            v接口s, v物理s, v协议s, v输入率s, v输出率s, v输入错误s, v输出错误s = 字符串.fe按位置分割(v行, *C物理接口表.ca列开始)
            v接口 = 设备.S接口.fc字符串(v接口s, 接口.ca接口名称)
            v状态 = "up" in v协议s
            yield 设备.S接口表项(a接口 = v接口, a状态 = v状态)

这代码可能有点跳跃,因为涉及的东西有点多。
上面的代码只用于华为,不能用于其他品牌,这并不能说明类型写死了没有扩展性。python是动态类型语言,具体是什么类型无所谓,只要是只“鸭子”就行。

应用示例:配置备份

利用显示命令导出设备配置设备信息的活已经有人做过了,就像这个:zhuanlan.zhihu.com/p/35
上面这篇文章的代码需要根据不同品牌的设备提前准备好不同的命令,不够灵活。而我写的脚本在理论上可以应用于所有品牌的网络设备,只要知道设备型号、系统版本,后面的操作代码几乎一样的。

直接上代码,开模拟器然后运行程序导出配置。

import cflw网络连接 as 连接
import cflw网络设备_华为 as 华为
def main():
    #连接到设备,取配置
    v连接 = 连接.C网络终端("ensp.localhost", 2000)
    v设备 = 华为.f创建设备(v连接, 华为.E型号.ar201)
    v用户 = v设备.f模式_用户()
    v配置 = str(v用户.f显示_当前配置())
    print(v配置)
    #保存
    v文件 = open("d:/test/a.txt", "w")
    v文件.write(v配置)
    print("结束")
if __name__ == "__main__":
    main()

具体实现细节都藏在代码库里,整个程序显得非常干净。

打开文本文件可以看到配置完整无误地保存在电脑上。

应用示例:跟踪接口状态

这个示例展示了一个不断监视多个设备的接口状态,当接口状态发生变化时会打印信息的脚本。在返回类型统一这一节已经写了如何解析接口列表信息,这个示例只要处理数据即可。

开模拟器,画好拓扑。

写代码。

import time
import cflw网络连接 as 连接
import cflw网络设备_华为 as 华为
ca设备信息 = [
    ("ensp.localhost", 2000, 华为.E型号.ar201),
    ("ensp.localhost", 2001, 华为.E型号.ar201),
    ("ensp.localhost", 2002, 华为.E型号.ar201),
    ("ensp.localhost", 2003, 华为.E型号.ar201),
]
def main():
    #初始化
    va设备 = []
    for v设备信息 in ca设备信息:
        v连接 = 连接.C网络终端(v设备信息[0], v设备信息[1])
        v设备 = 华为.f创建设备(v连接, v设备信息[2])
        va设备.append(v设备)
        v用户 = v设备.f模式_用户()
        v设备.m设备名称 = v用户.f显示_设备名称()
    #开始监视
    va状态 = {}
    print("开始监视")
    while True:
        for v设备 in va设备:
            v用户 = v设备.f模式_用户()
            v接口表 = v用户.f显示_接口表()
            for v行 in v接口表.fe行():
                v接口名称 = str(v行.m接口)
                v键 = (v设备, v接口名称)
                if v键 in va状态:
                    v状态 = va状态[v键]
                    if v行.m状态 != v状态:
                        print("状态变化: %s/%s %s->%s" % (v设备.m设备名称, v接口名称, v状态, v行.m状态))
                va状态[v键] = v行.m状态
        time.sleep(0.5)
if __name__ == "__main__":
    main()

运行之前还要把设备名改一下,因为4台路由器的设备名是默认的“Huawei”,如果不改名会分不清谁是谁。这里我把设备名分别改成R1、R2、R3、R4。

运行脚本,然后在模拟器里随便添加删除线条,可以看到脚本正确地监视接口状态并打印接口变化。

结尾

网络设备可显示的内容太多,全部写出来大概能写个一本书。显示命令的代码在逻辑上都差不多,所以这篇文章只写了如何显示和一点点展示。就像我在《用python编写控制网络设备的自动化脚本1:框架设计》结尾所写的:框架看起来很复杂,重要的东西只有这么点,剩下的就是具体应用。
这篇文章里的代码只给了大概实现,具体代码我已经发到github,见github.com/cflw/cflw_py

编辑于 2019-01-31

文章被以下专栏收录

    在所有编程语言和领域中尝试编写中文代码,开发相关工具,总结经验,一致代码风格。包括中文命名,汉化现有语言,创造中文语法的编程语言等等。作为最熟悉的母语,用来编写代码会让代码更容易被自己和母语相同的其他开发者理解。基于英文的编程语言和框架中,使用中文命名有时有技术问题。希望这里为后人趟雷,填坑。多数现有API是英文的,这里也会对其中一些常用的进行汉化。当然,这里也会对基于中文的编程语言进行探讨。包括汉化基于英文的编程语言,以及创造新的编程语言。