AnsibleでNW機器の情報取得してみた(IOSXR)

この記事は[Ansible Advent Calendar 2019](https://qiita.com/advent-calendar/2019/ansible)の7日目の記事です。

# やってみた
今回はAnsibleを使ってCiscoIOSXRの情報取得を行いたいと思います。

## 準備
AnsibleでNW機器へ接続する前にまずは以下の3点を確認します。
これはNW機器でなくても同じことだと思います。

1.   機器は何か？
2.   どんなモジュールで実装するか？
3.   接続方式は何か？

|接続方式            |モジュール | やりたいこと|
|:------------------|:-------------|:------------------|
|netconf            | netconf_get|show runを取る    |
|network_cli        | iosxr_command|show runを取る    |
|network_cli        | iosxr_command|ルートがあるか調べる     |


## 環境説明
ansible == 2.8.7
python  == 3.7.5

## 接続先
今回はCiscoDevnetのSandBoxにあるIOS XR Programmabilityを利用します
DevNetの環境があればだれでも使うことが可能です。

共有と占有のものがあり、今回は共有のもの利用します(なのでちょっと反応が遅い)

※占有のものを利用するには、予約が必要です、こちらもアカウントがあれば簡単にできます
※占有のものを利用する際はCiscoAnyConnectも必要になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246478/94d316a9-7cbe-b080-41d7-444297912024.png)


# show runを取る
それではさっそく、show runを取得してみましょう。
今回は２通りの方法で挑戦してみました。
## inventoryの内容説明

```iosxr.ini
[iosxr]
sbx-iosxr-mgmt.cisco.com

```

group_varsの設定は以下、
今回は接続しない方式のものはコメントアウトして書き換えてます(楽だったので)

```iosxr.yml
#ansible_connection: network_cli
ansible_connection: netconf
ansible_network_os: iosxr
#ansible_port: 8181
ansble_port: 10000
ansible_user: admin
ansible_password: C1sco12345
ansible_python_interpreter: "/usr/bin/python"

```

## 作成したPlaybook

メインのPlaybookはこんなシンプルに
ansibleのversionが2.8なのでgather_factsはnoで

```test.yml
---
- hosts: all
  gather_facts: no

  roles:
    - send_command

```

roleの中身はこんな感じ
Ansible_connectionとAnsible_network_osで対象のTaskを選択

```roles/send_command/tasks/send_command.yml
---
- name: iosxr_commandモジュールテスト(show_command)
  iosxr_command:
    commands: "{{ item }}"
  register: cli_result
  loop: "{{ show_commands }}"
  when: 
    - ansible_connection == "network_cli"
    - ansible_network_os == "iosxr"

- name: Network_cliでConfig取得
  debug:
    msg: "{{ item.stdout }}"
  loop: "{{ cli_result.results }}"
  loop_control:
    label: "commands"
  when:
    - ansible_connection == "network_cli"
    - ansible_network_os == "iosxr"


- name: NetconfでConfigを取得
  netconf_get:
    source: running
  register: netconf_result
  when: ansible_connection == "netconf"

- name: 表示
  debug:
    msg: "{{ netconf_result.stdout }}"
  when: ansible_connection == "netconf"

- name: nxos_commandモジュールテスト(show_command)
  nxos_command:
    commands: "{{ item }}"
  register: cli_result
  loop: "{{ show_commands }}"
  when: 
    - ansible_connection == "network_cli"
    - ansible_network_os == "nxos"


- name: commad実行結果表示
  debug:
    msg: "{{ item.stdout }}"
  loop: "{{ cli_result.results }}"
  loop_control:
    label: "commands"
  when:
    - ansible_connection == "network_cli"
    - ansible_network_os == "nxos"

```

## netconfで取ってみる

まずはncclientをpipでインストールする必要があります。

```
pip install ncclient
```

実行結果

