Cisco ACI モジュールの開発¶
Ansible 向けに新しい Cisco ACI モジュールを作成する方法に関する簡単なウォークスルーです。
Cisco ACI の詳細は「Cisco ACI ユーザーガイド」を参照してください。
本項で取り上げられている内容:
はじめに¶
Ansible にはすでに Cisco ACI モジュールの大規模なコレクションが同梱されていますが、ACI のオブジェクトモデルは膨大なものであり、可能な機能にすべて対応するには、1500 個以上のモジュールに簡単に対応できなければなりません。
特定の機能が必要な場合は、2 つのオプションがあります。
- ACI オブジェクトモデルを学習し、「aci_rest <aci_rest_module>」モジュールを使用して低レベルの APIC REST API を使用します。
- 独自の専用モジュールを作成することは、実際には非常に簡単です。
See also
- ACI の基本: ACI ポリシー <https://www.cisco.com/c/en/us/td/docs/switches/datacenter/aci/apic/sw/1-x/aci-fundamentals/b_ACI-Fundamentals/b_ACI-Fundamentals_chapter_010001.html>モデル
- ACI オブジェクトモデルの優れた概要。
- APIC 管理情報モデル参照
- APIC オブジェクトモデルの完全なリファレンス。
- APIC REST API 設定ガイド
- APIC REST API を設計および使用する方法についての詳細ガイドです。多数の例があります。
それでは、典型的な ACI モジュールがどのように構築されるかを見てみましょう。
ACI モジュール構造¶
Python ライブラリーからのオブジェクトのインポート¶
以下のインポートは、ACI モジュール全体で標準のものです。
from ansible.module_utils.aci import ACIModule, aci_argument_spec
from ansible.module_utils.basic import AnsibleModule
引数仕様の定義¶
最初の行は、標準の接続パラメーターをモジュールに追加します。その後、次のセクションでは、モジュール固有のパラメーターを使用して argument_spec
ディクショナリーを更新します。モジュール固有のパラメーターには以下を含める必要があります。
- object_id (通常は名前)
- オブジェクトの設定可能なプロパティー
- 親オブジェクト ID (ルートまでのすべての親)
- 1 対 1 の関係である子クラスのみ (1 対多/多対多 では、独自のモジュールが適切に管理する必要があります)。
- 状態
state: absent
(オブジェクトが存在しないことを確認する)state: present
(オブジェクトと設定が存在することを確認する (デフォルト))state: query
クラスのオブジェクトに関する情報を取得する
def main():
argument_spec = aci_argument_spec()
argument_spec.update(
object_id=dict(type='str', aliases=['name']),
object_prop1=dict(type='str'),
object_prop2=dict(type='str', choices=['choice1', 'choice2', 'choice3']),
object_prop3=dict(type='int'),
parent_id=dict(type='str'),
child_object_id=dict(type='str'),
child_object_prop=dict(type='str'),
state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
)
Hint
設定引数のデフォルト値を指定しないでください。デフォルト値により、オブジェクトへの意図しない変更が生じる可能性があります。
AnsibleModule オブジェクトの使用¶
以下のセクションは、AnsibleModule インスタンスを作成します。モジュールはチェックモードをサポートする必要があるため、argument_spec
引数および supports_check_mode
引数を渡します。これらのモジュールは、モジュールのクラスのすべてのオブジェクトに対する APIC のクエリーをサポートするため、オブジェクト/親 ID は state: absent
または state: present
の場合に限り必要になります。
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
required_if=[
['state', 'absent', ['object_id', 'parent_id']],
['state', 'present', ['object_id', 'parent_id']],
],
)
マッピング変数定義¶
AnsibleModule オブジェクトが開始したら、必要なパラメーター値を params
から抽出し``、データ検証を行う必要があります。通常、抽出する必要があるパラメーターは ACI オブジェクト設定およびその子設定に関連するパラメーターのみです。検証する整数オブジェクトがある場合、ここで検証を行う必要があります。ACIModule.payload()
メソッドは文字列変換を処理します。
object_id = object_id
object_prop1 = module.params['object_prop1']
object_prop2 = module.params['object_prop2']
object_prop3 = module.params['object_prop3']
if object_prop3 is not None and object_prop3 not in range(x, y):
module.fail_json(msg='Valid object_prop3 values are between x and (y-1)')
child_object_id = module.params[' child_objec_id']
child_object_prop = module.params['child_object_prop']
state = module.params['state']
ACIModule オブジェクトの使用¶
ACIModule クラスは、ACI モジュールのロジックの大部分を処理します。ACIModule は機能を AnsibleModule オブジェクトに拡張するため、モジュールインスタンスをクラスのインスタンス化に渡す必要があります。
aci = ACIModule(module)
ACIModule には、モジュールによって使用される 6 つの主なメソッドがあります。
- construct_url
- get_existing
- payload
- get_diff
- post_config
- delete_config
最初の 2 つのメソッドは、state
パラメーターに渡される値に関係なく使用されます。
URL の構築¶
construct_url()
メソッドは、オブジェクトと対話するために適切な URL と、URL に追加して結果にフィルターを設定する適切なフィルター文字列を動的にビルドするために使用されます。
state
がquery
でない場合、URL は APIC にアクセスするベース URL とオブジェクトにアクセスするための識別名です。フィルター文字列は返されたデータを設定データにのみ制限します。state
がquery
される場合、使用される URL およびフィルター文字列は、オブジェクトに渡されるパラメーターによって異なります。この方法は複雑性を処理するため、新しいモジュールの追加が容易になります。また、すべてのモジュールがどのタイプのデータを返すかに一貫性を持たせるようにします。
Note
設計目標は、値を持つすべての ID パラメーターを取り、可能な限り最も具体的なデータを返すことです。タスクに ID パラメーターを指定しないと、クラスのすべてのオブジェクトが返されます。タスクが ID パラメーターで構成されている場合は、特定のオブジェクトのデータが返されます。ID パラメーターの一部が渡されると、モジュールは URL およびフィルター文字列を適切にビルドするために渡された ID を使用します。
construct_url()
メソッドは 2 つの必須引数を取ります。
- self - クラスインスタンスで自動的に渡されます。
- root_class -
aci_class
キー、aci_rn
キー、target_filter
キー、およびmodule_object
キーで構成されるディクショナリー- aci_class - APIC で使用されるクラスの名前 (例:
fvTenant
) - aci_rn - オブジェクトの相対名 (例:
tn-ACME
)。 - target_filter - エントリーのサブセットを選択するクエリー文字列を構成するキーと値のペアを持つディクショナリー (例:
{'name': 'ACME'}
) - module_object - このクラスの特定のオブジェクト (例:
ACME
)
- aci_class - APIC で使用されるクラスの名前 (例:
例:
aci.construct_url(
root_class=dict(
aci_class='fvTenant',
aci_rn='tn-{0}'.format(tenant),
target_filter={'name': tenant},
module_object=tenant,
),
)
aci_tenant
などの一部のモジュールは root クラスであるため、メソッドに追加の引数を渡す必要はありません。
construct_url()
メソッドは、4 つの任意の引数を取ります。最初の 3 つの引数が上記のように root クラスを模倣しますが、これは子オブジェクト用です。
- subclass_1 -
aci_class
キー、aci_rn
キー、target_filter
キー、およびmodule_object
キーで構成されるディクショナリー- 例: Application Profile Class (AP)
- subclass_2 -
aci_class
キー、aci_rn
キー、target_filter
キー、およびmodule_object
キーで構成されるディクショナリー- 例: エンドポイントグループ (EPG)
- subclass_3 -
aci_class
キー、aci_rn
キー、target_filter
キー、および``module_object`` キーで構成されるディクショナリー- 例: EPG への契約のバインド
- child_classes - モジュールがサポートする子クラスの APIC 名のリスト。
- 1 つのリストであっても、これはリストです
- これらは、APIC が使用する分かりにくい名前です。
- 可能な場合は、返された child_classes を制限するために使用されます。
- 例:
child_classes=['fvRsBDSubnetToProfile', 'fvRsNdPfxPol']
Note
APIC は、特殊文字 ([、]、および -) を要求したり、名前にオブジェクトのメタデータ (「VLAN」プールの場合は「vlanns」) を使用したりすることがあります。モジュールは、予想される入力の単純さに保つために、特殊文字の追加または複数のパラメーターの結合を処理する必要があります。
既存設定の取得¶
URL およびフィルター文字列が構築されると、モジュールはオブジェクトの既存の設定を取得できるようになります。
state: present
は、タスクで入力された比較に使用する設定を取得します。既存の値と異なるすべての値は更新されます。state: absent
は、既存の設定を使用して項目が存在し、削除する必要があるかどうかを確認します。state: query
は、これを使用してタスクのクエリーを実行し、既存のデータを報告します。
aci.get_existing()
state が present の場合¶
state: present
の場合、モジュールは既存の設定とタスクエントリーに対して diff を実行する必要があります。値を更新する必要がある場合、モジュールは更新が必要な項目のみを持つ POST 要求を行います。一部のモジュールには、別のオブジェクトと 1 対 1 の関係にある子があります。このような場合は、モジュールを使用して子オブジェクトを管理できます。
ACI ペイロードの構築¶
aci.payload()
メソッドは、提案されたオブジェクト設定のディクショナリーを構築するために使用されます。タスクの値を提供しなかったパラメーターはすべて、ディクショナリー (オブジェクトとその子の両方) から削除されます。値を持つパラメーターは文字列に変換され、既存の設定と比較するために使用される最後のディクショナリーオブジェクトに追加されます。
aci.payload()
メソッドは、モジュールが子オブジェクトを管理するかどうかに応じて、2 つの必須引数と、1 つの任意の引数を取ります。
aci_class
はオブジェクトのクラスの APIC 名です (例:aci_class='fvBD'
)。class_config
は POST 要求のペイロードとして使用する適切なディクショナリーです。- キーは APIC で使用される名前と一致する必要があります。
- 値は
module.params
の対応する値である必要があります。これらは上記の変数です。
child_configs
は任意で、子設定のディクショナリーのリストです。- 子設定には、属性設定部分だけでなく、完全な子オブジェクトディクショナリーが含まれます。
- 設定部分は、オブジェクトと同じ方法で構築されます。
aci.payload(
aci_class=aci_class,
class_config=dict(
name=bd,
descr=description,
type=bd_type,
),
child_configs=[
dict(
fvRsCtx=dict(
attributes=dict(
tnFvCtxName=vrf
),
),
),
],
)
要求の実行¶
get_diff()
メソッドは diff の実行に使用され、必要な引数 aci_class
を 1 つだけ取ります。
例: aci.get_diff(aci_class='fvBD')
post_config()
メソッドは、必要に応じて APIC に対して POST 要求を実行するために使用されます。この方法は引数を取らず、チェックモードを処理します。
例: aci.post_config()
サンプルコード¶
if state == 'present':
aci.payload(
aci_class='<object APIC class>',
class_config=dict(
name=object_id,
prop1=object_prop1,
prop2=object_prop2,
prop3=object_prop3,
),
child_configs=[
dict(
'<child APIC class>'=dict(
attributes=dict(
child_key=child_object_id,
child_prop=child_object_prop
),
),
),
],
)
aci.get_diff(aci_class='<object APIC class>')
aci.post_config()
state が absent の場合¶
タスクが state を absent に設定すると、delete_config()
メソッドのみが必要になります。このメソッドは引数を取らず、チェックモードを処理します。
elif state == 'absent':
aci.delete_config()
ACI ライブラリー関数のテスト¶
以下の python スクリプトを使用すると、APIC ハードウェアにアクセスしなくても construct_url()
引数および payload()
引数をテストできます。
#!/usr/bin/python
import json
from ansible.module_utils.network.aci.aci import ACIModule
# Just another class mimicing a bare AnsibleModule class for construct_url() and payload() methods
class AltModule():
params = dict(
host='dummy',
port=123,
protocol='https',
state='present',
output_level='debug',
)
# A sub-class of ACIModule to overload __init__ (we don't need to log into APIC)
class AltACIModule(ACIModule):
def __init__(self):
self.result = dict(changed=False)
self.module = AltModule()
self.params = self.module.params
# Instantiate our version of the ACI module
aci = AltACIModule()
# Define the variables you need below
aep = 'AEP'
aep_domain = 'uni/phys-DOMAIN'
# Below test the construct_url() arguments to see if it produced correct results
aci.construct_url(
root_class=dict(
aci_class='infraAttEntityP',
aci_rn='infra/attentp-{}'.format(aep),
target_filter={'name': aep},
module_object=aep,
),
subclass_1=dict(
aci_class='infraRsDomP',
aci_rn='rsdomP-[{}]'.format(aep_domain),
target_filter={'tDn': aep_domain},
module_object=aep_domain,
),
)
# Below test the payload arguments to see if it produced correct results
aci.payload(
aci_class='infraRsDomP',
class_config=dict(tDn=aep_domain),
)
# Print the URL and proposed payload
print 'URL:', json.dumps(aci.url, indent=4)
print 'PAYLOAD:', json.dumps(aci.proposed, indent=4)
これにより、以下が生成されます。
URL: "https://dummy/api/mo/uni/infra/attentp-AEP/rsdomP-[phys-DOMAIN].json"
PAYLOAD: {
"infraRsDomP": {
"attributes": {
"tDn": "phys-DOMAIN"
}
}
}
健全性チェックのテスト¶
次のようなフォークから実行できます。
$ ansible-test sanity --python 2.7 lib/ansible/modules/network/aci/aci_tenant.py
See also
- サニティーテスト
- 健全性テストを構築する方法に関する情報
ACI 統合テストのテスト¶
以下を実行できます。
$ ansible-test network-integration --continue-on-error --allow-unsupported --diff -v aci_tenant
Note
テストの実行に適切な python バージョンを使用するには、--python 2.7
または --python 3.6
を追加しないといけない場合があります。
test/integration/inventory.networking で使用されたインベントリーを編集し、以下のように追加することもできます。
[aci:vars]
aci_hostname=my-apic-1
aci_username=admin
aci_password=my-password
aci_use_ssl=yes
aci_use_proxy=no
[aci]
localhost ansible_ssh_host=127.0.0.1 ansible_connection=local
See also
- 統合テスト
- インテグレーションテストの構築方法に関する情報。
テストカバレージのテスト¶
以下を実行できます。
$ ansible-test network-integration --python 2.7 --allow-unsupported --coverage aci_tenant
$ ansible-test coverage report