てくなべ (tekunabe)

ansible / network / automation

[nornir] NETCONF で Cisco IOS XE 機器のコンフィグや状態の情報を取得する

はじめに

Python 製自動化フレームワーク nornir は、接続方法として netmikoや、NAPALM の他に、NETCONF にも対応しています。

この記事では NETCONF 経由で、Cisco IOS XE 機器の runnin-config、インターフェース、ルーティングテーブを取得するサンプルをご紹介します。

環境


■ 準備

Python スクリプト本体を作成する前の準備として、以下の2つのファイルを作成します。

config.yml    # 設定ファイル(インベントリの形式、ファイルのパスなどを定義)
inventory/
  hosts.yaml  # インベントリファイル(対象機器の情報を定義)

インベントリ

対象機器の情報を定義するインベントリファイルを作成します。

インベントリに利用できる形式は、Ansible や NetBox などがあるようですが、ここでは一番シンプルなインベントリである Simple を利用します。

  • inventory/hosts.yaml
---
ios01:
  hostname: ios-xe-mgmt-latest.cisco.com
  port: 10000       # netconf 利用時のデフォルトは 830
  username: developer
  password: dummy
  platform: ios     # netconf の場合はなんでも良い模様
  connection_options:
    netconf:
        extras:
            allow_agent: False
            hostkey_verify: False

コンフィグファイル

インベントリの形式やファイルのパスを指定します。

  • config.yaml
---
inventory:
  plugin: nornir.plugins.inventory.simple.SimpleInventory
  options:
      host_file: "inventory/hosts.yaml"  # ホストインベントリファイルのパス


■ サンプル1: running-config の取得と表示

netconf_get_config を利用して、running-confg を取得します。

コード

from nornir import InitNornir
from nornir.plugins.tasks.networking import netconf_get_config
from xml.dom import minidom   # 表示用

nr = InitNornir(config_file="config.yaml")  # 初期化
result = nr.run(task=netconf_get_config, source="running")  # source は running

result_xml = minidom.parseString(result["ios01"].result)
print(result_xml.toprettyxml())

実行結果

$ python get_confg.py 
<?xml version="1.0" ?>
<data xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">
        <native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
                <version>16.11</version>
                <!-- 略 -->
                <hostname>csr1000v-1</hostname>
                <!-- 略 -->
                <interface>
                        <GigabitEthernet>
                                <name>1</name>
                                <description>MANAGEMENT INTERFACE - DON'T TOUCH ME</description>
                                <ip>
                                        <address>
                                                <primary>
                                                        <address>10.10.20.48</address>
                                                        <mask>255.255.255.0</mask>
                                                </primary>
                                        </address>
                                </ip>
                                <mop>
                                        <enabled>false</enabled>
                                        <sysid>false</sysid>
                                </mop>
                                <negotiation xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-ethernet">
                                        <auto>true</auto>
                                </negotiation>
                        </GigabitEthernet>
                <!-- 略 -->
                </interface>
                <!-- 略 -->
</data>

$ 


■ サンプル2: インターフェース情報の取得と表示

netconf_get を利用して、インターフェース情報を取得します。

コード

from nornir import InitNornir
from nornir.plugins.tasks.networking import netconf_get  # netconf_get_config ではなく
from xml.dom import minidom   # 表示用

nr = InitNornir(config_file="config.yaml")  # 初期化
result = nr.run(task=netconf_get, path="/interfaces-state") # ポイント

result_xml = minidom.parseString(result["ios01"].result)
print(result_xml.toprettyxml())

実行結果