```
22:32:36 IOS_Autamation $ansible-playbook -i inventory/iosxr.ini test.yml

PLAY [all] *************************************************************************************************************************************************************************************************

TASK [send_command : iosxr_commandモジュールテスト(show_command)] **************************************************************************************************************************************************
skipping: [sbx-iosxr-mgmt.cisco.com] => (item=show clock)
skipping: [sbx-iosxr-mgmt.cisco.com] => (item=show version)
skipping: [sbx-iosxr-mgmt.cisco.com] => (item=show run)
skipping: [sbx-iosxr-mgmt.cisco.com] => (item=show ip route)

TASK [send_command : Network_cliでConfig取得] *****************************************************************************************************************************************************************
skipping: [sbx-iosxr-mgmt.cisco.com] => (item=commands)
skipping: [sbx-iosxr-mgmt.cisco.com] => (item=commands)
skipping: [sbx-iosxr-mgmt.cisco.com] => (item=commands)
skipping: [sbx-iosxr-mgmt.cisco.com] => (item=commands)
skipping: [sbx-iosxr-mgmt.cisco.com]

TASK [send_command : NetconfでConfigを取得] ********************************************************************************************************************************************************************
ok: [sbx-iosxr-mgmt.cisco.com]

TASK [send_command : 表示] ***********************************************************************************************************************************************************************************
ok: [sbx-iosxr-mgmt.cisco.com] =>
  msg: |-
    <ns0:data xmlns:ns0="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:ns1="http://cisco.com/ns/yang/Cisco-IOS-XR-man-xml-ttyagent-cfg" 
...snip
      <ns1:xr-xml>
       <ns1:agent>
        <ns1:tty>
         <ns1:enable />
         <ns1:iteration-size>0</ns1:iteration-size>
        </ns1:tty>
       </ns1:agent>
      </ns1:xr-xml>
      <ns1:netconf>
       <ns1:agent>
        <ns1:tty>
         <ns1:enable />
        </ns1:tty>
       </ns1:agent>
      </ns1:netconf>
      <ns2:tpa>
       <ns2:vrf-names>
        <ns2:vrf-name>
         <ns2:vrf-name>default</ns2:vrf-name>
         <ns2:address-family>
          <ns2:ipv4>
           <ns2:default-route>mgmt</ns2:default-route>
          </ns2:ipv4>
         </ns2:address-family>
        </ns2:vrf-name>
       </ns2:vrf-names>
      </ns2:tpa>
      <ns3:router-static>
       <ns3:default-vrf>
        <ns3:address-family>
         <ns3:vrfipv4>
          <ns3:vrf-unicast>
           <ns3:vrf-prefixes>
            <ns3:vrf-prefix>
             <ns3:prefix>0.0.0.0</ns3:prefix>
             <ns3:prefix-length>0</ns3:prefix-length>
             <ns3:vrf-route>
              <ns3:vrf-next-hop-table>
               <ns3:vrf-next-hop-next-hop-address>
                <ns3:next-hop-address>10.10.20.254</ns3:next-hop-address>
               </ns3:vrf-next-hop-next-hop-address>
              </ns3:vrf-next-hop-table>
             </ns3:vrf-route>
            </ns3:vrf-prefix>
           </ns3:vrf-prefixes>
          </ns3:vrf-unicast>
         </ns3:vrfipv4>
        </ns3:address-family>
       </ns3:default-vrf>
      </ns3:router-static>
....snip
      <ns30:lldp>
       <ns30:config>
        <ns30:enabled>false</ns30:enabled>
       </ns30:config>
      </ns30:lldp>
     </ns0:data>

TASK [send_command : nxos_commandモジュールテスト(show_command)] ***************************************************************************************************************************************************
skipping: [sbx-iosxr-mgmt.cisco.com] => (item=show clock)
skipping: [sbx-iosxr-mgmt.cisco.com] => (item=show version)
skipping: [sbx-iosxr-mgmt.cisco.com] => (item=show run)
skipping: [sbx-iosxr-mgmt.cisco.com] => (item=show ip route)

TASK [send_command : commad実行結果表示] *************************************************************************************************************************************************************************
skipping: [sbx-iosxr-mgmt.cisco.com] => (item=commands)
skipping: [sbx-iosxr-mgmt.cisco.com] => (item=commands)
skipping: [sbx-iosxr-mgmt.cisco.com] => (item=commands)
skipping: [sbx-iosxr-mgmt.cisco.com] => (item=commands)
skipping: [sbx-iosxr-mgmt.cisco.com]

PLAY RECAP *************************************************************************************************************************************************************************************************
sbx-iosxr-mgmt.cisco.com   : ok=2    changed=0    unreachable=0    failed=0    skipped=4    rescued=0    ignored=0

22:35:07 IOS_Autamation $

```

無事取れました、マルチベンダでまだ試してはいませんが、思ったよりわかりやすいといった印象。
ACIとかPaloでXML見る機会が多かったかな？

駆動型のネットワーク（こうしたいと定義した通りになる）を実現するために、Netconfは今後少しずつ広がりを見せていくのではないでしょうか。
今回はneutron_*モジュールを利用しましたが、各ベンダモジュール（ios_*,junos_*)も物によってはnetconfに対応しているようです。

## network_cliで取ってみる
今度はnetwork_cliで情報取得をしてみましょう
role/group_varsには下記のmain.ymlが格納されており、send_commandのroleが呼び出されます。
※おまけで色々コマンド取ってます

```main.yml
---
show_commands:
  - show clock
  - show version
  - show runnnig-config
  - show ip route
```


始める前にgroup_varsをちょちょっと書き換えます。

