続・マルチベンダルータ制御APIライブラリ NAPALMを触ってみた

  • 15
    いいね
  • 0
    コメント

NetOpsCoding Advent Calendar 2016の24日目の記事です。

NAPALMはネットワーク装置を設定・情報取得するためのオープンソースのPythonライブラリであり、統一化されたインタフェースで複数のベンダ製品を制御することを目的として開発されています。マルチベンダなネットワーク運用環境における自動化を計画されている方々にとっては、NAPALMようなマルチベンダ対応ライブラリは待ち望まれた存在ではないでしょうか。

NAPALMについては2015年6月に「ルータ制御APIライブラリ NAPALMを触ってみた」の記事でも紹介していましたが、現在もなおNANOGを中心としたコミュニティ主導で積極的に開発が継続されており機能や対応機種が続々と増えています。「そろそろマルチベンダー環境な実運用現場に投入してみても良いかもしれない」と私自身が感じたので、今回改めて情報をまとめてみました。

対応機種

NAPALMでは、以下のネットワークOSに対応しています。

  • Arista EOS: 4.15.0F以降
  • Juniper JunOS: 12.1以降
  • Cisco IOS-XR: 5.1.0以降
  • Fortinet FortiOS: 5.2.0以降
  • IBM: 対応version不明
  • Cisco NXOS: 6.1以降 ( Nexus 5k,6k,7kはversion 7.2以降)
  • Cisco IOS: 12.4(20)T以降
  • Pluribus: N/A
  • PaloAltoNetworks PANOS:7.0以降

NAPALMのバックグランドでは、メーカ公式のAPI(Juniper PyEZArista eAPIなど)や、個人またはコミュニティが開発したメーカ非公式API(pyIOSXRnetmikoなど)を組み合わせて、構成されています。

コミュニティ主導による開発なので「足りない機能は自分たちで作れば良い」というアイディアでどんどん機能拡張が進んでいるようです。2015年6月時点では対応機種はIOSXR,JUNOS,EOS,FortiOSの4種類だけだったので、この1年半ほどで飛躍的に開発が進んでいることが見受けられます。

対応機能

NAPALMでは本記事の時点では以下の機能が実装されています。ただしOSによって実装されている機能はまちまちなので、詳細は公式ページSupported Devicesをご参照ください。

  • コンフィグの読み込み(replace/merge)/比較/Commit/Dicard/Rollback
  • 装置情報の取得
  • SNMP情報の取得
  • NTP情報の取得
  • ARPテーブルの取得
  • インタフェース情報の取得
  • LLDP情報の取得
  • BGP情報の取得
  • ルーティング情報の取得
  • SLA prove情報の取得
  • ユーザ情報の取得
  • 光出力情報の取得
  • VRF network instances 情報の取得
  • firewall policy情報の取得
  • ping情報の取得
  • Traceroute情報の取得

状態確認系の機能が充実していることは自動化ソフトウェアを開発する上で非常に助かります。
以前のブログ「PyEZとJSNAPyを使ってみた」でも書きましたが、ネットワーク装置の正常性確認工程をソフトウェア化することはとても骨の折れる作業です。
本来であればソフトウェア開発者が避けては通れない「情報取得コマンドの実施 -> 正規表現でターゲット文字列を取得 -> 抽出された文字列を情報として取得もしくは正常/異常を判定」を実装する工程を、ベンダー/機種を意識すること無くライブラリとして利用できることで開発工数の大幅な短縮につなげることができます。

自動化フレームワークとの連携

NAPALMはソフトウェアに組み込むだけではなく、サーバインフラ運用で導入が進んでいる自動化フレームワークから呼び出すことも想定されています。現時点ではAnsibleSaltと連携するためのNAPALMモジュールが開発されています。

本記事では詳細は紹介しませんが、ご興味があれば以下の資料をご参照ください。

NAPALMを使ってみた

実際にNAPALMを使うとどういったことができるのか、ルータを使いながら試してみます。

使ってみたい機能を手当たり次第触っていった結果、なかなかな分量になってしまったので、ご興味ある項目からご確認ください。

環境

本記事では、以下の環境で実験してみました。Macbook以外はいずれも無料で用意できるものです。

  • PC(ホストマシン)
    • MacBookAir OSX El Capitan 10.11.6
  • 仮想ルータ
    • Juniper firefly-perimeter JUNOS version 12.1X47-D20.7
      • OS: JUNOS 12.1X47-D20.7
    • Cisco IOSXRv
      • OS: IOS XR Version 6.2.1.23I
  • 仮想化ソフトウェア
    • Vagrant 1.9.0
    • VirtualBox Version 5.0.30 r112061
  • NAPALM version 1.1.0

fireflyやIOSXRvのVagrant連携について知りたい場合は、以下のブログを参考にしてみてください。
- Vagrantでfireflyを動かしたら自動化開発が捗った話
- IOS-XRv Vagrantを試してみた

参考

今回は以下の記事を見ながら実装を進めました。
- NAPALM Githubページ
- NAPALM 公式ドキュメント

インストール

NAPALMの最新版(このブログの時点ではversion 1.1.0)をインストールします。ここではPythonパッケージ管理システムであるpipを利用しています。

% pip install napalm
(snip)


% pip list
(snip)
napalm (1.1.0)
napalm-base (0.21.0)
napalm-eos (0.5.0)
napalm-fortios (0.3.0)
napalm-ibm (0.1.6)
napalm-ios (0.5.0)
napalm-iosxr (0.4.2)
napalm-junos (0.5.1)
napalm-nxos (0.5.0)
napalm-panos (0.2.2)
napalm-pluribus (0.4.2)
(snip)


