Skip to main content

基于已收集的网络设备信息进行的结构化数据分析框架

Project description

PyPI Python version GitHub stars GitHub license codecov Documentation Status

net_inspect 教程

前言

作为网络工程师,需要经常收集设备的设备信息,比如设备CPU利用率、内存利用率、软件版本、序列号、接口状态等,这些信息可以用来监控网络设备的运行状态,也可以用来做设备的资产管理。

在已经收集完成的情况下,我们需要将关键信息提取出来,然后将可用数据按需求格式输出。市面上目前没有针对多厂商的网络设备数据解析工具,厂家只会制作针对自己设备的解析工具。造成使用割裂,并且输出样式也无法做到自定义。

另外,网络工程师学习编程后大部分都是单兵作战,针对自己的使用场景,进行专门的脚本编写,但这就导致了脚本的可复用性差、性能优化不足、脚本设计考虑不够周全、扩展性不强、调试困难等。

通常情况下, 在获得设备回显信息后, 通过textFSM或者正则表达式解析数据,最后将可用的数据按需求格式进行输出。

但是,这样的操作方式有很多不便,比如:

  1. 在通过show或者display将信息收集后,如何分割命令的回显?哪些是无效命令?
  2. 网管平台收集的信息格式不一样怎么处理?
  3. 如何识别设备对应的厂商?
  4. 如何调用对应的textFSM模板?
  5. 如何搜索命令获取关键信息?
  6. 多厂商的情况下,如何做到统一的数据调用?
  7. 多厂商的情况下,如何做到统一的数据格式?
  8. ntc-templates大部分模板都是国外厂商,怎么办?
  9. 如何在调用ntc-templates的同时调用自己写的textFSM模板库?
  10. 设计的脚本只针对单一情况,如何解决复用性的问题?

要解决以上问题,就需要用到net_inspect这个框架。

它可以让使用者无需关心数据处理的各种细节以及各个厂家的差异。屏蔽了厂商差异,只需要在数据处理完成后通过统一的方式调用即可。

net_inspect框架介绍

net_inspect是一个基于python的网络设备数据解析自动化框架

框架设计的初衷就是将各个环节解耦,使各个环节能够独立开发扩展,最终实现对多厂商设备的数据解析。

安装方法:

pip install net_inspect

一个简单的例子

├── log
│   ├── Switch_A.log
│   ├── Switch_B.log
│   ├── Switch_C.log

可用看到,在log文件夹下有三台设备的命令的回显信息,这些文件是通过vty或者console连接到设备上作为记录日志保存下来。每个文件中都需要包含display version 这个命令来识别设备的厂商。

现在我们需要将这些设备的厂商、软件版本、管理IP,提取打印出来。

from net_inspect import NetInspect

net = NetInspect()
net.set_plugins(input_plugin='console')
net.run(input_path='log')

print('total devices:', len(net.cluster.devices))

for device in net.cluster.devices:
    info = device.info
    print(' | '.join([info.hostname, info.ip, info.vendor,
          info.version, info.model, info.cpu_usage]))

output:

total devices: 3
Switch_A | 24.44.1.248 | Huawei | 5.170 | S12704 Terabit Routing Switch | 6%
Switch_B | 24.45.1.240 | H3C | 5.20 Release: 3342 | SR8800 | 5%
Switch_C | 24.45.254.254 | H3C | 5.20 Release: 1238P08 | S9508E-V | 1%

可以看到,仅仅通过简单的几行代码,就可以实现对多厂商设备的数据解析和调用。

console代表的是vty或者console连接设备的回显信息格式。

可供使用的base_info属性信息在使用net_inspect -b查看 其中analysis的分析结果均是Optional[bool] 类型

  1. 如果为None,则表示未分析出结果。
  2. 如果为True,则表示有告警级别的信息。
  3. 如果为False,则表示为正常或者只是关注级别的信息。

框架的组成

net_inspect由以下几个部分组成:

  • input_plugin 输入插件,用于将设备的回显进行分割,将每个执行的命令和对应的回显作为字典储存。
  • parse_plugin 解析插件,用于对命令进行解析,一般情况下不需要进行修改。
  • analysis_plugin 分析插件,每一个分析插件对应一个状态分析,比如power_status就是用来分析设备的电源状态的。
  • output_plugin 输出插件,用于输出统一的格式报告。
  • base_info 基础信息,用于存放设备的基础信息,比如厂商、软件版本、管理IP等。可以全厂商统一调用。

