銀狐NetDevOps-網絡運維python之NETCONF(三)協程gevent+ncclient,2分鐘巡檢幾千臺華爲CE交換機 01 場景介紹 02 需求分析 03 代碼示例 04 代碼詳解

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的方式舉例,格式如下圖所示:

  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命名空間內容即可。

#===================================================================
#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

還沒了解面向對象的不用關注,只用主程序+函數的方式寫代碼也可滿足大部分工作,隨着我們代碼量的增加,需要考慮如何減少重複代碼提高效率的時候在去關注面向對象就好。

創作不易,轉載署名,謝謝。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章