% pip show napalm                                                                                           (git)-[master]
Name: napalm
Version: 1.1.0
Summary: Network Automation and Programmability Abstraction Layer with Multivendor support
Home-page: https://github.com/napalm-automation/napalm
Author: David Barroso
Author-email: dbarrosop@dravetech.com
License: UNKNOWN
Location: /usr/local/lib/python2.7/site-packages
Requires: napalm-pluribus, napalm-base, napalm-ios, napalm-iosxr, napalm-ibm, napalm-junos, napalm-eos, napalm-nxos, napalm-fortios, napalm-panos

ルータの下準備

firefly設定

fireflyを利用するために、以下のように設定しています。
- ホスト名: firefly1
- IPアドレス: 192.168.34.16/24
- ユーザ名: user1
- パスワード: password1

firefly Vagrantの構築手順はVagrantでfireflyを動かしたら自動化開発が捗った話をご参照ください。

Vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure(2) do |config|
    config.vm.box = "juniper/ffp-12.1X47-D20.7"
    config.vm.define :firefly1 do | firefly1 |
        firefly1.vm.hostname = 'firefly1'
        firefly1.vm.network "private_network",ip: "192.168.34.16",netmask: "255.255.255.0"
    end
end
firefly追加設定
set system root-authentication plain-text-password
set system login user user1 class super-user
set system login user user1 authentication plain-text-password

# デフォルト設定ではge-0/0/1がuntrust zoneに設定されているため、ホストマシンとの通信ができません。
# ここではホストマシン(MacbookAir)で自動化ツールを動作させて通信させたいのでge-0/0/1をtrust zoneに変更しています。
set security zones security-zone trust interfaces ge-0/0/1
set security zones security-zone trust interfaces ge-0/0/1.0 host-inbound-traffic system-services all
set system time-zone Asia/Tokyo


# デフォルト設定はflow-based転送モード(firewall機能を動作させるモード)が有効になっており、
# このままではBGPやOSPFなどのルーティングプロトコルは動作しません。
# ここではルーティングプロトコルを動作させるために、packet-based転送モードに変更する設定を実施しています。
# モードの切替には、設定後の再起動が必要です。
delete security policies
set security forwarding-options family mpls mode packet-based
set security forwarding-options family inet6 mode packet-based

# ダミー用のBGP関連設定
set routing-options autonomous-system 65001
set protocols bgp family inet unicast
set protocols bgp group ge-0/0/2 type external
set protocols bgp group ge-0/0/2 neighbor 192.168.35.2 peer-as 65002
set protocols bgp group ge-0/0/2 export advertised_for_firefly2
set routing-options rib inet.0 static route 10.10.10.0/24 discard
set routing-options rib inet.0 static route 10.10.20.0/24 discard
set policy-options policy-statement advertised_for_firefly2 term 10 from route-filter 10.10.10.0/24 exact
set policy-options policy-statement advertised_for_firefly2 term 10 then accept
set policy-options policy-statement advertised_for_firefly2 term 999 then reject


# なお、以下のNETCONFを設定することなくNAPALMを利用することができました。
# (設定してません) set system services netconf ssh
fireflyで設定確認

root@firefly1> show configuration
## Last commit: 2016-12-24 18:32:08 JST by root
version 12.1X47-D20.7;
system {
    host-name firefly1;
    time-zone Asia/Tokyo;
    root-authentication {
        encrypted-password "$1$EUe8ffbf$HT20ATkJCGIyslemV9hTf1"; ## SECRET-DATA
        ssh-rsa "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCqs9vHYiqhpS5/5QWI2EskcTz1nMdv+1NlxrCaDyO8+03rzyHiOjXILcYx5MHExvw4JRlMMxiO0D3dHW+i4EtIVTSstyzEyd2coxLtgBp4VfaINBLInQKQNxOPioPUtu7rJynR9cHPk7DQw7QjCCUawYgQHWzCiiYSnmKWZrJAgVQZzfP2LEj1+Cqrg1ro8VQ4CpLeplOT4qXmlTE/dvQFPHabhAmGdP7JZv4IDPwZtkJ7gRv/PfYdTpn96IiG4Y09yIMPXaq40A82bczptazcOdScVyjUrFe8NbzQfnkVpe2C6ieDc7lU7PQhqqBGPpb1eEbQo1vq2Lo9b88dT7EH vagrant"; ## SECRET-DATA
    }
    login {
        user user1 {
            uid 2001;
            class super-user;
            authentication {
                encrypted-password "$1$P9L/Y/Ca$dzv5waDzJeYa5VdKGDm340"; ## SECRET-DATA
            }
        }
        user vagrant {
            uid 2000;
            class super-user;
            authentication {
                ssh-rsa "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzIw+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoPkcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NOTd0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcWyLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQ== vagrant insecure public key"; ## SECRET-DATA
            }
        }
    }
    services {
        ssh {
            root-login allow;
        }
        web-management {
            http {
                interface ge-0/0/0.0;
            }
        }
    }
    syslog {
        user * {
            any emergency;
        }
        file messages {
            any any;
            authorization info;
        }
        file interactive-commands {
            interactive-commands any;
        }
    }
    license {
        autoupdate {
            url https://ae1.juniper.net/junos/key_retrieval;
        }
    }
}
interfaces {
    ge-0/0/0 {
        unit 0 {
            family inet {
                dhcp;
            }
        }
    }
    ge-0/0/1 {
        unit 0 {
            family inet {
                address 192.168.34.16/24;
            }
        }
    }
    ge-0/0/2 {
        unit 0 {
            family inet {
                address 192.168.35.1/30;
            }
        }
    }
}
routing-options {
    rib inet.0 {
        static {
            route 10.10.10.0/24 discard;
            route 10.10.20.0/24 discard;
        }
    }
    autonomous-system 65001;
}
protocols {
    bgp {
        family inet {
            unicast;
        }
        group ge-0/0/2 {
            type external;
            export advertised_for_firefly2;
            neighbor 192.168.35.2 {
                peer-as 65002;
            }
        }
    }
}
policy-options {
    policy-statement advertised_for_firefly2 {
        term 10 {
            from {
                route-filter 10.10.10.0/24 exact;
            }
            then accept;
        }
        term 999 {
            then reject;
        }
    }
}
security {
    forwarding-options {
        family {
            inet6 {
                mode packet-based;
            }
            mpls {
                mode packet-based;
            }
        }
    }
    screen {
        ids-option untrust-screen {
            icmp {
                ping-death;
            }
            ip {
                source-route-option;
                tear-drop;
            }
            tcp {
                syn-flood {
                    alarm-threshold 1024;
                    attack-threshold 200;
                    source-threshold 1024;
                    destination-threshold 2048;
                    queue-size 2000; ## Warning: 'queue-size' is deprecated
                    timeout 20;
                }
                land;
            }
        }
    }
    zones {
        functional-zone management {
            interfaces {
                ge-0/0/0.0 {
                    host-inbound-traffic {
                        system-services {
                            all;
                        }
                        protocols {
                            all;
                        }
                    }
                }
            }
        }
        security-zone trust {
            tcp-rst;
            interfaces {
                ge-0/0/1.0 {
                    host-inbound-traffic {
                        system-services {
                            all;
                        }
                    }
                }
                ge-0/0/2.0 {
                    host-inbound-traffic {
                        system-services {
                            all;
                        }
                    }
                }
            }
        }
        security-zone untrust {
            screen untrust-screen;
        }
    }
}
ホストマシンからfireflyにsshログインできることを確認
% ssh user1@192.168.34.16
Password:
--- JUNOS 12.1X47-D20.7 built 2015-03-03 21:53:50 UTC
user1@firefly1>
user1@firefly1> show version
Hostname: firefly1
Model: firefly-perimeter
JUNOS Software Release [12.1X47-D20.7]

