てくなべ (tekunabe)

ansible / network automation / 学習メモ

[Ansible] Cisco ACI モジュールの state: query でオブジェクトの情報を取得する

【目次】

■ はじめに

Ansible は Cisco ACI にも対応して、多数のモジュールがあります。ほとんどのモジュールには、どのような状態であるべきかを指定する state オプションがあります。このオプションは presentabsent のような「状態」に加えて、query という検索する「操作」も指定できます。

この記事では、簡単なサンプルを元に state: query での情報取得の方法と、assert による値の確認などを説明します。

(私自身は調査中の部分もありますが、記録として残しておきます)

  • 環境
    • Cisco DevNet Sandbox (APIC 4.1)
    • Ansible 2.9.0




■ 使い方

基本的な使い方

基本的には、state オプションに query を指定するだけです。

特定の Tenant 情報を取得

f:id:akira6592:20191103225139p:plain
特定の Tenant 情報を取得

まず単純に、Tenant の情報を取得する Playbook です。オブジェクトの階層の上のほうなので、検索条件の指定方法は単純です。

ここでは、aci_tenant モジュールを利用し、tenant1 という名前の Tenant の情報を取得します。 query の結果を register で指定した変数 resutlt に格納して、debug モジュールで表示します。

  • Playbook
---
- name: query
  hosts: apic
  gather_facts: no

  tasks:
    - name: tenant
      aci_tenant:
        host: "{{ ansible_host }}"
        username: "{{ username }}"
        password: "{{ password }}"
        validate_certs: no
        tenant: tenant1   # Tenant 名を指定
        state: query      # query する 
        output_level: debug
      register: result

    - name: debug
      debug:
        var: result
  • 実行結果(debug抜粋)
