银狐NetDevOps-网络运维python之NETCONF(三)协程gevent+ncclient,2分钟巡检几千台华为CE交换机

科技银狐

01 场景介绍

本章节介绍大规模数据中心巡检任务跑批策略,之前的章节介绍过NETCONF这类API接口相比SSH登录取返回结果的优势,除了返回结果是标准化XML编码以外,其实NETCONF在速度上也有很大的优势。所以在大规模数据中心(几千、几万台网络设备)进行网络巡检时,推荐使用NETCONF,结合协程gevent异步的特性,进行大并发跑批,几千台设备仅需几分钟就可结束任务。

国外主流厂商Cisco和Juniper的API类示例在Google、github上有大量文章可以借鉴,而国内华为这类技术分享比较少,所以我这边主要介绍下华为网络设备的实现思路和实现方式,目前华为CE系列的交换机基本都支持NETCONF协议,如果数据中心大批量采用华为CE设备,那下面的内容应该对你有所帮助。

02 需求分析

需要针对数据中心内部华为CE交换机进行跑批操作,获取上千台设备hostname、型号、版本、补丁、SN号,我们先列出这个任务的逻辑:

  1. 根据需求创建YANG模板,可直接查阅华为官网NETCONF Schema API参考,非常齐全;

  2. 读取CMDB资产信息(txt、excel、系统API),获得需要跑批设备的IP地址列表;本章节用excel的方式举例,格式如下图所示:

image.png
  1. 使用协程gevent调用IP列表,实现大并发的跑批任务;

  2. 筛选response内容中需要的数据,写入excel中,并进行保存。

03 代码示例

#!/usr/bin/env python
#coding: utf-8

import time
import sys
import os
from datetime import datetime
from gevent import monkey;monkey.patch_all()
from gevent import monkey,spawn
import gevent
from gevent.pool import Pool
from collections import OrderedDict
from openpyxl import Workbook
from openpyxl import load_workbook
import xmltodict
from ncclient import manager
from ncclient import operations
from ncclient.transport.errors import SSHError
from ncclient.transport.errors import AuthenticationError

devinfo_FILTER = '''
    <system xmlns="<http://www.huawei.com/netconf/vrp>" content-version="1.0" format-version="1.0">
      <systemInfo>
        <sysName></sysName>
        <productName></productName>
        <productVer></productVer>
        <patchVer></patchVer>
        <esn></esn>
      </systemInfo>
    </system>
'''

class ReadDeviceInfo(object):

    def __init__(self):      
        self.devices_filename = ('/pipenv_projects/py3/01-devnet/01-netpy/info02.xlsx')
        self.wb1 = load_workbook(self.devices_filename)
        self.ws1 = self.wb1.get_sheet_by_name("device list")

    def read_device_excel(self):
        ip_list = []
        for cow_num in range(2,self.ws1.max_row+1):
            ipaddr = self.ws1["b"+str(cow_num)].value
            ip_list.append(ipaddr)

        return ip_list

    def write_to_excel(self,device_ip,nc_list):
        new_cow_num = devices_list.index(device_ip) + 2
        print("--------writing to excel------------{}: {}".format(new_cow_num,device_ip))

        self.ws1["d"+str(new_cow_num)] = nc_list[0]["sysName"]
        self.ws1["e"+str(new_cow_num)] = nc_list[0]["productName"]
        self.ws1["f"+str(new_cow_num)] = nc_list[0]["productVer"]
        self.ws1["g"+str(new_cow_num)] = nc_list[0]["patchVer"]
        self.ws1["h"+str(new_cow_num)] = nc_list[0]["esn"]

        return

# Fill the device information and establish a NETCONF session
def huawei_connect(host, port, user, password):
    return manager.connect(host=host,
                            port=port,
                            username=user,
                            password=password,
                            hostkey_verify = False,
                            device_params={'name': "huawei"},
                            allow_agent = False,
                            look_for_keys = False)