有时候我们收集设备信息的方式不止是通过console,或者自己的telnet/ssh。而是通过网管平台进行的,回显格式不同,这时候就需要自己写一个input_plugin,将网管平台的数据转换成net_inspect需要的格式。

parse_plugin 使用的是ntc_templates_elinpf这个库,它是ntc_templates的一个分支,主要是为了解决ntc_templates没有对国内厂商支持的问题。

所以,需要先卸载pip uninstall ntc_templates 然后安装pip install ntc_templates_elinpf

net_inspect支持cli的命令行模式,可以通过net_inspect -h查看帮助信息。

进阶教程

上面的简单例子中还有很多地方没有涉及到,比如analysis_plugin的使用,base_info的使用,output_plugin的使用等等。

试想一个情况,我们需要获取设备的show clock信息,在ntc_templates_elinpf中没有,怎么办?

调用外部textFSM模板

要解决ntc_templates_elinpf中没有模板的问题,我们可以自己写一个本地的textFSM模板仓库,然后调用。

├── local_templates
│   ├── huawei_vrp_display_clock.textfsm
│   ├── hp_comware_display_clock.textfsm
│   ├── cisco_ios_show_clock.textfsm
│   ├── index

可以看到上面的本地模板库中,包含了huawei_vrphp_comwarecisco_ios三个厂商的display clock模板。

另外还有一个index文件,用于指定模板的调用命令,毕竟我们在敲命令的时候不会敲全,比如display clock,我们可以敲dis clo,这时候就需要index文件来指定。

index文件内容如下:

Template, Hostname, Platform, Command

huawei_vrp_display_clock.textfsm, .*, huawei_vrp, dis[[play]] clo[[ck]]

hp_comware_display_clock.textfsm, .*, hp_comware, di[[splay]] clo[[ck]]

cisco_ios_show_clock.textfsm, .*, cisco_ios, sh[[ow]] clo[[ck]]

注意每个厂商需要空格隔开, 并且开头必须要有Template, Hostname, Platform, Command这行。

做好准备工作后,只需要在脚本中调用本地模板库即可。

from net_inspect import NetInspect, vendor

net = NetInspect()
net.set_plugins(input_plugin='console')
net.set_external_templates('local_templates')  # 调用本地模板库
net.run(input_path='log')

print('total devices:', len(net.cluster.devices))

for device in net.cluster.devices:
    info = device.info
    clock = ''
    if device.vendor == vendor.Huawei:
        with device.search_cmd('display clock') as cmd:
            if cmd.parse_result:
                ps = cmd.parse_result[0]
                clock = f"{ps['year']}-{ps['month']}-{ps['day']} {ps['time']}"

    if device.vendor == vendor.H3C:
        with device.search_cmd('display clock') as cmd:
            if cmd.parse_result:
                ps = cmd.parse_result[0]
                clock = f"{ps['year']}-{ps['month']}-{ps['day']} {ps['time']}"

    print(' | '.join([info.hostname, clock]))

output:

total devices: 3
Switch_A | 2021-03-19 10:23:08
Switch_B | 2021-03-19 10:24:17
Switch_C | 2021-03-19 10:32:17

可以看到,我们通过调用本地模板库,成功解析了display clock命令。

然后搜索厂商对应的命令,将解析的结果格式化为我们想要的格式。最后再输出。

这样的写法是否有点麻烦,并且不利于复用呢?有没有更好的方式呢?

新增基础信息

我们可以在base_info中新增一个clock字段,然后只需要调用这个clock属性就可以了,方法如下:

from net_inspect import NetInspect, EachVendorDeviceInfo, BaseInfo, Device


class AppendClock(BaseInfo):
    clock: str = ''  # 巡检时间


class EachVendorWithClock(EachVendorDeviceInfo):

    base_info_class = AppendClock  # 基本信息类

    def do_huawei_vrp_baseinfo_2(self, device: Device, info: AppendClock):
        # 添加do_<vendor_platform>_baseinfo_<something>方法,可以自动运行
        with device.search_cmd('display clock') as cmd:
            if cmd.parse_result:
                row = cmd.parse_result[0]
                info.clock = f'{row["year"]}-{row["month"]}-{row["day"]} {row["time"]}'

    def do_hp_comware_baseinfo_2(self, device: Device, info: AppendClock):
        with device.search_cmd('display clock') as cmd:
            if cmd.parse_result:
                row = cmd.parse_result[0]
                info.clock = f'{row["year"]}-{row["month"]}-{row["day"]} {row["time"]}'


