NAPALM介绍与实操

[toc]

NAPALM概述

官方链接

其他drive链接(国人)

NAPALM介绍与实操_第1张图片

NAPALM全称为Network Automation and Programmability Abstraction Layer with Multivendor support,翻译起来就是支持"多厂商"网络自动化和可编程的抽象层.

NAPALM是一个python开源的第三方模块,截至2021.11月,支持的厂商如下所示:

虽然支持厂商不是很多,暂时没有支持国内厂商,但功能都是很强大的。

Cisco IOS
Cisco NX-OS
Cisco IOS-XR
Juniper JUNOS
Arista EOS

NX-API support on the Nexus 5k, 6k and 7k families was introduced in version 7.2

  • 依赖netmiko,本机安装即可.

支持的设备

通用支持模型

_ EOS Junos IOS-XR (NETCONF) IOS-XR (XML-Agent) NX-OS NX-OS SSH IOS
Driver Name eos junos iosxr_netconf iosxr nxos nxos_ssh ios
Structured data Yes Yes Yes No Yes No No
Minimum version 4.15.0F 12.1 7.0 5.1.0 6.1 [1] 12.4(20)T 6.3.2
Backend library pyeapi junos-eznc ncclient pyIOSXR pynxos netmiko netmiko
Caveats EOS IOS-XR (NETCONF) NXOS NXOS IOS

说明:

  • driver name:后面我们写代码需要填写的,如get_network_driver(ios)
  • structured data:支持的结构化数据
  • Minimum version:这里还有最低的版本要求;

配置支持模型

支持配置替换、合并、提交、对比、原子配置、回滚等。
_ EOS Junos IOS-XR (NETCONF) IOS-XR (XML-Agent) NX-OS IOS
Config. replace Yes Yes Yes Yes Yes Yes
Config. merge Yes Yes Yes Yes Yes Yes
Commit Confirm Yes Yes No No No No
Compare config Yes Yes Yes Yes [2] Yes [4] Yes
Atomic Changes Yes Yes Yes Yes Yes/No [5] Yes/No [5]
Rollback Yes [3] Yes Yes Yes Yes/No [5] Yes

[2] Hand-crafted by the API as the device doesn't support the feature.
[3] Not supported but emulated. Check caveats.
[4] For merges, the diff is very simplistic. See caveats.
[5] (1, 2, 3) No for merges. See caveats.
[6] NAPALM requires Junos OS >= 14.1 for commit-confirm functionality.

Getters支持的模型

该表官方会不定期自动更新。
EOS IOS IOSXR IOSXR_NETCONF JUNOS NXOS NXOS_SSH
get_arp_table
get_bgp_config
get_bgp_neighbors
get_bgp_neighbors_detail
get_config
get_environment
get_facts
get_firewall_policies
get_interfaces
get_interfaces_counters
get_interfaces_ip
get_ipv6_neighbors_table
get_lldp_neighbors
get_lldp_neighbors_detail
get_mac_address_table
get_network_instances
get_ntp_peers
get_ntp_servers
get_ntp_stats
get_optics
get_probes_config
get_probes_results
get_route_to
get_snmp_information
get_users
get_vlans
is_alive
ping
traceroute
  • ✅ - supported
  • ❌ - not supported
  • ☠ - broken

通过Getters类,比如get_config就可以得到设备的配置(running和startup)、get_facts获取设备的基本信息、get_interfaces获取接口信息等等,也是非常方便的。

其他方法

_ EOS Junos IOS-XR (NETCONF) IOS-XR NX-OS IOS
load_template
ping
traceroute

可用的配置模板

  • set_hostname (JunOS, IOS-XR, IOS) - Configures the hostname of the device.
  • set_ntp_peers (JunOS, IOS-XR, EOS, NXOS, IOS) - Configures NTP peers of the device.
  • delete_ntp_peers (JunOS, IOS-XR, EOS, NXOS, IOS): Removes NTP peers from device’s configuration.
  • set_probes (JunOS, IOS-XR): Configures RPM/SLA probes.
  • schedule_probes (IOS-XR): On Cisco devices, after defining the SLA probes, it is mandatory to schedule them. Defined also for JunOS as empty template, for consistency reasons.
  • delete_probes (JunOS, IOS-XR): Removes RPM/SLA probes.

以上方法,大家可以在实验环境测试下。

如何安装

需要确保netmiko已经被安装,依赖与netmiko的。

  • 通过pip3安装
python -m pip install napalm

备注:从版本3.0.0之后,NAPALM仅支持Python 3.6+.

如何升级

pip install napalm -U

备注:如果napalm有升级了,可以通过该方法进行升级.

实操示例

视频中演示的代码,都在这里了.

基本的设备连接

from napalm import get_network_driver
import pprint

pp = pprint.PrettyPrinter(indent=2)

