暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

自动化运维初级村-巡检-TextFSM-2


自动化运维,你玩转了吗?



【摘要】

经过前面两个章节的学习,我们已经初步掌握了正则表达式的原理,并从正则解析一步一步过渡到了 TextFSM,而且 TextFSM 在匹配机制上和对于大型项目的重要性上都让我们非常有必要将他集成到巡检模块中。

那么这一章节我们就主要讲解一下如何在 Python 中使用 TextFSM,以及如何将其集成到巡检框架中


【Python+TextFSM】

Python 中使用 TextFSM 需要安装一个第三方包,执行下列命令即可:

pip install textfsm



模板解析

上一个章节中我们已经举了三个使用 TextFSM 模板进行匹配的例子,但并没有演示在 Python 中如何使用模板进行文本解析。

而且大家最为熟知的 TextFSM 使用方法都是直接将其和 Netmiko 等模块结合使用,这种方式也很方便,我们后续同样会使用;但在此之前,我想说的是,TextFSM 本质就是一个模板解析的第三方库,所以肯定是可以用来解析任何文本的,不光局限于网络设备的输出;所以我先给大家讲解一下 Python 中是如何直接使用 TextFSM 进行解析的。


用模版文件解析

首先将模版内容保存在单独的文件中,如下:

# route.tmpl
Value Protocol (\S)
Value Type (\S\S)
Value Required Prefix (\S+)
Value List Gateway (\S+)
Value Distance (\d+)
Value Metric (\d+)
Value LastChange (\S+)

Start
  ^.*----- -> Routes

Routes
  ^\s\s\S\s\S\S -> Continue.Record
  ^\s\s${Protocol} ${Type} ${Prefix}\s+via ${Gateway}\s+${Distance}/${Metric}\s+${LastChange}
  ^\s+via ${Gateway}

这里需要说明的是,我经过测试模版文件的后缀并没有强制要求,之所以保存为 .tmpl 后缀,只是为了更为方便的识别该文件为模板文件。

Python 代码如下:

import json
from textfsm import TextFSM


def parse_from_file():
    stdout = """ Destination Gateway Dist/Metric Last Change
           ----------- ------- ----------- -----------
      B EX 0.0.0.0/0 via 192.0.2.73 20/100 4w0d
                              via 192.0.2.201
                              via 192.0.2.202
                              via 192.0.2.74
      B IN 192.0.2.76/30 via 203.0.113.183 200/100 4w2d
      B IN 192.0.2.204/30 via 203.0.113.183 200/100 4w2d
      B IN 192.0.2.80/30 via 203.0.113.183 200/100 4w2d
      B IN 192.0.2.208/30 via 203.0.113.183 200/100 4w2d"""

    
    with open("route.tmpl", "r+") as f:
        fsm = TextFSM(f)
    res = fsm.ParseTextToDicts(stdout)
    print(json.dumps(res, indent=2))


if __name__ == '__main__':
    parse_from_file()


上述代码中从 textfsm 库中引入了 TextFSM 类,之后将 "route.tmpl" 模板经过 open() 打开后得到一个文件对象,将其传入 TextFSM 类中,得到一个 TextFSM 的实例化对象。

该对象有两个方法是最常使用的,分别是 ParseText() 和 ParseTextToDicts(),这两个方法解析的过程都是一样的,不同的就是呈现结果的结构不一样,前者是以列表的形式返回解析结果,后者是以字典的形式。

通常使用的是后者,因为可以清晰的知道哪个值对应哪个字段,更有利于解析结果处理的准确性和代码的健壮性。

这里我将解析结果用 json 模块处理了一下,更方便打印输出展示,上述代码的输出结果如下:

[
  {
    "Protocol": "B",
    "Type": "EX",
    "Prefix": "0.0.0.0/0",
    "Gateway": [
      "192.0.2.73",
      "192.0.2.201",
      "192.0.2.202",
      "192.0.2.74"
    ],
    "Distance": "20",
    "Metric": "100",
    "LastChange": "4w0d"
  },
  {
    "Protocol": "B",
    "Type": "IN",
    "Prefix": "192.0.2.76/30",
    "Gateway": [
      "203.0.113.183"
    ],
    "Distance": "200",
    "Metric": "100",
    "LastChange": "4w2d"
  },
  {
    "Protocol": "B",
    "Type": "IN",
    "Prefix": "192.0.2.204/30",
    "Gateway": [
      "203.0.113.183"
    ],
    "Distance": "200",
    "Metric": "100",
    "LastChange": "4w2d"
  },
  {
    "Protocol": "B",
    "Type": "IN",
    "Prefix": "192.0.2.80/30",
    "Gateway": [
      "203.0.113.183"
    ],
    "Distance": "200",
    "Metric": "100",
    "LastChange": "4w2d"
  },
  {
    "Protocol": "B",
    "Type": "IN",
    "Prefix": "192.0.2.208/30",
    "Gateway": [
      "203.0.113.183"
    ],
    "Distance": "200",
    "Metric": "100",
    "LastChange": "4w2d"
  }
]


用模板字符串解析

目前大家常见的都是上述将模板保存到文件中,然后去解析,但实际项目中模板的内容并不一定是在文件中,因为如果要将解析过程集成到巡检功能中的话,就需要将模板的管理线上化,而通过服务器文件形式进行管理并不是很好的选择,在多进程的情况下可能会出现多个解析线程都去使用同一个模板解析内容,这时候还需要考虑文件锁

那么既然 TextFSM 模板是字符串,那理所当然我们可以将它保存到数据库中进行管理,这样也更有利于更删改查

所以当从数据库中读取到 TextFSM 模板后,模板内容在程序中实际是一个字符串,那这时候怎么进行解析呢?

从模板文件中解析的时候,初始化 TextFSM 对象需要传入一个打开的文件对象,那么我们可以从这里做文章。

计算机读取一个文件后,实际上是将该文件放在了内存中,供后续的程序使用,那么我们只需要传入一个内存中的文件流,就可以让 TextFSM 解析了

在 Python 中,StringIO 可以将字符串转化为内存中的 IO 流,具备和使用 open() 方法打开文件后获取到的文件对象几乎相同的属性和方法,所以代码如下:

import json
from io import StringIO
from textfsm import TextFSM

template = """Value Protocol (\S)
Value Type (\S\S)
Value Required Prefix (\S+)
Value List Gateway (\S+)
Value Distance (\d+)
Value Metric (\d+)
Value LastChange (\S+)

Start
  ^.*----- -> Routes

Routes
  ^\s\s\S\s\S\S -> Continue.Record
  ^\s\s${Protocol} ${Type} ${Prefix}\s+via ${Gateway}\s+${Distance}/${Metric}\s+${LastChange}
  ^\s+via ${Gateway}"""


stdout = """ Destination Gateway Dist/Metric Last Change
       ----------- ------- ----------- -----------
  B EX 0.0.0.0/0 via 192.0.2.73 20/100 4w0d
                          via 192.0.2.201
                          via 192.0.2.202
                          via 192.0.2.74
  B IN 192.0.2.76/30 via 203.0.113.183 200/100 4w2d
  B IN 192.0.2.204/30 via 203.0.113.183 200/100 4w2d
  B IN 192.0.2.80/30 via 203.0.113.183 200/100 4w2d
  B IN 192.0.2.208/30 via 203.0.113.183 200/100 4w2d"""



def parse_from_str():
    fsm = TextFSM(StringIO(template))
    res = fsm.ParseTextToDicts(stdout)
    print(json.dumps(res, indent=2))


if __name__ == '__main__':
    parse_from_str()


上述代码中,我们把模板定义成了一个变量,并且在初始化 TextFSM 类的时候传入了经过 StringIO() 处理的文件流对象,这样同样可以完成解析,而且调试起来也非常方便。

输出结果与刚才完全相同。


ntc-template的使用

在开源项目中,ntc-template 这个项目将知名厂商的最为常用的交换机命令的输出结果进行了解析,并开源了对应的解析模版(值得大家致敬一下)

这个库的好处就是只需要传入(厂商、命令、输出)就可以返回解析的结果,但如果要解析的命令在库中还没有对应的模板,那就需要做一些定制化的处理,具体的流程如下:

1. 将 ntc-templates 库中的 templates 目录拷贝到你的项目中

2. 在环境变量 NTC_TEMPLATES_DIR 改为你上一步的自定义目录(可以在系统中更改或者在代码中更改)

3. 在第一步的目录中添加自己的解析模板,并且添加相应规则到 index 文件中,格式如下:

{解析模板名称}, .*, {设备的平台}, {执行的命令}


但我个人并不推荐这种方式,首先是文件形式存储模板的缺点,我们在上文中已经提到了,另外就是每次添加自己的模版都要修改 index 文件并不是很友好;但考虑到内容的完整性,所以有必要进行上述步骤的介绍,对以上方式更为认可的朋友可以采用。


Netmiko中结合 ntc-template

在Netmiko中同样集成了解析功能,包括 TTP、TextFSM、Genie三种,并且在 TextFSM 方式里将 ntc-template 集成了进去,所以我们可以直接将执行命令完输出的结果进行解析,并返回结构化的内容,示例如下:

import json
from netmiko import ConnectHandler


def connect(params, command):
    conn = ConnectHandler(**params)
    output = conn.send_command(command, use_textfsm=True)
    print(json.dumps(output, indent=2))


if __name__ == '__main__':
    params = {
        "device_type": "cisco_ios",
        "host": "192.168.31.149",
        "username": "cisco",
        "password": "cisco"
    }
    command = "show version"
    connect(params, command)

""" output
[
  {
    "version": "",
    "rommon": "Bootstrap",
    "hostname": "r1",
    "uptime": "22 weeks, 23 hours, 23 minutes",
    "uptime_years": "",
    "uptime_weeks": "22",
    "uptime_days": "",
    "uptime_hours": "23",
    "uptime_minutes": "23",
    "reload_reason": "Unknown reason",
    "running_image": "/opt/unetlab/addons/iol/bin/i86bi-linux-l3-adventerprisek9-15.4",
    "hardware": [],
    "serial": [
      "67108896"
    ],
    "config_register": "0x0",
    "mac": [],
    "restarted": ""
  }
]
"""


上述代码中我们只需要在调用 send_command 时传入 use_textfsm=True 即可开启TextFSM的解析功能,本质上就是在获取到命令输出之后调用 ntc-template 进行了解析


【巡检解析】


Netmiko + TextFSM

上文中介绍了 Netmiko 中内置了 TextFSM 解析的功能,本质上是调用了 ntc-template 模块,但由于 ntc-template 的自定义方式不够灵活,所以并不适合集成到我们的巡检功能中,因此需要结合「模版字符串解析」的方式进行适当的改造,代码如下:

import json
from io import StringIO
from typing import List, Dict

from textfsm import TextFSM
from netmiko import ConnectHandler


def parse_from_str(output: str, template: str) -> List[Dict]:
    fsm = TextFSM(StringIO(template))
    res = fsm.ParseTextToDicts(output)
    return res


def connect(params: Dict, command: str, template: str):
    conn = ConnectHandler(**params)
    output = conn.send_command(command)
    res = parse_from_str(output, template)
    print(json.dumps(res, indent=2))


if __name__ == '__main__':
    params = {
        "device_type": "cisco_ios",
        "host": "192.168.31.149",
        "username": "cisco",
        "password": "cisco"
    }
    template = """模版内容从数据库中读取"""
    command = "show version"
    connect(params, command, template)


上述代码中,在执行完 send_command 后,将输出内容和模板变量一起传入到 parse_from_str 方法中即可;该方法与直接使用 send_command 方法的解析功能没有本质的区别,因为底层都是用了 TextFSM进行解析,但不同的是获取模板的方式,改为了可以被灵活处理,这里为了更好的与巡检功能结合,后续我们会从数据库中获取模板内容


【总结】

这一章节主要介绍了如何在 Python 中使用 TextFSM,并且根据逐步的过渡,已经可以初步看出如何将其集成到我们的巡检功能中了,下一章节就正式让巡检模块具备解析功能,并将其完整的运行起来。


yuefeiyu1024

添加作者微信加入专属学习交流群,获取更多干货秘籍



文章转载自python玩转自动化运维,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论