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)

 

posted on 2021-11-16 17:58  longfei2021  阅读(2654)  评论(0编辑  收藏  举报