def hw_nc_get(host, port, user, password):
    with huawei_connect(host, port=port, user=user, password=password) as m:
        return m.get(("subtree", devinfo_FILTER))

def main(hostip):
    print("---get information of device: {}".format(hostip))
    xml_rep = hw_nc_get(hostip,830,"user","pass").data_xml
    xml_dict = xmltodict.parse(xml_rep)

    nc_list = []
    nc_dict = (dict((x,y) for x,y in xml_dict['data']['system']['systemInfo'].items()))
    nc_list.append(nc_dict)
    devices.write_to_excel(hostip,nc_list)

#===================================================================
#ncclient抓取华为设备信息,并存入excel中
#===================================================================
if __name__ == '__main__':
    starting_time = time.time()
    devices = ReadDeviceInfo()
    devices_list = devices.read_device_excel()

    pool = Pool(5)
    pool.map(main,devices_list)                                 #map(func, iterable)
    pool.join()

    devices.wb1.save('info02.xlsx')
    print ('\\n---- End get config threading, elapsed time=', time.time() - starting_time)

04 代码详解

为了更好的理解,本次不按照从上至下的顺序分解,而是按照代码逻辑的顺序讲解。

import time
import sys
import os
from datetime import datetime
from gevent import monkey;monkey.patch_all()
from gevent import monkey,spawn
import gevent
from gevent.pool import Pool
from collections import OrderedDict
from openpyxl import Workbook
from openpyxl import load_workbook
import xmltodict
from ncclient import manager
from ncclient import operations
from ncclient.transport.errors import SSHError
from ncclient.transport.errors import AuthenticationError

常规步骤,先导入需要使用的模块,gevent是协程模块,openpyxl是处理excel使用的模块,协程推荐使用gevent,因为不需要重构代码,导入monkey.patch_all()就能使用,非常友好。

devinfo_FILTER = '''
    <system xmlns="<http://www.huawei.com/netconf/vrp>" content-version="1.0" format-version="1.0">
      <systemInfo>
        <sysName></sysName>
        <productName></productName>
        <productVer></productVer>
        <patchVer></patchVer>
        <esn></esn>
      </systemInfo>
    </system>
'''

构建YANG模板,华为不像Cisco和juniper那样可以登录设备通过CLI(比如show version | display xml)直接解析XML输出格式,只能通过官方文档获取。登录华为官方网站,在文档的二次开发中搜索《CloudEngine 8800, 7800, 6800HI, 6880EI, 6875EI, 6870EI, 6865EI, 6860EI, 6857EI, 5880EI V200R005C10 NETCONF Schema API参考》就好,里面YANG模板非常详细。

如下图所示,需求是获取设备信息,找到系统信息,get方法里的请求示例,直接使用system命名空间内容即可。

image.png
#===================================================================
#ncclient抓取华为设备信息,并存入excel中
#===================================================================
if __name__ == '__main__':
    starting_time = time.time()
        #为了任务执行时间,需要在程序首位2个地方打点,最后相减得到代码执行时间。
    devices = ReadDeviceInfo()
        #对象实例化
    devices_list = devices.read_device_excel()
        #获取设备IP地址的list
    pool = Pool(5)
        #定义pool数量,这种方式的好处是能控制并发数量,根据CPU情况调节
    pool.map(main,devices_list)                                 
    #map方法需要传递2个参数,第一个是整个跑批任务的主函数,第二个是可迭代参数,一般使用IP地址list
    pool.join()
        #等待协程结束,确保所有任务终止

    devices.wb1.save('info02.xlsx')
    #主函数运行完成,保存excel内容
    print ('\\n---- End get config threading, elapsed time=', time.time() - starting_time)

以上为主程序详解,用注释的方式进行说明,主要是为了让协程gevent调用我们主要的任务(主函数main)。