IOSXRv設定

IOSXRvを利用するために、以下のように設定しています。
- ホスト名:ios(デフォルト)
- IPアドレス: 127.0.0.1
- IOSXRv-Vagrantのデフォルト設定で、ホストマシンからのSSHポートにTCP2223を利用
- ユーザ名: vagrant
- パスワード: vagrant

IOSXRvの構築手順はIOS-XRv Vagrantを試してみたをご参照ください。

Vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
  config.vm.box = "IOS-XRv"
end
IOSXRv追加設定
# NAPALM利用時には必須
xml agent tty iteration off

# ダミー用BGP設定
router bgp 65001
 timers bgp 30 90
 address-family ipv4 unicast
  network 0.0.0.0/0
 !
 neighbor 192.168.0.1
  remote-as 65002
  description test_AS65002
  address-family ipv4 unicast
   send-community-ebgp
   next-hop-self
   soft-reconfiguration inbound always
  !
 !
!
end
IOSXRvで設定確認
RP/0/RP0/CPU0:ios#show running-config
Fri Dec 23 13:16:25.273 UTC
Building configuration...
!! IOS XR Configuration version = 6.2.1.23I
!! Last configuration change at Fri Dec 23 13:08:29 2016 by vagrant
!
telnet vrf default ipv4 server max-servers 10
username vagrant
 group root-lr
 group cisco-support
 secret 5 $1$RQve$C1P.pH/koIKYsybRgxtSZ0
!
tpa
 address-family ipv4
  update-source MgmtEth0/RP0/CPU0/0
 !
!
interface MgmtEth0/RP0/CPU0/0
 ipv4 address dhcp
!
router static
 address-family ipv4 unicast
  0.0.0.0/0 MgmtEth0/RP0/CPU0/0 10.0.2.2
 !
!
router bgp 65001
 timers bgp 30 90
 address-family ipv4 unicast
  network 0.0.0.0/0
 !
 neighbor 192.168.0.1
  remote-as 65002
  description test_AS65002
  address-family ipv4 unicast
   send-community-ebgp
   next-hop-self
   soft-reconfiguration inbound always
  !
 !
!
grpc
 port 57777
!
xml agent tty
 iteration off
!
ssh server v2
ssh server vrf default
end

NAPALM利用例: 機器情報の取得

まずは機器情報を取得してみます。get_facts()を利用することで、辞書型変数として情報を取得することができます。

JUNOSの場合

get_info.py
#! /usr/bin/env python
# -*- coding: utf-8 -*-

import napalm
from pprint import pprint

driver = napalm.get_network_driver('junos')
device = driver(
    hostname='192.168.34.16',
    username='user1',
    password='password1' )

print 'Open session: ',
device.open()
print 'OK'

pprint(device.get_facts())

print 'Close session: ',
device.close()
print 'OK'

実行結果
% python get_info.py 

Open session:  OK
get facts:
{u'fqdn': u'firefly1',
 u'hostname': u'firefly1',
 u'interface_list': ['ge-0/0/0',
                     'gr-0/0/0',
                     'ip-0/0/0',
                     'lsq-0/0/0',
                     'lt-0/0/0',
                     'mt-0/0/0',
                     'sp-0/0/0',
                     'ge-0/0/1',
                     'ge-0/0/2',
                     '.local.',
                     'dsc',
                     'gre',
                     'ipip',
                     'irb',
                     'lo0',
                     'lsi',
                     'mtun',
                     'pimd',
                     'pime',
                     'pp0',
                     'ppd0',
                     'ppe0',
                     'st0',
                     'tap',
                     'vlan'],
 u'model': u'FIREFLY-PERIMETER',
 u'os_version': u'12.1X47-D20.7',
 u'serial_number': u'f0016079634f',
 u'uptime': 1740,
 u'vendor': u'Juniper'}