# drive是一个类class,传入drive_name
drive = get_network_driver('ios')
# 类实例化
conn = drive(hostname='192.168.0.20',
             username='cisco',
             password='cisco',
             optional_args={'port': 22})
# 建立连接会话
conn.open()
# 使用其中一个方法,获取接口ip地址
ouput = conn.get_interfaces_ip()
# 打印结果
pp.pprint(ouput)

# 关闭连接
conn.close()

执行脚本后,结果如下所示:

备份配置

from napalm import get_network_driver

import pprint
import os

pp = pprint.PrettyPrinter(indent=2)

host = "192.168.0.20"

# drive是一个类class
drive = get_network_driver('ios')
# 类实例化
conn = drive(hostname= host,
             username= 'cisco',
             password= 'cisco',
             optional_args= {'port': 22})
# 建立连接会话
conn.open()
# 获取设备配置信息
output = conn.get_config()
# 打印结果
pp.pprint(output)
running_config = output['running']
# 写入文件
with open(os.path.join('LOG', f'{host}-running.conf'), 'w+') as f:
    f.writelines(running_config)

# 关闭连接
conn.close()

执行完脚本,结果如下所示:

默认包含了3个备份配置:

  • running配置:就是当前运行配置.
  • startup配置:就是启动配置了.
  • candidate配置,这个一般为空.

这里,我保存了running.config配置文件.

获取设备基础信息

# drive是一个类class
drive = get_network_driver('ios')
# 类实例化
conn = drive(hostname='192.168.0.20',
             username='cisco',
             password='cisco',
             optional_args={'port': 22})
# 建立连接会话
conn.open()
# 获取接口ip地址
output = conn.get_facts()
# 打印结果
pp.pprint(ouput)
# 关闭连接
conn.close()

执行脚本后,结果如下所示:

NAPALM介绍与实操_第2张图片

get_facts()方法采集了如下字段:

  • 域名、主机名称、接口列表、型号、版本号、序列号、运行时间、厂商.

发送命令

# drive是一个类class
drive = get_network_driver('ios')
# 类实例化
conn = drive(hostname='192.168.0.20',
             username='cisco',
             password='cisco',
             optional_args={'port': 22, 'secret': 'cisco'})
# 建立连接会话
conn.open()
# 发送命令
output = conn.cli(['show ip int brief', 'show arp'])
pp.pprint(output)

# 关闭连接
conn.close()

执行脚本后,结果如下所示:

说明:这里要留意下如果用户权限不够或者需要进入enable的,需要optional_args增加secret参数,表明需要进入特权模式了.

如果执行的命令权限不允许,就会报如下错误:

ValueError: Failed to enter enable mode. Please ensure you pass the 'secret' argument to ConnectHandler.

配置类

通过scp协议下发配置,它不需要enable密码.

前提条件:

  • 设备要开启scp服务, ip scp server enable .
  • 开启archive服务,备份配置到本地flash上;

    archive
     path flash:R1.conf
     write-memory

合并配置

说明:

  • load_merge_candidate():加载配置文件.
  • commit_config():提交配置.
  • revert_in=30:如果平台支持,可以设置时间挂起配置提交,超时没commit就恢复配置。

    基础玩法

    模板文件路径:templates\ios_logging_config.cfg

    logging host 10.1.1.2
from napalm import get_network_driver
import pprint

pp = pprint.PrettyPrinter(indent=2)

# drive是一个类class
drive = get_network_driver('ios')
# 类实例化
conn = drive(hostname='192.168.0.20',
             username='cisco',
             password='cisco',
             optional_args={'port': 22})
# 建立连接会话
conn.open()
try:
    conn.load_merge_candidate(filename='templates/ios_logging_config.cfg')
    conn.commit_config()
except Exception as e:
    print(e)    

如果设备上没开启scp服务就会报错:

SCP file transfers are not enabled. Configure 'ip scp server enable' on the device.

高级一点玩法:

模板文件路径:templates\ios_logging_config.cfg

def merge_config(vendor, devices, template_file):
    try:
        # drive是一个类class
        drive = get_network_driver(vendor)
        # 类实例化
        conn = drive(**devices)
        # 建立连接会话
        conn.open()
    except Exception as e:
        print("连接错误:{}".format(e))
        return

    try:
        conn.load_merge_candidate(filename=template_file)
        new_config = conn.compare_config()
        if new_config:
            print('预推送的配置如下:')
            print('='*80)
            print(new_config)
            print('=' * 80)

            choice = input("你要开始推送这些配置嘛, [Y/N]: ")
            if choice.lower() == 'y':
                print('开始提交配置,请稍后...')
                conn.commit_config()
                rollback = input("是否需要回滚配置,输入Y:回滚, 输入N,不回滚. [Y/N]")
                if rollback.lower() == 'y':
                    print('开始回滚配置,请稍后...')
                    conn.rollback()
                    print('配置已回滚,请检查配置.')
                else:
                    print('配置已经下发成功.')
            else:
                conn.discard_config()
                print('本次预配置没有推送.')
        else:
            print('无需重复配置.')

    except Exception as e:
        print(e)
    finally:
        conn.close()

