Ncclient簡介
Ncclient是一個開源的Python模塊,用來在NETCONF客戶端開發各種和NETCONF相關的網絡運維腳本和應用程序。截止2020年10月最新的版本為0.6.9,對系統環境有如下要求:
Python 2.7 或 Python 3.4+
setuptools 0.6+
Paramiko 1.7+
lxml 3.3.0+
libxml2
libxslt
安裝ncclient
pip3 install ncclient -i https://mirrors.aliyun.com/pypi/simple
如果報錯可以嘗試先更新pip
pip3 install --upgrade pip
H3C交換機
開啟NETCONF
H3C配置netconfig,參考官方文檔http://www.h3c.com/cn/d_201907/1218555_30005_0.htm
NETCONF支持標准協議規范中描述的NETCONF over SSH功能,使能該功能后,用戶可以通過使用支持NETCONF over SSH登錄方式的客戶端配置工具給設備下發NETCONF指令來實現對設備的訪問。
system-view netconf ssh server enable #缺省情況下,NETCONF over SSH功能處於關閉狀態 netconf ssh server port #缺省情況下,NETCONF over SSH使用端口830
配置NETCONF日志功能
NETCONF日志功能可以記錄操作源對設備進行的NETCONF操作,用戶通過指定NETCONF操作源和NETCONF操作類型,配置系統輸出指定類型的NETCONF日志。
system-view netconf log source all protocol-operation all #缺省情況下,未配置NETCONF日志功能
1、導入模塊
manager模塊用於通過netconf協議連接設備
>>> from ncclient import manager >>> from lxml import etree
2、連接設備
通過manager對象下的connect()函數連接網絡設備
device_params用來規定設備的類型和操作系統,比如思科包括iosxe, csr, iosxr, nxos等,這里我們登錄的是一台h3c交換機
Juniper: device_params={'name':'junos'}
Cisco CSR: device_params={‘name':'csr'}
Cisco Nexus: device_params={‘name':'nexus'}
Huawei: device_params={‘name':'huawei'}
H3C: device_params={‘name':'h3c'}
timeout超時時間,默認60秒,如果設備的running config配置過大,通過get_config獲取設備所有配置時可能會超時,可以將timeout改大一點
conn = manager.connect(host='x.x.x.x', port=830, username='***', password='***', device_params={'name':'h3c'}, timeout=300, hostkey_verify=False, look_for_keys=False)
通過ssh協議連接設備時會先保存對端的key,並從本機查找驗證,這里使用hostkey_verify=False和look_for_keys=False來跳過檢查
manager.py中包含了如下方法
OPERATIONS = { "get": operations.Get, "get_config": operations.GetConfig, "get_schema": operations.GetSchema, "dispatch": operations.Dispatch, "edit_config": operations.EditConfig, "copy_config": operations.CopyConfig, "validate": operations.Validate, "commit": operations.Commit, "discard_changes": operations.DiscardChanges, "cancel_commit": operations.CancelCommit, "delete_config": operations.DeleteConfig, "lock": operations.Lock, "unlock": operations.Unlock, "create_subscription": operations.CreateSubscription, "close_session": operations.CloseSession, "kill_session": operations.KillSession, "poweroff_machine": operations.PoweroffMachine, "reboot_machine": operations.RebootMachine, "rpc": operations.GenericRPC, }
class Get(RPC): def request(self, filter=None, with_defaults=None):
檢索運行配置和設備狀態信息。查詢的是設備當前運行的狀態數據,即只能從配置數據庫中獲取數據。
不需要使用source參數指定配置數據庫。
操作成功,Server回復的元素中含有參數,中封裝了獲取的結果數據。否則在消息中返回。
class GetConfig(RPC): def request(self, source, filter=None, with_defaults=None):
source:指定需要查詢的數據庫名稱。有running(正在運行的數據庫),startup(下次設備啟動配置數據庫),candidate(兩階段運行數據庫,需要commit提交生效)
fileter:過濾器。過濾器需要是一個集合,該集合內包含類型和xml內容,A tuple of (type, criteria),這里的type必須是xpath或者subtree。
def edit_config(config, default_operation=None, test_option=None, error_option=None):
將指定配置的全部或部分加載到目標配置數據存儲中。
target:指定要配置的數據庫。
config:必須放在元素中, 它可以指定為字符串或Element
default_operation: 如果指定,必須是{ “merge”, “replace”, or “none” } 其中之一。
test_option: { “test_then_set”, “set” } 之一。
error_option: { “stop-on-error”, “continue-on-error”, “rollback-on-error” } 之一。
edit_config:編輯配置內容,參數為要下發的xml內容,必須以config為根,可以是xml字符串,也可以是通過ElementMaker生成的xml類
class EditConfig(RPC): "`edit-config` RPC" def request(self, config, format='xml', target='candidate', default_operation=None,test_option=None, error_option=None): """Loads all or part of the specified *config* to the *target* configuration datastore. *target* is the name of the configuration datastore being edited *config* is the configuration, which must be rooted in the `config` element. It can be specified either as a string or an :class:`~xml.etree.ElementTree.Element`. *default_operation* if specified must be one of { `"merge"`, `"replace"`, or `"none"` } *test_option* if specified must be one of { `"test-then-set"`, `"set"`, `"test-only"` } *error_option* if specified must be one of { `"stop-on-error"`, `"continue-on-error"`, `"rollback-on-error"` } The `"rollback-on-error"` *error_option* depends on the `:rollback-on-error` capability. """ 。。。
例子1、獲取交換機running config所有配置
conn = manager.connect(host='x.x.x.x', port=830, username='***', password='***', device_params={'name':'h3c'}, timeout=300, hostkey_verify=False, look_for_keys=False) def running_config_get(): # 獲取running所有配置 conn = netconf_conn() ret = conn.get_config(source='running') return ret running_config = running_config_get() print(running_config)
例子2、獲取所有接口信息,包括配置和狀態,指定需要顯示的字段名稱
conn = manager.connect(host='x.x.x.x', port=830, username='***', password='***', device_params={'name':'h3c'}, timeout=300, hostkey_verify=False, look_for_keys=False)
1、構建xml文件
get_all_iface = """ <top xmlns="http://www.h3c.com/netconf/data:1.0"> <Ifmgr> <Interfaces> <Interface> <Name></Name> <InetAddressIPV4></InetAddressIPV4> <InetAddressIPV4Mask></InetAddressIPV4Mask> <AdminStatus></AdminStatus> </Interface> </Interfaces> </Ifmgr> </top> """
2、執行 XML
ret = conn.get(('subtree',get_all_iface)) #指定了一個過濾器,類型是集合 print(ret)
上面代碼中使用了 ncclient 封裝的get操作,我們只需要傳入Content層的XML信息即可
執行結果如下,只截取了部分內容:
<?xml version="1.0" encoding="utf-8"?> <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="urn:uuid:664ee1ba-5d45-41d7-a96f-9e88f7c68e5e"> <data> <top xmlns="http://www.h3c.com/netconf/data:1.0"> <Ifmgr> <Interfaces> <Interface><IfIndex>1</IfIndex><Name>Ten-GigabitEthernet1/0/1</Name><AdminStatus>1</AdminStatus></Interface> <Interface><IfIndex>2</IfIndex><Name>Ten-GigabitEthernet1/0/2</Name><AdminStatus>1</AdminStatus></Interface> <Interface><IfIndex>2148</IfIndex><Name>NULL0</Name><AdminStatus>1</AdminStatus></Interface> <Interface><IfIndex>2215</IfIndex><Name>LoopBack0</Name><AdminStatus>1</AdminStatus><InetAddressIPV4>10.x.x.x</InetAddressIPV4><InetAddressIPV4Mask>255.255.255.192</InetAddressIPV4Mask></Interface> <Interface><IfIndex>2233</IfIndex><Name>Vlan-interface2999</Name><AdminStatus>1</AdminStatus></Interface> </Interfaces> </Ifmgr> </top> </data> </rpc-reply>
例子3、獲取指定接口的配置和狀態信息,不指定具體參數名稱,可以查詢到該接口的所有信息
conn = manager.connect(host='x.x.x.x', port=830, username='***', password='***', device_params={'name':'h3c'}, timeout=300, hostkey_verify=False, look_for_keys=False) def ifcfg_get(index_num): # 獲取指定接口的配置信息 get_ifcfg_xml = """ <top xmlns="http://www.h3c.com/netconf/data:1.0"> <Ifmgr> <Interfaces> <Interface> <IfIndex>%s</IfIndex> </Interface> </Interfaces> </Ifmgr> </top> """ % index_num conn = netconf_conn() ifcfg_ret = conn.get(('subtree', get_ifcfg_xml)) return ifcfg_ret ifcfg = ifcfg_get('2233') #接口的索引值 print(ifcfg)
結果
<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="urn:uuid:544d5d6a-b8aa-4676-a55e-9babcb0683c4">
<data>
<top xmlns="http://www.h3c.com/netconf/data:1.0">
<Ifmgr>
<Interfaces>
<Interface>
<IfIndex>2233</IfIndex>
<Name>Vlan-interface2999</Name>
<AbbreviatedName>Vlan2999</AbbreviatedName>
<ifTypeExt>41</ifTypeExt>
<ifType>136</ifType>
<Description>netconf</Description>
<AdminStatus>1</AdminStatus>
<OperStatus>1</OperStatus>
<PortLayer>2</PortLayer>
<InetAddressIPV4>10.1.1.1</InetAddressIPV4>
<InetAddressIPV4Mask>255.255.255.0</InetAddressIPV4Mask>
<MAC>30-B0-37-BC-68-52</MAC>
<ForwardingAttributes>17</ForwardingAttributes>
<ConfigMTU>1500</ConfigMTU>
<ActualMTU>1500</ActualMTU>
<ActualBandwidth>100000000</ActualBandwidth>
<SubPort>false</SubPort>
<Interval>300</Interval>
<Actual64Bandwidth>100000000</Actual64Bandwidth>
</Interface>
</Interfaces>
</Ifmgr>
</top>
</data>
</rpc-reply>
獲取所有acl信息
def acl_all_get(): #獲取所有acl信息 get_all_acl_xml = """ <top xmlns="http://www.h3c.com/netconf/data:1.0"> <ACL> <IPv4BasicRules> <Rule> <GroupID></GroupID> <RuleID></RuleID> <Action></Action> <SrcAny></SrcAny> <SrcIPv4> <SrcIPv4Addr></SrcIPv4Addr> <SrcIPv4Wildcard></SrcIPv4Wildcard> </SrcIPv4> <Fragment></Fragment> <Counting></Counting> <Logging></Logging> <Status></Status> <Count></Count> </Rule> </IPv4BasicRules> </ACL> </top> """ conn = netconf_conn() ifcft_ret = conn.get(('subtree', get_all_acl_xml)) return ifcft_ret acl_all = acl_all_get() print(acl_all)
結果:
<?xml version="1.0" encoding="utf-8"?> <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="urn:uuid:2d0d709f-8a7c-409e-b567-93ad6717dbad"> <data> <top xmlns="http://www.h3c.com/netconf/data:1.0"> <ACL> <IPv4BasicRules> <Rule> <GroupID>2999</GroupID> <RuleID>0</RuleID> <Action>1</Action> <SrcAny>false</SrcAny> <SrcIPv4> <SrcIPv4Addr>1.1.1.1</SrcIPv4Addr> <SrcIPv4Wildcard>0.0.0.0</SrcIPv4Wildcard> </SrcIPv4> <Fragment>false</Fragment> <Counting>false</Counting> <Logging>false</Logging> <Status>1</Status> <Count>0</Count> </Rule> <Rule> <GroupID>2999</GroupID> <RuleID>5</RuleID> <Action>1</Action> <SrcAny>false</SrcAny> <SrcIPv4> <SrcIPv4Addr>1.1.1.2</SrcIPv4Addr> <SrcIPv4Wildcard>0.0.0.0</SrcIPv4Wildcard> </SrcIPv4> <Fragment>false</Fragment> <Counting>false</Counting> <Logging>false</Logging> <Status>1</Status> <Count>0</Count> </Rule> </IPv4BasicRules> </ACL> </top> </data> </rpc-reply>
例子4:獲取指定acl的配置信息,可以指定想要獲取的參數
conn = manager.connect(host='x.x.x.x', port=830, username='***', password='***', device_params={'name':'h3c'}, timeout=300, hostkey_verify=False, look_for_keys=False) def acl_get(acl_number): #獲取指定acl信息 get_acl_xml = """ <top xmlns="http://www.h3c.com/netconf/data:1.0"> <ACL> <IPv4BasicRules> <Rule> <GroupID>%s</GroupID> <Action></Action> <SrcAny></SrcAny> <SrcIPv4> <SrcIPv4Addr></SrcIPv4Addr> <SrcIPv4Wildcard></SrcIPv4Wildcard> </SrcIPv4> </Rule> </IPv4BasicRules> </ACL> </top> """%acl_number conn = netconf_conn() ifcft_ret = conn.get(('subtree', get_acl_xml)) return ifcft_ret acl = acl_get('2999') print(acl)
結果:
<?xml version="1.0" encoding="utf-8"?> <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="urn:uuid:84b6e836-051a-451a-8449-7171af5dd403"> <data> <top xmlns="http://www.h3c.com/netconf/data:1.0"> <ACL> <IPv4BasicRules> <Rule> <GroupID>2999</GroupID> <RuleID>0</RuleID> <Action>1</Action> <SrcAny>false</SrcAny> <SrcIPv4> <SrcIPv4Addr>1.1.1.1</SrcIPv4Addr> <SrcIPv4Wildcard>0.0.0.0</SrcIPv4Wildcard> </SrcIPv4> </Rule> <Rule> <GroupID>2999</GroupID> <RuleID>5</RuleID> <Action>1</Action> <SrcAny>false</SrcAny> <SrcIPv4> <SrcIPv4Addr>1.1.1.2</SrcIPv4Addr> <SrcIPv4Wildcard>0.0.0.0</SrcIPv4Wildcard> </SrcIPv4> </Rule> <Rule> <GroupID>2999</GroupID> <RuleID>6</RuleID> <Action>2</Action> <SrcAny>false</SrcAny> <SrcIPv4> <SrcIPv4Addr>1.1.1.3</SrcIPv4Addr> <SrcIPv4Wildcard>0.0.0.0</SrcIPv4Wildcard> </SrcIPv4> </Rule> </IPv4BasicRules> </ACL> </top> </data> </rpc-reply>
方式二:通過ElementMaker構建xml
def netconf_conn(): conn = manager.connect(host='10.255.64.3', port=830, username='cecloud', password='b200.fsp.cecloud.com', device_params={'name': 'h3c'}, timeout=300, hostkey_verify=False, look_for_keys=False ) return conn BASE_NS_1_0 = "urn:ietf:params:xml:ns:netconf:base:1:0" H3C_DATA_1_0 = "http://www.h3c.com/netconf/data:1.0" H3C_CONFIG_1_0 = "http://www.h3c.com/netconf/config:1.0" C = ElementMaker(namespace=BASE_NS_1_0, nsmap={None: BASE_NS_1_0}) E_data = ElementMaker(namespace=H3C_DATA_1_0, nsmap={None: H3C_DATA_1_0}) E_config = ElementMaker(namespace=H3C_CONFIG_1_0, nsmap={None: H3C_CONFIG_1_0}) def acl_get(acl_number): #獲取指定acl信息 acl_etree = E_data.top( E_data.ACL( E_data.IPv4BasicRules( E_data.Rule( E_data.GroupID(acl_number), E_data.Action(), E_data.SrcIPv4( E_data.SrcIPv4Addr(), E_data.SrcIPv4Wildcard(), ) ) ) ) ) conn = netconf_conn() # ifcft_ret = conn.get(('subtree', get_acl_xml)) ifcft_ret = conn.get(('subtree',acl_etree)) return ifcft_ret acl_get_ret = acl_get('2999') print(acl_get_ret)
結果:
<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="urn:uuid:c4719bd6-5b17-4c28-af4c-a71e1c7196a8">
<data>
<top xmlns="http://www.h3c.com/netconf/data:1.0">
<ACL>
<IPv4BasicRules>
<Rule>
<GroupID>2999</GroupID>
<RuleID>0</RuleID>
<Action>1</Action>
<SrcAny>false</SrcAny>
<SrcIPv4><SrcIPv4Addr>1.1.1.1</SrcIPv4Addr>
<SrcIPv4Wildcard>0.0.0.0</SrcIPv4Wildcard>
</SrcIPv4>
</Rule>
<Rule>
<GroupID>2999</GroupID>
<RuleID>5</RuleID>
<Action>1</Action>
<SrcAny>false</SrcAny>
<SrcIPv4>
<SrcIPv4Addr>1.1.1.2</SrcIPv4Addr>
<SrcIPv4Wildcard>0.0.0.0</SrcIPv4Wildcard>
</SrcIPv4></Rule>
<Rule>
<GroupID>2999</GroupID>
<RuleID>6</RuleID>
<Action>2</Action>
<SrcAny>false</SrcAny>
<SrcIPv4>
<SrcIPv4Addr>1.1.1.3</SrcIPv4Addr>
<SrcIPv4Wildcard>0.0.0.0</SrcIPv4Wildcard>
</SrcIPv4>
</Rule>
<Rule>
<GroupID>2999</GroupID>
<RuleID>10</RuleID>
<Action>1</Action>
<SrcAny>false</SrcAny>
<SrcIPv4>
<SrcIPv4Addr>1.1.1.10</SrcIPv4Addr>
<SrcIPv4Wildcard>0.0.0.0</SrcIPv4Wildcard>
</SrcIPv4>
</Rule>
<Rule>
<GroupID>2999</GroupID>
<RuleID>11</RuleID>
<Action>1</Action>
<SrcAny>false</SrcAny>
<SrcIPv4>
<SrcIPv4Addr>1.1.1.11</SrcIPv4Addr>
<SrcIPv4Wildcard>0.0.0.0</SrcIPv4Wildcard>
</SrcIPv4>
</Rule>
<Rule>
<GroupID>2999</GroupID>
<RuleID>13</RuleID>
<Action>1</Action><
SrcAny>false</SrcAny>
<SrcIPv4>
<SrcIPv4Addr>1.1.1.13</SrcIPv4Addr>
<SrcIPv4Wildcard>0.0.0.0</SrcIPv4Wildcard>
</SrcIPv4>
</Rule>
</IPv4BasicRules>
</ACL>
</top>
</data>
</rpc-reply> print(type(acl_get_ret)) <class 'ncclient.operations.retrieve.GetReply'>
配置acl 2999
在acl2999中添加一條 rule 10 deny source 1.1.1.10 0 def acl_edit(acl_number,rule_id,ip,wildcard): # 編輯指定acl的配置 acl_edit_xml = """ <config> <top xmlns="http://www.h3c.com/netconf/config:1.0"> <ACL> <IPv4NamedBasicRules> <Rule> <GroupIndex>%s</GroupIndex> <RuleID>%s</RuleID> <Action>1</Action> <SrcAny>false</SrcAny> <SrcIPv4> <SrcIPv4Addr>%s</SrcIPv4Addr> <SrcIPv4Wildcard>%s</SrcIPv4Wildcard> </SrcIPv4> </Rule> </IPv4NamedBasicRules> </ACL> </top> </config> """%(acl_number,rule_id,ip,wildcard) conn = netconf_conn() try: ifcft_ret = conn.edit_config(acl_edit_xml,target='running') return ifcft_ret except Exception as e: return(e) acl_config = acl_edit('2999','10','1.1.1.10','0.0.0.0') print(acl_config)
結果:
<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="urn:uuid:4259ba18-4c57-4f2b-aa99-1726a1268658"><ok/>
</rpc-reply>
def check_response(rpc_obj, snippet_name): #檢查執行結果是否成功 xml_str = rpc_obj.xml if "<ok/>" in xml_str: print("%s successful" % snippet_name) else: print("%s failed!!" % snippet_name) acl_config_res = check_response(acl_config,'ACL') print(acl_config_res)
結果:
ACL successful
配置vlan2999接口
from lxml import etree def ifcfg_edit(vlan_id,desc,ip,mask): ifcfg_edit_xml = ''' <config> <top xmlns="http://www.h3c.com/netconf/config:1.0"> <VLAN> <VLANs> <VLANID> <ID>%s</ID> <Description>%s</Description> <Ipv4> <Ipv4Address>%s</Ipv4Address> <Ipv4Mask>%s</Ipv4Mask> </Ipv4> </VLANID> </VLANs> </VLAN> </top> </config> '''%(vlan_id,desc,ip,mask) try: with manager.connect(**h3c_host) as m: ifcft_ret = m.edit_config(ifcfg_edit_xml, target='running') return ifcft_ret except Exception as e: return (e) def check_response(rpc_obj, snippet_name): xml_str = rpc_obj.xml if "<ok/>" in xml_str: print("%s successful" % snippet_name) else: print("%s failed!!" % snippet_name) ifcfg_config = ifcfg_edit('2999','10.1.1.2','255.255.255.192') print(ifcfg_config) acl_config_res = check_response(ifcfg_config,'Ifcfg')
結果:
<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="urn:uuid:15d06834-d6cd-4972-bcc4-76456f77d9af"><ok/>
</rpc-reply> Ifcfg successful
查看設備上的配置變化
# vlan 2999 description netconf # <ASW>dis cu int vl 2999 # interface Vlan-interface2999 ip address 10.1.1.2 255.255.255.192 # return
設備連接的另一種方式
h3c_host = { 'host': '10.255.1.1', 'port': 830, 'username': 'user123', 'password': 'password123', 'device_params': {'name': 'h3c'}, 'timeout': 300, 'hostkey_verify': False, 'look_for_keys': False } try: with manager.connect(**h3c_host) as m: ifcft_ret = m.edit_config(acl_edit_xml,target='running') return ifcft_ret except Exception as e: return(e)