01 場景介紹
本章節介紹大規模數據中心巡檢任務跑批策略,之前的章節介紹過NETCONF這類API接口相比SSH登錄取返回結果的優勢,除了返回結果是標準化XML編碼以外,其實NETCONF在速度上也有很大的優勢。所以在大規模數據中心(幾千、幾萬臺網絡設備)進行網絡巡檢時,推薦使用NETCONF,結合協程gevent異步的特性,進行大併發跑批,幾千臺設備僅需幾分鐘就可結束任務。
國外主流廠商Cisco和Juniper的API類示例在Google、github上有大量文章可以借鑑,而國內華爲這類技術分享比較少,所以我這邊主要介紹下華爲網絡設備的實現思路和實現方式,目前華爲CE系列的交換機基本都支持NETCONF協議,如果數據中心大批量採用華爲CE設備,那下面的內容應該對你有所幫助。
02 需求分析
需要針對數據中心內部華爲CE交換機進行跑批操作,獲取上千臺設備hostname、型號、版本、補丁、SN號,我們先列出這個任務的邏輯:
根據需求創建YANG模板,可直接查閱華爲官網NETCONF Schema API參考,非常齊全;
讀取CMDB資產信息(txt、excel、系統API),獲得需要跑批設備的IP地址列表;本章節用excel的方式舉例,格式如下圖所示:
使用協程gevent調用IP列表,實現大併發的跑批任務;
篩選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
還沒了解面向對象的不用關注,只用主程序+函數的方式寫代碼也可滿足大部分工作,隨着我們代碼量的增加,需要考慮如何減少重複代碼提高效率的時候在去關注面向對象就好。
創作不易,轉載署名,謝謝。