Close session:  OK
ルータ実機での状態確認
root@firefly1> show version
Hostname: firefly1
Model: firefly-perimeter
JUNOS Software Release [12.1X47-D20.7]

root@firefly1> show chassis hardware
Hardware inventory:
Item             Version  Part number  Serial number     Description
Chassis                                f0016079634f      FIREFLY-PERIMETER
Midplane
System IO
Routing Engine                                           FIREFLY-PERIMETER RE
FPC 0                                                    Virtual FPC
  PIC 0                                                  Virtual VTNET GE
Power Supply 0

IOSXRの場合

get_info.py
#! /usr/bin/env python
# -*- coding: utf-8 -*-

import napalm
from pprint import pprint

driver = napalm.get_network_driver('iosxr')
device = driver(
    hostname='127.0.0.1',
    username='vagrant',
    password='vagrant',
    # IOSXRv default ssh port
    optional_args={'port': 2223}) 

print 'Open session: ',
device.open()
print 'OK'

print 'get facts: '
pprint(device.get_facts())

print 'Close session: ',
device.close()
print 'OK'
実行結果
% python get_info.py                                                                                                                                                                         (git)-[master]
Open session:  OK
get facts:
{u'fqdn': u'ios',
 u'hostname': u'ios',
 u'interface_list': [u'Null0', u'MgmtEth0/RP0/CPU0/0'],
 u'model': u'XRV-P-L--CH',
 u'os_version': u'6.2.1.23I',
 u'serial_number': u'XRV-SN---CH',
 u'uptime': 1585,
 u'vendor': u'Cisco'}
ルータ実機での状態確認
RP/0/RP0/CPU0:ios#show version
Fri Dec 23 12:38:05.156 UTC

Cisco IOS XR Software, Version 6.2.1.23I
Copyright (c) 2013-2016 by Cisco Systems, Inc.

Build Information:
 Built By     : jwu
 Built On     : Mon Nov 21 00:33:58 PST 2016
 Build Host   : iox-ucs-005
 Workspace    : /auto/iox-ucs-005-san1/nightly/xr-dev_16.11.20C/iosxrv-x64
 Version      : 6.2.1.23I
 Location     : /opt/cisco/XR/packages/

cisco IOS XRv x64 () processor
System uptime is 34 minutes


RP/0/RP0/CPU0:ios#show inventory
Fri Dec 23 12:39:25.870 UTC
NAME: "0/RP0", DESCR: "FLEX_PH"
PID: XRV-P-L--RP       , VID: FLEX_PH, SN: XRV-SN---RP

NAME: "Rack 0", DESCR: "FLEX_PH"
PID: XRV-P-L--CH       , VID: FLEX_PH, SN: XRV-SN---CH

NAPALM利用例: インタフェースアドレス情報の取得

インタフェースアドレス情報を取得するにはget_interfaces_ip()関数を利用します。

今回は実施しませんが、インタフェースのup/downやMAC情報を取得したい場合はget_interfaces()関数、インタフェースエラーカウンタを取得したい場合はget_interfaces_counters()関数、光強度情報を取得したい場合はget_optics()関数をそれぞれ利用することで情報取得することができます。

JUNOSの場合

get_info.py
#! /usr/bin/env python
# -*- coding: utf-8 -*-

import napalm
from pprint import pprint

driver = napalm.get_network_driver('junos')
device = driver(
    hostname='192.168.34.16',
    username='user1',
    password='password1')

print 'Open session: ',
device.open()
print 'OK'

print 'get interface IP: '
pprint(device.get_interfaces_ip())

print 'Close session: ',
device.close()
print 'OK'
実行結果
% python get_info.py 

Open session:  OK
get interface IP:
{u'ge-0/0/0.0': {u'ipv4': {u'10.0.2.15': {u'prefix_length': 24}}},
 u'ge-0/0/1.0': {u'ipv4': {u'192.168.34.16': {u'prefix_length': 24}}},
 u'ge-0/0/2.0': {u'ipv4': {u'192.168.35.1': {u'prefix_length': 30}}},
 u'lo0.16384': {u'ipv4': {u'127.0.0.1': {u'prefix_length': 0}}},
 u'lo0.16385': {u'ipv4': {u'10.0.0.1': {u'prefix_length': 0},
                          u'10.0.0.16': {u'prefix_length': 0},
                          u'128.0.0.1': {u'prefix_length': 0},
                          u'128.0.0.4': {u'prefix_length': 0},
                          u'128.0.1.16': {u'prefix_length': 0}}},
 u'sp-0/0/0.16383': {u'ipv4': {u'10.0.0.1': {u'prefix_length': 0},
                               u'10.0.0.6': {u'prefix_length': 0},
                               u'128.0.0.1': {u'prefix_length': 0},
                               u'128.0.0.6': {u'prefix_length': 0}}}}
Close session:  OK
ルータ実機での状態確認
root@firefly1> show interfaces terse
Interface               Admin Link Proto    Local                 Remote
ge-0/0/0                up    up
ge-0/0/0.0              up    up   inet     10.0.2.15/24
gr-0/0/0                up    up
ip-0/0/0                up    up
lsq-0/0/0               up    up
lt-0/0/0                up    up
mt-0/0/0                up    up
sp-0/0/0                up    up
sp-0/0/0.0              up    up   inet
                                   inet6