if __name__ == '__main__':
    net = NetInspect()
    net.set_plugins(input_plugin='console')
    net.set_base_info_handler(EachVendorWithClock)  # 设置获取设备基本信息的处理类
    net.set_external_templates('local_templates')
    net.run(input_path='log')

    print('total devices:', len(net.cluster.devices))

    for device in net.cluster.devices:
        info = device.info  # type: AppendClock
        print(' | '.join([info.hostname, info.clock]))

output:

total devices: 3
Switch_A | 2021-03-19 10:23:08
Switch_B | 2021-03-19 10:24:17
Switch_C | 2021-03-19 10:32:17

输出的结果是一样的,可以看到,在base_info中新增了一个clock字段,然后只需要调用这个clock属性就可以了。这样的做法复用性强,后续再想要获取设备的时间的时候,只需要调用clock属性即可。

自定义分析信息

完成了数据的解析调用,下面还需要对设备进行分析,比如判断电源、风扇是否故障,cpu、memory利用率是否过高等。

net_inspect中没有需要的分析模块时,我们可以自定义分析模块。

比如我们写一个检查OSPF邻居状态的分析模块

from __future__ import annotations

from typing import TYPE_CHECKING
from net_inspect import NetInspect, vendor
from net_inspect.analysis_plugin import analysis, AnalysisPluginAbc

if TYPE_CHECKING:
    from net_inspect.analysis_plugin import TemplateInfo
    from net_inspect.domain import AnalysisResult


class AnalysisPluginWithOSPFStatus(AnalysisPluginAbc):
    """OSPF status 状态不能为Init"""

    @analysis.vendor(vendor.Huawei)
    @analysis.template_key('huawei_vrp_display_ospf_peer_brief.textfsm', ['neighbor', 'state'])
    def huawei_vrp(template: TemplateInfo, result: AnalysisResult):
        """华为状态检查"""
        for row in template['display ospf peer brief']:
            if row['state'].lower() == 'init':
                result.add_warning(f'{row["neighbor"]} is in init state')


if __name__ == '__main__':
    net = NetInspect()
    net.set_plugins(input_plugin='console')
    net.run(input_path='log')

    print('total devices:', len(net.cluster.devices))

    for device in net.cluster.devices:
        ospf_status = device.analysis_result.get('ospf status')
        warning_list = []
        for alarm in ospf_status:
            if alarm.is_warning:
                warning_list.append(alarm.message)

        print(' | '.join([device.info.hostname, ', '.join(warning_list)]))

output:

total devices: 3
Switch_A | 24.42.254.20 is in init state
Switch_B |
Switch_C |

这样我们就得到了一个可重复利用的分析模块,后续在想要检查OSPF邻居状态的时候,只需要调用这个模块即可。

这里的案例只添加了Huawei这一个厂家的分析方法,其他厂家的分析方法大家可以自行添加。

编写这个需要注意:

  1. @analysis.template_key中的templates名称需要完整包含后缀名。
  2. 类方法不需要self这个关键字。
  3. 结果加入到result中即可,不需要返回值。
  4. 整个过程不用单独设置,所有信息会直接写入analysis全局变量中。
  5. 类注释和方法注释必须要写,因为会用到这个注释作为分析结果的标题。

自定义输出模块

完成了数据的分析后,需要对结果进行调用,可以直接以写脚本的方式,也可以自定义输出模块。

自定义输出模块的方式方便二次调用,比如我们写一个输出table的例子:

from net_inspect import NetInspect, OutputPluginAbstract
from rich.table import Table
from rich.console import Console


class Output(OutputPluginAbstract):
    def main(self):
        self.check_args('title')  # 检查是否提供title参数
        console = Console()

        table = Table(title=self.args.output_params.get(
            'title'), show_lines=False)
        columns = ['hostname', 'ip', 'model', 'version', 'power status']
        for col in columns:
            table.add_column(col, justify='center')
        table.row_styles = ['green']

        for device in self.args.devices:
            info = device.info
            table.add_row(
                info.hostname,
                info.ip,
                info.model,
                info.version,
                'Abnormal' if info.analysis.power else 'Normal'
            )

        console.print(table)

if __name__ == '__main__':

    net = NetInspect()
    net.set_plugins(input_plugin='console', output_plugin=Output)
    cluster = net.run(input_path='log', output_plugin_params={
                    'title': '设备信息'})

output:

可以看到,我们自定义了一个输出模块,可以直接调用,而不需要写脚本。