if __name__ == '__main__':
    vendor = 'ios'
    devices = {'hostname': '192.168.0.20',
               'username': 'cisco',
               'password': 'cisco',
               'optional_args':{'port': 22}
               }
    tmp = 'templates/ios_logging_config.cfg'

    merge_config(vendor, devices, template_file=tmp)

配置替换

方法:load_replace_candidate()

def replace_config(vendor, devices, template_file):
    try:
        # drive是一个类class
        drive = get_network_driver(vendor)
        # 类实例化
        conn = drive(**devices)
        # 建立连接会话
        conn.open()
    except Exception as e:
        print("连接错误:{}".format(e))
        return

    try:
        if not (os.path.exists(template_file) and os.path.isfile(template_file)):
            msg = '文件不存在或文件类型不可用.'
            raise ValueError(msg)

        print("开始加载候选替换配置...")
        # 这个还未加载到设备上的
        conn.load_replace_candidate(template_file)
        # 配置比较
        print("\n预览配置对比:")
        print(">"*80)
        compare_result = conn.compare_config()
        print(compare_result)
        print(">" * 80)

        # You can commit or discard the candidate changes.
        if compare_result:
            try:
                choice = input("\nWould you like to commit these changes? [yN]: ").lower()
            except NameError:
                choice = input("\nWould you like to commit these changes? [yN]: ").lower()
            if choice == "y":
                print("Committing ...")
                conn.commit_config()
            else:
                print("Discarding ...")
                conn.discard_config()
        else:
            print('没有新的配置.')
            conn.discard_config()

        conn.close()

        print("Done...")

    except Exception as e:
        print(e)

if __name__ == '__main__':
    vendor = 'ios'
    devices = {'hostname': '192.168.0.20',
               'username': 'cisco',
               'password': 'cisco',
               'optional_args':{'port': 22}
               }
    tmp = 'LOG/192.168.0.20-running.conf'

    replace_config(vendor, devices, template_file=tmp)

说明:这里我用思科的设备,注意的是,如果running的配置里使用了banner,我是都提前把它删除了,不然会报错,这块我暂时没测试出来,如果哪位小伙伴试出来了,请告知我,谢谢。

Jinja2模板

  • 安装jinja2

    python -m pip install jinja2
  • jiaja2模板配置
    模板文件路径:templates/ssh-acl.tpl

    {% for host in hosts -%}
        access-list 20 permit host {{ host }}
    {% endfor -%}
    line vty 0 4
        access-class 20 in
  • 下发配置

    from napalm import get_network_driver
    from jinja2 import FileSystemLoader, Environment
    
    import pprint
    import logging
    
    pp = pprint.PrettyPrinter(indent=2)
    
    def connect_device(vendor, devices, hosts):
        try:
            # drive是一个类class
            drive = get_network_driver(vendor)
            # 类实例化
            conn = drive(**devices)
            # 建立连接会话
            conn.open()
    
        except Exception as e:
            print("连接错误:{}".format(e))
            return
    
        try:
            loader = FileSystemLoader('templates')
            env = Environment(loader=loader)
            tpl = env.get_template('ssh-acl.tpl')
            config_tpl = tpl.render({'hosts': hosts})
            # print(config_tpl)
            conn.load_merge_candidate(config=config_tpl)
            conn.commit_config()
    
        except Exception as e:
            print(e)
        finally:
            conn.close()
    
    if __name__ == '__main__':
        vendor = 'ios'
        devices = {'hostname': '192.168.0.20',
                   'username': 'cisco',
                   'password': 'cisco',
                   'optional_args':{'secret': 'cisco', 'port': 22}  # 如果enable需要密码,加入'secret'参数
                   }
        hosts = ['192.168.0.1', '192.168.0.254']
        connect_device(vendor, devices, hosts)

    这里可以打印下config_tpl,看看下发的配置是啥:

    # print(config_tpl)的输出结果如下所示:
    access-list 20 permit host 192.168.0.1
    access-list 20 permit host 192.168.0.254
    line vty 0 4
        access-class 20 in

配置更改回滚

conn.rollback()

备注:见前面章节演示.

验证部署

