【目次】
■ はじめに
Ansible は Cisco ACI にも対応して、多数のモジュールがあります。ほとんどのモジュールには、どのような状態であるべきかを指定する state
オプションがあります。このオプションは present
、absent
のような「状態」に加えて、query
という検索する「操作」も指定できます。
この記事では、簡単なサンプルを元に state: query
での情報取得の方法と、assert
による値の確認などを説明します。
(私自身は調査中の部分もありますが、記録として残しておきます)
■ 使い方
基本的な使い方
基本的には、state
オプションに query
を指定するだけです。
特定の 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
モジュールの場合、EPG は Tenant > Application Profile > EPG というオブジェクトの階層なので、tenant
や ap
オプションは query 条件に使用され、 desicription
はオブジェクトの階層には関係ない属性値なので query 条件に使用されない、とった具合です。
全 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
モジュール)の tenant
や ap
オプション)の有無によって、検索パターンがあります。
ここでは、EPG(階層は Tenant > Application Profile > EPG)を扱う aci_epg
モジュールを例にして、パターンごとに説明します。
パターン1. 何も指定なし
何も指定しないパターンです。
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 のみ指定
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", ...(略)... }
- リクエストURL
パターン3. 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", ...(略)... }
- リクエストURL
パターン4. 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", ...(略)... }
- リクエストURL
パターン5. 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", ...(略)... }
- リクエストURL
- https://apic01/api/mo/uni/tn-tenant1.json?rsp-subtree-filter=eq(fvAEPg.name, \"epg1\")&rsp-subtree-class=fvAEPg,fvRsBd&rsp-subtree=full
■ 戻り値について
各モジュールの説明ページの 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
であることを確認します。
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_tenant
や aci_epg
など、各オブジェクト用のモジュールで state: query
を指定することで、query を実行できますが、なかには思うような検索条件を指定できないこともあるかもしれません。
そんなときには、APIC の REST API を叩くことに特化した、aci_rest
モジュールを利用します。Playbook を書く側が REST API の仕様を意識する必要がある文、柔軟な処理を指定できます。
詳細は、公式ドキュメントの aci_rest
モジュール の説明ページを参照してください。
■ さいごに
Cisco ACI モジュール の state: query
でオブジェクト情報を取得したり、assert する方法をご紹介しました。
設定系のタスクよりも query のほうが、よりオブジェクトの階層を意識する必要があるきがしました。