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)