sp-0/0/0.16383          up    up   inet     10.0.0.1            --> 10.0.0.16
                                            10.0.0.6            --> 0/0
                                            128.0.0.1           --> 128.0.1.16
                                            128.0.0.6           --> 0/0
ge-0/0/1                up    up
ge-0/0/1.0              up    up   inet     192.168.34.16/24
ge-0/0/2                up    up
ge-0/0/2.0              up    up   inet     192.168.35.1/30
dsc                     up    up
gre                     up    up
ipip                    up    up
irb                     up    up
lo0                     up    up
lo0.16384               up    up   inet     127.0.0.1           --> 0/0
lo0.16385               up    up   inet     10.0.0.1            --> 0/0
                                            10.0.0.16           --> 0/0
                                            128.0.0.1           --> 0/0
                                            128.0.0.4           --> 0/0
                                            128.0.1.16          --> 0/0
lo0.32768               up    up
lsi                     up    up
mtun                    up    up
pimd                    up    up
pime                    up    up
pp0                     up    up
ppd0                    up    up
ppe0                    up    up
st0                     up    up
tap                     up    up
vlan                    up    down

IOSXRの場合

get_info.py
#! /usr/bin/env python
# -*- coding: utf-8 -*-

import napalm
from pprint import pprint

driver = napalm.get_network_driver('iosxr')
device = driver(
    hostname='127.0.0.1',
    username='vagrant',
    password='vagrant',
    # IOSXRv default ssh port
    optional_args={'port': 2223}) 

print 'Open session: ',
device.open()
print 'OK'

print 'get interface IP: '
pprint(device.get_interfaces_ip())

print 'Close session: ',
device.close()
print 'OK'
実行結果
% python get_info.py                                                                                                                                                                         (git)-[master]
Open session:  OK

get interface IP:
{u'MgmtEth0/RP0/CPU0/0': {u'ipv4': {u'10.0.2.15': {u'prefix_length': 24}}}}

Close session:  OK
ルータ実機での状態確認
RP/0/RP0/CPU0:ios#show ipv4 interface brief
Fri Dec 23 12:44:59.827 UTC

Interface                      IP-Address      Status          Protocol Vrf-Name
MgmtEth0/RP0/CPU0/0            10.0.2.15       Up              Up       default

NAPALM利用例: ARPテーブル情報の取得

ARPテーブル情報を取得するにはget_arp_table()関数を利用します。

JUNOSの場合

get_info.py
#! /usr/bin/env python
# -*- coding: utf-8 -*-

import napalm
from pprint import pprint

driver = napalm.get_network_driver('junos')
device = driver(
    hostname='192.168.34.16',
    username='user1',
    password='password1')

print 'Open session: ',
device.open()
print 'OK'

print 'get ARP table'
pprint(device.get_arp_table())

print 'Close session: ',
device.close()
print 'OK'
実行結果
% python get_info.py

Open session:  OK
get ARP table
[{'age': 530.0,
  'interface': u'ge-0/0/0.0',
  'ip': u'10.0.2.2',
  'mac': u'52:54:00:12:35:02'},
 {'age': 505.0,
  'interface': u'ge-0/0/0.0',
  'ip': u'10.0.2.3',
  'mac': u'52:54:00:12:35:03'},
 {'age': 1415.0,
  'interface': u'ge-0/0/1.0',
  'ip': u'192.168.34.1',
  'mac': u'0A:00:27:00:00:04'}]
Close session:  OK
ルータ実機での状態確認
root@firefly1> show arp
MAC Address       Address         Name                      Interface           Flags
52:54:00:12:35:02 10.0.2.2        10.0.2.2                  ge-0/0/0.0          none
52:54:00:12:35:03 10.0.2.3        10.0.2.3                  ge-0/0/0.0          none
0a:00:27:00:00:04 192.168.34.1    192.168.34.1              ge-0/0/1.0          none
Total entries: 3

IOSXRの場合

get_info.py
#! /usr/bin/env python
# -*- coding: utf-8 -*-

import napalm
from pprint import pprint

driver = napalm.get_network_driver('iosxr')
device = driver(
    hostname='127.0.0.1',
    username='vagrant',
    password='vagrant',
    # IOSXRv default ssh port
    optional_args={'port': 2223}) 

print 'Open session: ',
device.open()
print 'OK'

print 'get ARP table'
pprint(device.get_arp_table())

print 'Close session: ',
device.close()
print 'OK'
実行結果
% python get_info.py

Open session:  OK
get ARP table
[{u'age': 1228.0,
  u'interface': u'MgmtEth0/RP0/CPU0/0',
  u'ip': u'10.0.2.2',
  u'mac': u'52:54:00:12:35:02'},
 {u'age': 0.0,
  u'interface': u'MgmtEth0/RP0/CPU0/0',
  u'ip': u'10.0.2.15',
  u'mac': u'08:00:27:0D:1E:94'}]

Close session:  OK
ルータ実機での状態確認
RP/0/RP0/CPU0:ios#show arp
Fri Dec 23 12:46:58.918 UTC

-------------------------------------------------------------------------------
0/RP0/CPU0
-------------------------------------------------------------------------------
Address         Age        Hardware Addr   State      Type  Interface
10.0.2.2        00:37:45   5254.0012.3502  Dynamic    ARPA  MgmtEth0/RP0/CPU0/0
10.0.2.15       -          0800.270d.1e94  Interface  ARPA  MgmtEth0/RP0/CPU0/0

NAPALM利用例: 特定経路のルーティング情報の取得

get_route_to()関数を利用することで、指定した経路のルーティング情報を取得できます。

JUNOSの場合

get_info.py
#! /usr/bin/env python
# -*- coding: utf-8 -*-