验证设备的状态是否是你期望的.
  • 定义一个yaml文件,定义预期想要的状态

    模板文件路径:templates/napalm_t.yaml

    ---
    - get_facts:
        'serial_number': '99CBK1M35C217V5Z7ABWQ'
    - get_interfaces_ip:
        GigabitEthernet0/0:
          ipv4: 192.168.0.20
  • compliance_report方法

    import pprint
    from napalm import get_network_driver
    
    pp = pprint.PrettyPrinter(indent=2)
    
    def connect_device(vendor, devices, hosts):
        try:
            # drive是一个类class
            drive = get_network_driver(vendor)
            # 类实例化
            conn = drive(**devices)
            # 建立连接会话
            conn.open()
    
        except Exception as e:
            print("连接错误:{}".format(e))
            return
    
        try:
            out = conn.compliance_report('templates/napalm_t.yaml')
            pp.pprint(out)
    
        except Exception as e:
            print(e)
        finally:
            conn.close()
    
    if __name__ == '__main__':
        vendor = 'ios'
        devices = {'hostname': '192.168.0.20',
                   'username': 'cisco',
                   'password': 'cisco',
                   'optional_args':{'port': 22}
                   }
        hosts = ['192.168.0.1', '192.168.0.254']
        connect_device(vendor, devices, hosts)

    执行结果如下所示:

    { 'complies': True,
      'get_facts': { 'complies': True,
                     'extra': [],
                     'missing': [],
                     'present': { 'serial_number': { 'complies': True,
                                                     'nested': False}}},
      'get_interfaces_ip': { 'complies': True,
                             'extra': [],
                             'missing': [],
                             'present': { 'GigabitEthernet0/0': { 'complies': True,
                                                                  'nested': True}}},
      'skipped': []}

    说明:complies的value值为True说明正常,如果False表示和预期设想不太一致。

支持上下文管理

通过上下文管理就不需要调用open()和close()方法了.
# 类实例化
drive = get_network_driver('ios')
# with上下文管理
with drive(hostname='192.168.0.20',username='cisco',password='cisco',optional_args={'port': 22}) as conn:
    output = conn.get_facts()
    # 打印结果
    pp.pprint(ouput)

ping方法

默认格式:ping x.x.x.x timeout 2 size 100 repeat 5

#!/usr/bin/env python3
#-*- coding:UTF-8 -*-

from napalm import get_network_driver
import pprint
import logging

# logging.basicConfig(filename='debug.log', level=logging.DEBUG)
# logger = logging.getLogger('napalm')

pp = pprint.PrettyPrinter(indent=2)

def connect_device(vendor, devices, dst_ip):
    try:
        # drive是一个类class
        drive = get_network_driver(vendor)
        # 类实例化
        conn = drive(**devices)
        # 建立连接会话
        conn.open()

    except Exception as e:
        print("连接错误:{}".format(e))
        return

    try:
        for ip in dst_ip:
            output = conn.ping(ip)
            pp.pprint(output)

    except Exception as e:
        print(e)
    finally:
        conn.close()

if __name__ == '__main__':
    vendor = 'ios'
    devices = {'hostname': '192.168.0.20',
               'username': 'cisco',
               'password': 'cisco',
               'optional_args':{'port': 22}
               }
    dst_ip = ['192.168.0.19', '192.168.0.20']
    connect_device(vendor, devices, dst_ip)

回显的结果:

{ 'success': { 'packet_loss': 0,
               'probes_sent': 5,
               'results': [ {'ip_address': '192.168.0.19', 'rtt': 0.0},
                            {'ip_address': '192.168.0.19', 'rtt': 0.0},
                            {'ip_address': '192.168.0.19', 'rtt': 0.0},
                            {'ip_address': '192.168.0.19', 'rtt': 0.0},
                            {'ip_address': '192.168.0.19', 'rtt': 0.0}],
               'rtt_avg': 2.0,
               'rtt_max': 3.0,
               'rtt_min': 2.0,
               'rtt_stddev': 0.0}}
{ 'success': { 'packet_loss': 0,
               'probes_sent': 5,
               'results': [ {'ip_address': '192.168.0.20', 'rtt': 0.0},
                            {'ip_address': '192.168.0.20', 'rtt': 0.0},
                            {'ip_address': '192.168.0.20', 'rtt': 0.0},
                            {'ip_address': '192.168.0.20', 'rtt': 0.0},
                            {'ip_address': '192.168.0.20', 'rtt': 0.0}],
               'rtt_avg': 1.0,
               'rtt_max': 2.0,
               'rtt_min': 1.0,
               'rtt_stddev': 0.0}}    

好了,文章的讲解就到此结束了,各个功能已经给大家演示了,各位小伙伴如实操一遍,相信就能掌握了并根据需求进行改写了。

如果需要更详细的信息,请移动官方进行查阅。

别忘了,动动手分享下,点击、收藏、三连击! 哈哈...

如果喜欢的我的文章,欢迎关注我的公众号:点滴技术,扫码关注,不定期分享

公众号:点滴技术

你可能感兴趣的:(python)