```iosxr.yml
---
ansible_connection: network_cli
#ansible_connection: netconf
ansible_network_os: iosxr
ansible_port: 8181
#ansible_port: 10000
ansible_user: admin
ansible_password: C1sco12345
ansible_python_interpreter: "/usr/bin/python"
```

実行結果

```
22:32:15 IOS_Autamation $ansible-playbook -i inventory/iosxr.ini test.yml

PLAY [all] *************************************************************************************************************************************************************************************************

TASK [send_command : iosxr_commandモジュールテスト(show_command)] **************************************************************************************************************************************************
ok: [sbx-iosxr-mgmt.cisco.com] => (item=show clock)
ok: [sbx-iosxr-mgmt.cisco.com] => (item=show version)
ok: [sbx-iosxr-mgmt.cisco.com] => (item=show run)
ok: [sbx-iosxr-mgmt.cisco.com] => (item=show ip route)

TASK [send_command : Network_cliでConfig取得] *****************************************************************************************************************************************************************
ok: [sbx-iosxr-mgmt.cisco.com] => (item=commands) =>
  msg:
  - 15:03:08.279 UTC Fri Dec 6 2019
ok: [sbx-iosxr-mgmt.cisco.com] => (item=commands) =>
  msg:
  - |-
    Cisco IOS XR Software, Version 6.5.3
    Copyright (c) 2013-2019 by Cisco Systems, Inc.

    Build Information:
     Built By     : ahoang
     Built On     : Tue Mar 26 06:52:25 PDT 2019
     Built Host   : iox-ucs-019
     Workspace    : /auto/srcarchive13/prod/6.5.3/xrv9k/ws
     Version      : 6.5.3
     Location     : /opt/cisco/XR/packages/

    cisco IOS-XRv 9000 () processor
    System uptime is 4 days 8 hours 37 minutes
ok: [sbx-iosxr-mgmt.cisco.com] => (item=commands) =>
  msg:
  - |-
    Building configuration...
    !! IOS XR Configuration version = 6.5.3
    !! Last configuration change at Fri Dec  6 15:01:19 2019 by admin
    !
    hostname iosxr1
    domain name abc.inc
    username admin
     group root-lr
     group cisco-support
     secret 5 $1$oN8e$ft916PCBogrqPKt59kepW0
    !
    tpa
     vrf default
      address-family ipv4
       default-route mgmt
      !
     !
    !
    line console
     exec-timeout 0 0
     absolute-timeout 0
     session-timeout 0
    !
    line default
     exec-timeout 0 0
     absolute-timeout 0
     session-timeout 0
     transport input ssh
    !
    call-home
     service active
     contact smart-licensing
     profile CiscoTAC-1
      active
      destination transport-method http
     !
    !
    interface Loopback100
     description ***MERGE LOOPBACK 100****
     ipv4 address 1.1.1.100 255.255.255.255
    !
    interface Loopback200
     description ***MERGE LOOPBACK 200****
     ipv4 address 1.1.1.200 255.255.255.255
    !
    interface MgmtEth0/RP0/CPU0/0
     ipv4 address 10.10.20.175 255.255.255.0
    !
    interface GigabitEthernet0/0/0/0
     shutdown
    !
    interface GigabitEthernet0/0/0/1
     shutdown
    !
    interface GigabitEthernet0/0/0/2
     shutdown
    !
    interface GigabitEthernet0/0/0/3
     shutdown
    !
    interface GigabitEthernet0/0/0/4
     shutdown
    !
    interface GigabitEthernet0/0/0/5
     shutdown
    !
    interface GigabitEthernet0/0/0/6
     shutdown
    !
    router static
     address-family ipv4 unicast
      0.0.0.0/0 10.10.20.254
     !
    !
    xml agent tty
     iteration off
    !
    netconf agent tty
    !
    netconf-yang agent
     ssh
    !
    ssh server v2
    ssh server vrf default
    ssh server netconf vrf default
    end
ok: [sbx-iosxr-mgmt.cisco.com] => (item=commands) =>
  msg:
  - |-
    Codes: C - connected, S - static, R - RIP, B - BGP, (>) - Diversion path
           D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area
           N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2
           E1 - OSPF external type 1, E2 - OSPF external type 2, E - EGP
           i - ISIS, L1 - IS-IS level-1, L2 - IS-IS level-2
           ia - IS-IS inter area, su - IS-IS summary null, * - candidate default
           U - per-user static route, o - ODR, L - local, G  - DAGR, l - LISP
           A - access/subscriber, a - Application route
           M - mobile route, r - RPL, t - Traffic Engineering, (!) - FRR Backup path

    Gateway of last resort is 10.10.20.254 to network 0.0.0.0

    S*   0.0.0.0/0 [1/0] via 10.10.20.254, 4d08h
    L    1.1.1.100/32 is directly connected, 4d08h, Loopback100
    L    1.1.1.200/32 is directly connected, 4d08h, Loopback200
    C    10.10.20.0/24 is directly connected, 4d08h, MgmtEth0/RP0/CPU0/0
    L    10.10.20.175/32 is directly connected, 4d08h, MgmtEth0/RP0/CPU0/0

TASK [send_command : NetconfでConfigを取得] ********************************************************************************************************************************************************************
skipping: [sbx-iosxr-mgmt.cisco.com]

TASK [send_command : 表示] ***********************************************************************************************************************************************************************************
skipping: [sbx-iosxr-mgmt.cisco.com]

TASK [send_command : nxos_commandモジュールテスト(show_command)] ***************************************************************************************************************************************************
skipping: [sbx-iosxr-mgmt.cisco.com] => (item=show clock)
skipping: [sbx-iosxr-mgmt.cisco.com] => (item=show version)
skipping: [sbx-iosxr-mgmt.cisco.com] => (item=show run)
skipping: [sbx-iosxr-mgmt.cisco.com] => (item=show ip route)

TASK [send_command : commad実行結果表示] *************************************************************************************************************************************************************************
skipping: [sbx-iosxr-mgmt.cisco.com] => (item=commands)
skipping: [sbx-iosxr-mgmt.cisco.com] => (item=commands)
skipping: [sbx-iosxr-mgmt.cisco.com] => (item=commands)
skipping: [sbx-iosxr-mgmt.cisco.com] => (item=commands)
skipping: [sbx-iosxr-mgmt.cisco.com]

PLAY RECAP *************************************************************************************************************************************************************************************************
sbx-iosxr-mgmt.cisco.com   : ok=2    changed=0    unreachable=0    failed=0    skipped=4    rescued=0    ignored=0

22:32:36 IOS_Autamation $

```