import napalm
from pprint import pprint

driver = napalm.get_network_driver('junos')
device = driver(
    hostname='192.168.34.16',
    username='user1',
    password='password1')

print 'Open session: ',
device.open()
print 'OK'

print 'get route to 192.168.35.0'
pprint(device.get_route_to(destination=u'192.168.35.0'))

print 'Close session: ',
device.close()
print 'OK'

実行結果
% python get_info.py 

Open session:  OK
get route to 192.168.35.0
{u'192.168.35.0/30': [{'age': 3444,
                       'current_active': True,
                       'inactive_reason': u'',
                       'last_active': True,
                       'next_hop': None,
                       'outgoing_interface': u'ge-0/0/2.0',
                       'preference': 0,
                       'protocol': u'Direct',
                       u'protocol_attributes': {},
                       'routing_table': u'inet.0',
                       'selected_next_hop': True}]}
Close session:  OK
ルータ実機での状態確認
root@firefly1> show route 192.168.35.2 detail

inet.0: 7 destinations, 7 routes (7 active, 0 holddown, 0 hidden)
192.168.35.0/30 (1 entry, 1 announced)
        *Direct Preference: 0
                Next hop type: Interface
                Address: 0x9350448
                Next-hop reference count: 2
                Next hop: via ge-0/0/2.0, selected
                State: <Active Int>
                Age: 58:00
                Task: IF
                Announcement bits (1): 1-Resolve tree 1
                AS path: I

IOSXRの場合 (失敗)

get_info.py
#! /usr/bin/env python
# -*- coding: utf-8 -*-

import napalm
from pprint import pprint

driver = napalm.get_network_driver('iosxr')
device = driver(
    hostname='127.0.0.1',
    username='vagrant',
    password='vagrant',
    # IOSXRv default ssh port
    optional_args={'port': 2223}) 

print 'Open session: ',
device.open()
print 'OK'

pprint 'get route to 10.0.2.2'
pprint(device.get_route_to(destination=u'10.0.2.2'))

print 'Close session: ',
device.close()
print 'OK'
実行結果
% python get_info.py                                                                                                                                                                         

Open session:  OK
get route to 10.0.2.2
Traceback (most recent call last):
  File "get_info.py", line 38, in <module>
    pprint(device.get_route_to(destination=u'10.0.2.2'))
  File "/usr/local/lib/python2.7/site-packages/napalm_iosxr/iosxr.py", line 1218, in get_route_to
    protocol=protocol
TypeError: Protocol not supported: .

ルータでは以下のように確認できますが、NAPALMでは失敗してしまいました。

ルータ実機での状態確認
RP/0/RP0/CPU0:ios#show route 10.0.2.15
Fri Dec 23 12:49:19.184 UTC

Routing entry for 10.0.2.15/32
  Known via "local", distance 0, metric 0 (connected)
  Installed Dec 23 12:09:11.148 for 00:40:08
  Routing Descriptor Blocks
    directly connected, via MgmtEth0/RP0/CPU0/0
      Route metric is 0
  No advertising protos.

get_route_to()関数でオプション引数であるprotocolを指定しなかったのが悪かったのかと思い、以下のようなパターンも試してみましたがダメでした。

pprint(device.get_route_to(destination=u'10.0.2.2', protocol=u'bgp'))
実行結果
% python get_info.py                                                                                                                                                                         

Open session:  OK
get route to 10.0.2.2
Traceback (most recent call last):
  File "get_info.py", line 38, in <module>
    pprint(device.get_route_to(destination=u'10.0.2.2', protocol=u'bgp'))
  File "/usr/local/lib/python2.7/site-packages/napalm_iosxr/iosxr.py", line 1239, in get_route_to
    routes_tree = ETREE.fromstring(self.device.make_rpc_call(route_info_rpc_command))
  File "/usr/local/lib/python2.7/site-packages/pyIOSXR/iosxr.py", line 148, in make_rpc_call
    result = self._execute_rpc(rpc_command)
  File "/usr/local/lib/python2.7/site-packages/pyIOSXR/iosxr.py", line 412, in _execute_rpc
    raise XMLCLIError(error_msg, self)
pyIOSXR.exceptions.XMLCLIError:
Original call was: <?xml version="1.0" encoding="UTF-8"?><Request MajorVersion="1" MinorVersion="0"><Get><Operational><RIB><VRFTable><VRF><Naming><VRFName>default        </VRFName></Naming><AFTable><AF><Naming><AFName>IPv4</AFName></Naming><SAFTable><SAF>        <Naming><SAFName>Unicast</SAFName></Naming><IP_RIBRouteTable><IP_RIBRoute><Naming>        <RouteTableName>default</RouteTableName></Naming><RouteTable><Route><Naming><Address>        10.0.2.2</Address></Naming></Route></RouteTable></IP_RIBRoute></IP_RIBRouteTable>        </SAF></SAFTable></AF></AFTable></VRF></VRFTable></RIB></Operational></Get></Request>

pyIOSXRのバグかもしれないので、また時間があるときに詳細を調べてみようと思います。

NAPALM利用例: BGPネイバー情報の取得

get_bgp_neighbors()関数を利用することで、BGPネイバー情報を取得することができます。

JUNOSの場合

get_info.py
#! /usr/bin/env python
# -*- coding: utf-8 -*-

import napalm
from pprint import pprint

driver = napalm.get_network_driver('junos')
device = driver(
    hostname='192.168.34.16',
    username='user1',
    password='password1')

print 'Open session: ',
device.open()
print 'OK'

print 'get BGP neighbors'
pprint(device.get_bgp_neighbors())

print 'Close session: ',
device.close()
print 'OK'

