Cisco VIRLをAnsibleから操作する(VIRLサーバ操作編)

# はじめに
Cisco VIRL（Virtual Internet Routing Lab）とは、Ciscoや他ベンダの仮想プラットフォームやサーバを、仮想環境内で互いに接続したり、外部の物理環境と接続したりできるシミュレーターです。
具体的には、IOSv、IOSv Layer-2、IOS-XRv、NX-OSv、CSR1000v、ASAvのようなCisco OSを使用可能です。

以前の記事で、virlutilsというCLIのユーティリティを使い、VIRLファイルから環境構築を行いました。
[virlutilsを使ってCisco VIRLをCLIで構築してみた](https://qiita.com/tech_kitara/items/9ae497765abc16e0592c)

VIRLはREST APIも提供しており、HTTP GET/PUT/POST/DELETEで環境の作成、削除、確認が可能です。
API一覧は、UWM（VIRLサーバ）上のメニュ「Documentation > STD API > simengine」で確認可能です。
[VIRL API Details](http://virl.cisco.com/api.docs.php)

今回はAnsibleを使って、シミュレーションの作成から終了までの基本操作を行ってみました。

# 用意した環境
- VIRLサーバ 
Cisco DevNet Sandbox上のVIRLを使用しました。

- Ansible 
ローカル環境のCentOSにインストールした2.8.3を使用しました。Sandbox上のDevBox（Linux）のAnsibleでも問題ないと思います。

# Inventory
`virl_host`、`virl_username`、`virl_password`でVIRLサーバのログイン情報を定義しています。

`virl_file`で、環境構築に必要なトポロジーやConfigが定義されたVIRLファイルのパスを指定します。今回は[GitHub virlfiles](https://github.com/virlfiles)で公開されているvirlfiles/2-ios-router（2台のIOSvが相互接続された構成）を、`topology.virl`というファイル名でローカル環境に格納し、フルパスで指定しています。

`session`で、起動するシミュレーションの名前を指定します。今回は`test01`としました。

```inventory_virl
[cisco]
VIRL virl_host=[VIRLサーバのIPアドレス]

[cisco:vars]
virl_username=[VIRLサーバのユーザ名]
virl_password=[VIRLサーバのパスワード]
virl_file=/home/centos/venv/ansible/topology.virl
session=test01
```

# Playbook①: 起動しているシミュレーションの確認
まず始めに、現在起動中のシミュレーションを確認します。HTTP GETメソッドで、URL`～/list`を送信することで一覧を取得できます。
[以前の記事](https://qiita.com/tech_kitara/items/9ae497765abc16e0592c#%E6%97%A2%E5%AD%98%E3%82%B7%E3%83%9F%E3%83%A5%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E5%81%9C%E6%AD%A2)でご紹介した`virl ls --all`に相当するAPIです。


```yml:playbook_virl_list.yml
---

- hosts: cisco
  gather_facts: no
  connection: local

  tasks:
    - name: Get the list of running simulations
      uri:
        url: "http://{{ virl_host }}:19399/simengine/rest/list"
        user: "{{ virl_username }}"
        password: "{{ virl_password }}"
        method: GET
      register: result_list

    - name: Display running simulations
      debug:
        msg: "{{ result_list.json['simulations'] }}"
```

# 出力結果①: 起動しているシミュレーションの確認
`~jumphost`というシミュレーションが起動しています。デフォルトで作成されるものなので、そのままにして次に移ります。

```yml:
$ ansible-playbook -i inventory_virl playbook_virl_list.yml 

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

TASK [Get the list of running simulations] **********************************************************************************
ok: [VIRL]

TASK [Display running simulations] ******************************************************************************************
ok: [VIRL] => {
    "msg": {
        "~jumphost": {
            "expires": null,
            "launched": "2018-08-02T19:21:08.764190",
            "status": "ACTIVE"
        }
    }
}

PLAY RECAP ******************************************************************************************************************
VIRL                       : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 
```

# Playbook②: シミュレーションの開始
シミュレーション`test01`を開始します。HTTP POSTメソッドを使い、URL`～/launch`の中でパラメータ`session`を`test01`に指定しています。指定しない場合は勝手に名前が付けられます。
また、BodyにVIRLファイルの中身を指定しています。
リクエストに失敗した場合に備え、[GitHub - CiscoDevNet/ansible-virl](https://github.com/CiscoDevNet/ansible-virl)を参考にリトライ処理も加えています。
[以前の記事](https://qiita.com/tech_kitara/items/9ae497765abc16e0592c#%E3%82%B7%E3%83%9F%E3%83%A5%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E9%96%8B%E5%A7%8B)でご紹介した`virl up`に相当するAPIです。

```yml:playbook_virl_launch.yml
---

- hosts: cisco
  gather_facts: no
  connection: local

  tasks:
    - name: Launch the simulation {{ session }}
      uri:
        url: "http://{{ virl_host }}:19399/simengine/rest/launch?session={{ session }}"
        user: "{{ virl_username }}"
        password: "{{ virl_password }}"
        method: POST
        headers:
          Content-Type: "text/xml;charset=UTF-8"
        body: "{{ lookup('file', '{{ virl_file }}') }}"
      register: result_launch
      until: result_launch['status']|default(0) < 300
      retries: 60
      delay: 10

    - name: Display the launch result
      debug:
        msg: "{{ result_launch }}"
```

# 出力結果②: シミュレーションの開始
`"status": 200`と表示されているので、リクエストに成功したようです。

```yml:
$ ansible-playbook -i inventory_virl playbook_virl_launch.yml 

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

TASK [Launch the simulation test01] *****************************************************************************************
ok: [VIRL]

TASK [Display the launch result] ********************************************************************************************
ok: [VIRL] => {
    "msg": {
        "access_control_allow_origin": "*",
        "attempts": 1,
        "changed": false,
        "connection": "close",
        "content_length": "6",
        "content_type": "text/plain",
        "cookies": {},
        "cookies_string": "",
        "elapsed": 3,
        "failed": false,
        "msg": "OK (6 bytes)",
        "redirected": false,
        "server": "virl_std_server/0.10.37.23",
        "status": 200,
        "url": "http://10.10.20.160:19399/simengine/rest/launch?session=test01"
    }
}

PLAY RECAP ******************************************************************************************************************
VIRL                       : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  
```

VIRLサーバの画面を見ると、シミュレーション`test01`が作成され、router1、router2が起動していました。
![無題0826_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310082/686c1339-e07f-7c5a-cc3c-4cb2c79efd09.png)


# Playbook③: シミュレーション内のノード確認
念のためAPI経由でも、シミュレーション`test01`内のノード名やステータスを確認してみます。

```yml:playbook_virl_nodes.yml
---

- hosts: cisco
  gather_facts: no
  connection: local

  tasks:
    - name: Get the list of nodes in simulation {{ session }}
      uri:
        url: "http://{{ virl_host }}:19399/simengine/rest/nodes/{{ session }}"
        user: "{{ virl_username }}"
        password: "{{ virl_password }}"
        method: GET
      register: result_nodes

    - name: Display the node list
      debug:
        msg: "{{ result_nodes.json }}"
```


# 出力結果③: シミュレーション内のノード確認
②のGUIと同様の情報が表示されました。

```yml:
$ ansible-playbook -i inventory_virl playbook_virl_nodes.yml 

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

TASK [Get the list of nodes in simulation test01] ***************************************************************************
ok: [VIRL]

TASK [Display the node list] ************************************************************************************************
ok: [VIRL] => {
    "msg": {
        "test01": {
            "router1": {
                "management-protocol": "telnet",
                "management-proxy": "jumphost",
                "reachable": true,
                "serial-ports": 2,
                "state": "ACTIVE",
                "subtype": "IOSv",
                "vnc-console": false
            },
            "router2": {
                "management-protocol": "telnet",
                "management-proxy": "jumphost",
                "reachable": true,
                "serial-ports": 2,
                "state": "ACTIVE",
                "subtype": "IOSv",
                "vnc-console": false
            }
        }
    }
}

PLAY RECAP ******************************************************************************************************************
VIRL                       : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 
```

# Playbook④: シミュレーションの停止
VIRLは起動台数の上限があるため、使わない時は停止しておくケースが多いと思います。これもHTTP PUTメソッドでURL`～/update/{シミュレーション名}/stop`を送信すれば停止可能です。
逆に、起動する時はURL`～/update/{シミュレーション名}/start`で起動できます。

```yml:playbook_virl_stop
---

- hosts: cisco
  gather_facts: no
  connection: local

  tasks:
    - name: Stop the simulation {{ session }}
      uri:
        url: "http://{{ virl_host }}:19399/simengine/rest/update/{{ session }}/stop"
        user: "{{ virl_username }}"
        password: "{{ virl_password }}"
        method: PUT
        headers:
          Content-Type: "text/xml;charset=UTF-8"
      register: result_stop

    - name: Display the stop result
      debug:
        msg: "{{ result_stop }}"
```

# 出力結果④: シミュレーションの停止
jsonの結果が`"stopped": "~~==\"a|u|t|o\"==~~"`となっています。

```yml:
$ ansible-playbook -i inventory_virl playbook_virl_stop.yml 

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

TASK [Stop the simulation test01] *******************************************************************************************
ok: [VIRL]

TASK [Display the stop result] **********************************************************************************************
ok: [VIRL] => {
    "msg": {
        "access_control_allow_origin": "*",
        "changed": false,
        "connection": "close",
        "content_length": "34",
        "content_type": "application/json",
        "cookies": {},
        "cookies_string": "",
        "elapsed": 2,
        "failed": false,
        "json": {
            "stopped": "~~==\"a|u|t|o\"==~~"
        },
        "msg": "OK (34 bytes)",
        "redirected": false,
        "server": "virl_std_server/0.10.37.23",
        "status": 200,
        "url": "http://10.10.20.160:19399/simengine/rest/update/test01/stop"
    }
}

PLAY RECAP ******************************************************************************************************************
VIRL                       : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 
```
VIRLサーバの画面でも、各ノードのStateがABSENTになっています。
![無題0826_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310082/1e369928-5a7d-60c2-59c7-8ae214bb92a2.png)

# Playbook⑤: シミュレーションの終了（完全停止）
不要になったシミュレーションは、完全に停止し、VIRLサーバ上から削除します。この場合はHTTP GETメソッドでURL`/stop/{シミュレーション名}`を送信します。
[前回の記事](https://qiita.com/tech_kitara/items/9ae497765abc16e0592c#%E6%97%A2%E5%AD%98%E3%82%B7%E3%83%9F%E3%83%A5%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E5%81%9C%E6%AD%A2)でご紹介した`virl down --sim-name {シミュレーション名}`に相当するAPIです。

```yml:playbook_virl_termination.yml
---

- hosts: cisco
  gather_facts: no
  connection: local

  tasks:
    - name: Completely stop the simulation {{ session }}
      uri:
        url: "http://{{ virl_host }}:19399/simengine/rest/stop/{{ session }}"
        user: "{{ virl_username }}"
        password: "{{ virl_password }}"
        method: GET
      register: result_termination

    - name: Display the termination result
      debug:
        msg: "{{ result_termination }}"
```

# 出力結果⑤: シミュレーションの終了（完全停止）
問題なく`"status": 200`が返ってきました。

```yml:
$ ansible-playbook -i inventory_virl playbook_virl_termination.yml 

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

TASK [Completely stop the simulation test01] ********************************************************************************
ok: [VIRL]

TASK [Display the termination result] ***************************************************************************************
ok: [VIRL] => {
    "msg": {
        "access_control_allow_origin": "*",
        "changed": false,
        "connection": "close",
        "content_length": "7",
        "content_type": "text/plain",
        "cookies": {},
        "cookies_string": "",
        "elapsed": 2,
        "failed": false,
        "msg": "OK (7 bytes)",
        "redirected": false,
        "server": "virl_std_server/0.10.37.23",
        "status": 200,
        "url": "http://10.10.20.160:19399/simengine/rest/stop/test01"
    }
}

PLAY RECAP ******************************************************************************************************************
VIRL                       : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
```

VIRLサーバのシミュレーション一覧からも削除されました。
![無題0826_03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/310082/fb3648b1-04f9-ca54-87bf-f3e52f3ad788.png)

# 最後に
今回は完成しているVIRLファイルを使いましたが、Inventoryで指定したパラメータとJinja2テンプレートと組み合わせて、動的にConfig、シミュレーションを作成することもできます。
また作成時、マネジメントネットワーク経由でSSHアクセスする設定を入れておけば、その後の設定変更やshowコマンド確認をAnsibleの`xxx_config`や`xxx_command`モジュールで行うこともできます。この辺は、今後別の記事でご紹介したいと思います。