output_plugin_params中的所有参数都会传递到Output类中的output_params变量中,可以直接使用。

手动添加设备

有时候我们需要手动添加设备,比如我们需要添加一个设备,但是这个设备没有日志,我们可以手动添加,并且指定设备的厂家,这样就可以使用对应厂家的分析模块。

from net_inspect import NetInspect, InputPluginResult, vendor

net = NetInspect()

d = InputPluginResult()
d.add_cmd('display clock', "2021-03-19 10:23:08+08:00")
d.hostname = 'Device'
d.vendor = vendor.Huawei

net.add_device(d)

net.run()

for device in net.cluster.devices:
    print(device.info.hostname)
    print(device.parse_result('dis clo'))

output:

Device
[{'time': '10:23:08', 'timezone': '', 'dayweek': '', 'year': '2021', 'month': '03', 'day': '19'}]

关于贡献

分析插件还在持续开发中,develop_script.py脚本就是为高效开发提供的一个工具。

开发一个分析插件的流程,以开发检查风扇状态的fan_status插件为例:

  1. 创建一个新的插件文件, 对应的文件初始状态会一并准备好
python ./develop_script.py -p fan_status -g
  1. 在对应的文件中实现插件对每个厂商分析的函数
class AnalysisPluginWithFanStatus(AnalysisPluginAbc):
    """
    要求设备所有在位风扇模块运行在正常状态。
    """
    @analysis.vendor(vendor.H3C)
    @analysis.template_key('hp_comware_display_fan.textfsm', ['slot', 'id', 'status'])
    def hp_comware(template: TemplateInfo, result: AnalysisResult):
        """模块状态不为Normal的时候告警"""
        for row in template['display fan']:
            if row['status'].lower() != 'normal':
                result.add_warning(
                    f'Slot {row["slot"]} Fan {row["id"]} 状态异常' if row['slot'] else f'Fan {row["id"]} 状态异常')

其中@analysis是用来记录插件的分析类型的,vendor记录插件的厂商类型,template_key记录分析模块所需要的textfsm文件以及里面的哪些值。 这些值会在参数template: TemplateInfo中给出。

result: AnalysisResult用来记录分析结果。可以添加告警信息。

分析方法为类方法,不需要self,不需要给出返回值。

插件中的类注释和方法注释都会被记录下来,方便后续调用。

  1. 创建对应的测试文件

当编写了对应的分析方法后,再次执行创建命令,工具会自动根据分析方法中需要的命令,生成对应的测试文件。

测试文件路径为tests/check_analysis_plugins/<plugin_name>/<funcation_name>.raw

python ./development_script.py -p fan_status -f hp_comware -g
  1. 在测试文件中添加测试用例
  2. 执行测试
python ./development_script.py -p fan_status -f hp_comware -t
  1. 完成测试,确认测试结果为正常后,生成yml文件作为参考文件。
python ./development_script.py -p fan_status -f hp_comware -y

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

net_inspect-1.5.3.tar.gz (50.8 kB view details)

Uploaded Source

Built Distribution

net_inspect-1.5.3-py3-none-any.whl (55.7 kB view details)

Uploaded Python 3

File details

Details for the file net_inspect-1.5.3.tar.gz.

File metadata

  • Download URL: net_inspect-1.5.3.tar.gz
  • Upload date:
  • Size: 50.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.3.2 CPython/3.11.1 Linux/5.15.0-1031-azure

File hashes

Hashes for net_inspect-1.5.3.tar.gz
Algorithm Hash digest
SHA256 5868cf44419793df52bf6f069ebf73928421ed7b3ca4cb5eabbfa39066adf9ff
MD5 967dace0b60797a07df320c84d4edc0b
BLAKE2b-256 24f786f34420e5cc579131f8c31e003f987607172527e5c48165818c865647ce

See more details on using hashes here.

File details

Details for the file net_inspect-1.5.3-py3-none-any.whl.

File metadata

  • Download URL: net_inspect-1.5.3-py3-none-any.whl
  • Upload date:
  • Size: 55.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.3.2 CPython/3.11.1 Linux/5.15.0-1031-azure

File hashes

Hashes for net_inspect-1.5.3-py3-none-any.whl
Algorithm Hash digest
SHA256 c70e5542ca60d93de7188523c6118373aaa9162dfd2109d4bec4bc947ea3a2c6
MD5 518d335c551964cddc3d9c06373c82bc
BLAKE2b-256 4a9d925325f8934abc6f97d69f3f1a40415fb2211d40dae7d23047ef098eb8d1

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page