def main(hostip):
    print("---get information of device: {}".format(hostip))
        #打印这行内容,主要是判断传入的IP地址是否正确
    xml_rep = hw_nc_get(hostip,830,"user","pass").data_xml
        #调用函数hw_nc_get,得到的结果是<class 'ncclient.operations.retrieve.GetReply'>,
    #所以需要data.xml方法将内容转为字符串,最终得到的是xml编码格式的字符串。
    xml_dict = xmltodict.parse(xml_rep)
        #直接将xml内容解析为dict,所有数据就很方便处理了,xmltodict方法得到的是orderdict,
    #简单来说就是一种有顺序的dict,所以可以直接迭代。

    nc_list = []
        #定义一个list,方便后面把hostname,sn,version等信息存储,
        #以下内容在前面的文章讲解过,可以先查阅先前的内容。
    nc_dict = (dict((x,y) for x,y in xml_dict['data']['system']['systemInfo'].items()))
    #每台设备的所有信息存储为字典,如下所示:
        #{'sysName': 'xxxx', 'productName': 'CE6855HI', 'productVer': 'xxxx', 'patchVer': 'xxxx', 'esn': 'xxxx'}
        nc_list.append(nc_dict)
        #因为本人很多方法都是标准化直接传参即可,所以我这边将结果存储为list,传递给下一个方法,这块不强求,直接传递dict也是可以的。
    devices.write_to_excel(hostip,nc_list)
        #设备IP地址和get获取的设备信息(list、dict都可以),传入write_to_excel方法,我传递的是list。

上面的主函数有个代码简写了,这里说明一下:

   for k,y in xml_dict['data']['system']['systemInfo'].items():
        #将systeminfo的value元组化,并用k,y去for循环,得到的数据类似下面的样式:
        # ('productName', 'xxx'),
        # ('productVer', 'xxxx'), 
        # ('patchVer', 'xxxx'),
        # ('esn', 'xxxx')])

     nc_dict = (dict((x,y) for x,y in xml_dict['data']['system']['systemInfo'].items()))
   #其实就是将上面的结果存为dict

面向对象内容====================================================

class ReadDeviceInfo(object):

    def __init__(self):  
        #初始化属性    
        self.devices_filename = ('D:/pipenv_projects/py3/01-devnet/01-netpy/info02.xlsx')
        self.wb1 = load_workbook(self.devices_filename)
        self.ws1 = self.wb1.get_sheet_by_name("device list")

    def read_device_excel(self):
    #读取excel中的IP列表
        ip_list = []
        for cow_num in range(2,self.ws1.max_row+1):
            ipaddr = self.ws1["b"+str(cow_num)].value
            ip_list.append(ipaddr)

        return ip_list

    def write_to_excel(self,device_ip,nc_list):
    #获取的设备信息写入excel
        new_cow_num = devices_list.index(device_ip) + 2
        print("--------writing to excel------------{}: {}".format(new_cow_num,device_ip))

        self.ws1["d"+str(new_cow_num)] = nc_list[0]["sysName"]
        self.ws1["e"+str(new_cow_num)] = nc_list[0]["productName"]
        self.ws1["f"+str(new_cow_num)] = nc_list[0]["productVer"]
        self.ws1["g"+str(new_cow_num)] = nc_list[0]["patchVer"]
        self.ws1["h"+str(new_cow_num)] = nc_list[0]["esn"]

        return

这里使用了class类,这块内容之前没讲解过,为了方便我把excel的内容定义成一个类,然后进行实例化操作。这里不是本章重点,所以我就简单介绍下类的意思。

python的优势是面向对象,有三个特性,封装、继承、多态,主要是较少重复代码的问题,这其中的灵魂就是定义类,比如你设计飞机,class类就是你的图纸,造飞机需要知道飞机的属性,比如是红色还是黄色的,这个就是属性,还要知道怎么造这个飞机,比如机翼接到机身上,这个就是方法(类似于平时我们的def函数),平时见到的类属性和类方法大概是这个意思。

这里我把excel内容定义为初始化属性,每次调用类方法的时候都会加载这个excel内容,然后进行excel的读写操作。

如果确实对面向对象不太理解也无妨,上面的代码直接把class删除,把所有def函数前面的缩进取消,把所有self内容删除,直接当函数用。然后把属性放在主程序上当全局参数,变成下面的样子是不是就容易理解了,也可以看下class类和不用class的区别。