TASK [debug] ***********************************************************************************
ok: [apic01] => {
    "result": {
...(略)...
        "current": [
            {
                "fvTenant": {
                    "attributes": {
                        "annotation": "",
                        "childAction": "",
                        "descr": "",
                        "dn": "uni/tn-tenant1",
                        "extMngdBy": "",
                        "lcOwn": "local",
                        "modTs": "2019-11-03T12:25:26.882+00:00",
                        "monPolDn": "uni/tn-common/monepg-default",
                        "name": "tenant1",
                        "nameAlias": "",
                        "ownerKey": "",
                        "ownerTag": "",
                        "status": "",
                        "uid": "15374"
                    }
                }
            }
        ],
...(略)...
}

もし、指定した名前の Tenant がない場合は、current が空のリスト(current: [])になります。

補足: query の条件に使用される値と使用されない値がある

少し注意が必要なのは「query の条件に使用される値と使用されない値がある」という点です。例えば、tenant オプションを指定すれば、「この名前のオプションはあるか」という query になります。 一方で、 description オプションは query 時は無視されます。

具体的には

  - aci_tenant:
      # ...(略)...
      tenant: tenant1
      description: hogehoge
      state: query

という指定した場合、単に

  • Tenant 名が tenant1 であること

のみが query 条件(SQLでいう where 句のようなもの)になります。

  • Tenant 名が tenant1 、かつ description が hogehoge であること

ではありません。

どのようなオプションが query の条件に使用されるか、一言で説明したものは見つけられていません。いくつか試して「オブジェクトの階層を示すオプション」は条件に使用される、という感触はあります。

たとえば、aci_epg モジュールの場合、EPGTenant > Application Profile > EPG というオブジェクトの階層なので、tenantap オプションは query 条件に使用され、 desicription はオブジェクトの階層には関係ない属性値なので query 条件に使用されない、とった具合です。

全 Tenant 情報を取得

f:id:akira6592:20191103225223p:plain
全 Tenant 情報を取得

aci_tenant モジュールで、tenant オプションを指定しないで query すると、全 Tenant の情報を取得します。

  • Playbook
---
  tasks:
    - name: tenant
      aci_tenant:
        host: "{{ ansible_host }}"
        username: "{{ username }}"
        password: "{{ password }}"
        validate_certs: no
        state: query
        output_level: debug
      register: result

    - name: debug
      debug:
        var: result
  • 実行結果(debug抜粋)
TASK [debug] ***********************************************************************************
ok: [apic01] => {
    "result": {
...(略)...
        "current": [
...(略)...
            {
                "fvTenant": {
                    "attributes": {
                        "annotation": "",
                        "childAction": "",
                        "descr": "",
                        "dn": "uni/tn-tenant1",
...(略)...
                    }
                }
            },
            {
                "fvTenant": {
                    "attributes": {
                        "annotation": "",
                        "childAction": "",
                        "descr": "",
                        "dn": "uni/tn-tenant2",
...(略)...
                    }
                }
            },
...(略)...
}

少し応用的な使い方

階層を伴うオブジェクトのモジュールでは、階層を示すオプション(例えば aci_epg モジュール)の tenantap オプション)の有無によって、検索パターンがあります。

ここでは、EPG(階層は Tenant > Application Profile > EPG)を扱う aci_epg モジュールを例にして、パターンごとに説明します。

パターン1. 何も指定なし

f:id:akira6592:20191103225259p:plain
何も指定なし

何も指定しないパターンです。

  tasks:
    - name: epg
      aci_epg:
        host: "{{ ansible_host }}"
        username: "{{ username }}"
        password: "{{ password }}"
        validate_certs: no
        state: query      # query する 
        output_level: debug
      register: result

    - name: debug
      debug:
        var: result

全 Tenant、全 AP、全 EPG の情報を取得できます。dn を見るとオブジェクトの階層が分かります。

  • 実行結果(debug抜粋)
TASK [debug] *******************************************************************
ok: [apic01] => {
    "result": {
...(略)...
        "current": [
            {
                "fvAEPg": {
                    "attributes": {
...(略)...
                        "dn": "uni/tn-tenant2/ap-ap1/epg-epg1",
...(略)...
            {
                "fvAEPg": {
                    "attributes": {
...(略)...
                        "dn": "uni/tn-tenant2/ap-ap2/epg-epg2",
...(略)...
            {
                "fvAEPg": {
                    "attributes": {
...(略)...
                        "dn": "uni/tn-tenant1/ap-ap1/epg-epg1",
...(略)...
            {
                "fvAEPg": {
                    "attributes": {
...(略)...
                        "dn": "uni/tn-tenant1/ap-ap2/epg-epg2",
...(略)...
}

パターン2. Tenant のみ指定

f:id:akira6592:20191103225333p:plain
Tenant のみ指定

Tenant のみ指定しないパターンです。

  tasks:
    - name: epg
      aci_epg:
        host: "{{ ansible_host }}"
        username: "{{ username }}"
        password: "{{ password }}"
        validate_certs: no
        tenant: tenant1   # Tenant の指定
        state: query      # query する 
        output_level: debug
      register: result

    - name: debug
      debug:
        var: result

指定した Tenant 内の、全 AP、全 EPG の情報を取得できます。

  • 実行結果(debug抜粋)
TASK [debug] ***********************************************************************************
ok: [apic01] => {
    "result": {
...(略)...
        "current": [
            {
                "fvTenant": {
                    "attributes": {
...(略)...
                        "name": "tenant1",
...(略)...
                    },
                    "children": [
                        {
                            "fvAp": {
                                "attributes": {
...(略)...
                                    "name": "ap2",
...(略)...
                                },
                                "children": [
                                    {
                                        "fvAEPg": {
...(略)...
                                                "name": "epg2",
...(略)...
                                        }
                                    }
                                ]
                            }
                        },
                        {
                            "fvAp": {
                                "attributes": {
...(略)...
                                    "name": "ap1",
...(略)...
                                },
                                "children": [
                                    {
                                        "fvAEPg": {
                                            "attributes": {
                                                "name": "epg1",
...(略)...
}

パターン3. Tenant、AP を指定

f:id:akira6592:20191103225401p:plain
Tenant、AP を指定

  tasks:
      - name: epg
      aci_epg:
        host: "{{ ansible_host }}"
        username: "{{ username }}"
        password: "{{ password }}"
        validate_certs: no
        tenant: tenant1   # Tenant の指定
        ap: ap1           # AP の指定
        state: query      # query する 
        output_level: debug
      register: result

    - name: debug
      debug:
        var: result

指定した Tenant、AP 内の、全 EPG の情報を取得できます。

  • 実行結果(debug抜粋)
TASK [debug] *******************************************************************
ok: [apic01] => {
    "result": {
...(略)...
        "current": [
            {
                "fvAp": {
                    "attributes": {
                        "annotation": "",
                        "childAction": "",
                        "descr": "",
                        "dn": "uni/tn-tenant1/ap-ap1",
...(略)...
                    },
                    "children": [
                        {
                            "fvAEPg": {
                                "attributes": {
...(略)...
                                    "name": "epg1",
...(略)...
}

パターン4. Tenant、AP、EPG を指定

f:id:akira6592:20191103225438p:plain
Tenant、AP、EPG を指定

  tasks:
    - name: epg
      aci_epg:
        host: "{{ ansible_host }}"
        username: "{{ username }}"
        password: "{{ password }}"
        validate_certs: no
        tenant: tenant1   # Tenant の指定
        ap: ap1           # AP の指定
        epg: epg1         # EPG の指定
        state: query      # query する 
        output_level: debug
      register: result

    - name: debug
      debug:
        var: result

指定した Tenant、AP、EPG の情報を取得できます。

  • 実行結果(debug抜粋)
TASK [debug] *******************************************************************
ok: [apic01] => {
    "result": {
...(略)...
        "current": [
            {
                "fvAEPg": {
                    "attributes": {
                        "annotation": "",
                        "childAction": "",
                        "configIssues": "",
                        "configSt": "applied",
                        "descr": "",
                        "dn": "uni/tn-tenant1/ap-ap1/epg-epg1",
...(略)...
}

パターン5. Tenant、EPG を指定

f:id:akira6592:20191103225507p:plain
Tenant、EPG を指定

  tasks:
    - name: epg
      aci_epg:
        host: "{{ ansible_host }}"
        username: "{{ username }}"
        password: "{{ password }}"
        validate_certs: no
        tenant: tenant1   # Tenant の指定
        epg: epg1         # EPG の指定
        state: query      # query する 
        output_level: debug
      register: result

    - name: debug
      debug:
        var: result

指定した Tenant、内の、全 EPG の情報を取得できます。

  • 実行結果(debug抜粋)
TASK [debug] *******************************************************************
ok: [apic01] => {
    "result": {
...(略)...
        "current": [
            {
                "fvTenant": {
                    "attributes": {
...(略)...
                        "dn": "uni/tn-tenant1",
...(略)...
                    },
                    "children": [
                        {
                            "fvAp": {
                                "attributes": {
...(略)...
                                    "name": "ap1",
...(略)...
                                },
                                "children": [
                                    {
                                        "fvAEPg": {
                                            "attributes": {
...(略)...
                                                "name": "epg1",
...(略)...
}




■ 戻り値について

各モジュールの説明ページの Return Values に戻り値の説明があります。

例: aci_epg モジュールの Return Values

ここまでの Playbook の実行結果でも示されていますが、regsiter で指定した変数の中に query 結果が格納されます。ここでは register: result とした場合で補足説明します。

0件の場合は空のリストの current になる

result.current に query 結果そのものがリストとして格納されます。query 結果が 0 件の場合は、空のリスト result.current[] になります。そのため、result.current 自体の有無では、結果の有無の確認はできないので注意です。もしかしたら、エラーになるケースもあるかもしれません。

output_level: debug しておくとリクエストした URL も分かる

output_level: debug しておくと、result.url には、モジュールが APIC にリクエストした URL が、result.filter にはフィルター文字(クエリストリング)が格納されます。「この指定方法で、実際どんなリクエストをしたんだろう?」と知りたいときに非常に便利です。

ファイル出力時の整形には to_nice_json が便利

結果の内容をファイル場合は、copy モジュールcontent: {{ result }} のように指定します。

ただ、そのままですと、平ぺったい JSON

{"status": 200, "proposed": {}, "url": "https://apic01/api/mo/uni/tn-tenant1.json", ...(略)...

のように、人間には読みにくい形になっていまいます。そこで to_nice_json フィルターを利用すると、読みやすく nice になります。

    - name: file output
      copy:
        content: "{{ result | to_nice_json }}"
        dest: result_tenant.json
$ cat result_tenant.json
{
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "current": [
        {
            "fvTenant": {
                "attributes": {
                    "annotation": "",
                    "childAction": "",
                    "descr": "",
                    "dn": "uni/tn-tenant1",
...(略)...
}

assert でオブジェクトの有無や値をチェックする

情報を取得できるということは、assert モジュールを利用して、期待した状態とあっているかどうかのチェックもできます。

ここでは、オブジェクトの有無を assert する例と、オブジェクトが持っている値を assert する例を紹介します。

オブジェクトの有無を assert する

query した結果の current のリストの長さが 1 以上であることを確認することで、オブジェクトの有無を確認できます。

ここでは、指定した Tenant、AP 内に、指定した EPG があることを確認します。

  tasks:
      - name: assert test
        aci_epg:
          host: "{{ ansible_host }}"
          username: "{{ username }}"
          password: "{{ password }}"
          validate_certs: no
          tenant: tenant1   # Tenant の指定
          ap: ap1           # AP の指定
          epg: epg1         # EPG の指定
          state: query      # query する 
          output_level: debug
        register: result

      - name: assert
        assert:
          that:
            -  (result.current | length) >= 1

他にも色々方法はあると思います。

  • 実行結果(assert抜粋)
TASK [assert] ******************************************************************
ok: [apic01] => {
    "changed": false,
    "msg": "All assertions passed"
}

assert が OK であれば All assertions passed と表示されます。

値を assert する

query した結果の current の中の ssert したい値を指定して assert します。

ここでは、指定した Tenant、AP 内に、指定した EPG の Description が test であることを確認します。

f:id:akira6592:20191103225556p:plain:w400
EPG の Description

  tasks:
    - name: assert test
      aci_epg:
        host: "{{ ansible_host }}"
        username: "{{ username }}"
        password: "{{ password }}"
        validate_certs: no
        tenant: tenant1   # Tenant の指定
        ap: ap1           # AP の指定
        epg: epg1         # EPG の指定
        state: query      # query する 
        output_level: debug
      register: result

    - name: assert
      assert:
        that:
          -  result.current[0].fvAEPg.attributes.descr == "test"

current[0] と指定しているあたりが、あまりスマートではないですね・・)

  • 実行結果(assert抜粋)
TASK [assert] ******************************************************************
ok: [apic01] => {
    "changed": false,
    "msg": "All assertions passed"
}

assert が OK であれば All assertions passed と表示されます。




■ オブジェクト特化モジュールで対応できないなら aci_rest モジュールの出番

これまで紹介したように、aci_tenantaci_epg など、各オブジェクト用のモジュールで state: query を指定することで、query を実行できますが、なかには思うような検索条件を指定できないこともあるかもしれません。

そんなときには、APICREST API を叩くことに特化した、aci_rest モジュールを利用します。Playbook を書く側が REST API の仕様を意識する必要がある文、柔軟な処理を指定できます。

詳細は、公式ドキュメントの aci_rest モジュール の説明ページを参照してください。




■ さいごに

Cisco ACI モジュール の state: query でオブジェクト情報を取得したり、assert する方法をご紹介しました。

設定系のタスクよりも query のほうが、よりオブジェクトの階層を意識する必要があるきがしました。