こちらはよくみた形式ですね。
非常に親しみやすい表示で安心します
取得くらいなら、簡単に実装できることがお分かり頂けたかと思います。

# ルートを調べる
pyATS/genieのroleをansible-galaxyからinstallし、show ip routeをgenieにparse
assertモジュールを使って判定をさせてます。

roleをinstall

```
ansible-galaxy install clay584.parse_genie
```

pyatsとgenieをinstall

```
pip install pays
pip install genie

or

pip install pays[full]
```

実装したPlaybook

```genietest.yml
---
- hosts: iosxr
  gather_facts: false
  connection: network_cli
  vars:
    command: show route ipv4   

  tasks:
    - name: Genieのroleをinclude
      include_role:
        name: clay584.parse_genie

    - name: Route取得
      iosxr_command:
        commands: "{{ command }}"
      register: response1

    - name: Genieにパースして変数にする(set_fact)
      set_fact:
        result1: "{{ response1['stdout'][0] | parse_genie(command='show route ipv4',os='iosxr') }}"

    - name: コンフィグ確認
      assert:
        that: 
          - '"0.0.0.0/0" in result1.vrf.default.address_family.ipv4.routes'
        success_msg: 
          - 'ルーティングテーブルに0.0.0.0/0が含まれているね'
```

実行結果(成功)

```
23:10:12 IOS_Autamation $ansible-playbook -i inventory/iosxr.ini genie_test.yml

PLAY [iosxr] ***********************************************************************************************************************************************************************************************

TASK [Genieのroleをinclude] **********************************************************************************************************************************************************************************

TASK [Route取得] *********************************************************************************************************************************************************************************************
ok: [sbx-iosxr-mgmt.cisco.com]

TASK [Genieにパースして変数にする(set_fact)] **************************************************************************************************************************************************************************
ok: [sbx-iosxr-mgmt.cisco.com]

TASK [コンフィグ確認] *********************************************************************************************************************************************************************************************
ok: [sbx-iosxr-mgmt.cisco.com] => changed=false
  msg:
  - ルーティングテーブルに0.0.0.0/0が含まれているね

PLAY RECAP *************************************************************************************************************************************************************************************************
sbx-iosxr-mgmt.cisco.com   : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

23:10:30 IOS_Autamation $
```

genieにパースすることでshow ~~コマンドを辞書形式に変換してくれます。
これにより、判定させることが容易になります。


# まとめ
### 簡単に動かせる
動かすだけなら簡単でした、今回初めて接続方式にnetconfを利用してみましたが、
これは使いこなせれば非常に便利そうです。
ベンダによらず情報取得/設定/削除ができ、Playbookもキレイにできそうだなぁ
ですが、データ構造体YANGやXMLの理解が必要になるのかとふんわり思ってます(ここが難しいそう)

ansibleのversionも2.8を利用しましたが、2.9からはgather_factで情報の取得が構成情報の取得が可能となり
さらに活用の幅も広がりそうです。

今回の内容はわりと入門だったと思います、ぜひ皆さんもAnsible触っていきましょう！