Cisco DNA CenterをAnsibleから操作する(uriモジュール編)

# はじめに
「Cisco DNA Center」とは、Ciscoが提供するDNA（デジタルネットワークアーキテクチャ）のフロントエンドとして、管理者が利用する機能やツールを集約したダッシュボードです。
拠点からクラウドまで、あらゆるネットワークをエンドツーエンドで管理でき、以下を迅速かつ直感的に行うことができます。

- ネットワークのデザイン、プロビジョニング
- ネットワークのポリシー設計と適用
- ネットワークのアシュアランス（パフォーマンスの最適化、障害の予兆検知等）

※DNAの概要は[Cisco Digital Network Architecture 入門](https://www.cisco.com/c/dam/global/ja_jp/products/catalog/pdf/DNA_Guide.pdf)を参照

WebブラウザからのGUI操作だけでなく、ノースバウンドAPIとしてREST APIを提供しており、Postman、Python、Ansible等からDNA Center経由でネットワーク情報の取得、設定追加・変更・削除を行うことができます。
PostmanやPythonによる操作は、Cisco DevNet Learning Labsの[Cisco DNA Center のプログラマビリティの紹介](https://developer.cisco.com/learning/modules/dnav3-intro-dnac-jp)で分かりやすく解説されています。

今回は、上記の[Cisco DNA Center Northbound API "Hello Network" (Japanese)](https://developer.cisco.com/learning/modules/dnav3-intro-dnac-jp/05-dnac-01-dnac-nbapi-hello-world-jp/step/1)で紹介されている、ネットワーク機器の一覧取得をAnsibleで実施してみたいと思います。

# 用意した環境
- Ansible
CentOSにインストールした2.8.3を使用。

- DNA Center
Cisco DevNetのSandboxに、Always On（常時接続可能）とReserve（要予約）の環境が用意されています。今回は[Cisco DNA Center 1.2.10 (Always-On)](https://devnetsandbox.cisco.com/RM/Diagram/Index/471eb739-323e-4805-b2a6-d0ec813dc8fc?diagramType=Topology)を利用しました。
構成概要は以下の通りです。
![無題0824_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310082/1d5cba44-372d-8df1-c348-053cf7e3a9bb.png)


# Inventory
Ansibleのターゲットノードは、NW機器ではなくDNA Centerです。そのため、`ansible_host`、`ansible_port`、`username`、`password`はいずれもDNA Centerのものを指定します。

```text:inventory_dnac1
[cisco]
DNA_Center ansible_host=sandboxdnac2.cisco.com ansible_port=443

[cisco:vars]
username=[DNA Centerのユーザ名]
password=[DNA Centerのパスワード]
```

# Playbook
大まかな流れは以下の通りです。

1. 認証トークンの要求 
HTTPS POSTメソッドを使用して、以降のAPIコールで使う認証トークンを取得します。
Ansibleのモジュールとしては、HTTP/HTTPSアクセスで汎用的に使われるuriモジュールを使用しています。初回はBasic認証を使ってDNA Centerへログインするのですが、`force_basic_auth`がデフォルト値の`no`だとログインに失敗するため、`yes`にしています。また、今回はテスト用の簡易環境のため、`validate_certs`を`no`とし、SSL証明書のバリデーションを無効化しています。

2. 認証トークンの抽出 
1.のレスポンスメッセージから認証トークンを抽出し、変数`auth_token`に格納します。

3. 認証トークンの表示

4. ネットワークデバイス一覧の取得
HTTPS GETメソッドを使用して、DNA Centerが管理するネットワークデバイスの一覧を取得します。URLの末尾を`～/network-device`とする事でデバイス情報を取得可能です。認証トークンは先ほど取得した`auth_token`を使います。

5. デバイス一覧から必要な値を抽出
Cisco DevNet Learning Labsと表示結果を揃えるため、各デバイスの情報の中から`hostname`（ホスト名）、`managementIpAddress`（管理IPアドレス）、 `platformId`（型番）、`softwareVersion`（ソフトウェアバージョン）、`role`（ロール）、`upTime`（起動時間）を抽出しています。

6. デバイス一覧の表示

```yaml:playbook_dnac_device_list2.yml
---

- hosts: cisco
  gather_facts: no
  connection: local

  vars:
    device_list_extracted: [['hostname', 'managementIpAddress', 'platformId', 'softwareVersion', 'role', 'upTime']]

  tasks:
    - name: Get authencation token   # (1)
      uri:
        url: https://{{ansible_host}}:{{ansible_port}}/api/system/v1/auth/token
        method: POST
        url_username: "{{ username }}"
        url_password: "{{ password }}"
        force_basic_auth: yes
        validate_certs: no
      register: result

    - name: Extract authentication token   # (2)
      set_fact:
        auth_token: "{{ result.json['Token'] }}"

    - name: Display authentication token   # (3)
      debug:
        msg: "{{ auth_token }}"

    - name: Get network device list   # (4)
      uri:
        url: https://{{ansible_host}}:{{ansible_port}}/api/v1/network-device
        method: GET
        validate_certs: no
        headers:
          X-auth-token: "{{ auth_token }}"
      register: device_list

    - name: Extract necessary data from the device list   # (5)
      set_fact:
        device_list_extracted: "{{ device_list_extracted + [[item.hostname, item.managementIpAddress, 
                                   item.platformId, item.softwareVersion, item.role, item.upTime]] }}"
      loop: "{{ device_list.json['response'] | flatten(levels=1) }}"

    - name: Display extracted device list   # (6)
      debug:
        msg: "{{ device_list_extracted }}"
```

# 出力結果
Display extracted device listで、デバイス情報が取得できている事が分かります。

```yaml:
$ ansible-playbook -i inventory_dnac1 playbook_dnac_device_list2.yml

PLAY [cisco] ****************************************************************************************************************************************

TASK [Get authencation token] ***********************************************************************************************************************
ok: [DNA_Center]

TASK [Extract authentication token] *****************************************************************************************************************
ok: [DNA_Center]

TASK [Display authentication token] *****************************************************************************************************************
ok: [DNA_Center] => {
    "msg": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.（省略）"
}

TASK [Get network device list] **********************************************************************************************************************
ok: [DNA_Center]

TASK [Extract necessary data from the device list] **************************************************************************************************
ok: [DNA_Center] => (item={'location': None, 'type': 'Cisco 3504 Wireless LAN Controller', 'errorCode': 'ERROR-
CONNECTION-CLOSED', 'family': 'Wireless Controller', 'role': 'ACCESS', 'lastUpdateTime': 1566658953979, (省略)

TASK [Display extracted device list] ****************************************************************************************************************
ok: [DNA_Center] => {
    "msg": [
        [
            "hostname",
            "managementIpAddress",
            "platformId",
            "softwareVersion",
            "role",
            "upTime"
        ],
        [
            "3504_WLC",
            "10.10.20.51",
            "AIR-CT3504-K9",
            "8.5.140.0",
            "ACCESS",
            "159 days, 5:29:33.00"
        ],
        [
            "leaf1.labb.local",
            "10.10.20.81",
            "C9300-48U",
            "16.6.4a",
            "ACCESS",
            "126 days, 22:15:30.99"
        ],
        [
            "leaf2.labb.local",
            "10.10.20.82",
            "C9300-48U",
            "16.6.4a",
            "ACCESS",
            "126 days, 4:48:27.30"
        ],
        [
            "spine1.abc.in.labb.local",
            "10.10.20.80",
            "WS-C3850-24P-L",
            "16.3.5b",
            "DISTRIBUTION",
            "159 days, 6:00:01.52"
        ],
（省略）
    ]
}

PLAY RECAP ******************************************************************************************************************************************
DNA_Center                 : ok=6    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
```

# 最後に
今回はuriモジュールを使用しましたが、World Wide TechnologyからDNA Center用のサードパーティーモジュールがリリースされています。こちらを触ってみた結果も別記事でご紹介したいと思います。
[GitHub - jandiorio/ansible-dnac-modules](https://github.com/jandiorio/ansible-dnac-modules)