$ python get_if_state.py 
<?xml version="1.0" ?>
<data xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">
        <interfaces-state xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
                <interface>
                        <name>GigabitEthernet1</name>
                        <type xmlns:ianaift="urn:ietf:params:xml:ns:yang:iana-if-type">ianaift:ethernetCsmacd</type>
                        <admin-status>up</admin-status>
                        <oper-status>up</oper-status>
                        <last-change>2020-06-12T23:57:32.000772+00:00</last-change>
                        <if-index>1</if-index>
                        <phys-address>00:50:56:bb:e9:9c</phys-address>
                        <speed>1024000000</speed>
                        <statistics>
                                <discontinuity-time>2020-06-12T23:56:13.000936+00:00</discontinuity-time>
                                <in-octets>24546855591</in-octets>
                                <in-unicast-pkts>39304700</in-unicast-pkts>
                                <in-broadcast-pkts>0</in-broadcast-pkts>
                                <in-multicast-pkts>0</in-multicast-pkts>
                                <in-discards>0</in-discards>
                                <in-errors>0</in-errors>
                                <in-unknown-protos>0</in-unknown-protos>
                                <out-octets>172071875</out-octets>
                                <out-unicast-pkts>532219</out-unicast-pkts>
                                <out-broadcast-pkts>0</out-broadcast-pkts>
                                <out-multicast-pkts>0</out-multicast-pkts>
                                <out-discards>0</out-discards>
                                <out-errors>0</out-errors>
                        </statistics>
                </interface>
        <!-- 略 -->
        </interfaces-state>
</data>

$ 

subtree でも指定可能

path は、上記サンプルのような xpath の他にも subtree でも指定できます。

filter_type="subtree" を指定するのがポイントです。デフォルトは xpath です。

# 抜粋
query = """
  <interfaces-state xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
  </interfaces-state>
"""
result = nr.run(task=netconf_get, path=query, filter_type="subtree")


■ サンプル3: ルーティングテーブルの情報の取得と表示

サンプル2と同じく、netconf_get を利用して、ルーティングテーブルの情報を取得します。

違いは path のみです。

コード

from nornir import InitNornir
from nornir.plugins.tasks.networking import netconf_get  # netconf_get_config ではなく
from xml.dom import minidom   # 表示用

nr = InitNornir(config_file="config.yaml")  # 初期化
result = nr.run(task=netconf_get, path="/routing-state")  # サンプル2との違い

result_xml = minidom.parseString(result["ios01"].result)
print(result_xml.toprettyxml())

実行結果

$ python get_rouging.py 
<?xml version="1.0" ?>
<data xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0">
        <routing-state xmlns="urn:ietf:params:xml:ns:yang:ietf-routing">
                <routing-instance>
                        <name>default</name>
                        <type>default-routing-instance</type>
                        <router-id>0.0.0.0</router-id>
                        <routing-protocols>
                                <routing-protocol>
                                        <type>direct</type>
                                        <name>0</name>
                                </routing-protocol>
                                <routing-protocol>
                                        <type>static</type>
                                        <name>0</name>
                                </routing-protocol>
                        </routing-protocols>
                        <ribs>
                                <rib>
                                        <name>ipv4-default</name>
                                        <address-family>ipv4</address-family>
                                        <default-rib>false</default-rib>
                                        <routes>
                                                <route>
                                                        <destination-prefix>0.0.0.0/0</destination-prefix>
                                                        <route-preference>1</route-preference>
                                                        <metric>1</metric>
                                                        <next-hop>
                                                                <outgoing-interface>GigabitEthernet1</outgoing-interface>
                                                                <next-hop-address>10.10.20.254</next-hop-address>
                                                        </next-hop>
        <!-- 略 -->
        </routing-state>
</data>

$ 

subtree 指定する場合は以下のようなかたちです。

# 抜粋
query = """
  <routing-state xmlns="urn:ietf:params:xml:ns:yang:ietf-routing">
  </routing-state>
"""
result = nr.run(task=netconf_get, path=query, filter_type="subtree")


おわりに

nornir で NETCONF 経由で情報取得するサンプルをご紹介しました。

今回はざっくりとした情報を取得しました。XML で返ってくるので、必要な情報を抽出しやすいと思います。

もちろアクセスが頻繁にならずに済むのであれば、あらかじめ path や filter で絞っても良いと思います。

参考

www.youtube.com

tekunabe.hatenablog.jp