実行結果
% python get_info.py

Open session:  OK
get BGP neighbors
{u'global': {u'peers': {u'192.168.35.2': {u'address_family': {},
                                          'description': u'',
                                          'is_enabled': True,
                                          'is_up': False,
                                          'local_as': 65001,
                                          'remote_as': 65002,
                                          'remote_id': u'',
                                          u'uptime': 291}},
             u'router_id': u'None'}}
Close session:  OK
ルータ実機での状態確認
root@firefly1> show bgp neighbor
Peer: 192.168.35.2+179 AS 65002 Local: 192.168.35.1 AS 65001
  Type: External    State: Connect        Flags: <ImportEval>
  Last State: Connect       Last Event: ConnectRetry
  Last Error: None
  Export: [ advertised_for_firefly2 ]
  Options: <Preference AddressFamily PeerAS Refresh>
  Address families configured: inet-unicast
  Holdtime: 90 Preference: 170
  Number of flaps: 0

IOSXRの場合

get_info.py
#! /usr/bin/env python
# -*- coding: utf-8 -*-

import napalm
from pprint import pprint

driver = napalm.get_network_driver('iosxr')
device = driver(
    hostname='127.0.0.1',
    username='vagrant',
    password='vagrant',
    # IOSXRv default ssh port
    optional_args={'port': 2223}) 

print 'Open session: ',
device.open()
print 'OK'

print 'get BGP neighbors'
pprint(device.get_bgp_neighbors())

print 'Close session: ',
device.close()
print 'OK'
実行結果
% python get_info.py

Open session:  OK
get BGP neighbors
{u'global': {u'peers': {u'192.168.0.1': {u'address_family': {u'ipv4': {u'accepted_prefixes': 0,
                                                                       u'received_prefixes': 0,
                                                                       u'sent_prefixes': 0}},
                                         u'description': u'test_AS65002',
                                         u'is_enabled': False,
                                         u'is_up': False,
                                         u'local_as': 65001,
                                         u'remote_as': 65002,
                                         u'remote_id': u'0.0.0.0',
                                         u'uptime': -1}},
             u'router_id': u'0.0.0.0'}}
Close session:  OK
ルータ実機での状態確認
RP/0/RP0/CPU0:ios#show bgp neighbor
Fri Dec 23 13:14:16.879 UTC

BGP neighbor is 192.168.0.1
 Remote AS 65002, local AS 65001, external link
 Description: test_AS65002
 Remote router ID 0.0.0.0
  BGP state = Idle (eBGP neighbor not directly connected)
  NSR State: None
  Last read 00:00:00, Last read before reset 00:00:00
  Hold time is 180, keepalive interval is 60 seconds
  Configured hold time: 90, keepalive: 30, min acceptable hold time: 3
(snip)

NAPALM利用例: ルータのコンフィグ設定

以前のブログルータ制御APIライブラリ NAPALMを触ってみたで紹介したものと同じ手順でコンフィグ設定変更することができます。

JUNOSの場合 (失敗)

change_hostname_JUNOS.conf
system {
    host-name firefly1_changed_by_NAPALM;
}
set_hostname.py
#! /usr/bin/env python
# -*- coding: utf-8 -*-

import napalm

# For JUNOS firefly1
driver = napalm.get_network_driver('junos')
device = driver(
    hostname='192.168.34.16',
    username='user1',
    password='password1')

print 'Open session: ',
device.open()
print 'OK'

print 'get hostname : ', 
print device.get_facts()[u'hostname']

print 'Config load (merge mode): ',
device.load_merge_candidate(filename='./change_hostname_JUNOS.conf')
print 'OK'

print 'Compare config: '
print device.compare_config()

print 'Do you commit? y/n'
choice = raw_input().lower()
if choice == 'y':
    print 'Commit config:',
    device.commit_config()
    print 'OK'
else:
    print 'Discard config:',
    device.discard_config()
    print 'OK'

print 'Close session: ',
device.close()
print 'OK'
実行結果
% python set_hostname.py                                                                                                                                                                     (git)-[master]
Open session:  OK
get hostname :  firefly1
Config load (merge mode):  OK
Compare config:
[edit system]
-  host-name firefly1;
+  host-name firefly1_changed_by_NAPALM;
Do you commit? y/n
y
Commit config: OK
get hostname :
Traceback (most recent call last):
  File "set_hostname.py", line 41, in <module>
    print device.get_facts()[u'hostname']
  File "/usr/local/lib/python2.7/site-packages/napalm_junos/junos.py", line 182, in get_facts
    interfaces.get()
  File "/Library/Python/2.7/site-packages/jnpr/junos/factory/optable.py", line 64, in get
    self.xml = getattr(self.RPC, self.GET_RPC)(**rpc_args)
  File "/Library/Python/2.7/site-packages/jnpr/junos/rpcmeta.py", line 156, in _exec_rpc
    return self._junos.execute(rpc, **dec_args)
  File "/Library/Python/2.7/site-packages/jnpr/junos/decorators.py", line 58, in wrapper
    result = function(*args, **kwargs)
  File "/Library/Python/2.7/site-packages/jnpr/junos/decorators.py", line 26, in wrapper
    return function(*args, **kwargs)
  File "/Library/Python/2.7/site-packages/jnpr/junos/device.py", line 520, in execute
    raise EzErrors.RpcTimeoutError(self, rpc_cmd_e.tag, self.timeout)
jnpr.junos.exception.RpcTimeoutError: RpcTimeoutError(host: 192.168.34.16, cmd: get-interface-information, timeout: 60)

エラーが出てしまいました。。
しかしルータ実機では、問題なく変更が変更されたように見えます。

