前两天做了一个 Jinja2 的小笔记《使用 Jinja2 模版生成网络设备配置文件》,又发现一个 Jinja2 的“反向”工具 TTP - Text Template Parser。它和 Jinja2 的作用相反,是通过包含变量的模版从文本文件中提取 Key-Value 并生成结构化的数据。
其实,可以提供类似功能的工具有很多,Ansible 已经帮我们找出来了:

最直接的方法就是用正则表达式。但是对于传统的网工,写正则表达式是一种折磨。
最简单的方法是直接用 json 或者 xml 输出结果。但并不是所有的网络设备都能像 Cisco 的 NXOS 那么友好,也不是所有的网络设备都能提供完备的 API。

pyATS 虽然是 Cisco 写的,支持 IOS/IOSXE/IOSXR/NXOS/ASA,但 Cisco 的产品线显然并不只有这些。而且,友商咋办呢?
至于 ntc_template,TextFSM,还是要大量依赖正则表达式。前些天我写了一个程序自动化监控 Cisco WLC 无线控制器上的 AP 数据,发现 TextFSM 太难用了。我写两个正则表达式直接处理就完事了,反正就 2 个,何苦浪费时间去写专门的 TextFSM 模版呢。
但 TTP 可以很好滴帮助我偷懒。只有特殊情况才需要写表达式,多数情况下直接定义变量就行了。
下面是 WLC 的 show ap summary 的输出结果,我只选出 3 个有代表性的 AP 信息:
AP SummaryNumber of APs.................................... 358Global AP User Name.............................. Not ConfiguredGlobal AP Dot1x User Name........................ Not ConfiguredAP Name Slots AP Model Ethernet MAC Location Country IP Address Clients DSE Location------------------------------ ----- -------------------- ----------------- -------------------- ---------- --------------- ------- --------------SZ-S-30F08 2 AIR-AP3802I-H-K9 68:3b:78:8f:7e:ea default location CN 10.80.5.42 2 [0 ,0 ,0 ]SZ-S-33F17 2 AIR-AP3802I-H-K9 24:e9:b3:ec:82:93 XXX-6F-JLS CN 10.80.5.112 0 [0 ,0 ,0 ]SZ-S-29F17 2 AIR-AP3802I-H-K9 f8:c2:88:41:33:88 SZ-XXX-5 meeting room CN 10.80.4.72 3 [0 ,0 ,0 ]
这份 ap summary 有几个特点:
分成了两部分:上半部分是单纯的文本 line;下半部分是 table
下半部分 table 的每一列的宽度大致是固定的,对齐的
Location 列的数据比较复杂,有不带空格的,有 1 个空格的,还有 2 个空格的
DSE Location 也比较复杂,是一个 list
但使用 TTP 就很简单。我们把列名原封不动地复制到一个空白的文本文件中。不要删除列名之间的空格,因为 TTP 需要用这些空格来判断列宽。对于有空格的列名,例如 “AP Name”,可以用下划线替换中间的空格,改写成 “AP_Name”。然后在后面加上 {{ _headers_ }},就完成了我们的模版。
AP_Name Slots AP_Model Ethernet_MAC Location Country IP_Address Clients DSE_Location {{ _headers_ }}
接下来就是从源数据中提取 Key-Value 了。当然可以写一个 Python,但我们做 demo,先简单一点,直接在 terminal 运行 ttp,先输出一个 json:
-d:源数据文件
-t:模版文件
-o:输出格式
% ttp -d ./show_ap_summary.txt -t ./ap_sum.ttp.txt -o json 21-03-08 - 22:28:14[[[{"AP_Model": "--------------------","AP_Name": "------------------------------","Clients": "-------","Country": "----------","DSE_Location": "--------------","Ethernet_MAC": "-----------------","IP_Address": "---------------","Location": "--------------------","Slots": "-----"},{"AP_Model": "AIR-AP3802I-H-K9","AP_Name": "SZ-S-30F08","Clients": "2","Country": "CN","DSE_Location": "[0 ,0 ,0 ]","Ethernet_MAC": "68:3b:78:8f:7e:ea","IP_Address": "10.80.5.42","Location": "default location","Slots": "2"},{"AP_Model": "AIR-AP3802I-H-K9","AP_Name": "SZ-S-33F17","Clients": "0","Country": "CN","DSE_Location": "[0 ,0 ,0 ]","Ethernet_MAC": "24:e9:b3:ec:82:93","IP_Address": "10.80.5.112","Location": "XXX-6F-JLS","Slots": "2"},{"AP_Model": "AIR-AP3802I-H-K9","AP_Name": "SZ-S-29F17","Clients": "3","Country": "CN","DSE_Location": "[0 ,0 ,0 ]","Ethernet_MAC": "f8:c2:88:41:33:88","IP_Address": "10.80.4.72","Location": "SZ-XXX-5 meeting room","Slots": "2"}]]]
怎样?是不是超级简单?
但还不完美,因为它把 Header 下面用来分割的横线作为 Value 给提取出来了。因为 _headers_ 方法仅比较宽度,而不考虑数据的内容。我们换另外一种方式写模版。
首先处理复杂的 Location 列和 DSE Location 列。TTP 内置了很多 Regex Patterns,其中一个是 “ORPHRASE”,可以匹配连续的多个 word,只要这些 word 中间有且只有 1 个空格。所以在变量之后加上 “| ORPHRASE”,就可以匹配上面的 3 种 location,也可以匹配 DSE 列表。
然后,我们再增加一个匹配。TTP 内置的 Regex Patterns 还包括 MAC 地址,IP 地址,IPv6 地址,prefix 等等。其实我们只需要为 1 个变量增加 Regex Pattern 就可以,但是 demo 嘛,咱就多加几个:
{{ ap_name }} {{ slots | DIGIT }} {{ ap_model }} {{ eth_mac | MAC }} {{ location | ORPHRASE }} {{ country }} {{ ip_addr | IP }} {{ clients | DIGIT }} {{ dse_loc | ORPHRASE }}
然后再一次运行 ttp,这一次输出 YAML 格式:
% ttp -d ./show_ap_summary.txt -t ./ap_sum.ttp2.txt -o yaml 21-03-08 - 22:44:22- - - ap_model: AIR-AP3802I-H-K9ap_name: SZ-S-30F08clients: '2'country: CNdse_loc: '[0 ,0 ,0 ]'eth_mac: 68:3b:78:8f:7e:eaip_addr: 10.80.5.42location: default locationslots: '2'- ap_model: AIR-AP3802I-H-K9ap_name: SZ-S-33F17clients: '0'country: CNdse_loc: '[0 ,0 ,0 ]'eth_mac: 24:e9:b3:ec:82:93ip_addr: 10.80.5.112location: XXX-6F-JLSslots: '2'- ap_model: AIR-AP3802I-H-K9ap_name: SZ-S-29F17clients: '3'country: CNdse_loc: '[0 ,0 ,0 ]'eth_mac: f8:c2:88:41:33:88ip_addr: 10.80.4.72location: SZ-XXX-5 meeting roomslots: '2'
今天先到这儿,改天继续。




