VMware NSX-TのAnsibleモジュールを作る方法

VMware NSX-TのAnsibleモジュールを自作したので備忘録として作成方法を書いておこうと思います。

# NSX-T Ansible Module

[https://github.com/vmware/ansible-for-nsxt](https://github.com/vmware/ansible-for-nsxt)

# NSX-T API

[https://code.vmware.com/ja/apis/547/nsx-t](https://code.vmware.com/ja/apis/547/nsx-t)

# 開発環境

|  項目  | バージョン |
|--------|------------|
| OS     | CentOS7.6  |
| Python | 3.6        |
| Ansibl | 2.7.10     |

# 開発環境準備

必要なパッケージをインストールします。

```shell-session
[root@localhost dev]# yum -y install epel-release
[root@localhost dev]# yum -y install ansible python36
```

ここでは、virtualenvを使って環境を作ります。

```shell-session
[root@localhost ~]# mkdir dev
[root@localhost ~]# cd dev/
[root@localhost dev]# python3 -m venv venv
[root@localhost dev]# source venv/bin/activate
```

必要なモジュールをインストールします。

```shell-session
(venv) [root@localhost dev]# pip install --upgrade pyvmomi pyvim requests
```

NSX-TのAnsibleモジュールにある `module_utils` を使うのでリポジトリをクローンします。

```shell-session
(venv) [root@localhost dev]# git clone https://github.com/vmware/ansible-for-nsxt.git
(venv) [root@localhost dev]# mv ansible-for-nsxt/module_utils/ .
(venv) [root@localhost dev]# rm -rf ansible-for-nsxt/
``` 

これで準備が整いました。

# 開発用テンプレート

ここでは以下のテンプレートを使います。

```python
#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright: (c) 2019, 作成者名を書く
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function
__metaclass__ = type


ANSIBLE_METADATA = {'metadata_version': '1.1',
                    'status': ['preview'],
                    'supported_by': 'community'}

DOCUMENTATION = '''
module: nsxt_firewall_config
short_description: Back up or restore firewall rules.
author:
  - 作者名を書く (@アカウント名)
version_added: ''
description:
  - This module can back up or restore firewall rules.
requirements:
  - python >= 2.7
options:
    hostname:
      description: 'Deployed NSX manager hostname.'
      required: true
      type: str
    username:
      description: 'The username to authenticate with the NSX manager.'
      required: true
      type: str
    password:
      description: 'The password to authenticate with the NSX manager.'
      required: true
      type: str
    port:
      description: 'Port to access NSX manager.'
      default: 443
      type: int
    validate_certs:
      description:
      - Allows connection when SSL certificates are not valid. Set to C(false) when certificates are not trusted.
      - If the value is not specified in the task, the value of environment variable C(VMWARE_VALIDATE_CERTS) will be used instead.
      - Environment variable supported added in Ansible 2.6.
      - If set to C(yes), please make sure Python >= 2.7.9 is installed on the given machine.
      type: bool
      default: 'yes'
'''

EXAMPLES = '''
'''

RETURN = '''
'''

import json
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible.module_utils.vmware_nsxt import vmware_argument_spec, request


def main():
    argument_spec = vmware_argument_spec()
    # 必要であればオプションを追加する
    # 例
    '''
    argument_spec.update(name=dict(type='str', required=True))
    '''

    module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
    mgr_hostname = module.params['hostname']
    mgr_username = module.params['username']
    mgr_password = module.params['password']
    validate_certs = module.params['validate_certs']

    manager_url = 'https://{0}/api/v1'.format(mgr_hostname)

    headers = dict(Accept="application/json")
    headers['Content-Type'] = 'application/json'

    # 処理を書いて行く

if __name__ == '__main__':
    main()
```

# module_utilsの説明

`ansible-for-nsxt` では `vmware_nsxt` と言うmodule_utilsが準備されています。
その中には以下のメソッドが定義されています。

## vmware_argument_spec

`vmware_argument_spec関数` のソースです。

```python
(snip)
def vmware_argument_spec():
    return dict(
        hostname=dict(type='str', required=True),
        username=dict(type='str', required=True),
        password=dict(type='str', required=True, no_log=True),
        port=dict(type='int', default=443),
        validate_certs=dict(type='bool', requried=False, default=True),
    )
(snip)
```

vmware_argument_specを実行することで、NSX-T Managerへログインするためのオプションが自動で生成されます。

## request

`request関数` のソースです。

```python
(snip)
def request(url, data=None, headers=None, method='GET', use_proxy=True,
            force=False, last_mod_time=None, timeout=300, validate_certs=True,
            url_username=None, url_password=None, http_agent=None, force_basic_auth=True, ignore_errors=False):
(snip)
else:
        return resp_code, data
```

requestは、引数で渡したURLやログイン情報、メソッドなどを元にリクエストを実行します。
戻り値は `resp_code(レスポンスコード)` と `data(取得したBody)` です。

# NSX-T 自作モジュールを作ってみる

## 作ったもの

ここでは、簡単に論理ルーターの情報を取得するだけのモジュールを作ってみました。

```python:nsx_logical_router_facts.py
#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright: (c) 2019, sky-joker
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function
__metaclass__ = type


ANSIBLE_METADATA = {'metadata_version': '1.1',
                    'status': ['preview'],
                    'supported_by': 'community'}

DOCUMENTATION = '''
module: nsxt_firewall_config
short_description: Back up or restore firewall rules.
author:
  - sky-joker (@sky-joker)
version_added: ''
description:
  - This module can back up or restore firewall rules.
requirements:
  - python >= 2.7
options:
    hostname:
      description: 'Deployed NSX manager hostname.'
      required: true
      type: str
    username:
      description: 'The username to authenticate with the NSX manager.'
      required: true
      type: str
    password:
      description: 'The password to authenticate with the NSX manager.'
      required: true
      type: str
    port:
      description: 'Port to access NSX manager.'
      default: 443
      type: int
    validate_certs:
      description:
      - Allows connection when SSL certificates are not valid. Set to C(false) when certificates are not trusted.
      - If the value is not specified in the task, the value of environment variable C(VMWARE_VALIDATE_CERTS) will be used instead.
      - Environment variable supported added in Ansible 2.6.
      - If set to C(yes), please make sure Python >= 2.7.9 is installed on the given machine.
      type: bool
      default: 'yes'
    name:
      description:
      - Specifies the name of the logical router.
      type: str
      required: True
'''

EXAMPLES = '''
- name: Get Logical Router Info
  nsxt_logical_router_facts:
    hostname: "{{ nsxt_manager_hostname }}"
    username: "{{ nsxt_username }}"
    password: "{{ nsxt_password }}"
    validate_certs: no
    name: T0
  register: r
'''

RETURN = '''
result:
  description: Logical router information
  type: dict
'''

from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible.module_utils.vmware_nsxt import vmware_argument_spec, request


def main():
    argument_spec = vmware_argument_spec()
    argument_spec.update(name=dict(type='str', required=True))

    module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
    mgr_hostname = module.params['hostname']
    mgr_username = module.params['username']
    mgr_password = module.params['password']
    validate_certs = module.params['validate_certs']
    name = module.params['name']

    manager_url = 'https://{0}/api/v1'.format(mgr_hostname)

    headers = dict(Accept="application/json")
    headers['Content-Type'] = 'application/json'

    # 論理ルーターの情報を取得する
    try:
        (rc, resp) = request(manager_url + '/logical-routers', method='GET',
                             url_username=mgr_username, url_password=mgr_password, validate_certs=validate_certs,
                             ignore_errors=False)
    except Exception as err:
        module.fail_json(msg="%s" % to_native(err))

    # 取得した論理ルーターに指定した論理ルーターが存在するか確認する
    router_info = ''
    for router in resp['results']:
        if(router['display_name'] == name):
            router_info = router

    module.exit_json(changed=False, result=router_info)


if __name__ == '__main__':
    main()
```

`DOCUMENTATION` `EXAMPLES` `RETURN` を作成したモジュールの仕様通りに書き換えます。

作成したモジュールは以下に保存します。

```shell-session
(venv) [root@localhost dev]# mkdir library/
(venv) [root@localhost dev]# vi library/nsxt_logical_router_facts.py
(モジュールを貼り付け)
```

## 実行

以下のようなPlaybookを作成します。

```
(venv) [root@localhost dev]# vi test_nsxt_logical_router_facts.yml
---
- name: Get Logical Router Info Test
  hosts: localhost
  gather_facts: no
  tasks:
    - name: Get Logical Router Info
      nsxt_logical_router_facts:
        hostname: nsx-manager.local
        username: admin
        password: password
        validate_certs: no
        name: T0
      register: r

    - debug: msg="{{ r }}"
```

実行してみます。

```
(venv) [root@localhost dev]# ansible-playbook test_nsxt_logical_router_facts.yml
(snip)
TASK [debug] ******************************************************************
ok: [localhost] => {
    "msg": {
        "changed": false,
        "failed": false,
        "result": {
            "_create_time": 1557095899972,
            "_create_user": "admin",
            "_last_modified_time": 1557095899982,
            "_last_modified_user": "admin",
            "_protection": "NOT_PROTECTED",
            "_revision": 1,
            "_system_owned": false,
            "advanced_config": {
                "external_transit_networks": [
                    "100.64.0.0/16",
                    "fced:6d3c:2300::/48"
                ],
(snip)
PLAY RECAP ********************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0
```

論理ルーターの情報が取得できたことが確認できます :)

NSX-T Ansibleモジュールのプロジェクトではコミュニティ貢献を募集しているようです。
デプロイのモジュールはある程度揃ってるようですが、論理ネットワーク系のモジュールはまだまだのようです。
興味がある人はプルリクを送ってみるといいかもしれません :)