ルータ実機での状態確認
root@firefly1
(プログラム実行)

root@firefly1_changed_by_NAPALM>

root@firefly1_changed_by_NAPALM> show version
Hostname: firefly1_changed_by_NAPALM
Model: firefly-perimeter
JUNOS Software Release [12.1X47-D20.7]

[edit]
root@firefly1_changed_by_NAPALM# rollback ?
Possible completions:
  <[Enter]>            Execute this command
  0                    2016-12-24 18:55:57 JST by user1 via netconf
  1                    2016-12-24 18:32:08 JST by root via cli
  2                    2016-12-24 15:20:22 JST by root via cli

試しに条件を変えて、commit直後の「device.get_facts()[u'hostname']」を無くして試してみると、また別のエラーがでました。

実行結果
% python set_hostname.py                                                                                                                                                                     (git)-[master]
Open session:  OK
get hostname :  firefly1
Config load (merge mode):  OK
Compare config:
[edit system]
-  host-name firefly1;
+  host-name firefly1_changed_by_NAPALM;
Do you commit? y/n
y
Commit config: OK
Close session:
Traceback (most recent call last):
  File "set_hostname.py", line 44, in <module>
    device.close()
  File "/usr/local/lib/python2.7/site-packages/napalm_junos/junos.py", line 94, in close
    self._unlock()
  File "/usr/local/lib/python2.7/site-packages/napalm_junos/junos.py", line 106, in _unlock
    self.device.cu.unlock()
  File "/Library/Python/2.7/site-packages/jnpr/junos/utils/config.py", line 489, in unlock
    raise UnlockError(rsp=err.rsp)
jnpr.junos.exception.UnlockError: <exception str() failed>

時間がなかったのでトラブルシュートがやりきれてませんが、JUNOSのUnlock周りで動作がおかしくなっているのかもしれません。原因が分かり次第追記しようと思います。

IOSXRの場合

change_hostname_IOSXR.conf
hostname iosxrv1_changed_by_NAPALM
set_hostname.py
#! /usr/bin/env python
# -*- coding: utf-8 -*-

import napalm

# For Cisco IOSXRv
driver = napalm.get_network_driver('iosxr')
device = driver(
    hostname='127.0.0.1',
    username='vagrant',
    password='vagrant',
    # IOSXRv default ssh port
    optional_args={'port': 2223}) 

print 'Open session: ',
device.open()
print 'OK'

print 'get hostname : ', 
print device.get_facts()[u'hostname']

print 'Config load (merge mode): ',
device.load_merge_candidate(filename='./change_hostname_IOSXR.conf')
print 'OK'

print 'Compare config: '
print device.compare_config()

print 'Do you commit? y/n'
choice = raw_input().lower()
if choice == 'y':
    print 'Commit config:',
    device.commit_config()
    print 'OK'
else:
    print 'Discard config:',
    device.discard_config()
    print 'OK'

print 'get hostname : ', 
print device.get_facts()[u'hostname']

print 'Close session: ',
device.close()
print 'OK'
実行結果
% python set_hostname.py                                                                                                                                                                     (git)-[master]
Open session:  OK
get hostname :  ios
Config load (merge mode):  OK
Compare config:
---
+++
@@ -1,5 +1,6 @@
 !! Last configuration change at Fri Dec 23 13:20:24 2016 by vagrant
 !
+hostname iosxrv1_changed_by_NAPALM
 telnet vrf default ipv4 server max-servers 10
 username vagrant
  group root-lr
Do you commit? y/n
y
Commit config: OK
get hostname :  iosxrv1_changed_by_NAPALM
Close session:  OK
ルータ実機での状態確認
RP/0/RP0/CPU0:ios#
(プログラム実行)

RP/0/RP0/CPU0:iosxrv1_changed_by_NAPALM#

RP/0/RP0/CPU0:iosxrv1_changed_by_NAPALM#sh running-config hostname
Fri Dec 23 13:21:32.129 UTC
hostname iosxrv1_changed_by_NAPALM

IOSXRはうまく設定変更することができました!

まとめ

今回は、NAPALMを使うことで、JUNOSルータおよびIOSXRルータの環境で情報取得、ルータ設定が実施できることを確認しました。一部で上手くいなかないところもありましたが、今後改善されるかもしれません。問題の原因が分かったら追記させていただきます。

NAPALMでは既に非常に多くの機能が実装がされており、NAPALMを利用することでマルチベンダ環境における自動化ソフトウェア開発工数を大幅に削減できそうです。

またNAPALMのコミュニティによる開発が非常に積極的に行われていることから、今後さらなる機能拡充や対応機種の増加が期待できます。

現時点で足りなそうな機能としては、Brocade製品やOSPF関連,MPLS関連の機能くらいでしょうか。もう少し触ってみると、いろいろ足りない面についても気付くことになるかもしれません。

今回試したプログラムをGithubで公開していますので、もしよろしければご参考にしてください。(汚いコードで恐縮ですが。。)
https://github.com/taijiji/sample_nalapm

最後に

今回はNAPALMの機能紹介だけで終わってしまいましたが、今後時間が取れれば、過去ブログ「PyEZとJSNAPyを使ってみた。第四部: PyEZとJSNAPyでISP設定作業を自動化する編」で作った自動化ツールのマルチベンダ対応版(とりあえずはJUNOSとIOSXR)の開発にも挑戦してみようと思います。

もしNAPALMを利用して自動化ツールの開発に挑戦された方がいらっしゃれば、ぜひ今後のNetOpsCodingで紹介していただけるとうれしいです。

ではよいクリスマスを。

この投稿は NetOpsCoding Advent Calendar 201624日目の記事です。