Package: net-inspect
net_inspect 教程前言作为网络工程师，需要经常收集设备的设备信息，比如设备CPU利用率、内存利用率、软件版本、序列号、接口状态等，这些信息可以用来监控网络设备的运行状态，也可以用来做设备的资产管理。在已经收集完成的情况下，我们需要将关键信息提取出来，然后将可用数据按需求格式输出。市面上目前没有针对多厂商的网络设备数据解析工具，厂家只会制作针对自己设备的解析工具。造成使用割裂，并且输出样式也无法做到自定义。另外，网络工程师学习编程后大部分都是单兵作战，针对自己的使用场景，进行专门的脚本编写，但这就导致了脚本的可复用性差、性能优化不足、脚本设计考虑不够周全、扩展性不强、调试困难等。通常情况下, 在获得设备回显信息后, 通过textFSM或者正则表达式解析数据，最后将可用的数据按需求格式进行输出。但是，这样的操作方式有很多不便，比如：在通过show或者display将信息收集后，如何分割命令的回显？哪些是无效命令？网管平台收集的信息格式不一样怎么处理？如何识别设备对应的厂商？如何调用对应的textFSM模板？如何搜索命令获取关键信息？多厂商的情况下，如何做到统一的数据调用？多厂商的情况下，如何做到统一的数据格式？ntc-templates大部分模板都是国外厂商，怎么办？如何在调用ntc-templates的同时调用自己写的textFSM模板库？设计的脚本只针对单一情况，如何解决复用性的问题？要解决以上问题，就需要用到net_inspect这个框架。它可以让使用者无需关心数据处理的各种细节以及各个厂家的差异。屏蔽了厂商差异，只需要在数据处理完成后通过统一的方式调用即可。net_inspect框架介绍net_inspect是一个基于python的网络设备数据解析自动化框架框架设计的初衷就是将各个环节解耦，使各个环节能够独立开发扩展，最终实现对多厂商设备的数据解析。安装方法：pipinstallnet_inspect一个简单的例子├── log
│   ├── Switch_A.log
│   ├── Switch_B.log
│   ├── Switch_C.log可用看到，在log文件夹下有三台设备的命令的回显信息，这些文件是通过vty或者console连接到设备上作为记录日志保存下来。每个文件中都需要包含display version这个命令来识别设备的厂商。现在我们需要将这些设备的厂商、软件版本、管理IP，提取打印出来。fromnet_inspectimportNetInspectnet=NetInspect()net.set_plugins(input_plugin='console')net.run(input_path='log')print('total devices:',len(net.cluster.devices))fordeviceinnet.cluster.devices:info=device.infoprint(' | '.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]类型如果为None，则表示未分析出结果。如果为True，则表示有告警级别的信息。如果为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_elinpfnet_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_vrp、hp_comware、cisco_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这行。做好准备工作后，只需要在脚本中调用本地模板库即可。fromnet_inspectimportNetInspect,vendornet=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))fordeviceinnet.cluster.devices:info=device.infoclock=''ifdevice.vendor==vendor.Huawei:withdevice.search_cmd('display clock')ascmd:ifcmd.parse_result:ps=cmd.parse_result[0]clock=f"{ps['year']}-{ps['month']}-{ps['day']}{ps['time']}"ifdevice.vendor==vendor.H3C:withdevice.search_cmd('display clock')ascmd:ifcmd.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属性就可以了，方法如下：fromnet_inspectimportNetInspect,EachVendorDeviceInfo,BaseInfo,DeviceclassAppendClock(BaseInfo):clock:str=''# 巡检时间classEachVendorWithClock(EachVendorDeviceInfo):base_info_class=AppendClock# 基本信息类defdo_huawei_vrp_baseinfo_2(self,device:Device,info:AppendClock):# 添加do_<vendor_platform>_baseinfo_<something>方法，可以自动运行withdevice.search_cmd('display clock')ascmd:ifcmd.parse_result:row=cmd.parse_result[0]info.clock=f'{row["year"]}-{row["month"]}-{row["day"]}{row["time"]}'defdo_hp_comware_baseinfo_2(self,device:Device,info:AppendClock):withdevice.search_cmd('display clock')ascmd:ifcmd.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))fordeviceinnet.cluster.devices:info=device.info# type: AppendClockprint(' | '.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__importannotationsfromtypingimportTYPE_CHECKINGfromnet_inspectimportNetInspect,vendorfromnet_inspect.analysis_pluginimportanalysis,AnalysisPluginAbcifTYPE_CHECKING:fromnet_inspect.analysis_pluginimportTemplateInfofromnet_inspect.domainimportAnalysisResultclassAnalysisPluginWithOSPFStatus(AnalysisPluginAbc):"""OSPF status 状态不能为Init"""@analysis.vendor(vendor.Huawei)@analysis.template_key('huawei_vrp_display_ospf_peer_brief.textfsm',['neighbor','state'])defhuawei_vrp(template:TemplateInfo,result:AnalysisResult):"""华为状态检查"""forrowintemplate['display ospf peer brief']:ifrow['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))fordeviceinnet.cluster.devices:ospf_status=device.analysis_result.get('ospf status')warning_list=[]foralarminospf_status:ifalarm.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这一个厂家的分析方法，其他厂家的分析方法大家可以自行添加。编写这个需要注意：@analysis.template_key中的templates名称需要完整包含后缀名。类方法不需要self这个关键字。结果加入到result中即可，不需要返回值。整个过程不用单独设置，所有信息会直接写入analysis全局变量中。类注释和方法注释必须要写，因为会用到这个注释作为分析结果的标题。自定义输出模块完成了数据的分析后，需要对结果进行调用，可以直接以写脚本的方式，也可以自定义输出模块。自定义输出模块的方式方便二次调用，比如我们写一个输出table的例子：fromnet_inspectimportNetInspect,OutputPluginAbstractfromrich.tableimportTablefromrich.consoleimportConsoleclassOutput(OutputPluginAbstract):defmain(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']forcolincolumns:table.add_column(col,justify='center')table.row_styles=['green']fordeviceinself.args.devices:info=device.infotable.add_row(info.hostname,info.ip,info.model,info.version,'Abnormal'ifinfo.analysis.powerelse'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变量中，可以直接使用。手动添加设备有时候我们需要手动添加设备，比如我们需要添加一个设备，但是这个设备没有日志，我们可以手动添加，并且指定设备的厂家，这样就可以使用对应厂家的分析模块。fromnet_inspectimportNetInspect,InputPluginResult,vendornet=NetInspect()d=InputPluginResult()d.add_cmd('display clock',"2021-03-19 10:23:08+08:00")d.hostname='Device'd.vendor=vendor.Huaweinet.add_device(d)net.run()fordeviceinnet.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插件为例：创建一个新的插件文件, 对应的文件初始状态会一并准备好python./develop_script.py-pfan_status-g在对应的文件中实现插件对每个厂商分析的函数classAnalysisPluginWithFanStatus(AnalysisPluginAbc):"""要求设备所有在位风扇模块运行在正常状态。"""@analysis.vendor(vendor.H3C)@analysis.template_key('hp_comware_display_fan.textfsm',['slot','id','status'])defhp_comware(template:TemplateInfo,result:AnalysisResult):"""模块状态不为Normal的时候告警"""forrowintemplate['display fan']:ifrow['status'].lower()!='normal':result.add_warning(f'Slot{row["slot"]}Fan{row["id"]}状态异常'ifrow['slot']elsef'Fan{row["id"]}状态异常')其中@analysis是用来记录插件的分析类型的，vendor记录插件的厂商类型，template_key记录分析模块所需要的textfsm文件以及里面的哪些值。
这些值会在参数template: TemplateInfo中给出。result: AnalysisResult用来记录分析结果。可以添加告警信息。分析方法为类方法，不需要self,不需要给出返回值。插件中的类注释和方法注释都会被记录下来，方便后续调用。创建对应的测试文件当编写了对应的分析方法后，再次执行创建命令，工具会自动根据分析方法中需要的命令，生成对应的测试文件。测试文件路径为tests/check_analysis_plugins/<plugin_name>/<funcation_name>.rawpython./development_script.py-pfan_status-fhp_comware-g在测试文件中添加测试用例执行测试python./development_script.py-pfan_status-fhp_comware-t完成测试，确认测试结果为正常后，生成yml文件作为参考文件。python./development_script.py-pfan_status-fhp_comware-y