def read_device_excel():
    #读取excel中的IP列表,因为我的IP地址在B列,所以下面获取的是B列的value
    ip_list = []
    for cow_num in range(2,ws1.max_row+1):
    #遍历B列IP地址,因为第一行内容不需要,所以我们range(2,最大行),这里有个需要注意的点,
    #比如有10行数据,range(2,10)其实只遍历到第9行,所以我们需要最大行+1
        ipaddr = ws1["b"+str(cow_num)].value
        ip_list.append(ipaddr)

    return ip_list

def write_to_excel(device_ip,nc_list):
#获取的设备信息写入excel
    new_cow_num = devices_list.index(device_ip) + 2
#我们获取到设备信息要写入excel,但是这个信息写入到第几行呢?
#我们读取excel设备信息时,用for循环获取列表是有顺序的,这个顺序是从上至下的,
#正常情况下获取的信息写入excel时,第1台设备信息写入第2行,第2台设备信息写入第3行可以的,
#但是协程大并发场景下,这个顺序是随机的。
#我先给A1发get请求,然后不等待直接给A2发送请求,这个时候很可能A2先进行response,如果我把得到的
#信息按照从上至下的顺序写入excel,内容就乱了。
#所以我们需要根据IP地址使用index方法找到索引位置,然后写入相应的位置。
    print("--------writing to excel------------{}: {}".format(new_cow_num,device_ip))

    ws1["d"+str(new_cow_num)] = nc_list[0]["sysName"]
        #比如sheet1,D2内容 = 传入nc_list参数的sysName,因为我传入的是list,所以要加个索引[0]
        #D2这个行数就是靠index定位的
    ws1["e"+str(new_cow_num)] = nc_list[0]["productName"]
    ws1["f"+str(new_cow_num)] = nc_list[0]["productVer"]
    ws1["g"+str(new_cow_num)] = nc_list[0]["patchVer"]
    ws1["h"+str(new_cow_num)] = nc_list[0]["esn"]
        #
    return

if __name__ == '__main__':
    starting_time = time.time()
        devices_filename = ('/pipenv_projects/py3/01-devnet/01-netpy/info02.xlsx')
        wb1 = load_workbook(devices_filename)
    #定义wb1加载工作簿
        ws1 = self.wb1.get_sheet_by_name("device list")
        #定义工作表ws1,就是加载我们的sheet1(device list是sheet名称)

        pool = Pool(1)
    pool.map(main,devices_list)                                 #map(func, iterable)
    pool.join()

    devices.wb1.save('info02.xlsx')
        #注意保持,不然excel没变化。
    print ('\\n---- End get config threading, elapsed time=', time.time() - starting_time)

如下内容前两章讲过,就不重复了。

# Fill the device information and establish a NETCONF session
def huawei_connect(host, port, user, password):
    return manager.connect(host=host,
                            port=port,
                            username=user,
                            password=password,
                            hostkey_verify = False,
                            device_params={'name': "huawei"},
                            allow_agent = False,
                            look_for_keys = False)

def hw_nc_get(host, port, user, password):
    with huawei_connect(host, port=port, user=user, password=password) as m:
        return m.get(("subtree", devinfo_FILTER))

最终执行结果如下:

---get information of device: 10.7.1.1
---get information of device: 10.7.1.2
---get information of device: 10.7.1.3
---get information of device: 10.7.1.4
--------writing to excel------------3: 10.7.1.1
--------writing to excel------------2: 10.7.1.2
--------writing to excel------------4: 10.7.1.3
--------writing to excel------------5: 10.7.1.4

---- End get config threading, elapsed time= 5.876776933670044

还没了解面向对象的不用关注,只用主程序+函数的方式写代码也可满足大部分工作,随着我们代码量的增加,需要考虑如何减少重复代码提高效率的时候在去关注面向对象就好。

创作不易,转载署名,谢谢。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容