跳转至

之前学习了下Nornir这个NetDevops框架,实践中发现通过Nornir原生的yaml方式管理设备,实际工作量比较大,因为我们自身CMDB是做到Adminset里面的,通过数据库读写。一开始的思路是将设备信息从数据库读出来再写入yaml文件,~~然后Nornir再读取yaml文件~~,实际上这个需求就是自定义一个inventory插件,能够按照自己的方式灵活加载网络设备。

Nornir作为一款自比Flask的网络自动化框架,灵活性,本身就是其主打的feature之一,网上找了一个基于Nornir原生SimpleInventory自定义的CMDBInventory,核心就是yaml与字典的转换,构建Host列表,从而构建Inventory对象(defaults与group暂时用处不大,可以按需构建)PS:threaded这个runner插件也一样,大概看了一下源码是用的concurrent.futuresThreadPoolExecutor,甚至可以照着重写一个Processed runner插件。回归inventory正题,通过这种自定义插件很直观的体现了Nornir的灵活性,代码如下:

cmdb.py

```python import logging from typing import Any, Dict, Type

from nornir.core.inventory import ( Inventory, Group, Groups, Host, Hosts, Defaults, ConnectionOptions, HostOrGroup, ParentGroups, )

logger = logging.getLogger(name)

def _get_connection_options(data: Dict[str, Any]) -> Dict[str, ConnectionOptions]: cp = {} for cn, c in data.items(): cp[cn] = ConnectionOptions( hostname=c.get("hostname"), port=c.get("port"), username=c.get("username"), password=c.get("password"), platform=c.get("platform"), extras=c.get("extras"), ) return cp

def _get_defaults(data: Dict[str, Any]) -> Defaults: return Defaults( hostname=data.get("hostname"), port=data.get("port"), username=data.get("username"), password=data.get("password"), platform=data.get("platform"), data=data.get("data"), connection_options=_get_connection_options( data.get("connection_options", {})), )

def _get_inventory_element( typ: Type[HostOrGroup], data: Dict[str, Any], name: str, defaults: Defaults ) -> HostOrGroup: return typ( name=name, hostname=data.get("hostname") or data.get("ip"), port=data.get("port"), username=data.get("username"), password=data.get("password"), platform=data.get("platform"), data=data.get("data"), groups=data.get( "groups" ), # this is a hack, we will convert it later to the correct type defaults=defaults, connection_options=_get_connection_options( data.get("connection_options", {})), )

class CMDBInventory: def init(self, devices): """ 根据devices的字典列表加载所有网络主机 Args: devices: 网络设备信息字典的列表,形如 [ {'ip': '192.168.1.1', 'username': 'admin', 'password': '1!', 'port': 8181, 'platform': 'cisco_nxos', 'vendor':'cisco', 'region': 'XX园区' }, ] """ # 将host_info中的字段的数据保留,其他的都放到序列化后的dict数据的data字段中去。 host_info = ['hostname', 'username', 'password', 'port', 'platform', 'ip', 'secret', 'device_type'] reshape_devices = [] for device in devices: # 初始化一个序列化后的数据 reshape_device = { 'data': {} } for k, v in device.items(): # 数据在host_info中,则保留,对platform做一下处理,netmiko中是device_type,所以兼容了一下这个字段,将其转为platform if k in host_info: if k == 'device_type': reshape_device['platform'] = v else: reshape_device[k] = v # 数据不在host_info中,则放到data中去 else: reshape_device['data'][k] = v reshape_devices.append(reshape_device) # self.deivces后续构建inventory对象 self.deivces = reshape_devices

def load(self):
    # 初始化三个对象,分别是Hosts、Groups和Defaults的实例
    hosts = Hosts()
    groups = Groups()
    defaults = Defaults()
    # 我们本例只使用了hosts,大家如果真有需要,按需去添加其他两个对象的属性
    # 从原生的inventory我们实际能看出,inventory的hosts是一个类似dict的对象,所以我们通过dict的方式构建或添加设备。
    # 所以我们将原有的列表进行了一层转化,用ip作为每个host的name,在这段就是用ip作为字典的key值,value是之前构建的字典
    hosts_dict = {i['ip']: i for i in self.deivces}
    # 以上这行代码等于以下三行代码
    # hosts_dict = {}
    # for i in self.deivces:
    #     hosts_dict[i['ip']] = i

    # 我们准备好了添加网络设备的字典,然后在hosts中添加每个
    for name, host_dict in hosts_dict.items():
        hosts[name] = _get_inventory_element(
            Host, host_dict, name, defaults)
    return Inventory(hosts=hosts, groups=groups, defaults=defaults)

```

demo.py

```python from nornir import InitNornir from nornir.core.plugins.inventory import InventoryPluginRegister from nornir_netmiko import netmiko_send_command, netmiko_send_config from nornir_utils.plugins.functions import print_result from nornir.core.task import Result

from cmdb import CMDBInventory

InventoryPluginRegister.register("CMDBInventory", CMDBInventory)

device_type改为platform也是兼容的

devices = [ {'ip': '192.168.11.11', 'username': 'python', 'password': '123', 'device_type': 'huawei'}, { 'ip': '192.168.11.12', 'username': 'python', 'password': '123', 'platform': 'huawei'}, ]

nr = InitNornir( runner={ "plugin": "threaded", "options": { "num_workers": 100, }, }, inventory={ "plugin": "CMDBInventory", "options": { "devices": devices, }, }, )

results = nr.run(task=netmiko_send_command, name='dis users', command_string='dis users') print_result(results)

```