summaryrefslogtreecommitdiffstats
path: root/ansible_collections/cisco/mso
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
commit975f66f2eebe9dadba04f275774d4ab83f74cf25 (patch)
tree89bd26a93aaae6a25749145b7e4bca4a1e75b2be /ansible_collections/cisco/mso
parentInitial commit. (diff)
downloadansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.tar.xz
ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.zip
Adding upstream version 7.7.0+dfsg.upstream/7.7.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/cisco/mso')
-rw-r--r--ansible_collections/cisco/mso/.DS_Storebin0 -> 6148 bytes
-rw-r--r--ansible_collections/cisco/mso/.github/.DS_Storebin0 -> 6148 bytes
-rw-r--r--ansible_collections/cisco/mso/.github/ISSUE_TEMPLATE/Bug_Report.md80
-rw-r--r--ansible_collections/cisco/mso/.github/ISSUE_TEMPLATE/Feature_Request.md54
-rw-r--r--ansible_collections/cisco/mso/.github/ISSUE_TEMPLATE/config.yml1
-rw-r--r--ansible_collections/cisco/mso/.github/workflows/ansible-test.yml249
-rw-r--r--ansible_collections/cisco/mso/.github/workflows/changelog-generation.yml32
-rw-r--r--ansible_collections/cisco/mso/.github/workflows/codeql-analysis.yml43
-rw-r--r--ansible_collections/cisco/mso/.github/workflows/galaxy-importer.cfg9
-rw-r--r--ansible_collections/cisco/mso/.gitignore394
-rw-r--r--ansible_collections/cisco/mso/.vscode/settings.json18
-rw-r--r--ansible_collections/cisco/mso/CHANGELOG.rst481
-rw-r--r--ansible_collections/cisco/mso/FILES.json2637
-rw-r--r--ansible_collections/cisco/mso/LICENSE678
-rw-r--r--ansible_collections/cisco/mso/MANIFEST.json43
-rw-r--r--ansible_collections/cisco/mso/README.md122
-rw-r--r--ansible_collections/cisco/mso/changelogs/.gitignore1
-rw-r--r--ansible_collections/cisco/mso/changelogs/.plugin-cache.yaml343
-rw-r--r--ansible_collections/cisco/mso/changelogs/changelog.yaml404
-rw-r--r--ansible_collections/cisco/mso/changelogs/config.yaml31
-rw-r--r--ansible_collections/cisco/mso/codecov.yml4
-rw-r--r--ansible_collections/cisco/mso/meta/runtime.yml142
-rw-r--r--ansible_collections/cisco/mso/plugins/.DS_Storebin0 -> 6148 bytes
-rw-r--r--ansible_collections/cisco/mso/plugins/doc_fragments/modules.py83
-rw-r--r--ansible_collections/cisco/mso/plugins/httpapi/mso.py291
-rw-r--r--ansible_collections/cisco/mso/plugins/module_utils/constants.py24
-rw-r--r--ansible_collections/cisco/mso/plugins/module_utils/mso.py1354
-rw-r--r--ansible_collections/cisco/mso/plugins/module_utils/schema.py209
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_backup.py331
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_backup_schedule.py219
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_dhcp_option_policy.py167
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_dhcp_option_policy_option.py193
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_dhcp_relay_policy.py166
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_dhcp_relay_policy_provider.py256
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_label.py164
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_remote_location.py243
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_rest.py186
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_role.py285
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema.py132
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_clone.py125
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site.py194
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp.py224
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg.py301
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_bulk_staticport.py464
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_domain.py476
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_selector.py392
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_staticleaf.py258
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_staticport.py444
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_subnet.py281
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd.py236
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd_l3out.py255
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd_subnet.py290
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_external_epg.py234
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_external_epg_selector.py291
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_l3out.py246
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_service_graph.py279
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf.py207
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region.py274
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_cidr.py304
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_cidr_subnet.py320
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_hub_network.py245
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template.py263
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp.py210
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg.py471
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_contract.py263
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_selector.py277
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_subnet.py256
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd.py566
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd_dhcp_policy.py245
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd_subnet.py262
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_clone.py222
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_contract_filter.py396
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_contract_service_graph.py317
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_deploy.py147
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_deploy_status.py163
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg.py337
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_contract.py247
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_selector.py250
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_subnet.py224
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_externalepg.py337
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_filter_entry.py369
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_l3out.py233
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_migrate.py246
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_service_graph.py270
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_vrf.py228
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_vrf_contract.py263
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_validate.py74
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_service_node_type.py162
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_site.py290
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_tenant.py218
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_tenant_site.py387
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_user.py283
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_version.py65
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/ndo_schema_template_deploy.py153
-rw-r--r--ansible_collections/cisco/mso/requirements.txt1
-rw-r--r--ansible_collections/cisco/mso/tests/.DS_Storebin0 -> 6148 bytes
-rw-r--r--ansible_collections/cisco/mso/tests/.gitignore1
-rw-r--r--ansible_collections/cisco/mso/tests/integration/inventory.networking36
-rw-r--r--ansible_collections/cisco/mso/tests/integration/network-integration.requirements.txt1
-rw-r--r--ansible_collections/cisco/mso/tests/integration/target-prefixes.network2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_backup/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_backup/tasks/main.yml546
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_backup_schedule/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_backup_schedule/tasks/main.yml266
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_option_policy/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_option_policy/tasks/main.yaml298
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_option_policy_option/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_option_policy_option/tasks/main.yaml444
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_relay_policy/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_relay_policy/tasks/main.yaml270
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_relay_policy_provider/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_relay_policy_provider/tasks/main.yaml662
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_label/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_label/tasks/main.yml411
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/pki/rsa39
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/pki/rsa-passphrase42
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/pki/rsa-passphrase.pub1
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/pki/rsa.pub1
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/tasks/main.yml303
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_rest/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/error_handling.yml153
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/json_inline.yml289
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/json_string.yml224
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/json_template.yml67
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/main.yml28
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/tenant.json.j210
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/yaml_inline.yml214
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/yaml_string.yml214
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_role/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_role/tasks/main.yml44
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_role/tasks/role-ro.yml88
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_role/tasks/role-rw.yml275
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema/tasks/main.yml117
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_clone/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_clone/tasks/main.yml158
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site/tasks/main.yml273
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp/tasks/main.yml481
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg/tasks/main.yml705
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_bulk_staticport/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_bulk_staticport/tasks/main.yml774
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_domain/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_domain/tasks/main.yml1068
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_selector/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_selector/tasks/main.yml1103
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_staticport/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_staticport/tasks/main.yml870
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd/tasks/main.yml749
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd_l3out/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd_l3out/tasks/main.yml447
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd_subnet/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd_subnet/tasks/main.yml665
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_external_epg/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_external_epg/tasks/main.yml833
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_external_epg_selector/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_external_epg_selector/tasks/main.yml533
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_l3out/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_l3out/tasks/main.yml614
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_service_graph/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_service_graph/tasks/main.yml795
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region/tasks/main.yml588
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_cidr/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_cidr/tasks/main.yml721
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_cidr_subnet/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_cidr_subnet/tasks/main.yml886
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_hub_network/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_hub_network/tasks/hub_network.yml719
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_hub_network/tasks/main.yml32
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template/tasks/main.yml391
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp/tasks/main.yml311
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg/tasks/main.yml1263
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg_contract/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg_contract/tasks/main.yml620
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg_selector/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg_selector/tasks/main.yml794
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd/tasks/main.yml1907
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd_dhcp_policy/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd_dhcp_policy/tasks/main.yml454
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd_subnet/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd_subnet/tasks/main.yml630
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_clone/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_clone/tasks/main.yml293
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_contract_filter/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_contract_filter/tasks/main.yml1088
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_contract_service_graph/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_contract_service_graph/tasks/main.yml744
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_deploy/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_deploy/tasks/main.yml237
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_deploy_status/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_deploy_status/tasks/main.yml627
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg/tasks/main.yml1158
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_contract/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_contract/tasks/main.yml645
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_selector/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_selector/tasks/main.yml452
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_subnet/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_subnet/tasks/main.yml536
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_filter_entry/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_filter_entry/tasks/main.yml326
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_l3out/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_l3out/tasks/main.yml265
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_migrate/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_migrate/tasks/main.yml383
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_service_graph/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_service_graph/tasks/main.yml396
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_vrf/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_vrf/tasks/main.yml542
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_vrf_contract/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_vrf_contract/tasks/main.yml859
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_validate/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_schema_validate/tasks/main.yml251
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_service_node_type/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_service_node_type/tasks/main.yml199
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_site/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_site/tasks/connectivity.j2489
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_site/tasks/main.yml565
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_tenant/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_tenant/tasks/main.yml758
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_tenant_site/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_tenant_site/tasks/main.yml679
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_user/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_user/tasks/main.yml511
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_version/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/mso_version/tasks/main.yml96
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/ndo_schema_template_deploy/aliases2
-rw-r--r--ansible_collections/cisco/mso/tests/integration/targets/ndo_schema_template_deploy/tasks/main.yml407
-rw-r--r--ansible_collections/cisco/mso/tests/sanity/requirements.txt5
238 files changed, 62167 insertions, 0 deletions
diff --git a/ansible_collections/cisco/mso/.DS_Store b/ansible_collections/cisco/mso/.DS_Store
new file mode 100644
index 000000000..9538dd02f
--- /dev/null
+++ b/ansible_collections/cisco/mso/.DS_Store
Binary files differ
diff --git a/ansible_collections/cisco/mso/.github/.DS_Store b/ansible_collections/cisco/mso/.github/.DS_Store
new file mode 100644
index 000000000..1d3c85241
--- /dev/null
+++ b/ansible_collections/cisco/mso/.github/.DS_Store
Binary files differ
diff --git a/ansible_collections/cisco/mso/.github/ISSUE_TEMPLATE/Bug_Report.md b/ansible_collections/cisco/mso/.github/ISSUE_TEMPLATE/Bug_Report.md
new file mode 100644
index 000000000..f914a17c5
--- /dev/null
+++ b/ansible_collections/cisco/mso/.github/ISSUE_TEMPLATE/Bug_Report.md
@@ -0,0 +1,80 @@
+---
+name: 🐛 Bug Report
+about: If something isn't working as expected 🤔.
+labels: bug
+---
+
+<!--- Please keep this note for the community --->
+
+### Community Note
+
+* Please vote on this issue by adding a 👍 [reaction](https://blog.github.com/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) to the original issue to help the community and maintainers prioritize this request
+* Please do not leave "+1" or other comments that do not add relevant new information or questions, they generate extra noise for issue followers and do not help prioritize the request
+* If you are interested in working on this issue or have submitted a pull request, please leave a comment
+
+<!--- Thank you for keeping this note for the community --->
+
+### Description
+<!--- Please specify additional labels in the labels section if applicable to the issue.
+Possible additional labels: documentation/question --->
+
+<!--- Please leave a helpful description of the feature request here. --->
+
+* xxxx
+
+### Affected Module Name(s):
+
+<!--- Please list the affected module name(s). --->
+
+* mso_XXXXX
+
+### MSO version and MSO Platform
+
+* V x.x.x and Docker Swarm OVA-based/SE-based/ND-based/all.
+
+
+### APIC version and APIC Platform for Site Level Resources
+
+* V x.x.x and on-prem/cloud-aws/cloud-azure/all.
+
+
+### Collection versions
+
+* cisco.mso x.x.x
+
+### Output/ Error message
+
+<!---
+Please provide the generated error message.
+--->
+*
+
+### Expected Behavior
+
+<!--- What should have happened? --->
+*
+
+### Actual Behavior
+
+<!--- What actually happened? --->
+*
+
+### Playbook tasks to Reproduce
+
+<!--- Please list the playbook tasks required to reproduce the issue. --->
+
+*
+
+### Important Factoids
+
+<!--- Are there anything atypical about your setup that we should know? For example: Same task runs on different version of MSO? --->
+
+### References
+
+<!---
+Information about referencing Github Issues: https://help.github.com/articles/basic-writing-and-formatting-syntax/#referencing-issues-and-pull-requests
+
+Are there any other GitHub issues (open or closed) or pull requests that should be linked here? Vendor documentation? For example:
+--->
+
+* #0000 \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/.github/ISSUE_TEMPLATE/Feature_Request.md b/ansible_collections/cisco/mso/.github/ISSUE_TEMPLATE/Feature_Request.md
new file mode 100644
index 000000000..c1bb98962
--- /dev/null
+++ b/ansible_collections/cisco/mso/.github/ISSUE_TEMPLATE/Feature_Request.md
@@ -0,0 +1,54 @@
+---
+name: 🚀 Feature Request
+about: I have a suggestion (might want to implement it myself 🙂)!
+labels: enhancement
+---
+
+<!--- Please keep this note for the community --->
+
+### Community Note
+
+* Please vote on this issue by adding a 👍 [reaction](https://blog.github.com/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) to the original issue to help the community and maintainers prioritize this request
+* Please do not leave "+1" or other comments that do not add relevant new information or questions, they generate extra noise for issue followers and do not help prioritize the request
+* If you are interested in working on this issue or have submitted a pull request, please leave a comment
+
+<!--- Thank you for keeping this note for the community --->
+
+### Description
+<!--- Please specify additional labels in the labels section if applicable to the issue.
+Possible additional labels: new_module/new_plugin/documentation/feature --->
+
+
+<!--- Please leave a helpful description of the feature request here. --->
+
+* xxxx
+
+### New or Affected Module(s):
+
+<!--- Please list the new or affected module(s). --->
+
+* mso_XXXXX
+
+### MSO version and MSO Platform
+
+* V x.x.x and Docker Swarm OVA-based/SE-based/ND-based/all.
+
+
+### APIC version and APIC Platform for Site Level Resources
+
+* V x.x.x and on-prem/cloud-aws/cloud-azure/all.
+
+### Collection versions
+
+* cisco.mso x.x.x
+
+### References
+
+<!---
+Information about referencing Github Issues: https://help.github.com/articles/basic-writing-and-formatting-syntax/#referencing-issues-and-pull-requests
+
+Are there any other GitHub issues (open or closed) or pull requests that should be linked here? Vendor blog posts or documentation? For example:
+
+--->
+
+* #0000 \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/.github/ISSUE_TEMPLATE/config.yml b/ansible_collections/cisco/mso/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 000000000..ec4bb386b
--- /dev/null
+++ b/ansible_collections/cisco/mso/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1 @@
+blank_issues_enabled: false \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/.github/workflows/ansible-test.yml b/ansible_collections/cisco/mso/.github/workflows/ansible-test.yml
new file mode 100644
index 000000000..08ed31bba
--- /dev/null
+++ b/ansible_collections/cisco/mso/.github/workflows/ansible-test.yml
@@ -0,0 +1,249 @@
+name: CI
+on:
+ push:
+ branches: master
+ pull_request:
+ schedule:
+ # * is a special character in YAML so you have to quote this string
+ - cron: '0 7 * * *'
+env:
+ python_version: 3.9
+jobs:
+ build:
+ name: Build collection
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ ansible: [v2.9.27, v2.10.17, v2.11.12, v2.12.10, stable-2.13, stable-2.14]
+ steps:
+ - name: Check out code
+ uses: actions/checkout@v3
+
+ - name: Set up Python ${{ env.python_version }}
+ uses: actions/setup-python@v4
+ with:
+ python-version: ${{ env.python_version }}
+
+ - name: Install ansible-base (${{ matrix.ansible }})
+ run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check
+
+ - name: Build a collection tarball
+ run: ansible-galaxy collection build --output-path "${GITHUB_WORKSPACE}/.cache/collection-tarballs"
+
+ - name: Store migrated collection artifacts
+ uses: actions/upload-artifact@v3
+ with:
+ name: collection
+ path: .cache/collection-tarballs
+
+ black-formating:
+ name: Using Black to check formating
+ runs-on: ubuntu-latest
+ continue-on-error: true
+ strategy:
+ fail-fast: false
+ matrix:
+ experimental: [true]
+ steps:
+ - name: Check out code
+ uses: actions/checkout@v3
+
+ - name: Run black against code
+ uses: psf/black@stable
+ with:
+ options: "--check --diff --color -l 159"
+
+ importer:
+ name: Galaxy-importer check
+ needs:
+ - build
+ runs-on: ubuntu-latest
+ steps:
+ - name: Set up Python ${{ env.python_version }}
+ uses: actions/setup-python@v4
+ with:
+ python-version: ${{ env.python_version }}
+
+ - name: Install ansible-base (stable-2.14)
+ run: pip install https://github.com/ansible/ansible/archive/stable-2.14.tar.gz --disable-pip-version-check
+
+ - name: Download migrated collection artifacts
+ uses: actions/download-artifact@v3
+ with:
+ name: collection
+ path: .cache/collection-tarballs
+
+ - name: Install the collection tarball
+ run: ansible-galaxy collection install .cache/collection-tarballs/*.tar.gz
+
+ - name: Install galaxy-importer
+ run: pip install galaxy-importer
+
+ - name: Create galaxy-importer directory
+ run: sudo mkdir -p /etc/galaxy-importer
+
+ - name: Create galaxy-importer.cfg
+ run: sudo cp /home/runner/.ansible/collections/ansible_collections/cisco/mso/.github/workflows/galaxy-importer.cfg /etc/galaxy-importer/galaxy-importer.cfg
+
+ - name: Run galaxy-importer check
+ run: python -m galaxy_importer.main .cache/collection-tarballs/cisco-*.tar.gz | tee .cache/collection-tarballs/log.txt && sudo cp ./importer_result.json .cache/collection-tarballs/importer_result.json
+
+ - name: Check warnings and errors
+ run: if grep -E 'WARNING|ERROR' .cache/collection-tarballs/log.txt; then exit 1; else exit 0; fi
+
+ - name: Store galaxy_importer check log file
+ uses: actions/upload-artifact@v3
+ with:
+ name: galaxy-importer-log
+ path: .cache/collection-tarballs/importer_result.json
+
+
+ sanity:
+ name: Sanity in ubuntu-latest
+ needs:
+ - build
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ ansible: [v2.9.27, v2.10.17, v2.11.12, v2.12.10, stable-2.13, stable-2.14]
+ steps:
+ - name: Set up Python ${{ env.python_version }}
+ uses: actions/setup-python@v4
+ with:
+ python-version: ${{ env.python_version }}
+
+ - name: Install ansible-base (${{ matrix.ansible }})
+ run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check
+
+ - name: Install coverage (v4.5.4)
+ run: pip install coverage==4.5.4
+
+ - name: Download migrated collection artifacts
+ uses: actions/download-artifact@v3
+ with:
+ name: collection
+ path: .cache/collection-tarballs
+
+ - name: Install the collection tarball
+ run: ansible-galaxy collection install .cache/collection-tarballs/*.tar.gz
+
+ - name: Run sanity tests
+ run: ansible-test sanity --docker -v --color --truncate 0 --coverage
+ working-directory: /home/runner/.ansible/collections/ansible_collections/cisco/mso
+
+ - name: Generate coverage report
+ run: ansible-test coverage xml -v --requirements --group-by command --group-by version
+ working-directory: /home/runner/.ansible/collections/ansible_collections/cisco/mso
+
+ - name: Push coverate report to codecov.io
+ run: bash <(curl -s https://codecov.io/bash) -s 'tests/output/reports/' -F sanity
+ working-directory: /home/runner/.ansible/collections/ansible_collections/cisco/mso
+
+ units:
+ name: Units in ubuntu-latest
+ needs:
+ - build
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ ansible: [v2.10.17, v2.11.12, v2.12.10, stable-2.13, stable-2.14]
+ python-version: [3.9]
+ include:
+ - ansible: v2.9.27
+ python-version: 3.8
+ steps:
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v4
+ with:
+ python-version: ${{ matrix.python-version }}
+
+ - name: Install ansible-base (${{ matrix.ansible }})
+ run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check
+
+ - name: Install coverage (v4.5.4)
+ run: pip install coverage==4.5.4
+
+ - name: Download migrated collection artifacts
+ uses: actions/download-artifact@v3
+ with:
+ name: collection
+ path: .cache/collection-tarballs
+
+ - name: Install the collection tarball
+ run: ansible-galaxy collection install .cache/collection-tarballs/*.tar.gz
+
+ # - name: Run unit tests
+ # run: ansible-test units --docker -v --color --truncate 0 --python ${{ matrix.python-version }} --coverage
+ # working-directory: /home/runner/.ansible/collections/ansible_collections/cisco/mso
+
+ # - name: Generate coverage report.
+ # run: ansible-test coverage xml -v --requirements --group-by command --group-by version
+ # working-directory: /home/runner/.ansible/collections/ansible_collections/cisco/mso
+
+ # - name: Push coverate report to codecov.io
+ # run: bash <(curl -s https://codecov.io/bash) -s 'tests/output/reports/' -F unit
+ # working-directory: /home/runner/.ansible/collections/ansible_collections/cisco/mso
+
+ integration:
+ name: Integration in ubuntu-latest
+ needs:
+ - build
+ runs-on: ubuntu-latest
+ steps:
+ - name: Set up Python ${{ env.python_version }}
+ uses: actions/setup-python@v4
+ with:
+ python-version: ${{ env.python_version }}
+
+ - name: Install ansible-base (stable-2.13)
+ run: pip install https://github.com/ansible/ansible/archive/stable-2.13.tar.gz --disable-pip-version-check
+
+ - name: Install coverage (v4.5.4)
+ run: pip install coverage==4.5.4
+
+ - name: Download migrated collection artifacts
+ uses: actions/download-artifact@v3
+ with:
+ name: collection
+ path: .cache/collection-tarballs
+
+ - name: Install the collection tarball
+ run: ansible-galaxy collection install .cache/collection-tarballs/*.tar.gz
+
+ - name: Install the ND collection (NDO dependency)
+ run: ansible-galaxy collection install cisco.nd
+
+ - name: Install the ACI collection (test case dependency)
+ run: ansible-galaxy collection install cisco.aci
+
+ - name: Requesting integration mutex
+ uses: nev7n/wait_for_response@v1
+ with:
+ url: ${{ format('https://8v7s765ibh.execute-api.us-west-1.amazonaws.com/v1/ansible-mso?repo={0}&run_id={1}', github.repository, github.run_id) }}
+ responseCode: 200
+ timeout: 2000000
+ interval: 5000
+
+ - name: Run integration tests on Python ${{ env.python_version }}
+ run: ansible-test network-integration --docker -v --color --retry-on-error --python ${{ env.python_version }} --truncate 0 --continue-on-error --coverage
+ working-directory: /home/runner/.ansible/collections/ansible_collections/cisco/mso
+
+ - name: Releasing integration mutex
+ uses: nev7n/wait_for_response@v1
+ if: always()
+ with:
+ url: ${{ format('https://8v7s765ibh.execute-api.us-west-1.amazonaws.com/v1/ansible-mso/release?repo={0}&run_id={1}', github.repository, github.run_id) }}
+ responseCode: 200
+
+ - name: Generate coverage report
+ if: always()
+ run: ansible-test coverage xml -v --requirements --group-by command --group-by version
+ working-directory: /home/runner/.ansible/collections/ansible_collections/cisco/mso
+
+ - name: Push coverate report to codecov.io
+ if: always()
+ run: bash <(curl -s https://codecov.io/bash) -s 'tests/output/reports/' -F integration
+ working-directory: /home/runner/.ansible/collections/ansible_collections/cisco/mso
diff --git a/ansible_collections/cisco/mso/.github/workflows/changelog-generation.yml b/ansible_collections/cisco/mso/.github/workflows/changelog-generation.yml
new file mode 100644
index 000000000..a6342e23b
--- /dev/null
+++ b/ansible_collections/cisco/mso/.github/workflows/changelog-generation.yml
@@ -0,0 +1,32 @@
+name: Generate Changelog
+on:
+ push:
+ branches: master
+jobs:
+ generate-changelog:
+ name: Run automation script
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check out script code
+ uses: actions/checkout@v3
+ with:
+ repository: ciscoecosystem/release_script
+ path: ./release_script
+
+ - name: Check out collection code
+ uses: actions/checkout@v3
+ with:
+ path: ./collection
+
+ - name: Set up Python 3.9
+ uses: actions/setup-python@v4
+ with:
+ python-version: 3.9
+
+ - name: Install release script requirements
+ run: pip install -r ./release_script/requirements.txt
+
+ - name: Run automation script
+ run: python ./release_script/script.py
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/ansible_collections/cisco/mso/.github/workflows/codeql-analysis.yml b/ansible_collections/cisco/mso/.github/workflows/codeql-analysis.yml
new file mode 100644
index 000000000..76aa96c9b
--- /dev/null
+++ b/ansible_collections/cisco/mso/.github/workflows/codeql-analysis.yml
@@ -0,0 +1,43 @@
+name: "CodeQL"
+
+on:
+ push:
+ branches: [ "master" ]
+
+ schedule:
+ - cron: '40 8 * * 5'
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ 'python' ]
+ # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
+ # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v2
+ with:
+ languages: ${{ matrix.language }}
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
+
+ # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
+ # queries: security-extended,security-and-quality
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v2
diff --git a/ansible_collections/cisco/mso/.github/workflows/galaxy-importer.cfg b/ansible_collections/cisco/mso/.github/workflows/galaxy-importer.cfg
new file mode 100644
index 000000000..631359cf4
--- /dev/null
+++ b/ansible_collections/cisco/mso/.github/workflows/galaxy-importer.cfg
@@ -0,0 +1,9 @@
+[galaxy-importer]
+LOG_LEVEL_MAIN = INFO
+RUN_FLAKE8 = True
+RUN_ANSIBLE_DOC = True
+RUN_ANSIBLE_LINT = True
+RUN_ANSIBLE_TEST = False
+ANSIBLE_TEST_LOCAL_IMAGE = False
+LOCAL_IMAGE_DOCKER = False
+INFRA_OSD = False \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/.gitignore b/ansible_collections/cisco/mso/.gitignore
new file mode 100644
index 000000000..d984fa808
--- /dev/null
+++ b/ansible_collections/cisco/mso/.gitignore
@@ -0,0 +1,394 @@
+
+# Created by https://www.gitignore.io/api/git,linux,pydev,python,windows,pycharm+all,jupyternotebook,vim,webstorm,emacs,dotenv
+# Edit at https://www.gitignore.io/?templates=git,linux,pydev,python,windows,pycharm+all,jupyternotebook,vim,webstorm,emacs,dotenv
+
+### dotenv ###
+.env
+
+### Emacs ###
+# -*- mode: gitignore; -*-
+*~
+\#*\#
+/.emacs.desktop
+/.emacs.desktop.lock
+*.elc
+auto-save-list
+tramp
+.\#*
+
+# Org-mode
+.org-id-locations
+*_archive
+
+# flymake-mode
+*_flymake.*
+
+# eshell files
+/eshell/history
+/eshell/lastdir
+
+# elpa packages
+/elpa/
+
+# reftex files
+*.rel
+
+# AUCTeX auto folder
+/auto/
+
+# cask packages
+.cask/
+dist/
+
+# Flycheck
+flycheck_*.el
+
+# server auth directory
+/server/
+
+# projectiles files
+.projectile
+
+# directory configuration
+.dir-locals.el
+
+# network security
+/network-security.data
+
+
+### Git ###
+# Created by git for backups. To disable backups in Git:
+# $ git config --global mergetool.keepBackup false
+*.orig
+
+# Created by git when using merge tools for conflicts
+*.BACKUP.*
+*.BASE.*
+*.LOCAL.*
+*.REMOTE.*
+*_BACKUP_*.txt
+*_BASE_*.txt
+*_LOCAL_*.txt
+*_REMOTE_*.txt
+
+#!! ERROR: jupyternotebook is undefined. Use list command to see defined gitignore types !!#
+
+### Linux ###
+
+# temporary files which can be created if a process still has a handle open of a deleted file
+.fuse_hidden*
+
+# KDE directory preferences
+.directory
+
+# Linux trash folder which might appear on any partition or disk
+.Trash-*
+
+# .nfs files are created when an open file is removed but is still being accessed
+.nfs*
+
+### PyCharm+all ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+.DS_Store
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### PyCharm+all Patch ###
+# Ignores the whole .idea folder and all .iml files
+# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
+
+.idea/
+
+# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
+
+*.iml
+modules.xml
+.idea/misc.xml
+*.ipr
+
+# Sonarlint plugin
+.idea/sonarlint
+
+### pydev ###
+.pydevproject
+
+### Python ###
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+pip-wheel-metadata/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+.hypothesis/
+.pytest_cache/
+
+# Translations
+*.mo
+*.pot
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# pyenv
+.python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# celery beat schedule file
+celerybeat-schedule
+
+# SageMath parsed files
+*.sage.py
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# Mr Developer
+.mr.developer.cfg
+.project
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+### Vim ###
+# Swap
+[._]*.s[a-v][a-z]
+[._]*.sw[a-p]
+[._]s[a-rt-v][a-z]
+[._]ss[a-gi-z]
+[._]sw[a-p]
+
+# Session
+Session.vim
+Sessionx.vim
+
+# Temporary
+.netrwhist
+# Auto-generated tag files
+tags
+# Persistent undo
+[._]*.un~
+
+### WebStorm ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+
+# Generated files
+
+# Sensitive or high-churn files
+
+# Gradle
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+
+# Mongo Explorer plugin
+
+# File-based project format
+
+# IntelliJ
+
+# mpeltonen/sbt-idea plugin
+
+# JIRA plugin
+
+# Cursive Clojure plugin
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+
+# Editor-based Rest Client
+
+# Android studio 3.1+ serialized cache file
+
+### WebStorm Patch ###
+# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
+
+# *.iml
+# modules.xml
+# .idea/misc.xml
+# *.ipr
+
+# Sonarlint plugin
+.idea/**/sonarlint/
+
+# SonarQube Plugin
+.idea/**/sonarIssues.xml
+
+# Markdown Navigator plugin
+.idea/**/markdown-navigator.xml
+.idea/**/markdown-navigator/
+
+### Windows ###
+# Windows thumbnail cache files
+Thumbs.db
+Thumbs.db:encryptable
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# End of https://www.gitignore.io/api/git,linux,pydev,python,windows,pycharm+all,jupyternotebook,vim,webstorm,emacs,dotenv
+
+# vsCode
+.vscode
+
+# Ansible Collection tarball
+cisco-mso-*.tar.gz
diff --git a/ansible_collections/cisco/mso/.vscode/settings.json b/ansible_collections/cisco/mso/.vscode/settings.json
new file mode 100644
index 000000000..1d0d5ec8f
--- /dev/null
+++ b/ansible_collections/cisco/mso/.vscode/settings.json
@@ -0,0 +1,18 @@
+{
+ "python.linting.flake8Enabled": true,
+ "python.linting.enabled": true,
+ "python.linting.pylintArgs": [
+ "--max-line-length=160"
+ ],
+ "python.formatting.autopep8Args": [
+ "--max-line-length=160"
+ ],
+ "python.linting.flake8Args": [
+ "--max-line-length=160"
+ ],
+ "python.linting.pycodestyleArgs": [
+ "--max-line-length=160"
+ ],
+ "python.linting.pycodestyleEnabled": true,
+ "terminal.integrated.scrollback": 100000,
+} \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/CHANGELOG.rst b/ansible_collections/cisco/mso/CHANGELOG.rst
new file mode 100644
index 000000000..b7b32a9e4
--- /dev/null
+++ b/ansible_collections/cisco/mso/CHANGELOG.rst
@@ -0,0 +1,481 @@
+==========================================
+Cisco MSO Ansible Collection Release Notes
+==========================================
+
+.. contents:: Topics
+
+This changelog describes changes after version 0.0.4.
+
+v2.4.0
+======
+
+Release Summary
+---------------
+
+Release v2.4.0 of the ``ansible-mso`` collection on 2023-04-19.
+This changelog describes all changes made to the modules and plugins included in this collection since v2.3.0.
+
+
+Minor Changes
+-------------
+
+- Add ip_data_plane_learning and preferred_group arguments to mso_schema_template_vrf module (#358)
+
+Bugfixes
+--------
+
+- Add attributes to payload for changed schema behaviour of deploymentImmediacy (deployImmediacy) and vmmDomainProperties (properties at domain level in payload) (#362)
+- Fix mso_backup for NDO and ND-based MSO v3.2+ (#333)
+- Fix validation condition for path in mso_schema_site_anp_epg_bulk_staticport module (#360)
+
+v2.3.0
+======
+
+Release Summary
+---------------
+
+Release v2.3.0 of the ``ansible-mso`` collection on 2023-03-30.
+This changelog describes all changes made to the modules and plugins included in this collection since v2.2.1.
+
+
+Minor Changes
+-------------
+
+- Add module mso_schema_site_anp_epg_bulk_staticport (#330)
+- Add route_reachability attribute to mso_schema_site_external_epg module (#335)
+
+Bugfixes
+--------
+
+- Fix idempotency for mso_schema_site_bd_l3out
+
+v2.2.1
+======
+
+Release Summary
+---------------
+
+Release v2.2.1 of the ``ansible-mso`` collection on 2023-01-31.
+This changelog describes all changes made to the modules and plugins included in this collection since v2.2.0.
+
+
+Bugfixes
+--------
+
+- Fix datetime support for python2.7 in mso_backup_schedule (#323)
+
+v2.2.0
+======
+
+Release Summary
+---------------
+
+Release v2.2.0 of the ``ansible-mso`` collection on 2023-01-29.
+This changelog describes all changes made to the modules and plugins included in this collection since v2.1.0.
+
+
+Minor Changes
+-------------
+
+- Add automatic creation of site bd when not existing in mso_schema_site_bd_subnet module (#263)
+- Add automatic schema validation functionality to mso_schema_template_deploy and ndo_schema_template_deploy (#318)
+- Add ndo_schema_template_deploy to support NDO 4+ deploy functionality (#305)
+- Add support for l3out from different template or schema in mso_schema_site_bd_l3out (#304)
+- Add support for orchestrator_only attribute for mso_tenant with state absent (#268)
+
+Bugfixes
+--------
+
+- Fix MSO HTTPAPI plugin login domain issue (#317)
+- Fix deploymentImmediacy key inconsistency in the API used by mso_schema_site_anp and mso_schema_site_anp_epg (#283)
+- Fix mso_schema_template_bd issue when created with unicast_routing as false (#278)
+- Fix to be able to add multiple filter and filters with "-" in their names (#306)
+
+v2.1.0
+======
+
+Release Summary
+---------------
+
+Release v2.1.0 of the ``ansible-mso`` collection on 2022-10-14.
+This changelog describes all changes made to the modules and plugins included in this collection since v1.4.0.
+The version was bumped directly to 2.1.0 due to a previous collection upload issue on galaxy.
+
+
+Minor Changes
+-------------
+
+- Add aci_remote_location module (#259)
+- Add mso_backup_schedule module (#250)
+- Add mso_chema_template_contract_service_graph module (#257)
+- Add mso_schema_template_service_graph, mso_schema_site_service_graph and mso_service_node_type modules (#243)
+- Add primary attribute to mso_schema_site_bd_subnet (#254)
+
+Deprecated Features
+-------------------
+
+- The mso_schema_template_contract_filter contract_filter_type attribute is deprecated. The value is now deduced from filter_type.
+
+Bugfixes
+--------
+
+- Fix time issue when host running ansible is in a different timezone then NDO
+- Remove mso_guide from notes
+
+v1.4.0
+======
+
+Release Summary
+---------------
+
+Release v1.4.0 of the ``ansible-mso`` collection on 2022-03-15.
+This changelog describes all changes made to the modules and plugins included in this collection since v1.3.0.
+
+
+Minor Changes
+-------------
+
+- Update mso_schema_template_clone to use new method from NDO and unrestrict it to earlier version
+
+Bugfixes
+--------
+
+- Fix arp_entry value issue in mso_schema_template_filter_entry
+- Fix mso_schema_site_anp idempotency when children exists
+- Fix use_ssl documentation to explain usage when used with HTTPAPI connection plugin
+
+v1.3.0
+======
+
+Release Summary
+---------------
+
+Release v1.3.0 of the ``cisco.mso`` collection on 2021-12-18.
+This changelog describes all changes made to the modules and plugins included in this collection since v1.2.0.
+
+
+Minor Changes
+-------------
+
+- Add container_overlay and underlay_context_profile support to mso_schema_site_vrf_region
+- Add description support to various modules
+- Add hosted_vrf support to mso_schema_site_vrf_region_cidr_subnet
+- Add module mso_schema_validate to check schema validations
+- Add private_link_label support to mso_schema_site_anp_epg and mso_schema_site_vrf_region_cidr_subnet
+- Add qos_level and Service EPG support to mso_schema_template_anp_epg
+- Add qos_level, action and priority support to mso_schema_template_contract_filter
+- Add schema and template description support to mso_schema_template
+- Add subnet as primary support to mso_schema_template_bd_subnet
+- Add support for automatically creating anp structure at site level when using mso_schema_site_anp_epg
+- Add support for encap-flood as multi_destination_flooding in mso_schema_template_bd
+- Add test file for mso_schema_site_anp, mso_schema_site_anp_epg, mso_schema_template_external_epg_subnet mso_schema_template_filter_entry
+- Improve scope attribute documentation in mso_schema_template_external_epg_subnet
+- Update Ansible version used in automated testing to v2.9.27, v2.10.16 and addition of v2.11.7 and v2.12.1
+
+Bugfixes
+--------
+
+- Add no_log to aws_access_key and secret_key in mso_tenant_site
+- Fix MSO HTTP API to work without host, user and password module attribute
+- Fix issue with unicast_routing idemptotency in mso_schema_template_bd
+- Fix mso_schema_site_anp and mso_schema_site_anp_epg idempotency issue
+- Remove sanity ignore files and fix sanity issues that were previously ignored
+
+v1.2.0
+======
+
+Release Summary
+---------------
+
+Release v1.2.0 of the ``cisco.mso`` collection on 2021-06-02.
+This changelog describes all changes made to the modules and plugins included in this collection since v1.1.0.
+
+
+Minor Changes
+-------------
+
+- Add Ansible common HTTPAPI dependancy in galaxy.yml
+- Add HTTPAPI connection plugin support and HTTPAPI MSO connection plugin
+- Add primary and unicast_routing attributes to mso_schema_template_bd
+- Add requirements.txt for Ansible Environment support
+- Add schema and template cloning modules mso_schema_clone and mso_schema_template_clone
+- Add support cisco.nd.nd connection plugin
+- Add support for multiple DCHP policies in a BD and new module mso_schema_template_bd_dhcp_policy
+- Upgrade CI to latest Ansible version and Python 3.8
+
+Bugfixes
+--------
+
+- Add test case and small fixes to mso_schema_site_bd_l3out module
+- Fix documentation issues accross modules
+- Fix fail_json usage accross module_utils/mso.py
+- Fix mso_rest to support HTTPAPI plugin and tests to support ND platform
+- Fix mso_user to due to error in v1 API in MSO 3.2
+- Fix path issue in mso_schema_template_migrate
+- Fixes for site level external epgs and site level L3Outs
+- Fixes to support MSO 3.3
+- Remove query of all schemas to get schema ID and only query schema ID indentity list API
+
+New Plugins
+-----------
+
+Httpapi
+~~~~~~~
+
+- cisco.mso.mso - MSO Ansible HTTPAPI Plugin.
+
+v1.1.0
+======
+
+Release Summary
+---------------
+
+Release v1.1.0 of the ``cisco.mso`` collection on 2021-01-20.
+This changelog describes all changes made to the modules and plugins included in this collection since v1.0.1.
+
+
+Minor Changes
+-------------
+
+- Add DHCP Policy Operations
+- Add SVI MAC Addreess option in mso_schema_site_bd
+- Add additional test file to add tenant from templated payload file
+- Add attribute virtual_ip to mso_schema_site_bd_subnet
+- Add capability for restore and download backup
+- Add capability to upload backup
+- Add check for undeploy under MSO version
+- Add error handeling test file
+- Add error message to display when yaml has failed to load
+- Add galaxy-importer check
+- Add galaxy-importer config
+- Add mso_dhcp_option_policy and mso_dhcp_option_policy_option and test files
+- Add new module mso_rest and test case files to support GET api method
+- Add new options to template bd and updated test file
+- Add notes to use region_cidr module to create region
+- Add task to undeploy the template from the site
+- Add tasks in test file to remove templates for mso_schema_template_migrate
+- Add test case for schema removing
+- Add test cases to verify GET, PUT, POST and DELETE API methods for sites in mso_rest.py
+- Add test file for mso_schema
+- Add test file for mso_schema_template_anp
+- Add test file for region module
+- Add test files yaml_inline and yaml_string to support YAML
+- Add userAssociations to tenants to resolve CI issues
+- Addition of cloud setting for ext epg
+- Changes made to payload of mso_schema_template_external_epg
+- Changes to options in template bd
+- Check warning
+- Documentation Corrected
+- Force arp flood to be true when l2unkwunicast is flood
+- Make changes to display correct status code
+- Modify mso library and updated test file
+- Modify mso_rest test files to make PATCH available, and test other methods against schemas
+- Move options for subnet from mso to the template_bd_subnet module
+- Python lint corrected
+- Redirect log to both stdout and log.txt file & Check warnings and errors
+- Remove creation example in document of mso_schema_site_vrf_region
+- Remove present state from mso_schema module
+- Removed unused variable in mso_schema_site_vrf_region_hub_network
+- Test DHCP Policy Provider added
+- Test file for mso_dhcp_relay_policy added
+- Test file for template_bd_subnet and new option foe module
+
+Bugfixes
+--------
+
+- Fix anp idempotency issue
+- Fix crash issue when using irrelevant site-template
+- Fix default value for mso_schema state parameter
+- Fix examples for mso_schema
+- Fix galaxy-importer check warnings
+- Fix issue on mso_schema_site_vrf_region_cidr_subnet to allow an AWS subnet to be used for a TGW Attachment (Hub Network)
+- Fix module name in example of mso_schema_site_vrf_region
+- Fix mso_backup upload issue
+- Fix sanity test error mso_schema_site_bd
+- Fix some coding standard and improvements to contributed mso_dhcp_relay modules and test files
+- Fix space in asssertion
+- Fix space in site_anp_epg_domain
+- Fix space in test file
+- Remove space from template name in all modules
+- Remove space in template name
+
+v1.0.1
+======
+
+Release Summary
+---------------
+
+Release v1.0.1 of the ``cisco.mso`` collection on 2020-10-30.
+This changelog describes all changes made to the modules and plugins included in this collection since v1.0.0.
+
+
+Minor Changes
+-------------
+
+- Add delete capability to mso_schema_site
+- Add env_fallback for mso_argument_spec params
+- Add non existing template deletion test
+- Add test file for mso_schema_template
+- Add test file for site_bd_subnet
+- Bump module to v1.0.1
+- Extent mso_tenant test case coverage
+
+Bugfixes
+--------
+
+- Fix default value for l2Stretch in mso_schema_template_bd module
+- Fix deletion of schema when wrong template is provided in single template schema
+- Fix examples in documentation for mso_schema_template_l3out and mso_user
+- Fix naming issue in deploy module
+- Remove author emails due to length restriction
+- Remove dead code branch in mso_schema_template
+
+v1.0.0
+======
+
+Release Summary
+---------------
+
+This is the first official release of the ``cisco.mso`` collection on 2020-08-18.
+This changelog describes all changes made to the modules and plugins included in this collection since Ansible 2.9.0.
+
+
+Minor Changes
+-------------
+
+- Add changelog
+- Fix M() and module to use FQCN
+- Update Ansible version in CI and add 2.10.0 to sanity in CI.
+- Update Readme with supported versions
+
+Bugfixes
+--------
+
+- Fix sanity issues to support 2.10.0
+
+v0.0.8
+======
+
+Release Summary
+---------------
+
+New release v0.0.8
+
+Minor Changes
+-------------
+
+- Add Login Domain support to mso_site
+- Add aliases file for contract_filter module
+- Add contract information in current and previous part
+- Add new module and test file to query MSO version
+- New backup module and test file (https://github.com/CiscoDevNet/ansible-mso/pull/80)
+- Renaming mso_schema_template_externalepg module to mso_schema_template_external_epg while keeping both working.
+- Update cidr module, udpate attributes in hub network module and its test file
+- Use a function to reuuse duplicate part
+
+Bugfixes
+--------
+
+- Add login_domain to existing test.
+- Add missing tests for VRF settings and changing those settings.
+- Add test for specifying read-only roles and increase overall test coverage of mso_user (https://github.com/CiscoDevNet/ansible-mso/pull/77)
+- Add test to mso_schema_template_vrf, mso_schema_template_external_epg and mso_schema_template_anp_epg to check for API error when pushing changes to object with existing contract.
+- Cleanup unused imports, unused variables and branches and change a variable from ambiguous name to reduce warnings at Ansible Galaxy import
+- Fix API error when pushing EPG with existing contracts
+- Fix role tests to work with pre/post 2.2.4 and re-enable them
+- Fix site issue if no site present and fix test issues with MSO v3.0
+- Fixing External EPG renaming for 2.9 and later
+- Fixing L3MCast test to pass on 2.2.4
+- Fixing wrong removal of schemas
+- Test hub network module after creating region manually
+- Updating Azure site IP in inventory and add second MSO version to inventory
+
+v0.0.7
+======
+
+Release Summary
+---------------
+
+New release v0.0.7
+
+Minor Changes
+-------------
+
+- Add l3out, preferred_group and test file for mso_schema_template_externalepg
+- Add mso_schema_template_vrf_contract module and test file
+- Add new attribute choice "policy_compression" to mso_Schema_template_contract_filter
+- Add new functionality - Direct Port Channel (dpc), micro-seg-vlan and default values
+- Add new module for anp-epg-selector in site level
+- Add new module mso_schema_template_anp_epg_selector and its test file
+- Add new module mso_schema_vrf_contract
+- Add new module mso_tenant_site to support cloud and non-cloud sites association with a tenant and test file (https://github.com/CiscoDevNet/ansible-mso/pull/62)
+- Add new mso_site_external_epg_selector module and test file
+- Add site external epg and contract filter test
+- Add support for VGW attribute in mso_schema_site_vrf_region_cidr_subnet
+- Add support to set account as inactive using account_status attribute in mso_user
+- Add test for mso_schema_site_vrf_region_cidr module
+- Add test for mso_schema_site_vrf_region_cidr_subnet module
+- Add vzAny attribute in mso_schema_template_vrf
+- Automatically add ANP and EPG at site level and new test file for mso_schema_site_anp_epg_staticport (https://github.com/CiscoDevNet/ansible-mso/pull/55)
+- Modified External EPG module and addition of new Selector module
+
+Bugfixes
+--------
+
+- Fix mso_schema_site_vrf_region_cidr to automatically create VRF and Region if not present at site level
+- Fix query condition when VRF or Region do not exist at site level
+- Remove unused regions attribute from mso_schema_template_vrf
+
+v0.0.6
+======
+
+Release Summary
+---------------
+
+New release v0.0.6
+
+Minor Changes
+-------------
+
+- ACI/MSO - Use get() dict lookups (https://github.com/ansible/ansible/pull/63074)
+- Add EPG and ANP at site level when needed
+- Add github action CI pipeline with test coverage
+- Add login domain support for authentication in all modules
+- Add support for DHCP querier to all subnet objects. Add partial test in mso_schema_template_bd integration test.
+- Add support for clean output if needed for debuging
+- Add test file for mso_schema_template_anp_epg
+- Added DHCP relay options and scope options to MSO schema template bd
+- Added ability to bind epg to static fex port
+- Added module to manage contracts for external EPG in Cisco MSO (https://github.com/ansible/ansible/pull/63550)
+- Added module to manage template external epg subnet for Cisco MSO (https://github.com/ansible/ansible/pull/63542)
+- Disabling tests for the role modules as API is not supported after 2.2.3i until further notice
+- Increased test coverage for existing module integration tests.
+- Modified fail messages for site and updated documentation
+- Moving test to Ansible v2.9.9 and increasing timelimit for mutex to 30+ min
+- Update authors.
+- Update mso_schema_site_anp.py (https://github.com/ansible/ansible/pull/67099)
+- Updated Test File Covering all conditions
+- mso_schema_site_anp_epg_staticport - Add VPC support (https://github.com/ansible/ansible/pull/62803)
+
+Bugfixes
+--------
+
+- Add aliases for backward support of permissions in role module.
+- Add integration test for mso_schema_template_db and fix un-needed push to API found by integration test.
+- Consistent object output on domain_associations
+- Fix EPG / External EPG Contract issue and create test for mso_schema_template_anp_epg_contract and mso_schema_template_external_epg_contract
+- Fix contract filter issue and add contract-filter test file
+- Fix duplicate user, add admin user to associated user list and update tenant test file
+- Fix intersite_multicast_source attribute issue in mso_schema_template_anp_epg and add the proxy_arp argument.
+- Fix mso_schema_template_anp_epg idempotancy for both EPG and EPG with contracts
+- Remove label with test domain before create it
+- Send context instead of vrf when vrf parameter is used
+- Update mso_schema_template_bd.py example for BD in another schema
+
+v0.0.5
+======
+
+Release Summary
+---------------
+
+New release v0.0.5
diff --git a/ansible_collections/cisco/mso/FILES.json b/ansible_collections/cisco/mso/FILES.json
new file mode 100644
index 000000000..1d891e57b
--- /dev/null
+++ b/ansible_collections/cisco/mso/FILES.json
@@ -0,0 +1,2637 @@
+{
+ "files": [
+ {
+ "name": ".",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "codecov.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e55a41874408d15b899f7df85fa9f4c6c3c476cf4a9e919b9a396c3f1aa1a1f0",
+ "format": 1
+ },
+ {
+ "name": ".DS_Store",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f675a3307852ecd3abb2cce8e8f2f39c607937538cc9a55749ddaac6154a4d9a",
+ "format": 1
+ },
+ {
+ "name": "LICENSE",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1c38eb11b6b8cd591aed90793ca80bd3c499393f207b96640ddfa571f61f7b4f",
+ "format": 1
+ },
+ {
+ "name": "requirements.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f78777e211f01bcaa37835d89fd0a8de3b9c1bf37d0a43be57991643a0ab7bcc",
+ "format": 1
+ },
+ {
+ "name": "plugins",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "plugins/.DS_Store",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "2cac03b3d027d06cc80e205126f9229303c5947e78f05fe29be390740df07f88",
+ "format": 1
+ },
+ {
+ "name": "plugins/doc_fragments",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "plugins/doc_fragments/modules.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "05e4cbb55e517d30ceb130dcc7c920b3ead10d31b3ae993d018c1364b7f17dfc",
+ "format": 1
+ },
+ {
+ "name": "plugins/httpapi",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "plugins/httpapi/mso.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e31f980274cce2fd620e627313d88c0b848e616956c747772f768c99a002e9b2",
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils/mso.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c6ee1a1c7fe26b4ebbf7d11442a429eee577cc59e6340b96e119dba10961bfdf",
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils/constants.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9521675e783e3ec3ae79f42c20225a79fdfd295840ef38aca2f7579f401dec12",
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils/schema.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "faf68907d23381a03c29a50a12dd24243cfec96e3830b2bd477d445356b3d3dc",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_template_contract_filter.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6f1cd0ad89ea16105dedec1ee73865c3039c0d440f51835c01bea909088200e1",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_site_anp_epg_staticport.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "24c0bbb68eddf4b2dbd1920ad0dbb8f487318ed2897467c4b357878ba4a27313",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_site_anp.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "53b683bfe3797188a7114b2f7fd32c80f20c82ab75af2cbb52c65ef86ab34b6c",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_dhcp_option_policy_option.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1d2486a946afc26f3bb7b7a8797670747848c844b9109f1782564a2ded687a1a",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_site_vrf_region_hub_network.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "7a43523bd0dc4cfffe22440d971b3f89e85d2d9e0a50aeb39389c3c2830c4bd9",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_template_clone.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "467bbd942d0e26497d51bb70c33338eb07079e4743700c1c256e7e2e4c5aa4ca",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_site_external_epg_selector.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4363f0f76203e827914ee92e71976ddd77281b00a132e0ac3ead2b92afd00674",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_site_anp_epg.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "5b0ee7e4bcb554da6cd227ebb70187b63d1bcccd68b1dcd7bc7e18c785b942b5",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_site_bd_subnet.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "548b14f399f547ccfefc18346676a0e24cce3a7826557e8988d6a392813b138f",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_template_anp.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "563af9420bd43908af689c4f8931a4ea5ce461008e08d45ac0ba1578d398388a",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_template_bd_subnet.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a57db7b1e169ac4cd985f8c754ccf15df5654b7527cc5234b3980fcd83ab3ec9",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_site_service_graph.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f61b1039f2822f8e5bc0d520e429821f67b220847a66553d7a2424680ff89c7e",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_template_deploy.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4675f4f2242f139b0198e240e39bc0033d7807dcde46b4e5fe5cce4ab00f7a43",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_tenant.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f6cfabf979e75755aba9b5de7485a55512bb02d1083d7c0afa97aadf6bfde44b",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_site_vrf_region_cidr_subnet.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "bb9d0be9896f46fa894f4ee2242683cba38141c1ad826aa7fc9720b4a752143b",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_site_vrf.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "cfc09a18b5f1ddb2598e9bb69241f35b4c3a967f84f529266ce8b610400f3939",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_site_anp_epg_staticleaf.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "517fe0774f3853668ba3fa20a27db581974292e6a068eeaeba266fd13e7a2130",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_template_contract_service_graph.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "d5f117924e42571f83fb0fd7e3006938d6c7a03535c2b9b1d0e8fef1187e9d43",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_template_external_epg_contract.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1f977a4e86c0133551d4885a8f2e738be26ff169f84bf6e75eda627e2237e8ef",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_template_external_epg.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "cca13445dac9275a27359ba8f7904208056934c22019553342a5bd81a5927541",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_template_bd_dhcp_policy.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "ccc7584c47937ca7a2759f4bd83766d0589d5ecbf83fa2beb75683719e739458",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_site_vrf_region_cidr.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "537ea2679378ebab08599813a7e89de4866baed013c8816499f081b440b80c8b",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_template_anp_epg.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "fbd12de54e5336dc265f43bf76c59215d9583c8e09d590ccfd63acd5e346f543",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_template_anp_epg_contract.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "dac857729a99d0a1d6d778c94565ac54def3c1a548d949643288f3ae0d7ff047",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_dhcp_relay_policy_provider.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "39748852f207b8d1a2f0572c8204326bc70c16799e6d414c44d70562781d76f1",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_tenant_site.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "8ab681658fcdc71ce3e0dee372959d3e3c816833cbc64f78e2ca64e406495876",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_backup.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "023cdd8db111487656d214b5664691b2524b93b8792f6833cd6e44ff24d33775",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_site.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "2dc848085c68033f3b8ef93d4c82e7a4791074736507c69b5b7e630108e1494e",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_remote_location.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "fbc21ccb26b9b6a4a206b218c24d05996917ad200160195ac5a3bd23ce2b10fa",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_site_anp_epg_selector.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f759e142e699d3013a1a625563a0bf561ac7b1a78e6a0fb511ac952a68ef7ce0",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_template_vrf.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "92bf0092c6f6e72ef1d5748b2316c368b99839ab172caea71668fa44ea2a08c8",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_clone.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "d72660f2a0bb86fd807259b2f4046ea75bc7775abd2659a1ad4301bbce3b5de4",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_site_external_epg.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1c42b50d3217281fab3bdbdb63831973ffa0abb7b427059338b24ec9ead91111",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_site_anp_epg_bulk_staticport.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "7fa1e1ba14119d99731281f5edb1c29e9a00ce8e9e68dc885c14f057e8611a5a",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "eb277796d3882cd5af04d61252d31d73bd6bcbbe37bd378169fb7e9b9ca17a1d",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_service_node_type.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f8b2c340e2a19be2b2a3993555500a8a07254a4fd6bbd424f787c59e50ddadd3",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_site_bd_l3out.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a673bd1f3b2be1b8e290b0aca6b71b784ee6dcbc3b7c83e6688a69b374ac3859",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_site_anp_epg_domain.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "eea4baa850dee443f4763f8821171b7a9018ad32863fcc9419c711b794cbaeaf",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_role.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "676bb9102ab59c395bc74b1e7ae88188ec2d759d243b563dc6f7c40e7954fcc5",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_template_externalepg.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "cca13445dac9275a27359ba8f7904208056934c22019553342a5bd81a5927541",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_template_external_epg_subnet.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "ce326dbc4882ff9fb12721721c84184dfee85c35a31f88eed7ea4f1ee15ce95f",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_template_filter_entry.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "0bcd5ec3532d03574c2116dd8235fb60fa23aa60212a8f2d1d8fb8cf54ae66f1",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_template_service_graph.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1758c7b2153df304380c61c29c5861e83e6dbabc447bb738c474755128bc4c78",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_site_bd.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "efa09b8140a5e1deb8ad7ee81e32d610b75475bdb510d3118cac7928443b7700",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_site_anp_epg_subnet.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9a255127f9740b65860e5106edb61c5265c31e841d104328324bffb109116450",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_rest.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "716c55c4720e7717076cd4c94d6fd14696ff7bbc95f8e4638be9b6cd43eacf94",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_template_vrf_contract.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c4c30b7dbc29b7b17217bab52b48d9f2763266ee64f059dc91a193b7f713fb24",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_validate.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b09b020a522340cefe979ae6d7dde26c16d57723f9c4d98568791d1bbd1009e1",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_label.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "91f3e18abb64d487e24698602aa2c8122275077b76f7ce3e44c9c72615769a6b",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_site_vrf_region.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9d1d6ac938b724f90bee0389ba5449bd39009470c2470b491cbb9c6fa9476f7e",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/ndo_schema_template_deploy.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "245a3f6f5127f6bce09cf8be06aa8705c70f03b9cc09d9a068053a34a9ffae36",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_template_external_epg_selector.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c3c4b25e653b0ffe17b3855a110ff5183b45a7c131deeb720e37bf0e3999512d",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_template_migrate.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1e02912efe8eb9f53827a2843d0ab91aecfc57d5d11fb2b2b535d593fc0b1874",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_version.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1a91a748dc001172e3a0c485848fa0fdd41831b79a0f0862a55625c9c6d78133",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_template_l3out.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e0bb66ebb85fd043104a8cbd14338e3014d16ba49a349eb39a4eacf6319ea21",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_dhcp_relay_policy.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "3eb53debf9a3c8ee3bfed778e538a73e03dac02371f77ad8046e9be200f04806",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_template_anp_epg_subnet.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6d3eb9cfffe291d742bd95c98e2222696b47b9532de5960d405390823f4195c6",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_template_deploy_status.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c9017bda8f5590b60047f9654db54e4608064f97abf2cbae45ce455e2cde815f",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_dhcp_option_policy.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "80d5de0dec0b967c33be6f0a882ed477d774e38823f082ed928bd99acbda77e0",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_template_anp_epg_selector.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "3fd399a8567a0f217d09cd52bc98233bc66d8f95198cce9d3dca1d3c9605e2d3",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_user.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "5a1253f212aaef3c7f1f0a6f0d782db05d56b5943a71a3ef22f170d2b180436a",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_backup_schedule.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "ffcb686b57665f77ba98e5c9da90371bd375651ee14a872946a6ef410795d028",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_template_bd.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "bea197a237bc35514301873858b1b089474b68859e73755c97fbeef8bd9cf022",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_site_l3out.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "640901827b0efe076fc738afbb8909d60bfbbfd23ee11116f397a80ef2567e1a",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_site.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a06f86812347eaddbc5b39836e8cc96703e4207a327673876883550b8109277a",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/mso_schema_template.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "fd7d0b52e775c6607e2c2da18e655f9ae5f760b616f51629f3dda9dcc95bebb5",
+ "format": 1
+ },
+ {
+ "name": "tests",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/.DS_Store",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c43950b0d43cfc92663c93c4106c8265af0878a180281af56a27b402dbe04258",
+ "format": 1
+ },
+ {
+ "name": "tests/integration",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_dhcp_relay_policy",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_dhcp_relay_policy/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_dhcp_relay_policy/tasks/main.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "d9ef7c98101c518cc0689cd0c3052c7a965924473d468d9f28cf94616035f9f3",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_dhcp_relay_policy/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_service_graph",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_service_graph/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_service_graph/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "827c0652a8363fa1585bc3087517cdcc9622f62d6937cc8e4325162c8d1f977b",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_service_graph/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_deploy",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_deploy/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_deploy/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "0c70364503504b44437583c853a4dd70dfb8ed09c3dcb60d254790cefbb0a6c5",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_deploy/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_l3out",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_l3out/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_l3out/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "329a355a98ac22dd8e1e8b7548d054b3610dc834d8485cdd8830f415f5214106",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_l3out/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_vrf_region_cidr_subnet",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_vrf_region_cidr_subnet/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_vrf_region_cidr_subnet/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "95b8ad80d173f5a830b41a0b4c6cdb523a89dadc12413e4d4f4f956a82244c04",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_vrf_region_cidr_subnet/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "69e1eb1e15a1af3fe9e08e301c4173e32f26592337ec169896a202e914747440",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_rest",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_rest/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_rest/tasks/json_template.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "8cc983a7d84493d9f41cbe38b197727174afec4426f78c376bce540a4b4a4fec",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_rest/tasks/tenant.json.j2",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6636b61fb38e341e4520e1f158bbce619e6b70e6e6d4bd243177ec86069f50dd",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_rest/tasks/json_inline.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "79a502e927c89c5968a435bf2c2418ab416cfa47a6b41d48079eef0c36149f29",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_rest/tasks/json_string.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "db213807aee3f02d301fea223f60d9e54dff6ad8ea8bdf787d20c03bf7ca98c9",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_rest/tasks/yaml_inline.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "d3b9e0b8414481027b4b3fc2ac7b06002e835bd1af89a05a2d7800007b12af87",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_rest/tasks/yaml_string.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "de348a80485987b8ccd22ced6deb28150d79e81947929908a395fd8019fab605",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_rest/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "52f1fb7f60fafa3735b17352ca4e1da195810b0edf3f2680738092e4d52fb1b1",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_rest/tasks/error_handling.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "833535c5837bd384a895d6741629bae58d64af7b2b8f5af78d288ca9ad601947",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_rest/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_remote_location",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_remote_location/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_remote_location/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "38221f3e70c4a94b31aa03a33e8da81ad05164ba82afbf2ecdb6c3e77fd3977f",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_remote_location/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_remote_location/pki",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_remote_location/pki/rsa-passphrase.pub",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "627a0a5fd0d48dd796488fc3c6581beb656d0781730d759205158d7cfc256361",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_remote_location/pki/rsa",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "7ea356fc3f12a611c5508051db0d3ec4e8f0945f806ac9b7645cc72f8342639a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_remote_location/pki/rsa-passphrase",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a66131fd3b769bd4dd960daa74a619b7786e546841f3b113ec012c2e9a0498ee",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_remote_location/pki/rsa.pub",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6f33804de57beeb48f784be435cdf7484886f1d227ab6bd016ab2e03f1150e78",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_external_epg_subnet",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_external_epg_subnet/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_external_epg_subnet/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "5836b610052cfbdc33f4603cac14419dd92c3c64f3c56f17d557f89198055ccb",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_external_epg_subnet/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "fe21b23b905888b210fb78e8102768cdb990ab083ff7de03e6c6ef519e947c59",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c1312bf7a3945d1864d7a94e5a5224ebdfcad2076570a76f32a211f351aae11f",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_migrate",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_migrate/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_migrate/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "071adadb5df6a35002c55292ca8c7435e5893be90318e66f3defe6b124345cde",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_migrate/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_l3out",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_l3out/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_l3out/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c254fa26dea680790cff05b10a996729ab1d4ae6d58a22f9e0870ded774c0ad1",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_l3out/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_anp",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_anp/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_anp/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "39d7a4e61d89fcf18b1dc760e208d6f2e7293987033a87432f380c7497dcc92a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_anp/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/ndo_schema_template_deploy",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/ndo_schema_template_deploy/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/ndo_schema_template_deploy/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "d9dc44d7e2603c394f4d0d28305b233bc2b00b0f3bc6d21f79d8f59914acc053",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/ndo_schema_template_deploy/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_anp_epg_selector",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_anp_epg_selector/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_anp_epg_selector/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "8eaa3bd0ba02460cf49baad9c987294915aeb9dd70086caf5a5d742ef2e450f0",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_anp_epg_selector/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_external_epg_selector",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_external_epg_selector/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_external_epg_selector/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "2270c9de7e7ccae07b323d7f4fa7779faa468a8bdabcf7318746dd8966a67e0e",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_external_epg_selector/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_clone",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_clone/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_clone/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "514d4e40cfb3e4dcff5bbfb105cefd20fbaeb0ea0620450eff89ac48311486d7",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_clone/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_contract_service_graph",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_contract_service_graph/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_contract_service_graph/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "578c3318a14ef60ec71adaadcb725f767b60ab223d177aa19965ed1421b08799",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_contract_service_graph/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_external_epg_contract",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_external_epg_contract/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_external_epg_contract/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "82a0285141349272dba64176a122887414245415ba23ce9e7edb68f2f24a5768",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_external_epg_contract/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_vrf_region",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_vrf_region/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_vrf_region/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "77efce495c27cf34aee29ad0d23857ee75862a8285cf28c7d2e36b1f7d5a21e5",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_vrf_region/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_contract_filter",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_contract_filter/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_contract_filter/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "3f2ee3ce9770c0a1e82f0359641f889c34e21f1a1a357256818762ff7c34baf4",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_contract_filter/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_dhcp_option_policy_option",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_dhcp_option_policy_option/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_dhcp_option_policy_option/tasks/main.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "51f135acdfc12bd620051460c18d84c139adeca91bcfbf53e44ed5c1a3879213",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_dhcp_option_policy_option/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_external_epg_selector",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_external_epg_selector/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_external_epg_selector/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e493d7a02b30e3933cdde6112452b3d8848718fd7cdbad91c02cf9ead974824c",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_external_epg_selector/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_backup_schedule",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_backup_schedule/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_backup_schedule/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "07a0e9ba07f2daa25c9fba4c9ea0ddf30f8a7c8742d23db43c606d418502fcdd",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_backup_schedule/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_anp_epg",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_anp_epg/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_anp_epg/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9d33506200b0c453f2d6d810772e4d4df6f4bf2cafa60728196bf2e0f69e6229",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_anp_epg/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_bd",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_bd/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_bd/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "333679f0f0e047c4923b582594ef248ade0ea86781386548e12fecbb3abe071c",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_bd/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_bd",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_bd/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_bd/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6901d10171226d330516388d635afe119a679289eef9ef4035ac4de6353c3b94",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_bd/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_external_epg",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_external_epg/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_external_epg/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "ff9797ef3ae1245f5f8c22f8b690f2da40ee624029286bec15391a83ca808574",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_external_epg/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_site",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_site/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_site/tasks/connectivity.j2",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "5ad7198616f8396d761ddfff46c03886cf49c8af64decc1439cd0b893e2e8645",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_site/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b1069b4a82af6ab3a3f5c48a3e1ccd7934914ce4a01986527c8abd1b9644f60f",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_site/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_backup",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_backup/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_backup/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "18d20201dae1300b207fb2a4aca24df285f859edb393c5f86f773bb8df08e7eb",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_backup/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_validate",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_validate/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_validate/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1af77eaab77c5a6b06eda3a60b2bd918b84dfa5750f6288aaad1d002d069bc6b",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_validate/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_external_epg",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_external_epg/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_external_epg/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6bdfe858336ec0d8a3fb0d5d09142ef1c2a4a0cbd99fcc389e6784ac8ddd2b41",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_external_epg/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_bd_l3out",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_bd_l3out/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_bd_l3out/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "7b158c1ec848377082954461ef32fb48016b0986ac9d9a7e8b5be178410df282",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_bd_l3out/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_vrf",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_vrf/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_vrf/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1c7674afec5735c25d6728fe1ad92d3c18fbeb86285c8ab671d0feca2f2f1c16",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_vrf/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_dhcp_relay_policy_provider",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_dhcp_relay_policy_provider/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_dhcp_relay_policy_provider/tasks/main.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "5d51e2b02f8d1e76e11de62f429d8ef56d434d5b2cdb69ad0b919889ff2f5936",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_dhcp_relay_policy_provider/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_version",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_version/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_version/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "82ab83bd4bea802e0816502ad436051701ebf7ad413979fafaf48a89a9f373a6",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_version/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_dhcp_option_policy",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_dhcp_option_policy/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_dhcp_option_policy/tasks/main.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c8a3d5702a82e14a426180a493c3479435a0e19d46bd02c8493b035296d958c0",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_dhcp_option_policy/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_anp",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_anp/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_anp/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "418ff7da34a7ad01685d47cc802cedff5ba29cbe769c954ee9083f682dad7ae1",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_anp/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_vrf_region_hub_network",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_vrf_region_hub_network/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_vrf_region_hub_network/tasks/hub_network.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e1a5fd0bbf1f1adf0d4da159ccfe96f57b5c10f4e8fa48c6c76da8339b10c530",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_vrf_region_hub_network/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "ffa9f5e1ba49276fbf6d6cb24151327d6901a5a778d57e4ddb1c8ac0c722487e",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_vrf_region_hub_network/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_anp_epg_staticport",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_anp_epg_staticport/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_anp_epg_staticport/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f4cd4ae7386ed895636f750b632a5001c004d8a8805c21de5059cfbbad3c04c4",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_anp_epg_staticport/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_vrf_region_cidr",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_vrf_region_cidr/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_vrf_region_cidr/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b28d23e8386bd853d29a9544995258c438bec534f7abd563642f0c08de3d9784",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_vrf_region_cidr/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_clone",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_clone/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_clone/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "d844a496be851967b5ad5b12b7e87c97cfeb43fb9aea9a017fe1ff5eef8872c4",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_clone/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_bd_dhcp_policy",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_bd_dhcp_policy/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_bd_dhcp_policy/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "78b2e112fa8a4f86ae598358e9230f87e97032cac5c42cce6e2225b2d214684e",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_bd_dhcp_policy/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_bd_subnet",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_bd_subnet/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_bd_subnet/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "13b1b2addac18340cc8e491f123f1bfd6eb036b1bc9436ab49e95fb723bb652c",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_bd_subnet/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_service_graph",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_service_graph/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_service_graph/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "484c7ee52533c8a3945e0895aea08fc8ce43f4cbeb214f09382da2b94f8be366",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_service_graph/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_tenant_site",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_tenant_site/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_tenant_site/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "62050f4750b7759d9ffe9fb05eb907652902632e60bab409dd5046ed295aa32e",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_tenant_site/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_bd_subnet",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_bd_subnet/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_bd_subnet/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "7ff362dc5f1bc93ef2020db1ec557092607256e917d8ddd430c323d6f5d8ec5f",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_bd_subnet/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_filter_entry",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_filter_entry/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_filter_entry/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "fa3311bf4f0a496d1f4fb2ced7e15eba2fc91f94dc9d3ff03fcc90d914edfef6",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_filter_entry/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_anp_epg_bulk_staticport",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_anp_epg_bulk_staticport/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_anp_epg_bulk_staticport/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "54fb49cc6419f67dbbad1d40262ad3b5d51c4397a4051dc1bf1517b9a0cc2649",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_anp_epg_bulk_staticport/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_tenant",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_tenant/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_tenant/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "947a71a53705d001c273db32700e5068456b9c4485e965ce74f6f7a1b9deac21",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_tenant/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_user",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_user/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_user/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4da63f32ce74b3890e7ee8d4c3c075036bbf41c12a05993ae0149e200802a69f",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_user/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_label",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_label/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_label/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "480fe2aaf7ae221186ad81b486471d176476c07347e005255d0a399ac14c2086",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_label/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_anp_epg_contract",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_anp_epg_contract/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_anp_epg_contract/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1c4ffb17ea697e5f4084e51af650ea8926d15db223e0e0ae010a7954def3c6ff",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_anp_epg_contract/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_anp_epg_selector",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_anp_epg_selector/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_anp_epg_selector/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b076386dec1bbce7c25deeb36f6c674d6ba271e2d22d87241db43dd8fbf60913",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_anp_epg_selector/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_anp_epg",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_anp_epg/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_anp_epg/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4745d027cba442156d8f6c5c441695cf796bd7a3dc2f3db5addffcff340c1268",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_anp_epg/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_anp_epg_domain",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_anp_epg_domain/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_anp_epg_domain/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "ee1fb2b9a12e1cf8279a729d0e1931f8428bd9ebba10437d919b33e5c68bc7ea",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_site_anp_epg_domain/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_vrf_contract",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_vrf_contract/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_vrf_contract/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c1639c0d1bda672778a81703b34dfde366b560c7e075fc78abfd42b0aadae844",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_vrf_contract/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_role",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_role/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_role/tasks/role-ro.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e6bd02045da50a15d5637fa33f5fd81cf2be74a7169ad5f3d3bb3f8bb5ddbf04",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_role/tasks/role-rw.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "71801c0349fceb078b940dd5fcec5cdf2009698f1217dffd56742ac102d21f8f",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_role/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "65a2f97fa530d35fa0f1c647b8b3052d42d2dee2d6a9e6ea766e9eae531ff177",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_role/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_service_node_type",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_service_node_type/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_service_node_type/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "0f1f0fc9edff212a859e9a1a92cabdc157a25b5458d9dd9f429cc3e867ebfaa3",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_service_node_type/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_deploy_status",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_deploy_status/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_deploy_status/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "eb8bc653c1850980ecdef451053e466effa70eec2482c9b62d997ed7a131a036",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/mso_schema_template_deploy_status/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4e674607496d01ce032bfdbcc3e77ecdcde02f3e9d57080a95682a0cdff6809a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/network-integration.requirements.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "26d3ea388bb679036608d912734c5369bb63f362795a62fd5c9e0469c9abaeb8",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/target-prefixes.network",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a751644aed44198c1a29a15bccfe9d4db59a5dabaf118620623579ce0caf0f89",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/inventory.networking",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "8279ccd9d9f069814c4799fe859517982de7fdef62f6844c86ce3d43f1eb75b1",
+ "format": 1
+ },
+ {
+ "name": "tests/sanity",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/sanity/requirements.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "99b118eeccecd08f68761335ef6a9ea2ace42095d52512c6e575a1d017362a2d",
+ "format": 1
+ },
+ {
+ "name": "tests/.gitignore",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b5726d3ec9335a09c124469eca039523847a6b0f08a083efaefd002b83326600",
+ "format": 1
+ },
+ {
+ "name": "meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "meta/runtime.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "d6216481ba4478b00a1c7dd50630465ebdfeb69257a3203cb1bd78243b57e5e2",
+ "format": 1
+ },
+ {
+ "name": "changelogs",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "changelogs/fragments",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "changelogs/config.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "43206e71b00b28f61464ff729ee7c096a6babe481924585846dc0ad75f4e5ae0",
+ "format": 1
+ },
+ {
+ "name": "changelogs/.gitignore",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "919ef00776e7d2ff349950ac4b806132aa9faf006e214d5285de54533e443b33",
+ "format": 1
+ },
+ {
+ "name": "changelogs/.plugin-cache.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "63e4189122f605bc252cee64b0e2a606ba71624e4efeef2f0b345aba2b137d52",
+ "format": 1
+ },
+ {
+ "name": "changelogs/changelog.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "add2fb84a8fccaa2a670024fd29718afab7e6001ffebbbbf5e8a5fd48b50c3e7",
+ "format": 1
+ },
+ {
+ "name": "README.md",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "613db67fc4c860791ee0bc02b1e0ace99d9a0daae54db9cc8309fdfbf061d239",
+ "format": 1
+ },
+ {
+ "name": ".gitignore",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "611ba6325f4ab58aa157eb91fbe3edf77f2285f854e612f6ec433d73ae4291cc",
+ "format": 1
+ },
+ {
+ "name": ".github",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": ".github/.DS_Store",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c61e10aac987566d2e1f152dcd3c4b4b410d6aa298fc8a35fde20ebf91925f82",
+ "format": 1
+ },
+ {
+ "name": ".github/workflows",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": ".github/workflows/galaxy-importer.cfg",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "edc6f2746e7b8c7b94dea479036ba246953cf69234974b89c069c10d614dcaad",
+ "format": 1
+ },
+ {
+ "name": ".github/workflows/codeql-analysis.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "dcb0879cb0566d1f60a7e468d2d00c174dbb17de01afde105e47fb9ee834463d",
+ "format": 1
+ },
+ {
+ "name": ".github/workflows/ansible-test.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "55a01eb1c79e977744677bca59563ff6dfbb3f12b0932130d4cc7a46d49deb04",
+ "format": 1
+ },
+ {
+ "name": ".github/workflows/changelog-generation.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "52da33c388cefb32ae9d7c147111ea30534fd32f8b8b546e5b0688cb49a50d33",
+ "format": 1
+ },
+ {
+ "name": ".github/ISSUE_TEMPLATE",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": ".github/ISSUE_TEMPLATE/Feature_Request.md",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "734f00beb33216222396e76ae426a8d5b85605b37f7c4ea5fb5899f6a81f0899",
+ "format": 1
+ },
+ {
+ "name": ".github/ISSUE_TEMPLATE/Bug_Report.md",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "ab91fb0030575562037e71b97d5c7a011ef0068812195a1ce78bc7233cbbbc79",
+ "format": 1
+ },
+ {
+ "name": ".github/ISSUE_TEMPLATE/config.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "05905c8f244d51298bbd1778c286c8a6c9f7adf0d0e5a5f72f764d71ec82cc64",
+ "format": 1
+ },
+ {
+ "name": "CHANGELOG.rst",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e6387ce16814cecfb727532d8d1f0231849b4b1ccc11875af32502499af84ef8",
+ "format": 1
+ },
+ {
+ "name": ".vscode",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": ".vscode/settings.json",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "d0b33897350295766ce49d5ddf87964e33e1ad285bf7713bc668c518fcd14814",
+ "format": 1
+ }
+ ],
+ "format": 1
+} \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/LICENSE b/ansible_collections/cisco/mso/LICENSE
new file mode 100644
index 000000000..e09a4143a
--- /dev/null
+++ b/ansible_collections/cisco/mso/LICENSE
@@ -0,0 +1,678 @@
+Copyright (c) 2019, Cisco Systems
+All rights reserved.
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
+
diff --git a/ansible_collections/cisco/mso/MANIFEST.json b/ansible_collections/cisco/mso/MANIFEST.json
new file mode 100644
index 000000000..c795dd895
--- /dev/null
+++ b/ansible_collections/cisco/mso/MANIFEST.json
@@ -0,0 +1,43 @@
+{
+ "collection_info": {
+ "namespace": "cisco",
+ "name": "mso",
+ "version": "2.4.0",
+ "authors": [
+ "Dag Wieers (@dagwieers)",
+ "Nirav Katarmal (@nkatarmal-crest)",
+ "Lionel Hercot (@lhercot)",
+ "Cindy Zhao (@cizhao)",
+ "Shreyas Srish (@shrsr)"
+ ],
+ "readme": "README.md",
+ "tags": [
+ "cisco",
+ "aci",
+ "cloud",
+ "collection",
+ "networking",
+ "sdn",
+ "mso",
+ "multisite"
+ ],
+ "description": "An Ansible collection for managing Cisco ACI Multi-Site",
+ "license": [],
+ "license_file": "LICENSE",
+ "dependencies": {
+ "ansible.netcommon": "*"
+ },
+ "repository": "https://github.com/CiscoDevNet/ansible-mso",
+ "documentation": "https://docs.ansible.com/ansible/latest/scenario_guides/guide_aci.html",
+ "homepage": "https://cisco.com/go/aci",
+ "issues": "https://github.com/CiscoDevNet/ansible-mso/issues"
+ },
+ "file_manifest_file": {
+ "name": "FILES.json",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "876c6bead4e943a7f2ed1b6bcc6db722a3024362587618ffccf5e7c7ec54419b",
+ "format": 1
+ },
+ "format": 1
+} \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/README.md b/ansible_collections/cisco/mso/README.md
new file mode 100644
index 000000000..d83eb5297
--- /dev/null
+++ b/ansible_collections/cisco/mso/README.md
@@ -0,0 +1,122 @@
+# ansible-mso
+
+The `ansible-mso` project provides an Ansible collection for managing and automating your Cisco ACI Multi-Site environment.
+It consists of a set of modules and roles for performing tasks related to ACI Multi-Site.
+
+This collection has been tested and supports MSO 2.1+.
+Modules supporting new features introduced in MSO API in specific MSO versions might not be supported in earlier MSO releases.
+
+*Note: This collection is not compatible with versions of Ansible before v2.8.*
+*Note: The Nexus Dashboard (ND) HTTPAPI connection plugin should be used when Cisco ACI Multi-Site is installed on Nexus Dashboard (v3.2+) or when using this collection with Nexus Dashboard Orchestrator (v3.6+).*
+
+## Requirements
+- Ansible v2.9 or newer
+
+## Install
+Ansible must be installed
+```
+sudo pip install ansible
+```
+
+Install the collection
+```
+ansible-galaxy collection install cisco.mso
+```
+
+Install the Nexus Dashboard (ND) collection when Cisco ACI Multi-Site is installed on Nexus Dashboard (v3.2+) or when using this collection with Nexus Dashboard Orchestrator (v3.6+)
+```
+ansible-galaxy collection install cisco.nd
+```
+
+## Usage
+Once the collection is installed, you can use it in a playbook by specifying the full namespace path to the module, plugin and/or role.
+```yaml
+- hosts: mso
+ gather_facts: no
+
+ tasks:
+ - name: Add a new site EPG
+ cisco.mso.mso_schema_site_anp_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ state: present
+ delegate_to: localhost
+```
+
+You can also use the MSO HTTPAPI connection plugin by setting the following variables in your inventory file (cisco.mso collection v1.2+).
+```yaml
+ansible_connection=ansible.netcommon.httpapi
+ansible_network_os=cisco.mso.mso
+```
+
+The HTTPAPI connection plugin will also allow you to specify additional parameters as variable and omit them from the task itself. Module parameters will override global variables.
+```yaml
+ansible_host=10.0.0.1
+ansible_user=admin
+ansible_ssh_pass="MySuperPassword"
+ansible_httpapi_validate_certs=False
+ansible_httpapi_use_ssl=True
+ansible_httpapi_use_proxy=True
+```
+
+You should use the Nexus Dashboard (ND) collection plugin, which is available in the [cisco.nd](https://galaxy.ansible.com/cisco/nd) collection, when Cisco ACI Multi-Site is installed on Nexus Dashboard (v3.2+) or when using this collection with Nexus Dashboard Orchestrator (v3.6+) by changing the following variables.
+```yaml
+ansible_connection=ansible.netcommon.httpapi
+ansible_network_os=cisco.nd.nd
+ansible_httpapi_use_ssl=True
+```
+
+## Update
+Getting the latest/nightly collection build
+
+### First Approach
+Clone the ansible-mso repository.
+```
+git clone https://github.com/CiscoDevNet/ansible-mso.git
+```
+
+Go to the ansible-mso directory
+```
+cd ansible-mso
+```
+
+Pull the latest master on your mso
+```
+git pull origin master
+```
+
+Build and Install a collection from source
+```
+ansible-galaxy collection build --force
+ansible-galaxy collection install cisco-mso-* --force
+```
+
+### Second Approach
+Go to: https://github.com/CiscoDevNet/ansible-mso/actions
+
+Select the latest CI build
+
+Under Artifacts download collection and unzip it using Terminal or Console.
+
+*Note: The collection file is a zip file containing a tar.gz file. We recommend using CLI because some GUI-based unarchiver might unarchive both nested archives in one go.*
+
+Install the unarchived tar.gz file
+```
+ansible-galaxy collection install cisco-mso-1.0.0.tar.gz —-force
+```
+
+### See Also:
+
+* [Ansible Using collections](https://docs.ansible.com/ansible/latest/user_guide/collections_using.html) for more details.
+
+## Contributing to this collection
+
+Ongoing development efforts and contributions to this collection are tracked as issues in this repository.
+
+We welcome community contributions to this collection. If you find problems, need an enhancement or need a new module, please open an issue or create a PR against the [Cisco MSO collection repository](https://github.com/CiscoDevNet/ansible-mso/issues).
diff --git a/ansible_collections/cisco/mso/changelogs/.gitignore b/ansible_collections/cisco/mso/changelogs/.gitignore
new file mode 100644
index 000000000..6be6b5331
--- /dev/null
+++ b/ansible_collections/cisco/mso/changelogs/.gitignore
@@ -0,0 +1 @@
+/.plugin-cache.yaml
diff --git a/ansible_collections/cisco/mso/changelogs/.plugin-cache.yaml b/ansible_collections/cisco/mso/changelogs/.plugin-cache.yaml
new file mode 100644
index 000000000..c1fce4465
--- /dev/null
+++ b/ansible_collections/cisco/mso/changelogs/.plugin-cache.yaml
@@ -0,0 +1,343 @@
+objects:
+ role: {}
+plugins:
+ become: {}
+ cache: {}
+ callback: {}
+ cliconf: {}
+ connection: {}
+ httpapi:
+ mso:
+ description: MSO Ansible HTTPAPI Plugin.
+ name: mso
+ version_added: 1.2.0
+ inventory: {}
+ lookup: {}
+ module:
+ mso_backup:
+ description: Manages backups
+ name: mso_backup
+ namespace: ''
+ version_added: null
+ mso_backup_schedule:
+ description: Manages backup schedules
+ name: mso_backup_schedule
+ namespace: ''
+ version_added: null
+ mso_dhcp_option_policy:
+ description: Manage DHCP Option policies.
+ name: mso_dhcp_option_policy
+ namespace: ''
+ version_added: null
+ mso_dhcp_option_policy_option:
+ description: Manage DHCP options in a DHCP Option policy.
+ name: mso_dhcp_option_policy_option
+ namespace: ''
+ version_added: null
+ mso_dhcp_relay_policy:
+ description: Manage DHCP Relay policies.
+ name: mso_dhcp_relay_policy
+ namespace: ''
+ version_added: null
+ mso_dhcp_relay_policy_provider:
+ description: Manage DHCP providers in a DHCP Relay policy.
+ name: mso_dhcp_relay_policy_provider
+ namespace: ''
+ version_added: null
+ mso_label:
+ description: Manage labels
+ name: mso_label
+ namespace: ''
+ version_added: null
+ mso_remote_location:
+ description: Manages remote locations
+ name: mso_remote_location
+ namespace: ''
+ version_added: null
+ mso_rest:
+ description: Direct access to the Cisco MSO REST API
+ name: mso_rest
+ namespace: ''
+ version_added: null
+ mso_role:
+ description: Manage roles
+ name: mso_role
+ namespace: ''
+ version_added: null
+ mso_schema:
+ description: Manage schemas
+ name: mso_schema
+ namespace: ''
+ version_added: null
+ mso_schema_clone:
+ description: Clone schemas
+ name: mso_schema_clone
+ namespace: ''
+ version_added: null
+ mso_schema_site:
+ description: Manage sites in schemas
+ name: mso_schema_site
+ namespace: ''
+ version_added: null
+ mso_schema_site_anp:
+ description: Manage site-local Application Network Profiles (ANPs) in schema
+ template
+ name: mso_schema_site_anp
+ namespace: ''
+ version_added: null
+ mso_schema_site_anp_epg:
+ description: Manage site-local Endpoint Groups (EPGs) in schema template
+ name: mso_schema_site_anp_epg
+ namespace: ''
+ version_added: null
+ mso_schema_site_anp_epg_domain:
+ description: Manage site-local EPG domains in schema template
+ name: mso_schema_site_anp_epg_domain
+ namespace: ''
+ version_added: null
+ mso_schema_site_anp_epg_selector:
+ description: Manage site-local EPG selector in schema templates
+ name: mso_schema_site_anp_epg_selector
+ namespace: ''
+ version_added: null
+ mso_schema_site_anp_epg_staticleaf:
+ description: Manage site-local EPG static leafs in schema template
+ name: mso_schema_site_anp_epg_staticleaf
+ namespace: ''
+ version_added: null
+ mso_schema_site_anp_epg_staticport:
+ description: Manage site-local EPG static ports in schema template
+ name: mso_schema_site_anp_epg_staticport
+ namespace: ''
+ version_added: null
+ mso_schema_site_anp_epg_subnet:
+ description: Manage site-local EPG subnets in schema template
+ name: mso_schema_site_anp_epg_subnet
+ namespace: ''
+ version_added: null
+ mso_schema_site_bd:
+ description: Manage site-local Bridge Domains (BDs) in schema template
+ name: mso_schema_site_bd
+ namespace: ''
+ version_added: null
+ mso_schema_site_bd_l3out:
+ description: Manage site-local BD l3out's in schema template
+ name: mso_schema_site_bd_l3out
+ namespace: ''
+ version_added: null
+ mso_schema_site_bd_subnet:
+ description: Manage site-local BD subnets in schema template
+ name: mso_schema_site_bd_subnet
+ namespace: ''
+ version_added: null
+ mso_schema_site_external_epg:
+ description: Manage External EPG in schema of sites
+ name: mso_schema_site_external_epg
+ namespace: ''
+ version_added: null
+ mso_schema_site_external_epg_selector:
+ description: Manage External EPG selector in schema of cloud sites
+ name: mso_schema_site_external_epg_selector
+ namespace: ''
+ version_added: null
+ mso_schema_site_l3out:
+ description: Manage site-local layer3 Out (L3Outs) in schema template
+ name: mso_schema_site_l3out
+ namespace: ''
+ version_added: null
+ mso_schema_site_service_graph:
+ description: Manage Service Graph in schema sites
+ name: mso_schema_site_service_graph
+ namespace: ''
+ version_added: null
+ mso_schema_site_vrf:
+ description: Manage site-local VRFs in schema template
+ name: mso_schema_site_vrf
+ namespace: ''
+ version_added: null
+ mso_schema_site_vrf_region:
+ description: Manage site-local VRF regions in schema template
+ name: mso_schema_site_vrf_region
+ namespace: ''
+ version_added: null
+ mso_schema_site_vrf_region_cidr:
+ description: Manage site-local VRF region CIDRs in schema template
+ name: mso_schema_site_vrf_region_cidr
+ namespace: ''
+ version_added: null
+ mso_schema_site_vrf_region_cidr_subnet:
+ description: Manage site-local VRF regions in schema template
+ name: mso_schema_site_vrf_region_cidr_subnet
+ namespace: ''
+ version_added: null
+ mso_schema_site_vrf_region_hub_network:
+ description: Manage site-local VRF region hub network in schema template
+ name: mso_schema_site_vrf_region_hub_network
+ namespace: ''
+ version_added: null
+ mso_schema_template:
+ description: Manage templates in schemas
+ name: mso_schema_template
+ namespace: ''
+ version_added: null
+ mso_schema_template_anp:
+ description: Manage Application Network Profiles (ANPs) in schema templates
+ name: mso_schema_template_anp
+ namespace: ''
+ version_added: null
+ mso_schema_template_anp_epg:
+ description: Manage Endpoint Groups (EPGs) in schema templates
+ name: mso_schema_template_anp_epg
+ namespace: ''
+ version_added: null
+ mso_schema_template_anp_epg_contract:
+ description: Manage EPG contracts in schema templates
+ name: mso_schema_template_anp_epg_contract
+ namespace: ''
+ version_added: null
+ mso_schema_template_anp_epg_selector:
+ description: Manage EPG selector in schema templates
+ name: mso_schema_template_anp_epg_selector
+ namespace: ''
+ version_added: null
+ mso_schema_template_anp_epg_subnet:
+ description: Manage EPG subnets in schema templates
+ name: mso_schema_template_anp_epg_subnet
+ namespace: ''
+ version_added: null
+ mso_schema_template_bd:
+ description: Manage Bridge Domains (BDs) in schema templates
+ name: mso_schema_template_bd
+ namespace: ''
+ version_added: null
+ mso_schema_template_bd_dhcp_policy:
+ description: Manage BD DHCP Policy in schema templates
+ name: mso_schema_template_bd_dhcp_policy
+ namespace: ''
+ version_added: null
+ mso_schema_template_bd_subnet:
+ description: Manage BD subnets in schema templates
+ name: mso_schema_template_bd_subnet
+ namespace: ''
+ version_added: null
+ mso_schema_template_clone:
+ description: Clone templates
+ name: mso_schema_template_clone
+ namespace: ''
+ version_added: null
+ mso_schema_template_contract_filter:
+ description: Manage contract filters in schema templates
+ name: mso_schema_template_contract_filter
+ namespace: ''
+ version_added: null
+ mso_schema_template_contract_service_graph:
+ description: Manage the service graph association with a contract in schema
+ template
+ name: mso_schema_template_contract_service_graph
+ namespace: ''
+ version_added: null
+ mso_schema_template_deploy:
+ description: Deploy schema templates to sites
+ name: mso_schema_template_deploy
+ namespace: ''
+ version_added: null
+ mso_schema_template_deploy_status:
+ description: Check query of objects before deployment to site
+ name: mso_schema_template_deploy_status
+ namespace: ''
+ version_added: null
+ mso_schema_template_external_epg:
+ description: Manage external EPGs in schema templates
+ name: mso_schema_template_external_epg
+ namespace: ''
+ version_added: null
+ mso_schema_template_external_epg_contract:
+ description: Manage Extrnal EPG contracts in schema templates
+ name: mso_schema_template_external_epg_contract
+ namespace: ''
+ version_added: 0.0.8
+ mso_schema_template_external_epg_selector:
+ description: Manage External EPG selector in schema templates
+ name: mso_schema_template_external_epg_selector
+ namespace: ''
+ version_added: null
+ mso_schema_template_external_epg_subnet:
+ description: Manage External EPG subnets in schema templates
+ name: mso_schema_template_external_epg_subnet
+ namespace: ''
+ version_added: 0.0.8
+ mso_schema_template_filter_entry:
+ description: Manage filter entries in schema templates
+ name: mso_schema_template_filter_entry
+ namespace: ''
+ version_added: null
+ mso_schema_template_l3out:
+ description: Manage l3outs in schema templates
+ name: mso_schema_template_l3out
+ namespace: ''
+ version_added: null
+ mso_schema_template_migrate:
+ description: Migrate Bridge Domains (BDs) and EPGs between templates
+ name: mso_schema_template_migrate
+ namespace: ''
+ version_added: null
+ mso_schema_template_service_graph:
+ description: Manage Service Graph in schema templates
+ name: mso_schema_template_service_graph
+ namespace: ''
+ version_added: null
+ mso_schema_template_vrf:
+ description: Manage VRFs in schema templates
+ name: mso_schema_template_vrf
+ namespace: ''
+ version_added: null
+ mso_schema_template_vrf_contract:
+ description: Manage vrf contracts in schema templates
+ name: mso_schema_template_vrf_contract
+ namespace: ''
+ version_added: 0.0.8
+ mso_schema_validate:
+ description: Validate the schema before deploying it to site
+ name: mso_schema_validate
+ namespace: ''
+ version_added: 1.3.0
+ mso_service_node_type:
+ description: Manage Service Node Types
+ name: mso_service_node_type
+ namespace: ''
+ version_added: null
+ mso_site:
+ description: Manage sites
+ name: mso_site
+ namespace: ''
+ version_added: null
+ mso_tenant:
+ description: Manage tenants
+ name: mso_tenant
+ namespace: ''
+ version_added: null
+ mso_tenant_site:
+ description: Manage tenants with cloud sites.
+ name: mso_tenant_site
+ namespace: ''
+ version_added: null
+ mso_user:
+ description: Manage users
+ name: mso_user
+ namespace: ''
+ version_added: null
+ mso_version:
+ description: Get version of MSO
+ name: mso_version
+ namespace: ''
+ version_added: null
+ ndo_schema_template_deploy:
+ description: Deploy schema templates to sites for NDO v3.7 and higher
+ name: ndo_schema_template_deploy
+ namespace: ''
+ version_added: null
+ netconf: {}
+ shell: {}
+ strategy: {}
+ vars: {}
+version: 2.2.0
diff --git a/ansible_collections/cisco/mso/changelogs/changelog.yaml b/ansible_collections/cisco/mso/changelogs/changelog.yaml
new file mode 100644
index 000000000..86e29d812
--- /dev/null
+++ b/ansible_collections/cisco/mso/changelogs/changelog.yaml
@@ -0,0 +1,404 @@
+ancestor: 0.0.4
+releases:
+ 0.0.5:
+ changes:
+ release_summary: New release v0.0.5
+ release_date: '2020-04-07'
+ 0.0.6:
+ changes:
+ bugfixes:
+ - Add aliases for backward support of permissions in role module.
+ - Add integration test for mso_schema_template_db and fix un-needed push to
+ API found by integration test.
+ - Consistent object output on domain_associations
+ - Fix EPG / External EPG Contract issue and create test for mso_schema_template_anp_epg_contract
+ and mso_schema_template_external_epg_contract
+ - Fix contract filter issue and add contract-filter test file
+ - Fix duplicate user, add admin user to associated user list and update tenant
+ test file
+ - Fix intersite_multicast_source attribute issue in mso_schema_template_anp_epg
+ and add the proxy_arp argument.
+ - Fix mso_schema_template_anp_epg idempotancy for both EPG and EPG with contracts
+ - Remove label with test domain before create it
+ - Send context instead of vrf when vrf parameter is used
+ - Update mso_schema_template_bd.py example for BD in another schema
+ minor_changes:
+ - ACI/MSO - Use get() dict lookups (https://github.com/ansible/ansible/pull/63074)
+ - Add EPG and ANP at site level when needed
+ - Add github action CI pipeline with test coverage
+ - Add login domain support for authentication in all modules
+ - Add support for DHCP querier to all subnet objects. Add partial test in mso_schema_template_bd
+ integration test.
+ - Add support for clean output if needed for debuging
+ - Add test file for mso_schema_template_anp_epg
+ - Added DHCP relay options and scope options to MSO schema template bd
+ - Added ability to bind epg to static fex port
+ - Added module to manage contracts for external EPG in Cisco MSO (https://github.com/ansible/ansible/pull/63550)
+ - Added module to manage template external epg subnet for Cisco MSO (https://github.com/ansible/ansible/pull/63542)
+ - Disabling tests for the role modules as API is not supported after 2.2.3i
+ until further notice
+ - Increased test coverage for existing module integration tests.
+ - Modified fail messages for site and updated documentation
+ - Moving test to Ansible v2.9.9 and increasing timelimit for mutex to 30+ min
+ - Update authors.
+ - Update mso_schema_site_anp.py (https://github.com/ansible/ansible/pull/67099)
+ - Updated Test File Covering all conditions
+ - mso_schema_site_anp_epg_staticport - Add VPC support (https://github.com/ansible/ansible/pull/62803)
+ release_summary: New release v0.0.6
+ release_date: '2020-06-13'
+ 0.0.7:
+ changes:
+ bugfixes:
+ - Fix mso_schema_site_vrf_region_cidr to automatically create VRF and Region
+ if not present at site level
+ - Fix query condition when VRF or Region do not exist at site level
+ - Remove unused regions attribute from mso_schema_template_vrf
+ minor_changes:
+ - Add l3out, preferred_group and test file for mso_schema_template_externalepg
+ - Add mso_schema_template_vrf_contract module and test file
+ - Add new attribute choice "policy_compression" to mso_Schema_template_contract_filter
+ - Add new functionality - Direct Port Channel (dpc), micro-seg-vlan and default
+ values
+ - Add new module for anp-epg-selector in site level
+ - Add new module mso_schema_template_anp_epg_selector and its test file
+ - Add new module mso_schema_vrf_contract
+ - Add new module mso_tenant_site to support cloud and non-cloud sites association
+ with a tenant and test file (https://github.com/CiscoDevNet/ansible-mso/pull/62)
+ - Add new mso_site_external_epg_selector module and test file
+ - Add site external epg and contract filter test
+ - Add support for VGW attribute in mso_schema_site_vrf_region_cidr_subnet
+ - Add support to set account as inactive using account_status attribute in mso_user
+ - Add test for mso_schema_site_vrf_region_cidr module
+ - Add test for mso_schema_site_vrf_region_cidr_subnet module
+ - Add vzAny attribute in mso_schema_template_vrf
+ - Automatically add ANP and EPG at site level and new test file for mso_schema_site_anp_epg_staticport
+ (https://github.com/CiscoDevNet/ansible-mso/pull/55)
+ - Modified External EPG module and addition of new Selector module
+ release_summary: New release v0.0.7
+ release_date: '2020-07-08'
+ 0.0.8:
+ changes:
+ bugfixes:
+ - Add login_domain to existing test.
+ - Add missing tests for VRF settings and changing those settings.
+ - Add test for specifying read-only roles and increase overall test coverage
+ of mso_user (https://github.com/CiscoDevNet/ansible-mso/pull/77)
+ - Add test to mso_schema_template_vrf, mso_schema_template_external_epg and
+ mso_schema_template_anp_epg to check for API error when pushing changes to
+ object with existing contract.
+ - Cleanup unused imports, unused variables and branches and change a variable
+ from ambiguous name to reduce warnings at Ansible Galaxy import
+ - Fix API error when pushing EPG with existing contracts
+ - Fix role tests to work with pre/post 2.2.4 and re-enable them
+ - Fix site issue if no site present and fix test issues with MSO v3.0
+ - Fixing External EPG renaming for 2.9 and later
+ - Fixing L3MCast test to pass on 2.2.4
+ - Fixing wrong removal of schemas
+ - Test hub network module after creating region manually
+ - Updating Azure site IP in inventory and add second MSO version to inventory
+ minor_changes:
+ - Add Login Domain support to mso_site
+ - Add aliases file for contract_filter module
+ - Add contract information in current and previous part
+ - Add new module and test file to query MSO version
+ - New backup module and test file (https://github.com/CiscoDevNet/ansible-mso/pull/80)
+ - Renaming mso_schema_template_externalepg module to mso_schema_template_external_epg
+ while keeping both working.
+ - Update cidr module, udpate attributes in hub network module and its test file
+ - Use a function to reuuse duplicate part
+ release_summary: New release v0.0.8
+ release_date: '2020-07-21'
+ 1.0.0:
+ changes:
+ bugfixes:
+ - Fix sanity issues to support 2.10.0
+ minor_changes:
+ - Add changelog
+ - Fix M() and module to use FQCN
+ - Update Ansible version in CI and add 2.10.0 to sanity in CI.
+ - Update Readme with supported versions
+ release_summary: 'This is the first official release of the ``cisco.mso`` collection
+ on 2020-08-18.
+
+ This changelog describes all changes made to the modules and plugins included
+ in this collection since Ansible 2.9.0.
+
+ '
+ release_date: '2020-08-18'
+ 1.0.1:
+ changes:
+ bugfixes:
+ - Fix default value for l2Stretch in mso_schema_template_bd module
+ - Fix deletion of schema when wrong template is provided in single template
+ schema
+ - Fix examples in documentation for mso_schema_template_l3out and mso_user
+ - Fix naming issue in deploy module
+ - Remove author emails due to length restriction
+ - Remove dead code branch in mso_schema_template
+ minor_changes:
+ - Add delete capability to mso_schema_site
+ - Add env_fallback for mso_argument_spec params
+ - Add non existing template deletion test
+ - Add test file for mso_schema_template
+ - Add test file for site_bd_subnet
+ - Bump module to v1.0.1
+ - Extent mso_tenant test case coverage
+ release_summary: 'Release v1.0.1 of the ``cisco.mso`` collection on 2020-10-30.
+
+ This changelog describes all changes made to the modules and plugins included
+ in this collection since v1.0.0.
+
+ '
+ release_date: '2020-10-30'
+ 1.1.0:
+ changes:
+ bugfixes:
+ - Fix anp idempotency issue
+ - Fix crash issue when using irrelevant site-template
+ - Fix default value for mso_schema state parameter
+ - Fix examples for mso_schema
+ - Fix galaxy-importer check warnings
+ - Fix issue on mso_schema_site_vrf_region_cidr_subnet to allow an AWS subnet
+ to be used for a TGW Attachment (Hub Network)
+ - Fix module name in example of mso_schema_site_vrf_region
+ - Fix mso_backup upload issue
+ - Fix sanity test error mso_schema_site_bd
+ - Fix some coding standard and improvements to contributed mso_dhcp_relay modules
+ and test files
+ - Fix space in asssertion
+ - Fix space in site_anp_epg_domain
+ - Fix space in test file
+ - Remove space from template name in all modules
+ - Remove space in template name
+ minor_changes:
+ - Add DHCP Policy Operations
+ - Add SVI MAC Addreess option in mso_schema_site_bd
+ - Add additional test file to add tenant from templated payload file
+ - Add attribute virtual_ip to mso_schema_site_bd_subnet
+ - Add capability for restore and download backup
+ - Add capability to upload backup
+ - Add check for undeploy under MSO version
+ - Add error handeling test file
+ - Add error message to display when yaml has failed to load
+ - Add galaxy-importer check
+ - Add galaxy-importer config
+ - Add mso_dhcp_option_policy and mso_dhcp_option_policy_option and test files
+ - Add new module mso_rest and test case files to support GET api method
+ - Add new options to template bd and updated test file
+ - Add notes to use region_cidr module to create region
+ - Add task to undeploy the template from the site
+ - Add tasks in test file to remove templates for mso_schema_template_migrate
+ - Add test case for schema removing
+ - Add test cases to verify GET, PUT, POST and DELETE API methods for sites in
+ mso_rest.py
+ - Add test file for mso_schema
+ - Add test file for mso_schema_template_anp
+ - Add test file for region module
+ - Add test files yaml_inline and yaml_string to support YAML
+ - Add userAssociations to tenants to resolve CI issues
+ - Addition of cloud setting for ext epg
+ - Changes made to payload of mso_schema_template_external_epg
+ - Changes to options in template bd
+ - Check warning
+ - Documentation Corrected
+ - Force arp flood to be true when l2unkwunicast is flood
+ - Make changes to display correct status code
+ - Modify mso library and updated test file
+ - Modify mso_rest test files to make PATCH available, and test other methods
+ against schemas
+ - Move options for subnet from mso to the template_bd_subnet module
+ - Python lint corrected
+ - Redirect log to both stdout and log.txt file & Check warnings and errors
+ - Remove creation example in document of mso_schema_site_vrf_region
+ - Remove present state from mso_schema module
+ - Removed unused variable in mso_schema_site_vrf_region_hub_network
+ - Test DHCP Policy Provider added
+ - Test file for mso_dhcp_relay_policy added
+ - Test file for template_bd_subnet and new option foe module
+ release_summary: 'Release v1.1.0 of the ``cisco.mso`` collection on 2021-01-20.
+
+ This changelog describes all changes made to the modules and plugins included
+ in this collection since v1.0.1.
+
+ '
+ release_date: '2021-01-20'
+ 1.2.0:
+ changes:
+ bugfixes:
+ - Add test case and small fixes to mso_schema_site_bd_l3out module
+ - Fix documentation issues accross modules
+ - Fix fail_json usage accross module_utils/mso.py
+ - Fix mso_rest to support HTTPAPI plugin and tests to support ND platform
+ - Fix mso_user to due to error in v1 API in MSO 3.2
+ - Fix path issue in mso_schema_template_migrate
+ - Fixes for site level external epgs and site level L3Outs
+ - Fixes to support MSO 3.3
+ - Remove query of all schemas to get schema ID and only query schema ID indentity
+ list API
+ minor_changes:
+ - Add Ansible common HTTPAPI dependancy in galaxy.yml
+ - Add HTTPAPI connection plugin support and HTTPAPI MSO connection plugin
+ - Add primary and unicast_routing attributes to mso_schema_template_bd
+ - Add requirements.txt for Ansible Environment support
+ - Add schema and template cloning modules mso_schema_clone and mso_schema_template_clone
+ - Add support cisco.nd.nd connection plugin
+ - Add support for multiple DCHP policies in a BD and new module mso_schema_template_bd_dhcp_policy
+ - Upgrade CI to latest Ansible version and Python 3.8
+ release_summary: 'Release v1.2.0 of the ``cisco.mso`` collection on 2021-06-02.
+
+ This changelog describes all changes made to the modules and plugins included
+ in this collection since v1.1.0.
+
+ '
+ plugins:
+ httpapi:
+ - description: MSO Ansible HTTPAPI Plugin.
+ name: mso
+ namespace: null
+ release_date: '2021-06-02'
+ 1.3.0:
+ changes:
+ bugfixes:
+ - Add no_log to aws_access_key and secret_key in mso_tenant_site
+ - Fix MSO HTTP API to work without host, user and password module attribute
+ - Fix issue with unicast_routing idemptotency in mso_schema_template_bd
+ - Fix mso_schema_site_anp and mso_schema_site_anp_epg idempotency issue
+ - Remove sanity ignore files and fix sanity issues that were previously ignored
+ minor_changes:
+ - Add container_overlay and underlay_context_profile support to mso_schema_site_vrf_region
+ - Add description support to various modules
+ - Add hosted_vrf support to mso_schema_site_vrf_region_cidr_subnet
+ - Add module mso_schema_validate to check schema validations
+ - Add private_link_label support to mso_schema_site_anp_epg and mso_schema_site_vrf_region_cidr_subnet
+ - Add qos_level and Service EPG support to mso_schema_template_anp_epg
+ - Add qos_level, action and priority support to mso_schema_template_contract_filter
+ - Add schema and template description support to mso_schema_template
+ - Add subnet as primary support to mso_schema_template_bd_subnet
+ - Add support for automatically creating anp structure at site level when using
+ mso_schema_site_anp_epg
+ - Add support for encap-flood as multi_destination_flooding in mso_schema_template_bd
+ - Add test file for mso_schema_site_anp, mso_schema_site_anp_epg, mso_schema_template_external_epg_subnet
+ mso_schema_template_filter_entry
+ - Improve scope attribute documentation in mso_schema_template_external_epg_subnet
+ - Update Ansible version used in automated testing to v2.9.27, v2.10.16 and
+ addition of v2.11.7 and v2.12.1
+ release_summary: 'Release v1.3.0 of the ``cisco.mso`` collection on 2021-12-18.
+
+ This changelog describes all changes made to the modules and plugins included
+ in this collection since v1.2.0.
+
+ '
+ release_date: '2021-12-18'
+ 1.4.0:
+ changes:
+ bugfixes:
+ - Fix arp_entry value issue in mso_schema_template_filter_entry
+ - Fix mso_schema_site_anp idempotency when children exists
+ - Fix use_ssl documentation to explain usage when used with HTTPAPI connection
+ plugin
+ minor_changes:
+ - Update mso_schema_template_clone to use new method from NDO and unrestrict
+ it to earlier version
+ release_summary: 'Release v1.4.0 of the ``ansible-mso`` collection on 2022-03-15.
+
+ This changelog describes all changes made to the modules and plugins included
+ in this collection since v1.3.0.
+
+ '
+ release_date: '2022-03-15'
+ 2.1.0:
+ changes:
+ bugfixes:
+ - Fix time issue when host running ansible is in a different timezone then NDO
+ - Remove mso_guide from notes
+ deprecated_features:
+ - The mso_schema_template_contract_filter contract_filter_type attribute is
+ deprecated. The value is now deduced from filter_type.
+ minor_changes:
+ - Add aci_remote_location module (#259)
+ - Add mso_backup_schedule module (#250)
+ - Add mso_chema_template_contract_service_graph module (#257)
+ - Add mso_schema_template_service_graph, mso_schema_site_service_graph and mso_service_node_type
+ modules (#243)
+ - Add primary attribute to mso_schema_site_bd_subnet (#254)
+ release_summary: 'Release v2.1.0 of the ``ansible-mso`` collection on 2022-10-14.
+
+ This changelog describes all changes made to the modules and plugins included
+ in this collection since v1.4.0.
+
+ The version was bumped directly to 2.1.0 due to a previous collection upload
+ issue on galaxy.
+
+ '
+ release_date: '2022-10-14'
+ 2.2.0:
+ changes:
+ bugfixes:
+ - Fix MSO HTTPAPI plugin login domain issue (#317)
+ - Fix deploymentImmediacy key inconsistency in the API used by mso_schema_site_anp
+ and mso_schema_site_anp_epg (#283)
+ - Fix mso_schema_template_bd issue when created with unicast_routing as false
+ (#278)
+ - Fix to be able to add multiple filter and filters with "-" in their names
+ (#306)
+ minor_changes:
+ - Add automatic creation of site bd when not existing in mso_schema_site_bd_subnet
+ module (#263)
+ - Add automatic schema validation functionality to mso_schema_template_deploy
+ and ndo_schema_template_deploy (#318)
+ - Add ndo_schema_template_deploy to support NDO 4+ deploy functionality (#305)
+ - Add support for l3out from different template or schema in mso_schema_site_bd_l3out
+ (#304)
+ - Add support for orchestrator_only attribute for mso_tenant with state absent
+ (#268)
+ release_summary: 'Release v2.2.0 of the ``ansible-mso`` collection on 2023-01-29.
+
+ This changelog describes all changes made to the modules and plugins included
+ in this collection since v2.1.0.
+
+ '
+ release_date: '2023-01-29'
+ 2.2.1:
+ changes:
+ bugfixes:
+ - Fix datetime support for python2.7 in mso_backup_schedule (#323)
+ release_summary: 'Release v2.2.1 of the ``ansible-mso`` collection on 2023-01-31.
+
+ This changelog describes all changes made to the modules and plugins included
+ in this collection since v2.2.0.
+
+ '
+ release_date: '2023-01-31'
+ 2.3.0:
+ changes:
+ bugfixes:
+ - Fix idempotency for mso_schema_site_bd_l3out
+ minor_changes:
+ - Add module mso_schema_site_anp_epg_bulk_staticport (#330)
+ - Add route_reachability attribute to mso_schema_site_external_epg module (#335)
+ release_summary: 'Release v2.3.0 of the ``ansible-mso`` collection on 2023-03-30.
+
+ This changelog describes all changes made to the modules and plugins included
+ in this collection since v2.2.1.
+
+ '
+ release_date: '2023-03-30'
+ 2.4.0:
+ changes:
+ bugfixes:
+ - Add attributes to payload for changed schema behaviour of deploymentImmediacy
+ (deployImmediacy) and vmmDomainProperties (properties at domain level in payload)
+ (#362)
+ - Fix mso_backup for NDO and ND-based MSO v3.2+ (#333)
+ - Fix validation condition for path in mso_schema_site_anp_epg_bulk_staticport
+ module (#360)
+ minor_changes:
+ - Add ip_data_plane_learning and preferred_group arguments to mso_schema_template_vrf
+ module (#358)
+ release_summary: 'Release v2.4.0 of the ``ansible-mso`` collection on 2023-04-19.
+
+ This changelog describes all changes made to the modules and plugins included
+ in this collection since v2.3.0.
+
+ '
+ release_date: '2023-04-19'
diff --git a/ansible_collections/cisco/mso/changelogs/config.yaml b/ansible_collections/cisco/mso/changelogs/config.yaml
new file mode 100644
index 000000000..d6a4c94a0
--- /dev/null
+++ b/ansible_collections/cisco/mso/changelogs/config.yaml
@@ -0,0 +1,31 @@
+changelog_filename_template: ../CHANGELOG.rst
+changelog_filename_version_depth: 0
+changes_file: changelog.yaml
+changes_format: combined
+ignore_other_fragment_extensions: true
+keep_fragments: false
+mention_ancestor: true
+new_plugins_after_name: removed_features
+notesdir: fragments
+prelude_section_name: release_summary
+prelude_section_title: Release Summary
+sections:
+- - major_changes
+ - Major Changes
+- - minor_changes
+ - Minor Changes
+- - breaking_changes
+ - Breaking Changes / Porting Guide
+- - deprecated_features
+ - Deprecated Features
+- - removed_features
+ - Removed Features (previously deprecated)
+- - security_fixes
+ - Security Fixes
+- - bugfixes
+ - Bugfixes
+- - known_issues
+ - Known Issues
+title: Cisco MSO Ansible Collection
+trivial_section_name: trivial
+use_fqcn: true
diff --git a/ansible_collections/cisco/mso/codecov.yml b/ansible_collections/cisco/mso/codecov.yml
new file mode 100644
index 000000000..f0f6358e6
--- /dev/null
+++ b/ansible_collections/cisco/mso/codecov.yml
@@ -0,0 +1,4 @@
+coverage:
+ precision: 2
+ round: down
+ range: "70...100"
diff --git a/ansible_collections/cisco/mso/meta/runtime.yml b/ansible_collections/cisco/mso/meta/runtime.yml
new file mode 100644
index 000000000..bbc15c503
--- /dev/null
+++ b/ansible_collections/cisco/mso/meta/runtime.yml
@@ -0,0 +1,142 @@
+---
+requires_ansible: '>=2.9.10'
+plugin_routing:
+ modules:
+ mso_schema_template_externalepg:
+ redirect: cisco.mso.mso_schema_template_external_epg
+action_groups:
+ mso:
+ - mso_backup
+ - mso_backup_schedule
+ - mso_dhcp_option_policy
+ - mso_dhcp_option_policy_option
+ - mso_dhcp_relay_policy
+ - mso_dhcp_relay_policy_provider
+ - mso_label
+ - mso_remote_location
+ - mso_rest
+ - mso_role
+ - mso_schema
+ - mso_schema_clone
+ - mso_schema_site
+ - mso_schema_site_anp
+ - mso_schema_site_anp_epg
+ - mso_schema_site_anp_epg_bulk_staticport
+ - mso_schema_site_anp_epg_domain
+ - mso_schema_site_anp_epg_selector
+ - mso_schema_site_anp_epg_staticleaf
+ - mso_schema_site_anp_epg_staticport
+ - mso_schema_site_anp_epg_subnet
+ - mso_schema_site_bd
+ - mso_schema_site_bd_l3out
+ - mso_schema_site_bd_subnet
+ - mso_schema_site_external_epg
+ - mso_schema_site_external_epg_selector
+ - mso_schema_site_l3out
+ - mso_schema_site_service_graph
+ - mso_schema_site_vrf
+ - mso_schema_site_vrf_region
+ - mso_schema_site_vrf_region_cidr
+ - mso_schema_site_vrf_region_cidr_subnet
+ - mso_schema_site_vrf_region_hub_network
+ - mso_schema_template
+ - mso_schema_template_anp
+ - mso_schema_template_anp_epg
+ - mso_schema_template_anp_epg_contract
+ - mso_schema_template_anp_epg_selector
+ - mso_schema_template_anp_epg_subnet
+ - mso_schema_template_bd
+ - mso_schema_template_bd_dhcp_policy
+ - mso_schema_template_bd_subnet
+ - mso_schema_template_clone
+ - mso_schema_template_contract_filter
+ - mso_schema_template_contract_service_graph
+ - mso_schema_template_deploy
+ - mso_schema_template_deploy_status
+ - mso_schema_template_external_epg
+ - mso_schema_template_external_epg_contract
+ - mso_schema_template_external_epg_selector
+ - mso_schema_template_external_epg_subnet
+ - mso_schema_template_externalepg
+ - mso_schema_template_filter_entry
+ - mso_schema_template_l3out
+ - mso_schema_template_migrate
+ - mso_schema_template_service_graph
+ - mso_schema_template_vrf
+ - mso_schema_template_vrf_contract
+ - mso_schema_validate
+ - mso_service_node_type
+ - mso_site
+ - mso_tenant
+ - mso_tenant_site
+ - mso_user
+ - mso_version
+ ndo:
+ - ndo_schema_template_deploy
+ all:
+ - mso_backup
+ - mso_backup_schedule
+ - mso_dhcp_option_policy
+ - mso_dhcp_option_policy_option
+ - mso_dhcp_relay_policy
+ - mso_dhcp_relay_policy_provider
+ - mso_label
+ - mso_remote_location
+ - mso_rest
+ - mso_role
+ - mso_schema
+ - mso_schema_clone
+ - mso_schema_site
+ - mso_schema_site_anp
+ - mso_schema_site_anp_epg
+ - mso_schema_site_anp_epg_bulk_staticport
+ - mso_schema_site_anp_epg_domain
+ - mso_schema_site_anp_epg_selector
+ - mso_schema_site_anp_epg_staticleaf
+ - mso_schema_site_anp_epg_staticport
+ - mso_schema_site_anp_epg_subnet
+ - mso_schema_site_bd
+ - mso_schema_site_bd_l3out
+ - mso_schema_site_bd_subnet
+ - mso_schema_site_external_epg
+ - mso_schema_site_external_epg_selector
+ - mso_schema_site_l3out
+ - mso_schema_site_service_graph
+ - mso_schema_site_vrf
+ - mso_schema_site_vrf_region
+ - mso_schema_site_vrf_region_cidr
+ - mso_schema_site_vrf_region_cidr_subnet
+ - mso_schema_site_vrf_region_hub_network
+ - mso_schema_template
+ - mso_schema_template_anp
+ - mso_schema_template_anp_epg
+ - mso_schema_template_anp_epg_contract
+ - mso_schema_template_anp_epg_selector
+ - mso_schema_template_anp_epg_subnet
+ - mso_schema_template_bd
+ - mso_schema_template_bd_dhcp_policy
+ - mso_schema_template_bd_subnet
+ - mso_schema_template_clone
+ - mso_schema_template_contract_filter
+ - mso_schema_template_contract_service_graph
+ - mso_schema_template_deploy
+ - mso_schema_template_deploy_status
+ - mso_schema_template_external_epg
+ - mso_schema_template_external_epg_contract
+ - mso_schema_template_external_epg_selector
+ - mso_schema_template_external_epg_subnet
+ - mso_schema_template_externalepg
+ - mso_schema_template_filter_entry
+ - mso_schema_template_l3out
+ - mso_schema_template_migrate
+ - mso_schema_template_service_graph
+ - mso_schema_template_vrf
+ - mso_schema_template_vrf_contract
+ - mso_schema_validate
+ - mso_service_node_type
+ - mso_site
+ - mso_tenant
+ - mso_tenant_site
+ - mso_user
+ - mso_version
+ - ndo_schema_template_deploy
diff --git a/ansible_collections/cisco/mso/plugins/.DS_Store b/ansible_collections/cisco/mso/plugins/.DS_Store
new file mode 100644
index 000000000..1ffed58d7
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/.DS_Store
Binary files differ
diff --git a/ansible_collections/cisco/mso/plugins/doc_fragments/modules.py b/ansible_collections/cisco/mso/plugins/doc_fragments/modules.py
new file mode 100644
index 000000000..c7d3d81c0
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/doc_fragments/modules.py
@@ -0,0 +1,83 @@
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+
+class ModuleDocFragment(object):
+ # Standard files documentation fragment
+ DOCUMENTATION = r"""
+options:
+ host:
+ description:
+ - IP Address or hostname of the ACI Multi Site Orchestrator host.
+ - If the value is not specified in the task, the value of environment variable C(MSO_HOST) will be used instead.
+ type: str
+ aliases: [ hostname ]
+ port:
+ description:
+ - Port number to be used for the REST connection.
+ - The default value depends on parameter `use_ssl`.
+ - If the value is not specified in the task, the value of environment variable C(MSO_PORT) will be used instead.
+ type: int
+ username:
+ description:
+ - The username to use for authentication.
+ - If the value is not specified in the task, the value of environment variables C(MSO_USERNAME) or C(ANSIBLE_NET_USERNAME) will be used instead.
+ type: str
+ password:
+ description:
+ - The password to use for authentication.
+ - If the value is not specified in the task, the value of environment variables C(MSO_PASSWORD) or C(ANSIBLE_NET_PASSWORD) will be used instead.
+ type: str
+ output_level:
+ description:
+ - Influence the output of this MSO module.
+ - C(normal) means the standard output, incl. C(current) dict
+ - C(info) adds informational output, incl. C(previous), C(proposed) and C(sent) dicts
+ - C(debug) adds debugging output, incl. C(filter_string), C(method), C(response), C(status) and C(url) information
+ - If the value is not specified in the task, the value of environment variable C(MSO_OUTPUT_LEVEL) will be used instead.
+ type: str
+ choices: [ debug, info, normal ]
+ default: normal
+ timeout:
+ description:
+ - The socket level timeout in seconds.
+ - If the value is not specified in the task, the value of environment variable C(MSO_TIMEOUT) will be used instead.
+ type: int
+ default: 30
+ use_proxy:
+ description:
+ - If C(false), it will not use a proxy, even if one is defined in an environment variable on the target hosts.
+ - If the value is not specified in the task, the value of environment variable C(MSO_USE_PROXY) will be used instead.
+ - The default is C(true).
+ type: bool
+ use_ssl:
+ description:
+ - If C(false), an HTTP connection will be used instead of the default HTTPS connection.
+ - If the value is not specified in the task, the value of environment variable C(MSO_USE_SSL) will be used instead.
+ - When using a HTTPAPI connection plugin the inventory variable C(ansible_httpapi_use_ssl) will be used if this attribute is not specified.
+ - The default is C(false) when using a HTTPAPI connection plugin (mso or nd) and C(true) when using the legacy connection method (only for mso).
+ type: bool
+ validate_certs:
+ description:
+ - If C(false), SSL certificates will not be validated.
+ - This should only set to C(false) when used on personally controlled sites using self-signed certificates.
+ - If the value is not specified in the task, the value of environment variable C(MSO_VALIDATE_CERTS) will be used instead.
+ - The default is C(true).
+ type: bool
+ login_domain:
+ description:
+ - The login domain name to use for authentication.
+ - The default value is Local.
+ - If the value is not specified in the task, the value of environment variable C(MSO_LOGIN_DOMAIN) will be used instead.
+ type: str
+requirements:
+- Multi Site Orchestrator v2.1 or newer
+notes:
+- This module was written to support Multi Site Orchestrator v2.1 or newer. Some or all functionality may not work on earlier versions.
+"""
diff --git a/ansible_collections/cisco/mso/plugins/httpapi/mso.py b/ansible_collections/cisco/mso/plugins/httpapi/mso.py
new file mode 100644
index 000000000..5d69c8a64
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/httpapi/mso.py
@@ -0,0 +1,291 @@
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com>
+#
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+---
+name: mso
+short_description: MSO Ansible HTTPAPI Plugin.
+description:
+ - This MSO plugin provides the HTTPAPI transport methods needed to initiate
+ a connection to MSO, send API requests and process the
+ response.
+version_added: "1.2.0"
+"""
+
+import json
+import re
+import pickle
+
+# import ipaddress
+import traceback
+
+from ansible.module_utils.six import PY3
+from ansible.module_utils._text import to_text
+from ansible.module_utils.connection import ConnectionError
+from ansible.plugins.httpapi import HttpApiBase
+
+
+class HttpApi(HttpApiBase):
+ def __init__(self, *args, **kwargs):
+ super(HttpApi, self).__init__(*args, **kwargs)
+ self.platform = "cisco.mso"
+ self.headers = {"Content-Type": "application/json"}
+ self.params = {}
+ self.auth = None
+ self.backup_hosts = None
+ self.host_counter = 0
+
+ self.error = None
+ self.method = "GET"
+ self.path = ""
+ self.status = -1
+ self.info = {}
+
+ def get_platform(self):
+ return self.platform
+
+ def set_params(self, params):
+ self.params = params
+
+ def set_backup_hosts(self):
+ try:
+ list_of_hosts = re.sub(r"[[\]]", "", self.connection.get_option("host")).split(",")
+ # ipaddress.ip_address(list_of_hosts[0])
+ return list_of_hosts
+ except Exception:
+ return []
+
+ def login(self, username, password):
+ """Log in to MSO"""
+ # Perform login request
+ self.connection.queue_message("vvvv", "Starting Login to {0}".format(self.connection.get_option("host")))
+
+ method = "POST"
+ path = "/mso/api/v1/auth/login"
+ full_path = self.connection.get_option("host") + path
+
+ if (self.params.get("login_domain") is not None) and (self.params.get("login_domain") != "Local"):
+ domain_id = self._get_login_domain_id(self.params.get("login_domain"))
+ payload = {"username": self.connection.get_option("remote_user"), "password": self.connection.get_option("password"), "domainId": domain_id}
+ else:
+ payload = {"username": self.connection.get_option("remote_user"), "password": self.connection.get_option("password")}
+
+ # Override the global username/password with the ones specified per task
+ if self.params.get("username") is not None:
+ payload["username"] = self.params.get("username")
+ if self.params.get("password") is not None:
+ payload["password"] = self.params.get("password")
+ data = json.dumps(payload)
+ try:
+ self.connection.queue_message("vvvv", "login() - connection.send({0}, {1}, {2}, {3})".format(path, data, method, self.headers))
+ response, response_data = self.connection.send(path, data, method=method, headers=self.headers)
+ # Handle MSO response
+ self.status = response.getcode()
+ if self.status != 201:
+ self.connection.queue_message("vvvv", "login status incorrect status={0}".format(self.status))
+ json_response = self._response_to_json(response_data)
+ self.error = dict(code=self.status, message="Authentication failed: {0}".format(json_response))
+ raise ConnectionError(json.dumps(self._verify_response(response, method, full_path, response_data)))
+ self.connection._auth = {"Authorization": "Bearer {0}".format(self._response_to_json(response_data).get("token"))}
+
+ except ConnectionError:
+ self.connection.queue_message("vvvv", "login() - ConnectionError Exception")
+ raise
+ except Exception as e:
+ self.connection.queue_message("vvvv", "login() - Generic Exception")
+ self.error = dict(code=self.status, message="Authentication failed: Request failed: {0}".format(e))
+ raise ConnectionError(json.dumps(self._verify_response(None, method, full_path, None)))
+
+ def logout(self):
+ method = "DELETE"
+ path = "/mso/api/v1/auth/logout"
+
+ try:
+ response, response_data = self.connection.send(path, {}, method=method, headers=self.headers)
+ except Exception as e:
+ self.error = dict(code=self.status, message="Error on attempt to logout from MSO. {0}".format(e))
+ raise ConnectionError(json.dumps(self._verify_response(None, method, self.connection.get_option("host") + path, None)))
+ self.connection._auth = None
+
+ def send_request(self, method, path, data=None):
+ """This method handles all MSO REST API requests other than login"""
+
+ self.error = None
+ self.path = ""
+ self.status = -1
+ self.info = {}
+ self.method = "GET"
+
+ if data is None:
+ data = {}
+
+ self.connection.queue_message("vvvv", "send_request method called")
+ # # Case1: List of hosts is provided
+ # self.backup_hosts = self.set_backup_hosts()
+ # if not self.backup_hosts:
+ if self.connection._connected is True and self.params.get("host") != self.connection.get_option("host"):
+ self.connection._connected = False
+ self.connection.queue_message(
+ "vvvv",
+ "send_request reseting connection as host has changed from {0} to {1}".format(self.connection.get_option("host"), self.params.get("host")),
+ )
+
+ if self.params.get("host") is not None:
+ self.connection.set_option("host", self.params.get("host"))
+
+ else:
+ try:
+ with open("my_hosts.pk", "rb") as fi:
+ self.host_counter = pickle.load(fi)
+ except FileNotFoundError:
+ pass
+ try:
+ self.connection.set_option("host", self.backup_hosts[self.host_counter])
+ except (IndexError, TypeError):
+ pass
+
+ if self.params.get("port") is not None:
+ self.connection.set_option("port", self.params.get("port"))
+
+ if self.params.get("username") is not None:
+ self.connection.set_option("remote_user", self.params.get("username"))
+
+ if self.params.get("password") is not None:
+ self.connection.set_option("password", self.params.get("password"))
+
+ if self.params.get("use_proxy") is not None:
+ self.connection.set_option("use_proxy", self.params.get("use_proxy"))
+
+ if self.params.get("use_ssl") is not None:
+ self.connection.set_option("use_ssl", self.params.get("use_ssl"))
+
+ if self.params.get("validate_certs") is not None:
+ self.connection.set_option("validate_certs", self.params.get("validate_certs"))
+
+ # Perform some very basic path input validation.
+ path = str(path)
+ if path[0] != "/":
+ self.error = dict(code=self.status, message="Value of <path> does not appear to be formated properly")
+ raise ConnectionError(json.dumps(self._verify_response(None, method, path, None)))
+ full_path = self.connection.get_option("host") + path
+ try:
+ self.connection.queue_message("vvvv", "send_request() - connection.send({0}, {1}, {2}, {3})".format(path, data, method, self.headers))
+ response, rdata = self.connection.send(path, data, method=method, headers=self.headers)
+ except ConnectionError:
+ self.connection.queue_message("vvvv", "login() - ConnectionError Exception")
+ raise
+ except Exception as e:
+ self.connection.queue_message("vvvv", "send_request() - Generic Exception")
+ if self.error is None:
+ self.error = dict(code=self.status, message="MSO HTTPAPI send_request() Exception: {0} - {1}".format(e, traceback.format_exc()))
+ raise ConnectionError(json.dumps(self._verify_response(None, method, full_path, None)))
+ return self._verify_response(response, method, full_path, rdata)
+
+ def handle_error(self):
+ self.host_counter += 1
+ if self.host_counter == len(self.backup_hosts):
+ raise ConnectionError("No hosts left in cluster to continue operation")
+ with open("my_hosts.pk", "wb") as host_file:
+ pickle.dump(self.host_counter, host_file)
+ try:
+ self.connection.set_option("host", self.backup_hosts[self.host_counter])
+ except IndexError:
+ pass
+ self.login(self.connection.get_option("remote_user"), self.connection.get_option("password"))
+ return True
+
+ def _verify_response(self, response, method, path, data):
+ """Process the return code and response object from MSO"""
+ response_data = None
+ response_code = -1
+ self.info.update(dict(url=path))
+ if data is not None:
+ response_data = self._response_to_json(data)
+ if response is not None:
+ response_code = response.getcode()
+ path = response.geturl()
+ self.info.update(self._get_formated_info(response))
+
+ # Handle possible MSO error information
+ if response_code not in [200, 201, 202, 204]:
+ self.error = dict(code=self.status, message=response_data)
+
+ self.info["method"] = method
+ if self.error is not None:
+ self.info["error"] = self.error
+
+ self.info["body"] = response_data
+
+ return self.info
+
+ def _response_to_json(self, response_data):
+ """Convert response_data to json format"""
+ try:
+ response_value = response_data.getvalue()
+ except Exception:
+ response_value = response_data
+ response_text = to_text(response_value)
+ try:
+ return json.loads(response_text) if response_text else {}
+ # JSONDecodeError only available on Python 3.5+
+ except Exception as e:
+ # Expose RAW output for troubleshooting
+ self.error = dict(code=-1, message="Unable to parse output as JSON, see 'raw' output. {0}".format(e))
+ self.info["raw"] = response_text
+ return
+
+ def _get_login_domain_id(self, domain_name):
+ """Get a domain and return its id"""
+ if domain_name is None:
+ return None
+
+ method = "GET"
+ path = "/mso/api/v1/auth/login-domains"
+ full_path = self.connection.get_option("host") + path
+
+ # TODO: Replace response by -
+ response, data = self.connection.send(path, None, method=method, headers=self.headers)
+
+ if data is not None:
+ response_data = self._response_to_json(data)
+ domains = response_data.get("domains")
+ if domains is not None:
+ for domain in domains:
+ if domain.get("name") == domain_name:
+ if "id" in domain:
+ return domain.get("id")
+ else:
+ self.error = dict(code=-1, message="Login domain lookup failed for domain '{0}': {1}".format(domain_name, domain))
+ raise ConnectionError(json.dumps(self._verify_response(None, method, full_path, None)))
+ self.error = dict(code=-1, message="Login domain '{0}' is not a valid domain name.".format(domain_name))
+ raise ConnectionError(json.dumps(self._verify_response(None, method, full_path, None)))
+ else:
+ self.error = dict(code=-1, message="Key 'domains' missing from data")
+ raise ConnectionError(json.dumps(self._verify_response(None, method, full_path, None)))
+
+ def _get_formated_info(self, response):
+ """The code in this function is based out of Ansible fetch_url code
+ at https://github.com/ansible/ansible/blob/devel/lib/ansible/module_utils/urls.py"""
+ info = dict(msg="OK (%s bytes)" % response.headers.get("Content-Length", "unknown"), url=response.geturl(), status=response.getcode())
+ # Lowercase keys, to conform to py2 behavior, so that py3 and py2 are predictable
+ info.update(dict((k.lower(), v) for k, v in response.info().items()))
+
+ # Don't be lossy, append header values for duplicate headers
+ # In Py2 there is nothing that needs done, py2 does this for us
+ if PY3:
+ temp_headers = {}
+ for name, value in response.headers.items():
+ # The same as above, lower case keys to match py2 behavior, and create more consistent results
+ name = name.lower()
+ if name in temp_headers:
+ temp_headers[name] = ", ".join((temp_headers[name], value))
+ else:
+ temp_headers[name] = value
+ info.update(temp_headers)
+ return info
diff --git a/ansible_collections/cisco/mso/plugins/module_utils/constants.py b/ansible_collections/cisco/mso/plugins/module_utils/constants.py
new file mode 100644
index 000000000..ea461f76c
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/module_utils/constants.py
@@ -0,0 +1,24 @@
+FILTER_KEY_MAP = {
+ "both-way": "filterRelationships",
+ "consumer-to-provider": "filterRelationshipsConsumerToProvider",
+ "provider-to-consumer": "filterRelationshipsProviderToConsumer",
+}
+
+PRIORITY_MAP = {
+ "default": "default",
+ "lowest_priority": "level1",
+ "medium_priority": "level2",
+ "highest_priority": "level3",
+}
+
+SERVICE_NODE_CONNECTOR_MAP = {
+ "bd": {"id": "bd", "connector_type": "general"}
+ # 'external_epg': {'id': 'externalEpg', 'connector_type': 'route-peering'}
+}
+
+YES_OR_NO_TO_BOOL_STRING_MAP = {"yes": "true", "no": "false"}
+
+NDO_4_UNIQUE_IDENTIFIERS = ["templateID", "autoRouteTargetImport", "autoRouteTargetExport"]
+
+NDO_API_VERSION_FORMAT = "/mso/api/{api_version}"
+NDO_API_VERSION_PATH_FORMAT = "/mso/api/{api_version}/{path}"
diff --git a/ansible_collections/cisco/mso/plugins/module_utils/mso.py b/ansible_collections/cisco/mso/plugins/module_utils/mso.py
new file mode 100644
index 000000000..91475336b
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/module_utils/mso.py
@@ -0,0 +1,1354 @@
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+from copy import deepcopy
+import re
+import os
+import ast
+import datetime
+import shutil
+import tempfile
+from ansible.module_utils.basic import json
+from ansible.module_utils.basic import env_fallback
+from ansible.module_utils.six import PY3
+from ansible.module_utils.six.moves import filterfalse
+from ansible.module_utils.six.moves.urllib.parse import urlencode, urljoin
+from ansible.module_utils.urls import fetch_url
+from ansible.module_utils._text import to_native, to_text
+from ansible.module_utils.connection import Connection
+from ansible_collections.cisco.mso.plugins.module_utils.constants import NDO_API_VERSION_PATH_FORMAT
+
+try:
+ from requests_toolbelt.multipart.encoder import MultipartEncoder
+
+ HAS_MULTIPART_ENCODER = True
+except ImportError:
+ HAS_MULTIPART_ENCODER = False
+
+
+if PY3:
+
+ def cmp(a, b):
+ return (a > b) - (a < b)
+
+
+def issubset(subset, superset):
+ """Recurse through nested dictionary and compare entries"""
+
+ # Both objects are the same object
+ if subset is superset:
+ return True
+
+ # Both objects are identical
+ if subset == superset:
+ return True
+
+ # Both objects have a different type
+ if type(subset) != type(superset):
+ return False
+
+ for key, value in subset.items():
+ # Ignore empty values
+ if value is None:
+ return True
+
+ # Item from subset is missing from superset
+ if key not in superset:
+ return False
+
+ # Item has different types in subset and superset
+ if type(superset.get(key)) != type(value):
+ return False
+
+ # Compare if item values are subset
+ if isinstance(value, dict):
+ if not issubset(superset.get(key), value):
+ return False
+ elif isinstance(value, list):
+ try:
+ # NOTE: Fails for lists of dicts
+ if not set(value) <= set(superset.get(key)):
+ return False
+ except TypeError:
+ # Fall back to exact comparison for lists of dicts
+ diff = list(filterfalse(lambda i: i in value, superset.get(key))) + list(filterfalse(lambda j: j in superset.get(key), value))
+ if diff:
+ return False
+ elif isinstance(value, set):
+ if not value <= superset.get(key):
+ return False
+ else:
+ if not value == superset.get(key):
+ return False
+
+ return True
+
+
+def update_qs(params):
+ """Append key-value pairs to self.filter_string"""
+ accepted_params = dict((k, v) for (k, v) in params.items() if v is not None)
+ return "?" + urlencode(accepted_params)
+
+
+def mso_argument_spec():
+ return dict(
+ host=dict(type="str", required=False, aliases=["hostname"], fallback=(env_fallback, ["MSO_HOST"])),
+ port=dict(type="int", required=False, fallback=(env_fallback, ["MSO_PORT"])),
+ username=dict(type="str", required=False, fallback=(env_fallback, ["MSO_USERNAME", "ANSIBLE_NET_USERNAME"])),
+ password=dict(type="str", required=False, no_log=True, fallback=(env_fallback, ["MSO_PASSWORD", "ANSIBLE_NET_PASSWORD"])),
+ output_level=dict(type="str", default="normal", choices=["debug", "info", "normal"], fallback=(env_fallback, ["MSO_OUTPUT_LEVEL"])),
+ timeout=dict(type="int", default=30, fallback=(env_fallback, ["MSO_TIMEOUT"])),
+ use_proxy=dict(type="bool", fallback=(env_fallback, ["MSO_USE_PROXY"])),
+ use_ssl=dict(type="bool", fallback=(env_fallback, ["MSO_USE_SSL"])),
+ validate_certs=dict(type="bool", fallback=(env_fallback, ["MSO_VALIDATE_CERTS"])),
+ login_domain=dict(type="str", fallback=(env_fallback, ["MSO_LOGIN_DOMAIN"])),
+ )
+
+
+def mso_reference_spec():
+ return dict(
+ name=dict(type="str", required=True),
+ schema=dict(type="str"),
+ template=dict(type="str"),
+ )
+
+
+def mso_epg_subnet_spec():
+ return dict(
+ subnet=dict(type="str", required=True, aliases=["ip"]),
+ description=dict(type="str"),
+ scope=dict(type="str", default="private", choices=["private", "public"]),
+ shared=dict(type="bool", default=False),
+ no_default_gateway=dict(type="bool", default=False),
+ )
+
+
+def mso_subnet_spec():
+ subnet_spec = mso_epg_subnet_spec()
+ subnet_spec.update(dict(querier=dict(type="bool", default=False)))
+ return subnet_spec
+
+
+def mso_bd_subnet_spec():
+ subnet_spec = mso_epg_subnet_spec()
+ subnet_spec.update(dict(querier=dict(type="bool", default=False)))
+ subnet_spec.update(dict(primary=dict(type="bool", default=False)))
+ subnet_spec.update(dict(virtual=dict(type="bool", default=False)))
+ return subnet_spec
+
+
+def mso_dhcp_spec():
+ return dict(
+ dhcp_option_policy=dict(type="dict", options=mso_dhcp_option_spec()),
+ name=dict(type="str", required=True),
+ version=dict(type="int", required=True),
+ )
+
+
+def mso_dhcp_option_spec():
+ return dict(
+ name=dict(type="str", required=True),
+ version=dict(type="int", required=True),
+ )
+
+
+def mso_contractref_spec():
+ return dict(
+ name=dict(type="str", required=True),
+ schema=dict(type="str"),
+ template=dict(type="str"),
+ type=dict(type="str", required=True, choices=["consumer", "provider"]),
+ )
+
+
+def mso_expression_spec():
+ return dict(
+ type=dict(type="str", required=True, aliases=["tag"]),
+ operator=dict(type="str", choices=["not_in", "in", "equals", "not_equals", "has_key", "does_not_have_key"], required=True),
+ value=dict(type="str"),
+ )
+
+
+def mso_expression_spec_ext_epg():
+ return dict(
+ type=dict(type="str", choices=["ip_address"], required=True),
+ operator=dict(type="str", choices=["equals"], required=True),
+ value=dict(type="str", required=True),
+ )
+
+
+def mso_hub_network_spec():
+ return dict(
+ name=dict(type="str", required=True),
+ tenant=dict(type="str", required=True),
+ )
+
+
+def mso_object_migrate_spec():
+ return dict(
+ epg=dict(type="str", required=True),
+ anp=dict(type="str", required=True),
+ )
+
+
+def mso_service_graph_node_spec():
+ return dict(
+ type=dict(type="str", required=True),
+ )
+
+
+def mso_service_graph_node_device_spec():
+ return dict(
+ name=dict(type="str", required=True),
+ )
+
+
+def mso_service_graph_connector_spec():
+ return dict(
+ provider=dict(type="str", required=True),
+ consumer=dict(type="str", required=True),
+ # Only connectorType bd with value "general" is supported for now thus fixed in code
+ # when connectorType externalEpg is supported "route-peering" should be added
+ # also change SERVICE_NODE_CONNECTOR_TYPE_MAP in constants.py
+ # also verify if connector type is specific to provider or always same for both
+ connector_object_type=dict(type="str", default="bd", choices=["bd"]),
+ provider_schema=dict(type="str"),
+ provider_template=dict(type="str"),
+ consumer_schema=dict(type="str"),
+ consumer_template=dict(type="str"),
+ )
+
+
+def mso_site_anp_epg_bulk_staticport_spec():
+ return dict(
+ type=dict(type="str", choices=["port", "vpc", "dpc"]),
+ pod=dict(type="str"), # This parameter is not required for querying all objects
+ leaf=dict(type="str"), # This parameter is not required for querying all objects
+ fex=dict(type="str"), # This parameter is not required for querying all objects
+ path=dict(type="str"), # This parameter is not required for querying all objects
+ vlan=dict(type="int"), # This parameter is not required for querying all objects
+ primary_micro_segment_vlan=dict(type="int"), # This parameter is not required for querying all objects
+ deployment_immediacy=dict(type="str", choices=["immediate", "lazy"]),
+ mode=dict(type="str", choices=["native", "regular", "untagged"]),
+ )
+
+
+# Copied from ansible's module uri.py (url): https://github.com/ansible/ansible/blob/cdf62edc65f564fff6b7e575e084026fa7faa409/lib/ansible/modules/uri.py
+def write_file(module, url, dest, content, resp, tmpsrc=None):
+ # create a tempfile with some test content
+
+ if tmpsrc is None and content is not None:
+ fd, tmpsrc = tempfile.mkstemp(dir=module.tmpdir)
+ f = open(tmpsrc, "wb")
+ try:
+ f.write(content)
+ except Exception as e:
+ os.remove(tmpsrc)
+ module.fail_json(msg="Failed to create temporary content file: {0}".format(to_native(e)))
+ f.close()
+
+ checksum_src = None
+ checksum_dest = None
+
+ # raise an error if there is no tmpsrc file
+ if not os.path.exists(tmpsrc):
+ os.remove(tmpsrc)
+ module.fail_json(msg="Source '{0}' does not exist".format(tmpsrc))
+ if not os.access(tmpsrc, os.R_OK):
+ os.remove(tmpsrc)
+ module.fail_json(msg="Source '{0}' is not readable".format(tmpsrc))
+ checksum_src = module.sha1(tmpsrc)
+
+ # check if there is no dest file
+ if os.path.exists(dest):
+ # raise an error if copy has no permission on dest
+ if not os.access(dest, os.W_OK):
+ os.remove(tmpsrc)
+ module.fail_json(msg="Destination '{0}' not writable".format(dest))
+ if not os.access(dest, os.R_OK):
+ os.remove(tmpsrc)
+ module.fail_json(msg="Destination '{0}' not readable".format(dest))
+ checksum_dest = module.sha1(dest)
+ else:
+ if not os.access(os.path.dirname(dest), os.W_OK):
+ os.remove(tmpsrc)
+ module.fail_json(msg="Destination dir '{0}' not writable".format(os.path.dirname(dest)))
+
+ if checksum_src != checksum_dest:
+ try:
+ shutil.copyfile(tmpsrc, dest)
+ except Exception as e:
+ os.remove(tmpsrc)
+ module.fail_json(msg="failed to copy {0} to {1}: {2}".format(tmpsrc, dest, to_native(e)))
+
+ os.remove(tmpsrc)
+
+
+class MSOModule(object):
+ def __init__(self, module):
+ self.module = module
+ self.params = module.params
+ self.result = dict(changed=False)
+ self.headers = {"Content-Type": "text/json"}
+ self.platform = "mso"
+
+ # normal output
+ self.existing = dict()
+
+ # mso_rest output
+ self.jsondata = None
+ self.error = dict(code=None, message=None, info=None)
+
+ # info output
+ self.previous = dict()
+ self.proposed = dict()
+ self.sent = dict()
+ self.stdout = None
+ self.patch_operation = None
+
+ # debug output
+ self.has_modified = False
+ self.filter_string = ""
+ self.method = None
+ self.path = None
+ self.response = None
+ self.status = None
+ self.url = None
+ self.httpapi_logs = list()
+
+ if self.module._debug:
+ self.module.warn("Enable debug output because ANSIBLE_DEBUG was set.")
+ self.params["output_level"] = "debug"
+
+ if self.module._socket_path is None:
+ if self.params.get("use_ssl") is None:
+ self.params["use_ssl"] = True
+ if self.params.get("use_proxy") is None:
+ self.params["use_proxy"] = True
+ if self.params.get("validate_certs") is None:
+ self.params["validate_certs"] = True
+
+ # Ensure protocol is set
+ self.params["protocol"] = "https" if self.params.get("use_ssl", True) else "http"
+
+ # Set base_uri
+ if self.params.get("port") is not None:
+ self.base_only_uri = "{protocol}://{host}:{port}/".format(**self.params)
+ self.baseuri = "{0}api/v1/".format(self.base_only_uri)
+ else:
+ self.base_only_uri = "{protocol}://{host}/".format(**self.params)
+ self.baseuri = "{0}api/v1/".format(self.base_only_uri)
+
+ if self.params.get("host") is None:
+ self.fail_json(msg="Parameter 'host' is required when not using the HTTP API connection plugin")
+
+ if self.params.get("password"):
+ # Perform password-based authentication, log on using password
+ self.login()
+ else:
+ self.fail_json(msg="Parameter 'password' is required for authentication")
+ else:
+ self.connection = Connection(self.module._socket_path)
+ if self.connection.get_platform() == "cisco.nd":
+ self.platform = "nd"
+
+ def get_login_domain_id(self, domain):
+ """Get a domain and return its id"""
+ if domain is None:
+ return domain
+ d = self.get_obj("auth/login-domains", key="domains", name=domain)
+ if not d:
+ self.fail_json(msg="Login domain '%s' is not a valid domain name." % domain)
+ if "id" not in d:
+ self.fail_json(msg="Login domain lookup failed for domain '%s': %s" % (domain, d))
+ return d["id"]
+
+ def login(self):
+ """Log in to MSO"""
+
+ # Perform login request
+ if (self.params.get("login_domain") is not None) and (self.params.get("login_domain") != "Local"):
+ domain_id = self.get_login_domain_id(self.params.get("login_domain"))
+ payload = {"username": self.params.get("username", "admin"), "password": self.params.get("password"), "domainId": domain_id}
+ else:
+ payload = {"username": self.params.get("username", "admin"), "password": self.params.get("password")}
+ self.url = urljoin(self.baseuri, "auth/login")
+ resp, auth = fetch_url(
+ self.module,
+ self.url,
+ data=json.dumps(payload),
+ method="POST",
+ headers=self.headers,
+ timeout=self.params.get("timeout"),
+ use_proxy=self.params.get("use_proxy"),
+ )
+
+ # Handle MSO response
+ if auth.get("status") not in [200, 201]:
+ self.response = auth.get("msg")
+ self.status = auth.get("status")
+ self.fail_json(msg="Authentication failed: {msg}".format(**auth))
+
+ payload = json.loads(resp.read())
+
+ self.headers["Authorization"] = "Bearer {token}".format(**payload)
+
+ def response_json(self, rawoutput):
+ """Handle MSO JSON response output"""
+ try:
+ self.jsondata = json.loads(rawoutput)
+ except Exception as e:
+ # Expose RAW output for troubleshooting
+ self.error = dict(code=-1, message="Unable to parse output as JSON, see 'raw' output. %s" % e)
+ self.result["raw"] = rawoutput
+ return
+
+ # Handle possible MSO error information
+ if self.status not in [200, 201, 202, 204]:
+ self.error = self.jsondata
+
+ def request_download(self, path, destination=None, method="GET", api_version="v1"):
+ if self.platform != "nd":
+ self.url = urljoin(self.baseuri, path)
+
+ redirected = False
+ redir_info = {}
+ redirect = {}
+ content = None
+ data = None
+
+ src = self.params.get("src")
+ if src:
+ try:
+ self.headers.update({"Content-Length": os.stat(src).st_size})
+ data = open(src, "rb")
+ except OSError:
+ self.fail_json(msg="Unable to open source file %s" % src, elapsed=0)
+
+ kwargs = {}
+ if destination is not None and os.path.isdir(destination):
+ # first check if we are redirected to a file download
+ if self.platform == "nd":
+ redir_info = self.connection.get_remote_file_io_stream(
+ NDO_API_VERSION_PATH_FORMAT.format(api_version=api_version, path=path), self.module.tmpdir, method
+ )
+ # In place of Content-Disposition, NDO get_remote_file_io_stream returns content-disposition.
+ content_disposition = redir_info.get("content-disposition")
+ else:
+ check, redir_info = fetch_url(self.module, self.url, headers=self.headers, method=method, timeout=self.params.get("timeout"))
+ content_disposition = check.headers.get("Content-Disposition")
+
+ if content_disposition:
+ file_name = content_disposition.split("filename=")[1]
+ else:
+ self.fail_json(msg="Failed to fetch {0} backup information from MSO/NDO, response: {1}".format(self.params.get("backup"), redir_info))
+
+ # if we are redirected, update the url with the location header and update dest with the new url filename
+ if redir_info["status"] in (301, 302, 303, 307):
+ self.url = redir_info.get("location")
+ redirected = True
+ destination = os.path.join(destination, file_name)
+
+ # if destination file already exist, only download if file newer
+ if os.path.exists(destination):
+ kwargs["last_mod_time"] = datetime.datetime.utcfromtimestamp(os.path.getmtime(destination))
+
+ if self.platform == "nd":
+ if redir_info["status"] == 200 and redirected is False:
+ info = redir_info
+ else:
+ info = self.connection.get_remote_file_io_stream("/mso/{0}".format(self.url.split("/mso/", 1)), self.module.tmpdir, method)
+ else:
+ resp, info = fetch_url(
+ self.module,
+ self.url,
+ data=data,
+ headers=self.headers,
+ method=method,
+ timeout=self.params.get("timeout"),
+ unix_socket=self.params.get("unix_socket"),
+ **kwargs
+ )
+
+ try:
+ content = resp.read()
+ except AttributeError:
+ # there was no content, but the error read() may have been stored in the info as 'body'
+ content = info.pop("body", "")
+
+ if src:
+ # Try to close the open file handle
+ try:
+ data.close()
+ except Exception:
+ pass
+
+ redirect["redirected"] = redirected or info.get("url") != self.url
+ redirect.update(redir_info)
+ redirect.update(info)
+
+ write_file(self.module, self.url, destination, content, redirect, info.get("tmpsrc"))
+
+ return redirect, destination
+
+ def request_upload(self, path, fields=None, method="POST", api_version="v1"):
+ """Generic HTTP MultiPart POST method for MSO uploads."""
+ self.path = path
+ if self.platform != "nd":
+ self.url = urljoin(self.baseuri, path)
+
+ info = dict()
+
+ if self.platform == "nd":
+ try:
+ if os.path.exists(self.params.get("backup")):
+ info = self.connection.send_file_request(
+ method,
+ NDO_API_VERSION_PATH_FORMAT.format(api_version=api_version, path=path),
+ file=self.params.get("backup"),
+ remote_path=self.params.get("remote_path"),
+ )
+ else:
+ self.fail_json(msg="Upload failed due to: No such file or directory, Backup file: '{0}'".format(self.params.get("backup")))
+ except Exception as error:
+ self.fail_json("NDO upload failed due to: {0}".format(error))
+ else:
+ if not HAS_MULTIPART_ENCODER:
+ self.fail_json(msg="requests-toolbelt is required for the upload state of this module")
+
+ mp_encoder = MultipartEncoder(fields=fields)
+ self.headers["Content-Type"] = mp_encoder.content_type
+ self.headers["Accept-Encoding"] = "gzip, deflate, br"
+
+ resp, info = fetch_url(
+ self.module,
+ self.url,
+ headers=self.headers,
+ data=mp_encoder,
+ method=method,
+ timeout=self.params.get("timeout"),
+ use_proxy=self.params.get("use_proxy"),
+ )
+
+ self.response = info.get("msg")
+ self.status = info.get("status")
+
+ # Get change status from HTTP headers
+ if "modified" in info:
+ self.has_modified = True
+ if info.get("modified") == "false":
+ self.result["changed"] = False
+ elif info.get("modified") == "true":
+ self.result["changed"] = True
+
+ # 200: OK, 201: Created, 202: Accepted, 204: No Content
+ if self.status in (200, 201, 202, 204):
+ if self.platform == "nd":
+ return info
+ else:
+ output = resp.read()
+ if output:
+ return json.loads(output)
+
+ # 400: Bad Request, 401: Unauthorized, 403: Forbidden,
+ # 405: Method Not Allowed, 406: Not Acceptable
+ # 500: Internal Server Error, 501: Not Implemented
+ elif self.status:
+ if self.status >= 400:
+ try:
+ if self.platform == "nd":
+ payload = info.get("body")
+ else:
+ payload = json.loads(resp.read())
+ except (ValueError, AttributeError):
+ try:
+ payload = json.loads(info.get("body"))
+ except Exception:
+ self.fail_json(msg="MSO Error:", info=info)
+ if "code" in payload:
+ self.fail_json(msg="MSO Error {code}: {message}".format(**payload), info=info, payload=payload)
+ else:
+ self.fail_json(msg="MSO Error:".format(**payload), info=info, payload=payload)
+ else:
+ self.fail_json(msg="Backup file upload failed due to: {0}".format(info))
+ return {}
+
+ def request(self, path, method=None, data=None, qs=None, api_version="v1"):
+ """Generic HTTP method for MSO requests."""
+ self.path = path
+
+ if method is not None:
+ self.method = method
+
+ # If we PATCH with empty operations, return
+ if method == "PATCH" and not data:
+ return {}
+ else:
+ self.patch_operation = data
+
+ # if method in ['PATCH', 'PUT']:
+ # if qs is not None:
+ # qs['enableVersionCheck'] = 'true'
+ # else:
+ # qs = dict(enableVersionCheck='true')
+
+ if method in ["PATCH"]:
+ if qs is not None:
+ qs["validate"] = "false"
+ else:
+ qs = dict(validate="false")
+
+ resp = None
+ if self.module._socket_path:
+ self.connection.set_params(self.params)
+ if api_version is not None:
+ uri = NDO_API_VERSION_PATH_FORMAT.format(api_version=api_version, path=self.path)
+ else:
+ uri = self.path
+
+ if qs is not None:
+ uri = uri + update_qs(qs)
+
+ try:
+ info = self.connection.send_request(method, uri, json.dumps(data))
+ self.url = info.get("url")
+ self.httpapi_logs.extend(self.connection.pop_messages())
+ info.pop("date", None)
+ except Exception as e:
+ try:
+ error_obj = json.loads(to_text(e))
+ except Exception:
+ error_obj = dict(
+ error=dict(code=-1, message="Unable to parse error output as JSON. Raw error message: {0}".format(e), exception=to_text(e))
+ )
+ pass
+ self.fail_json(msg=error_obj["error"]["message"])
+
+ else:
+ if api_version is not None:
+ self.url = "{0}api/{1}/{2}".format(self.base_only_uri, api_version, self.path.lstrip("/"))
+ else:
+ self.url = "{0}{1}".format(self.base_only_uri, self.path.lstrip("/"))
+
+ if qs is not None:
+ self.url = self.url + update_qs(qs)
+ resp, info = fetch_url(
+ self.module,
+ self.url,
+ headers=self.headers,
+ data=json.dumps(data),
+ method=self.method,
+ timeout=self.params.get("timeout"),
+ use_proxy=self.params.get("use_proxy"),
+ )
+
+ self.response = info.get("msg")
+ self.status = info.get("status", -1)
+
+ # Get change status from HTTP headers
+ if "modified" in info:
+ self.has_modified = True
+ if info.get("modified") == "false":
+ self.result["changed"] = False
+ elif info.get("modified") == "true":
+ self.result["changed"] = True
+
+ # 200: OK, 201: Created, 202: Accepted
+ if self.status in (200, 201, 202):
+ try:
+ output = resp.read()
+ if output:
+ try:
+ return json.loads(output)
+ except Exception as e:
+ self.error = dict(code=-1, message="Unable to parse output as JSON, see 'raw' output. {0}".format(e))
+ self.result["raw"] = output
+ return
+ except AttributeError:
+ return info.get("body")
+
+ # 204: No Content
+ elif self.status == 204:
+ return {}
+
+ # 404: Not Found
+ elif self.method == "DELETE" and self.status == 404:
+ return {}
+
+ # 400: Bad Request, 401: Unauthorized, 403: Forbidden,
+ # 405: Method Not Allowed, 406: Not Acceptable
+ # 500: Internal Server Error, 501: Not Implemented
+ elif self.status >= 400:
+ self.result["status"] = self.status
+ body = info.get("body")
+ if body is not None:
+ try:
+ if isinstance(body, dict):
+ payload = body
+ else:
+ payload = json.loads(body)
+ except Exception as e:
+ self.error = dict(code=-1, message="Unable to parse output as JSON, see 'raw' output. %s" % e)
+ self.result["raw"] = body
+ self.fail_json(msg="MSO Error:", data=data, info=info)
+ self.error = payload
+ if "code" in payload:
+ self.fail_json(msg="MSO Error {code}: {message}".format(**payload), data=data, info=info, payload=payload)
+ else:
+ self.fail_json(msg="MSO Error:".format(**payload), data=data, info=info, payload=payload)
+ else:
+ # Connection error
+ msg = "Connection failed for {0}. {1}".format(info.get("url"), info.get("msg"))
+ self.error = msg
+ self.fail_json(msg=msg)
+ return {}
+
+ def query_objs(self, path, key=None, api_version="v1", **kwargs):
+ """Query the MSO REST API for objects in a path"""
+ found = []
+ objs = self.request(path, api_version=api_version, method="GET")
+
+ if objs == {} or objs == []:
+ return found
+
+ if key is None:
+ key = path
+
+ if isinstance(objs, dict):
+ if key not in objs:
+ self.fail_json(msg="Key '{0}' missing from data".format(key), data=objs)
+ objs_list = objs.get(key)
+ else:
+ objs_list = objs
+ for obj in objs_list:
+ for kw_key, kw_value in kwargs.items():
+ if kw_value is None:
+ continue
+ if isinstance(kw_value, dict):
+ obj_value = obj.get(kw_key)
+ if obj_value is not None and isinstance(obj_value, dict):
+ breakout = False
+ for kw_key_lvl2, kw_value_lvl2 in kw_value.items():
+ if obj_value.get(kw_key_lvl2) != kw_value_lvl2:
+ breakout = True
+ break
+ if breakout:
+ break
+ else:
+ break
+ elif obj.get(kw_key) != kw_value:
+ break
+ else:
+ found.append(obj)
+
+ return found
+
+ def query_obj(self, path, api_version="v1", **kwargs):
+ """Query the MSO REST API for the whole object at a path"""
+ obj = self.request(path, api_version=api_version, method="GET")
+ if obj == {}:
+ return {}
+ for kw_key, kw_value in kwargs.items():
+ if kw_value is None:
+ continue
+ if isinstance(kw_value, dict):
+ obj_value = obj.get(kw_key)
+ if obj_value is not None and isinstance(obj_value, dict):
+ for kw_key_lvl2, kw_value_lvl2 in kw_value.items():
+ if obj_value.get(kw_key_lvl2) != kw_value_lvl2:
+ return {}
+ elif obj.get(kw_key) != kw_value:
+ return {}
+ return obj
+
+ def get_obj(self, path, api_version="v1", **kwargs):
+ """Get a specific object from a set of MSO REST objects"""
+ objs = self.query_objs(path, api_version=api_version, **kwargs)
+ if len(objs) == 0:
+ return {}
+ if len(objs) > 1:
+ self.fail_json(msg="More than one object matches unique filter: {0}".format(kwargs))
+ return objs[0]
+
+ def lookup_schema(self, schema):
+ """Look up schema and return its id"""
+ if schema is None:
+ return schema
+
+ schema_summary = self.query_objs("schemas/list-identity", key="schemas", displayName=schema)
+ if not schema_summary:
+ self.fail_json(msg="Provided schema '{0}' does not exist.".format(schema))
+ schema_id = schema_summary[0].get("id")
+ if not schema_id:
+ self.fail_json(msg="Schema lookup failed for schema '{0}': '{1}'".format(schema, schema_id))
+ return schema_id
+
+ def lookup_domain(self, domain):
+ """Look up a domain and return its id"""
+ if domain is None:
+ return domain
+
+ d = self.get_obj("auth/domains", key="domains", name=domain)
+ if not d:
+ self.fail_json(msg="Domain '%s' is not a valid domain name." % domain)
+ if "id" not in d:
+ self.fail_json(msg="Domain lookup failed for domain '%s': %s" % (domain, d))
+ return d.get("id")
+
+ def lookup_roles(self, roles):
+ """Look up roles and return their ids"""
+ if roles is None:
+ return roles
+
+ ids = []
+ for role in roles:
+ access_type = "readWrite"
+ try:
+ role = ast.literal_eval(role)
+ if type(role) is dict and "name" in role:
+ name = role.get("name")
+ if role.get("access_type") == "read":
+ access_type = "readOnly"
+ except ValueError:
+ name = role
+
+ r = self.get_obj("roles", name=name)
+ if not r:
+ self.fail_json(msg="Role '%s' is not a valid role name." % name)
+ if "id" not in r:
+ self.fail_json(msg="Role lookup failed for role '%s': %s" % (name, r))
+ ids.append(dict(roleId=r.get("id"), accessType=access_type))
+ return ids
+
+ def lookup_site(self, site):
+ """Look up a site and return its id"""
+ if site is None:
+ return site
+
+ s = self.get_obj("sites", name=site)
+ if not s:
+ self.fail_json(msg="Site '%s' is not a valid site name." % site)
+ if "id" not in s:
+ self.fail_json(msg="Site lookup failed for site '%s': %s" % (site, s))
+ return s.get("id")
+
+ def lookup_sites(self, sites):
+ """Look up sites and return their ids"""
+ if sites is None:
+ return sites
+
+ ids = []
+ for site in sites:
+ s = self.get_obj("sites", name=site)
+ if not s:
+ self.fail_json(msg="Site '%s' is not a valid site name." % site)
+ if "id" not in s:
+ self.fail_json(msg="Site lookup failed for site '%s': %s" % (site, s))
+ ids.append(dict(siteId=s.get("id"), securityDomains=[]))
+ return ids
+
+ def lookup_tenant(self, tenant):
+ """Look up a tenant and return its id"""
+ if tenant is None:
+ return tenant
+
+ t = self.get_obj("tenants", key="tenants", name=tenant)
+ if not t:
+ self.fail_json(msg="Tenant '%s' is not valid tenant name." % tenant)
+ if "id" not in t:
+ self.fail_json(msg="Tenant lookup failed for tenant '%s': %s" % (tenant, t))
+ return t.get("id")
+
+ def lookup_remote_location(self, remote_location):
+ """Look up a remote location and return its path and id"""
+ if remote_location is None:
+ return None
+
+ remote = self.get_obj("platform/remote-locations", key="remoteLocations", name=remote_location)
+ if "id" not in remote:
+ self.fail_json(msg="No remote location found for remote '%s'" % (remote_location))
+ remote_info = dict(id=remote.get("id"), path=remote.get("credential")["remotePath"])
+ return remote_info
+
+ def lookup_users(self, users):
+ """Look up users and return their ids"""
+ # Ensure tenant has at least admin user
+ if users is None:
+ users = ["admin"]
+ elif "admin" not in users:
+ users.append("admin")
+
+ ids = []
+ for user in users:
+ if self.platform == "nd":
+ u = self.get_obj("users", loginID=user, api_version="v2")
+ else:
+ u = self.get_obj("users", username=user)
+ if not u:
+ self.fail_json(msg="User '%s' is not a valid user name." % user)
+ if "id" not in u:
+ if "userID" not in u:
+ self.fail_json(msg="User lookup failed for user '%s': %s" % (user, u))
+ id = dict(userId=u.get("userID"))
+ else:
+ id = dict(userId=u.get("id"))
+ if id in ids:
+ self.fail_json(msg="User '%s' is duplicate." % user)
+ ids.append(id)
+
+ return ids
+
+ def create_label(self, label, label_type):
+ """Create a new label"""
+ return self.request("labels", method="POST", data=dict(displayName=label, type=label_type))
+
+ def lookup_labels(self, labels, label_type):
+ """Look up labels and return their ids (create if necessary)"""
+ if labels is None:
+ return None
+
+ ids = []
+ for label in labels:
+ label_obj = self.get_obj("labels", displayName=label)
+ if not label_obj:
+ label_obj = self.create_label(label, label_type)
+ if "id" not in label_obj:
+ self.fail_json(msg="Label lookup failed for label '%s': %s" % (label, label_obj))
+ ids.append(label_obj.get("id"))
+ return ids
+
+ def anp_ref(self, **data):
+ """Create anpRef string"""
+ return "/schemas/{schema_id}/templates/{template}/anps/{anp}".format(**data)
+
+ def epg_ref(self, **data):
+ """Create epgRef string"""
+ return "/schemas/{schema_id}/templates/{template}/anps/{anp}/epgs/{epg}".format(**data)
+
+ def bd_ref(self, **data):
+ """Create bdRef string"""
+ return "/schemas/{schema_id}/templates/{template}/bds/{bd}".format(**data)
+
+ def contract_ref(self, **data):
+ """Create contractRef string"""
+ # Support the contract argspec
+ if "name" in data:
+ data["contract"] = data.get("name")
+ return "/schemas/{schema_id}/templates/{template}/contracts/{contract}".format(**data)
+
+ def filter_ref(self, **data):
+ """Create a filterRef string"""
+ return "/schemas/{schema_id}/templates/{template}/filters/{filter}".format(**data)
+
+ def vrf_ref(self, **data):
+ """Create vrfRef string"""
+ return "/schemas/{schema_id}/templates/{template}/vrfs/{vrf}".format(**data)
+
+ def l3out_ref(self, **data):
+ """Create l3outRef string"""
+ return "/schemas/{schema_id}/templates/{template}/l3outs/{l3out}".format(**data)
+
+ def ext_epg_ref(self, **data):
+ """Create extEpgRef string"""
+ return "/schemas/{schema_id}/templates/{template}/externalEpgs/{external_epg}".format(**data)
+
+ def service_graph_ref(self, **data):
+ """Create serviceGraphRef string"""
+ return "/schemas/{schema_id}/templates/{template}/serviceGraphs/{service_graph}".format(**data)
+
+ def vrf_dict_from_ref(self, data):
+ vrf_ref_regex = re.compile(r"\/schemas\/(.*)\/templates\/(.*)\/vrfs\/(.*)")
+ vrf_dict = vrf_ref_regex.search(data)
+ return {
+ "vrfName": vrf_dict.group(3),
+ "schemaId": vrf_dict.group(1),
+ "templateName": vrf_dict.group(2),
+ }
+
+ def dict_from_ref(self, data):
+ if data and data != "":
+ ref_regex = re.compile(r"\/schemas\/(.*)\/templates\/(.*?)\/(.*?)\/(.*)")
+ dic = ref_regex.search(data)
+ if dic is not None:
+ schema_id = dic.group(1)
+ template_name = dic.group(2)
+ category = dic.group(3)
+ name = dic.group(4)
+ uri_map = {
+ "vrfs": ["vrfName", "schemaId", "templateName"],
+ "bds": ["bdName", "schemaId", "templateName"],
+ "filters": ["filterName", "schemaId", "templateName"],
+ "contracts": ["contractName", "schemaId", "templateName"],
+ "l3outs": ["l3outName", "schemaId", "templateName"],
+ "anps": ["anpName", "schemaId", "templateName"],
+ "serviceGraphs": ["serviceGraphName", "schemaId", "templateName"],
+ "serviceNode": ["serviceNodeName", "schemaId", "templateName", "serviceGraphName"],
+ }
+ result = {
+ uri_map[category][1]: schema_id,
+ uri_map[category][2]: template_name,
+ }
+
+ self.recursive_dict_from_ref_regex(name, result, uri_map[category][0])
+
+ return result
+ else:
+ self.fail_json(msg="There was no group in search: {data}".format(data=data))
+
+ def recursive_dict_from_ref_regex(self, data, result, category):
+ continued_ref_regex = re.compile(r"(.*?)\/([a-zA-Z]+.*)")
+ section_ref_regex = re.compile(r"([a-zA-Z]+)\/(.*)")
+ dic_name = continued_ref_regex.search(data)
+ if dic_name is not None:
+ result[category] = dic_name.group(1)
+ next_section = dic_name.group(2)
+ dic_next_section = section_ref_regex.search(next_section)
+ if dic_next_section is not None:
+ next_name = dic_next_section.group(2)
+ self.recursive_dict_from_ref_regex(next_name, result, dic_next_section.group(1).rstrip("s") + "Name")
+ else:
+ result[category] = data
+
+ def recursive_dict_from_ref(self, data):
+ for key in data:
+ if key.endswith("Ref"):
+ data[key] = self.dict_from_ref(data.get(key))
+ if isinstance(data[key], list):
+ for item in data[key]:
+ self.recursive_dict_from_ref(item)
+ return data
+
+ def make_reference(self, data, reftype, schema_id, template):
+ """Create a reference from a dictionary"""
+ # Removes entry from payload
+ if data is None:
+ return None
+
+ if data.get("schema") is not None:
+ schema_obj = self.get_obj("schemas", displayName=data.get("schema"))
+ if not schema_obj:
+ self.fail_json(msg="Referenced schema '{schema}' in {reftype}ref does not exist".format(reftype=reftype, **data))
+ schema_id = schema_obj.get("id")
+
+ if data.get("template") is not None:
+ template = data.get("template")
+
+ refname = "%sName" % reftype
+
+ return {
+ refname: data.get("name"),
+ "schemaId": schema_id,
+ "templateName": template,
+ }
+
+ def make_subnets(self, data, is_bd_subnet=True):
+ """Create a subnets list from input"""
+ if data is None:
+ return None
+
+ subnets = []
+ for subnet in data:
+ if "subnet" in subnet:
+ subnet["ip"] = subnet.get("subnet")
+ if subnet.get("description") is None:
+ subnet["description"] = subnet.get("subnet")
+ subnet_payload = dict(
+ ip=subnet.get("ip"),
+ description=str(subnet.get("description")),
+ scope=subnet.get("scope"),
+ shared=subnet.get("shared"),
+ noDefaultGateway=subnet.get("no_default_gateway"),
+ )
+ if is_bd_subnet:
+ subnet_payload.update(dict(querier=subnet.get("querier"), primary=subnet.get("primary"), virtual=subnet.get("virtual")))
+ subnets.append(subnet_payload)
+
+ return subnets
+
+ def make_dhcp_label(self, data):
+ """Create a DHCP policy from input"""
+ if data is None:
+ return None
+ if type(data) == list:
+ dhcps = []
+ for dhcp in data:
+ if "dhcp_option_policy" in dhcp:
+ dhcp["dhcpOptionLabel"] = dhcp.get("dhcp_option_policy")
+ del dhcp["dhcp_option_policy"]
+ dhcps.append(dhcp)
+ return dhcps
+ if "version" in data:
+ data["version"] = int(data.get("version"))
+ if data and "dhcp_option_policy" in data:
+ dhcp_option_policy = data.get("dhcp_option_policy")
+ if dhcp_option_policy is not None and "version" in dhcp_option_policy:
+ dhcp_option_policy["version"] = int(dhcp_option_policy.get("version"))
+ data["dhcpOptionLabel"] = dhcp_option_policy
+ del data["dhcp_option_policy"]
+ return data
+
+ def sanitize(self, updates, collate=False, required=None, unwanted=None):
+ """Clean up unset keys from a request payload"""
+ if required is None:
+ required = []
+ if unwanted is None:
+ unwanted = []
+ self.proposed = deepcopy(self.existing)
+ self.sent = deepcopy(self.existing)
+
+ if isinstance(self.existing, dict):
+ for key in self.existing:
+ # Remove References
+ if key.endswith("Ref"):
+ del self.proposed[key]
+ del self.sent[key]
+ continue
+
+ # Removed unwanted keys
+ elif key in unwanted:
+ del self.proposed[key]
+ del self.sent[key]
+ continue
+
+ if isinstance(updates, dict):
+ # Clean up self.sent
+ for key in updates:
+ # Always retain 'id'
+ if key in required:
+ if key in self.existing or updates.get(key) is not None:
+ self.sent[key] = updates.get(key)
+ continue
+
+ # Remove unspecified values
+ elif not collate and updates.get(key) is None:
+ if key in self.existing:
+ del self.sent[key]
+ continue
+
+ # Remove identical values
+ elif not collate and updates.get(key) == self.existing.get(key):
+ del self.sent[key]
+ continue
+
+ # Add everything else
+ if updates.get(key) is not None:
+ self.sent[key] = updates.get(key)
+
+ # Update self.proposed
+ self.proposed.update(self.sent)
+
+ elif updates is not None:
+ self.sent = updates
+ # Update self.proposed
+ self.proposed = self.sent
+
+ def delete_keys_from_dict(self, dict_to_sanitize, keys):
+ # TODO investigate combine this method above sanitize method
+ copy = deepcopy(dict_to_sanitize)
+ for (
+ k,
+ v,
+ ) in copy.items():
+ if k in keys:
+ del dict_to_sanitize[k]
+ elif isinstance(v, dict):
+ dict_to_sanitize[k] = self.delete_keys_from_dict(v, keys)
+ elif isinstance(v, list):
+ for index, item in enumerate(v):
+ if isinstance(item, dict):
+ dict_to_sanitize[k][index] = self.delete_keys_from_dict(item, keys)
+ return dict_to_sanitize
+
+ def exit_json(self, **kwargs):
+ """Custom written method to exit from module."""
+
+ if self.params.get("state") in ("absent", "present", "upload", "restore", "download", "move", "clone"):
+ if self.params.get("output_level") in ("debug", "info"):
+ self.result["previous"] = self.previous
+ # FIXME: Modified header only works for PATCH
+ if not self.has_modified and self.previous != self.existing:
+ self.result["changed"] = True
+ if self.stdout:
+ self.result["stdout"] = self.stdout
+
+ # Return the gory details when we need it
+ if self.params.get("output_level") == "debug":
+ self.result["method"] = self.method
+ self.result["response"] = self.response
+ self.result["status"] = self.status
+ self.result["url"] = self.url
+ self.result["httpapi_logs"] = self.httpapi_logs
+ self.result["socket"] = self.module._socket_path
+
+ if self.params.get("state") in ("absent", "present"):
+ self.result["sent"] = self.sent
+ self.result["proposed"] = self.proposed
+
+ if self.method == "PATCH":
+ self.result["patch_operation"] = self.patch_operation
+
+ self.result["current"] = self.existing
+
+ if self.module._diff and self.result.get("changed") is True:
+ self.result["diff"] = dict(
+ before=self.previous,
+ after=self.existing,
+ )
+
+ self.result.update(**kwargs)
+ self.module.exit_json(**self.result)
+
+ def fail_json(self, msg, **kwargs):
+ """Custom written method to return info on failure."""
+
+ if self.params.get("state") in ("absent", "present"):
+ if self.params.get("output_level") in ("debug", "info"):
+ self.result["previous"] = self.previous
+ # FIXME: Modified header only works for PATCH
+ if not self.has_modified and self.previous != self.existing:
+ self.result["changed"] = True
+ if self.stdout:
+ self.result["stdout"] = self.stdout
+
+ # Return the gory details when we need it
+ if self.params.get("output_level") == "debug":
+ if self.url is not None:
+ self.result["method"] = self.method
+ self.result["response"] = self.response
+ self.result["status"] = self.status
+ self.result["url"] = self.url
+ self.result["httpapi_logs"] = self.httpapi_logs
+ self.result["socket"] = self.module._socket_path
+
+ if self.params.get("state") in ("absent", "present"):
+ self.result["sent"] = self.sent
+ self.result["proposed"] = self.proposed
+
+ if self.method == "PATCH":
+ self.result["patch_operation"] = self.patch_operation
+
+ self.result["current"] = self.existing
+
+ self.result.update(**kwargs)
+ self.module.fail_json(msg=msg, **self.result)
+
+ def check_changed(self):
+ """Check if changed by comparing new values from existing"""
+ existing = self.existing
+ if "password" in existing:
+ existing["password"] = self.sent.get("password")
+
+ existing = self.remove_keys_from_dict_when_value_empty(existing)
+ self.stdout = json.dumps(existing)
+
+ return not issubset(self.sent, existing)
+
+ def update_service_graph_obj(self, service_graph_obj):
+ """update filter with more information"""
+ service_graph_obj["serviceGraphRef"] = self.dict_from_ref(service_graph_obj.get("serviceGraphRef"))
+ for service_node in service_graph_obj["serviceNodesRelationship"]:
+ service_node.get("consumerConnector")["bdRef"] = self.dict_from_ref(service_node.get("consumerConnector").get("bdRef"))
+ service_node.get("providerConnector")["bdRef"] = self.dict_from_ref(service_node.get("providerConnector").get("bdRef"))
+ service_node["serviceNodeRef"] = self.dict_from_ref(service_node.get("serviceNodeRef"))
+ if service_graph_obj.get("serviceGraphContractRelationRef"):
+ del service_graph_obj["serviceGraphContractRelationRef"]
+
+ def update_filter_obj(self, contract_obj, filter_obj, filter_type, contract_display_name=None, update_filter_ref=True):
+ """update filter with more information"""
+ if update_filter_ref:
+ filter_obj["filterRef"] = self.dict_from_ref(filter_obj.get("filterRef"))
+ if contract_display_name:
+ filter_obj["displayName"] = contract_display_name
+ else:
+ filter_obj["displayName"] = contract_obj.get("displayName")
+ filter_obj["filterType"] = filter_type
+ filter_obj["contractScope"] = contract_obj.get("scope")
+ filter_obj["contractFilterType"] = contract_obj.get("filterType")
+ # Conditional statement 'description == ""' is needed to set empty string.
+ if contract_obj.get("description") or contract_obj.get("description") == "":
+ filter_obj["description"] = contract_obj.get("description")
+ # Conditional statement is needed to determine if "prio" exist in contract object.
+ # Same reason as described mso_schema_template_contract_filter.py.
+ if contract_obj.get("prio"):
+ filter_obj["prio"] = contract_obj.get("prio")
+
+ def query_schema(self, schema):
+ schema_id = self.lookup_schema(schema)
+ schema_path = "schemas/{0}".format(schema_id)
+ schema_obj = self.query_obj(schema_path, displayName=schema)
+ if not schema_obj:
+ self.module.fail_json(msg="Schema '{0}' is not a valid schema name.".format(schema))
+ return schema_id, schema_path, schema_obj
+
+ def query_service_node_types(self):
+ node_objs = self.query_objs("schemas/service-node-types", key="serviceNodeTypes")
+ if not node_objs:
+ self.module.fail_json(msg="Service node types do not exist")
+ return node_objs
+
+ def lookup_service_node_device(self, site_id, tenant, device_name=None, service_node_type=None):
+ if service_node_type is None:
+ node_devices = self.query_objs("sites/{0}/aci/tenants/{1}/devices".format(site_id, tenant), key="devices")
+ else:
+ node_devices = self.query_objs("sites/{0}/aci/tenants/{1}/devices?deviceType={2}".format(site_id, tenant, service_node_type), key="devices")
+ if device_name is not None:
+ for device in node_devices:
+ if device_name == device.get("name"):
+ return device
+ self.module.fail_json(msg="Provided device '{0}' of type '{1}' does not exist.".format(device_name, service_node_type))
+ return node_devices
+
+ # Workaround function due to inconsistency in attributes REQUEST/RESPONSE API
+ # Fix for MSO Error 400: Bad Request: (0)(0)(0)(0)/deploymentImmediacy error.path.missing
+ def find_dicts_with_target_key(self, target_dict, target, replace, result=None):
+ if result is None:
+ result = []
+
+ for key, value in target_dict.items():
+ if key == target:
+ result.append(target_dict)
+ if isinstance(value, dict):
+ self.find_dicts_with_target_key(value, target, replace, result)
+ if isinstance(value, list):
+ for entry in value:
+ if isinstance(entry, dict):
+ self.find_dicts_with_target_key(entry, target, replace, result)
+
+ return result
+
+ # Workaround function due to inconsistency in attributes REQUEST/RESPONSE API
+ # Fix for MSO Error 400: Bad Request: (0)(0)(0)(0)/deploymentImmediacy error.path.missing
+ def replace_keys_in_dict(self, target, replace, target_dict=None):
+ if target_dict is None:
+ target_dict = self.existing
+
+ key_list = self.find_dicts_with_target_key(target_dict, target, replace)
+ for item in key_list:
+ item[replace] = item.get(target)
+ del item[target]
+
+ # Workaround function to remove null/None fields returned by API RESPONSE
+ def remove_keys_from_dict_when_value_empty(self, target_dict, modified_target=None):
+ if modified_target is None:
+ modified_target = deepcopy(target_dict)
+
+ for key, value in target_dict.items():
+ if value is None:
+ del modified_target[key]
+ elif isinstance(value, dict):
+ self.remove_keys_from_dict_when_value_empty(value, modified_target[key])
+ elif isinstance(value, list):
+ for entry_index, entry in enumerate(value):
+ if isinstance(entry, dict):
+ self.remove_keys_from_dict_when_value_empty(entry, modified_target[key][entry_index])
+
+ return modified_target
+
+ def validate_schema(self, schema_id):
+ return self.request("schemas/{id}/validate".format(id=schema_id), method="GET")
diff --git a/ansible_collections/cisco/mso/plugins/module_utils/schema.py b/ansible_collections/cisco/mso/plugins/module_utils/schema.py
new file mode 100644
index 000000000..ca08ab10b
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/module_utils/schema.py
@@ -0,0 +1,209 @@
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2022, Akini Ross (@akinross) <akinross@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+from collections import namedtuple
+
+KVPair = namedtuple("KVPair", "key value")
+Item = namedtuple("Item", "index details")
+
+
+class MSOSchema:
+ def __init__(self, mso_module, schema_name, template_name=None, site_name=None):
+ self.mso = mso_module
+ self.schema_name = schema_name
+ self.id, self.path, self.schema = mso_module.query_schema(schema_name)
+ self.schema_objects = {}
+ if template_name:
+ self.set_template(template_name)
+ if site_name and template_name:
+ self.set_site(template_name, site_name)
+
+ @staticmethod
+ def get_object_from_list(search_list, kv_list):
+ """
+ Get the first matched object from a list of mso object dictionaries.
+ :param search_list: Objects to search through -> List.
+ :param kv_list: Key/value pairs that should match in the object. -> List[KVPair(Str, Str)]
+ :return: The index and details of the object. -> Item (Named Tuple)
+ Values of provided keys of all existing objects. -> List
+ """
+
+ def kv_match(kvs, item):
+ return all((item.get(kv.key) == kv.value for kv in kvs))
+
+ match = next((Item(index, item) for index, item in enumerate(search_list) if kv_match(kv_list, item)), None)
+ existing = [item.get(kv.key) for item in search_list for kv in kv_list]
+ return match, existing
+
+ def validate_schema_objects_present(self, required_schema_objects):
+ """
+ Validate that attributes are set to a value that is not equal None.
+ :param required_schema_objects: List of schema objects to verify. -> List
+ :return: None
+ """
+ for schema_object in required_schema_objects:
+ if schema_object not in self.schema_objects.keys():
+ msg = "Required attribute '{0}' is not specified on schema instance with name {1}".format(schema_object, self.schema_name)
+ self.mso.fail_json(msg=msg)
+
+ def set_template(self, template_name, fail_module=True):
+ """
+ Get template item that matches the name of a template.
+ :param template_name: Name of the template to match. -> Str
+ :param fail_module: When match is not found fail the ansible module. -> Bool
+ :return: Template item. -> Item(Int, Dict) | None
+ """
+
+ kv_list = [KVPair("name", template_name)]
+ match, existing = self.get_object_from_list(self.schema.get("templates"), kv_list)
+ if not match and fail_module:
+ msg = "Provided template '{0}' not matching existing template(s): {1}".format(template_name, ", ".join(existing))
+ self.mso.fail_json(msg=msg)
+ self.schema_objects["template"] = match
+
+ def set_template_bd(self, bd, fail_module=True):
+ """
+ Get template bridge domain item that matches the name of a bd.
+ :param bd: Name of the bd to match. -> Str
+ :param fail_module: When match is not found fail the ansible module. -> Bool
+ :return: Template bd item. -> Item(Int, Dict) | None
+ """
+ self.validate_schema_objects_present(["template"])
+ kv_list = [KVPair("name", bd)]
+ match, existing = self.get_object_from_list(self.schema_objects["template"].details.get("bds"), kv_list)
+ if not match and fail_module:
+ msg = "Provided BD '{0}' not matching existing bd(s): {1}".format(bd, ", ".join(existing))
+ self.mso.fail_json(msg=msg)
+ self.schema_objects["template_bd"] = match
+
+ def set_template_anp(self, anp, fail_module=True):
+ """
+ Get template application profile item that matches the name of an anp.
+ :param anp: Name of the anp to match. -> Str
+ :param fail_module: When match is not found fail the ansible module. -> Bool
+ :return: Template anp item. -> Item(Int, Dict) | None
+ """
+ self.validate_schema_objects_present(["template"])
+ kv_list = [KVPair("name", anp)]
+ match, existing = self.get_object_from_list(self.schema_objects["template"].details.get("anps"), kv_list)
+ if not match and fail_module:
+ msg = "Provided ANP '{0}' not matching existing anp(s): {1}".format(anp, ", ".join(existing))
+ self.mso.fail_json(msg=msg)
+ self.schema_objects["template_anp"] = match
+
+ def set_template_anp_epg(self, epg, fail_module=True):
+ """
+ Get template endpoint group item that matches the name of an epg.
+ :param epg: Name of the epg to match. -> Str
+ :param fail_module: When match is not found fail the ansible module. -> Bool
+ :return: Template epg item. -> Item(Int, Dict) | None
+ """
+ self.validate_schema_objects_present(["template_anp"])
+ kv_list = [KVPair("name", epg)]
+ match, existing = self.get_object_from_list(self.schema_objects["template_anp"].details.get("epgs"), kv_list)
+ if not match and fail_module:
+ msg = "Provided EPG '{0}' not matching existing epg(s): {1}".format(epg, ", ".join(existing))
+ self.mso.fail_json(msg=msg)
+ self.schema_objects["template_anp_epg"] = match
+
+ def set_template_external_epg(self, external_epg, fail_module=True):
+ """
+ Get template external epg item that matches the name of an anp.
+ :param anp: Name of the anp to match. -> Str
+ :param fail_module: When match is not found fail the ansible module. -> Bool
+ :return: Template anp item. -> Item(Int, Dict) | None
+ """
+ self.validate_schema_objects_present(["template"])
+ kv_list = [KVPair("name", external_epg)]
+ match, existing = self.get_object_from_list(self.schema_objects["template"].details.get("externalEpgs"), kv_list)
+ if not match and fail_module:
+ msg = "Provided External EPG '{0}' not matching existing external_epg(s): {1}".format(external_epg, ", ".join(existing))
+ self.mso.fail_json(msg=msg)
+ self.schema_objects["template_external_epg"] = match
+
+ def set_site(self, template_name, site_name, fail_module=True):
+ """
+ Get site item that matches the name of a site.
+ :param template_name: Name of the template to match. -> Str
+ :param site_name: Name of the site to match. -> Str
+ :param fail_module: When match is not found fail the ansible module. -> Bool
+ :return: Site item. -> Item(Int, Dict) | None
+ """
+ if not self.schema.get("sites"):
+ msg = "No sites associated with schema '{0}'. Associate the site with the schema using (M) mso_schema_site.".format(self.schema_name)
+ self.mso.fail_json(msg=msg)
+
+ kv_list = [KVPair("siteId", self.mso.lookup_site(site_name)), KVPair("templateName", template_name)]
+ match, existing = self.get_object_from_list(self.schema.get("sites"), kv_list)
+ if not match and fail_module:
+ msg = "Provided site '{0}' not associated with template '{1}'. Site is currently associated with template(s): {2}".format(
+ site_name, template_name, ", ".join(existing[1::2])
+ )
+ self.mso.fail_json(msg=msg)
+ self.schema_objects["site"] = match
+
+ def set_site_bd(self, bd_name, fail_module=True):
+ """
+ Get site bridge domain item that matches the name of a bd.
+ :param bd_name: Name of the bd to match. -> Str
+ :param fail_module: When match is not found fail the ansible module. -> Bool
+ :return: Site bd item. -> Item(Int, Dict) | None
+ """
+ self.validate_schema_objects_present(["template", "site"])
+ kv_list = [KVPair("bdRef", self.mso.bd_ref(schema_id=self.id, template=self.schema_objects["template"].details.get("name"), bd=bd_name))]
+ match, existing = self.get_object_from_list(self.schema_objects["site"].details.get("bds"), kv_list)
+ if not match and fail_module:
+ msg = "Provided BD '{0}' not matching existing site bd(s): {1}".format(bd_name, ", ".join(existing))
+ self.mso.fail_json(msg=msg)
+ self.schema_objects["site_bd"] = match
+
+ def set_site_bd_subnet(self, subnet, fail_module=True):
+ """
+ Get site bridge domain subnet item that matches the ip of a subnet.
+ :param subnet: Subnet (ip) to match. -> Str
+ :param fail_module: When match is not found fail the ansible module. -> Bool
+ :return: Site bd subnet item. -> Item(Int, Dict) | None
+ """
+ self.validate_schema_objects_present(["site_bd"])
+ kv_list = [KVPair("ip", subnet)]
+ match, existing = self.get_object_from_list(self.schema_objects["site_bd"].details.get("subnets"), kv_list)
+ if not match and fail_module:
+ msg = "Provided subnet '{0}' not matching existing site bd subnet(s): {1}".format(subnet, ", ".join(existing))
+ self.mso.fail_json(msg=msg)
+ self.schema_objects["site_bd_subnet"] = match
+
+ def set_site_anp(self, anp_name, fail_module=True):
+ """
+ Get site application profile item that matches the name of a anp.
+ :param anp_name: Name of the anp to match. -> Str
+ :param fail_module: When match is not found fail the ansible module. -> Bool
+ :return: Site anp item. -> Item(Int, Dict) | None
+ """
+ self.validate_schema_objects_present(["template_anp", "site"])
+ kv_list = [KVPair("anpRef", self.schema_objects["template_anp"].details.get("anpRef"))]
+ match, existing = self.get_object_from_list(self.schema_objects["site"].details.get("anps"), kv_list)
+ if not match and fail_module:
+ msg = "Provided ANP '{0}' not matching existing site anp(s): {1}".format(anp_name, ", ".join(existing))
+ self.mso.fail_json(msg=msg)
+ self.schema_objects["site_anp"] = match
+
+ def set_site_anp_epg(self, epg_name, fail_module=True):
+ """
+ Get site anp epg item that matches the epgs.
+ :param epg: epg to match. -> Str
+ :param fail_module: When match is not found fail the ansible module. -> Bool
+ :return: Site anp epg item. -> Item(Int, Dict) | None
+ """
+ self.validate_schema_objects_present(["site_anp", "template_anp_epg"])
+ kv_list = [KVPair("epgRef", self.schema_objects["template_anp_epg"].details.get("epgRef"))]
+ match, existing = self.get_object_from_list(self.schema_objects["site_anp"].details.get("epgs"), kv_list)
+ if not match and fail_module:
+ msg = "Provided EPG '{0}' not matching existing site anp epg(s): {1}".format(epg_name, ", ".join(existing))
+ self.mso.fail_json(msg=msg)
+ self.schema_objects["site_anp_epg"] = match
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_backup.py b/ansible_collections/cisco/mso/plugins/modules/mso_backup.py
new file mode 100644
index 000000000..fc2564b82
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_backup.py
@@ -0,0 +1,331 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com>
+# Copyright: (c) 2023, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+# Copyright: (c) 2023, Sabari Jaganathan (@sajagana) <sajagana@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_backup
+short_description: Manages backups
+description:
+- Manage backups on Cisco ACI Multi-Site.
+author:
+- Shreyas Srish (@shrsr)
+- Lionel Hercot (@lhercot)
+- Sabari Jaganathan (@sajagana)
+options:
+ location_type:
+ description:
+ - The type of location for the backup to be stored
+ type: str
+ choices: [ local, remote]
+ default: local
+ backup:
+ description:
+ - The name given to the backup
+ - C(backup) is mutually exclusive with C(backup_id). Only use one of the two.
+ type: str
+ aliases: [ name ]
+ backup_id:
+ description:
+ - The id of a specific backup
+ - C(backup_id) is mutually exclusive with C(backup). Only use one of the two.
+ type: str
+ aliases: [ id ]
+ remote_location:
+ description:
+ - The remote location's name where the backup should be stored
+ type: str
+ remote_path:
+ description:
+ - This path is relative to the remote location.
+ - A '/' is automatically added between the remote location folder and this path.
+ - This folder structure should already exist on the remote location.
+ type: str
+ description:
+ description:
+ - Brief information about the backup.
+ type: str
+ destination:
+ description:
+ - Location where to download the backup to
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ - Use C(upload) for uploading backup.
+ - Use C(restore) for restoring backup.
+ - Use C(download) for downloading backup.
+ - Use C(move) for moving backup from local to remote location.
+ type: str
+ choices: [ absent, present, query, upload, restore, download, move ]
+ default: present
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Create a new local backup
+ cisco.mso.mso_backup:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ backup: Backup
+ description: via Ansible
+ location_type: local
+ state: present
+ delegate_to: localhost
+
+- name: Create a new remote backup
+ cisco.mso.mso_backup:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ backup: Backup
+ description: via Ansible
+ location_type: remote
+ remote_location: ansible_test
+ state: present
+ delegate_to: localhost
+
+- name: Move backup to remote location
+ cisco.mso.mso_backup:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ backup: Backup0
+ remote_location: ansible_test
+ remote_path: test
+ state: move
+ delegate_to: localhost
+
+- name: Download a backup
+ cisco.mso.mso_backup:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ backup: Backup
+ destination: ./
+ state: download
+ delegate_to: localhost
+
+- name: Upload a backup
+ cisco.mso.mso_backup:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ backup: ./Backup
+ state: upload
+ delegate_to: localhost
+
+- name: Restore a backup
+ cisco.mso.mso_backup:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ backup: Backup
+ state: restore
+ delegate_to: localhost
+
+- name: Remove a Backup
+ cisco.mso.mso_backup:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ backup: Backup
+ state: absent
+ delegate_to: localhost
+
+- name: Query a backup
+ cisco.mso.mso_backup:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ backup: Backup
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query a backup with its complete name
+ cisco.mso.mso_backup:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ backup: Backup_20200721220043
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all backups
+ cisco.mso.mso_backup:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+import os
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ location_type=dict(type="str", default="local", choices=["local", "remote"]),
+ description=dict(type="str"),
+ backup=dict(type="str", aliases=["name"]),
+ backup_id=dict(type="str", aliases=["id"]),
+ remote_location=dict(type="str"),
+ remote_path=dict(type="str"),
+ state=dict(type="str", default="present", choices=["absent", "present", "query", "upload", "restore", "download", "move"]),
+ destination=dict(type="str"),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["location_type", "remote", ["remote_location"]],
+ ["state", "absent", ["backup", "backup_id"], True],
+ ["state", "present", ["backup"]],
+ ["state", "upload", ["backup", "backup_id"], True],
+ ["state", "restore", ["backup", "backup_id"], True],
+ ["state", "download", ["backup", "backup_id"], True],
+ ["state", "download", ["destination"]],
+ ["state", "move", ["backup", "backup_id"], True],
+ ["state", "move", ["remote_location", "remote_path"]],
+ ],
+ mutually_exclusive=[
+ ("backup", "backup_id"),
+ ],
+ )
+
+ description = module.params.get("description")
+ location_type = module.params.get("location_type")
+ state = module.params.get("state")
+ backup = module.params.get("backup")
+ backup_id = module.params.get("backup_id")
+ remote_location = module.params.get("remote_location")
+ remote_path = module.params.get("remote_path")
+ destination = module.params.get("destination")
+
+ mso = MSOModule(module)
+
+ backup_names = []
+ mso.existing = mso.query_objs("backups/backupRecords", key="backupRecords")
+ if backup or backup_id:
+ if mso.existing:
+ data = mso.existing
+ mso.existing = []
+ for backup_info in data:
+ if (backup_id and backup_id == backup_info.get("id")) or (
+ backup and (backup == backup_info.get("name").split("_")[0] or backup == backup_info.get("name"))
+ ):
+ mso.existing.append(backup_info)
+ backup_names.append(backup_info.get("name"))
+
+ if state == "query":
+ mso.exit_json()
+
+ elif state == "absent":
+ mso.previous = mso.existing
+ if len(mso.existing) > 1:
+ mso.module.fail_json(msg="Multiple backups with same name found. Existing backups with similar names: {0}".format(", ".join(backup_names)))
+ elif len(mso.existing) == 1:
+ if module.check_mode:
+ mso.existing = {}
+ else:
+ mso.existing = mso.request("backups/backupRecords/{id}".format(id=mso.existing[0].get("id")), method="DELETE")
+ mso.exit_json()
+
+ elif state == "present":
+ mso.previous = mso.existing
+
+ payload = dict(name=backup, description=description, locationType=location_type)
+
+ if location_type == "remote":
+ remote_location_info = mso.lookup_remote_location(remote_location)
+ payload.update(remoteLocationId=remote_location_info.get("id"))
+ if remote_path:
+ remote_path = "{0}/{1}".format(remote_location_info.get("path"), remote_path)
+ payload.update(remotePath=remote_path)
+
+ mso.proposed = payload
+
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request("backups", method="POST", data=payload)
+ mso.exit_json()
+
+ elif state == "upload":
+ mso.previous = mso.existing
+
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ try:
+ request_url = "backups/upload"
+ payload = dict()
+ if mso.platform == "nd":
+ if remote_location is None or remote_path is None:
+ mso.module.fail_json(msg="NDO backup upload failed: remote_location and remote_path are required for NDO backup upload")
+ remote_location_info = mso.lookup_remote_location(remote_location)
+ request_url = "backups/remoteUpload/{0}".format(remote_location_info.get("id"))
+ else:
+ payload = dict(name=(os.path.basename(backup), open(backup, "rb"), "application/x-gzip"))
+
+ mso.existing = mso.request_upload(request_url, fields=payload)
+ except Exception as error:
+ mso.module.fail_json(msg="Upload failed due to: {0}, Backup file: '{1}'".format(error, ", ".join(backup.split("/")[-1:])))
+ mso.exit_json()
+
+ if len(mso.existing) == 0:
+ mso.module.fail_json(msg="Backup '{0}' does not exist".format(backup))
+ elif len(mso.existing) > 1:
+ mso.module.fail_json(msg="Multiple backups with same name found. Existing backups with similar names: {0}".format(", ".join(backup_names)))
+
+ elif state == "restore":
+ mso.previous = mso.existing
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request("backups/{id}/restore".format(id=mso.existing[0].get("id")), method="PUT")
+
+ elif state == "download":
+ mso.previous = mso.existing
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request_download("backups/{id}/download".format(id=mso.existing[0].get("id")), destination=destination)
+
+ elif state == "move":
+ mso.previous = mso.existing
+ remote_location_info = mso.lookup_remote_location(remote_location)
+ remote_path = "{0}/{1}".format(remote_location_info.get("path"), remote_path)
+ payload = dict(remoteLocationId=remote_location_info.get("id"), remotePath=remote_path, backupRecordId=mso.existing[0].get("id"))
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request("backups/remote-location", method="POST", data=payload)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_backup_schedule.py b/ansible_collections/cisco/mso/plugins/modules/mso_backup_schedule.py
new file mode 100644
index 000000000..e97b59b2e
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_backup_schedule.py
@@ -0,0 +1,219 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_backup_schedule
+short_description: Manages backup schedules
+description:
+- Manage backup schedules on Cisco ACI Multi-Site.
+author:
+- Akini Ross (@akinross)
+options:
+ start_date:
+ description:
+ - The date to start the scheduler in format YYYY-MM-DD
+ - If no date is provided, the current date will be used.
+ type: str
+ start_time:
+ description:
+ - The time to start the scheduler in format HH:MM:SS
+ - If no time is provided, midnight "00:00:00" will be used.
+ type: str
+ frequency_unit:
+ description:
+ - The interval unit type
+ choices: [ hours, days ]
+ type: str
+ frequency_length:
+ description:
+ - Amount of hours or days for the schedule trigger frequency
+ type: int
+ remote_location:
+ description:
+ - The remote location's name where the backup should be stored
+ type: str
+ remote_path:
+ description:
+ - This path is relative to the remote location.
+ - A '/' is automatically added between the remote location folder and this path.
+ - This folder structure should already exist on the remote location.
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Get current backup schedule
+ cisco.mso.mso_backup_schedule:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+
+- name: Set backup schedule
+ cisco.mso.mso_backup_schedule:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ frequency_unit: hours
+ frequency_length: 7
+ remote_location: ansible_test
+ state: present
+ delegate_to: localhost
+
+- name: Set backup schedule with date and time
+ cisco.mso.mso_backup_schedule:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ frequency_unit: days
+ frequency_length: 1
+ remote_location: ansible_test
+ remote_path: test
+ start_time: 20:57:36
+ start_date: 2023-04-09
+ state: present
+ delegate_to: localhost
+
+- name: Delete backup schedule
+ cisco.mso.mso_backup_schedule:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ state: absent
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+from datetime import datetime, tzinfo, timedelta
+
+# UTC Timezone implementation as datetime.timezone is not supported in Python 2.7
+
+
+class UTC(tzinfo):
+ """UTC"""
+
+ def utcoffset(self, dt):
+ return timedelta(0)
+
+ def tzname(self, dt):
+ return "UTC"
+
+ def dst(self, dt):
+ return timedelta(0)
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ start_date=dict(type="str"),
+ start_time=dict(type="str"),
+ frequency_unit=dict(type="str", choices=["hours", "days"]),
+ frequency_length=dict(type="int"),
+ remote_location=dict(type="str"),
+ remote_path=dict(type="str"),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec, supports_check_mode=True, required_if=[["state", "present", ["frequency_unit", "frequency_length", "remote_location"]]]
+ )
+
+ start_date = module.params.get("start_date")
+ start_time = module.params.get("start_time")
+ frequency_unit = module.params.get("frequency_unit")
+ frequency_length = module.params.get("frequency_length")
+ remote_location = module.params.get("remote_location")
+ remote_path = module.params.get("remote_path")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+ api_path = "backups/schedule"
+ mso.existing = mso.request(api_path, method="GET")
+
+ if state == "absent":
+ mso.previous = mso.existing
+ if module.check_mode:
+ mso.existing = {}
+ else:
+ mso.existing = mso.request(api_path, method="DELETE")
+
+ elif state == "present":
+ mso.previous = mso.existing
+
+ remote_location_info = mso.lookup_remote_location(remote_location)
+
+ if start_date:
+ try:
+ y, m, d = start_date.split("-")
+ year = int(y)
+ month = int(m)
+ day = int(d)
+ except Exception as e:
+ module.fail_json(msg="Failed to parse date format 'YYYY-MM-DD' %s, %s" % (start_date, e))
+ else:
+ current_date = datetime.now(UTC()).date()
+ year = current_date.year
+ month = current_date.month
+ day = current_date.day
+
+ if start_time:
+ try:
+ h, m, s = start_time.split(":")
+ hours = int(h)
+ minutes = int(m)
+ seconds = int(s)
+ except Exception as e:
+ module.fail_json(msg="Failed to parse time format 'HH:MM:SS' %s, %s" % (start_time, e))
+ else:
+ hours = minutes = seconds = 0
+
+ try:
+ set_date = datetime(year, month, day, hours, minutes, seconds)
+ except Exception as e:
+ module.fail_json(msg="Failed to create datetime object with date '%s', and time '%s'. Error: %s" % (start_date, start_time, e))
+
+ payload = dict(
+ startDate="{0}.000Z".format(set_date.isoformat()),
+ intervalTimeUnit=frequency_unit.upper(),
+ intervalLength=frequency_length,
+ remoteLocationId=remote_location_info.get("id"),
+ locationType="remote",
+ )
+
+ if remote_path:
+ payload.update(remotePath="{0}/{1}".format(remote_location_info.get("path"), remote_path))
+
+ mso.proposed = payload
+
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request(api_path, method="POST", data=payload)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_option_policy.py b/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_option_policy.py
new file mode 100644
index 000000000..e9b7f23d3
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_option_policy.py
@@ -0,0 +1,167 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+# Copyright: (c) 2020, Jorge Gomez (@jgomezve) <jgomezve@cisco.com> (based on mso_dhcp_relay_policy module)
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_dhcp_option_policy
+short_description: Manage DHCP Option policies.
+description:
+- Manage DHCP Option policies on Cisco Multi-Site Orchestrator.
+author:
+- Lionel Hercot (@lhercot)
+options:
+ dhcp_option_policy:
+ description:
+ - Name of the DHCP Option Policy
+ type: str
+ aliases: [ name ]
+ description:
+ description:
+ - Description of the DHCP Option Policy
+ type: str
+ tenant:
+ description:
+ - Tenant where the DHCP Option Policy is located.
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new DHCP Option Policy
+ cisco.mso.mso_dhcp_option_policy:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ dhcp_option_policy: my_test_dhcp_policy
+ description: "My Test DHCP Policy"
+ tenant: ansible_test
+ state: present
+ delegate_to: localhost
+
+- name: Remove DHCP Option Policy
+ cisco.mso.mso_dhcp_option_policy:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ dhcp_option_policy: my_test_dhcp_policy
+ state: absent
+ delegate_to: localhost
+
+- name: Query a DHCP Option Policy
+ cisco.mso.mso_dhcp_option_policy:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ dhcp_option_policy: my_test_dhcp_policy
+ state: query
+ delegate_to: localhost
+
+- name: Query all DHCP Option Policies
+ cisco.mso.mso_dhcp_option_policy:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ dhcp_option_policy=dict(type="str", aliases=["name"]),
+ description=dict(type="str"),
+ tenant=dict(type="str"),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["dhcp_option_policy"]],
+ ["state", "present", ["dhcp_option_policy", "tenant"]],
+ ],
+ )
+
+ dhcp_option_policy = module.params.get("dhcp_option_policy")
+ description = module.params.get("description")
+ tenant = module.params.get("tenant")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ path = "policies/dhcp/option"
+
+ # Query for existing object(s)
+ if dhcp_option_policy:
+ mso.existing = mso.get_obj(path, name=dhcp_option_policy, key="DhcpRelayPolicies")
+ if mso.existing:
+ policy_id = mso.existing.get("id")
+ # If we found an existing object, continue with it
+ path = "{0}/{1}".format(path, policy_id)
+ else:
+ mso.existing = mso.query_objs(path, key="DhcpRelayPolicies")
+
+ mso.previous = mso.existing
+
+ if state == "absent":
+ if mso.existing:
+ if module.check_mode:
+ mso.existing = {}
+ else:
+ mso.existing = mso.request(path, method="DELETE", data=mso.sent)
+
+ elif state == "present":
+ tenant_id = mso.lookup_tenant(tenant)
+ payload = dict(
+ name=dhcp_option_policy,
+ desc=description,
+ policyType="dhcp",
+ policySubtype="option",
+ tenantId=tenant_id,
+ )
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ if mso.check_changed():
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request(path, method="PUT", data=mso.sent)
+ else:
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request(path, method="POST", data=mso.sent)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_option_policy_option.py b/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_option_policy_option.py
new file mode 100644
index 000000000..f4c397c24
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_option_policy_option.py
@@ -0,0 +1,193 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+# Copyright: (c) 2020, Jorge Gomez (@jgomezve) <jgomezve@cisco.com> (based on mso_dhcp_relay_policy module)
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_dhcp_option_policy_option
+short_description: Manage DHCP options in a DHCP Option policy.
+description:
+- Manage DHCP options in a DHCP Option policy on Cisco Multi-Site Orchestrator.
+author:
+- Lionel Hercot (@lhercot)
+options:
+ dhcp_option_policy:
+ description:
+ - Name of the DHCP Option Policy
+ type: str
+ required: true
+ aliases: [ name ]
+ name:
+ description:
+ - Name of the option in the DHCP Option Policy
+ type: str
+ aliases: [ option ]
+ id:
+ description:
+ - Id of the option in the DHCP Option Policy
+ type: int
+ data:
+ description:
+ - Data of the DHCP option in the DHCP Option Policy
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new option to a DHCP Option Policy
+ cisco.mso.mso_dhcp_option_policy_option:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ dhcp_option_policy: my_test_dhcp_policy
+ name: ansible_test
+ id: 1
+ data: Data stored in the option
+ state: present
+ delegate_to: localhost
+
+- name: Remove a option to a DHCP Option Policy
+ cisco.mso.mso_dhcp_option_policy_option:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ dhcp_option_policy: my_test_dhcp_policy
+ name: ansible_test
+ state: absent
+ delegate_to: localhost
+
+- name: Query a option to a DHCP Option Policy
+ cisco.mso.mso_dhcp_option_policy_option:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ dhcp_option_policy: my_test_dhcp_policy
+ name: ansible_test
+ state: query
+ delegate_to: localhost
+
+- name: Query all option of a DHCP Option Policy
+ cisco.mso.mso_dhcp_option_policy_option:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ dhcp_option_policy: my_test_dhcp_policy
+ state: query
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+"""
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import (
+ MSOModule,
+ mso_argument_spec,
+)
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ dhcp_option_policy=dict(type="str", required=True),
+ name=dict(type="str", aliases=["option"]),
+ id=dict(type="int"),
+ data=dict(type="str"),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "present", ["name", "id", "data"]],
+ ["state", "absent", ["name"]],
+ ],
+ )
+
+ dhcp_option_policy = module.params.get("dhcp_option_policy")
+ option_id = module.params.get("id")
+ name = module.params.get("name")
+ data = module.params.get("data")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ path = "policies/dhcp/option"
+
+ option_index = None
+ previous_option = {}
+
+ # Query for existing object(s)
+ dhcp_option_obj = mso.get_obj(path, name=dhcp_option_policy, key="DhcpRelayPolicies")
+ if "id" not in dhcp_option_obj:
+ mso.fail_json(msg="DHCP Option Policy '{0}' is not a valid DHCP Option Policy name.".format(dhcp_option_policy))
+ policy_id = dhcp_option_obj.get("id")
+ options = []
+ if "dhcpOption" in dhcp_option_obj:
+ options = dhcp_option_obj.get("dhcpOption")
+ for index, opt in enumerate(options):
+ if opt.get("name") == name:
+ previous_option = opt
+ option_index = index
+
+ # If we found an existing object, continue with it
+ path = "{0}/{1}".format(path, policy_id)
+
+ if state == "query":
+ mso.existing = options
+ if name is not None:
+ mso.existing = previous_option
+ mso.exit_json()
+
+ mso.previous = previous_option
+ if state == "absent":
+ option = {}
+ if previous_option and option_index is not None:
+ options.pop(option_index)
+
+ elif state == "present":
+ option = dict(
+ id=str(option_id),
+ name=name,
+ data=data,
+ )
+ if option_index is not None:
+ options[option_index] = option
+ else:
+ options.append(option)
+
+ if module.check_mode:
+ mso.existing = option
+ else:
+ mso.existing = dhcp_option_obj
+ dhcp_option_obj["dhcpOption"] = options
+ mso.sanitize(dhcp_option_obj, collate=True)
+ new_dhcp_option_obj = mso.request(path, method="PUT", data=mso.sent)
+ mso.existing = {}
+ for index, opt in enumerate(new_dhcp_option_obj.get("dhcpOption")):
+ if opt.get("name") == name:
+ mso.existing = opt
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_relay_policy.py b/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_relay_policy.py
new file mode 100644
index 000000000..394825feb
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_relay_policy.py
@@ -0,0 +1,166 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Jorge Gomez Velasquez <jgomezve@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_dhcp_relay_policy
+short_description: Manage DHCP Relay policies.
+description:
+- Manage DHCP Relay policies on Cisco Multi-Site Orchestrator.
+author:
+- Jorge Gomez (@jorgegome2307)
+options:
+ dhcp_relay_policy:
+ description:
+ - Name of the DHCP Relay Policy
+ type: str
+ aliases: [ name ]
+ description:
+ description:
+ - Description of the DHCP Relay Policy
+ type: str
+ tenant:
+ description:
+ - Tenant where the DHCP Relay Policy is located.
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new DHCP Relay Policy
+ cisco.mso.mso_dhcp_relay_policy:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ dhcp_relay_policy: my_test_dhcp_policy
+ description: "My Test DHCP Policy"
+ tenant: ansible_test
+ state: present
+ delegate_to: localhost
+
+- name: Remove DHCP Relay Policy
+ cisco.mso.mso_dhcp_relay_policy:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ dhcp_relay_policy: my_test_dhcp_policy
+ state: absent
+ delegate_to: localhost
+
+- name: Query a DHCP Relay Policy
+ cisco.mso.mso_dhcp_relay_policy:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ dhcp_relay_policy: my_test_dhcp_policy
+ state: query
+ delegate_to: localhost
+
+- name: Query all DHCP Relay Policies
+ cisco.mso.mso_dhcp_relay_policy:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ dhcp_relay_policy=dict(type="str", aliases=["name"]),
+ description=dict(type="str"),
+ tenant=dict(type="str"),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["dhcp_relay_policy"]],
+ ["state", "present", ["dhcp_relay_policy", "tenant"]],
+ ],
+ )
+
+ dhcp_relay_policy = module.params.get("dhcp_relay_policy")
+ description = module.params.get("description")
+ tenant = module.params.get("tenant")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ path = "policies/dhcp/relay"
+
+ # Query for existing object(s)
+ if dhcp_relay_policy:
+ mso.existing = mso.get_obj(path, name=dhcp_relay_policy, key="DhcpRelayPolicies")
+ if mso.existing:
+ policy_id = mso.existing.get("id")
+ # If we found an existing object, continue with it
+ path = "{0}/{1}".format(path, policy_id)
+ else:
+ mso.existing = mso.query_objs(path, key="DhcpRelayPolicies")
+
+ mso.previous = mso.existing
+
+ if state == "absent":
+ if mso.existing:
+ if module.check_mode:
+ mso.existing = {}
+ else:
+ mso.existing = mso.request(path, method="DELETE", data=mso.sent)
+
+ elif state == "present":
+ tenant_id = mso.lookup_tenant(tenant)
+ payload = dict(
+ name=dhcp_relay_policy,
+ desc=description,
+ policyType="dhcp",
+ policySubtype="relay",
+ tenantId=tenant_id,
+ )
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ if mso.check_changed():
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request(path, method="PUT", data=mso.sent)
+ else:
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request(path, method="POST", data=mso.sent)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_relay_policy_provider.py b/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_relay_policy_provider.py
new file mode 100644
index 000000000..760d90430
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_relay_policy_provider.py
@@ -0,0 +1,256 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Jorge Gomez Velasquez <jgomezve@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_dhcp_relay_policy_provider
+short_description: Manage DHCP providers in a DHCP Relay policy.
+description:
+- Manage DHCP providers in a DHCP Relay policy on Cisco Multi-Site Orchestrator.
+author:
+- Jorge Gomez (@jorgegome2307)
+options:
+ dhcp_relay_policy:
+ description:
+ - Name of the DHCP Relay Policy
+ type: str
+ required: true
+ aliases: [ name ]
+ ip:
+ description:
+ - IP address of the DHCP Server
+ type: str
+ tenant:
+ description:
+ - Tenant where the DHCP provider is located.
+ type: str
+ schema:
+ description:
+ - Schema where the DHCP provider is configured
+ type: str
+ template:
+ description:
+ - template where the DHCP provider is configured
+ type: str
+ application_profile:
+ description:
+ - Application Profile where the DHCP provider is configured
+ type: str
+ aliases: [ anp ]
+ endpoint_group:
+ description:
+ - EPG where the DHCP provider is configured
+ type: str
+ aliases: [ epg ]
+ external_endpoint_group:
+ description:
+ - External EPG where the DHCP provider is configured
+ type: str
+ aliases: [ ext_epg, external_epg ]
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new provider to a DHCP Relay Policy
+ cisco.mso.mso_dhcp_relay_policy_provider:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ dhcp_relay_policy: my_test_dhcp_policy
+ tenant: ansible_test
+ schema: ansible_test
+ template: Template 1
+ application_profile: ansible_test
+ endpoint_group: ansible_test
+ state: present
+ delegate_to: localhost
+
+- name: Remove a provider to a DHCP Relay Policy
+ cisco.mso.mso_dhcp_relay_policy_provider:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ dhcp_relay_policy: my_test_dhcp_policy
+ tenant: ansible_test
+ schema: ansible_test
+ template: Template 1
+ application_profile: ansible_test
+ endpoint_group: ansible_test
+ state: absent
+ delegate_to: localhost
+
+- name: Query a provider to a DHCP Relay Policy
+ cisco.mso.mso_dhcp_relay_policy_provider:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ dhcp_relay_policy: my_test_dhcp_policy
+ tenant: ansible_test
+ schema: ansible_test
+ template: Template 1
+ application_profile: ansible_test
+ endpoint_group: ansible_test
+ state: query
+ delegate_to: localhost
+
+- name: Query all provider of a DHCP Relay Policy
+ cisco.mso.mso_dhcp_relay_policy_provider:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ dhcp_relay_policy: my_test_dhcp_policy
+ state: query
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+"""
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import (
+ MSOModule,
+ mso_argument_spec,
+)
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ dhcp_relay_policy=dict(type="str", required=True, aliases=["name"]),
+ ip=dict(type="str"),
+ tenant=dict(type="str"),
+ schema=dict(type="str"),
+ template=dict(type="str"),
+ application_profile=dict(type="str", aliases=["anp"]),
+ endpoint_group=dict(type="str", aliases=["epg"]),
+ external_endpoint_group=dict(type="str", aliases=["ext_epg", "external_epg"]),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "present", ["ip", "tenant", "schema", "template"]],
+ ["state", "absent", ["tenant", "schema", "template"]],
+ ],
+ )
+
+ dhcp_relay_policy = module.params.get("dhcp_relay_policy")
+ ip = module.params.get("ip")
+ tenant = module.params.get("tenant")
+ schema = module.params.get("schema")
+ template = module.params.get("template")
+ if template is not None:
+ template = template.replace(" ", "")
+ application_profile = module.params.get("application_profile")
+ endpoint_group = module.params.get("endpoint_group")
+ external_endpoint_group = module.params.get("external_endpoint_group")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ path = "policies/dhcp/relay"
+
+ tenant_id = mso.lookup_tenant(tenant)
+ # Get schema_id
+ schema_id = mso.lookup_schema(schema)
+
+ provider = dict(
+ addr=ip,
+ externalEpgRef="",
+ epgRef="",
+ l3Ref="",
+ tenantId=tenant_id,
+ )
+ provider_index = None
+ previous_provider = {}
+
+ if application_profile is not None and endpoint_group is not None:
+ provider["epgRef"] = "/schemas/{schemaId}/templates/{templateName}/anps/{app}/epgs/{epg}".format(
+ schemaId=schema_id,
+ templateName=template,
+ app=application_profile,
+ epg=endpoint_group,
+ )
+ elif external_endpoint_group is not None:
+ provider["externalEpgRef"] = "/schemas/{schemaId}/templates/{templateName}/externalEpgs/{ext_epg}".format(
+ schemaId=schema_id, templateName=template, ext_epg=external_endpoint_group
+ )
+
+ # Query for existing object(s)
+ dhcp_relay_obj = mso.get_obj(path, name=dhcp_relay_policy, key="DhcpRelayPolicies")
+ if "id" not in dhcp_relay_obj:
+ mso.fail_json(msg="DHCP Relay Policy '{0}' is not a valid DHCP Relay Policy name.".format(dhcp_relay_policy))
+ policy_id = dhcp_relay_obj.get("id")
+ providers = []
+ if "provider" in dhcp_relay_obj:
+ providers = dhcp_relay_obj.get("provider")
+ for index, prov in enumerate(providers):
+ if (provider.get("epgRef") != "" and prov.get("epgRef") == provider.get("epgRef")) or (
+ provider.get("externalEpgRef") != "" and prov.get("externalEpgRef") == provider.get("externalEpgRef")
+ ):
+ previous_provider = prov
+ provider_index = index
+
+ # If we found an existing object, continue with it
+ path = "{0}/{1}".format(path, policy_id)
+
+ if state == "query":
+ mso.existing = providers
+ if endpoint_group is not None or external_endpoint_group is not None:
+ mso.existing = previous_provider
+ mso.exit_json()
+
+ if endpoint_group is None and external_endpoint_group is None:
+ mso.fail_json(msg="Missing either endpoint_group or external_endpoint_group required attribute.")
+
+ mso.previous = previous_provider
+ if state == "absent":
+ provider = {}
+ if previous_provider:
+ if provider_index is not None:
+ providers.pop(provider_index)
+
+ elif state == "present":
+ if provider_index is not None:
+ providers[provider_index] = provider
+ else:
+ providers.append(provider)
+
+ if module.check_mode:
+ mso.existing = provider
+ else:
+ mso.existing = dhcp_relay_obj
+ dhcp_relay_obj["provider"] = providers
+ mso.sanitize(dhcp_relay_obj, collate=True)
+ new_dhcp_relay_obj = mso.request(path, method="PUT", data=mso.sent)
+ mso.existing = {}
+ for index, prov in enumerate(new_dhcp_relay_obj.get("provider")):
+ if (provider.get("epgRef") != "" and prov.get("epgRef") == provider.get("epgRef")) or (
+ provider.get("externalEpgRef") != "" and prov.get("externalEpgRef") == provider.get("externalEpgRef")
+ ):
+ mso.existing = prov
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_label.py b/ansible_collections/cisco/mso/plugins/modules/mso_label.py
new file mode 100644
index 000000000..f4f05f7de
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_label.py
@@ -0,0 +1,164 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_label
+short_description: Manage labels
+description:
+- Manage labels on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ label:
+ description:
+ - The name of the label.
+ type: str
+ aliases: [ name ]
+ type:
+ description:
+ - The type of the label.
+ type: str
+ choices: [ site ]
+ default: site
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new label
+ cisco.mso.mso_label:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ label: Belgium
+ type: site
+ state: present
+ delegate_to: localhost
+
+- name: Remove a label
+ cisco.mso.mso_label:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ label: Belgium
+ state: absent
+ delegate_to: localhost
+
+- name: Query a label
+ cisco.mso.mso_label:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ label: Belgium
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all labels
+ cisco.mso.mso_label:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ label=dict(type="str", aliases=["name"]),
+ type=dict(type="str", default="site", choices=["site"]),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["label"]],
+ ["state", "present", ["label"]],
+ ],
+ )
+
+ label = module.params.get("label")
+ label_type = module.params.get("type")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ label_id = None
+ path = "labels"
+
+ # Query for existing object(s)
+ if label:
+ mso.existing = mso.get_obj(path, displayName=label)
+ if mso.existing:
+ label_id = mso.existing.get("id")
+ # If we found an existing object, continue with it
+ path = "labels/{id}".format(id=label_id)
+ else:
+ mso.existing = mso.query_objs(path)
+
+ if state == "query":
+ pass
+
+ elif state == "absent":
+ mso.previous = mso.existing
+ if mso.existing:
+ if module.check_mode:
+ mso.existing = {}
+ else:
+ mso.existing = mso.request(path, method="DELETE")
+
+ elif state == "present":
+ mso.previous = mso.existing
+
+ payload = dict(
+ id=label_id,
+ displayName=label,
+ type=label_type,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ if mso.check_changed():
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request(path, method="PUT", data=mso.sent)
+ else:
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request(path, method="POST", data=mso.sent)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_remote_location.py b/ansible_collections/cisco/mso/plugins/modules/mso_remote_location.py
new file mode 100644
index 000000000..10546563f
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_remote_location.py
@@ -0,0 +1,243 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2022, Akini Ross (@akinross) <akinross@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_remote_location
+short_description: Manages remote locations
+description:
+- Manage remote locations on Cisco ACI Multi-Site.
+author:
+- Akini Ross (@akinross)
+options:
+ remote_location:
+ description:
+ - The remote location's name.
+ type: str
+ aliases: [ name ]
+ description:
+ description:
+ - The remote location's description.
+ type: str
+ remote_protocol:
+ description:
+ - The protocol used to export to the remote server.
+ - If the remote location is a Windows server, you must use the C(sftp) protocol.
+ choices: [ scp, sftp ]
+ type: str
+ remote_host:
+ description:
+ - The host name or IP address of the remote server.
+ type: str
+ remote_path:
+ description:
+ - The full path to a directory on the remote server where backups are saved.
+ - The path must start with a slash (/) character and must not contain periods (.) or backslashes (\).
+ - The directory must already exist on the server.
+ type: str
+ remote_port:
+ description:
+ - The port used to connect to the remote server.
+ default: 22
+ type: int
+ authentication_type:
+ description:
+ - The authentication method used to connect to the remote server.
+ choices: [ password, ssh ]
+ type: str
+ remote_username:
+ description:
+ - The username used to log in to the remote server.
+ type: str
+ remote_password:
+ description:
+ - The password used to log in to the remote server.
+ type: str
+ remote_ssh_key:
+ description:
+ - The private ssh key used to log in to the remote server.
+ - The private ssh key must be provided in PEM format.
+ - The private ssh key must be a single line string with linebreaks represent as "\n".
+ type: str
+ remote_ssh_passphrase:
+ description:
+ - The private ssh key passphrase used to log in to the remote server.
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Query all remote locations
+ cisco.mso.mso_remote_location:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: backups
+
+- name: Query a remote location
+ cisco.mso.mso_remote_location:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ remote_location: ansible_test
+ state: query
+ delegate_to: localhost
+
+- name: Configure a remote location
+ cisco.mso.mso_remote_location:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ remote_location: ansible_test
+ remote_protocol: scp
+ remote_host: 10.0.0.1
+ remote_path: /username/backup
+ remote_authentication_type: password
+ remote_username: username
+ remote_password: password
+ state: present
+ delegate_to: localhost
+
+- name: Delete a remote location
+ cisco.mso.mso_remote_location:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ remote_location: ansible_test
+ state: absent
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ remote_location=dict(type="str", aliases=["name"]),
+ description=dict(type="str"),
+ remote_protocol=dict(type="str", choices=["scp", "sftp"]),
+ remote_host=dict(type="str"),
+ remote_path=dict(type="str"),
+ remote_port=dict(type="int", default=22),
+ authentication_type=dict(type="str", choices=["password", "ssh"]),
+ remote_username=dict(type="str"),
+ remote_password=dict(type="str", no_log=True),
+ remote_ssh_key=dict(type="str", no_log=True),
+ remote_ssh_passphrase=dict(type="str", no_log=True),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "present", ["remote_location", "remote_protocol", "remote_host", "remote_path", "authentication_type"]],
+ ["state", "absent", ["remote_location"]],
+ ["authentication_type", "password", ["remote_username", "remote_password"]],
+ ["authentication_type", "ssh", ["remote_ssh_key"]],
+ ],
+ )
+
+ location_name = module.params.get("remote_location")
+ description = module.params.get("description")
+ protocol = module.params.get("remote_protocol")
+ host = module.params.get("remote_host")
+ path = module.params.get("remote_path")
+ port = module.params.get("remote_port")
+ authentication_type = module.params.get("authentication_type")
+ username = module.params.get("remote_username")
+ password = module.params.get("remote_password")
+ ssh_key = module.params.get("remote_ssh_key")
+ passphrase = module.params.get("remote_ssh_passphrase")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+ api_path = "platform/remote-locations"
+ mso.existing = mso.query_objs(api_path, key="remoteLocations")
+
+ remote_location_obj = None
+ if location_name and mso.existing:
+ remote_location_obj = next((item for item in mso.existing if item.get("name") == location_name), None)
+ if remote_location_obj:
+ mso.existing = remote_location_obj
+
+ if state == "query":
+ if location_name and not remote_location_obj:
+ existing_location_list = ", ".join([item.get("name") for item in mso.existing])
+ mso.module.fail_json(msg="Remote location {0} not found. Remote locations configured: {1}".format(location_name, existing_location_list))
+
+ elif state == "absent":
+ mso.previous = mso.existing
+
+ if module.check_mode:
+ mso.existing = {}
+ elif remote_location_obj:
+ mso.existing = mso.request("{0}/{1}".format(api_path, remote_location_obj.get("id")), method="DELETE")
+
+ elif state == "present":
+ mso.previous = mso.existing
+
+ credential = dict(
+ authType=authentication_type if authentication_type == "password" else "sshKey",
+ hostname=host,
+ port=port,
+ protocolType=protocol,
+ remotePath=path,
+ username=username,
+ )
+
+ if authentication_type == "password":
+ credential.update(password=password)
+ else:
+ credential.update(sshKey=ssh_key)
+ if passphrase:
+ credential.update(passPhrase=passphrase)
+
+ payload = dict(name=location_name, credential=credential)
+
+ if description:
+ payload.update(description=description)
+
+ mso.proposed = payload
+
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ if remote_location_obj:
+ payload.update(id=remote_location_obj.get("id"))
+ mso.existing = mso.request("{0}/{1}".format(api_path, remote_location_obj.get("id")), method="PUT", data=payload)
+ else:
+ mso.existing = mso.request(api_path, method="POST", data=payload)
+
+ mso.existing["credential"].pop("password", None)
+ mso.existing["credential"].pop("sshKey", None)
+ mso.existing["credential"].pop("passPhrase", None)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_rest.py b/ansible_collections/cisco/mso/plugins/modules/mso_rest.py
new file mode 100644
index 000000000..ae05b093b
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_rest.py
@@ -0,0 +1,186 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_rest
+short_description: Direct access to the Cisco MSO REST API
+description:
+- Enables the management of the Cisco MSO fabric through direct access to the Cisco MSO REST API.
+- This module is not idempotent and does not report changes.
+options:
+ method:
+ description:
+ - The HTTP method of the request.
+ - Using C(delete) is typically used for deleting objects.
+ - Using C(get) is typically used for querying objects.
+ - Using C(post) is typically used for modifying objects.
+ - Using C(put) is typically used for modifying existing objects.
+ - Using C(patch) is typically also used for modifying existing objects.
+ type: str
+ choices: [ delete, get, post, put, patch ]
+ default: get
+ aliases: [ action ]
+ path:
+ description:
+ - URI being used to execute API calls.
+ type: str
+ required: true
+ aliases: [ uri ]
+ content:
+ description:
+ - Sets the payload of the API request directly.
+ - This may be convenient to template simple requests.
+ - For anything complex use the C(template) lookup plugin (see examples).
+ type: raw
+ aliases: [ payload ]
+extends_documentation_fragment:
+- cisco.mso.modules
+
+notes:
+- Most payloads are known not to be idempotent, so be careful when constructing payloads.
+seealso:
+- module: cisco.mso.mso_tenant
+author:
+- Anvitha Jain (@anvitha-jain)
+"""
+
+EXAMPLES = r"""
+- name: Add schema (JSON)
+ cisco.mso.mso_rest:
+ host: mso
+ username: admin
+ password: SomeSecretPassword
+ path: /mso/api/v1/schemas
+ method: post
+ content:
+ {
+ "displayName": "{{ mso_schema | default('ansible_test') }}",
+ "templates": [{
+ "name": "Template_1",
+ "tenantId": "{{ add_tenant.jsondata.id }}",
+ "displayName": "Template_1",
+ "templateSubType": [],
+ "templateType": "stretched-template",
+ "anps": [],
+ "contracts": [],
+ "vrfs": [],
+ "bds": [],
+ "filters": [],
+ "externalEpgs": [],
+ "serviceGraphs": [],
+ "intersiteL3outs": []
+ }],
+ "sites": [],
+ "_updateVersion": 0
+ }
+ delegate_to: localhost
+
+- name: Query schema
+ cisco.mso.mso_rest:
+ host: mso
+ username: admin
+ password: SomeSecretPassword
+ path: /mso/api/v1/schemas
+ method: get
+ delegate_to: localhost
+
+- name: Patch schema (YAML)
+ cisco.mso.mso_rest:
+ host: mso
+ username: admin
+ password: SomeSecretPassword
+ path: "/mso/api/v1/schemas/{{ add_schema.jsondata.id }}"
+ method: patch
+ content:
+ - op: add
+ path: /templates/Template_1/anps/-
+ value:
+ name: AP2
+ displayName: AP2
+ epgs: []
+ _updateVersion: 0
+ delegate_to: localhost
+
+- name: Add a tenant from a templated payload file from templates
+ cisco.mso.mso_rest:
+ host: mso
+ username: admin
+ password: SomeSecretPassword
+ method: post
+ path: /api/v1/tenants
+ content: "{{ lookup('template', 'mso/tenant.json.j2') }}"
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+"""
+
+# Optional, only used for YAML validation
+try:
+ import yaml
+
+ HAS_YAML = True
+except Exception:
+ HAS_YAML = False
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+from ansible.module_utils._text import to_text
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ path=dict(type="str", required=True, aliases=["uri"]),
+ method=dict(type="str", default="get", choices=["delete", "get", "post", "put", "patch"], aliases=["action"]),
+ content=dict(type="raw", aliases=["payload"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ )
+
+ content = module.params.get("content")
+ path = module.params.get("path")
+
+ mso = MSOModule(module)
+
+ # Validate content/payload
+ if content and isinstance(content, str) and HAS_YAML:
+ try:
+ # Validate YAML/JSON string
+ content = yaml.safe_load(content)
+ except Exception as e:
+ module.fail_json(msg="Failed to parse provided JSON/YAML payload: %s" % to_text(e), exception=to_text(e), payload=content)
+
+ mso.method = mso.params.get("method").upper()
+
+ # Perform request
+ if module.check_mode:
+ mso.result["jsondata"] = content
+ else:
+ mso.result["jsondata"] = mso.request(path, method=mso.method, data=content, api_version=None)
+
+ mso.result["status"] = mso.status
+
+ if mso.method != "GET":
+ mso.result["changed"] = True
+ if mso.method == "DELETE":
+ mso.result["jsondata"] = None
+
+ # Report success
+ mso.exit_json(**mso.result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_role.py b/ansible_collections/cisco/mso/plugins/modules/mso_role.py
new file mode 100644
index 000000000..cfa4483b0
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_role.py
@@ -0,0 +1,285 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_role
+short_description: Manage roles
+description:
+- Manage roles on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ role:
+ description:
+ - The name of the role.
+ type: str
+ aliases: [ name ]
+ display_name:
+ description:
+ - The name of the role to be displayed in the web UI.
+ type: str
+ description:
+ description:
+ - The description of the role.
+ type: str
+ read_permissions:
+ description:
+ - A list of read permissions tied to this role.
+ type: list
+ elements: str
+ choices:
+ - backup-db
+ - manage-audit-records
+ - manage-labels
+ - manage-roles
+ - manage-schemas
+ - manage-sites
+ - manage-tenants
+ - manage-tenant-schemas
+ - manage-users
+ - platform-logs
+ - view-all-audit-records
+ - view-labels
+ - view-roles
+ - view-schemas
+ - view-sites
+ - view-tenants
+ - view-tenant-schemas
+ - view-users
+ write_permissions:
+ description:
+ - A list of write permissions tied to this role.
+ type: list
+ elements: str
+ aliases: [ permissions ]
+ choices:
+ - backup-db
+ - manage-audit-records
+ - manage-labels
+ - manage-roles
+ - manage-schemas
+ - manage-sites
+ - manage-tenants
+ - manage-tenant-schemas
+ - manage-users
+ - platform-logs
+ - view-all-audit-records
+ - view-labels
+ - view-roles
+ - view-schemas
+ - view-sites
+ - view-tenants
+ - view-tenant-schemas
+ - view-users
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new role
+ cisco.mso.mso_role:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ role: readOnly
+ display_name: Read Only
+ description: Read-only access for troubleshooting
+ read_permissions:
+ - view-roles
+ - view-schemas
+ - view-sites
+ - view-tenants
+ - view-tenant-schemas
+ - view-users
+ write_permissions:
+ - manage-roles
+ - manage-schemas
+ - manage-sites
+ - manage-tenants
+ - manage-tenant-schemas
+ - manage-users
+ state: present
+ delegate_to: localhost
+
+- name: Remove a role
+ cisco.mso.mso_role:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ role: readOnly
+ state: absent
+ delegate_to: localhost
+
+- name: Query a role
+ cisco.mso.mso_role:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ role: readOnly
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all roles
+ cisco.mso.mso_role:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ role=dict(type="str", aliases=["name"]),
+ display_name=dict(type="str"),
+ description=dict(type="str"),
+ read_permissions=dict(
+ type="list",
+ elements="str",
+ choices=[
+ "backup-db",
+ "manage-audit-records",
+ "manage-labels",
+ "manage-roles",
+ "manage-schemas",
+ "manage-sites",
+ "manage-tenants",
+ "manage-tenant-schemas",
+ "manage-users",
+ "platform-logs",
+ "view-all-audit-records",
+ "view-labels",
+ "view-roles",
+ "view-schemas",
+ "view-sites",
+ "view-tenants",
+ "view-tenant-schemas",
+ "view-users",
+ ],
+ ),
+ write_permissions=dict(
+ type="list",
+ elements="str",
+ aliases=["permissions"],
+ choices=[
+ "backup-db",
+ "manage-audit-records",
+ "manage-labels",
+ "manage-roles",
+ "manage-schemas",
+ "manage-sites",
+ "manage-tenants",
+ "manage-tenant-schemas",
+ "manage-users",
+ "platform-logs",
+ "view-all-audit-records",
+ "view-labels",
+ "view-roles",
+ "view-schemas",
+ "view-sites",
+ "view-tenants",
+ "view-tenant-schemas",
+ "view-users",
+ ],
+ ),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["role"]],
+ ["state", "present", ["role"]],
+ ],
+ )
+
+ role = module.params.get("role")
+ description = module.params.get("description")
+ read_permissions = module.params.get("read_permissions")
+ write_permissions = module.params.get("write_permissions")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ role_id = None
+ path = "roles"
+
+ # Query for existing object(s)
+ if role:
+ mso.existing = mso.get_obj(path, name=role)
+ if mso.existing:
+ role_id = mso.existing.get("id")
+ # If we found an existing object, continue with it
+ path = "roles/{id}".format(id=role_id)
+ else:
+ mso.existing = mso.query_objs(path)
+
+ if state == "query":
+ pass
+
+ elif state == "absent":
+ mso.previous = mso.existing
+ if mso.existing:
+ if module.check_mode:
+ mso.existing = {}
+ else:
+ mso.existing = mso.request(path, method="DELETE")
+
+ elif state == "present":
+ mso.previous = mso.existing
+
+ payload = dict(
+ id=role_id,
+ name=role,
+ displayName=role,
+ description=description,
+ readPermissions=read_permissions,
+ writePermissions=write_permissions,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ if mso.check_changed():
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request(path, method="PUT", data=mso.sent)
+ else:
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request(path, method="POST", data=mso.sent)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema.py
new file mode 100644
index 000000000..2eba13ac9
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema.py
@@ -0,0 +1,132 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema
+short_description: Manage schemas
+description:
+- Manage schemas on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ aliases: [ name ]
+ state:
+ description:
+ - Use C(absent) for removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, query ]
+ default: query
+notes:
+- Due to restrictions of the MSO REST API this module cannot create empty schemas (i.e. schemas without templates).
+ Use the M(cisco.mso.mso_schema_template) to automatically create schemas with templates.
+seealso:
+- module: cisco.mso.mso_schema_site
+- module: cisco.mso.mso_schema_template
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Remove schemas
+ cisco.mso.mso_schema:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a schema
+ cisco.mso.mso_schema:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all schemas
+ cisco.mso.mso_schema:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", aliases=["name"]),
+ # messages=dict(type='dict'),
+ # associations=dict(type='list'),
+ # health_faults=dict(type='list'),
+ # references=dict(type='dict'),
+ # policy_states=dict(type='list'),
+ state=dict(type="str", default="query", choices=["absent", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["schema"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ schema_id = None
+ path = "schemas"
+
+ # Query for existing object(s)
+ if schema:
+ mso.existing = mso.get_obj(path, displayName=schema)
+ if mso.existing:
+ schema_id = mso.existing.get("id")
+ path = "schemas/{id}".format(id=schema_id)
+ else:
+ mso.existing = mso.query_objs(path)
+
+ if state == "query":
+ pass
+
+ elif state == "absent":
+ mso.previous = mso.existing
+ if mso.existing:
+ if module.check_mode:
+ mso.existing = {}
+ else:
+ mso.existing = mso.request(path, method="DELETE")
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_clone.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_clone.py
new file mode 100644
index 000000000..840fb12ca
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_clone.py
@@ -0,0 +1,125 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_clone
+short_description: Clone schemas
+description:
+- Clone schemas on Cisco ACI Multi-Site.
+- Clones only template objects and not site objects.
+- This module can only be used on versions of MSO that are 3.3 or greater.
+author:
+- Anvitha Jain (@anvitha-jain)
+options:
+ source_schema:
+ description:
+ - The name of the source_schema.
+ type: str
+ destination_schema:
+ description:
+ - The name of the destination_schema.
+ type: str
+ state:
+ description:
+ - Use C(clone) for adding.
+ type: str
+ choices: [ clone ]
+ default: clone
+seealso:
+- module: cisco.mso.mso_schema
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Clone schema
+ cisco.mso.mso_schema_clone:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ source_schema: Source_Schema
+ destination_schema: Destination_Schema
+ state: clone
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+from ansible_collections.cisco.mso.plugins.module_utils.constants import NDO_4_UNIQUE_IDENTIFIERS
+import json
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ source_schema=dict(type="str"),
+ destination_schema=dict(type="str"),
+ state=dict(type="str", default="clone", choices=["clone"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "clone", ["destination_schema"]],
+ ],
+ )
+
+ source_schema = module.params.get("source_schema")
+ destination_schema = module.params.get("destination_schema")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ # Get source schema details
+ source_schema_path = "schemas/{0}".format(mso.lookup_schema(source_schema))
+ source_schema_obj = mso.query_obj(source_schema_path, displayName=source_schema)
+
+ source_data = source_schema_obj.get("templates")
+ source_data = json.loads(json.dumps(source_data).replace("/{0}".format(source_schema_path), ""))
+ # certain unique identifiers are present in NDO4.0> source which need to be deleted from source_data prior to POST
+ for template in source_data:
+ mso.delete_keys_from_dict(template, NDO_4_UNIQUE_IDENTIFIERS)
+
+ path = "schemas"
+
+ # Check if source and destination schema are named differently
+ if source_schema == destination_schema:
+ mso.fail_json(msg="Source and Destination schema cannot have same names.")
+ # Query for existing object(s)
+ if destination_schema:
+ mso.existing = mso.get_obj(path, displayName=destination_schema)
+ if mso.existing:
+ mso.fail_json(msg="Schema with the name '{0}' already exists. Please use another name.".format(destination_schema))
+
+ if state == "clone":
+ mso.previous = mso.existing
+ payload = dict(
+ displayName=destination_schema,
+ templates=source_data,
+ )
+ mso.sanitize(payload, collate=True)
+
+ if not mso.existing:
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request(path, method="POST", data=mso.sent)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site.py
new file mode 100644
index 000000000..83a5213a2
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site.py
@@ -0,0 +1,194 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_site
+short_description: Manage sites in schemas
+description:
+- Manage sites on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+- Shreyas Srish (@shrsr)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ site:
+ description:
+ - The name of the site to manage.
+ type: str
+ template:
+ description:
+ - The name of the template.
+ type: str
+ aliases: [ name ]
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+seealso:
+- module: cisco.mso.mso_schema_template
+- module: cisco.mso.mso_site
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new site to a schema
+ cisco.mso.mso_schema_site:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ site: Site1
+ template: Template 1
+ state: present
+ delegate_to: localhost
+
+- name: Remove a site from a schema
+ cisco.mso.mso_schema_site:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ site: Site1
+ template: Template 1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a schema site
+ cisco.mso.mso_schema_site:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ site: Site1
+ template: Template 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all schema sites
+ cisco.mso.mso_schema_site:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ site=dict(type="str", aliases=["name"]),
+ template=dict(type="str"),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["site", "template"]],
+ ["state", "present", ["site", "template"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ site = module.params.get("site")
+ template = module.params.get("template")
+ if template is not None:
+ template = template.replace(" ", "")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ # Get schema
+ schema_id, schema_path, schema_obj = mso.query_schema(schema)
+
+ # Get site
+ site_id = mso.lookup_site(site)
+
+ mso.existing = {}
+ if "sites" in schema_obj:
+ sites = [(s.get("siteId"), s.get("templateName")) for s in schema_obj.get("sites")]
+ if template:
+ if (site_id, template) in sites:
+ site_idx = sites.index((site_id, template))
+ site_path = "/sites/{0}".format(site_idx)
+ mso.existing = schema_obj.get("sites")[site_idx]
+ else:
+ mso.existing = schema_obj.get("sites")
+
+ if state == "query":
+ if not mso.existing:
+ if template:
+ mso.fail_json(msg="Template '{0}' not found".format(template))
+ else:
+ mso.existing = []
+ mso.exit_json()
+
+ sites_path = "/sites"
+ ops = []
+
+ mso.previous = mso.existing
+ if state == "absent":
+ if mso.existing:
+ # Remove existing site
+ mso.sent = mso.existing = {}
+ ops.append(dict(op="remove", path=site_path))
+
+ elif state == "present":
+ if not mso.existing:
+ # Add new site
+ payload = dict(
+ siteId=site_id,
+ templateName=template,
+ anps=[],
+ bds=[],
+ contracts=[],
+ externalEpgs=[],
+ intersiteL3outs=[],
+ serviceGraphs=[],
+ vrfs=[],
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ ops.append(dict(op="add", path=sites_path + "/-", value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp.py
new file mode 100644
index 000000000..0552b6a31
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp.py
@@ -0,0 +1,224 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_site_anp
+short_description: Manage site-local Application Network Profiles (ANPs) in schema template
+description:
+- Manage site-local ANPs in schema template on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ site:
+ description:
+ - The name of the site.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: true
+ anp:
+ description:
+ - The name of the ANP to manage.
+ type: str
+ aliases: [ name ]
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+seealso:
+- module: cisco.mso.mso_schema_site
+- module: cisco.mso.mso_schema_site_anp_epg
+- module: cisco.mso.mso_schema_template_anp
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new site ANP
+ cisco.mso.mso_schema_site_anp:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ state: present
+ delegate_to: localhost
+
+- name: Remove a site ANP
+ cisco.mso.mso_schema_site_anp:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific site ANP
+ cisco.mso.mso_schema_site_anp:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all site ANPs
+ cisco.mso.mso_schema_site_anp:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ site=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ anp=dict(type="str", aliases=["name"]), # This parameter is not required for querying all objects
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["anp"]],
+ ["state", "present", ["anp"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ site = module.params.get("site")
+ template = module.params.get("template").replace(" ", "")
+ anp = module.params.get("anp")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ # Get schema objects
+ schema_id, schema_path, schema_obj = mso.query_schema(schema)
+
+ # Get site
+ site_id = mso.lookup_site(site)
+
+ # Get site_idx
+ if not schema_obj.get("sites"):
+ mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template))
+ sites = [(s.get("siteId"), s.get("templateName")) for s in schema_obj.get("sites")]
+ sites_list = [s.get("siteId") + "/" + s.get("templateName") for s in schema_obj.get("sites")]
+ if (site_id, template) not in sites:
+ mso.fail_json(
+ msg="Provided site/siteId/template '{0}/{1}/{2}' does not exist. "
+ "Existing siteIds/templates: {3}".format(site, site_id, template, ", ".join(sites_list))
+ )
+
+ # Schema-access uses indexes
+ site_idx = sites.index((site_id, template))
+ # Path-based access uses site_id-template
+ site_template = "{0}-{1}".format(site_id, template)
+
+ # Get ANP
+ anp_ref = mso.anp_ref(schema_id=schema_id, template=template, anp=anp)
+ anps = [a.get("anpRef") for a in schema_obj.get("sites")[site_idx]["anps"]]
+
+ if anp is not None and anp_ref in anps:
+ anp_idx = anps.index(anp_ref)
+ anp_path = "/sites/{0}/anps/{1}".format(site_template, anp)
+ mso.existing = schema_obj.get("sites")[site_idx]["anps"][anp_idx]
+
+ if state == "query":
+ if anp is None:
+ mso.existing = schema_obj.get("sites")[site_idx]["anps"]
+ elif not mso.existing:
+ mso.fail_json(msg="ANP '{anp}' not found".format(anp=anp))
+ mso.exit_json()
+
+ anps_path = "/sites/{0}/anps".format(site_template)
+ ops = []
+
+ # Workaround due to inconsistency in attributes REQUEST/RESPONSE API
+ # FIX for MSO Error 400: Bad Request: (0)(0)(0)(0)/deploymentImmediacy error.path.missing
+ mso.replace_keys_in_dict("deployImmediacy", "deploymentImmediacy")
+ if mso.existing.get("anpRef"):
+ anp_ref = mso.dict_from_ref(mso.existing.get("anpRef"))
+ mso.existing["anpRef"] = anp_ref
+
+ mso.previous = mso.existing
+ if state == "absent":
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op="remove", path=anp_path))
+
+ elif state == "present":
+ payload = dict(
+ anpRef=dict(
+ schemaId=schema_id,
+ templateName=template,
+ anpName=anp,
+ ),
+ )
+
+ if "epgs" in mso.existing:
+ for epg in mso.existing.get("epgs"):
+ epg = mso.recursive_dict_from_ref(epg)
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op="replace", path=anp_path, value=mso.sent))
+ else:
+ ops.append(dict(op="add", path=anps_path + "/-", value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode and mso.existing != mso.previous:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg.py
new file mode 100644
index 000000000..1c2ad6433
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg.py
@@ -0,0 +1,301 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_site_anp_epg
+short_description: Manage site-local Endpoint Groups (EPGs) in schema template
+description:
+- Manage site-local EPGs in schema template on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ site:
+ description:
+ - The name of the site.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: true
+ anp:
+ description:
+ - The name of the ANP.
+ type: str
+ required: true
+ epg:
+ description:
+ - The name of the EPG to manage.
+ type: str
+ aliases: [ name ]
+ private_link_label:
+ description:
+ - The private link label used to represent this subnet.
+ - This parameter is available for MSO version greater than 3.3.
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+seealso:
+- module: cisco.mso.mso_schema_site_anp
+- module: cisco.mso.mso_schema_site_anp_epg_subnet
+- module: cisco.mso.mso_schema_template_anp_epg
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new site EPG
+ cisco.mso.mso_schema_site_anp_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ state: present
+ delegate_to: localhost
+
+- name: Remove a site EPG
+ cisco.mso.mso_schema_site_anp_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific site EPGs
+ cisco.mso.mso_schema_site_anp_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all site EPGs
+ cisco.mso.mso_schema_site_anp_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ site=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ anp=dict(type="str", required=True),
+ epg=dict(type="str", aliases=["name"]), # This parameter is not required for querying all objects
+ private_link_label=dict(type="str"),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["epg"]],
+ ["state", "present", ["epg"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ site = module.params.get("site")
+ template = module.params.get("template").replace(" ", "")
+ anp = module.params.get("anp")
+ epg = module.params.get("epg")
+ private_link_label = module.params.get("private_link_label")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ # Get schema objects
+ schema_id, schema_path, schema_obj = mso.query_schema(schema)
+
+ # Get template
+ templates = [t.get("name") for t in schema_obj.get("templates")]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates)))
+ template_idx = templates.index(template)
+
+ # Get site
+ site_id = mso.lookup_site(site)
+
+ # Get site_idx
+ if not schema_obj.get("sites"):
+ mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template))
+ sites = [(s.get("siteId"), s.get("templateName")) for s in schema_obj.get("sites")]
+ sites_list = [s.get("siteId") + "/" + s.get("templateName") for s in schema_obj.get("sites")]
+ if (site_id, template) not in sites:
+ mso.fail_json(
+ msg="Provided site/siteId/template '{0}/{1}/{2}' does not exist. "
+ "Existing siteIds/templates: {3}".format(site, site_id, template, ", ".join(sites_list))
+ )
+
+ # Schema-access uses indexes
+ site_idx = sites.index((site_id, template))
+ # Path-based access uses site_id-template
+ site_template = "{0}-{1}".format(site_id, template)
+
+ payload = {}
+ ops = []
+ op_path = ""
+
+ # Get ANP
+ anp_ref = mso.anp_ref(schema_id=schema_id, template=template, anp=anp)
+ anps = [a.get("anpRef") for a in schema_obj["sites"][site_idx]["anps"]]
+ anps_in_temp = [a.get("name") for a in schema_obj["templates"][template_idx]["anps"]]
+ if anp not in anps_in_temp:
+ mso.fail_json(msg="Provided anp '{0}' does not exist. Existing anps: {1}".format(anp, ", ".join(anps_in_temp)))
+ else:
+ # Get anp index at template level
+ template_anp_idx = anps_in_temp.index(anp)
+
+ # If anp not at site level but exists at template level
+ if anp_ref not in anps:
+ op_path = "/sites/{0}/anps".format(site_template)
+ payload = dict(
+ anpRef=dict(
+ schemaId=schema_id,
+ templateName=template,
+ anpName=anp,
+ ),
+ )
+ else:
+ # Get anp index at site level
+ anp_idx = anps.index(anp_ref)
+
+ if epg is not None:
+ # Get EPG
+ epg_ref = mso.epg_ref(schema_id=schema_id, template=template, anp=anp, epg=epg)
+ new_epg = dict(
+ epgRef=dict(
+ schemaId=schema_id,
+ templateName=template,
+ anpName=anp,
+ epgName=epg,
+ )
+ )
+
+ # If anp exists at site level
+ if "anpRef" not in payload:
+ epgs = [e.get("epgRef") for e in schema_obj["sites"][site_idx]["anps"][anp_idx]["epgs"]]
+
+ # If anp already at site level AND if epg not at site level (or) anp not at site level?
+ if ("anpRef" not in payload and epg_ref not in epgs) or "anpRef" in payload:
+ epgs_in_temp = [e.get("name") for e in schema_obj["templates"][template_idx]["anps"][template_anp_idx]["epgs"]]
+
+ # If EPG not at template level - Fail
+ if epg not in epgs_in_temp:
+ mso.fail_json(msg="Provided EPG '{0}' does not exist. Existing EPGs: {1}".format(epg, ", ".join(epgs_in_temp)))
+
+ # EPG at template level but not at site level. Create payload at site level for EPG
+ else:
+ # If anp not in payload then, anp already exists at site level. New payload will only have new EPG payload
+ if "anpRef" not in payload:
+ op_path = "/sites/{0}/anps/{1}/epgs".format(site_template, anp)
+ payload = new_epg
+ else:
+ # If anp in payload, anp exists at site level. Update payload with EPG payload
+ payload["epgs"] = [new_epg]
+
+ # Get index of EPG at site level
+ else:
+ epg_idx = epgs.index(epg_ref)
+ epg_path = "/sites/{0}/anps/{1}/epgs/{2}".format(site_template, anp, epg)
+ mso.existing = schema_obj.get("sites")[site_idx]["anps"][anp_idx]["epgs"][epg_idx]
+ payload = new_epg
+
+ ops = []
+
+ if state == "query":
+ if anp_ref not in anps:
+ mso.fail_json(msg="Provided anp '{0}' does not exist at site level.".format(anp))
+ if epg is None:
+ mso.existing = schema_obj.get("sites")[site_idx]["anps"][anp_idx]["epgs"]
+ elif not mso.existing:
+ mso.fail_json(msg="EPG '{epg}' not found".format(epg=epg))
+ mso.exit_json()
+
+ # Workaround due to inconsistency in attributes REQUEST/RESPONSE API
+ # FIX for MSO Error 400: Bad Request: (0)(0)(0)(0)/deploymentImmediacy error.path.missing
+ mso.replace_keys_in_dict("deployImmediacy", "deploymentImmediacy")
+ if mso.existing.get("epgRef"):
+ epg_ref = mso.dict_from_ref(mso.existing.get("epgRef"))
+ mso.existing["epgRef"] = epg_ref
+
+ mso.previous = mso.existing
+
+ if state == "absent":
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op="remove", path=epg_path))
+
+ elif state == "present":
+ if private_link_label is not None:
+ payload["privateLinkLabel"] = dict(name=private_link_label)
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op="replace", path=epg_path, value=mso.sent))
+ else:
+ ops.append(dict(op="add", path=op_path + "/-", value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode and mso.existing != mso.previous:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_bulk_staticport.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_bulk_staticport.py
new file mode 100644
index 000000000..eda60cfd1
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_bulk_staticport.py
@@ -0,0 +1,464 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2023, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_site_anp_epg_bulk_staticport
+short_description: Manage site-local EPG static ports in bulk in schema template
+description:
+- Manage site-local EPG static ports in bulk in schema template on Cisco ACI Multi-Site.
+author:
+- Anvitha Jain (@anvjain)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ site:
+ description:
+ - The name of the site.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: true
+ anp:
+ description:
+ - The name of the ANP.
+ type: str
+ required: true
+ epg:
+ description:
+ - The name of the EPG.
+ type: str
+ required: true
+ type:
+ description:
+ - The path type of the static port
+ - vpc is used for a Virtual Port Channel
+ - dpc is used for a Direct Port Channel
+ - port is used for a single interface
+ type: str
+ choices: [ port, vpc, dpc ]
+ default: port
+ pod:
+ description:
+ - The pod of the static port.
+ type: str
+ leaf:
+ description:
+ - The leaf of the static port.
+ type: str
+ fex:
+ description:
+ - The fex id of the static port.
+ type: str
+ path:
+ description:
+ - The path of the static port.
+ type: str
+ vlan:
+ description:
+ - The port encap VLAN id of the static port.
+ type: int
+ deployment_immediacy:
+ description:
+ - The deployment immediacy of the static port.
+ - C(immediate) means B(Deploy immediate).
+ - C(lazy) means B(deploy on demand).
+ type: str
+ choices: [ immediate, lazy ]
+ default: lazy
+ mode:
+ description:
+ - The mode of the static port.
+ - C(native) means B(Access (802.1p)).
+ - C(regular) means B(Trunk).
+ - C(untagged) means B(Access (untagged)).
+ type: str
+ choices: [ native, regular, untagged ]
+ default: untagged
+ primary_micro_segment_vlan:
+ description:
+ - Primary micro-seg VLAN of static port.
+ type: int
+ static_ports:
+ description:
+ - List of static port configurations and elements in the form of a dictionary.
+ - Module level attributes will be overridden by the path level attributes.
+ - Making changes to an item in the list will update the whole payload.
+ type: list
+ elements: dict
+ suboptions:
+ type:
+ description:
+ - The path type of the static port
+ - vpc is used for a Virtual Port Channel
+ - dpc is used for a Direct Port Channel
+ - port is used for a single interface
+ type: str
+ choices: [ port, vpc, dpc ]
+ pod:
+ description:
+ - The pod of the static port.
+ type: str
+ leaf:
+ description:
+ - The leaf of the static port.
+ type: str
+ fex:
+ description:
+ - The fex id of the static port.
+ type: str
+ path:
+ description:
+ - The path of the static port.
+ - Path has to be unique for each static port in a particular leaf.
+ type: str
+ vlan:
+ description:
+ - The port encap VLAN id of the static port.
+ type: int
+ deployment_immediacy:
+ description:
+ - The deployment immediacy of the static port.
+ - C(immediate) means B(Deploy immediate).
+ - C(lazy) means B(deploy on demand).
+ type: str
+ choices: [ immediate, lazy ]
+ mode:
+ description:
+ - The mode of the static port.
+ - C(native) means B(Access (802.1p)).
+ - C(regular) means B(Trunk).
+ - C(untagged) means B(Access (untagged)).
+ type: str
+ choices: [ native, regular, untagged ]
+ primary_micro_segment_vlan:
+ description:
+ - Primary micro-seg VLAN of the static port.
+ type: int
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+notes:
+- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index.
+ This can cause silent corruption on concurrent access when changing/removing an object as
+ the wrong object may be referenced. This module is affected by this deficiency.
+seealso:
+- module: cisco.mso.mso_schema_site_anp_epg
+- module: cisco.mso.mso_schema_template_anp_epg
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new static port to a site EPG
+ cisco.mso.mso_schema_site_anp_epg_bulk_staticport:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ type: port
+ pod: pod-1
+ leaf: 101
+ path: eth1/1
+ vlan: 126
+ deployment_immediacy: immediate
+ static_ports:
+ - path: eth1/2
+ leaf: 102
+ - path: eth1/3
+ vlan: 124
+ state: present
+ delegate_to: localhost
+
+- name: Add a new static fex port to a site EPG
+ cisco.mso.mso_schema_site_anp_epg_bulk_staticport:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ type: port
+ pod: pod-1
+ leaf: 101
+ path: eth1/1
+ vlan: 126
+ deployment_immediacy: lazy
+ static_ports:
+ - path: eth1/2
+ leaf: 102
+ - path: eth1/3
+ vlan: 124
+ - fex: 151
+ state: present
+ delegate_to: localhost
+
+- name: Add a new static VPC to a site EPG
+ cisco.mso.mso_schema_site_anp_epg_bulk_staticport:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ type: port
+ pod: pod-1
+ leaf: 101
+ path: eth1/1
+ vlan: 126
+ static_ports:
+ - path: eth1/2
+ leaf: 102
+ - path: eth1/3
+ vlan: 124
+ - fex: 151
+ - leaf: 101-102
+ path: ansible_polgrp
+ vlan: 127
+ type: vpc
+ mode: untagged
+ deployment_immediacy: lazy
+ state: present
+ delegate_to: localhost
+
+- name: Remove static ports from a site EPG
+ cisco.mso.mso_schema_site_anp_epg_bulk_staticport:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ state: absent
+ delegate_to: localhost
+
+- name: Query all site EPG static ports
+ cisco.mso.mso_schema_site_anp_epg_bulk_staticport:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_site_anp_epg_bulk_staticport_spec
+from ansible_collections.cisco.mso.plugins.module_utils.schema import MSOSchema
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ site=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ anp=dict(type="str", required=True),
+ epg=dict(type="str", required=True),
+ type=dict(type="str", default="port", choices=["port", "vpc", "dpc"]),
+ pod=dict(type="str"), # This parameter is not required for querying all objects
+ leaf=dict(type="str"), # This parameter is not required for querying all objects
+ fex=dict(type="str"), # This parameter is not required for querying all objects
+ path=dict(type="str"), # This parameter is not required for querying all objects
+ vlan=dict(type="int"), # This parameter is not required for querying all objects
+ primary_micro_segment_vlan=dict(type="int"), # This parameter is not required for querying all objects
+ deployment_immediacy=dict(type="str", default="lazy", choices=["immediate", "lazy"]),
+ mode=dict(type="str", default="untagged", choices=["native", "regular", "untagged"]),
+ static_ports=dict(type="list", elements="dict", options=mso_site_anp_epg_bulk_staticport_spec()),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "present", ["static_ports"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ site = module.params.get("site")
+ template = module.params.get("template").replace(" ", "")
+ anp = module.params.get("anp")
+ epg = module.params.get("epg")
+ module_path_type = module.params.get("type")
+ module_pod = module.params.get("pod")
+ module_leaf = module.params.get("leaf")
+ module_fex = module.params.get("fex")
+ module_path = module.params.get("path")
+ module_vlan = module.params.get("vlan")
+ module_primary_micro_segment_vlan = module.params.get("primary_micro_segment_vlan")
+ module_deployment_immediacy = module.params.get("deployment_immediacy")
+ module_mode = module.params.get("mode")
+ static_ports = module.params.get("static_ports")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ # Get schema objects
+ mso_schema = MSOSchema(mso, schema, template, site)
+ mso_objects = mso_schema.schema_objects
+
+ # Verifies ANP and EPG exists at template level
+ mso_schema.set_template_anp(anp)
+ mso_schema.set_template_anp_epg(epg)
+
+ # Verifies if ANP exists at site level
+ mso_schema.set_site_anp(anp, fail_module=False)
+
+ payload = dict()
+ ops = []
+ op_path = "/sites/{0}-{1}/anps".format(mso_objects.get("site").details.get("siteId"), template)
+ mso.existing = []
+
+ # If anp not at site level but exists at template level
+ if not mso_objects.get("site_anp"):
+ op_path = op_path + "/-"
+ payload.update(
+ anpRef=dict(
+ schemaId=mso_schema.id,
+ templateName=template,
+ anpName=anp,
+ ),
+ )
+ else:
+ mso_schema.set_site_anp_epg(epg, fail_module=False)
+
+ # If epg not at site level (or) anp not at site level payload
+ if not mso_objects.get("site_anp_epg") or "anpRef" in payload:
+ # EPG at template level but not at site level. Create payload at site level for EPG
+ new_epg = dict(
+ epgRef=dict(
+ schemaId=mso_schema.id,
+ templateName=template,
+ anpName=anp,
+ epgName=epg,
+ )
+ )
+
+ # If anp not in payload then, anp already exists at site level. New payload will only have new EPG payload
+ if "anpRef" not in payload:
+ op_path = "{0}/{1}/epgs/-".format(op_path, anp)
+ payload = new_epg
+ else:
+ # If anp in payload, anp exists at site level. Update payload with EPG payload
+ payload["epgs"] = [new_epg]
+ else:
+ # If anp and epg exists at site level
+ op_path = "{0}/{1}/epgs/{2}/staticPorts".format(op_path, anp, epg)
+ mso.existing = mso_objects.get("site_anp_epg").details.get("staticPorts", [])
+
+ staticport_list = []
+ unique_paths = []
+
+ mso.previous = mso.existing
+
+ if state == "absent":
+ if mso.existing:
+ mso.sent = mso.existing = []
+ ops.append(dict(op="remove", path=op_path))
+
+ elif state == "present":
+ for static_port in static_ports:
+ path_type = static_port.get("type") or module_path_type
+ pod = static_port.get("pod") or module_pod
+ leaf = static_port.get("leaf") or module_leaf
+ fex = static_port.get("fex") or module_fex
+ path = static_port.get("path") or module_path # Note :path has to be diffent in each leaf for every static port in the list.
+ vlan = static_port.get("vlan") or module_vlan
+ primary_micro_segment_vlan = static_port.get("primary_micro_segment_vlan") or module_primary_micro_segment_vlan
+ deployment_immediacy = static_port.get("deployment_immediacy") or module_deployment_immediacy
+ mode = static_port.get("mode") or module_mode
+
+ required_dict = {"pod": pod, "leaf": leaf, "path": path, "vlan": vlan}
+ if None in required_dict.values():
+ res = [key for key in required_dict.keys() if required_dict[key] is None]
+ mso.fail_json(msg="state is present but all of the following are missing: {0}.".format(", ".join(res)))
+ else:
+ if path_type == "port" and fex is not None:
+ # Select port path for fex if fex param is used
+ portpath = "topology/{0}/paths-{1}/extpaths-{2}/pathep-[{3}]".format(pod, leaf, fex, path)
+ elif path_type == "vpc":
+ portpath = "topology/{0}/protpaths-{1}/pathep-[{2}]".format(pod, leaf, path)
+ else:
+ portpath = "topology/{0}/paths-{1}/pathep-[{2}]".format(pod, leaf, path)
+
+ new_leaf = dict(
+ deploymentImmediacy=deployment_immediacy,
+ mode=mode,
+ path=portpath,
+ portEncapVlan=vlan,
+ type=path_type,
+ )
+
+ if primary_micro_segment_vlan:
+ new_leaf.update(microSegVlan=primary_micro_segment_vlan)
+
+ # validate and append staticports to staticport_list if path variable is different
+ if portpath in unique_paths:
+ mso.fail_json(msg="Each leaf in a pod of a static port should have an unique path.")
+ else:
+ unique_paths.append(portpath)
+ staticport_list.append(new_leaf)
+
+ # If payload is empty, anp and EPG already exist at site level
+ if not payload:
+ payload = staticport_list
+ elif "anpRef" not in payload: # If anp already exists at site level
+ payload["staticPorts"] = staticport_list
+ else:
+ payload["epgs"][0]["staticPorts"] = staticport_list
+
+ mso.proposed = staticport_list
+ mso.sent = payload
+
+ if mso.existing:
+ ops.append(dict(op="replace", path=op_path, value=mso.sent))
+ else:
+ ops.append(dict(op="add", path=op_path, value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode and mso.proposed != mso.previous:
+ mso.request(mso_schema.path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_domain.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_domain.py
new file mode 100644
index 000000000..c684a27be
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_domain.py
@@ -0,0 +1,476 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Nirav Katarmal (@nkatarmal-crest) <nirav.katarmal@crestdatasys.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_site_anp_epg_domain
+short_description: Manage site-local EPG domains in schema template
+description:
+- Manage site-local EPG domains in schema template on Cisco ACI Multi-Site.
+author:
+- Nirav Katarmal (@nkatarmal-crest)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ site:
+ description:
+ - The name of the site.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: true
+ anp:
+ description:
+ - The name of the ANP.
+ type: str
+ required: true
+ epg:
+ description:
+ - The name of the EPG.
+ type: str
+ required: true
+ domain_association_type:
+ description:
+ - The type of domain to associate.
+ type: str
+ choices: [ vmmDomain, l3ExtDomain, l2ExtDomain, physicalDomain, fibreChannelDomain ]
+ domain_profile:
+ description:
+ - The domain profile name.
+ type: str
+ deployment_immediacy:
+ description:
+ - The deployment immediacy of the domain.
+ - C(immediate) means B(Deploy immediate).
+ - C(lazy) means B(deploy on demand).
+ type: str
+ choices: [ immediate, lazy ]
+ resolution_immediacy:
+ description:
+ - Determines when the policies should be resolved and available.
+ - Defaults to C(lazy) when unset during creation.
+ type: str
+ choices: [ immediate, lazy, pre-provision ]
+ micro_seg_vlan_type:
+ description:
+ - Virtual LAN type for microsegmentation. This attribute can only be used with vmmDomain domain association.
+ - vlan is currently the only accepted value.
+ type: str
+ micro_seg_vlan:
+ description:
+ - Virtual LAN for microsegmentation. This attribute can only be used with vmmDomain domain association.
+ type: int
+ port_encap_vlan_type:
+ description:
+ - Virtual LAN type for port encap. This attribute can only be used with vmmDomain domain association.
+ - vlan is currently the only accepted value.
+ type: str
+ port_encap_vlan:
+ description:
+ - Virtual LAN type for port encap. This attribute can only be used with vmmDomain domain association.
+ type: int
+ vlan_encap_mode:
+ description:
+ - Which VLAN enacap mode to use. This attribute can only be used with vmmDomain domain association.
+ type: str
+ choices: [ static, dynamic ]
+ allow_micro_segmentation:
+ description:
+ - Specifies microsegmentation is enabled or not. This attribute can only be used with vmmDomain domain association.
+ type: bool
+ switch_type:
+ description:
+ - Which switch type to use with this domain association. This attribute can only be used with vmmDomain domain association.
+ type: str
+ switching_mode:
+ description:
+ - Which switching mode to use with this domain association. This attribute can only be used with vmmDomain domain association.
+ type: str
+ enhanced_lagpolicy_name:
+ description:
+ - EPG enhanced lagpolicy name. This attribute can only be used with vmmDomain domain association.
+ type: str
+ enhanced_lagpolicy_dn:
+ description:
+ - Distinguished name of EPG lagpolicy. This attribute can only be used with vmmDomain domain association.
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+notes:
+- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index.
+ This can cause silent corruption on concurrent access when changing/removing on object as
+ the wrong object may be referenced. This module is affected by this deficiency.
+seealso:
+- module: cisco.mso.mso_schema_site_anp_epg
+- module: cisco.mso.mso_schema_template_anp_epg
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new domain to a site EPG
+ cisco.mso.mso_schema_site_anp_epg_domain:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ domain_association_type: vmmDomain
+ domain_profile: 'VMware-VMM'
+ deployment_immediacy: lazy
+ resolution_immediacy: pre-provision
+ state: present
+ delegate_to: localhost
+
+- name: Remove a domain from a site EPG
+ cisco.mso.mso_schema_site_anp_epg_domain:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ domain_association_type: vmmDomain
+ domain_profile: 'VMware-VMM'
+ deployment_immediacy: lazy
+ resolution_immediacy: pre-provision
+ state: absent
+ delegate_to: localhost
+
+- name: Query a domain associated with a specific site EPG
+ cisco.mso.mso_schema_site_anp_epg_domain:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ domain_association_type: vmmDomain
+ domain_profile: 'VMware-VMM'
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all domains associated with a site EPG
+ cisco.mso.mso_schema_site_anp_epg_domain:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ site=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ anp=dict(type="str", required=True),
+ epg=dict(type="str", required=True),
+ domain_association_type=dict(type="str", choices=["vmmDomain", "l3ExtDomain", "l2ExtDomain", "physicalDomain", "fibreChannelDomain"]),
+ domain_profile=dict(type="str"),
+ deployment_immediacy=dict(type="str", choices=["immediate", "lazy"]),
+ resolution_immediacy=dict(type="str", choices=["immediate", "lazy", "pre-provision"]),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ micro_seg_vlan_type=dict(type="str"),
+ micro_seg_vlan=dict(type="int"),
+ port_encap_vlan_type=dict(type="str"),
+ port_encap_vlan=dict(type="int"),
+ vlan_encap_mode=dict(type="str", choices=["static", "dynamic"]),
+ allow_micro_segmentation=dict(type="bool"),
+ switch_type=dict(type="str"),
+ switching_mode=dict(type="str"),
+ enhanced_lagpolicy_name=dict(type="str"),
+ enhanced_lagpolicy_dn=dict(type="str"),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["domain_association_type", "domain_profile", "deployment_immediacy", "resolution_immediacy"]],
+ ["state", "present", ["domain_association_type", "domain_profile", "deployment_immediacy", "resolution_immediacy"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ site = module.params.get("site")
+ template = module.params.get("template").replace(" ", "")
+ anp = module.params.get("anp")
+ epg = module.params.get("epg")
+ domain_association_type = module.params.get("domain_association_type")
+ domain_profile = module.params.get("domain_profile")
+ deployment_immediacy = module.params.get("deployment_immediacy")
+ resolution_immediacy = module.params.get("resolution_immediacy")
+ state = module.params.get("state")
+ micro_seg_vlan_type = module.params.get("micro_seg_vlan_type")
+ micro_seg_vlan = module.params.get("micro_seg_vlan")
+ port_encap_vlan_type = module.params.get("port_encap_vlan_type")
+ port_encap_vlan = module.params.get("port_encap_vlan")
+ vlan_encap_mode = module.params.get("vlan_encap_mode")
+ allow_micro_segmentation = module.params.get("allow_micro_segmentation")
+ switch_type = module.params.get("switch_type")
+ switching_mode = module.params.get("switching_mode")
+ enhanced_lagpolicy_name = module.params.get("enhanced_lagpolicy_name")
+ enhanced_lagpolicy_dn = module.params.get("enhanced_lagpolicy_dn")
+
+ mso = MSOModule(module)
+
+ # Get schema objects
+ schema_id, schema_path, schema_obj = mso.query_schema(schema)
+
+ # Get template
+ templates = [t.get("name") for t in schema_obj.get("templates")]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates)))
+ template_idx = templates.index(template)
+
+ # Get site
+ site_id = mso.lookup_site(site)
+
+ # Get site_idx
+ if not schema_obj.get("sites"):
+ mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template))
+ sites = [(s.get("siteId"), s.get("templateName")) for s in schema_obj.get("sites")]
+ sites_list = [s.get("siteId") + "/" + s.get("templateName") for s in schema_obj.get("sites")]
+ if (site_id, template) not in sites:
+ mso.fail_json(
+ msg="Provided site/siteId/template '{0}/{1}/{2}' does not exist. "
+ "Existing siteIds/templates: {3}".format(site, site_id, template, ", ".join(sites_list))
+ )
+
+ # Schema-access uses indexes
+ site_idx = sites.index((site_id, template))
+ # Path-based access uses site_id-template
+ site_template = "{0}-{1}".format(site_id, template)
+
+ payload = dict()
+ ops = []
+ op_path = ""
+
+ # Get ANP
+ anp_ref = mso.anp_ref(schema_id=schema_id, template=template, anp=anp)
+ anps = [a.get("anpRef") for a in schema_obj["sites"][site_idx]["anps"]]
+ anps_in_temp = [a.get("name") for a in schema_obj["templates"][template_idx]["anps"]]
+ if anp not in anps_in_temp:
+ mso.fail_json(msg="Provided anp '{0}' does not exist. Existing anps: {1}".format(anp, ", ".join(anps)))
+ else:
+ # Update anp index at template level
+ template_anp_idx = anps_in_temp.index(anp)
+
+ # If anp not at site level but exists at template level
+ if anp_ref not in anps:
+ op_path = "/sites/{0}/anps/-".format(site_template)
+ payload.update(
+ anpRef=dict(
+ schemaId=schema_id,
+ templateName=template,
+ anpName=anp,
+ ),
+ )
+
+ else:
+ # Update anp index at site level
+ anp_idx = anps.index(anp_ref)
+
+ # Get EPG
+ epg_ref = mso.epg_ref(schema_id=schema_id, template=template, anp=anp, epg=epg)
+
+ # If anp exists at site level
+ if "anpRef" not in payload:
+ epgs = [e.get("epgRef") for e in schema_obj["sites"][site_idx]["anps"][anp_idx]["epgs"]]
+
+ # If anp already at site level AND if epg not at site level (or) anp not at site level?
+ if ("anpRef" not in payload and epg_ref not in epgs) or "anpRef" in payload:
+ epgs_in_temp = [e.get("name") for e in schema_obj["templates"][template_idx]["anps"][template_anp_idx]["epgs"]]
+
+ # If EPG not at template level - Fail
+ if epg not in epgs_in_temp:
+ mso.fail_json(msg="Provided EPG '{0}' does not exist. Existing EPGs: {1} epgref {2}".format(epg, ", ".join(epgs_in_temp), epg_ref))
+
+ # EPG at template level but not at site level. Create payload at site level for EPG
+ else:
+ new_epg = dict(
+ epgRef=dict(
+ schemaId=schema_id,
+ templateName=template,
+ anpName=anp,
+ epgName=epg,
+ )
+ )
+
+ # If anp not in payload then, anp already exists at site level. New payload will only have new EPG payload
+ if "anpRef" not in payload:
+ op_path = "/sites/{0}/anps/{1}/epgs/-".format(site_template, anp)
+ payload = new_epg
+ else:
+ # If anp in payload, anp exists at site level. Update payload with EPG payload
+ payload["epgs"] = [new_epg]
+
+ # Update index of EPG at site level
+ else:
+ epg_idx = epgs.index(epg_ref)
+
+ if domain_association_type == "vmmDomain":
+ domain_dn = "uni/vmmp-VMware/dom-{0}".format(domain_profile)
+ elif domain_association_type == "l3ExtDomain":
+ domain_dn = "uni/l3dom-{0}".format(domain_profile)
+ elif domain_association_type == "l2ExtDomain":
+ domain_dn = "uni/l2dom-{0}".format(domain_profile)
+ elif domain_association_type == "physicalDomain":
+ domain_dn = "uni/phys-{0}".format(domain_profile)
+ elif domain_association_type == "fibreChannelDomain":
+ domain_dn = "uni/fc-{0}".format(domain_profile)
+ else:
+ domain_dn = ""
+
+ # Get Domains
+ # If anp at site level and epg is at site level
+ if "anpRef" not in payload and "epgRef" not in payload:
+ domains = [dom.get("dn") for dom in schema_obj["sites"][site_idx]["anps"][anp_idx]["epgs"][epg_idx]["domainAssociations"]]
+ if domain_dn in domains:
+ domain_idx = domains.index(domain_dn)
+ domain_path = "/sites/{0}/anps/{1}/epgs/{2}/domainAssociations/{3}".format(site_template, anp, epg, domain_idx)
+ mso.existing = schema_obj["sites"][site_idx]["anps"][anp_idx]["epgs"][epg_idx]["domainAssociations"][domain_idx]
+
+ if state == "query":
+ if domain_association_type is None or domain_profile is None:
+ mso.existing = schema_obj.get("sites")[site_idx]["anps"][anp_idx]["epgs"][epg_idx]["domainAssociations"]
+ elif not mso.existing:
+ mso.fail_json(
+ msg="Domain association '{domain_association_type}/{domain_profile}' not found".format(
+ domain_association_type=domain_association_type, domain_profile=domain_profile
+ )
+ )
+ mso.exit_json()
+
+ domains_path = "/sites/{0}/anps/{1}/epgs/{2}/domainAssociations".format(site_template, anp, epg)
+ ops = []
+ new_domain = dict(
+ dn=domain_dn,
+ domainType=domain_association_type,
+ deploymentImmediacy=deployment_immediacy, # keeping for backworths compatibility
+ deployImmediacy=deployment_immediacy, # rename of deploymentImmediacy
+ resolutionImmediacy=resolution_immediacy,
+ )
+
+ if domain_association_type == "vmmDomain":
+ vmmDomainProperties = {}
+ if micro_seg_vlan_type and micro_seg_vlan:
+ microSegVlan = dict(vlanType=micro_seg_vlan_type, vlan=micro_seg_vlan)
+ vmmDomainProperties["microSegVlan"] = microSegVlan
+ elif not micro_seg_vlan_type and micro_seg_vlan:
+ mso.fail_json(msg="micro_seg_vlan_type is required when micro_seg_vlan is provided.")
+ elif micro_seg_vlan_type and not micro_seg_vlan:
+ mso.fail_json(msg="micro_seg_vlan is required when micro_seg_vlan_type is provided.")
+
+ if port_encap_vlan_type and port_encap_vlan:
+ portEncapVlan = dict(vlanType=port_encap_vlan_type, vlan=port_encap_vlan)
+ vmmDomainProperties["portEncapVlan"] = portEncapVlan
+ elif not port_encap_vlan_type and port_encap_vlan:
+ mso.fail_json(msg="port_encap_vlan_type is required when port_encap_vlan is provided.")
+ elif port_encap_vlan_type and not port_encap_vlan:
+ mso.fail_json(msg="port_encap_vlan is required when port_encap_vlan_type is provided.")
+
+ if vlan_encap_mode:
+ vmmDomainProperties["vlanEncapMode"] = vlan_encap_mode
+
+ if allow_micro_segmentation:
+ vmmDomainProperties["allowMicroSegmentation"] = allow_micro_segmentation
+ if switch_type:
+ vmmDomainProperties["switchType"] = switch_type
+ if switching_mode:
+ vmmDomainProperties["switchingMode"] = switching_mode
+
+ if enhanced_lagpolicy_name and enhanced_lagpolicy_dn:
+ enhancedLagPol = dict(name=enhanced_lagpolicy_name, dn=enhanced_lagpolicy_dn)
+ epgLagPol = dict(enhancedLagPol=enhancedLagPol)
+ vmmDomainProperties["epgLagPol"] = epgLagPol
+ elif not enhanced_lagpolicy_name and enhanced_lagpolicy_dn:
+ mso.fail_json(msg="enhanced_lagpolicy_name is required when enhanced_lagpolicy_dn is provided.")
+ elif enhanced_lagpolicy_name and not enhanced_lagpolicy_dn:
+ mso.fail_json(msg="enhanced_lagpolicy_dn is required when enhanced_lagpolicy_name is provided.")
+
+ if vmmDomainProperties:
+ new_domain["vmmDomainProperties"] = vmmDomainProperties
+ properties = ["allowMicroSegmentation", "epgLagPol", "switchType", "switchingMode", "vlanEncapMode", "portEncapVlan", "microSegVlan"]
+ for property in properties:
+ if property in vmmDomainProperties:
+ new_domain[property] = vmmDomainProperties[property]
+
+ # If payload is empty, anp and EPG already exist at site level
+ if not payload:
+ op_path = domains_path + "/-"
+ payload = new_domain
+
+ # If payload exists
+ else:
+ # If anp already exists at site level...(AND payload != epg as well?)
+ if "anpRef" not in payload:
+ payload["domainAssociations"] = [new_domain]
+ else:
+ payload["epgs"][0]["domainAssociations"] = [new_domain]
+
+ mso.previous = mso.existing
+ if state == "absent":
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op="remove", path=domain_path))
+ elif state == "present":
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op="replace", path=domain_path, value=mso.sent))
+ else:
+ ops.append(dict(op="add", path=op_path, value=mso.sent))
+
+ mso.existing = new_domain
+
+ if not module.check_mode:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_selector.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_selector.py
new file mode 100644
index 000000000..ffd3c682e
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_selector.py
@@ -0,0 +1,392 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_site_anp_epg_selector
+short_description: Manage site-local EPG selector in schema templates
+description:
+- Manage EPG selector in schema template on Cisco ACI Multi-Site.
+author:
+- Cindy Zhao (@cizhao)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ site:
+ description:
+ - The name of the site.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: true
+ anp:
+ description:
+ - The name of the ANP.
+ type: str
+ required: true
+ epg:
+ description:
+ - The name of the EPG to manage.
+ type: str
+ required: true
+ selector:
+ description:
+ - The name of the selector.
+ type: str
+ expressions:
+ description:
+ - Expressions associated to this selector.
+ type: list
+ elements: dict
+ suboptions:
+ type:
+ description:
+ - The type of the expression.
+ - The type is custom or is one of region, zone and ip_address
+ - The type can be zone only when the site is AWS.
+ required: true
+ type: str
+ aliases: [ tag ]
+ operator:
+ description:
+ - The operator associated to the expression.
+ - Operator has_key or does_not_have_key is only available for custom type / tag
+ required: true
+ type: str
+ choices: [ not_in, in, equals, not_equals, has_key, does_not_have_key ]
+ value:
+ description:
+ - The value associated to the expression.
+ - If the operator is in or not_in, the value should be a comma separated string.
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+seealso:
+- module: cisco.mso.mso_schema_site_anp_epg
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a selector to a site EPG
+ cisco.mso.mso_schema_site_anp_epg_selector:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ site: Site 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ selector: selector_1
+ expressions:
+ - type: expression_1
+ operator: in
+ value: test
+ state: present
+ delegate_to: localhost
+
+- name: Remove a Selector from a site EPG
+ cisco.mso.mso_schema_site_anp_epg_selector:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ site: Site 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ selector: selector_1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific Selector
+ cisco.mso.mso_schema_site_anp_epg_selector:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ site: Site 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ selector: selector_1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all Selectors
+ cisco.mso.mso_schema_site_anp_epg_selector:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ site: Site 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_expression_spec
+
+EXPRESSION_KEYS = {
+ "ip_address": "ipAddress",
+ "region": "region",
+ "zone": "zone",
+}
+
+EXPRESSION_OPERATORS = {
+ "not_in": "notIn",
+ "not_equals": "notEquals",
+ "has_key": "keyExist",
+ "does_not_have_key": "keyNotExist",
+ "in": "in",
+ "equals": "equals",
+}
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ site=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ anp=dict(type="str", required=True),
+ epg=dict(type="str", required=True),
+ selector=dict(type="str"),
+ expressions=dict(type="list", elements="dict", options=mso_expression_spec()),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["selector"]],
+ ["state", "present", ["selector"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ site = module.params.get("site")
+ template = module.params.get("template").replace(" ", "")
+ anp = module.params.get("anp")
+ epg = module.params.get("epg")
+ selector = module.params.get("selector")
+ expressions = module.params.get("expressions")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ # Get schema objects
+ schema_id, schema_path, schema_obj = mso.query_schema(schema)
+
+ # Get template
+ templates = [t.get("name") for t in schema_obj.get("templates")]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates)))
+ template_idx = templates.index(template)
+
+ # Get site
+ site_id = mso.lookup_site(site)
+
+ # Get cloud type
+ site_type = mso.get_obj("sites", name=site).get("cloudProviders")[0]
+
+ # Get site_idx
+ if not schema_obj.get("sites"):
+ mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template))
+ sites = [(s.get("siteId"), s.get("templateName")) for s in schema_obj.get("sites")]
+ if (site_id, template) not in sites:
+ mso.fail_json(msg="Provided site-template association '{0}-{1}' does not exist.".format(site, template))
+
+ # Schema-access uses indexes
+ site_idx = sites.index((site_id, template))
+ # Path-based access uses site_id-template
+ site_template = "{0}-{1}".format(site_id, template)
+
+ payload = dict()
+ ops = []
+ op_path = ""
+
+ # Get ANP
+ anp_ref = mso.anp_ref(schema_id=schema_id, template=template, anp=anp)
+ anps = [a.get("anpRef") for a in schema_obj["sites"][site_idx]["anps"]]
+ anps_in_temp = [a.get("name") for a in schema_obj["templates"][template_idx]["anps"]]
+ if anp not in anps_in_temp:
+ mso.fail_json(msg="Provided anp '{0}' does not exist. Existing anps: {1}".format(anp, ", ".join(anps_in_temp)))
+ else:
+ # Get anp index at template level
+ template_anp_idx = anps_in_temp.index(anp)
+
+ # If anp not at site level but exists at template level
+ if anp_ref not in anps:
+ op_path = "/sites/{0}/anps/-".format(site_template)
+ payload.update(
+ anpRef=dict(
+ schemaId=schema_id,
+ templateName=template,
+ anpName=anp,
+ ),
+ )
+
+ else:
+ # Get anp index at site level
+ anp_idx = anps.index(anp_ref)
+
+ # Get EPG
+ epg_ref = mso.epg_ref(schema_id=schema_id, template=template, anp=anp, epg=epg)
+
+ # If anp exists at site level
+ if "anpRef" not in payload:
+ epgs = [e.get("epgRef") for e in schema_obj["sites"][site_idx]["anps"][anp_idx]["epgs"]]
+
+ # If anp already at site level AND if epg not at site level (or) anp not at site level?
+ if ("anpRef" not in payload and epg_ref not in epgs) or "anpRef" in payload:
+ epgs_in_temp = [e.get("name") for e in schema_obj["templates"][template_idx]["anps"][template_anp_idx]["epgs"]]
+
+ # If EPG not at template level - Fail
+ if epg not in epgs_in_temp:
+ mso.fail_json(msg="Provided EPG '{0}' does not exist. Existing EPGs: {1}".format(epg, ", ".join(epgs_in_temp)))
+
+ # EPG at template level but not at site level. Create payload at site level for EPG
+ else:
+ new_epg = dict(
+ epgRef=dict(
+ schemaId=schema_id,
+ templateName=template,
+ anpName=anp,
+ epgName=epg,
+ )
+ )
+
+ # If anp not in payload then, anp already exists at site level. New payload will only have new EPG payload
+ if "anpRef" not in payload:
+ op_path = "/sites/{0}/anps/{1}/epgs/-".format(site_template, anp)
+ payload = new_epg
+ else:
+ # If anp in payload, anp exists at site level. Update payload with EPG payload
+ payload["epgs"] = [new_epg]
+
+ # Get index of EPG at site level
+ else:
+ epg_idx = epgs.index(epg_ref)
+
+ # Get selectors
+ # If anp at site level and epg is at site level
+ if "anpRef" not in payload and "epgRef" not in payload:
+ if selector and " " in selector:
+ mso.fail_json(msg="There should not be any space in selector name.")
+ selectors = [s.get("name") for s in schema_obj.get("sites")[site_idx]["anps"][anp_idx]["epgs"][epg_idx]["selectors"]]
+ if selector in selectors:
+ selector_idx = selectors.index(selector)
+ selector_path = "/sites/{0}/anps/{1}/epgs/{2}/selectors/{3}".format(site_template, anp, epg, selector_idx)
+ mso.existing = schema_obj["sites"][site_idx]["anps"][anp_idx]["epgs"][epg_idx]["selectors"][selector_idx]
+
+ if state == "query":
+ if "anpRef" in payload:
+ mso.fail_json(msg="Anp '{anp}' does not exist in site level.".format(anp=anp))
+ if "epgRef" in payload:
+ mso.fail_json(msg="Epg '{epg}' does not exist in site level.".format(epg=epg))
+ if selector is None:
+ mso.existing = schema_obj["sites"][site_idx]["anps"][anp_idx]["epgs"][epg_idx]["selectors"]
+ elif not mso.existing:
+ mso.fail_json(msg="Selector '{selector}' not found".format(selector=selector))
+ mso.exit_json()
+
+ mso.previous = mso.existing
+ if state == "absent":
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op="remove", path=selector_path))
+ elif state == "present":
+ # Get expressions
+ all_expressions = []
+ if expressions:
+ for expression in expressions:
+ type = expression.get("type")
+ operator = expression.get("operator")
+ value = expression.get("value")
+ if " " in type:
+ mso.fail_json(msg="There should not be any space in 'type' attribute of expression '{0}'".format(type))
+ if operator in ["has_key", "does_not_have_key"] and value:
+ mso.fail_json(msg="Attribute 'value' is not supported for operator '{0}' in expression '{1}'".format(operator, type))
+ if operator in ["not_in", "in", "equals", "not_equals"] and not value:
+ mso.fail_json(msg="Attribute 'value' needed for operator '{0}' in expression '{1}'".format(operator, type))
+ if type in ["region", "zone", "ip_address"]:
+ if type == "zone" and site_type != "aws":
+ mso.fail_json(msg="Type 'zone' is only supported for aws")
+ if operator in ["has_key", "does_not_have_key"]:
+ mso.fail_json(msg="Operator '{0}' is not supported when expression type is '{1}'".format(operator, type))
+ type = EXPRESSION_KEYS.get(type)
+ else:
+ type = "Custom:" + type
+ all_expressions.append(
+ dict(
+ key=type,
+ operator=EXPRESSION_OPERATORS.get(operator),
+ value=value,
+ )
+ )
+ new_selector = dict(
+ name=selector,
+ expressions=all_expressions,
+ )
+
+ selectors_path = "/sites/{0}/anps/{1}/epgs/{2}/selectors/-".format(site_template, anp, epg)
+
+ # if payload is empty, anp and epg already exist at site level
+ if not payload:
+ op_path = selectors_path
+ payload = new_selector
+ # if payload exist
+ else:
+ # if anp already exists at site level
+ if "anpRef" not in payload:
+ payload["selectors"] = [new_selector]
+ else:
+ payload["epgs"][0]["selectors"] = [new_selector]
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op="replace", path=selector_path, value=mso.sent))
+ else:
+ ops.append(dict(op="add", path=op_path, value=mso.sent))
+
+ mso.existing = new_selector
+
+ if not module.check_mode and mso.existing != mso.previous:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_staticleaf.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_staticleaf.py
new file mode 100644
index 000000000..01e2ac386
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_staticleaf.py
@@ -0,0 +1,258 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_site_anp_epg_staticleaf
+short_description: Manage site-local EPG static leafs in schema template
+description:
+- Manage site-local EPG static leafs in schema template on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ site:
+ description:
+ - The name of the site.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: true
+ anp:
+ description:
+ - The name of the ANP.
+ type: str
+ required: true
+ epg:
+ description:
+ - The name of the EPG.
+ type: str
+ required: true
+ pod:
+ description:
+ - The pod of the static leaf.
+ type: str
+ leaf:
+ description:
+ - The path of the static leaf.
+ type: str
+ aliases: [ name ]
+ vlan:
+ description:
+ - The VLAN id of the static leaf.
+ type: int
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+notes:
+- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index.
+ This can cause silent corruption on concurrent access when changing/removing on object as
+ the wrong object may be referenced. This module is affected by this deficiency.
+seealso:
+- module: cisco.mso.mso_schema_site_anp_epg
+- module: cisco.mso.mso_schema_template_anp_epg
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new static leaf to a site EPG
+ cisco.mso.mso_schema_site_anp_epg_staticleaf:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ leaf: Leaf1
+ vlan: 123
+ state: present
+ delegate_to: localhost
+
+- name: Remove a static leaf from a site EPG
+ cisco.mso.mso_schema_site_anp_epg_staticleaf:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ leaf: Leaf1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific site EPG static leaf
+ cisco.mso.mso_schema_site_anp_epg_staticleaf:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ leaf: Leaf1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all site EPG static leafs
+ cisco.mso.mso_schema_site_anp_epg_staticleaf:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ site=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ anp=dict(type="str", required=True),
+ epg=dict(type="str", required=True),
+ pod=dict(type="str"), # This parameter is not required for querying all objects
+ leaf=dict(type="str", aliases=["name"]),
+ vlan=dict(type="int"),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["pod", "leaf", "vlan"]],
+ ["state", "present", ["pod", "leaf", "vlan"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ site = module.params.get("site")
+ template = module.params.get("template").replace(" ", "")
+ anp = module.params.get("anp")
+ epg = module.params.get("epg")
+ pod = module.params.get("pod")
+ leaf = module.params.get("leaf")
+ vlan = module.params.get("vlan")
+ state = module.params.get("state")
+
+ leafpath = "topology/{0}/node-{1}".format(pod, leaf)
+
+ mso = MSOModule(module)
+
+ # Get schema objects
+ schema_id, schema_path, schema_obj = mso.query_schema(schema)
+
+ # Get site
+ site_id = mso.lookup_site(site)
+
+ # Get site_idx
+ if not schema_obj.get("sites"):
+ mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template))
+ sites = [(s.get("siteId"), s.get("templateName")) for s in schema_obj.get("sites")]
+ if (site_id, template) not in sites:
+ mso.fail_json(msg="Provided site/template '{0}-{1}' does not exist. Existing sites/templates: {2}".format(site, template, ", ".join(sites)))
+
+ # Schema-access uses indexes
+ site_idx = sites.index((site_id, template))
+ # Path-based access uses site_id-template
+ site_template = "{0}-{1}".format(site_id, template)
+
+ # Get ANP
+ anp_ref = mso.anp_ref(schema_id=schema_id, template=template, anp=anp)
+ anps = [a.get("anpRef") for a in schema_obj.get("sites")[site_idx]["anps"]]
+ if anp_ref not in anps:
+ mso.fail_json(msg="Provided anp '{0}' does not exist. Existing anps: {1}".format(anp, ", ".join(anps)))
+ anp_idx = anps.index(anp_ref)
+
+ # Get EPG
+ epg_ref = mso.epg_ref(schema_id=schema_id, template=template, anp=anp, epg=epg)
+ epgs = [e.get("epgRef") for e in schema_obj.get("sites")[site_idx]["anps"][anp_idx]["epgs"]]
+ if epg_ref not in epgs:
+ mso.fail_json(msg="Provided epg '{0}' does not exist. Existing epgs: {1}".format(epg, ", ".join(epgs)))
+ epg_idx = epgs.index(epg_ref)
+
+ # Get Leaf
+ leafs = [(leaf.get("path"), leaf.get("portEncapVlan")) for leaf in schema_obj.get("sites")[site_idx]["anps"][anp_idx]["epgs"][epg_idx]["staticLeafs"]]
+ if (leafpath, vlan) in leafs:
+ leaf_idx = leafs.index((leafpath, vlan))
+ # FIXME: Changes based on index are DANGEROUS
+ leaf_path = "/sites/{0}/anps/{1}/epgs/{2}/staticLeafs/{3}".format(site_template, anp, epg, leaf_idx)
+ mso.existing = schema_obj.get("sites")[site_idx]["anps"][anp_idx]["epgs"][epg_idx]["staticLeafs"][leaf_idx]
+
+ if state == "query":
+ if leaf is None or vlan is None:
+ mso.existing = schema_obj.get("sites")[site_idx]["anps"][anp_idx]["epgs"][epg_idx]["staticLeafs"]
+ elif not mso.existing:
+ mso.fail_json(msg="Static leaf '{leaf}/{vlan}' not found".format(leaf=leaf, vlan=vlan))
+ mso.exit_json()
+
+ leafs_path = "/sites/{0}/anps/{1}/epgs/{2}/staticLeafs".format(site_template, anp, epg)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == "absent":
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op="remove", path=leaf_path))
+
+ elif state == "present":
+ payload = dict(
+ path=leafpath,
+ portEncapVlan=vlan,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op="replace", path=leaf_path, value=mso.sent))
+ else:
+ ops.append(dict(op="add", path=leafs_path + "/-", value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_staticport.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_staticport.py
new file mode 100644
index 000000000..d05336c52
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_staticport.py
@@ -0,0 +1,444 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_site_anp_epg_staticport
+short_description: Manage site-local EPG static ports in schema template
+description:
+- Manage site-local EPG static ports in schema template on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ site:
+ description:
+ - The name of the site.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: true
+ anp:
+ description:
+ - The name of the ANP.
+ type: str
+ required: true
+ epg:
+ description:
+ - The name of the EPG.
+ type: str
+ required: true
+ type:
+ description:
+ - The path type of the static port
+ - vpc is used for a Virtual Port Channel
+ - dpc is used for a Direct Port Channel
+ - port is used for a single interface
+ type: str
+ choices: [ port, vpc, dpc ]
+ default: port
+ pod:
+ description:
+ - The pod of the static port.
+ type: str
+ leaf:
+ description:
+ - The leaf of the static port.
+ type: str
+ fex:
+ description:
+ - The fex id of the static port.
+ type: str
+ path:
+ description:
+ - The path of the static port.
+ type: str
+ vlan:
+ description:
+ - The port encap VLAN id of the static port.
+ type: int
+ deployment_immediacy:
+ description:
+ - The deployment immediacy of the static port.
+ - C(immediate) means B(Deploy immediate).
+ - C(lazy) means B(deploy on demand).
+ type: str
+ choices: [ immediate, lazy ]
+ default: lazy
+ mode:
+ description:
+ - The mode of the static port.
+ - C(native) means B(Access (802.1p)).
+ - C(regular) means B(Trunk).
+ - C(untagged) means B(Access (untagged)).
+ type: str
+ choices: [ native, regular, untagged ]
+ default: untagged
+ primary_micro_segment_vlan:
+ description:
+ - Primary micro-seg VLAN of static port.
+ type: int
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+notes:
+- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index.
+ This can cause silent corruption on concurrent access when changing/removing an object as
+ the wrong object may be referenced. This module is affected by this deficiency.
+seealso:
+- module: cisco.mso.mso_schema_site_anp_epg
+- module: cisco.mso.mso_schema_template_anp_epg
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new static port to a site EPG
+ cisco.mso.mso_schema_site_anp_epg_staticport:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ type: port
+ pod: pod-1
+ leaf: 101
+ path: eth1/1
+ vlan: 126
+ deployment_immediacy: immediate
+ state: present
+ delegate_to: localhost
+
+- name: Add a new static fex port to a site EPG
+ mso_schema_site_anp_epg_staticport:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ type: port
+ pod: pod-1
+ leaf: 101
+ fex: 151
+ path: eth1/1
+ vlan: 126
+ deployment_immediacy: lazy
+ state: present
+ delegate_to: localhost
+
+- name: Add a new static VPC to a site EPG
+ mso_schema_site_anp_epg_staticport:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ pod: pod-1
+ leaf: 101-102
+ path: ansible_polgrp
+ vlan: 127
+ type: vpc
+ mode: untagged
+ deployment_immediacy: lazy
+ state: present
+ delegate_to: localhost
+
+- name: Remove a static port from a site EPG
+ cisco.mso.mso_schema_site_anp_epg_staticport:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ type: port
+ pod: pod-1
+ leaf: 101
+ path: eth1/1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific site EPG static port
+ cisco.mso.mso_schema_site_anp_epg_staticport:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ type: port
+ pod: pod-1
+ leaf: 101
+ path: eth1/1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all site EPG static ports
+ cisco.mso.mso_schema_site_anp_epg_staticport:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ site=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ anp=dict(type="str", required=True),
+ epg=dict(type="str", required=True),
+ type=dict(type="str", default="port", choices=["port", "vpc", "dpc"]),
+ pod=dict(type="str"), # This parameter is not required for querying all objects
+ leaf=dict(type="str"), # This parameter is not required for querying all objects
+ fex=dict(type="str"), # This parameter is not required for querying all objects
+ path=dict(type="str"), # This parameter is not required for querying all objects
+ vlan=dict(type="int"), # This parameter is not required for querying all objects
+ primary_micro_segment_vlan=dict(type="int"), # This parameter is not required for querying all objects
+ deployment_immediacy=dict(type="str", default="lazy", choices=["immediate", "lazy"]),
+ mode=dict(type="str", default="untagged", choices=["native", "regular", "untagged"]),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["type", "pod", "leaf", "path", "vlan"]],
+ ["state", "present", ["type", "pod", "leaf", "path", "vlan"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ site = module.params.get("site")
+ template = module.params.get("template").replace(" ", "")
+ anp = module.params.get("anp")
+ epg = module.params.get("epg")
+ path_type = module.params.get("type")
+ pod = module.params.get("pod")
+ leaf = module.params.get("leaf")
+ fex = module.params.get("fex")
+ path = module.params.get("path")
+ vlan = module.params.get("vlan")
+ primary_micro_segment_vlan = module.params.get("primary_micro_segment_vlan")
+ deployment_immediacy = module.params.get("deployment_immediacy")
+ mode = module.params.get("mode")
+ state = module.params.get("state")
+
+ if path_type == "port" and fex is not None:
+ # Select port path for fex if fex param is used
+ portpath = "topology/{0}/paths-{1}/extpaths-{2}/pathep-[{3}]".format(pod, leaf, fex, path)
+ elif path_type == "vpc":
+ portpath = "topology/{0}/protpaths-{1}/pathep-[{2}]".format(pod, leaf, path)
+ else:
+ portpath = "topology/{0}/paths-{1}/pathep-[{2}]".format(pod, leaf, path)
+
+ mso = MSOModule(module)
+
+ # Get schema objects
+ schema_id, schema_path, schema_obj = mso.query_schema(schema)
+
+ # Get template
+ templates = [t.get("name") for t in schema_obj.get("templates")]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates)))
+ template_idx = templates.index(template)
+
+ # Get site
+ site_id = mso.lookup_site(site)
+
+ # Get site_idx
+ if not schema_obj.get("sites"):
+ mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template))
+ sites = [(s.get("siteId"), s.get("templateName")) for s in schema_obj.get("sites")]
+ sites_list = [s.get("siteId") + "/" + s.get("templateName") for s in schema_obj.get("sites")]
+ if (site_id, template) not in sites:
+ mso.fail_json(
+ msg="Provided site/siteId/template '{0}/{1}/{2}' does not exist. "
+ "Existing siteIds/templates: {3}".format(site, site_id, template, ", ".join(sites_list))
+ )
+
+ # Schema-access uses indexes
+ site_idx = sites.index((site_id, template))
+ # Path-based access uses site_id-template
+ site_template = "{0}-{1}".format(site_id, template)
+
+ payload = dict()
+ ops = []
+ op_path = ""
+
+ # Get ANP
+ anp_ref = mso.anp_ref(schema_id=schema_id, template=template, anp=anp)
+ anps = [a.get("anpRef") for a in schema_obj["sites"][site_idx]["anps"]]
+ anps_in_temp = [a.get("name") for a in schema_obj["templates"][template_idx]["anps"]]
+ if anp not in anps_in_temp:
+ mso.fail_json(msg="Provided anp '{0}' does not exist. Existing anps: {1}".format(anp, ", ".join(anps)))
+ else:
+ # Update anp index at template level
+ template_anp_idx = anps_in_temp.index(anp)
+
+ # If anp not at site level but exists at template level
+ if anp_ref not in anps:
+ op_path = "/sites/{0}/anps/-".format(site_template)
+ payload.update(
+ anpRef=dict(
+ schemaId=schema_id,
+ templateName=template,
+ anpName=anp,
+ ),
+ )
+
+ else:
+ # Update anp index at site level
+ anp_idx = anps.index(anp_ref)
+
+ # Get EPG
+ epg_ref = mso.epg_ref(schema_id=schema_id, template=template, anp=anp, epg=epg)
+
+ # If anp exists at site level
+ if "anpRef" not in payload:
+ epgs = [e.get("epgRef") for e in schema_obj["sites"][site_idx]["anps"][anp_idx]["epgs"]]
+
+ # If anp already at site level AND if epg not at site level (or) anp not at site level
+ if ("anpRef" not in payload and epg_ref not in epgs) or "anpRef" in payload:
+ epgs_in_temp = [e.get("name") for e in schema_obj["templates"][template_idx]["anps"][template_anp_idx]["epgs"]]
+
+ # If EPG not at template level - Fail
+ if epg not in epgs_in_temp:
+ mso.fail_json(msg="Provided EPG '{0}' does not exist. Existing EPGs: {1} epgref {2}".format(epg, ", ".join(epgs_in_temp), epg_ref))
+
+ # EPG at template level but not at site level. Create payload at site level for EPG
+ else:
+ new_epg = dict(
+ epgRef=dict(
+ schemaId=schema_id,
+ templateName=template,
+ anpName=anp,
+ epgName=epg,
+ )
+ )
+
+ # If anp not in payload then, anp already exists at site level. New payload will only have new EPG payload
+ if "anpRef" not in payload:
+ op_path = "/sites/{0}/anps/{1}/epgs/-".format(site_template, anp)
+ payload = new_epg
+ else:
+ # If anp in payload, anp exists at site level. Update payload with EPG payload
+ payload["epgs"] = [new_epg]
+
+ # Update index of EPG at site level
+ else:
+ epg_idx = epgs.index(epg_ref)
+
+ # Get Leaf
+ # If anp at site level and epg is at site level
+ if "anpRef" not in payload and "epgRef" not in payload:
+ portpaths = [p.get("path") for p in schema_obj.get("sites")[site_idx]["anps"][anp_idx]["epgs"][epg_idx]["staticPorts"]]
+ if portpath in portpaths:
+ portpath_idx = portpaths.index(portpath)
+ port_path = "/sites/{0}/anps/{1}/epgs/{2}/staticPorts/{3}".format(site_template, anp, epg, portpath_idx)
+ mso.existing = schema_obj.get("sites")[site_idx]["anps"][anp_idx]["epgs"][epg_idx]["staticPorts"][portpath_idx]
+
+ if state == "query":
+ if leaf is None or vlan is None:
+ mso.existing = schema_obj.get("sites")[site_idx]["anps"][anp_idx]["epgs"][epg_idx]["staticPorts"]
+ elif not mso.existing:
+ mso.fail_json(msg="Static port '{portpath}' not found".format(portpath=portpath))
+ mso.exit_json()
+
+ ports_path = "/sites/{0}/anps/{1}/epgs/{2}/staticPorts".format(site_template, anp, epg)
+ ops = []
+ new_leaf = dict(
+ deploymentImmediacy=deployment_immediacy,
+ mode=mode,
+ path=portpath,
+ portEncapVlan=vlan,
+ type=path_type,
+ )
+ if primary_micro_segment_vlan:
+ new_leaf.update(microSegVlan=primary_micro_segment_vlan)
+
+ # If payload is empty, anp and EPG already exist at site level
+ if not payload:
+ op_path = ports_path + "/-"
+ payload = new_leaf
+
+ # If payload exists
+ else:
+ # If anp already exists at site level
+ if "anpRef" not in payload:
+ payload["staticPorts"] = [new_leaf]
+ else:
+ payload["epgs"][0]["staticPorts"] = [new_leaf]
+
+ mso.previous = mso.existing
+ if state == "absent":
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op="remove", path=port_path))
+
+ elif state == "present":
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op="replace", path=port_path, value=mso.sent))
+ else:
+ ops.append(dict(op="add", path=op_path, value=mso.sent))
+
+ mso.existing = new_leaf
+
+ if not module.check_mode:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_subnet.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_subnet.py
new file mode 100644
index 000000000..e5aaeba37
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_subnet.py
@@ -0,0 +1,281 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_site_anp_epg_subnet
+short_description: Manage site-local EPG subnets in schema template
+description:
+- Manage site-local EPG subnets in schema template on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ site:
+ description:
+ - The name of the site.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: true
+ anp:
+ description:
+ - The name of the ANP.
+ type: str
+ required: true
+ epg:
+ description:
+ - The name of the EPG.
+ type: str
+ required: true
+ subnet:
+ description:
+ - The IP range in CIDR notation.
+ type: str
+ required: true
+ aliases: [ ip ]
+ description:
+ description:
+ - The description of this subnet.
+ type: str
+ scope:
+ description:
+ - The scope of the subnet.
+ type: str
+ default: private
+ choices: [ private, public ]
+ shared:
+ description:
+ - Whether this subnet is shared between VRFs.
+ type: bool
+ default: false
+ no_default_gateway:
+ description:
+ - Whether this subnet has a default gateway.
+ type: bool
+ default: false
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+notes:
+- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index.
+ This can cause silent corruption on concurrent access when changing/removing on object as
+ the wrong object may be referenced. This module is affected by this deficiency.
+seealso:
+- module: cisco.mso.mso_schema_site_anp_epg
+- module: cisco.mso.mso_schema_template_anp_epg_subnet
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new subnet to a site EPG
+ cisco.mso.mso_schema_site_anp_epg_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ subnet: 10.0.0.0/24
+ state: present
+ delegate_to: localhost
+
+- name: Remove a subnet from a site EPG
+ cisco.mso.mso_schema_site_anp_epg_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ subnet: 10.0.0.0/24
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific site EPG subnet
+ cisco.mso.mso_schema_site_anp_epg_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ subnet: 10.0.0.0/24
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all site EPG subnets
+ cisco.mso.mso_schema_site_anp_epg_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_epg_subnet_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ site=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ anp=dict(type="str", required=True),
+ epg=dict(type="str", required=True),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+ argument_spec.update(mso_epg_subnet_spec())
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["subnet"]],
+ ["state", "present", ["subnet"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ site = module.params.get("site")
+ template = module.params.get("template").replace(" ", "")
+ anp = module.params.get("anp")
+ epg = module.params.get("epg")
+ subnet = module.params.get("subnet")
+ description = module.params.get("description")
+ scope = module.params.get("scope")
+ shared = module.params.get("shared")
+ no_default_gateway = module.params.get("no_default_gateway")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ # Get schema objects
+ schema_id, schema_path, schema_obj = mso.query_schema(schema)
+
+ # Get site
+ site_id = mso.lookup_site(site)
+
+ # Get site_idx
+ if not schema_obj.get("sites"):
+ mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template))
+ sites = [(s.get("siteId"), s.get("templateName")) for s in schema_obj.get("sites")]
+ if (site_id, template) not in sites:
+ mso.fail_json(msg="Provided site/template '{0}-{1}' does not exist. Existing sites/templates: {2}".format(site, template, ", ".join(sites)))
+
+ # Schema-access uses indexes
+ site_idx = sites.index((site_id, template))
+ # Path-based access uses site_id-template
+ site_template = "{0}-{1}".format(site_id, template)
+
+ # Get ANP
+ anp_ref = mso.anp_ref(schema_id=schema_id, template=template, anp=anp)
+ anps = [a.get("anpRef") for a in schema_obj.get("sites")[site_idx]["anps"]]
+ if anp_ref not in anps:
+ mso.fail_json(msg="Provided anp '{0}' does not exist. Existing anps: {1}".format(anp, ", ".join(anps)))
+ anp_idx = anps.index(anp_ref)
+
+ # Get EPG
+ epg_ref = mso.epg_ref(schema_id=schema_id, template=template, anp=anp, epg=epg)
+ epgs = [e.get("epgRef") for e in schema_obj.get("sites")[site_idx]["anps"][anp_idx]["epgs"]]
+ if epg_ref not in epgs:
+ mso.fail_json(msg="Provided epg '{0}' does not exist. Existing epgs: {1}".format(epg, ", ".join(epgs)))
+ epg_idx = epgs.index(epg_ref)
+
+ # Get Subnet
+ subnets = [s.get("ip") for s in schema_obj.get("sites")[site_idx]["anps"][anp_idx]["epgs"][epg_idx]["subnets"]]
+ if subnet in subnets:
+ subnet_idx = subnets.index(subnet)
+ # FIXME: Changes based on index are DANGEROUS
+ subnet_path = "/sites/{0}/anps/{1}/epgs/{2}/subnets/{3}".format(site_template, anp, epg, subnet_idx)
+ mso.existing = schema_obj.get("sites")[site_idx]["anps"][anp_idx]["epgs"][epg_idx]["subnets"][subnet_idx]
+
+ if state == "query":
+ if subnet is None:
+ mso.existing = schema_obj.get("sites")[site_idx]["anps"][anp_idx]["epgs"][epg_idx]["subnets"]
+ elif not mso.existing:
+ mso.fail_json(msg="Subnet '{subnet}' not found".format(subnet=subnet))
+ mso.exit_json()
+
+ subnets_path = "/sites/{0}/anps/{1}/epgs/{2}/subnets".format(site_template, anp, epg)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == "absent":
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op="remove", path=subnet_path))
+
+ elif state == "present":
+ if not mso.existing:
+ if description is None:
+ description = subnet
+ if scope is None:
+ scope = "private"
+ if shared is None:
+ shared = False
+ if no_default_gateway is None:
+ no_default_gateway = False
+
+ payload = dict(
+ ip=subnet,
+ description=description,
+ scope=scope,
+ shared=shared,
+ noDefaultGateway=no_default_gateway,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op="replace", path=subnet_path, value=mso.sent))
+ else:
+ ops.append(dict(op="add", path=subnets_path + "/-", value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd.py
new file mode 100644
index 000000000..35c352fb6
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd.py
@@ -0,0 +1,236 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_site_bd
+short_description: Manage site-local Bridge Domains (BDs) in schema template
+description:
+- Manage site-local BDs in schema template on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ site:
+ description:
+ - The name of the site.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: true
+ bd:
+ description:
+ - The name of the BD to manage.
+ type: str
+ aliases: [ name ]
+ host_route:
+ description:
+ - Whether host-based routing is enabled.
+ type: bool
+ svi_mac:
+ description:
+ - SVI MAC Address
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+seealso:
+- module: cisco.mso.mso_schema_site
+- module: cisco.mso.mso_schema_site_bd_l3out
+- module: cisco.mso.mso_schema_site_bd_subnet
+- module: cisco.mso.mso_schema_template_bd
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new site BD
+ cisco.mso.mso_schema_site_bd:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ bd: BD1
+ state: present
+ delegate_to: localhost
+
+- name: Remove a site BD
+ cisco.mso.mso_schema_site_bd:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ bd: BD1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific site BD
+ cisco.mso.mso_schema_site_bd:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ bd: BD1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all site BDs
+ cisco.mso.mso_schema_site_bd:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ site=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ bd=dict(type="str", aliases=["name"]), # This parameter is not required for querying all objects
+ host_route=dict(type="bool"),
+ svi_mac=dict(type="str"),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["bd"]],
+ ["state", "present", ["bd"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ site = module.params.get("site")
+ template = module.params.get("template").replace(" ", "")
+ bd = module.params.get("bd")
+ host_route = module.params.get("host_route")
+ svi_mac = module.params.get("svi_mac")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ # Get schema objects
+ schema_id, schema_path, schema_obj = mso.query_schema(schema)
+
+ # Get template
+ templates = [t.get("name") for t in schema_obj.get("templates")]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates)))
+
+ # Get site
+ site_id = mso.lookup_site(site)
+
+ # Get site_idx
+ if not schema_obj.get("sites"):
+ mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template))
+ sites = [(s.get("siteId"), s.get("templateName")) for s in schema_obj.get("sites")]
+ if (site_id, template) not in sites:
+ mso.fail_json(msg="Provided site-template association '{0}-{1}' does not exist.".format(site, template))
+
+ # Schema-access uses indexes
+ site_idx = sites.index((site_id, template))
+ # Path-based access uses site_id-template
+ site_template = "{0}-{1}".format(site_id, template)
+
+ # Get BD
+ bd_ref = mso.bd_ref(schema_id=schema_id, template=template, bd=bd)
+ bds = [v.get("bdRef") for v in schema_obj.get("sites")[site_idx]["bds"]]
+ if bd is not None and bd_ref in bds:
+ bd_idx = bds.index(bd_ref)
+ bd_path = "/sites/{0}/bds/{1}".format(site_template, bd)
+ mso.existing = schema_obj.get("sites")[site_idx]["bds"][bd_idx]
+ mso.existing["bdRef"] = mso.dict_from_ref(mso.existing.get("bdRef"))
+
+ if state == "query":
+ if bd is None:
+ mso.existing = schema_obj.get("sites")[site_idx]["bds"]
+ for bd in mso.existing:
+ bd["bdRef"] = mso.dict_from_ref(bd.get("bdRef"))
+ elif not mso.existing:
+ mso.fail_json(msg="BD '{bd}' not found".format(bd=bd))
+ mso.exit_json()
+
+ bds_path = "/sites/{0}/bds".format(site_template)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == "absent":
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op="remove", path=bd_path))
+
+ elif state == "present":
+ if not mso.existing:
+ if host_route is None:
+ host_route = False
+
+ payload = dict(
+ bdRef=dict(
+ schemaId=schema_id,
+ templateName=template,
+ bdName=bd,
+ ),
+ hostBasedRouting=host_route,
+ )
+ if svi_mac is not None:
+ payload.update(mac=svi_mac)
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op="replace", path=bd_path, value=mso.sent))
+ else:
+ ops.append(dict(op="add", path=bds_path + "/-", value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode and mso.existing != mso.previous:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd_l3out.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd_l3out.py
new file mode 100644
index 000000000..8f9581294
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd_l3out.py
@@ -0,0 +1,255 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
+# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+# Copyright: (c) 2022, Akini Ross (@akinross) <akinross@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_site_bd_l3out
+short_description: Manage site-local BD l3out's in schema template
+description:
+- Manage site-local BDs l3out's in schema template on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+- Anvitha Jain (@anvitha-jain)
+- Akini Ross (@akinross)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ site:
+ description:
+ - The name of the site.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: true
+ bd:
+ description:
+ - The name of the BD.
+ type: str
+ required: true
+ aliases: [ name ]
+ l3out:
+ description:
+ - The l3out associated to this BD.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The name of the l3out to associate with.
+ required: true
+ type: str
+ schema:
+ description:
+ - The schema that defines the referenced l3out.
+ - If this parameter is unspecified, it defaults to the current schema.
+ type: str
+ template:
+ description:
+ - The template that defines the referenced l3out.
+ - If this parameter is unspecified, it defaults to the current schema.
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+notes:
+- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index.
+ This can cause silent corruption on concurrent access when changing/removing on object as
+ the wrong object may be referenced. This module is affected by this deficiency.
+seealso:
+- module: cisco.mso.mso_schema_site_bd
+- module: cisco.mso.mso_schema_template_bd
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new site BD l3out
+ cisco.mso.mso_schema_site_bd_l3out:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ bd: BD1
+ l3out:
+ name: L3out1
+ state: present
+ delegate_to: localhost
+
+- name: Add a new site BD l3out with different schema and template
+ cisco.mso.mso_schema_site_bd_l3out:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ bd: BD1
+ l3out:
+ name: L3out1
+ schema: Schema2
+ template: Template2
+ state: present
+ delegate_to: localhost
+
+- name: Remove a site BD l3out
+ cisco.mso.mso_schema_site_bd_l3out:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ bd: BD1
+ l3out:
+ name: L3out1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific site BD l3out
+ cisco.mso.mso_schema_site_bd_l3out:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ bd: BD1
+ l3out:
+ name: L3out1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all site BD l3outs
+ cisco.mso.mso_schema_site_bd_l3out:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ bd: BD1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_reference_spec
+from ansible_collections.cisco.mso.plugins.module_utils.schema import MSOSchema
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ site=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ bd=dict(type="str", required=True),
+ l3out=dict(type="dict", options=mso_reference_spec(), aliases=["name"]), # This parameter is not required for querying all objects
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["l3out"]],
+ ["state", "present", ["l3out"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ site = module.params.get("site")
+ template = module.params.get("template").replace(" ", "")
+ bd = module.params.get("bd")
+ l3out = module.params.get("l3out")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ mso_schema = MSOSchema(mso, schema, template, site)
+ mso_objects = mso_schema.schema_objects
+
+ mso_schema.set_template_bd(bd)
+ mso_schema.set_site_bd(bd, fail_module=False)
+
+ bd_path = "/sites/{0}-{1}/bds".format(mso_objects.get("site").details.get("siteId"), template)
+ ops = []
+ payload = dict()
+
+ if l3out:
+ l3out_schema_id = mso.lookup_schema(l3out.get("schema")) if l3out.get("schema") else mso_schema.id
+ l3out_template = l3out.get("template") if l3out.get("template") else template
+ l3out_ref = mso.l3out_ref(schema_id=l3out_schema_id, template=l3out_template, l3out=l3out.get("name"))
+ if not mso_objects.get("site_bd"):
+ payload = dict(bdRef=dict(schemaId=mso_schema.id, templateName=template, bdName=bd), l3Outs=[l3out.get("name")], l3OutRefs=[l3out_ref])
+ else:
+ mso_objects.get("site_bd").details["bdRef"] = dict(schemaId=mso_schema.id, templateName=template, bdName=bd)
+ l3out_refs = mso_objects.get("site_bd").details.get("l3OutRefs", [])
+ l3outs = mso_objects.get("site_bd").details.get("l3Outs", [])
+ # check on name because refs are handled differently between versions
+ if l3out.get("name") in l3outs:
+ mso.existing = mso.make_reference(l3out, "l3out", l3out_schema_id, l3out_template)
+
+ if state == "query":
+ if l3out is None:
+ if "l3OutRefs" in mso_objects.get("site_bd", {}).details.keys():
+ mso.existing = [mso.dict_from_ref(l3) for l3 in mso_objects.get("site_bd", {}).details.get("l3OutRefs", [])]
+ else:
+ mso.existing = [dict(l3outName=l3) for l3 in mso_objects.get("site_bd", {}).details.get("l3Outs", [])]
+ elif not mso.existing:
+ mso.fail_json(msg="L3out '{0}' not found".format(l3out.get("name")))
+ mso.exit_json()
+
+ mso.previous = mso.existing
+ if state == "absent":
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ if l3out.get("name") in l3outs:
+ del l3outs[l3outs.index(l3out.get("name"))]
+ if l3out_ref in l3out_refs:
+ del l3out_refs[l3out_refs.index(l3out_ref)]
+ ops.append(dict(op="replace", path="{0}/{1}".format(bd_path, bd), value=mso_objects.get("site_bd").details))
+
+ elif state == "present":
+ if payload:
+ ops.append(dict(op="add", path="{0}/-".format(bd_path), value=payload))
+ elif not mso.existing:
+ l3outs.append(l3out.get("name"))
+ l3out_refs.append(l3out_ref)
+ ops.append(dict(op="replace", path="{0}/{1}".format(bd_path, bd), value=mso_objects.get("site_bd").details))
+ mso.existing = mso.make_reference(l3out, "l3out", l3out_schema_id, l3out_template)
+
+ if not module.check_mode:
+ mso.request(mso_schema.path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd_subnet.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd_subnet.py
new file mode 100644
index 000000000..c4ab52eec
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd_subnet.py
@@ -0,0 +1,290 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2022, Akini Ross (@akinross) <akinross@cisco.com>
+# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_site_bd_subnet
+short_description: Manage site-local BD subnets in schema template
+description:
+- Manage site-local BD subnets in schema template on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ site:
+ description:
+ - The name of the site.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: true
+ bd:
+ description:
+ - The name of the BD.
+ type: str
+ required: true
+ aliases: [ name ]
+ subnet:
+ description:
+ - The IP range in CIDR notation.
+ type: str
+ aliases: [ ip ]
+ description:
+ description:
+ - The description of this subnet.
+ type: str
+ scope:
+ description:
+ - The scope of the subnet.
+ type: str
+ choices: [ private, public ]
+ shared:
+ description:
+ - Whether this subnet is shared between VRFs.
+ type: bool
+ default: false
+ no_default_gateway:
+ description:
+ - Whether this subnet has a default gateway.
+ type: bool
+ default: false
+ querier:
+ description:
+ - Whether this subnet is an IGMP querier.
+ type: bool
+ default: false
+ primary:
+ description:
+ - Treat as Primary Subnet.
+ - There can be only one primary subnet per address family under a BD.
+ - This option can only be used on versions of MSO that are 3.1.1h or greater.
+ type: bool
+ default: false
+ is_virtual_ip:
+ description:
+ - Treat as Virtual IP Address
+ type: bool
+ default: false
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+notes:
+- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index.
+ This can cause silent corruption on concurrent access when changing/removing on object as
+ the wrong object may be referenced. This module is affected by this deficiency.
+seealso:
+- module: cisco.mso.mso_schema_site_bd
+- module: cisco.mso.mso_schema_template_bd
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new site BD subnet
+ cisco.mso.mso_schema_site_bd_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ bd: BD1
+ subnet: 11.11.11.0/24
+ state: present
+ delegate_to: localhost
+
+- name: Remove a site BD subnet
+ cisco.mso.mso_schema_site_bd_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ bd: BD1
+ subnet: 11.11.11.0/24
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific site BD subnet
+ cisco.mso.mso_schema_site_bd_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ bd: BD1
+ subnet: 11.11.11.0/24
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all site BD subnets
+ cisco.mso.mso_schema_site_bd_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ bd: BD1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_subnet_spec
+from ansible_collections.cisco.mso.plugins.module_utils.schema import MSOSchema
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(mso_subnet_spec())
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ site=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ bd=dict(type="str", aliases=["name"], required=True),
+ subnet=dict(type="str", aliases=["ip"]),
+ description=dict(type="str"),
+ scope=dict(type="str", choices=["private", "public"]),
+ shared=dict(type="bool", default=False),
+ no_default_gateway=dict(type="bool", default=False),
+ querier=dict(type="bool", default=False),
+ primary=dict(type="bool", default=False),
+ is_virtual_ip=dict(type="bool", default=False),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["subnet"]],
+ ["state", "present", ["subnet"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ site = module.params.get("site")
+ template = module.params.get("template").replace(" ", "")
+ bd = module.params.get("bd")
+ ip = module.params.get("subnet")
+ description = module.params.get("description")
+ scope = module.params.get("scope")
+ shared = module.params.get("shared")
+ no_default_gateway = module.params.get("no_default_gateway")
+ querier = module.params.get("querier")
+ primary = module.params.get("primary")
+ is_virtual_ip = module.params.get("is_virtual_ip")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ mso_schema = MSOSchema(mso, schema, template, site)
+ mso_objects = mso_schema.schema_objects
+
+ mso_schema.set_template_bd(bd)
+ if mso_objects.get("template_bd") and mso_objects.get("template_bd").details.get("l2Stretch") is True and state == "present":
+ mso.fail_json(
+ msg="The l2Stretch of template bd should be false in order to create a site bd subnet. " "Set l2Stretch as false using mso_schema_template_bd"
+ )
+
+ if state == "query":
+ mso_schema.set_site_bd(bd)
+ if not ip:
+ mso.existing = mso_objects.get("site_bd").details.get("subnets")
+ else:
+ mso_schema.set_site_bd_subnet(ip)
+ mso.existing = mso_objects.get("site_bd_subnet").details
+ mso.exit_json()
+
+ mso_schema.set_site_bd(bd, fail_module=False)
+
+ subnet = None
+ if mso_objects.get("site_bd"):
+ mso_schema.set_site_bd_subnet(ip, fail_module=False)
+ subnet = mso_objects.get("site_bd_subnet")
+
+ mso.previous = mso.existing = subnet.details if subnet else mso.existing
+
+ bd_path = "/sites/{0}-{1}/bds".format(mso_objects.get("site").details.get("siteId"), template)
+ subnet_path = "{0}/{1}/subnets".format(bd_path, bd)
+ ops = []
+
+ if state == "absent":
+ if subnet:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op="remove", path=subnet_path))
+
+ elif state == "present":
+ if not mso_objects.get("site_bd"):
+ bd_payload = dict(
+ bdRef=dict(
+ schemaId=mso_schema.id,
+ templateName=template,
+ bdName=bd,
+ ),
+ hostBasedRouting=False,
+ )
+ ops.append(dict(op="add", path=bd_path + "/-", value=bd_payload))
+
+ if not subnet:
+ if description is None:
+ description = ip
+ if scope is None:
+ scope = "private"
+
+ subnet_payload = dict(
+ ip=ip,
+ description=description,
+ scope=scope,
+ shared=shared,
+ noDefaultGateway=no_default_gateway,
+ virtual=is_virtual_ip,
+ querier=querier,
+ primary=primary,
+ )
+
+ mso.sanitize(subnet_payload, collate=True)
+
+ if subnet:
+ ops.append(dict(op="replace", path="{0}/{1}".format(subnet_path, subnet.index), value=mso.sent))
+ else:
+ ops.append(dict(op="add", path="{0}/-".format(subnet_path), value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(mso_schema.path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_external_epg.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_external_epg.py
new file mode 100644
index 000000000..dff4cf152
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_external_epg.py
@@ -0,0 +1,234 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_site_external_epg
+short_description: Manage External EPG in schema of sites
+description:
+- Manage External EPG in schema of sites on Cisco ACI Multi-Site.
+- This module can only be used on versions of MSO that are 3.3 or greater.
+author:
+- Anvitha Jain (@anvitha-jain)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template to change.
+ type: str
+ required: true
+ l3out:
+ description:
+ - The L3Out associated with the external epg.
+ - Required when site is of type on-premise.
+ type: str
+ external_epg:
+ description:
+ - The name of the External EPG to be managed.
+ type: str
+ aliases: [ name ]
+ site:
+ description:
+ - The name of the site.
+ type: str
+ required: true
+ route_reachability:
+ description:
+ - Configures if an external EPG route is pointing to the internet or to an external remote network.
+ - Only available when associated with an azure site.
+ type: str
+ choices: [ internet, site-ext ]
+ default: internet
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+seealso:
+- module: cisco.mso.mso_schema_template_external_epg
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a Site External EPG
+ cisco.mso.mso_schema_site_external_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ external_epg: External EPG 1
+ l3out: L3out1
+ state: present
+ delegate_to: localhost
+
+- name: Remove a Site External EPG
+ cisco.mso.mso_schema_site_external_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ external_epg: External EPG 1
+ l3out: L3out1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a Site External EPG
+ cisco.mso.mso_schema_site_external_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ external_epg: External EPG 1
+ l3out: L3out1
+ state: query
+ delegate_to: localhost
+
+- name: Query all Site External EPGs
+ cisco.mso.mso_schema_site_external_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ state: query
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+from ansible_collections.cisco.mso.plugins.module_utils.schema import MSOSchema
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ site=dict(type="str", required=True),
+ l3out=dict(type="str"),
+ external_epg=dict(type="str", aliases=["name"]),
+ route_reachability=dict(type="str", default="internet", choices=["internet", "site-ext"]),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["external_epg"]],
+ ["state", "present", ["external_epg"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ template = module.params.get("template")
+ site = module.params.get("site")
+ external_epg = module.params.get("external_epg")
+ l3out = module.params.get("l3out")
+ route_reachability = module.params.get("route_reachability")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ mso_schema = MSOSchema(mso, schema, template, site)
+ mso_objects = mso_schema.schema_objects
+
+ mso_schema.set_template_external_epg(external_epg, fail_module=False)
+
+ # Path-based access uses site_id-template
+ site_template = "{0}-{1}".format(mso_objects.get("site").details.get("siteId"), template)
+
+ payload = dict()
+ op_path = "/sites/{0}/externalEpgs/-".format(site_template)
+
+ # Get template External EPG
+ if mso_objects.get("template_external_epg") is not None:
+ ext_epg_ref = mso_objects.get("template_external_epg").details.get("externalEpgRef")
+ external_epgs = [e.get("externalEpgRef") for e in mso_objects.get("site").details.get("externalEpgs")]
+
+ # Get Site External EPG
+ if ext_epg_ref in external_epgs:
+ external_epg_idx = external_epgs.index(ext_epg_ref)
+ mso.existing = mso_objects.get("site").details.get("externalEpgs")[external_epg_idx]
+ op_path = "/sites/{0}/externalEpgs/{1}".format(site_template, external_epg)
+
+ ops = []
+ l3out_dn = ""
+
+ if state == "query":
+ if external_epg is None:
+ mso.existing = mso_objects.get("site").details.get("externalEpgs")
+ elif not mso.existing:
+ mso.fail_json(msg="External EPG '{external_epg}' not found".format(external_epg=external_epg))
+ mso.exit_json()
+
+ mso.previous = mso.existing
+
+ if state == "absent":
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op="remove", path=op_path))
+
+ elif state == "present":
+ # Get external EPGs type from template level and verify template_external_epg type.
+ if mso_objects.get("template_external_epg").details.get("extEpgType") != "cloud":
+ if l3out is not None:
+ path = "tenants/{0}".format(mso_objects.get("template").details.get("tenantId"))
+ tenant_name = mso.request(path, method="GET").get("name")
+ l3out_dn = "uni/tn-{0}/out-{1}".format(tenant_name, l3out)
+ else:
+ mso.fail_json(msg="L3Out cannot be empty when template external EPG type is 'on-premise'.")
+
+ payload = dict(
+ externalEpgRef=dict(
+ schemaId=mso_schema.id,
+ templateName=template,
+ externalEpgName=external_epg,
+ ),
+ l3outDn=l3out_dn,
+ l3outRef=dict(
+ schemaId=mso_schema.id,
+ templateName=template,
+ l3outName=l3out,
+ ),
+ routeReachabilityInternetType=route_reachability,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op="replace", path=op_path, value=mso.sent))
+ else:
+ ops.append(dict(op="add", path=op_path, value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode and mso.proposed != mso.previous:
+ mso.request(mso_schema.path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_external_epg_selector.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_external_epg_selector.py
new file mode 100644
index 000000000..001ebf2b5
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_external_epg_selector.py
@@ -0,0 +1,291 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_site_external_epg_selector
+short_description: Manage External EPG selector in schema of cloud sites
+description:
+- Manage External EPG selector in schema of cloud sites on Cisco ACI Multi-Site.
+author:
+- Shreyas Srish (@shrsr)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template to change.
+ type: str
+ required: true
+ external_epg:
+ description:
+ - The name of the External EPG to be managed.
+ type: str
+ required: true
+ site:
+ description:
+ - The name of the cloud site.
+ type: str
+ required: true
+ selector:
+ description:
+ - The name of the selector.
+ type: str
+ expressions:
+ description:
+ - Expressions associated to this selector.
+ type: list
+ elements: dict
+ suboptions:
+ type:
+ description:
+ - The name of the expression which in this case is always IP address.
+ required: true
+ type: str
+ choices: [ ip_address ]
+ operator:
+ description:
+ - The operator associated with the expression which in this case is always equals.
+ required: true
+ type: str
+ choices: [ equals ]
+ value:
+ description:
+ - The value of the IP Address / Subnet associated with the expression.
+ required: true
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+seealso:
+- module: cisco.mso.mso_schema_template_external_epg
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a selector to an External EPG
+ cisco.mso.mso_schema_site_external_epg_selector:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: ansible_test
+ template: Template1
+ site: azure_ansible_test
+ external_epg: ext1
+ selector: test
+ expressions:
+ - type: ip_address
+ operator: equals
+ value: 10.0.0.0
+ state: present
+ delegate_to: localhost
+
+- name: Remove a Selector
+ cisco.mso.mso_schema_site_external_epg_selector:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: ansible_test
+ template: Template1
+ site: azure_ansible_test
+ external_epg: ext1
+ selector: test
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific Selector
+ cisco.mso.mso_schema_site_external_epg_selector:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: ansible_test
+ template: Template1
+ site: azure_ansible_test
+ external_epg: ext1
+ selector: selector_1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all Selectors
+ cisco.mso.mso_schema_site_external_epg_selector:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: ansible_test
+ template: Template1
+ site: azure_ansible_test
+ external_epg: ext1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_expression_spec_ext_epg
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ site=dict(type="str", required=True),
+ external_epg=dict(type="str", required=True),
+ selector=dict(type="str"),
+ expressions=dict(type="list", elements="dict", options=mso_expression_spec_ext_epg()),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ )
+
+ schema = module.params.get("schema")
+ template = module.params.get("template").replace(" ", "")
+ site = module.params.get("site")
+ external_epg = module.params.get("external_epg")
+ selector = module.params.get("selector")
+ expressions = module.params.get("expressions")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ # Get schema objects
+ schema_id, schema_path, schema_obj = mso.query_schema(schema)
+
+ # Get template
+ templates = [t.get("name") for t in schema_obj.get("templates")]
+ if template not in templates:
+ mso.fail_json(
+ msg="Provided template '{template}' does not exist. Existing templates: {templates}".format(template=template, templates=", ".join(templates))
+ )
+
+ # Get site
+ site_id = mso.lookup_site(site)
+
+ # Get site_idx
+ if not schema_obj.get("sites"):
+ mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template))
+ sites = [(s.get("siteId"), s.get("templateName")) for s in schema_obj.get("sites")]
+ sites_list = [s.get("siteId") + "/" + s.get("templateName") for s in schema_obj.get("sites")]
+ if (site_id, template) not in sites:
+ mso.fail_json(
+ msg="Provided site/siteId/template '{0}/{1}/{2}' does not exist. "
+ "Existing siteIds/templates: {3}".format(site, site_id, template, ", ".join(sites_list))
+ )
+
+ # Schema-access uses indexes
+ site_idx = sites.index((site_id, template))
+ # Path-based access uses site_id-template
+ site_template = "{0}-{1}".format(site_id, template)
+
+ payload = dict()
+ op_path = ""
+
+ # Get External EPG
+ ext_epg_ref = mso.ext_epg_ref(schema_id=schema_id, template=template, external_epg=external_epg)
+ external_epgs = [e.get("externalEpgRef") for e in schema_obj.get("sites")[site_idx]["externalEpgs"]]
+
+ if ext_epg_ref not in external_epgs:
+ op_path = "/sites/{0}/externalEpgs/-".format(site_template)
+ payload = dict(
+ externalEpgRef=dict(
+ schemaId=schema_id,
+ templateName=template,
+ externalEpgName=external_epg,
+ ),
+ l3outDn="",
+ )
+
+ else:
+ external_epg_idx = external_epgs.index(ext_epg_ref)
+
+ # Get Selector
+ selectors = [s.get("name") for s in schema_obj["sites"][site_idx]["externalEpgs"][external_epg_idx]["subnets"]]
+ if selector in selectors:
+ selector_idx = selectors.index(selector)
+ selector_path = "/sites/{0}/externalEpgs/{1}/subnets/{2}".format(site_template, external_epg, selector_idx)
+ mso.existing = schema_obj["sites"][site_idx]["externalEpgs"][external_epg_idx]["subnets"][selector_idx]
+
+ selectors_path = "/sites/{0}/externalEpgs/{1}/subnets/-".format(site_template, external_epg)
+ ops = []
+
+ if state == "query":
+ if selector is None:
+ mso.existing = schema_obj["sites"][site_idx]["externalEpgs"][external_epg_idx]["subnets"]
+ elif not mso.existing:
+ mso.fail_json(msg="Selector '{selector}' not found".format(selector=selector))
+ mso.exit_json()
+
+ mso.previous = mso.existing
+
+ if state == "absent":
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op="remove", path=selector_path))
+
+ elif state == "present":
+ # Get expressions
+ types = dict(ip_address="ipAddress")
+ all_expressions = []
+ if expressions:
+ for expression in expressions:
+ type_val = expression.get("type")
+ operator = expression.get("operator")
+ value = expression.get("value")
+ all_expressions.append(
+ dict(
+ key=types.get(type_val),
+ operator=operator,
+ value=value,
+ )
+ )
+ else:
+ mso.fail_json(msg="Missing expressions in selector")
+
+ subnets = dict(name=selector, ip=all_expressions[0]["value"])
+
+ if not external_epgs:
+ payload["subnets"] = [subnets]
+ else:
+ payload = subnets
+ op_path = selectors_path
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op="replace", path=selector_path, value=mso.sent))
+ else:
+ ops.append(dict(op="add", path=op_path, value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode and mso.proposed != mso.previous:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_l3out.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_l3out.py
new file mode 100644
index 000000000..4741e4bd7
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_l3out.py
@@ -0,0 +1,246 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_site_l3out
+short_description: Manage site-local layer3 Out (L3Outs) in schema template
+description:
+- Manage site-local L3Outs in schema template on Cisco ACI Multi-Site.
+- This module can only be used on versions of MSO that are 3.0 or greater.
+- NOTE - Usage of this module for version lesser than 3.0 might break the MSO.
+author:
+- Anvitha Jain (@anvitha-jain)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ site:
+ description:
+ - The name of the site.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: true
+ vrf:
+ description:
+ - The VRF associated to this L3out.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The name of the VRF to associate with.
+ required: true
+ type: str
+ schema:
+ description:
+ - The schema that defines the referenced VRF.
+ - If this parameter is unspecified, it defaults to the current schema.
+ type: str
+ template:
+ description:
+ - The template that defines the referenced VRF.
+ - If this parameter is unspecified, it defaults to the current schema.
+ type: str
+ l3out:
+ description:
+ - The name of the l3out to manage.
+ type: str
+ aliases: [ name ]
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+seealso:
+- module: cisco.mso.mso_schema_site
+- module: cisco.mso.mso_schema_template_l3out
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new site L3Out
+ cisco.mso.mso_schema_site_l3out:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ l3out: L3out1
+ vrf:
+ name: vrfName
+ template: TemplateName
+ schema: schemaName
+ state: present
+ delegate_to: localhost
+
+- name: Remove a site L3Out
+ cisco.mso.mso_schema_site_l3out:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ l3out: L3out1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific site L3Out
+ cisco.mso.mso_schema_site_l3out:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ l3out: L3out1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all site l3outs
+ cisco.mso.mso_schema_site_l3out:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_reference_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ site=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ vrf=dict(type="dict", options=mso_reference_spec()),
+ l3out=dict(type="str", aliases=["name"]), # This parameter is not required for querying all objects
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["l3out"]],
+ ["state", "present", ["l3out", "vrf"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ site = module.params.get("site")
+ template = module.params.get("template").replace(" ", "")
+ l3out = module.params.get("l3out")
+ vrf = module.params.get("vrf")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ # Get schema objects
+ schema_id, schema_path, schema_obj = mso.query_schema(schema)
+
+ # Get template
+ templates = [t.get("name") for t in schema_obj.get("templates")]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates)))
+
+ # Get site
+ site_id = mso.lookup_site(site)
+
+ # Get site_idx
+ if not schema_obj.get("sites"):
+ mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template))
+ sites = [(s.get("siteId"), s.get("templateName")) for s in schema_obj.get("sites")]
+ if (site_id, template) not in sites:
+ mso.fail_json(msg="Provided template '{0}' is not associated to site".format(template))
+
+ # Schema-access uses indexes
+ site_idx = sites.index((site_id, template))
+ # Path-based access uses site_id-template
+ site_template = "{0}-{1}".format(site_id, template)
+
+ # Get l3out
+ l3out_ref = mso.l3out_ref(schema_id=schema_id, template=template, l3out=l3out)
+ l3outs = [v.get("l3outRef") for v in schema_obj.get("sites")[site_idx]["intersiteL3outs"]]
+
+ if l3out is not None and l3out_ref in l3outs:
+ l3out_idx = l3outs.index(l3out_ref)
+ l3out_path = "/sites/{0}/intersiteL3outs/{1}".format(site_template, l3out)
+ mso.existing = schema_obj.get("sites")[site_idx]["intersiteL3outs"][l3out_idx]
+
+ if state == "query":
+ if l3out is None:
+ mso.existing = schema_obj.get("sites")[site_idx]["intersiteL3outs"]
+ for l3out in mso.existing:
+ l3out["l3outRef"] = mso.dict_from_ref(l3out.get("l3outRef"))
+ elif not mso.existing:
+ mso.fail_json(msg="L3Out '{l3out}' not found".format(l3out=l3out))
+ mso.exit_json()
+
+ l3outs_path = "/sites/{0}/intersiteL3outs".format(site_template)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == "absent":
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op="remove", path=l3out_path))
+
+ elif state == "present":
+ vrf_ref = mso.make_reference(vrf, "vrf", schema_id, template)
+
+ payload = dict(
+ l3outRef=dict(
+ schemaId=schema_id,
+ templateName=template,
+ l3outName=l3out,
+ ),
+ vrfRef=vrf_ref,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op="replace", path=l3out_path, value=mso.sent))
+ else:
+ ops.append(dict(op="add", path=l3outs_path + "/-", value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_service_graph.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_service_graph.py
new file mode 100644
index 000000000..9495a501d
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_service_graph.py
@@ -0,0 +1,279 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2022, Shreyas Srish (@shrsr) <ssrish@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_site_service_graph
+short_description: Manage Service Graph in schema sites
+description:
+- Manage Service Graph in schema sites on Cisco ACI Multi-Site.
+- This module is only supported in MSO/NDO version 3.3 and above.
+author:
+- Shreyas Srish (@shrsr)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: true
+ site:
+ description:
+ - The name of the site.
+ type: str
+ required: true
+ tenant:
+ description:
+ - The name of the tenant.
+ type: str
+ service_graph:
+ description:
+ - The name of the Service Graph to manage.
+ type: str
+ aliases: [ name ]
+ devices:
+ description:
+ - A list of devices to be associated with the Service Graph.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - The name of the device
+ required: true
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a Service Graph
+ cisco.mso.mso_schema_site_service_graph_node:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ template: Template1
+ service_graph: SG1
+ site: site1
+ tenant: tenant1
+ devices:
+ - name: ansible_test_firewall
+ - name: ansible_test_adc
+ - name: ansible_test_other
+ state: present
+ delegate_to: localhost
+
+- name: Remove a Service Graph
+ cisco.mso.mso_schema_site_service_graph_node:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ template: Template1
+ service_graph: SG1
+ site: site1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific Service Graph
+ cisco.mso.mso_schema_site_service_graph_node:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ template: Template1
+ service_graph: SG1
+ site: site1
+ state: query
+ delegate_to: localhost
+
+- name: Query all Service Graphs
+ cisco.mso.mso_schema_site_service_graph_node:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ template: Template1
+ site: site1
+ state: query
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_service_graph_node_device_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ service_graph=dict(type="str", aliases=["name"]),
+ tenant=dict(type="str"),
+ site=dict(type="str", required=True),
+ devices=dict(type="list", elements="dict", options=mso_service_graph_node_device_spec()),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["service_graph"]],
+ ["state", "present", ["service_graph", "devices"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ template = module.params.get("template").replace(" ", "")
+ service_graph = module.params.get("service_graph")
+ devices = module.params.get("devices")
+ site = module.params.get("site")
+ tenant = module.params.get("tenant")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ # Get schema
+ schema_id, schema_path, schema_obj = mso.query_schema(schema)
+
+ # Get template
+ templates = schema_obj.get("templates")
+ template_names = [t.get("name") for t in templates]
+ if template not in template_names:
+ mso.fail_json(
+ msg="Provided template '{template}' does not exist. Existing templates: {templates}".format(template=template, templates=", ".join(template_names))
+ )
+ template_idx = template_names.index(template)
+
+ # Get site
+ site_id = mso.lookup_site(site)
+
+ # Get site_idx
+ if not schema_obj.get("sites"):
+ mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template))
+ sites = [(s.get("siteId"), s.get("templateName")) for s in schema_obj.get("sites")]
+ if (site_id, template) not in sites:
+ mso.fail_json(msg="Provided site-template association '{0}-{1}' does not exist.".format(site, template))
+
+ # Schema-access uses indexes
+ site_idx = sites.index((site_id, template))
+ # Path-based access uses site_id-template
+ site_template = "{0}-{1}".format(site_id, template)
+
+ mso.existing = {}
+ service_graph_idx = None
+
+ # Get Service Graph
+ service_graph_ref = mso.service_graph_ref(schema_id=schema_id, template=template, service_graph=service_graph)
+ service_graph_refs = [f.get("serviceGraphRef") for f in schema_obj.get("sites")[site_idx]["serviceGraphs"]]
+ if service_graph is not None and service_graph_ref in service_graph_refs:
+ service_graph_idx = service_graph_refs.index(service_graph_ref)
+ mso.existing = schema_obj.get("sites")[site_idx]["serviceGraphs"][service_graph_idx]
+
+ if state == "query":
+ if service_graph is None:
+ mso.existing = schema_obj.get("sites")[site_idx]["serviceGraphs"]
+ elif service_graph is not None and service_graph_idx is None:
+ mso.fail_json(msg="Service Graph '{service_graph}' not found".format(service_graph=service_graph))
+ mso.exit_json()
+
+ service_graphs_path = "/sites/{0}/serviceGraphs/-".format(site_template)
+ service_graph_path = "/sites/{0}/serviceGraphs/{1}".format(site_template, service_graph)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == "absent":
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op="remove", path=service_graph_path))
+
+ elif state == "present":
+ devices_payload = []
+ service_graphs = templates[template_idx]["serviceGraphs"]
+ for graph in service_graphs:
+ if graph.get("name") == service_graph:
+ service_node_types_from_template = graph["serviceNodes"]
+ user_number_devices = len(devices)
+ number_of_nodes_in_template = len(service_node_types_from_template)
+ if user_number_devices != number_of_nodes_in_template:
+ mso.fail_json(
+ msg="Service Graph '{0}' has '{1}' service node type(s) but '{2}' service node(s) were given for the service graph".format(
+ service_graph, number_of_nodes_in_template, user_number_devices
+ )
+ )
+
+ if devices is not None:
+ service_node_type_names_from_template = [type.get("name") for type in service_node_types_from_template]
+ for index, device in enumerate(devices):
+ template_node_type = service_node_type_names_from_template[index]
+ apic_type = "OTHERS"
+ if template_node_type == "firewall":
+ apic_type = "FW"
+ elif template_node_type == "load-balancer":
+ apic_type = "ADC"
+ query_device_data = mso.lookup_service_node_device(site_id, tenant, device.get("name"), apic_type)
+ devices_payload.append(
+ dict(
+ device=dict(
+ dn=query_device_data.get("dn"),
+ funcTyp=query_device_data.get("funcType"),
+ ),
+ serviceNodeRef=dict(
+ serviceNodeName=template_node_type,
+ serviceGraphName=service_graph,
+ templateName=template,
+ schemaId=schema_id,
+ ),
+ ),
+ )
+
+ payload = dict(
+ serviceGraphRef=dict(
+ serviceGraphName=service_graph,
+ templateName=template,
+ schemaId=schema_id,
+ ),
+ serviceNodes=devices_payload,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if not mso.existing:
+ ops.append(dict(op="add", path=service_graphs_path, value=payload))
+ else:
+ ops.append(dict(op="replace", path=service_graph_path, value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf.py
new file mode 100644
index 000000000..a0b864a8b
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf.py
@@ -0,0 +1,207 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_site_vrf
+short_description: Manage site-local VRFs in schema template
+description:
+- Manage site-local VRFs in schema template on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ site:
+ description:
+ - The name of the site.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: true
+ vrf:
+ description:
+ - The name of the VRF to manage.
+ type: str
+ aliases: [ name ]
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+seealso:
+- module: cisco.mso.mso_schema_site
+- module: cisco.mso.mso_schema_template_vrf
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new site VRF
+ cisco.mso.mso_schema_site_vrf:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ vrf: VRF1
+ state: present
+ delegate_to: localhost
+
+- name: Remove a site VRF
+ cisco.mso.mso_schema_site_vrf:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ vrf: VRF1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific site VRF
+ cisco.mso.mso_schema_site_vrf:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ vrf: VRF1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all site VRFs
+ cisco.mso.mso_schema_site_vrf:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ site=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ vrf=dict(type="str", aliases=["name"]), # This parameter is not required for querying all objects
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["vrf"]],
+ ["state", "present", ["vrf"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ site = module.params.get("site")
+ template = module.params.get("template").replace(" ", "")
+ vrf = module.params.get("vrf")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ # Get schema objects
+ schema_id, schema_path, schema_obj = mso.query_schema(schema)
+
+ # Get site
+ site_id = mso.lookup_site(site)
+
+ # Get site_idx
+ if not schema_obj.get("sites"):
+ mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template))
+ sites = [(s.get("siteId"), s.get("templateName")) for s in schema_obj.get("sites")]
+ if (site_id, template) not in sites:
+ mso.fail_json(msg="Provided site/template '{0}-{1}' does not exist. Existing sites/templates: {2}".format(site, template, ", ".join(sites)))
+
+ # Schema-access uses indexes
+ site_idx = sites.index((site_id, template))
+ # Path-based access uses site_id-template
+ site_template = "{0}-{1}".format(site_id, template)
+
+ # Get VRF
+ vrf_ref = mso.vrf_ref(schema_id=schema_id, template=template, vrf=vrf)
+ vrfs = [v.get("vrfRef") for v in schema_obj.get("sites")[site_idx]["vrfs"]]
+ if vrf is not None and vrf_ref in vrfs:
+ vrf_idx = vrfs.index(vrf_ref)
+ vrf_path = "/sites/{0}/vrfs/{1}".format(site_template, vrf)
+ mso.existing = schema_obj.get("sites")[site_idx]["vrfs"][vrf_idx]
+
+ if state == "query":
+ if vrf is None:
+ mso.existing = schema_obj.get("sites")[site_idx]["vrfs"]
+ elif not mso.existing:
+ mso.fail_json(msg="VRF '{vrf}' not found".format(vrf=vrf))
+ mso.exit_json()
+
+ vrfs_path = "/sites/{0}/vrfs".format(site_template)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == "absent":
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op="remove", path=vrf_path))
+
+ elif state == "present":
+ payload = dict(
+ vrfRef=dict(
+ schemaId=schema_id,
+ templateName=template,
+ vrfName=vrf,
+ ),
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op="replace", path=vrf_path, value=mso.sent))
+ else:
+ ops.append(dict(op="add", path=vrfs_path + "/-", value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region.py
new file mode 100644
index 000000000..5ac5804e6
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region.py
@@ -0,0 +1,274 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
+# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_site_vrf_region
+short_description: Manage site-local VRF regions in schema template
+description:
+- Manage site-local VRF regions in schema template on Cisco ACI Multi-Site.
+author:
+- Anvitha Jain (@anvitha-jain)
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ site:
+ description:
+ - The name of the site.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: true
+ vrf:
+ description:
+ - The name of the VRF.
+ type: str
+ required: true
+ region:
+ description:
+ - The name of the region to manage.
+ type: str
+ aliases: [ name ]
+ vpn_gateway_router:
+ description:
+ - Whether VPN Gateway Router is enabled or not.
+ type: bool
+ container_overlay:
+ description:
+ - The name of the context profile type.
+ - This is supported on versions of MSO that are 3.3 or greater.
+ type: bool
+ underlay_context_profile:
+ description:
+ - The name of the context profile type.
+ - This parameter can only be added when container_overlay is True.
+ - This is supported on versions of MSO that are 3.3 or greater.
+ type: dict
+ suboptions:
+ vrf:
+ description:
+ - The name of the VRF to associate with underlay context profile.
+ type: str
+ required: true
+ region:
+ description:
+ - The name of the region associated with underlay context profile VRF.
+ type: str
+ required: true
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+notes:
+- Due to restrictions of the MSO REST API, this module cannot create empty region (i.e. regions without cidrs)
+ Use the M(cisco.mso.mso_schema_site_vrf_region_cidr) to automatically create regions with cidrs.
+seealso:
+- module: cisco.mso.mso_schema_site_vrf
+- module: cisco.mso.mso_schema_template_vrf
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Remove VPN Gateway Router at site VRF Region
+ cisco.mso.mso_schema_site_vrf_region:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ vrf: VRF1
+ region: us-west-1
+ vpn_gateway_router: false
+ state: present
+ delegate_to: localhost
+
+- name: Remove a site VRF region
+ cisco.mso.mso_schema_site_vrf_region:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ vrf: VRF1
+ region: us-west-1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific site VRF region
+ cisco.mso.mso_schema_site_vrf_region:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ vrf: VRF1
+ region: us-west-1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all site VRF regions
+ cisco.mso.mso_schema_site_vrf_region:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ vrf: VRF1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ site=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ vrf=dict(type="str", required=True),
+ region=dict(type="str", aliases=["name"]), # This parameter is not required for querying all objects
+ vpn_gateway_router=dict(type="bool"),
+ container_overlay=dict(type="bool"),
+ underlay_context_profile=dict(
+ type="dict",
+ options=dict(
+ vrf=dict(type="str", required=True),
+ region=dict(type="str", required=True),
+ ),
+ ),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["region"]],
+ ["state", "present", ["region"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ site = module.params.get("site")
+ template = module.params.get("template").replace(" ", "")
+ vrf = module.params.get("vrf")
+ region = module.params.get("region")
+ vpn_gateway_router = module.params.get("vpn_gateway_router")
+ container_overlay = module.params.get("container_overlay")
+ underlay_context_profile = module.params.get("underlay_context_profile")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ # Get schema objects
+ schema_id, schema_path, schema_obj = mso.query_schema(schema)
+
+ # Get site
+ site_id = mso.lookup_site(site)
+
+ # Get site_idx
+ if not schema_obj.get("sites"):
+ mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template))
+ sites = [(s.get("siteId"), s.get("templateName")) for s in schema_obj.get("sites")]
+ if (site_id, template) not in sites:
+ mso.fail_json(msg="Provided site-template association '{0}-{1}' does not exist.".format(site, template))
+
+ # Schema-access uses indexes
+ site_idx = sites.index((site_id, template))
+ # Path-based access uses site_id-template
+ site_template = "{0}-{1}".format(site_id, template)
+
+ # Get VRF
+ vrf_ref = mso.vrf_ref(schema_id=schema_id, template=template, vrf=vrf)
+ vrfs = [v.get("vrfRef") for v in schema_obj.get("sites")[site_idx]["vrfs"]]
+ vrfs_name = [mso.dict_from_ref(v).get("vrfName") for v in vrfs]
+ if vrf_ref not in vrfs:
+ mso.fail_json(msg="Provided vrf '{0}' does not exist. Existing vrfs: {1}".format(vrf, ", ".join(vrfs_name)))
+ vrf_idx = vrfs.index(vrf_ref)
+
+ # Get Region
+ regions = [r.get("name") for r in schema_obj.get("sites")[site_idx]["vrfs"][vrf_idx]["regions"]]
+ if region is not None and region in regions:
+ region_idx = regions.index(region)
+ region_path = "/sites/{0}/vrfs/{1}/regions/{2}".format(site_template, vrf, region)
+ mso.existing = schema_obj.get("sites")[site_idx]["vrfs"][vrf_idx]["regions"][region_idx]
+
+ if state == "query":
+ if region is None:
+ mso.existing = schema_obj.get("sites")[site_idx]["vrfs"][vrf_idx]["regions"]
+ elif not mso.existing:
+ mso.fail_json(msg="Region '{region}' not found".format(region=region))
+ mso.exit_json()
+
+ regions_path = "/sites/{0}/vrfs/{1}/regions".format(site_template, vrf)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == "absent":
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op="remove", path=region_path))
+
+ elif state == "present":
+ payload = dict(
+ name=region,
+ isVpnGatewayRouter=vpn_gateway_router,
+ )
+
+ if container_overlay:
+ payload["contextProfileType"] = "container-overlay"
+ if mso.existing:
+ underlay_dict = dict(
+ vrfRef=dict(schemaId=schema_id, templateName=template, vrfName=underlay_context_profile["vrf"]),
+ regionName=underlay_context_profile["region"],
+ )
+ payload["underlayCtxProfile"] = underlay_dict
+
+ mso.sanitize(payload, collate=True)
+ if mso.existing:
+ ops.append(dict(op="replace", path=region_path, value=mso.sent))
+ else:
+ ops.append(dict(op="add", path=regions_path + "/-", value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_cidr.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_cidr.py
new file mode 100644
index 000000000..ef409f710
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_cidr.py
@@ -0,0 +1,304 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_site_vrf_region_cidr
+short_description: Manage site-local VRF region CIDRs in schema template
+description:
+- Manage site-local VRF region CIDRs in schema template on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+- Lionel Hercot (@lhercot)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ site:
+ description:
+ - The name of the site.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: true
+ vrf:
+ description:
+ - The name of the VRF.
+ type: str
+ required: true
+ region:
+ description:
+ - The name of the region.
+ type: str
+ required: true
+ cidr:
+ description:
+ - The name of the region CIDR to manage.
+ type: str
+ aliases: [ ip ]
+ primary:
+ description:
+ - Whether this is the primary CIDR.
+ type: bool
+ default: true
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+notes:
+- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index.
+ This can cause silent corruption on concurrent access when changing/removing on object as
+ the wrong object may be referenced. This module is affected by this deficiency.
+seealso:
+- module: cisco.mso.mso_schema_site_vrf_region
+- module: cisco.mso.mso_schema_site_vrf_region_cidr_subnet
+- module: cisco.mso.mso_schema_template_vrf
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new site VRF region CIDR
+ cisco.mso.mso_schema_site_vrf_region_cidr:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ vrf: VRF1
+ region: us-west-1
+ cidr: 14.14.14.1/24
+ state: present
+ delegate_to: localhost
+
+- name: Remove a site VRF region CIDR
+ cisco.mso.mso_schema_site_vrf_region_cidr:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ vrf: VRF1
+ region: us-west-1
+ cidr: 14.14.14.1/24
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific site VRF region CIDR
+ cisco.mso.mso_schema_site_vrf_region_cidr:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ vrf: VRF1
+ region: us-west-1
+ cidr: 14.14.14.1/24
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all site VRF region CIDR
+ cisco.mso.mso_schema_site_vrf_region_cidr:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ vrf: VRF1
+ region: us-west-1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ site=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ vrf=dict(type="str", required=True),
+ region=dict(type="str", required=True),
+ cidr=dict(type="str", aliases=["ip"]), # This parameter is not required for querying all objects
+ primary=dict(type="bool", default=True),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["cidr"]],
+ ["state", "present", ["cidr"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ site = module.params.get("site")
+ template = module.params.get("template").replace(" ", "")
+ vrf = module.params.get("vrf")
+ region = module.params.get("region")
+ cidr = module.params.get("cidr")
+ primary = module.params.get("primary")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ # Get schema objects
+ schema_id, schema_path, schema_obj = mso.query_schema(schema)
+
+ # Get template
+ templates = [t.get("name") for t in schema_obj.get("templates")]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates)))
+ template_idx = templates.index(template)
+
+ payload = dict()
+ op_path = ""
+ new_cidr = dict(
+ ip=cidr,
+ primary=primary,
+ )
+
+ # Get site
+ site_id = mso.lookup_site(site)
+
+ # Get site_idx
+ all_sites = schema_obj.get("sites")
+ sites = []
+ if all_sites is not None:
+ sites = [(s.get("siteId"), s.get("templateName")) for s in all_sites]
+
+ # Get VRF
+ vrf_ref = mso.vrf_ref(schema_id=schema_id, template=template, vrf=vrf)
+ template_vrfs = [a.get("name") for a in schema_obj["templates"][template_idx]["vrfs"]]
+ if vrf not in template_vrfs:
+ mso.fail_json(msg="Provided vrf '{0}' does not exist. Existing vrfs: {1}".format(vrf, ", ".join(template_vrfs)))
+
+ # if site-template does not exist, create it
+ if (site_id, template) not in sites:
+ op_path = "/sites/-"
+ payload.update(
+ siteId=site_id,
+ templateName=template,
+ vrfs=[
+ dict(
+ vrfRef=dict(
+ schemaId=schema_id,
+ templateName=template,
+ vrfName=vrf,
+ ),
+ regions=[dict(name=region, cidrs=[new_cidr])],
+ )
+ ],
+ )
+
+ else:
+ # Schema-access uses indexes
+ site_idx = sites.index((site_id, template))
+ # Path-based access uses site_id-template
+ site_template = "{0}-{1}".format(site_id, template)
+
+ # If vrf not at site level but exists at template level
+ vrfs = [v.get("vrfRef") for v in schema_obj.get("sites")[site_idx]["vrfs"]]
+ if vrf_ref not in vrfs:
+ op_path = "/sites/{0}/vrfs/-".format(site_template)
+ payload.update(
+ vrfRef=dict(
+ schemaId=schema_id,
+ templateName=template,
+ vrfName=vrf,
+ ),
+ regions=[dict(name=region, cidrs=[new_cidr])],
+ )
+ else:
+ # Update vrf index at site level
+ vrf_idx = vrfs.index(vrf_ref)
+
+ # Get Region
+ regions = [r.get("name") for r in schema_obj.get("sites")[site_idx]["vrfs"][vrf_idx]["regions"]]
+ if region not in regions:
+ op_path = "/sites/{0}/vrfs/{1}/regions/-".format(site_template, vrf)
+ payload.update(name=region, cidrs=[new_cidr])
+ else:
+ region_idx = regions.index(region)
+
+ # Get CIDR
+ cidrs = [c.get("ip") for c in schema_obj.get("sites")[site_idx]["vrfs"][vrf_idx]["regions"][region_idx]["cidrs"]]
+ if cidr is not None:
+ if cidr in cidrs:
+ cidr_idx = cidrs.index(cidr)
+ # FIXME: Changes based on index are DANGEROUS
+ cidr_path = "/sites/{0}/vrfs/{1}/regions/{2}/cidrs/{3}".format(site_template, vrf, region, cidr_idx)
+ mso.existing = schema_obj.get("sites")[site_idx]["vrfs"][vrf_idx]["regions"][region_idx]["cidrs"][cidr_idx]
+ op_path = "/sites/{0}/vrfs/{1}/regions/{2}/cidrs/-".format(site_template, vrf, region)
+ payload = new_cidr
+
+ if state == "query":
+ if (site_id, template) not in sites:
+ mso.fail_json(msg="Provided site-template association '{0}-{1}' does not exist.".format(site, template))
+ elif vrf_ref not in vrfs:
+ mso.fail_json(msg="Provided vrf '{0}' does not exist at site level.".format(vrf))
+ elif not regions or region not in regions:
+ mso.fail_json(msg="Provided region '{0}' does not exist. Existing regions: {1}".format(region, ", ".join(regions)))
+ elif cidr is None and not payload:
+ mso.existing = schema_obj.get("sites")[site_idx]["vrfs"][vrf_idx]["regions"][region_idx]["cidrs"]
+ elif not mso.existing:
+ mso.fail_json(msg="CIDR IP '{cidr}' not found".format(cidr=cidr))
+ mso.exit_json()
+
+ ops = []
+
+ mso.previous = mso.existing
+ if state == "absent":
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op="remove", path=cidr_path))
+
+ elif state == "present":
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op="replace", path=cidr_path, value=mso.sent))
+ else:
+ ops.append(dict(op="add", path=op_path, value=mso.sent))
+
+ mso.existing = new_cidr
+
+ if not module.check_mode and mso.previous != mso.existing:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_cidr_subnet.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_cidr_subnet.py
new file mode 100644
index 000000000..85a00ea9c
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_cidr_subnet.py
@@ -0,0 +1,320 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_site_vrf_region_cidr_subnet
+short_description: Manage site-local VRF regions in schema template
+description:
+- Manage site-local VRF regions in schema template on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+- Lionel Hercot (@lhercot)
+- Anvitha Jain (@anvitha-jain)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ site:
+ description:
+ - The name of the site.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: true
+ vrf:
+ description:
+ - The name of the VRF.
+ type: str
+ required: true
+ region:
+ description:
+ - The name of the region.
+ type: str
+ required: true
+ cidr:
+ description:
+ - The IP range of for the region CIDR.
+ type: str
+ required: true
+ subnet:
+ description:
+ - The IP subnet of this region CIDR.
+ type: str
+ aliases: [ ip ]
+ private_link_label:
+ description:
+ - The private link label used to represent this subnet.
+ - This parameter is available for MSO version greater than 3.3.
+ type: str
+ zone:
+ description:
+ - The name of the zone for the region CIDR subnet.
+ - This argument is required for AWS sites.
+ type: str
+ aliases: [ name ]
+ vgw:
+ description:
+ - Whether this subnet is used for the Azure Gateway in Azure.
+ - Whether this subnet is used for the Transit Gateway Attachment in AWS.
+ type: bool
+ aliases: [ hub_network ]
+ hosted_vrf:
+ description:
+ - The name of hosted vrf associated with region CIDR subnet.
+ - This is supported on versions of MSO that are 3.3 or greater.
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+notes:
+- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index.
+ This can cause silent corruption on concurrent access when changing/removing on object as
+ the wrong object may be referenced. This module is affected by this deficiency.
+seealso:
+- module: cisco.mso.mso_schema_site_vrf_region_cidr
+- module: cisco.mso.mso_schema_template_vrf
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new site VRF region CIDR subnet
+ cisco.mso.mso_schema_site_vrf_region_cidr_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ vrf: VRF1
+ region: us-west-1
+ cidr: 14.14.14.1/24
+ subnet: 14.14.14.2/24
+ zone: us-west-1a
+ state: present
+ delegate_to: localhost
+
+- name: Remove a site VRF region CIDR subnet
+ cisco.mso.mso_schema_site_vrf_region_cidr_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ vrf: VRF1
+ region: us-west-1
+ cidr: 14.14.14.1/24
+ subnet: 14.14.14.2/24
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific site VRF region CIDR subnet
+ cisco.mso.mso_schema_site_vrf_region_cidr_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ vrf: VRF1
+ region: us-west-1
+ cidr: 14.14.14.1/24
+ subnet: 14.14.14.2/24
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all site VRF region CIDR subnet
+ cisco.mso.mso_schema_site_vrf_region_cidr_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ vrf: VRF1
+ region: us-west-1
+ cidr: 14.14.14.1/24
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ site=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ vrf=dict(type="str", required=True),
+ region=dict(type="str", required=True),
+ cidr=dict(type="str", required=True),
+ subnet=dict(type="str", aliases=["ip"]), # This parameter is not required for querying all objects
+ private_link_label=dict(type="str"),
+ zone=dict(type="str", aliases=["name"]),
+ vgw=dict(type="bool", aliases=["hub_network"]),
+ hosted_vrf=dict(type="str"),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["subnet"]],
+ ["state", "present", ["subnet"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ site = module.params.get("site")
+ template = module.params.get("template").replace(" ", "")
+ vrf = module.params.get("vrf")
+ region = module.params.get("region")
+ cidr = module.params.get("cidr")
+ subnet = module.params.get("subnet")
+ private_link_label = module.params.get("private_link_label")
+ zone = module.params.get("zone")
+ hosted_vrf = module.params.get("hosted_vrf")
+ vgw = module.params.get("vgw")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ # Get schema objects
+ schema_id, schema_path, schema_obj = mso.query_schema(schema)
+
+ # Get template
+ templates = [t.get("name") for t in schema_obj.get("templates")]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates)))
+
+ # Get site
+ site_id = mso.lookup_site(site)
+
+ # Get site_idx
+ if not schema_obj.get("sites"):
+ mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template))
+ sites = [(s.get("siteId"), s.get("templateName")) for s in schema_obj.get("sites")]
+ sites_list = [s.get("siteId") + "/" + s.get("templateName") for s in schema_obj.get("sites")]
+ if (site_id, template) not in sites:
+ mso.fail_json(
+ msg="Provided site/siteId/template '{0}/{1}/{2}' does not exist. "
+ "Existing siteIds/templates: {3}".format(site, site_id, template, ", ".join(sites_list))
+ )
+
+ # Schema-access uses indexes
+ site_idx = sites.index((site_id, template))
+ # Path-based access uses site_id-template
+ site_template = "{0}-{1}".format(site_id, template)
+
+ # Get VRF at site level
+ vrf_ref = mso.vrf_ref(schema_id=schema_id, template=template, vrf=vrf)
+ vrfs = [v.get("vrfRef") for v in schema_obj.get("sites")[site_idx]["vrfs"]]
+
+ # If vrf not at site level but exists at template level
+ if vrf_ref not in vrfs:
+ mso.fail_json(msg="Provided vrf '{0}' does not exist at site level." " Use mso_schema_site_vrf_region_cidr to create it.".format(vrf))
+ vrf_idx = vrfs.index(vrf_ref)
+
+ # Get Region
+ regions = [r.get("name") for r in schema_obj.get("sites")[site_idx]["vrfs"][vrf_idx]["regions"]]
+ if region not in regions:
+ mso.fail_json(
+ msg="Provided region '{0}' does not exist. Existing regions: {1}."
+ " Use mso_schema_site_vrf_region_cidr to create it.".format(region, ", ".join(regions))
+ )
+ region_idx = regions.index(region)
+
+ # Get CIDR
+ cidrs = [c.get("ip") for c in schema_obj.get("sites")[site_idx]["vrfs"][vrf_idx]["regions"][region_idx]["cidrs"]]
+ if cidr not in cidrs:
+ mso.fail_json(
+ msg="Provided CIDR IP '{0}' does not exist. Existing CIDR IPs: {1}."
+ " Use mso_schema_site_vrf_region_cidr to create it.".format(cidr, ", ".join(cidrs))
+ )
+ cidr_idx = cidrs.index(cidr)
+
+ # Get Subnet
+ subnets = [s.get("ip") for s in schema_obj.get("sites")[site_idx]["vrfs"][vrf_idx]["regions"][region_idx]["cidrs"][cidr_idx]["subnets"]]
+ if subnet is not None and subnet in subnets:
+ subnet_idx = subnets.index(subnet)
+ # FIXME: Changes based on index are DANGEROUS
+ subnet_path = "/sites/{0}/vrfs/{1}/regions/{2}/cidrs/{3}/subnets/{4}".format(site_template, vrf, region, cidr_idx, subnet_idx)
+ mso.existing = schema_obj.get("sites")[site_idx]["vrfs"][vrf_idx]["regions"][region_idx]["cidrs"][cidr_idx]["subnets"][subnet_idx]
+
+ if state == "query":
+ if subnet is None:
+ mso.existing = schema_obj.get("sites")[site_idx]["vrfs"][vrf_idx]["regions"][region_idx]["cidrs"][cidr_idx]["subnets"]
+ elif not mso.existing:
+ mso.fail_json(msg="Subnet IP '{subnet}' not found".format(subnet=subnet))
+ mso.exit_json()
+
+ subnets_path = "/sites/{0}/vrfs/{1}/regions/{2}/cidrs/{3}/subnets".format(site_template, vrf, region, cidr_idx)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == "absent":
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op="remove", path=subnet_path))
+
+ elif state == "present":
+ payload = dict(ip=subnet, zone="")
+
+ if zone is not None:
+ payload["zone"] = zone
+ if vgw is True:
+ payload["usage"] = "gateway"
+ if private_link_label is not None:
+ payload["privateLinkLabel"] = dict(name=private_link_label)
+ if hosted_vrf is not None:
+ payload["vrfRef"] = dict(schemaId=schema_id, templateName=template, vrfName=hosted_vrf)
+ payload["inEditing"] = "false"
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op="replace", path=subnet_path, value=mso.sent))
+ else:
+ ops.append(dict(op="add", path=subnets_path + "/-", value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_hub_network.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_hub_network.py
new file mode 100644
index 000000000..9df7bab4f
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_hub_network.py
@@ -0,0 +1,245 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_site_vrf_region_hub_network
+short_description: Manage site-local VRF region hub network in schema template
+description:
+- Manage site-local VRF region hub network in schema template on Cisco ACI Multi-Site.
+- The 'Hub Network' feature was introduced in Multi-Site Orchestrator (MSO) version 3.0(1) for AWS and version 3.0(2) for Azure.
+author:
+- Cindy Zhao (@cizhao)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ site:
+ description:
+ - The name of the site.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: true
+ vrf:
+ description:
+ - The name of the VRF.
+ type: str
+ required: true
+ region:
+ description:
+ - The name of the region.
+ type: str
+ required: true
+ hub_network:
+ description:
+ - The hub network to be managed.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The name of the hub network.
+ - The hub-default is the default created hub network.
+ type: str
+ required: true
+ tenant:
+ description:
+ - The tenant name of the hub network.
+ type: str
+ required: true
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+notes:
+- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index.
+ This can cause silent corruption on concurrent access when changing/removing on object as
+ the wrong object may be referenced. This module is affected by this deficiency.
+seealso:
+- module: cisco.mso.mso_schema_site_vrf_region
+- module: cisco.mso.mso_schema_template_vrf
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new site VRF region hub network
+ cisco.mso.mso_schema_site_vrf_region_hub_network:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ vrf: VRF1
+ region: us-west-1
+ hub_network:
+ name: hub-default
+ tenant: infra
+ state: present
+ delegate_to: localhost
+
+- name: Remove a site VRF region hub network
+ cisco.mso.mso_schema_site_vrf_region_hub_network:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ vrf: VRF1
+ state: absent
+ delegate_to: localhost
+
+- name: Query site VRF region hub network
+ cisco.mso.mso_schema_site_vrf_region_hub_network:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ vrf: VRF1
+ region: us-west-1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_hub_network_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ site=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ vrf=dict(type="str", required=True),
+ region=dict(type="str", required=True),
+ hub_network=dict(type="dict", options=mso_hub_network_spec()),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "present", ["hub_network"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ site = module.params.get("site")
+ template = module.params.get("template").replace(" ", "")
+ vrf = module.params.get("vrf")
+ region = module.params.get("region")
+ hub_network = module.params.get("hub_network")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ # Get schema objects
+ schema_id, schema_path, schema_obj = mso.query_schema(schema)
+
+ # Get template
+ templates = [t.get("name") for t in schema_obj.get("templates")]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates)))
+
+ # Get site
+ site_id = mso.lookup_site(site)
+
+ # Get site_idx
+ if not schema_obj.get("sites"):
+ mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template))
+ sites = [(s.get("siteId"), s.get("templateName")) for s in schema_obj.get("sites")]
+ if (site_id, template) not in sites:
+ mso.fail_json(msg="Provided site-template association '{0}-{1}' does not exist.".format(site, template))
+
+ # Schema-access uses indexes
+ site_idx = sites.index((site_id, template))
+ # Path-based access uses site_id-template
+ site_template = "{0}-{1}".format(site_id, template)
+
+ # Get VRF
+ vrf_ref = mso.vrf_ref(schema_id=schema_id, template=template, vrf=vrf)
+ vrfs = [v.get("vrfRef") for v in schema_obj.get("sites")[site_idx]["vrfs"]]
+ vrfs_name = [mso.dict_from_ref(v).get("vrfName") for v in vrfs]
+ if vrf_ref not in vrfs:
+ mso.fail_json(msg="Provided vrf '{0}' does not exist. Existing vrfs: {1}".format(vrf, ", ".join(vrfs_name)))
+ vrf_idx = vrfs.index(vrf_ref)
+
+ # Get Region
+ regions = [r.get("name") for r in schema_obj.get("sites")[site_idx]["vrfs"][vrf_idx]["regions"]]
+ if region not in regions:
+ mso.fail_json(msg="Provided region '{0}' does not exist. Existing regions: {1}".format(region, ", ".join(regions)))
+ region_idx = regions.index(region)
+ # Get Region object
+ region_obj = schema_obj.get("sites")[site_idx]["vrfs"][vrf_idx]["regions"][region_idx]
+ region_path = "/sites/{0}/vrfs/{1}/regions/{2}".format(site_template, vrf, region)
+
+ # Get hub network
+ existing_hub_network = region_obj.get("cloudRsCtxProfileToGatewayRouterP")
+ if existing_hub_network is not None:
+ mso.existing = existing_hub_network
+
+ if state == "query":
+ if not mso.existing:
+ mso.fail_json(msg="Hub network not found")
+ mso.exit_json()
+
+ ops = []
+
+ mso.previous = mso.existing
+ if state == "absent":
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op="remove", path=region_path + "/cloudRsCtxProfileToGatewayRouterP"))
+ ops.append(dict(op="replace", path=region_path + "/isTGWAttachment", value=False))
+
+ elif state == "present":
+ new_hub_network = dict(
+ name=hub_network.get("name"),
+ tenantName=hub_network.get("tenant"),
+ )
+ payload = region_obj
+ payload.update(
+ cloudRsCtxProfileToGatewayRouterP=new_hub_network,
+ isTGWAttachment=True,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ ops.append(dict(op="replace", path=region_path, value=mso.sent))
+
+ mso.existing = new_hub_network
+
+ if not module.check_mode and mso.previous != mso.existing:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template.py
new file mode 100644
index 000000000..6f4ece9ca
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template.py
@@ -0,0 +1,263 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_template
+short_description: Manage templates in schemas
+description:
+- Manage templates on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ tenant:
+ description:
+ - The tenant used for this template.
+ type: str
+ required: true
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ schema_description:
+ description:
+ - The description of Schema is supported on versions of MSO that are 3.3 or greater.
+ type: str
+ template_description:
+ description:
+ - The description of template is supported on versions of MSO that are 3.3 or greater.
+ type: str
+ template:
+ description:
+ - The name of the template.
+ type: str
+ aliases: [ name ]
+ display_name:
+ description:
+ - The name as displayed on the MSO web interface.
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+notes:
+- Due to restrictions of the MSO REST API this module creates schemas when needed, and removes them when the last template has been removed.
+seealso:
+- module: cisco.mso.mso_schema
+- module: cisco.mso.mso_schema_site
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new template to a schema
+ cisco.mso.mso_schema_template:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ tenant: Tenant 1
+ schema: Schema 1
+ template: Template 1
+ state: present
+ delegate_to: localhost
+
+- name: Remove a template from a schema
+ cisco.mso.mso_schema_template:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ tenant: Tenant 1
+ schema: Schema 1
+ template: Template 1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a template
+ cisco.mso.mso_schema_template:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ tenant: Tenant 1
+ schema: Schema 1
+ template: Template 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all templates
+ cisco.mso.mso_schema_template:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ tenant: Tenant 1
+ schema: Schema 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ tenant=dict(type="str", required=True),
+ schema=dict(type="str", required=True),
+ schema_description=dict(type="str"),
+ template_description=dict(type="str"),
+ template=dict(type="str", aliases=["name"]),
+ display_name=dict(type="str"),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["template"]],
+ ["state", "present", ["template"]],
+ ],
+ )
+
+ tenant = module.params.get("tenant")
+ schema = module.params.get("schema")
+ schema_description = module.params.get("schema_description")
+ template_description = module.params.get("template_description")
+ template = module.params.get("template")
+ if template is not None:
+ template = template.replace(" ", "")
+ display_name = module.params.get("display_name")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ # Get schema
+ schema_obj = mso.get_obj("schemas", displayName=schema)
+
+ mso.existing = {}
+ if schema_obj:
+ # Schema exists
+ schema_path = "schemas/{id}".format(**schema_obj)
+
+ # Get template
+ templates = [t.get("name") for t in schema_obj.get("templates")]
+ if template:
+ if template in templates:
+ template_idx = templates.index(template)
+ mso.existing = schema_obj.get("templates")[template_idx]
+ else:
+ mso.existing = schema_obj.get("templates")
+ else:
+ schema_path = "schemas"
+
+ if state == "query":
+ if not mso.existing:
+ if template:
+ mso.fail_json(msg="Template '{0}' not found".format(template))
+ else:
+ mso.existing = []
+ mso.exit_json()
+
+ template_path = "/templates/{0}".format(template)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == "absent":
+ mso.proposed = mso.sent = {}
+ if not schema_obj:
+ # There was no schema to begin with
+ pass
+ elif len(templates) == 1 and mso.existing:
+ # There is only one tenant, remove schema
+ mso.existing = {}
+ if not module.check_mode:
+ mso.request(schema_path, method="DELETE")
+ elif mso.existing:
+ # Remove existing template
+ mso.existing = {}
+ ops.append(dict(op="remove", path=template_path))
+
+ elif state == "present":
+ tenant_id = mso.lookup_tenant(tenant)
+
+ if display_name is None:
+ display_name = mso.existing.get("displayName", template)
+
+ if not schema_obj:
+ # Schema does not exist, so we have to create it
+ payload = dict(
+ displayName=schema,
+ templates=[
+ dict(
+ name=template,
+ displayName=display_name,
+ tenantId=tenant_id,
+ )
+ ],
+ sites=[],
+ )
+
+ if schema_description is not None:
+ payload.update(description=schema_description)
+ if template_description is not None:
+ payload["templates"][0].update(description=template_description)
+
+ mso.existing = payload.get("templates")[0]
+
+ if not module.check_mode:
+ mso.request(schema_path, method="POST", data=payload)
+
+ elif mso.existing:
+ # Template exists, so we have to update it
+ payload = dict(
+ name=template,
+ displayName=display_name,
+ description=template_description,
+ tenantId=tenant_id,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ ops.append(dict(op="replace", path=template_path + "/displayName", value=display_name))
+ ops.append(dict(op="replace", path=template_path + "/tenantId", value=tenant_id))
+
+ mso.existing = mso.proposed
+ else:
+ # Template does not exist, so we have to add it
+ payload = dict(
+ name=template,
+ displayName=display_name,
+ tenantId=tenant_id,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ ops.append(dict(op="add", path="/templates/-", value=payload))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp.py
new file mode 100644
index 000000000..9922750f8
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp.py
@@ -0,0 +1,210 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_template_anp
+short_description: Manage Application Network Profiles (ANPs) in schema templates
+description:
+- Manage ANPs in schema templates on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: true
+ anp:
+ description:
+ - The name of the ANP to manage.
+ type: str
+ aliases: [ name ]
+ description:
+ description:
+ - The description of ANP is supported on versions of MSO that are 3.3 or greater.
+ type: str
+ display_name:
+ description:
+ - The name as displayed on the MSO web interface.
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+seealso:
+- module: cisco.mso.mso_schema_template
+- module: cisco.mso.mso_schema_template_anp_epg
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new ANP
+ cisco.mso.mso_schema_template_anp:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ state: present
+ delegate_to: localhost
+
+- name: Remove an ANP
+ cisco.mso.mso_schema_template_anp:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific ANPs
+ cisco.mso.mso_schema_template_anp:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all ANPs
+ cisco.mso.mso_schema_template_anp:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ anp=dict(type="str", aliases=["name"]), # This parameter is not required for querying all objects
+ description=dict(type="str"),
+ display_name=dict(type="str"),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["anp"]],
+ ["state", "present", ["anp"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ template = module.params.get("template").replace(" ", "")
+ anp = module.params.get("anp")
+ description = module.params.get("description")
+ display_name = module.params.get("display_name")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ # Get schema
+ schema_id, schema_path, schema_obj = mso.query_schema(schema)
+
+ # Get template
+ templates = [t.get("name") for t in schema_obj.get("templates")]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates)))
+ template_idx = templates.index(template)
+
+ # Get ANP
+ anps = [a.get("name") for a in schema_obj.get("templates")[template_idx]["anps"]]
+
+ if anp is not None and anp in anps:
+ anp_idx = anps.index(anp)
+ mso.existing = schema_obj.get("templates")[template_idx]["anps"][anp_idx]
+
+ if state == "query":
+ if anp is None:
+ mso.existing = schema_obj.get("templates")[template_idx]["anps"]
+ elif not mso.existing:
+ mso.fail_json(msg="ANP '{anp}' not found".format(anp=anp))
+ mso.exit_json()
+
+ anps_path = "/templates/{0}/anps".format(template)
+ anp_path = "/templates/{0}/anps/{1}".format(template, anp)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == "absent":
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op="remove", path=anp_path))
+
+ elif state == "present":
+ if display_name is None and not mso.existing:
+ display_name = anp
+
+ epgs = []
+ if mso.existing:
+ epgs = None
+
+ payload = dict(
+ name=anp,
+ displayName=display_name,
+ epgs=epgs,
+ )
+
+ if description is not None:
+ payload.update(description=description)
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ if display_name is not None:
+ ops.append(dict(op="replace", path=anp_path + "/displayName", value=display_name))
+ else:
+ ops.append(dict(op="add", path=anps_path + "/-", value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if "anpRef" in mso.previous:
+ del mso.previous["anpRef"]
+
+ if not module.check_mode:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg.py
new file mode 100644
index 000000000..6c021e7f7
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg.py
@@ -0,0 +1,471 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_template_anp_epg
+short_description: Manage Endpoint Groups (EPGs) in schema templates
+description:
+- Manage EPGs in schema templates on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+- Anvitha Jain (@anvitha-jain)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: true
+ anp:
+ description:
+ - The name of the ANP.
+ type: str
+ required: true
+ epg:
+ description:
+ - The name of the EPG to manage.
+ type: str
+ aliases: [ name ]
+ display_name:
+ description:
+ - The name as displayed on the MSO web interface.
+ type: str
+ description:
+ description:
+ - The description as displayed on the MSO web interface.
+ - The description is supported on versions of MSO that are 3.3 or greater.
+ type: str
+# contracts:
+# description:
+# - A list of contracts associated to this ANP.
+# type: list
+ bd:
+ description:
+ - The BD associated to this ANP.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The name of the BD to associate with.
+ required: true
+ type: str
+ schema:
+ description:
+ - The schema that defines the referenced BD.
+ - If this parameter is unspecified, it defaults to the current schema.
+ type: str
+ template:
+ description:
+ - The template that defines the referenced BD.
+ type: str
+ vrf:
+ description:
+ - The VRF associated to this ANP.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The name of the VRF to associate with.
+ required: true
+ type: str
+ schema:
+ description:
+ - The schema that defines the referenced VRF.
+ - If this parameter is unspecified, it defaults to the current schema.
+ type: str
+ template:
+ description:
+ - The template that defines the referenced VRF.
+ type: str
+ subnets:
+ description:
+ - The subnets associated to this ANP.
+ type: list
+ elements: dict
+ suboptions:
+ subnet:
+ description:
+ - The IP range in CIDR notation.
+ type: str
+ required: true
+ aliases: [ ip ]
+ description:
+ description:
+ - The description of this subnet.
+ type: str
+ scope:
+ description:
+ - The scope of the subnet.
+ type: str
+ default: private
+ choices: [ private, public ]
+ shared:
+ description:
+ - Whether this subnet is shared between VRFs.
+ type: bool
+ default: false
+ no_default_gateway:
+ description:
+ - Whether this subnet has a default gateway.
+ type: bool
+ default: false
+ useg_epg:
+ description:
+ - Whether this is a USEG EPG.
+ type: bool
+# useg_epg_attributes:
+# description:
+# - A dictionary consisting of USEG attributes.
+# type: dict
+ intra_epg_isolation:
+ description:
+ - Whether intra EPG isolation is enforced.
+ - When not specified, this parameter defaults to C(unenforced).
+ type: str
+ choices: [ enforced, unenforced ]
+ intersite_multicast_source:
+ description:
+ - Whether intersite multicast source is enabled.
+ - When not specified, this parameter defaults to C(no).
+ type: bool
+ proxy_arp:
+ description:
+ - Whether proxy arp is enabled.
+ - When not specified, this parameter defaults to C(no).
+ type: bool
+ preferred_group:
+ description:
+ - Whether this EPG is added to preferred group or not.
+ - When not specified, this parameter defaults to C(no).
+ type: bool
+ qos_level:
+ description:
+ - Quality of Service (QoS) allows you to classify the network traffic in the fabric.
+ - It helps prioritize and police the traffic flow to help avoid congestion in the network.
+ - The Contract QoS Level parameter is supported on versions of MSO that are 3.1 or greater.
+ type: str
+ epg_type:
+ description:
+ - The EPG type parameter is supported on versions of MSO that are 3.3 or greater.
+ type: str
+ choices: [ application, service ]
+ deployment_type:
+ description:
+ - The deployment_type parameter indicates how and where the service is deployed.
+ - This parameter is available only when epg_type is service.
+ type: str
+ choices: [ cloud_native, cloud_native_managed, third_party ]
+ access_type:
+ description:
+ - This parameter indicates how the service will be accessed.
+ - It is only available when epg_type is service.
+ type: str
+ choices: [ private, public, public_and_private ]
+ service_type:
+ description:
+ - The service_type parameter refers to the type of cloud services.
+ - Only certain deployment types, and certain access types within each deployment type, are supported for each service type.
+ - This parameter is available only when epg_type is service.
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+seealso:
+- module: cisco.mso.mso_schema_template_anp
+- module: cisco.mso.mso_schema_template_anp_epg_subnet
+- module: cisco.mso.mso_schema_template_bd
+- module: cisco.mso.mso_schema_template_contract_filter
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new EPG
+ cisco.mso.mso_schema_template_anp_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ bd:
+ name: bd1
+ vrf:
+ name: vrf1
+ state: present
+ delegate_to: localhost
+
+- name: Add a new EPG with preferred group.
+ cisco.mso.mso_schema_template_anp_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ state: present
+ preferred_group: true
+ delegate_to: localhost
+
+- name: Remove an EPG
+ cisco.mso.mso_schema_template_anp_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ bd:
+ name: bd1
+ vrf:
+ name: vrf1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific EPG
+ cisco.mso.mso_schema_template_anp_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ bd:
+ name: bd1
+ vrf:
+ name: vrf1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all EPGs
+ cisco.mso.mso_schema_template_anp_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ bd:
+ name: bd1
+ vrf:
+ name: vrf1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_reference_spec, mso_epg_subnet_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ anp=dict(type="str", required=True),
+ epg=dict(type="str", aliases=["name"]), # This parameter is not required for querying all objects
+ description=dict(type="str"),
+ bd=dict(type="dict", options=mso_reference_spec()),
+ vrf=dict(type="dict", options=mso_reference_spec()),
+ display_name=dict(type="str"),
+ useg_epg=dict(type="bool"),
+ intra_epg_isolation=dict(type="str", choices=["enforced", "unenforced"]),
+ intersite_multicast_source=dict(type="bool"),
+ proxy_arp=dict(type="bool"),
+ subnets=dict(type="list", elements="dict", options=mso_epg_subnet_spec()),
+ qos_level=dict(type="str"),
+ epg_type=dict(type="str", choices=["application", "service"]),
+ deployment_type=dict(type="str", choices=["cloud_native", "cloud_native_managed", "third_party"]),
+ service_type=dict(type="str"),
+ access_type=dict(type="str", choices=["private", "public", "public_and_private"]),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ preferred_group=dict(type="bool"),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["epg"]],
+ ["state", "present", ["epg"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ template = module.params.get("template").replace(" ", "")
+ anp = module.params.get("anp")
+ epg = module.params.get("epg")
+ description = module.params.get("description")
+ display_name = module.params.get("display_name")
+ bd = module.params.get("bd")
+ if bd is not None and bd.get("template") is not None:
+ bd["template"] = bd.get("template").replace(" ", "")
+ vrf = module.params.get("vrf")
+ if vrf is not None and vrf.get("template") is not None:
+ vrf["template"] = vrf.get("template").replace(" ", "")
+ useg_epg = module.params.get("useg_epg")
+ intra_epg_isolation = module.params.get("intra_epg_isolation")
+ intersite_multicast_source = module.params.get("intersite_multicast_source")
+ proxy_arp = module.params.get("proxy_arp")
+ subnets = module.params.get("subnets")
+ qos_level = module.params.get("qos_level")
+ epg_type = module.params.get("epg_type")
+ deployment_type = module.params.get("deployment_type")
+ service_type = module.params.get("service_type")
+ access_type = module.params.get("access_type")
+ state = module.params.get("state")
+ preferred_group = module.params.get("preferred_group")
+
+ mso = MSOModule(module)
+
+ # Get schema objects
+ schema_id, schema_path, schema_obj = mso.query_schema(schema)
+
+ # Get template
+ templates = [t.get("name") for t in schema_obj.get("templates")]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates)))
+ template_idx = templates.index(template)
+
+ # Get ANP
+ anps = [a.get("name") for a in schema_obj.get("templates")[template_idx]["anps"]]
+ if anp not in anps:
+ mso.fail_json(msg="Provided anp '{0}' does not exist. Existing anps: {1}".format(anp, ", ".join(anps)))
+ anp_idx = anps.index(anp)
+
+ # Get EPG
+ epgs = [e.get("name") for e in schema_obj.get("templates")[template_idx]["anps"][anp_idx]["epgs"]]
+ if epg is not None and epg in epgs:
+ epg_idx = epgs.index(epg)
+ mso.existing = schema_obj.get("templates")[template_idx]["anps"][anp_idx]["epgs"][epg_idx]
+
+ if state == "query":
+ if epg is None:
+ mso.existing = schema_obj.get("templates")[template_idx]["anps"][anp_idx]["epgs"]
+ elif not mso.existing:
+ mso.fail_json(msg="EPG '{epg}' not found".format(epg=epg))
+
+ if "bdRef" in mso.existing:
+ mso.existing["bdRef"] = mso.dict_from_ref(mso.existing["bdRef"])
+ if "vrfRef" in mso.existing:
+ mso.existing["vrfRef"] = mso.dict_from_ref(mso.existing["vrfRef"])
+ mso.exit_json()
+
+ epgs_path = "/templates/{0}/anps/{1}/epgs".format(template, anp)
+ epg_path = "/templates/{0}/anps/{1}/epgs/{2}".format(template, anp, epg)
+ service_path = "{0}/cloudServiceEpgConfig".format(epg_path)
+ ops = []
+ cloud_service_epg_config = {}
+
+ mso.previous = mso.existing
+ if state == "absent":
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op="remove", path=epg_path))
+
+ elif state == "present":
+ bd_ref = mso.make_reference(bd, "bd", schema_id, template)
+ vrf_ref = mso.make_reference(vrf, "vrf", schema_id, template)
+ subnets = mso.make_subnets(subnets, is_bd_subnet=False)
+
+ if display_name is None and not mso.existing:
+ display_name = epg
+
+ payload = dict(
+ name=epg,
+ displayName=display_name,
+ uSegEpg=useg_epg,
+ intraEpg=intra_epg_isolation,
+ mCastSource=intersite_multicast_source,
+ proxyArp=proxy_arp,
+ # FIXME: Missing functionality
+ # uSegAttrs=[],
+ subnets=subnets,
+ bdRef=bd_ref,
+ preferredGroup=preferred_group,
+ vrfRef=vrf_ref,
+ )
+ if description is not None:
+ payload.update(description=description)
+ if qos_level is not None:
+ payload.update(prio=qos_level)
+ if epg_type is not None:
+ payload.update(epgType=epg_type)
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ # Clean contractRef to fix api issue
+ for contract in mso.sent.get("contractRelationships"):
+ contract["contractRef"] = mso.dict_from_ref(contract.get("contractRef"))
+ ops.append(dict(op="replace", path=epg_path, value=mso.sent))
+ else:
+ ops.append(dict(op="add", path=epgs_path + "/-", value=mso.sent))
+
+ if epg_type == "service":
+ access_type_map = {
+ "private": "Private",
+ "public": "Public",
+ "public_and_private": "PublicAndPrivate",
+ }
+ deployment_type_map = {
+ "cloud_native": "CloudNative",
+ "cloud_native_managed": "CloudNativeManaged",
+ "third_party": "Third-party",
+ }
+ if cloud_service_epg_config != {}:
+ cloud_service_epg_config.update(
+ dict(deploymentType=deployment_type_map[deployment_type], serviceType=service_type, accessType=access_type_map[access_type])
+ )
+ ops.append(dict(op="replace", path=service_path, value=cloud_service_epg_config))
+ else:
+ cloud_service_epg_config.update(
+ dict(deploymentType=deployment_type_map[deployment_type], serviceType=service_type, accessType=access_type_map[access_type])
+ )
+ ops.append(dict(op="add", path=service_path, value=cloud_service_epg_config))
+
+ mso.existing = mso.proposed
+
+ if "epgRef" in mso.previous:
+ del mso.previous["epgRef"]
+ if "bdRef" in mso.previous and mso.previous["bdRef"] != "":
+ mso.previous["bdRef"] = mso.dict_from_ref(mso.previous["bdRef"])
+ if "vrfRef" in mso.previous and mso.previous["bdRef"] != "":
+ mso.previous["vrfRef"] = mso.dict_from_ref(mso.previous["vrfRef"])
+
+ if not module.check_mode and mso.proposed != mso.previous:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_contract.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_contract.py
new file mode 100644
index 000000000..d8c881d5d
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_contract.py
@@ -0,0 +1,263 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_template_anp_epg_contract
+short_description: Manage EPG contracts in schema templates
+description:
+- Manage EPG contracts in schema templates on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template to change.
+ type: str
+ required: true
+ anp:
+ description:
+ - The name of the ANP.
+ type: str
+ required: true
+ epg:
+ description:
+ - The name of the EPG to manage.
+ type: str
+ required: true
+ contract:
+ description:
+ - A contract associated to this EPG.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The name of the Contract to associate with.
+ required: true
+ type: str
+ schema:
+ description:
+ - The schema that defines the referenced BD.
+ - If this parameter is unspecified, it defaults to the current schema.
+ type: str
+ template:
+ description:
+ - The template that defines the referenced BD.
+ type: str
+ type:
+ description:
+ - The type of contract.
+ type: str
+ required: true
+ choices: [ consumer, provider ]
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+seealso:
+- module: cisco.mso.mso_schema_template_anp_epg
+- module: cisco.mso.mso_schema_template_contract_filter
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a contract to an EPG
+ cisco.mso.mso_schema_template_anp_epg_contract:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ contract:
+ name: Contract 1
+ type: consumer
+ state: present
+ delegate_to: localhost
+
+- name: Remove a Contract
+ cisco.mso.mso_schema_template_anp_epg_contract:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ contract:
+ name: Contract 1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific Contract
+ cisco.mso.mso_schema_template_anp_epg_contract:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ contract:
+ name: Contract 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all Contracts
+ cisco.mso.mso_schema_template_anp_epg_contract:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_contractref_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ anp=dict(type="str", required=True),
+ epg=dict(type="str", required=True),
+ contract=dict(type="dict", options=mso_contractref_spec()),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["contract"]],
+ ["state", "present", ["contract"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ template = module.params.get("template").replace(" ", "")
+ anp = module.params.get("anp")
+ epg = module.params.get("epg")
+ contract = module.params.get("contract")
+ if contract is not None and contract.get("template") is not None:
+ contract["template"] = contract.get("template").replace(" ", "")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ if contract:
+ if contract.get("schema") is None:
+ contract["schema"] = schema
+ contract["schema_id"] = mso.lookup_schema(contract.get("schema"))
+ if contract.get("template") is None:
+ contract["template"] = template
+
+ # Get schema
+ schema_id, schema_path, schema_obj = mso.query_schema(schema)
+
+ # Get template
+ templates = [t.get("name") for t in schema_obj.get("templates")]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates)))
+ template_idx = templates.index(template)
+
+ # Get ANP
+ anps = [a.get("name") for a in schema_obj.get("templates")[template_idx]["anps"]]
+ if anp not in anps:
+ mso.fail_json(msg="Provided anp '{0}' does not exist. Existing anps: {1}".format(anp, ", ".join(anps)))
+ anp_idx = anps.index(anp)
+
+ # Get EPG
+ epgs = [e.get("name") for e in schema_obj.get("templates")[template_idx]["anps"][anp_idx]["epgs"]]
+ if epg not in epgs:
+ mso.fail_json(msg="Provided epg '{epg}' does not exist. Existing epgs: {epgs}".format(epg=epg, epgs=", ".join(epgs)))
+ epg_idx = epgs.index(epg)
+
+ # Get Contract
+ if contract:
+ contracts = [
+ (c.get("contractRef"), c.get("relationshipType"))
+ for c in schema_obj.get("templates")[template_idx]["anps"][anp_idx]["epgs"][epg_idx]["contractRelationships"]
+ ]
+ contract_ref = mso.contract_ref(**contract)
+ if (contract_ref, contract.get("type")) in contracts:
+ contract_idx = contracts.index((contract_ref, contract.get("type")))
+ contract_path = "/templates/{0}/anps/{1}/epgs/{2}/contractRelationships/{3}".format(template, anp, epg, contract_idx)
+ mso.existing = schema_obj.get("templates")[template_idx]["anps"][anp_idx]["epgs"][epg_idx]["contractRelationships"][contract_idx]
+
+ if state == "query":
+ if not contract:
+ mso.existing = schema_obj.get("templates")[template_idx]["anps"][anp_idx]["epgs"][epg_idx]["contractRelationships"]
+ elif not mso.existing:
+ mso.fail_json(msg="Contract '{0}' not found".format(contract_ref))
+
+ if "contractRef" in mso.existing:
+ mso.existing["contractRef"] = mso.dict_from_ref(mso.existing.get("contractRef"))
+ mso.exit_json()
+
+ contracts_path = "/templates/{0}/anps/{1}/epgs/{2}/contractRelationships".format(template, anp, epg)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == "absent":
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op="remove", path=contract_path))
+
+ elif state == "present":
+ payload = dict(
+ relationshipType=contract.get("type"),
+ contractRef=dict(
+ contractName=contract.get("name"),
+ templateName=contract.get("template"),
+ schemaId=contract.get("schema_id"),
+ ),
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op="replace", path=contract_path, value=mso.sent))
+ else:
+ ops.append(dict(op="add", path=contracts_path + "/-", value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if "contractRef" in mso.previous:
+ mso.previous["contractRef"] = mso.dict_from_ref(mso.previous.get("contractRef"))
+ if not module.check_mode and mso.proposed != mso.previous:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_selector.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_selector.py
new file mode 100644
index 000000000..bd98fc321
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_selector.py
@@ -0,0 +1,277 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_template_anp_epg_selector
+short_description: Manage EPG selector in schema templates
+description:
+- Manage EPG selector in schema templates on Cisco ACI Multi-Site.
+author:
+- Cindy Zhao (@cizhao)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template to change.
+ type: str
+ required: true
+ anp:
+ description:
+ - The name of the ANP.
+ type: str
+ required: true
+ epg:
+ description:
+ - The name of the EPG to manage.
+ type: str
+ required: true
+ selector:
+ description:
+ - The name of the selector.
+ type: str
+ expressions:
+ description:
+ - Expressions associated to this selector.
+ type: list
+ elements: dict
+ suboptions:
+ type:
+ description:
+ - The name of the expression.
+ required: true
+ type: str
+ aliases: [ tag ]
+ operator:
+ description:
+ - The operator associated to the expression.
+ required: true
+ type: str
+ choices: [ not_in, in, equals, not_equals, has_key, does_not_have_key ]
+ value:
+ description:
+ - The value associated to the expression.
+ - If the operator is in or not_in, the value should be a comma separated str.
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+seealso:
+- module: cisco.mso.mso_schema_template_anp_epg
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a selector to an EPG
+ cisco.mso.mso_schema_template_anp_epg_selector:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ selector: selector_1
+ expressions:
+ - type: expression_1
+ operator: in
+ value: test
+ state: present
+ delegate_to: localhost
+
+- name: Remove a Selector
+ cisco.mso.mso_schema_template_anp_epg_selector:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ selector: selector_1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific Selector
+ cisco.mso.mso_schema_template_anp_epg_selector:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ selector: selector_1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all Selectors
+ cisco.mso.mso_schema_template_anp_epg_selector:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_expression_spec
+
+EXPRESSION_KEYS = {
+ "not_in": "notIn",
+ "not_equals": "notEquals",
+ "has_key": "keyExist",
+ "does_not_have_key": "keyNotExist",
+ "in": "in",
+ "equals": "equals",
+}
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ anp=dict(type="str", required=True),
+ epg=dict(type="str", required=True),
+ selector=dict(type="str"),
+ expressions=dict(type="list", elements="dict", options=mso_expression_spec()),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["selector"]],
+ ["state", "present", ["selector"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ template = module.params.get("template").replace(" ", "")
+ anp = module.params.get("anp")
+ epg = module.params.get("epg")
+ selector = module.params.get("selector")
+ expressions = module.params.get("expressions")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ # Get schema
+ schema_id, schema_path, schema_obj = mso.query_schema(schema)
+
+ # Get template
+ templates = [t.get("name") for t in schema_obj.get("templates")]
+ if template not in templates:
+ mso.fail_json(
+ msg="Provided template '{template}' does not exist. Existing templates: {templates}".format(template=template, templates=", ".join(templates))
+ )
+ template_idx = templates.index(template)
+
+ # Get ANP
+ anps = [a.get("name") for a in schema_obj.get("templates")[template_idx]["anps"]]
+ if anp not in anps:
+ mso.fail_json(msg="Provided anp '{anp}' does not exist. Existing anps: {anps}".format(anp=anp, anps=", ".join(anps)))
+ anp_idx = anps.index(anp)
+
+ # Get EPG
+ epgs = [e.get("name") for e in schema_obj.get("templates")[template_idx]["anps"][anp_idx]["epgs"]]
+ if epg not in epgs:
+ mso.fail_json(msg="Provided epg '{epg}' does not exist. Existing epgs: {epgs}".format(epg=epg, epgs=", ".join(epgs)))
+ epg_idx = epgs.index(epg)
+
+ # Get Selector
+ if selector and " " in selector:
+ mso.fail_json(msg="There should not be any space in selector name.")
+ selectors = [s.get("name") for s in schema_obj.get("templates")[template_idx]["anps"][anp_idx]["epgs"][epg_idx]["selectors"]]
+ if selector in selectors:
+ selector_idx = selectors.index(selector)
+ selector_path = "/templates/{0}/anps/{1}/epgs/{2}/selectors/{3}".format(template, anp, epg, selector_idx)
+ mso.existing = schema_obj.get("templates")[template_idx]["anps"][anp_idx]["epgs"][epg_idx]["selectors"][selector_idx]
+
+ if state == "query":
+ if selector is None:
+ mso.existing = schema_obj.get("templates")[template_idx]["anps"][anp_idx]["epgs"][epg_idx]["selectors"]
+ elif not mso.existing:
+ mso.fail_json(msg="Selector '{selector}' not found".format(selector=selector))
+ mso.exit_json()
+
+ selectors_path = "/templates/{0}/anps/{1}/epgs/{2}/selectors/-".format(template, anp, epg)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == "absent":
+ mso.sent = mso.existing = {}
+ ops.append(dict(op="remove", path=selector_path))
+
+ elif state == "present":
+ # Get expressions
+ all_expressions = []
+ if expressions:
+ for expression in expressions:
+ tag = expression.get("type")
+ operator = expression.get("operator")
+ value = expression.get("value")
+ if " " in tag:
+ mso.fail_json(msg="There should not be any space in 'type' attribute of expression '{0}'".format(tag))
+ if operator in ["has_key", "does_not_have_key"] and value:
+ mso.fail_json(msg="Attribute 'value' is not supported for operator '{0}' in expression '{1}'".format(operator, tag))
+ if operator in ["not_in", "in", "equals", "not_equals"] and not value:
+ mso.fail_json(msg="Attribute 'value' needed for operator '{0}' in expression '{1}'".format(operator, tag))
+ all_expressions.append(
+ dict(
+ key="Custom:" + tag,
+ operator=EXPRESSION_KEYS.get(operator),
+ value=value,
+ )
+ )
+
+ payload = dict(
+ name=selector,
+ expressions=all_expressions,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op="replace", path=selector_path, value=mso.sent))
+ else:
+ ops.append(dict(op="add", path=selectors_path, value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode and mso.existing != mso.previous:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_subnet.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_subnet.py
new file mode 100644
index 000000000..b060ddd5e
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_subnet.py
@@ -0,0 +1,256 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_template_anp_epg_subnet
+short_description: Manage EPG subnets in schema templates
+description:
+- Manage EPG subnets in schema templates on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template to change.
+ type: str
+ required: true
+ anp:
+ description:
+ - The name of the ANP.
+ type: str
+ required: true
+ epg:
+ description:
+ - The name of the EPG to manage.
+ type: str
+ required: true
+ subnet:
+ description:
+ - The IP range in CIDR notation.
+ type: str
+ required: true
+ aliases: [ ip ]
+ description:
+ description:
+ - The description of this subnet.
+ type: str
+ scope:
+ description:
+ - The scope of the subnet.
+ type: str
+ default: private
+ choices: [ private, public ]
+ shared:
+ description:
+ - Whether this subnet is shared between VRFs.
+ type: bool
+ default: false
+ no_default_gateway:
+ description:
+ - Whether this subnet has a default gateway.
+ type: bool
+ default: false
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+notes:
+- Due to restrictions of the MSO REST API concurrent modifications to EPG subnets can be dangerous and corrupt data.
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new subnet to an EPG
+ cisco.mso.mso_schema_template_anp_epg_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ subnet: 10.0.0.0/24
+ state: present
+ delegate_to: localhost
+
+- name: Remove a subnet from an EPG
+ cisco.mso.mso_schema_template_anp_epg_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ subnet: 10.0.0.0/24
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific EPG subnet
+ cisco.mso.mso_schema_template_anp_epg_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ subnet: 10.0.0.0/24
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all EPGs subnets
+ cisco.mso.mso_schema_template_anp_epg_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_epg_subnet_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ anp=dict(type="str", required=True),
+ epg=dict(type="str", required=True),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+ argument_spec.update(mso_epg_subnet_spec())
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["subnet"]],
+ ["state", "present", ["subnet"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ template = module.params.get("template").replace(" ", "")
+ anp = module.params.get("anp")
+ epg = module.params.get("epg")
+ subnet = module.params.get("subnet")
+ description = module.params.get("description")
+ scope = module.params.get("scope")
+ shared = module.params.get("shared")
+ no_default_gateway = module.params.get("no_default_gateway")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ # Get schema
+ schema_id, schema_path, schema_obj = mso.query_schema(schema)
+
+ # Get template
+ templates = [t.get("name") for t in schema_obj.get("templates")]
+ if template not in templates:
+ mso.fail_json(
+ msg="Provided template '{template}' does not exist. Existing templates: {templates}".format(template=template, templates=", ".join(templates))
+ )
+ template_idx = templates.index(template)
+
+ # Get ANP
+ anps = [a.get("name") for a in schema_obj.get("templates")[template_idx]["anps"]]
+ if anp not in anps:
+ mso.fail_json(msg="Provided anp '{anp}' does not exist. Existing anps: {anps}".format(anp=anp, anps=", ".join(anps)))
+ anp_idx = anps.index(anp)
+
+ # Get EPG
+ epgs = [e.get("name") for e in schema_obj.get("templates")[template_idx]["anps"][anp_idx]["epgs"]]
+ if epg not in epgs:
+ mso.fail_json(msg="Provided epg '{epg}' does not exist. Existing epgs: {epgs}".format(epg=epg, epgs=", ".join(epgs)))
+ epg_idx = epgs.index(epg)
+
+ # Get Subnet
+ subnets = [s.get("ip") for s in schema_obj.get("templates")[template_idx]["anps"][anp_idx]["epgs"][epg_idx]["subnets"]]
+ if subnet in subnets:
+ subnet_idx = subnets.index(subnet)
+ # FIXME: Changes based on index are DANGEROUS
+ subnet_path = "/templates/{0}/anps/{1}/epgs/{2}/subnets/{3}".format(template, anp, epg, subnet_idx)
+ mso.existing = schema_obj.get("templates")[template_idx]["anps"][anp_idx]["epgs"][epg_idx]["subnets"][subnet_idx]
+
+ if state == "query":
+ if subnet is None:
+ mso.existing = schema_obj.get("templates")[template_idx]["anps"][anp_idx]["epgs"][epg_idx]["subnets"]
+ elif not mso.existing:
+ mso.fail_json(msg="Subnet '{subnet}' not found".format(subnet=subnet))
+ mso.exit_json()
+
+ subnets_path = "/templates/{0}/anps/{1}/epgs/{2}/subnets".format(template, anp, epg)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == "absent":
+ if mso.existing:
+ mso.existing = {}
+ ops.append(dict(op="remove", path=subnet_path))
+
+ elif state == "present":
+ if not mso.existing:
+ if description is None:
+ description = subnet
+ if scope is None:
+ scope = "private"
+ if shared is None:
+ shared = False
+ if no_default_gateway is None:
+ no_default_gateway = False
+
+ payload = dict(
+ ip=subnet,
+ description=description,
+ scope=scope,
+ shared=shared,
+ noDefaultGateway=no_default_gateway,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op="replace", path=subnet_path, value=mso.sent))
+ else:
+ ops.append(dict(op="add", path=subnets_path + "/-", value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd.py
new file mode 100644
index 000000000..0793e844d
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd.py
@@ -0,0 +1,566 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# Copyright: (c) 2021, Shreyas Srish (@shrsr) <ssrish@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_template_bd
+short_description: Manage Bridge Domains (BDs) in schema templates
+description:
+- Manage BDs in schema templates on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+- Shreyas Srish (@shrsr)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template.
+ - Display Name of template for operations can only be used in some versions of mso.
+ - Use the name of template instead of Display Name to avoid discrepency.
+ type: str
+ required: true
+ bd:
+ description:
+ - The name of the BD to manage.
+ type: str
+ aliases: [ name ]
+ display_name:
+ description:
+ - The name as displayed on the MSO web interface.
+ type: str
+ description:
+ description:
+ - The description of BD is supported on versions of MSO that are 3.3 or greater.
+ type: str
+ vrf:
+ description:
+ - The VRF associated to this BD. This is required only when creating a new BD.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The name of the VRF to associate with.
+ required: true
+ type: str
+ schema:
+ description:
+ - The schema that defines the referenced VRF.
+ - If this parameter is unspecified, it defaults to the current schema.
+ type: str
+ template:
+ description:
+ - The template that defines the referenced VRF.
+ - If this parameter is unspecified, it defaults to the current template.
+ type: str
+ dhcp_policy:
+ description:
+ - The DHCP Policy
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The name of the DHCP Relay Policy
+ type: str
+ required: true
+ version:
+ description:
+ - The version of DHCP Relay Policy
+ type: int
+ required: true
+ dhcp_option_policy:
+ description:
+ - The DHCP Option Policy
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The name of the DHCP Option Policy
+ type: str
+ required: true
+ version:
+ description:
+ - The version of the DHCP Option Policy
+ type: int
+ required: true
+ dhcp_policies:
+ description:
+ - A list DHCP Policies to be assciated with the BD
+ - This option can only be used on versions of MSO that are 3.1.1h or greater.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - The name of the DHCP Relay Policy
+ type: str
+ required: true
+ version:
+ description:
+ - The version of DHCP Relay Policy
+ type: int
+ required: true
+ dhcp_option_policy:
+ description:
+ - The DHCP Option Policy
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The name of the DHCP Option Policy
+ type: str
+ required: true
+ version:
+ description:
+ - The version of the DHCP Option Policy
+ type: int
+ required: true
+ subnets:
+ description:
+ - The subnets associated to this BD.
+ type: list
+ elements: dict
+ suboptions:
+ subnet:
+ description:
+ - The IP range in CIDR notation.
+ type: str
+ required: true
+ aliases: [ ip ]
+ description:
+ description:
+ - The description of this subnet.
+ type: str
+ scope:
+ description:
+ - The scope of the subnet.
+ type: str
+ default: private
+ choices: [ private, public ]
+ shared:
+ description:
+ - Whether this subnet is shared between VRFs.
+ type: bool
+ default: false
+ no_default_gateway:
+ description:
+ - Whether this subnet has a default gateway.
+ type: bool
+ default: false
+ querier:
+ description:
+ - Whether this subnet is an IGMP querier.
+ type: bool
+ default: false
+ virtual:
+ description:
+ - Treat as Virtual IP Address.
+ type: bool
+ default: false
+ primary:
+ description:
+ - Treat as Primary Subnet.
+ - There can be only one primary subnet per address family under a BD.
+ - This option can only be used on versions of MSO that are 3.1.1h or greater.
+ type: bool
+ default: false
+ intersite_bum_traffic:
+ description:
+ - Whether to allow intersite BUM traffic.
+ type: bool
+ optimize_wan_bandwidth:
+ description:
+ - Whether to optimize WAN bandwidth.
+ type: bool
+ layer2_stretch:
+ description:
+ - Whether to enable L2 stretch.
+ type: bool
+ default: true
+ layer2_unknown_unicast:
+ description:
+ - Layer2 unknown unicast.
+ type: str
+ choices: [ flood, proxy ]
+ layer3_multicast:
+ description:
+ - Whether to enable L3 multicast.
+ type: bool
+ unknown_multicast_flooding:
+ description:
+ - Unknown Multicast Flooding can either be Flood or Optimized Flooding.
+ type: str
+ choices: [ flood, optimized_flooding ]
+ multi_destination_flooding:
+ description:
+ - Multi-Destination Flooding can either be Flood in BD, Drop or Flood in Encapsulation.
+ - Flood in Encapsulation is only supported on versions of MSO that are 3.3 or greater.
+ type: str
+ choices: [ flood_in_bd, drop, encap-flood ]
+ ipv6_unknown_multicast_flooding:
+ description:
+ - IPv6 Unknown Multicast Flooding can either be Flood or Optimized Flooding
+ type: str
+ choices: [ flood, optimized_flooding ]
+ arp_flooding:
+ description:
+ - ARP Flooding
+ type: bool
+ virtual_mac_address:
+ description:
+ - Virtual MAC Address
+ type: str
+ unicast_routing:
+ description:
+ - Unicast Routing
+ - This option can only be used on versions of MSO that are 3.1.1h or greater.
+ type: bool
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new BD
+ cisco.mso.mso_schema_template_bd:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ bd: BD 1
+ vrf:
+ name: VRF1
+ state: present
+ delegate_to: localhost
+
+- name: Add a new BD from another Schema
+ mso_schema_template_bd:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ bd: BD 1
+ vrf:
+ name: VRF1
+ schema: Schema Origin
+ template: Template Origin
+ state: present
+ delegate_to: localhost
+
+- name: Add bd with options available on version 3.1
+ mso_schema_template_bd:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ bd: BD 1
+ intersite_bum_traffic: true
+ optimize_wan_bandwidth: false
+ layer2_stretch: true
+ layer2_unknown_unicast: flood
+ layer3_multicast: false
+ unknown_multicast_flooding: flood
+ multi_destination_flooding: drop
+ ipv6_unknown_multicast_flooding: flood
+ arp_flooding: false
+ virtual_mac_address: 00:00:5E:00:01:3C
+ subnets:
+ - subnet: 10.0.0.128/24
+ - subnet: 10.0.1.254/24
+ description: 1234567890
+ - ip: 192.168.0.254/24
+ description: "My description for a subnet"
+ scope: private
+ shared: false
+ no_default_gateway: true
+ vrf:
+ name: vrf1
+ schema: Test
+ template: Template1
+ dhcp_policy:
+ name: ansible_test
+ version: 1
+ dhcp_option_policy:
+ name: ansible_test_option
+ version: 1
+ state: present
+
+- name: Add bd with options available on version 3.1.1h or greater
+ mso_schema_template_bd:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ bd: BD 1
+ intersite_bum_traffic: true
+ optimize_wan_bandwidth: false
+ layer2_stretch: true
+ layer2_unknown_unicast: flood
+ layer3_multicast: false
+ unknown_multicast_flooding: flood
+ multi_destination_flooding: drop
+ ipv6_unknown_multicast_flooding: flood
+ arp_flooding: false
+ virtual_mac_address: 00:00:5E:00:01:3C
+ unicast_routing: true
+ subnets:
+ - subnet: 10.0.0.128/24
+ primary: true
+ - subnet: 10.0.1.254/24
+ description: 1234567890
+ virtual: true
+ - ip: 192.168.0.254/24
+ description: "My description for a subnet"
+ scope: private
+ shared: false
+ no_default_gateway: true
+ vrf:
+ name: vrf1
+ schema: Schema1
+ template: Template1
+ dhcp_policies:
+ - name: ansible_test
+ version: 1
+ dhcp_option_policy:
+ name: ansible_test_option
+ version: 1
+ - name: ansible_test2
+ version: 1
+ dhcp_option_policy:
+ name: ansible_test_option2
+ version: 1
+ - name: ansible_test3
+ version: 1
+ dhcp_option_policy:
+ name: ansible_test_option
+ version: 1
+ state: present
+ delegate_to: localhost
+
+- name: Remove a BD
+ cisco.mso.mso_schema_template_bd:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ bd: BD1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific BD
+ cisco.mso.mso_schema_template_bd:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ bd: BD1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all BDs
+ cisco.mso.mso_schema_template_bd:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_reference_spec, mso_bd_subnet_spec, mso_dhcp_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ bd=dict(type="str", aliases=["name"]), # This parameter is not required for querying all objects
+ display_name=dict(type="str"),
+ description=dict(type="str"),
+ intersite_bum_traffic=dict(type="bool"),
+ optimize_wan_bandwidth=dict(type="bool"),
+ layer2_stretch=dict(type="bool", default="true"),
+ layer2_unknown_unicast=dict(type="str", choices=["flood", "proxy"]),
+ layer3_multicast=dict(type="bool"),
+ vrf=dict(type="dict", options=mso_reference_spec()),
+ dhcp_policy=dict(type="dict", options=mso_dhcp_spec()),
+ dhcp_policies=dict(type="list", elements="dict", options=mso_dhcp_spec()),
+ subnets=dict(type="list", elements="dict", options=mso_bd_subnet_spec()),
+ unknown_multicast_flooding=dict(type="str", choices=["optimized_flooding", "flood"]),
+ multi_destination_flooding=dict(type="str", choices=["flood_in_bd", "drop", "encap-flood"]),
+ ipv6_unknown_multicast_flooding=dict(type="str", choices=["optimized_flooding", "flood"]),
+ arp_flooding=dict(type="bool"),
+ virtual_mac_address=dict(type="str"),
+ unicast_routing=dict(type="bool"),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["bd"]],
+ ["state", "present", ["bd", "vrf"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ template = module.params.get("template").replace(" ", "")
+ bd = module.params.get("bd")
+ display_name = module.params.get("display_name")
+ description = module.params.get("description")
+ intersite_bum_traffic = module.params.get("intersite_bum_traffic")
+ optimize_wan_bandwidth = module.params.get("optimize_wan_bandwidth")
+ layer2_stretch = module.params.get("layer2_stretch")
+ layer2_unknown_unicast = module.params.get("layer2_unknown_unicast")
+ layer3_multicast = module.params.get("layer3_multicast")
+ vrf = module.params.get("vrf")
+ if vrf is not None and vrf.get("template") is not None:
+ vrf["template"] = vrf.get("template").replace(" ", "")
+ dhcp_policy = module.params.get("dhcp_policy")
+ dhcp_policies = module.params.get("dhcp_policies")
+ subnets = module.params.get("subnets")
+ unknown_multicast_flooding = module.params.get("unknown_multicast_flooding")
+ multi_destination_flooding = module.params.get("multi_destination_flooding")
+ ipv6_unknown_multicast_flooding = module.params.get("ipv6_unknown_multicast_flooding")
+ arp_flooding = module.params.get("arp_flooding")
+ virtual_mac_address = module.params.get("virtual_mac_address")
+ unicast_routing = module.params.get("unicast_routing")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ # Map choices
+ if unknown_multicast_flooding == "optimized_flooding":
+ unknown_multicast_flooding = "opt-flood"
+ if ipv6_unknown_multicast_flooding == "optimized_flooding":
+ ipv6_unknown_multicast_flooding = "opt-flood"
+ if multi_destination_flooding == "flood_in_bd":
+ multi_destination_flooding = "bd-flood"
+
+ if layer2_unknown_unicast == "flood":
+ arp_flooding = True
+
+ # Get schema objects
+ schema_id, schema_path, schema_obj = mso.query_schema(schema)
+
+ # Get template
+ templates = [t.get("name") for t in schema_obj.get("templates")]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates)))
+ template_idx = templates.index(template)
+
+ # Get BDs
+ bds = [b.get("name") for b in schema_obj.get("templates")[template_idx]["bds"]]
+
+ if bd is not None and bd in bds:
+ bd_idx = bds.index(bd)
+ mso.existing = schema_obj.get("templates")[template_idx]["bds"][bd_idx]
+
+ if state == "query":
+ if bd is None:
+ mso.existing = schema_obj.get("templates")[template_idx]["bds"]
+ elif not mso.existing:
+ mso.fail_json(msg="BD '{bd}' not found".format(bd=bd))
+ mso.exit_json()
+
+ bds_path = "/templates/{0}/bds".format(template)
+ bd_path = "/templates/{0}/bds/{1}".format(template, bd)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == "absent":
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op="remove", path=bd_path))
+
+ elif state == "present":
+ vrf_ref = mso.make_reference(vrf, "vrf", schema_id, template)
+ subnets = mso.make_subnets(subnets)
+ dhcp_label = mso.make_dhcp_label(dhcp_policy)
+ dhcp_labels = mso.make_dhcp_label(dhcp_policies)
+
+ if display_name is None and not mso.existing:
+ display_name = bd
+ if subnets is None and not mso.existing:
+ subnets = []
+
+ payload = dict(
+ name=bd,
+ displayName=display_name,
+ intersiteBumTrafficAllow=intersite_bum_traffic,
+ optimizeWanBandwidth=optimize_wan_bandwidth,
+ l2UnknownUnicast=layer2_unknown_unicast,
+ l2Stretch=layer2_stretch,
+ l3MCast=layer3_multicast,
+ subnets=subnets,
+ vrfRef=vrf_ref,
+ dhcpLabel=dhcp_label,
+ unkMcastAct=unknown_multicast_flooding,
+ multiDstPktAct=multi_destination_flooding,
+ v6unkMcastAct=ipv6_unknown_multicast_flooding,
+ vmac=virtual_mac_address,
+ arpFlood=arp_flooding,
+ )
+
+ if dhcp_labels:
+ payload.update(dhcpLabels=dhcp_labels)
+
+ if unicast_routing is not None:
+ payload.update(unicastRouting=unicast_routing)
+
+ if description:
+ payload.update(description=description)
+
+ mso.sanitize(payload, collate=True, required=["dhcpLabel", "dhcpLabels"])
+ if mso.existing:
+ ops.append(dict(op="replace", path=bd_path, value=mso.sent))
+ else:
+ ops.append(dict(op="add", path=bds_path + "/-", value=mso.sent))
+ mso.existing = mso.proposed
+
+ if "bdRef" in mso.previous:
+ del mso.previous["bdRef"]
+ if "vrfRef" in mso.previous:
+ mso.previous["vrfRef"] = mso.vrf_dict_from_ref(mso.previous.get("vrfRef"))
+
+ if not module.check_mode and mso.proposed != mso.previous:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd_dhcp_policy.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd_dhcp_policy.py
new file mode 100644
index 000000000..64fe360ea
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd_dhcp_policy.py
@@ -0,0 +1,245 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2021, Shreyas Srish (@shrsr) <ssrish@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_template_bd_dhcp_policy
+short_description: Manage BD DHCP Policy in schema templates
+description:
+- Manage BD DHCP policies in schema templates on Cisco ACI Multi-Site.
+author:
+- Shreyas Srish (@shrsr)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template to change.
+ type: str
+ required: true
+ bd:
+ description:
+ - The name of the BD to manage.
+ type: str
+ required: true
+ dhcp_policy:
+ description:
+ - The DHCP Policy
+ type: str
+ aliases: [ name ]
+ version:
+ description:
+ - The version of DHCP Relay Policy.
+ type: int
+ dhcp_option_policy:
+ description:
+ - The DHCP Option Policy.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The name of the DHCP Option Policy.
+ type: str
+ required: true
+ version:
+ description:
+ - The version of the DHCP Option Policy.
+ type: int
+ required: true
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+notes:
+- This module can only be used on versions of MSO that are 3.1.1h or greater.
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new DHCP policy to a BD
+ cisco.mso.mso_schema_template_bd_dhcp_policy:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ bd: BD 1
+ name: ansible_test
+ version: 1
+ dhcp_option_policy:
+ name: ansible_test_option
+ version: 1
+ state: present
+ delegate_to: localhost
+
+- name: Remove a DHCP policy from a BD
+ cisco.mso.mso_schema_template_bd_dhcp_policy:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ bd: BD 1
+ name: ansible_test
+ version: 1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific BD DHCP Policy
+ cisco.mso.mso_schema_template_bd_dhcp_policy:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ bd: BD 1
+ name: ansible_test
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all BD DHCP Policies
+ cisco.mso.mso_schema_template_bd_dhcp_policy:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ bd: BD 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_dhcp_option_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ bd=dict(type="str", required=True),
+ dhcp_policy=dict(type="str", aliases=["name"]),
+ version=dict(type="int"),
+ dhcp_option_policy=dict(type="dict", options=mso_dhcp_option_spec()),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["dhcp_policy"]],
+ ["state", "present", ["dhcp_policy", "version"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ template = module.params.get("template").replace(" ", "")
+ bd = module.params.get("bd")
+ dhcp_policy = module.params.get("dhcp_policy")
+ dhcp_option_policy = module.params.get("dhcp_option_policy")
+ version = module.params.get("version")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ # Get schema
+ schema_id, schema_path, schema_obj = mso.query_schema(schema)
+
+ # Get template
+ templates = [t.get("name") for t in schema_obj.get("templates")]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates)))
+ template_idx = templates.index(template)
+
+ # Get BD
+ bds = [b.get("name") for b in schema_obj.get("templates")[template_idx]["bds"]]
+ if bd not in bds:
+ mso.fail_json(msg="Provided BD '{0}' does not exist. Existing BDs: {1}".format(bd, ", ".join(bds)))
+ bd_idx = bds.index(bd)
+
+ # Check if DHCP policy already exists
+ if dhcp_policy:
+ check_policy = mso.get_obj("policies/dhcp/relay", name=dhcp_policy, key="DhcpRelayPolicies")
+ if check_policy:
+ pass
+ else:
+ mso.fail_json(msg="DHCP policy '{dhcp_policy}' does not exist".format(dhcp_policy=dhcp_policy))
+
+ # Check if DHCP option policy already exists
+ if dhcp_option_policy:
+ check_option_policy = mso.get_obj("policies/dhcp/option", name=dhcp_option_policy.get("name"), key="DhcpRelayPolicies")
+ if check_option_policy:
+ pass
+ else:
+ mso.fail_json(msg="DHCP option policy '{dhcp_option_policy}' does not exist".format(dhcp_option_policy=dhcp_option_policy.get("name")))
+
+ # Get DHCP policies
+ dhcp_policies = [s.get("name") for s in schema_obj.get("templates")[template_idx]["bds"][bd_idx]["dhcpLabels"]]
+ if dhcp_policy in dhcp_policies:
+ dhcp_idx = dhcp_policies.index(dhcp_policy)
+ # FIXME: Changes based on index are DANGEROUS
+ dhcp_policy_path = "/templates/{0}/bds/{1}/dhcpLabels/{2}".format(template, bd, dhcp_idx)
+ mso.existing = schema_obj.get("templates")[template_idx]["bds"][bd_idx]["dhcpLabels"][dhcp_idx]
+
+ if state == "query":
+ if dhcp_policy is None:
+ mso.existing = schema_obj.get("templates")[template_idx]["bds"][bd_idx]["dhcpLabels"]
+ elif not mso.existing:
+ mso.fail_json(msg="DHCP policy not associated with the bd")
+ mso.exit_json()
+
+ dhcp_policy_paths = "/templates/{0}/bds/{1}/dhcpLabels".format(template, bd)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == "absent":
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op="remove", path=dhcp_policy_path))
+
+ elif state == "present":
+ payload = dict(
+ name=dhcp_policy,
+ version=version,
+ dhcpOptionLabel=dhcp_option_policy,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op="replace", path=dhcp_policy_path, value=mso.sent))
+ else:
+ ops.append(dict(op="add", path=dhcp_policy_paths + "/-", value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd_subnet.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd_subnet.py
new file mode 100644
index 000000000..cc55eea7b
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd_subnet.py
@@ -0,0 +1,262 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_template_bd_subnet
+short_description: Manage BD subnets in schema templates
+description:
+- Manage BD subnets in schema templates on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template to change.
+ type: str
+ required: true
+ bd:
+ description:
+ - The name of the BD to manage.
+ type: str
+ required: true
+ subnet:
+ description:
+ - The IP range in CIDR notation.
+ type: str
+ aliases: [ ip ]
+ description:
+ description:
+ - The description of this subnet.
+ type: str
+ is_virtual_ip:
+ description:
+ - Treat as Virtual IP Address
+ type: bool
+ default: false
+ scope:
+ description:
+ - The scope of the subnet.
+ type: str
+ choices: [ private, public ]
+ shared:
+ description:
+ - Whether this subnet is shared between VRFs.
+ type: bool
+ default: false
+ no_default_gateway:
+ description:
+ - Whether this subnet has a default gateway.
+ type: bool
+ default: false
+ querier:
+ description:
+ - Whether this subnet is an IGMP querier.
+ type: bool
+ default: false
+ primary:
+ description:
+ - Treat as Primary Subnet.
+ - There can be only one primary subnet per address family under a BD.
+ - This option can only be used on versions of MSO that are 3.1.1h or greater.
+ type: bool
+ default: false
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+notes:
+- Due to restrictions of the MSO REST API concurrent modifications to BD subnets can be dangerous and corrupt data.
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new subnet to a BD
+ cisco.mso.mso_schema_template_bd_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ bd: BD 1
+ subnet: 10.0.0.0/24
+ state: present
+ delegate_to: localhost
+
+- name: Remove a subset from a BD
+ cisco.mso.mso_schema_template_bd_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ bd: BD 1
+ subnet: 10.0.0.0/24
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific BD subnet
+ cisco.mso.mso_schema_template_bd_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ bd: BD 1
+ subnet: 10.0.0.0/24
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all BD subnets
+ cisco.mso.mso_schema_template_bd_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ bd: BD 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ bd=dict(type="str", required=True),
+ subnet=dict(type="str", aliases=["ip"]),
+ description=dict(type="str"),
+ is_virtual_ip=dict(type="bool", default=False),
+ scope=dict(type="str", choices=["private", "public"]),
+ shared=dict(type="bool", default=False),
+ no_default_gateway=dict(type="bool", default=False),
+ querier=dict(type="bool", default=False),
+ primary=dict(type="bool", default=False),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["subnet"]],
+ ["state", "present", ["subnet"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ template = module.params.get("template").replace(" ", "")
+ bd = module.params.get("bd")
+ subnet = module.params.get("subnet")
+ description = module.params.get("description")
+ is_virtual_ip = module.params.get("is_virtual_ip")
+ scope = module.params.get("scope")
+ shared = module.params.get("shared")
+ no_default_gateway = module.params.get("no_default_gateway")
+ querier = module.params.get("querier")
+ primary = module.params.get("primary")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ # Get schema
+ schema_id, schema_path, schema_obj = mso.query_schema(schema)
+
+ # Get template
+ templates = [t.get("name") for t in schema_obj.get("templates")]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates)))
+ template_idx = templates.index(template)
+
+ # Get BD
+ bds = [b.get("name") for b in schema_obj.get("templates")[template_idx]["bds"]]
+ if bd not in bds:
+ mso.fail_json(msg="Provided BD '{0}' does not exist. Existing BDs: {1}".format(bd, ", ".join(bds)))
+ bd_idx = bds.index(bd)
+
+ # Get Subnet
+ subnets = [s.get("ip") for s in schema_obj.get("templates")[template_idx]["bds"][bd_idx]["subnets"]]
+ if subnet in subnets:
+ subnet_idx = subnets.index(subnet)
+ # FIXME: Changes based on index are DANGEROUS
+ subnet_path = "/templates/{0}/bds/{1}/subnets/{2}".format(template, bd, subnet_idx)
+ mso.existing = schema_obj.get("templates")[template_idx]["bds"][bd_idx]["subnets"][subnet_idx]
+
+ if state == "query":
+ if subnet is None:
+ mso.existing = schema_obj.get("templates")[template_idx]["bds"][bd_idx]["subnets"]
+ elif not mso.existing:
+ mso.fail_json(msg="Subnet IP '{subnet}' not found".format(subnet=subnet))
+ mso.exit_json()
+
+ subnets_path = "/templates/{0}/bds/{1}/subnets".format(template, bd)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == "absent":
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op="remove", path=subnet_path))
+
+ elif state == "present":
+ if not mso.existing:
+ if description is None:
+ description = subnet
+ if scope is None:
+ scope = "private"
+
+ payload = dict(
+ ip=subnet,
+ description=description,
+ virtual=is_virtual_ip,
+ scope=scope,
+ shared=shared,
+ noDefaultGateway=no_default_gateway,
+ querier=querier,
+ primary=primary,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op="replace", path=subnet_path, value=mso.sent))
+ else:
+ ops.append(dict(op="add", path=subnets_path + "/-", value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_clone.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_clone.py
new file mode 100644
index 000000000..0cd41779f
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_clone.py
@@ -0,0 +1,222 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_template_clone
+short_description: Clone templates
+description:
+- Clone templates on Cisco ACI Multi-Site.
+- Clones only template objects and not site objects.
+author:
+- Anvitha Jain (@anvitha-jain)
+options:
+ source_schema:
+ description:
+ - The name of the source_schema.
+ type: str
+ destination_schema:
+ description:
+ - The name of the destination_schema.
+ type: str
+ destination_tenant:
+ description:
+ - The name of the destination_schema.
+ type: str
+ source_template_name:
+ description:
+ - The name of the source template.
+ type: str
+ destination_template_name:
+ description:
+ - The name of the destination template.
+ type: str
+ destination_template_display_name:
+ description:
+ - The display name of the destination template.
+ type: str
+ state:
+ description:
+ - Use C(clone) for adding.
+ type: str
+ choices: [ clone ]
+ default: clone
+seealso:
+- module: cisco.mso.mso_schema
+- module: cisco.mso.mso_schema_clone
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Clone template in the same schema
+ cisco.mso.mso_schema_template_clone:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ source_schema: Schema1
+ destination_schema: Schema1
+ destination_tenant: ansible_test
+ source_template_name: Template1
+ destination_template_name: Template1_clone
+ destination_template_display_name: Template1_clone
+ state: clone
+ delegate_to: localhost
+
+- name: Clone template to different schema
+ cisco.mso.mso_schema_template_clone:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ source_schema: Schema1
+ destination_schema: Schema2
+ destination_tenant: ansible_test
+ source_template_name: Template2
+ destination_template_name: Cloned_template_1
+ destination_template_display_name: Cloned_template_1
+ state: clone
+ delegate_to: localhost
+
+- name: Clone template in the same schema but different tenant attached
+ cisco.mso.mso_schema_template_clone:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ source_schema: Schema1
+ destination_schema: Schema1
+ destination_tenant: common
+ source_template_name: Template1_clone
+ destination_template_name: Template1_clone_2
+ state: clone
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+from ansible_collections.cisco.mso.plugins.module_utils.constants import NDO_4_UNIQUE_IDENTIFIERS
+import json
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ source_schema=dict(type="str"),
+ destination_schema=dict(type="str"),
+ destination_tenant=dict(type="str"),
+ source_template_name=dict(type="str"),
+ destination_template_name=dict(type="str"),
+ destination_template_display_name=dict(type="str"),
+ state=dict(type="str", default="clone", choices=["clone"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "clone", ["source_schema", "source_template_name"]],
+ ],
+ )
+
+ source_schema = module.params.get("source_schema")
+ destination_schema = module.params.get("destination_schema")
+ destination_tenant = module.params.get("destination_tenant")
+ source_template_name = module.params.get("source_template_name")
+ destination_template_name = module.params.get("destination_template_name")
+ destination_template_display_name = module.params.get("destination_template_display_name")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ source_schema_id = None
+ destination_schema_id = None
+ destination_tenant_id = None
+ ops = []
+
+ if destination_schema is None:
+ destination_schema = source_schema
+
+ if destination_template_name is None:
+ destination_template_name = source_template_name
+
+ if destination_template_display_name is None:
+ destination_template_display_name = destination_template_name
+
+ # Check if source and destination template are named differently if in same schema
+ if source_schema == destination_schema:
+ if source_template_name == destination_template_name:
+ mso.fail_json(msg="Source and destination templates in the same schema cannot have same names.")
+
+ # Get source schema id and destination schema id
+ schema_summary = mso.query_objs("schemas/list-identity", key="schemas")
+
+ for schema in schema_summary:
+ if schema.get("displayName") == source_schema:
+ source_schema_id = schema.get("id")
+
+ if schema.get("displayName") == destination_schema:
+ destination_schema_id = schema.get("id")
+ for template in schema.get("templates"):
+ if template.get("name") == destination_template_name:
+ mso.fail_json(msg="Template with the name '{0}' already exists. Please use another name.".format(destination_template_name))
+
+ if source_schema_id is None:
+ mso.fail_json(msg="Schema with the name '{0}' does not exist.".format(source_schema))
+ elif destination_schema_id is None:
+ mso.fail_json(msg="Schema with the name '{0}' does not exist.".format(destination_schema))
+
+ # Get destination schema details before change
+ destination_schema_path = "schemas/{0}".format(destination_schema_id)
+ mso.existing = mso.query_obj(destination_schema_path, displayName=destination_schema)
+
+ if state == "clone":
+ # Get destination tenant id
+ if destination_tenant is not None:
+ destination_tenant_id = mso.lookup_tenant(destination_tenant)
+
+ # Get source schema details
+ source_schema_path = "schemas/{0}".format(source_schema_id)
+ source_schema_obj = mso.query_obj(source_schema_path, displayName=source_schema)
+
+ source_template_path = "/{0}/templates/{1}".format(source_schema_path, source_template_name)
+ destination_template_path = "/{0}/templates/{1}".format(destination_schema_path, destination_template_name)
+
+ source_templates = source_schema_obj.get("templates")
+ new_template = None
+ for template in source_templates:
+ if template.get("name") == source_template_name:
+ new_template = json.loads(json.dumps(template).replace(source_template_path, destination_template_path))
+ new_template["name"] = destination_template_name
+ new_template["displayName"] = destination_template_display_name
+ if destination_tenant_id is not None:
+ new_template["tenantId"] = destination_tenant_id
+ mso.delete_keys_from_dict(new_template, NDO_4_UNIQUE_IDENTIFIERS)
+ break
+
+ if new_template is None:
+ mso.fail_json(msg="Source template with the name '{0}' does not exist.".format(source_template_name))
+
+ new_template = mso.recursive_dict_from_ref(new_template)
+ mso.previous = mso.existing
+
+ ops.append(dict(op="add", path="/templates/-", value=new_template))
+ if not module.check_mode:
+ mso.request(destination_schema_path, method="PATCH", data=ops)
+
+ mso.existing = mso.query_obj(destination_schema_path, displayName=destination_schema)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_contract_filter.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_contract_filter.py
new file mode 100644
index 000000000..11bf08731
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_contract_filter.py
@@ -0,0 +1,396 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2022, Akini Ross (@akinross) <akinross@cisco.com>
+# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_template_contract_filter
+short_description: Manage contract filters in schema templates
+description:
+- Manage contract filters in schema templates on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: true
+ contract:
+ description:
+ - The name of the contract to manage.
+ type: str
+ required: true
+ description:
+ description:
+ - The description of contract is supported on versions of MSO/NDO that are 3.3 or greater.
+ type: str
+ contract_display_name:
+ description:
+ - The name as displayed on the MSO web interface.
+ - This defaults to the contract name when unset on creation.
+ type: str
+ contract_filter_type:
+ description:
+ - DEPRECATION WARNING, contract_filter_type will not be used anymore and is deduced from filter_type.
+ - The type of filters defined in this contract.
+ - This defaults to C(both-way) when unset on creation.
+ default: both-way
+ type: str
+ choices: [ both-way, one-way ]
+ contract_scope:
+ description:
+ - The scope of the contract.
+ - This defaults to C(vrf) when unset on creation.
+ type: str
+ choices: [ application-profile, global, tenant, vrf ]
+ filter:
+ description:
+ - The filter to associate with this contract.
+ type: str
+ aliases: [ name ]
+ filter_template:
+ description:
+ - The template name in which the filter is located.
+ type: str
+ filter_schema:
+ description:
+ - The schema name in which the filter is located.
+ type: str
+ filter_type:
+ description:
+ - The type of filter to manage.
+ - Prior to MSO/NDO 3.3 remove and re-apply contract to change the filter type.
+ type: str
+ choices: [ both-way, consumer-to-provider, provider-to-consumer ]
+ default: both-way
+ aliases: [ type ]
+ filter_directives:
+ description:
+ - A list of filter directives.
+ type: list
+ elements: str
+ choices: [ log, none, policy_compression ]
+ qos_level:
+ description:
+ - The Contract QoS Level parameter is supported on versions of MSO/NDO that are 3.3 or greater.
+ type: str
+ choices: [ unspecified, level1, level2, level3, level4, level5, level6 ]
+ action:
+ description:
+ - The filter action parameter is supported on versions of MSO/NDO that are 3.3 or greater.
+ type: str
+ choices: [ permit, deny ]
+ priority:
+ description:
+ - The filter priority override parameter is supported on versions of MSO/NDO that are 3.3 or greater.
+ type: str
+ choices: [ default, lowest_priority, medium_priority, highest_priority ]
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+seealso:
+- module: cisco.mso.mso_schema_template_filter_entry
+notes:
+- Due to restrictions of the MSO/NDO REST API this module creates contracts when needed, and removes them when the last filter has been removed.
+- Due to restrictions of the MSO/NDO REST API concurrent modifications to contract filters can be dangerous and corrupt data.
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new contract filter
+ cisco.mso.mso_schema_template_contract_filter:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ contract: Contract 1
+ contract_scope: global
+ filter: Filter 1
+ state: present
+ delegate_to: localhost
+
+- name: Remove a contract filter
+ cisco.mso.mso_schema_template_contract_filter:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ contract: Contract 1
+ filter: Filter 1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific contract filter
+ cisco.mso.mso_schema_template_contract_filter:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ contract: Contract 1
+ filter: Filter 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all contract filters
+ cisco.mso.mso_schema_template_contract_filter:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ contract: Contract 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+from ansible_collections.cisco.mso.plugins.module_utils.constants import FILTER_KEY_MAP, PRIORITY_MAP
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ contract=dict(type="str", required=True),
+ description=dict(type="str"),
+ contract_display_name=dict(type="str"),
+ contract_scope=dict(type="str", choices=["application-profile", "global", "tenant", "vrf"]),
+ # Deprecated input: contract_filter_type is deduced from filter_type
+ contract_filter_type=dict(type="str", default="both-way", choices=["both-way", "one-way"]),
+ filter=dict(type="str", aliases=["name"]), # This parameter is not required for querying all objects
+ filter_directives=dict(type="list", elements="str", choices=["log", "none", "policy_compression"]),
+ filter_template=dict(type="str"),
+ filter_schema=dict(type="str"),
+ filter_type=dict(type="str", default="both-way", choices=list(FILTER_KEY_MAP), aliases=["type"]),
+ qos_level=dict(type="str", choices=["unspecified", "level1", "level2", "level3", "level4", "level5", "level6"]),
+ action=dict(type="str", choices=["permit", "deny"]),
+ priority=dict(type="str", choices=["default", "lowest_priority", "medium_priority", "highest_priority"]),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["filter"]],
+ ["state", "present", ["filter"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ template_name = module.params.get("template").replace(" ", "")
+ contract_name = module.params.get("contract")
+ contract_display_name = module.params.get("contract_display_name")
+ description = module.params.get("description")
+ # Deprecated input: contract_filter_type is deduced from filter_type.
+ # contract_filter_type = module.params.get('contract_filter_type')
+ contract_scope = module.params.get("contract_scope")
+ filter_name = module.params.get("filter")
+ filter_directives = module.params.get("filter_directives")
+ filter_template = module.params.get("filter_template")
+ filter_schema = module.params.get("filter_schema")
+ filter_type = module.params.get("filter_type")
+ filter_action = module.params.get("action")
+ filter_priority = module.params.get("priority")
+ qos_level = module.params.get("qos_level")
+
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ # Initialize variables
+ ops = []
+ filter_obj = None
+ filter_key = FILTER_KEY_MAP.get(filter_type)
+ filter_template = template_name if filter_template is None else filter_template.replace(" ", "")
+ filter_schema = schema if filter_schema is None else filter_schema
+ filter_schema_id = mso.lookup_schema(filter_schema)
+ contract_filter_type = "bothWay" if filter_type == "both-way" else "oneWay"
+
+ # Set path defaults, when object (contract or filter) is found append /{name} to base paths
+ base_contract_path = "/templates/{0}/contracts".format(template_name)
+ base_filter_path = "{0}/{1}/{2}".format(base_contract_path, contract_name, filter_key)
+ contract_path = "{0}/-".format(base_contract_path)
+ filter_path = "{0}/-".format(base_filter_path)
+
+ # Get schema information.
+ schema_id, schema_path, schema_obj = mso.query_schema(schema)
+
+ # Get template by unique identifier "name".
+ template_obj = next((item for item in schema_obj.get("templates") if item.get("name") == template_name), None)
+ if not template_obj:
+ existing_templates = [t.get("name") for t in schema_obj.get("templates")]
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template_name, ", ".join(existing_templates)))
+
+ filter_ref = mso.filter_ref(schema_id=filter_schema_id, template=filter_template, filter=filter_name)
+
+ # Get contract by unique identifier "name".
+ contract_obj = next((item for item in template_obj.get("contracts") if item.get("name") == contract_name), None)
+ if contract_obj:
+ if contract_obj.get("filterType") != contract_filter_type:
+ mso.fail_json(
+ msg="Current filter type '{0}' for contract '{1}' is not allowed to change to '{2}'.".format(
+ contract_obj.get("filterType"), contract_name, contract_filter_type
+ )
+ )
+ contract_path = "{0}/{1}".format(base_contract_path, contract_name)
+ if filter_name:
+ # Get filter by unique identifier "filterRef".
+ filter_obj = next((item for item in contract_obj.get(filter_key) if item.get("filterRef") == filter_ref), None)
+ if filter_obj:
+ filter_path = "{0}/{1}".format(base_filter_path, filter_name)
+ mso.update_filter_obj(contract_obj, filter_obj, filter_type)
+ mso.existing = filter_obj
+
+ if state == "query":
+ if not contract_obj:
+ existing_contracts = [c.get("name") for c in template_obj.get("contracts")]
+ mso.fail_json(msg="Provided contract '{0}' does not exist. Existing contracts: {1}".format(contract_name, ", ".join(existing_contracts)))
+
+ # If filter name is not provided, provide overview of all filter objects for the filter type.
+ if not filter_name:
+ mso.existing = contract_obj.get(filter_key)
+ for filter_obj in mso.existing:
+ mso.update_filter_obj(contract_obj, filter_obj, filter_type)
+
+ elif not mso.existing:
+ mso.fail_json(msg="FilterRef '{filter_ref}' not found".format(filter_ref=filter_ref))
+
+ mso.exit_json()
+
+ mso.previous = mso.existing
+
+ if state == "absent":
+ # Contracts need at least one filter left, remove contract if remove would lead 0 filters remaining.
+ if contract_obj:
+ if len(contract_obj.get(filter_key)) == 1:
+ mso.existing = {}
+ ops.append(dict(op="remove", path=contract_path))
+ elif len(contract_obj.get(filter_key)) > 1:
+ mso.existing = {}
+ ops.append(dict(op="remove", path=filter_path))
+
+ elif state == "present":
+ contract_scope = "context" if contract_scope == "vrf" else contract_scope
+
+ # Initialize "present" state filter variables
+ if not filter_directives:
+ # Avoid validation error: "Bad Request: (0)(1)(0) 'directives' is undefined on object
+ if not filter_obj:
+ filter_directives = ["none"]
+ else:
+ filter_directives = filter_obj.get("directives", ["none"])
+
+ elif "policy_compression" in filter_directives:
+ filter_directives[filter_directives.index("policy_compression")] = "no_stats"
+ filter_payload = dict(
+ filterRef=dict(
+ filterName=filter_name,
+ templateName=filter_template,
+ schemaId=filter_schema_id,
+ ),
+ directives=filter_directives,
+ )
+ if filter_action:
+ filter_payload.update(action=filter_action)
+ if filter_action == "deny" and filter_priority:
+ filter_payload.update(priorityOverride=PRIORITY_MAP.get(filter_priority))
+
+ # If contract exist the operation should be set to replace else operation is add to create new contract.
+ if contract_obj:
+ if contract_display_name:
+ ops.append(dict(op="replace", path=contract_path + "/displayName", value=contract_display_name))
+ # Conditional statement 'description == ""' is needed to allow setting the description back to empty string.
+ if description or description == "":
+ ops.append(dict(op="replace", path=contract_path + "/description", value=description))
+ if qos_level:
+ # Conditional statement is needed to determine if "prio" exist in contract object.
+ # An object can be created in 3.3 higher version without prio via the API.
+ # In the GUI a default is set to "unspecified" and thus prio is always configured via GUI.
+ # We can't set a default of "unspecified" because prior to version 3.3 qos_level is not supported,
+ # thus the logic is needed for both add and replace operation
+ if contract_obj.get("prio"):
+ ops.append(dict(op="replace", path=contract_path + "/prio", value=qos_level))
+ else:
+ ops.append(dict(op="add", path=contract_path + "/prio", value=qos_level))
+ if contract_scope:
+ ops.append(dict(op="replace", path=contract_path + "/scope", value=contract_scope))
+
+ # If filter exist the operation should be set to replace else operation is add to create new filter.
+ if filter_obj:
+ ops.append(dict(op="replace", path=filter_path, value=filter_payload))
+ else:
+ ops.append(dict(op="add", path=filter_path, value=filter_payload))
+
+ else:
+ contract_display_name = contract_display_name if contract_display_name else contract_name
+ # If contract_scope is not provided default to context to match GUI behaviour on create new contract.
+ contract_scope = "context" if contract_scope is None else contract_scope
+ contract_payload = dict(name=contract_name, displayName=contract_display_name, filterType=contract_filter_type, scope=contract_scope)
+ if description:
+ contract_payload.update(description=description)
+ if qos_level:
+ contract_payload.update(prio=qos_level)
+ if filter_key == "filterRelationships":
+ contract_payload.update(filterRelationships=[filter_payload])
+ elif filter_key == "filterRelationshipsConsumerToProvider":
+ contract_payload.update(filterRelationshipsConsumerToProvider=[filter_payload])
+ elif filter_key == "filterRelationshipsProviderToConsumer":
+ contract_payload.update(filterRelationshipsProviderToConsumer=[filter_payload])
+ ops.append(dict(op="add", path=contract_path, value=contract_payload))
+
+ mso.sanitize(filter_payload, collate=True, unwanted=["filterType", "contractScope", "contractFilterType"])
+
+ # Update existing with filter (mso.sent) and contract information.
+ mso.existing = mso.sent
+ mso.existing["displayName"] = contract_display_name if contract_display_name else contract_obj.get("displayName")
+ mso.existing["filterType"] = filter_type
+ mso.existing["contractScope"] = contract_scope if contract_scope else contract_obj.get("scope")
+ mso.existing["contractFilterType"] = contract_filter_type
+ # Conditional statement 'description == ""' is needed to allow setting the description back to empty string.
+ if description or (contract_obj and (contract_obj.get("description") or contract_obj.get("description") == "")):
+ mso.existing["description"] = description if description or description == "" else contract_obj.get("description")
+ # Conditional statement to check qos_level is defined or is present in the contract object.
+ # qos_level is not supported prior to 3.3 thus this check in place, GUI uses default of "unspecified" from 3.3.
+ # When default of "unspecified" is set, conditional statement can be simplified since "prio" always present.
+ if qos_level or (contract_obj and contract_obj.get("prio")):
+ mso.existing["prio"] = qos_level if qos_level else contract_obj.get("prio")
+
+ if not module.check_mode and mso.existing != mso.previous:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_contract_service_graph.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_contract_service_graph.py
new file mode 100644
index 000000000..0e398843b
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_contract_service_graph.py
@@ -0,0 +1,317 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2022, Akini Ross (@akinross) <akinross@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_template_contract_service_graph
+short_description: Manage the service graph association with a contract in schema template
+description:
+- Manage the service graph association with a contract in schema template on Cisco ACI Multi-Site.
+- The Contract Service Graph parameter is supported on versions of MSO/NDO that are 3.3 or greater.
+author:
+- Akini Ross (@akinross)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: true
+ contract:
+ description:
+ - The name of the contract.
+ type: str
+ required: true
+ service_graph:
+ description:
+ - The service graph to associate with this contract.
+ type: str
+ service_graph_template:
+ description:
+ - The template name in which the service graph is located.
+ type: str
+ service_graph_schema:
+ description:
+ - The schema name in which the service graph is located.
+ type: str
+ service_nodes:
+ description:
+ - A list of nodes and their connector details associated with the Service Graph.
+ - The order of the list matches the node id ordering in GUI, so first entry in list will be match node 1.
+ type: list
+ elements: dict
+ suboptions:
+ provider:
+ description:
+ - The name of the Bridge Domain.
+ required: true
+ type: str
+ consumer:
+ description:
+ - The name of the Bridge Domain.
+ required: true
+ type: str
+ connector_object_type:
+ description:
+ - The connector ACI object type of the node.
+ type: str
+ default: bd
+ choices: [ bd ]
+ provider_schema:
+ description:
+ - The schema name in which the provider is located.
+ type: str
+ provider_template:
+ description:
+ - The template name in which the provider is located.
+ type: str
+ consumer_schema:
+ description:
+ - The schema name in which the consumer is located.
+ type: str
+ consumer_template:
+ description:
+ - The template name in which the consumer is located.
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+seealso:
+- module: cisco.mso.mso_schema_template_contract_filter
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new contract service graph
+ cisco.mso.mso_schema_template_contract_service_graph:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ contract: Contract 1
+ service_graph: SG1
+ service_graph_nodes:
+ - provider: b1
+ consumer: b2
+ filter: Filter 1
+ state: present
+ delegate_to: localhost
+
+- name: Remove a contract service graph
+ cisco.mso.mso_schema_template_contract_service_graph:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ contract: Contract 1
+ service_graph: SG1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a contract service graph
+ cisco.mso.mso_schema_template_contract_service_graph:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ contract: Contract 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_service_graph_connector_spec
+from ansible_collections.cisco.mso.plugins.module_utils.constants import SERVICE_NODE_CONNECTOR_MAP
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ contract=dict(type="str", required=True),
+ service_graph=dict(type="str"),
+ service_graph_template=dict(type="str"),
+ service_graph_schema=dict(type="str"),
+ service_nodes=dict(type="list", elements="dict", options=mso_service_graph_connector_spec()),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["service_graph"]],
+ ["state", "present", ["service_graph", "service_nodes"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ template_name = module.params.get("template").replace(" ", "")
+ contract_name = module.params.get("contract")
+ service_graph_name = module.params.get("service_graph")
+ service_graph_template = module.params.get("service_graph_template")
+ service_graph_schema = module.params.get("service_graph_schema")
+ service_nodes = module.params.get("service_nodes")
+
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ # Initialize variables
+ ops = []
+ service_graph_obj = None
+
+ # Set path defaults for create logic, if object (contract or filter) is found replace the "-" for specific value
+ base_contract_path = "/templates/{0}/contracts".format(template_name)
+ service_graph_path = "{0}/{1}/serviceGraphRelationship".format(base_contract_path, contract_name)
+
+ # Get schema
+ schema_id, schema_path, schema_obj = mso.query_schema(schema)
+
+ # Get template
+ template_obj = next((item for item in schema_obj.get("templates") if item.get("name") == template_name), None)
+ if not template_obj:
+ mso.fail_json(
+ msg="Provided template '{0}' does not exist. Existing templates: {1}".format(
+ template_name, ", ".join([t.get("name") for t in schema_obj.get("templates")])
+ )
+ )
+
+ # Get contract
+ contract_obj = next((item for item in template_obj.get("contracts") if item.get("name") == contract_name), None)
+ if contract_obj:
+ # Get service graph if it exists in contract
+ if contract_obj.get("serviceGraphRelationship"):
+ service_graph_obj = contract_obj.get("serviceGraphRelationship")
+ mso.update_service_graph_obj(service_graph_obj)
+ mso.existing = service_graph_obj
+ else:
+ mso.fail_json(
+ msg="Provided contract '{0}' does not exist. Existing contracts: {1}".format(
+ contract_name, ", ".join([c.get("name") for c in template_obj.get("contracts")])
+ )
+ )
+
+ if state == "query":
+ mso.exit_json()
+
+ mso.previous = mso.existing
+
+ if state == "absent":
+ if contract_obj.get("serviceGraphRelationship"):
+ mso.existing = {}
+ ops.append(dict(op="remove", path=service_graph_path))
+
+ elif state == "present":
+ service_nodes_relationship = []
+ service_graph_template = service_graph_template.replace(" ", "") if service_graph_template else template_name
+ service_graph_schema = service_graph_schema if service_graph_schema else schema
+ service_graph_schema_id, service_graph_schema_path, service_graph_schema_obj = mso.query_schema(service_graph_schema)
+
+ # Validation to check if amount of service graph nodes provided is matching the service graph template.
+ # The API allows providing more or less service graph nodes behaviour but the GUI does not.
+ service_graph_template_obj = next((item for item in service_graph_schema_obj.get("templates") if item.get("name") == service_graph_template), None)
+ if not service_graph_template_obj:
+ mso.fail_json(
+ msg="Provided template '{0}' does not exist. Existing templates: {1}".format(
+ template_name, ", ".join([t.get("name") for t in service_graph_schema_obj.get("templates")])
+ )
+ )
+ service_graph_schema_obj = next((item for item in service_graph_template_obj.get("serviceGraphs") if item.get("name") == service_graph_name), None)
+ if service_graph_schema_obj:
+ if len(service_nodes) < len(service_graph_schema_obj.get("serviceNodes")):
+ mso.fail_json(
+ msg="Not enough service nodes defined, {0} service node(s) provided when {1} needed.".format(
+ len(service_nodes), len(service_graph_schema_obj.get("serviceNodes"))
+ )
+ )
+ elif len(service_nodes) > len(service_graph_schema_obj.get("serviceNodes")):
+ mso.fail_json(
+ msg="Too many service nodes defined, {0} service nodes provided when {1} needed.".format(
+ len(service_nodes), len(service_graph_schema_obj.get("serviceNodes"))
+ )
+ )
+ else:
+ mso.fail_json(msg="Provided service graph '{0}' does not exist.".format(service_graph_name))
+
+ for node_id, service_node in enumerate(service_nodes, 0):
+ # Consumer and provider share connector details (so provider/consumer could have separate details in future)
+ connector_details = SERVICE_NODE_CONNECTOR_MAP.get(service_node.get("connector_object_type"))
+ provider_schema = mso.lookup_schema(service_node.get("provider_schema")) if service_node.get("provider_schema") else schema_id
+ provider_template = service_node.get("provider_template").replace(" ", "") if service_node.get("provider_template") else template_name
+ consumer_schema = mso.lookup_schema(service_node.get("consumer_schema")) if service_node.get("consumer_schema") else schema_id
+ consumer_template = service_node.get("consumer_template").replace(" ", "") if service_node.get("consumer_template") else template_name
+
+ service_nodes_relationship.append(
+ {
+ "serviceNodeRef": dict(
+ schemaId=service_graph_schema_id,
+ templateName=service_graph_template,
+ serviceGraphName=service_graph_name,
+ serviceNodeName=service_graph_schema_obj.get("serviceNodes")[node_id].get("name"),
+ ),
+ "providerConnector": {
+ "connectorType": connector_details.get("connector_type"),
+ "{0}Ref".format(connector_details.get("id")): {
+ "schemaId": provider_schema,
+ "templateName": provider_template,
+ "{0}Name".format(connector_details.get("id")): service_node.get("provider"),
+ },
+ },
+ "consumerConnector": {
+ "connectorType": connector_details.get("connector_type"),
+ "{0}Ref".format(connector_details.get("id")): {
+ "schemaId": consumer_schema,
+ "templateName": consumer_template,
+ "{0}Name".format(connector_details.get("id")): service_node.get("consumer"),
+ },
+ },
+ }
+ )
+
+ service_graph_payload = dict(
+ serviceGraphRef=dict(serviceGraphName=service_graph_name, templateName=service_graph_template, schemaId=service_graph_schema_id),
+ serviceNodesRelationship=service_nodes_relationship,
+ )
+
+ # If service graph exist the operation should be set to "replace" else operation is "add" to create new
+ if service_graph_obj:
+ ops.append(dict(op="replace", path=service_graph_path, value=service_graph_payload))
+ else:
+ ops.append(dict(op="add", path=service_graph_path, value=service_graph_payload))
+
+ mso.existing = mso.sent = service_graph_payload
+
+ if not module.check_mode and mso.existing != mso.previous:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_deploy.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_deploy.py
new file mode 100644
index 000000000..49df465c5
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_deploy.py
@@ -0,0 +1,147 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_template_deploy
+short_description: Deploy schema templates to sites
+description:
+- Deploy schema templates to sites.
+- Prior to deploy a schema validation is executed for MSO releases running on the ND platform.
+- When schema validation fails, M(cisco.mso.mso_schema_template_deploy) fails and deploy will not be executed.
+- DEPRECATED for NDO v4.1 and later. Use M(cisco.mso.ndo_schema_template_deploy) on NDO v4.1 and later.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: true
+ aliases: [ name ]
+ site:
+ description:
+ - The name of the site B(to undeploy).
+ type: str
+ state:
+ description:
+ - Use C(deploy) to deploy schema template.
+ - Use C(status) to get deployment status.
+ - Use C(undeploy) to deploy schema template from a site.
+ type: str
+ choices: [ deploy, status, undeploy ]
+ default: deploy
+seealso:
+- module: cisco.mso.mso_schema_site
+- module: cisco.mso.mso_schema_template
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Deploy a schema template
+ cisco.mso.mso_schema_template_deploy:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ state: deploy
+ delegate_to: localhost
+
+- name: Undeploy a schema template
+ cisco.mso.mso_schema_template_deploy:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ site: Site 1
+ state: undeploy
+ delegate_to: localhost
+
+- name: Get deployment status
+ cisco.mso.mso_schema:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ state: status
+ delegate_to: localhost
+ register: status_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ template=dict(type="str", required=True, aliases=["name"]),
+ site=dict(type="str"),
+ state=dict(type="str", default="deploy", choices=["deploy", "status", "undeploy"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "undeploy", ["site"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ template = module.params.get("template").replace(" ", "")
+ site = module.params.get("site")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ # Get schema id
+ schema_id = mso.lookup_schema(schema)
+
+ payload = dict(
+ schemaId=schema_id,
+ templateName=template,
+ )
+
+ qs = None
+ if state == "deploy":
+ if mso.platform == "nd":
+ mso.validate_schema(schema_id)
+ path = "execute/schema/{0}/template/{1}".format(schema_id, template)
+ elif state == "status":
+ path = "status/schema/{0}/template/{1}".format(schema_id, template)
+ elif state == "undeploy":
+ path = "execute/schema/{0}/template/{1}".format(schema_id, template)
+ site_id = mso.lookup_site(site)
+ qs = dict(undeploy=site_id)
+
+ if not module.check_mode:
+ status = mso.request(path, method="GET", data=payload, qs=qs)
+ mso.exit_json(**status)
+ else:
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_deploy_status.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_deploy_status.py
new file mode 100644
index 000000000..707ad7320
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_deploy_status.py
@@ -0,0 +1,163 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2021, Shreyas Srish (@shrsr) <ssrish@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_template_deploy_status
+short_description: Check query of objects before deployment to site
+description:
+- Check query of objects in a template of a schema
+author:
+- Shreyas Srish (@shrsr)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ aliases: [ name ]
+ template:
+ description:
+ - The name of the template.
+ type: str
+ site:
+ description:
+ - The name of the site.
+ type: str
+ state:
+ description:
+ - Use C(query) for listing query of objects.
+ type: str
+ choices: [ query ]
+ default: query
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+
+- name: Query status of objects in a template
+ cisco.mso.mso_schema_template_deploy_status:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query status of objects using site
+ cisco.mso.mso_schema_template_deploy_status:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ site: ansible_test
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query status of objects in a template associated with a site
+ cisco.mso.mso_schema_template_deploy_status:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ site: ansible_test
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query status of objects in all templates
+ cisco.mso.mso_schema_template_deploy_status:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", aliases=["name"]),
+ template=dict(type="str"),
+ site=dict(type="str"),
+ state=dict(type="str", default="query", choices=["query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "query", ["schema"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ template = module.params.get("template")
+ if template is not None:
+ template = template.replace(" ", "")
+ site = module.params.get("site")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ schema_id = None
+ path = "schemas"
+
+ get_schema = mso.get_obj(path, displayName=schema)
+ if get_schema:
+ schema_id = get_schema.get("id")
+ path = "schemas/{id}/policy-states".format(id=schema_id)
+ else:
+ mso.fail_json(msg="Schema '{0}' not found.".format(schema))
+
+ if state == "query":
+ get_data = mso.request(path, method="GET")
+ mso.existing = []
+ if template:
+ for configuration_objects in get_data.get("policyStates"):
+ if configuration_objects.get("templateName") == template:
+ mso.existing.append(configuration_objects)
+ if not mso.existing:
+ mso.fail_json(msg="Template '{0}' not found.".format(template))
+
+ if site:
+ mso.existing.clear()
+ for configuration_objects in get_data.get("policyStates"):
+ if configuration_objects.get("siteId") == mso.lookup_site(site):
+ if template:
+ if configuration_objects.get("templateName") == template:
+ mso.existing = configuration_objects
+ else:
+ mso.existing.append(configuration_objects)
+ if template is not None and not mso.existing:
+ mso.fail_json(msg="Provided Template '{0}' not associated with Site '{1}'.".format(template, site))
+
+ if template is None and site is None:
+ mso.existing = get_data
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg.py
new file mode 100644
index 000000000..ce201913f
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg.py
@@ -0,0 +1,337 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_template_external_epg
+short_description: Manage external EPGs in schema templates
+description:
+- Manage external EPGs in schema templates on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: true
+ external_epg:
+ description:
+ - The name of the external EPG to manage.
+ type: str
+ aliases: [ name, externalepg ]
+ description:
+ description:
+ - The description of external EPG is supported on versions of MSO that are 3.3 or greater.
+ type: str
+ type:
+ description:
+ - The type of external epg.
+ - anp needs to be associated with external epg when the type is cloud.
+ - l3out can be associated with external epg when the type is on-premise.
+ type: str
+ choices: [ on-premise, cloud ]
+ default: on-premise
+ display_name:
+ description:
+ - The name as displayed on the MSO web interface.
+ type: str
+ vrf:
+ description:
+ - The VRF associated with the external epg.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The name of the VRF to associate with.
+ required: true
+ type: str
+ schema:
+ description:
+ - The schema that defines the referenced VRF.
+ - If this parameter is unspecified, it defaults to the current schema.
+ type: str
+ template:
+ description:
+ - The template that defines the referenced VRF.
+ - If this parameter is unspecified, it defaults to the current template.
+ type: str
+ l3out:
+ description:
+ - The L3Out associated with the external epg.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The name of the L3Out to associate with.
+ required: true
+ type: str
+ schema:
+ description:
+ - The schema that defines the referenced L3Out.
+ - If this parameter is unspecified, it defaults to the current schema.
+ type: str
+ template:
+ description:
+ - The template that defines the referenced L3Out.
+ - If this parameter is unspecified, it defaults to the current template.
+ type: str
+ anp:
+ description:
+ - The anp associated with the external epg.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The name of the anp to associate with.
+ required: true
+ type: str
+ schema:
+ description:
+ - The schema that defines the referenced anp.
+ - If this parameter is unspecified, it defaults to the current schema.
+ type: str
+ template:
+ description:
+ - The template that defines the referenced anp.
+ - If this parameter is unspecified, it defaults to the current template.
+ type: str
+ preferred_group:
+ description:
+ - Preferred Group is enabled for this External EPG or not.
+ type: bool
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new external EPG
+ cisco.mso.mso_schema_template_external_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ external_epg: External EPG 1
+ vrf:
+ name: VRF
+ schema: Schema 1
+ template: Template 1
+ state: present
+ delegate_to: localhost
+
+- name: Add a new external EPG with external epg in cloud
+ cisco.mso.mso_schema_template_external_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ external_epg: External EPG 1
+ type: cloud
+ vrf:
+ name: VRF
+ schema: Schema 1
+ template: Template 1
+ anp:
+ name: ANP1
+ schema: Schema 1
+ template: Template 1
+ state: present
+ delegate_to: localhost
+
+- name: Remove an external EPG
+ cisco.mso.mso_schema_template_external_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ external_epg: external EPG1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific external EPGs
+ cisco.mso.mso_schema_template_external_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ external_epg: external EPG1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all external EPGs
+ cisco.mso.mso_schema_template_external_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_reference_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ external_epg=dict(type="str", aliases=["name", "externalepg"]), # This parameter is not required for querying all objects
+ description=dict(type="str"),
+ display_name=dict(type="str"),
+ vrf=dict(type="dict", options=mso_reference_spec()),
+ l3out=dict(type="dict", options=mso_reference_spec()),
+ anp=dict(type="dict", options=mso_reference_spec()),
+ preferred_group=dict(type="bool"),
+ type=dict(type="str", default="on-premise", choices=["on-premise", "cloud"]),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["external_epg"]],
+ ["state", "present", ["external_epg", "vrf"]],
+ ["type", "cloud", ["anp"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ template = module.params.get("template").replace(" ", "")
+ external_epg = module.params.get("external_epg")
+ description = module.params.get("description")
+ display_name = module.params.get("display_name")
+ vrf = module.params.get("vrf")
+ if vrf is not None and vrf.get("template") is not None:
+ vrf["template"] = vrf.get("template").replace(" ", "")
+ l3out = module.params.get("l3out")
+ if l3out is not None and l3out.get("template") is not None:
+ l3out["template"] = l3out.get("template").replace(" ", "")
+ anp = module.params.get("anp")
+ if anp is not None and anp.get("template") is not None:
+ anp["template"] = anp.get("template").replace(" ", "")
+ preferred_group = module.params.get("preferred_group")
+ type_ext_epg = module.params.get("type")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ # Get schema objects
+ schema_id, schema_path, schema_obj = mso.query_schema(schema)
+
+ # Get template
+ templates = [t.get("name") for t in schema_obj.get("templates")]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates)))
+ template_idx = templates.index(template)
+
+ # Get external EPGs
+ external_epgs = [e.get("name") for e in schema_obj.get("templates")[template_idx]["externalEpgs"]]
+
+ if external_epg is not None and external_epg in external_epgs:
+ external_epg_idx = external_epgs.index(external_epg)
+ mso.existing = schema_obj.get("templates")[template_idx]["externalEpgs"][external_epg_idx]
+ if "externalEpgRef" in mso.existing:
+ del mso.existing["externalEpgRef"]
+ if "vrfRef" in mso.existing:
+ mso.existing["vrfRef"] = mso.dict_from_ref(mso.existing.get("vrfRef"))
+ if "l3outRef" in mso.existing:
+ mso.existing["l3outRef"] = mso.dict_from_ref(mso.existing.get("l3outRef"))
+ if "anpRef" in mso.existing:
+ mso.existing["anpRef"] = mso.dict_from_ref(mso.existing.get("anpRef"))
+
+ if state == "query":
+ if external_epg is None:
+ mso.existing = schema_obj.get("templates")[template_idx]["externalEpgs"]
+ elif not mso.existing:
+ mso.fail_json(msg="External EPG '{external_epg}' not found".format(external_epg=external_epg))
+ mso.exit_json()
+
+ eepgs_path = "/templates/{0}/externalEpgs".format(template)
+ eepg_path = "/templates/{0}/externalEpgs/{1}".format(template, external_epg)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == "absent":
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op="remove", path=eepg_path))
+
+ elif state == "present":
+ vrf_ref = mso.make_reference(vrf, "vrf", schema_id, template)
+ l3out_ref = mso.make_reference(l3out, "l3out", schema_id, template)
+ anp_ref = mso.make_reference(anp, "anp", schema_id, template)
+ if display_name is None and not mso.existing:
+ display_name = external_epg
+
+ payload = dict(
+ name=external_epg,
+ displayName=display_name,
+ vrfRef=vrf_ref,
+ preferredGroup=preferred_group,
+ )
+
+ if description is not None:
+ payload.update(description=description)
+
+ if type_ext_epg == "cloud":
+ payload["extEpgType"] = "cloud"
+ payload["anpRef"] = anp_ref
+ else:
+ payload["l3outRef"] = l3out_ref
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ # clean anpRef when anpRef is null
+ if "anpRef" in mso.existing and mso.existing.get("anpRef") is None:
+ del mso.existing["anpRef"]
+ # clean contractRef to fix api issue
+ for contract in mso.sent.get("contractRelationships"):
+ contract["contractRef"] = mso.dict_from_ref(contract.get("contractRef"))
+ ops.append(dict(op="replace", path=eepg_path, value=mso.sent))
+ else:
+ ops.append(dict(op="add", path=eepgs_path + "/-", value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode and mso.proposed != mso.previous:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_contract.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_contract.py
new file mode 100644
index 000000000..4029175bc
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_contract.py
@@ -0,0 +1,247 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# 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 = r"""
+---
+module: mso_schema_template_external_epg_contract
+short_description: Manage Extrnal EPG contracts in schema templates
+description:
+- Manage External EPG contracts in schema templates on Cisco ACI Multi-Site.
+author:
+- Devarshi Shah (@devarshishah3)
+version_added: '0.0.8'
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template to change.
+ type: str
+ required: true
+ external_epg:
+ description:
+ - The name of the EPG to manage.
+ type: str
+ required: true
+ contract:
+ description:
+ - A contract associated to this EPG.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The name of the Contract to associate with.
+ required: true
+ type: str
+ schema:
+ description:
+ - The schema that defines the referenced BD.
+ - If this parameter is unspecified, it defaults to the current schema.
+ type: str
+ template:
+ description:
+ - The template that defines the referenced BD.
+ type: str
+ type:
+ description:
+ - The type of contract.
+ type: str
+ required: true
+ choices: [ consumer, provider ]
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+seealso:
+- module: cisco.mso.mso_schema_template_external_epg
+- module: cisco.mso.mso_schema_template_contract_filter
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a contract to an EPG
+ cisco.mso.mso_schema_template_external_epg_contract:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ epg: EPG 1
+ contract:
+ name: Contract 1
+ type: consumer
+ state: present
+ delegate_to: localhost
+
+- name: Remove a Contract
+ cisco.mso.mso_schema_template_external_epg_contract:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ epg: EPG 1
+ contract:
+ name: Contract 1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific Contract
+ cisco.mso.mso_schema_template_external_epg_contract:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ epg: EPG 1
+ contract:
+ name: Contract 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all Contracts
+ cisco.mso.mso_schema_template_external_epg_contract:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_contractref_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ external_epg=dict(type="str", required=True),
+ contract=dict(type="dict", options=mso_contractref_spec()),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["contract"]],
+ ["state", "present", ["contract"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ template = module.params.get("template").replace(" ", "")
+ external_epg = module.params.get("external_epg")
+ contract = module.params.get("contract")
+ if contract is not None and contract.get("template") is not None:
+ contract["template"] = contract.get("template").replace(" ", "")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ if contract:
+ if contract.get("schema") is None:
+ contract["schema"] = schema
+ contract["schema_id"] = mso.lookup_schema(contract.get("schema"))
+ if contract.get("template") is None:
+ contract["template"] = template
+
+ # Get schema
+ schema_id, schema_path, schema_obj = mso.query_schema(schema)
+
+ # Get template
+ templates = [t.get("name") for t in schema_obj.get("templates")]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates)))
+ template_idx = templates.index(template)
+
+ # Get EPG
+ epgs = [e.get("name") for e in schema_obj.get("templates")[template_idx]["externalEpgs"]]
+ if external_epg not in epgs:
+ mso.fail_json(msg="Provided epg '{epg}' does not exist. Existing epgs: {epgs}".format(epg=external_epg, epgs=", ".join(epgs)))
+ epg_idx = epgs.index(external_epg)
+
+ # Get Contract
+ if contract:
+ contracts = [
+ (c.get("contractRef"), c.get("relationshipType"))
+ for c in schema_obj.get("templates")[template_idx]["externalEpgs"][epg_idx]["contractRelationships"]
+ ]
+ contract_ref = mso.contract_ref(**contract)
+ if (contract_ref, contract.get("type")) in contracts:
+ contract_idx = contracts.index((contract_ref, contract.get("type")))
+ contract_path = "/templates/{0}/externalEpgs/{1}/contractRelationships/{2}".format(template, external_epg, contract_idx)
+ mso.existing = schema_obj.get("templates")[template_idx]["externalEpgs"][epg_idx]["contractRelationships"][contract_idx]
+
+ if state == "query":
+ if not contract:
+ mso.existing = schema_obj.get("templates")[template_idx]["externalEpgs"][epg_idx]["contractRelationships"]
+ elif not mso.existing:
+ mso.fail_json(msg="Contract '{0}' not found".format(contract_ref))
+
+ if "contractRef" in mso.existing:
+ mso.existing["contractRef"] = mso.dict_from_ref(mso.existing.get("contractRef"))
+ mso.exit_json()
+
+ contracts_path = "/templates/{0}/externalEpgs/{1}/contractRelationships".format(template, external_epg)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == "absent":
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op="remove", path=contract_path))
+
+ elif state == "present":
+ payload = dict(
+ relationshipType=contract.get("type"),
+ contractRef=dict(
+ contractName=contract.get("name"),
+ templateName=contract.get("template"),
+ schemaId=contract.get("schema_id"),
+ ),
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op="replace", path=contract_path, value=mso.sent))
+ else:
+ ops.append(dict(op="add", path=contracts_path + "/-", value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if "contractRef" in mso.previous:
+ mso.previous["contractRef"] = mso.dict_from_ref(mso.previous.get("contractRef"))
+
+ if not module.check_mode and mso.proposed != mso.previous:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_selector.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_selector.py
new file mode 100644
index 000000000..7d4b93b27
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_selector.py
@@ -0,0 +1,250 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com>
+# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_template_external_epg_selector
+short_description: Manage External EPG selector in schema templates
+description:
+- Manage External EPG selector in schema templates on Cisco ACI Multi-Site.
+author:
+- Shreyas Srish (@shrsr)
+- Cindy Zhao (@cizhao)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template to change.
+ type: str
+ required: true
+ external_epg:
+ description:
+ - The name of the External EPG to be managed.
+ type: str
+ required: true
+ selector:
+ description:
+ - The name of the selector.
+ type: str
+ expressions:
+ description:
+ - Expressions associated to this selector.
+ type: list
+ elements: dict
+ suboptions:
+ type:
+ description:
+ - The name of the expression which in this case is always IP address.
+ required: true
+ type: str
+ choices: [ ip_address ]
+ operator:
+ description:
+ - The operator associated with the expression which in this case is always equals.
+ required: true
+ type: str
+ choices: [ equals ]
+ value:
+ description:
+ - The value of the IP Address / Subnet associated with the expression.
+ required: true
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+seealso:
+- module: cisco.mso.mso_schema_template_external_epg
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a selector to an External EPG
+ cisco.mso.mso_schema_template_external_epg_selector:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ external_epg: extEPG 1
+ selector: selector_1
+ expressions:
+ - type: ip_address
+ operator: equals
+ value: 10.0.0.0
+ state: present
+ delegate_to: localhost
+
+- name: Remove a Selector
+ cisco.mso.mso_schema_template_external_epg_selector:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ external_epg: extEPG 1
+ selector: selector_1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific Selector
+ cisco.mso.mso_schema_template_external_epg_selector:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ external_epg: extEPG 1
+ selector: selector_1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all Selectors
+ cisco.mso.mso_schema_template_external_epg_selector:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ external_epg: extEPG 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_expression_spec_ext_epg
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ external_epg=dict(type="str", required=True),
+ selector=dict(type="str"),
+ expressions=dict(type="list", elements="dict", options=mso_expression_spec_ext_epg()),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["selector"]],
+ ["state", "present", ["selector"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ template = module.params.get("template").replace(" ", "")
+ external_epg = module.params.get("external_epg")
+ selector = module.params.get("selector")
+ expressions = module.params.get("expressions")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ # Get schema
+ schema_id, schema_path, schema_obj = mso.query_schema(schema)
+
+ # Get template
+ templates = [t.get("name") for t in schema_obj.get("templates")]
+ if template not in templates:
+ mso.fail_json(
+ msg="Provided template '{template}' does not exist. Existing templates: {templates}".format(template=template, templates=", ".join(templates))
+ )
+ template_idx = templates.index(template)
+
+ # Get External EPG
+ external_epgs = [e.get("name") for e in schema_obj.get("templates")[template_idx]["externalEpgs"]]
+ if external_epg not in external_epgs:
+ mso.fail_json(
+ msg="Provided external epg '{external_epg}' does not exist. Existing epgs: {external_epgs}".format(
+ external_epg=external_epg, external_epgs=", ".join(external_epgs)
+ )
+ )
+ external_epg_idx = external_epgs.index(external_epg)
+
+ # Get Selector
+ selectors = [s.get("name") for s in schema_obj.get("templates")[template_idx]["externalEpgs"][external_epg_idx]["selectors"]]
+ if selector in selectors:
+ selector_idx = selectors.index(selector)
+ selector_path = "/templates/{0}/externalEpgs/{1}/selectors/{2}".format(template, external_epg, selector_idx)
+ mso.existing = schema_obj.get("templates")[template_idx]["externalEpgs"][external_epg_idx]["selectors"][selector_idx]
+
+ if state == "query":
+ if selector is None:
+ mso.existing = schema_obj.get("templates")[template_idx]["externalEpgs"][external_epg_idx]["selectors"]
+ elif not mso.existing:
+ mso.fail_json(msg="Selector '{selector}' not found".format(selector=selector))
+ mso.exit_json()
+
+ selectors_path = "/templates/{0}/externalEpgs/{1}/selectors/-".format(template, external_epg)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == "absent":
+ mso.sent = mso.existing = {}
+ ops.append(dict(op="remove", path=selector_path))
+
+ elif state == "present":
+ # Get expressions
+ types = dict(ip_address="ipAddress")
+ all_expressions = []
+ if expressions:
+ for expression in expressions:
+ type_val = expression.get("type")
+ operator = expression.get("operator")
+ value = expression.get("value")
+ all_expressions.append(
+ dict(
+ key=types.get(type_val),
+ operator=operator,
+ value=value,
+ )
+ )
+
+ payload = dict(
+ name=selector,
+ expressions=all_expressions,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op="replace", path=selector_path, value=mso.sent))
+ else:
+ ops.append(dict(op="add", path=selectors_path, value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode and mso.existing != mso.previous:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_subnet.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_subnet.py
new file mode 100644
index 000000000..c7512c2ee
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_subnet.py
@@ -0,0 +1,224 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_template_external_epg_subnet
+short_description: Manage External EPG subnets in schema templates
+description:
+- Manage External EPG subnets in schema templates on Cisco ACI Multi-Site.
+author:
+- Devarshi Shah (@devarshishah3)
+- Anvitha Jain (@anvitha-jain)
+version_added: '0.0.8'
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template to change.
+ type: str
+ required: true
+ external_epg:
+ description:
+ - The name of the External EPG to manage.
+ type: str
+ required: true
+ subnet:
+ description:
+ - The IP range in CIDR notation.
+ type: str
+ scope:
+ description:
+ - The scope parameter contains two sections 1. Route Control and 2. External EPG Classification.
+ - The existing Route Control parameters are C(export-rtctrl) for Export Route Control, C(import-rtctrl) for Import Route Control
+ - and C(shared-rtctrl) for Shared Route Control
+ - The existing External EPG Classification parameters are C(import-security) for External Subnets for External EPG
+ - and C(shared-security) for Shared Security Import
+ - The C(shared-security) for Shared Security Import can only be used when External Subnets for External EPG is present
+ type: list
+ elements: str
+ aggregate:
+ description:
+ - The aggregate option aggregates shared routes for the subnet.
+ - Use C(shared-rtctrl) to add Aggregate Shared Routes
+ - The C(shared-rtctrl) option can only be used when scope parameter Shared Route Control in the Route Control section is selected.
+ type: list
+ elements: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+notes:
+- Due to restrictions of the MSO REST API concurrent modifications to EPG subnets can be dangerous and corrupt data.
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new subnet to an External EPG
+ cisco.mso.mso_schema_template_external_epg_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ external_epg: EPG 1
+ subnet: 10.0.0.0/24
+ state: present
+ delegate_to: localhost
+
+- name: Remove a subnet from an External EPG
+ cisco.mso.mso_schema_template_external_epg_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ external_epg: EPG 1
+ subnet: 10.0.0.0/24
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific External EPG subnet
+ cisco.mso.mso_schema_template_external_epg_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ external_epg: EPG 1
+ subnet: 10.0.0.0/24
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all External EPGs subnets
+ cisco.mso.mso_schema_template_external_epg_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ external_epg=dict(type="str", required=True),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ subnet=dict(type="str"),
+ scope=dict(type="list", elements="str", default=[]),
+ aggregate=dict(type="list", elements="str", default=[]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["subnet"]],
+ ["state", "present", ["subnet"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ template = module.params.get("template").replace(" ", "")
+ external_epg = module.params.get("external_epg")
+ subnet = module.params.get("subnet")
+ scope = module.params.get("scope")
+ aggregate = module.params.get("aggregate")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ # Get schema
+ schema_id, schema_path, schema_obj = mso.query_schema(schema)
+
+ # Get template
+ templates = [t.get("name") for t in schema_obj.get("templates")]
+ if template not in templates:
+ mso.fail_json(
+ msg="Provided template '{template}' does not exist. Existing templates: {templates}".format(template=template, templates=", ".join(templates))
+ )
+ template_idx = templates.index(template)
+
+ # Get EPG
+ external_epgs = [e.get("name") for e in schema_obj.get("templates")[template_idx]["externalEpgs"]]
+ if external_epg not in external_epgs:
+ mso.fail_json(msg="Provided External EPG '{epg}' does not exist. Existing epgs: {epgs}".format(epg=external_epg, epgs=", ".join(external_epgs)))
+ epg_idx = external_epgs.index(external_epg)
+
+ # Get Subnet
+ subnets = [s.get("ip") for s in schema_obj.get("templates")[template_idx]["externalEpgs"][epg_idx]["subnets"]]
+ if subnet in subnets:
+ subnet_idx = subnets.index(subnet)
+ # FIXME: Changes based on index are DANGEROUS
+ subnet_path = "/templates/{0}/externalEpgs/{1}/subnets/{2}".format(template, external_epg, subnet_idx)
+ mso.existing = schema_obj.get("templates")[template_idx]["externalEpgs"][epg_idx]["subnets"][subnet_idx]
+
+ if state == "query":
+ if subnet is None:
+ mso.existing = schema_obj.get("templates")[template_idx]["externalEpgs"][epg_idx]["subnets"]
+ elif not mso.existing:
+ mso.fail_json(msg="Subnet '{subnet}' not found".format(subnet=subnet))
+ mso.exit_json()
+
+ subnets_path = "/templates/{0}/externalEpgs/{1}/subnets".format(template, external_epg)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == "absent":
+ if mso.existing:
+ mso.existing = {}
+ ops.append(dict(op="remove", path=subnet_path))
+
+ elif state == "present":
+ payload = dict(
+ ip=subnet,
+ scope=scope,
+ aggregate=aggregate,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op="replace", path=subnet_path, value=mso.sent))
+ else:
+ ops.append(dict(op="add", path=subnets_path + "/-", value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_externalepg.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_externalepg.py
new file mode 100644
index 000000000..ce201913f
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_externalepg.py
@@ -0,0 +1,337 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_template_external_epg
+short_description: Manage external EPGs in schema templates
+description:
+- Manage external EPGs in schema templates on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: true
+ external_epg:
+ description:
+ - The name of the external EPG to manage.
+ type: str
+ aliases: [ name, externalepg ]
+ description:
+ description:
+ - The description of external EPG is supported on versions of MSO that are 3.3 or greater.
+ type: str
+ type:
+ description:
+ - The type of external epg.
+ - anp needs to be associated with external epg when the type is cloud.
+ - l3out can be associated with external epg when the type is on-premise.
+ type: str
+ choices: [ on-premise, cloud ]
+ default: on-premise
+ display_name:
+ description:
+ - The name as displayed on the MSO web interface.
+ type: str
+ vrf:
+ description:
+ - The VRF associated with the external epg.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The name of the VRF to associate with.
+ required: true
+ type: str
+ schema:
+ description:
+ - The schema that defines the referenced VRF.
+ - If this parameter is unspecified, it defaults to the current schema.
+ type: str
+ template:
+ description:
+ - The template that defines the referenced VRF.
+ - If this parameter is unspecified, it defaults to the current template.
+ type: str
+ l3out:
+ description:
+ - The L3Out associated with the external epg.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The name of the L3Out to associate with.
+ required: true
+ type: str
+ schema:
+ description:
+ - The schema that defines the referenced L3Out.
+ - If this parameter is unspecified, it defaults to the current schema.
+ type: str
+ template:
+ description:
+ - The template that defines the referenced L3Out.
+ - If this parameter is unspecified, it defaults to the current template.
+ type: str
+ anp:
+ description:
+ - The anp associated with the external epg.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The name of the anp to associate with.
+ required: true
+ type: str
+ schema:
+ description:
+ - The schema that defines the referenced anp.
+ - If this parameter is unspecified, it defaults to the current schema.
+ type: str
+ template:
+ description:
+ - The template that defines the referenced anp.
+ - If this parameter is unspecified, it defaults to the current template.
+ type: str
+ preferred_group:
+ description:
+ - Preferred Group is enabled for this External EPG or not.
+ type: bool
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new external EPG
+ cisco.mso.mso_schema_template_external_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ external_epg: External EPG 1
+ vrf:
+ name: VRF
+ schema: Schema 1
+ template: Template 1
+ state: present
+ delegate_to: localhost
+
+- name: Add a new external EPG with external epg in cloud
+ cisco.mso.mso_schema_template_external_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ external_epg: External EPG 1
+ type: cloud
+ vrf:
+ name: VRF
+ schema: Schema 1
+ template: Template 1
+ anp:
+ name: ANP1
+ schema: Schema 1
+ template: Template 1
+ state: present
+ delegate_to: localhost
+
+- name: Remove an external EPG
+ cisco.mso.mso_schema_template_external_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ external_epg: external EPG1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific external EPGs
+ cisco.mso.mso_schema_template_external_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ external_epg: external EPG1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all external EPGs
+ cisco.mso.mso_schema_template_external_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_reference_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ external_epg=dict(type="str", aliases=["name", "externalepg"]), # This parameter is not required for querying all objects
+ description=dict(type="str"),
+ display_name=dict(type="str"),
+ vrf=dict(type="dict", options=mso_reference_spec()),
+ l3out=dict(type="dict", options=mso_reference_spec()),
+ anp=dict(type="dict", options=mso_reference_spec()),
+ preferred_group=dict(type="bool"),
+ type=dict(type="str", default="on-premise", choices=["on-premise", "cloud"]),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["external_epg"]],
+ ["state", "present", ["external_epg", "vrf"]],
+ ["type", "cloud", ["anp"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ template = module.params.get("template").replace(" ", "")
+ external_epg = module.params.get("external_epg")
+ description = module.params.get("description")
+ display_name = module.params.get("display_name")
+ vrf = module.params.get("vrf")
+ if vrf is not None and vrf.get("template") is not None:
+ vrf["template"] = vrf.get("template").replace(" ", "")
+ l3out = module.params.get("l3out")
+ if l3out is not None and l3out.get("template") is not None:
+ l3out["template"] = l3out.get("template").replace(" ", "")
+ anp = module.params.get("anp")
+ if anp is not None and anp.get("template") is not None:
+ anp["template"] = anp.get("template").replace(" ", "")
+ preferred_group = module.params.get("preferred_group")
+ type_ext_epg = module.params.get("type")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ # Get schema objects
+ schema_id, schema_path, schema_obj = mso.query_schema(schema)
+
+ # Get template
+ templates = [t.get("name") for t in schema_obj.get("templates")]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates)))
+ template_idx = templates.index(template)
+
+ # Get external EPGs
+ external_epgs = [e.get("name") for e in schema_obj.get("templates")[template_idx]["externalEpgs"]]
+
+ if external_epg is not None and external_epg in external_epgs:
+ external_epg_idx = external_epgs.index(external_epg)
+ mso.existing = schema_obj.get("templates")[template_idx]["externalEpgs"][external_epg_idx]
+ if "externalEpgRef" in mso.existing:
+ del mso.existing["externalEpgRef"]
+ if "vrfRef" in mso.existing:
+ mso.existing["vrfRef"] = mso.dict_from_ref(mso.existing.get("vrfRef"))
+ if "l3outRef" in mso.existing:
+ mso.existing["l3outRef"] = mso.dict_from_ref(mso.existing.get("l3outRef"))
+ if "anpRef" in mso.existing:
+ mso.existing["anpRef"] = mso.dict_from_ref(mso.existing.get("anpRef"))
+
+ if state == "query":
+ if external_epg is None:
+ mso.existing = schema_obj.get("templates")[template_idx]["externalEpgs"]
+ elif not mso.existing:
+ mso.fail_json(msg="External EPG '{external_epg}' not found".format(external_epg=external_epg))
+ mso.exit_json()
+
+ eepgs_path = "/templates/{0}/externalEpgs".format(template)
+ eepg_path = "/templates/{0}/externalEpgs/{1}".format(template, external_epg)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == "absent":
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op="remove", path=eepg_path))
+
+ elif state == "present":
+ vrf_ref = mso.make_reference(vrf, "vrf", schema_id, template)
+ l3out_ref = mso.make_reference(l3out, "l3out", schema_id, template)
+ anp_ref = mso.make_reference(anp, "anp", schema_id, template)
+ if display_name is None and not mso.existing:
+ display_name = external_epg
+
+ payload = dict(
+ name=external_epg,
+ displayName=display_name,
+ vrfRef=vrf_ref,
+ preferredGroup=preferred_group,
+ )
+
+ if description is not None:
+ payload.update(description=description)
+
+ if type_ext_epg == "cloud":
+ payload["extEpgType"] = "cloud"
+ payload["anpRef"] = anp_ref
+ else:
+ payload["l3outRef"] = l3out_ref
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ # clean anpRef when anpRef is null
+ if "anpRef" in mso.existing and mso.existing.get("anpRef") is None:
+ del mso.existing["anpRef"]
+ # clean contractRef to fix api issue
+ for contract in mso.sent.get("contractRelationships"):
+ contract["contractRef"] = mso.dict_from_ref(contract.get("contractRef"))
+ ops.append(dict(op="replace", path=eepg_path, value=mso.sent))
+ else:
+ ops.append(dict(op="add", path=eepgs_path + "/-", value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode and mso.proposed != mso.previous:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_filter_entry.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_filter_entry.py
new file mode 100644
index 000000000..c0ab485a4
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_filter_entry.py
@@ -0,0 +1,369 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_template_filter_entry
+short_description: Manage filter entries in schema templates
+description:
+- Manage filter entries in schema templates on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+- Anvitha Jain (@anvitha-jain)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: true
+ filter:
+ description:
+ - The name of the filter to manage.
+ - There should be no space in the filter name. APIC will throw an error if a space is provided in the filter name.
+ - See the C(filter_display_name) attribute if you want the display name of the filter to contain a space.
+ type: str
+ required: true
+ filter_description:
+ description:
+ - The description of this filter is supported on versions of MSO that are 3.3 or greater.
+ type: str
+ default: ''
+ filter_display_name:
+ description:
+ - The name as displayed on the MSO web interface.
+ type: str
+ entry:
+ description:
+ - The filter entry name to manage.
+ type: str
+ aliases: [ name ]
+ display_name:
+ description:
+ - The name as displayed on the MSO web interface.
+ type: str
+ aliases: [ entry_display_name ]
+ filter_entry_description:
+ description:
+ - The description of this filter entry.
+ type: str
+ aliases: [ entry_description, description ]
+ default: ''
+ ethertype:
+ description:
+ - The ethernet type to use for this filter entry.
+ type: str
+ choices: [ arp, fcoe, ip, ipv4, ipv6, mac-security, mpls-unicast, trill, unspecified ]
+ ip_protocol:
+ description:
+ - The IP protocol to use for this filter entry.
+ type: str
+ choices: [ eigrp, egp, icmp, icmpv6, igmp, igp, l2tp, ospfigp, pim, tcp, udp, unspecified ]
+ tcp_session_rules:
+ description:
+ - A list of TCP session rules.
+ type: list
+ elements: str
+ choices: [ acknowledgement, established, finish, synchronize, reset, unspecified ]
+ source_from:
+ description:
+ - The source port range from.
+ type: str
+ source_to:
+ description:
+ - The source port range to.
+ type: str
+ destination_from:
+ description:
+ - The destination port range from.
+ type: str
+ destination_to:
+ description:
+ - The destination port range to.
+ type: str
+ arp_flag:
+ description:
+ - The ARP flag to use for this filter entry.
+ type: str
+ choices: [ reply, request, unspecified ]
+ stateful:
+ description:
+ - Whether this filter entry is stateful.
+ type: bool
+ fragments_only:
+ description:
+ - Whether this filter entry only matches fragments.
+ type: bool
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+seealso:
+- module: cisco.mso.mso_schema_template_contract_filter
+notes:
+- Due to restrictions of the MSO REST API this module creates filters when needed, and removes them when the last entry has been removed.
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new filter entry
+ cisco.mso.mso_schema_template_filter_entry:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ filter: Filter 1
+ state: present
+ delegate_to: localhost
+
+- name: Remove a filter entry
+ cisco.mso.mso_schema_template_filter_entry:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ filter: Filter 1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific filter entry
+ cisco.mso.mso_schema_template_filter_entry:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ filter: Filter 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all filter entries
+ cisco.mso.mso_schema_template_filter_entry:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ filter=dict(type="str", required=True),
+ filter_description=dict(type="str", default=""),
+ filter_display_name=dict(type="str"),
+ entry=dict(type="str", aliases=["name"]), # This parameter is not required for querying all objects
+ filter_entry_description=dict(type="str", default="", aliases=["entry_description", "description"]),
+ display_name=dict(type="str", aliases=["entry_display_name"]),
+ ethertype=dict(type="str", choices=["arp", "fcoe", "ip", "ipv4", "ipv6", "mac-security", "mpls-unicast", "trill", "unspecified"]),
+ ip_protocol=dict(type="str", choices=["eigrp", "egp", "icmp", "icmpv6", "igmp", "igp", "l2tp", "ospfigp", "pim", "tcp", "udp", "unspecified"]),
+ tcp_session_rules=dict(type="list", elements="str", choices=["acknowledgement", "established", "finish", "synchronize", "reset", "unspecified"]),
+ source_from=dict(type="str"),
+ source_to=dict(type="str"),
+ destination_from=dict(type="str"),
+ destination_to=dict(type="str"),
+ arp_flag=dict(type="str", choices=["reply", "request", "unspecified"]),
+ stateful=dict(type="bool"),
+ fragments_only=dict(type="bool"),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["entry"]],
+ ["state", "present", ["entry"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ template = module.params.get("template").replace(" ", "")
+ filter_name = module.params.get("filter")
+ filter_display_name = module.params.get("filter_display_name")
+ filter_description = module.params.get("filter_description")
+ entry = module.params.get("entry")
+ display_name = module.params.get("display_name")
+ filter_entry_description = module.params.get("filter_entry_description")
+ ethertype = module.params.get("ethertype")
+ ip_protocol = module.params.get("ip_protocol")
+ tcp_session_rules = module.params.get("tcp_session_rules")
+ source_from = module.params.get("source_from")
+ source_to = module.params.get("source_to")
+ destination_from = module.params.get("destination_from")
+ destination_to = module.params.get("destination_to")
+ arp_flag = module.params.get("arp_flag")
+ if arp_flag == "request":
+ arp_flag = "req"
+ stateful = module.params.get("stateful")
+ fragments_only = module.params.get("fragments_only")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ # Get schema
+ schema_id, schema_path, schema_obj = mso.query_schema(schema)
+
+ # Get template
+ templates = [t.get("name") for t in schema_obj.get("templates")]
+ if template not in templates:
+ mso.fail_json(
+ msg="Provided template '{template}' does not exist. Existing templates: {templates}".format(template=template, templates=", ".join(templates))
+ )
+ template_idx = templates.index(template)
+
+ # Get filters
+ mso.existing = {}
+ filter_idx = None
+ entry_idx = None
+ filters = [f.get("name") for f in schema_obj.get("templates")[template_idx]["filters"]]
+ if filter_name in filters:
+ filter_idx = filters.index(filter_name)
+
+ entries = [f.get("name") for f in schema_obj.get("templates")[template_idx]["filters"][filter_idx]["entries"]]
+ if entry in entries:
+ entry_idx = entries.index(entry)
+ mso.existing = schema_obj.get("templates")[template_idx]["filters"][filter_idx]["entries"][entry_idx]
+
+ if state == "query":
+ if entry is None:
+ if filter_idx is None:
+ mso.fail_json(msg="Filter '{filter}' not found".format(filter=filter_name))
+ mso.existing = schema_obj.get("templates")[template_idx]["filters"][filter_idx]["entries"]
+ elif not mso.existing:
+ mso.fail_json(msg="Entry '{entry}' not found".format(entry=entry))
+ mso.exit_json()
+
+ filters_path = "/templates/{0}/filters".format(template)
+ filter_path = "/templates/{0}/filters/{1}".format(template, filter_name)
+ entries_path = "/templates/{0}/filters/{1}/entries".format(template, filter_name)
+ entry_path = "/templates/{0}/filters/{1}/entries/{2}".format(template, filter_name, entry)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == "absent":
+ mso.proposed = mso.sent = {}
+
+ if filter_idx is None:
+ # There was no filter to begin with
+ pass
+ elif entry_idx is None:
+ # There was no entry to begin with
+ pass
+ elif len(entries) == 1:
+ # There is only one entry, remove filter
+ mso.existing = {}
+ ops.append(dict(op="remove", path=filter_path))
+
+ else:
+ mso.existing = {}
+ ops.append(dict(op="remove", path=entry_path))
+
+ elif state == "present":
+ if not mso.existing:
+ if display_name is None:
+ display_name = entry
+ if ethertype is None:
+ ethertype = "unspecified"
+ if ip_protocol is None:
+ ip_protocol = "unspecified"
+ if tcp_session_rules is None:
+ tcp_session_rules = ["unspecified"]
+ if source_from is None:
+ source_from = "unspecified"
+ if source_to is None:
+ source_to = "unspecified"
+ if destination_from is None:
+ destination_from = "unspecified"
+ if destination_to is None:
+ destination_to = "unspecified"
+ if arp_flag is None:
+ arp_flag = "unspecified"
+ if stateful is None:
+ stateful = False
+ if fragments_only is None:
+ fragments_only = False
+
+ payload = dict(
+ name=entry,
+ displayName=display_name,
+ description=filter_entry_description,
+ etherType=ethertype,
+ ipProtocol=ip_protocol,
+ tcpSessionRules=tcp_session_rules,
+ sourceFrom=source_from,
+ sourceTo=source_to,
+ destinationFrom=destination_from,
+ destinationTo=destination_to,
+ arpFlag=arp_flag,
+ stateful=stateful,
+ matchOnlyFragments=fragments_only,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if filter_idx is None:
+ # Filter does not exist, so we have to create it
+ if filter_display_name is None:
+ filter_display_name = filter_name
+
+ payload = dict(
+ name=filter_name,
+ displayName=filter_display_name,
+ description=filter_description,
+ entries=[mso.sent],
+ )
+
+ ops.append(dict(op="add", path=filters_path + "/-", value=payload))
+
+ elif entry_idx is None:
+ # Entry does not exist, so we have to add it
+ ops.append(dict(op="add", path=entries_path + "/-", value=mso.sent))
+
+ else:
+ # Entry exists, we have to update it
+ for key, value in mso.sent.items():
+ ops.append(dict(op="replace", path=entry_path + "/" + key, value=value))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_l3out.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_l3out.py
new file mode 100644
index 000000000..4b8c1b66d
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_l3out.py
@@ -0,0 +1,233 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_template_l3out
+short_description: Manage l3outs in schema templates
+description:
+- Manage l3outs in schema templates on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: true
+ l3out:
+ description:
+ - The name of the l3out to manage.
+ type: str
+ aliases: [ name ]
+ description:
+ description:
+ - The description of l3out is supported on versions of MSO that are 3.3 or greater.
+ type: str
+ display_name:
+ description:
+ - The name as displayed on the MSO web interface.
+ type: str
+ vrf:
+ description:
+ - The VRF associated to this L3out.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The name of the VRF to associate with.
+ required: true
+ type: str
+ schema:
+ description:
+ - The schema that defines the referenced VRF.
+ - If this parameter is unspecified, it defaults to the current schema.
+ type: str
+ template:
+ description:
+ - The template that defines the referenced VRF.
+ - If this parameter is unspecified, it defaults to the current schema.
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new L3out
+ cisco.mso.mso_schema_template_l3out:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ validate_certs: false
+ schema: Schema 1
+ template: Template 1
+ l3out: L3out 1
+ vrf:
+ name: vrfName
+ schema: vrfSchema
+ template: vrfTemplate
+ state: present
+ delegate_to: localhost
+
+- name: Remove an L3out
+ cisco.mso.mso_schema_template_l3out:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ l3out: L3out 1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific L3outs
+ cisco.mso.mso_schema_template_l3out:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ validate_certs: false
+ schema: Schema 1
+ template: Template 1
+ l3out: L3out 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all L3outs
+ cisco.mso.mso_schema_template_l3out:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ validate_certs: false
+ schema: Schema 1
+ template: Template 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_reference_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ l3out=dict(type="str", aliases=["name"]), # This parameter is not required for querying all objects
+ description=dict(type="str"),
+ display_name=dict(type="str"),
+ vrf=dict(type="dict", options=mso_reference_spec()),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["l3out"]],
+ ["state", "present", ["l3out", "vrf"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ template = module.params.get("template").replace(" ", "")
+ l3out = module.params.get("l3out")
+ description = module.params.get("description")
+ display_name = module.params.get("display_name")
+ vrf = module.params.get("vrf")
+ if vrf is not None and vrf.get("template") is not None:
+ vrf["template"] = vrf.get("template").replace(" ", "")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ # Get schema objects
+ schema_id, schema_path, schema_obj = mso.query_schema(schema)
+
+ # Get template
+ templates = [t.get("name") for t in schema_obj.get("templates")]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates)))
+ template_idx = templates.index(template)
+
+ # Get L3out
+ l3outs = [l3.get("name") for l3 in schema_obj.get("templates")[template_idx]["intersiteL3outs"]]
+
+ if l3out is not None and l3out in l3outs:
+ l3out_idx = l3outs.index(l3out)
+ mso.existing = schema_obj.get("templates")[template_idx]["intersiteL3outs"][l3out_idx]
+
+ if state == "query":
+ if l3out is None:
+ mso.existing = schema_obj.get("templates")[template_idx]["intersiteL3outs"]
+ elif not mso.existing:
+ mso.fail_json(msg="L3out '{l3out}' not found".format(l3out=l3out))
+ mso.exit_json()
+
+ l3outs_path = "/templates/{0}/intersiteL3outs".format(template)
+ l3out_path = "/templates/{0}/intersiteL3outs/{1}".format(template, l3out)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == "absent":
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op="remove", path=l3out_path))
+
+ elif state == "present":
+ vrf_ref = mso.make_reference(vrf, "vrf", schema_id, template)
+
+ if display_name is None and not mso.existing:
+ display_name = l3out
+
+ payload = dict(
+ name=l3out,
+ displayName=display_name,
+ vrfRef=vrf_ref,
+ )
+
+ if description is not None:
+ payload.update(description=description)
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op="replace", path=l3out_path, value=mso.sent))
+ else:
+ ops.append(dict(op="add", path=l3outs_path + "/-", value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_migrate.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_migrate.py
new file mode 100644
index 000000000..d0e15b8d0
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_migrate.py
@@ -0,0 +1,246 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_template_migrate
+short_description: Migrate Bridge Domains (BDs) and EPGs between templates
+description:
+- Migrate BDs and EPGs between templates of same and different schemas.
+author:
+- Anvitha Jain (@anvitha-jain)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: true
+ bds:
+ description:
+ - The name of the BDs to migrate.
+ type: list
+ elements: str
+ epgs:
+ description:
+ - The name of the EPGs and the ANP it is in to migrate.
+ type: list
+ elements: dict
+ suboptions:
+ epg:
+ description:
+ - The name of the EPG to migrate.
+ type: str
+ required: true
+ anp:
+ description:
+ - The name of the anp to migrate.
+ type: str
+ required: true
+ target_schema:
+ description:
+ - The name of the target_schema.
+ type: str
+ required: true
+ target_template:
+ description:
+ - The name of the target_template.
+ type: str
+ required: true
+ state:
+ description:
+ - Use C(present) for adding.
+ type: str
+ default: present
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Migration of objects between templates of same schema
+ mso_schema_template_migrate:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ target_schema: Schema 1
+ target_template: Template 2
+ bds:
+ - BD
+ epgs:
+ - epg: EPG1
+ anp: ANP
+ state: present
+ delegate_to: localhost
+
+- name: Migration of objects between templates of different schema
+ mso_schema_template_migrate:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ target_schema: Schema 2
+ target_template: Template 2
+ bds:
+ - BD
+ epgs:
+ - epg: EPG1
+ anp: ANP
+ state: present
+ delegate_to: localhost
+
+- name: Migration of BD object between templates of same schema
+ mso_schema_template_migrate:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ target_schema: Schema 1
+ target_template: Template 2
+ bds:
+ - BD
+ - BD1
+ state: present
+ delegate_to: localhost
+
+- name: Migration of BD object between templates of different schema
+ mso_schema_template_migrate:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ target_schema: Schema 2
+ target_template: Template 2
+ bds:
+ - BD
+ - BD1
+ state: present
+ delegate_to: localhost
+
+- name: Migration of EPG objects between templates of same schema
+ mso_schema_template_migrate:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ target_schema: Schema 2
+ target_template: Template 2
+ epgs:
+ - epg: EPG1
+ anp: ANP
+ - epg: EPG2
+ anp: ANP2
+ state: present
+ delegate_to: localhost
+
+- name: Migration of EPG objects between templates of different schema
+ mso_schema_template_migrate:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ target_schema: Schema 2
+ target_template: Template 2
+ epgs:
+ - epg: EPG1
+ anp: ANP
+ - epg: EPG2
+ anp: ANP2
+ state: present
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_object_migrate_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ bds=dict(type="list", elements="str"),
+ epgs=dict(type="list", elements="dict", options=mso_object_migrate_spec()),
+ target_schema=dict(type="str", required=True),
+ target_template=dict(type="str", required=True),
+ state=dict(type="str", default="present"),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ )
+
+ schema = module.params.get("schema")
+ template = module.params.get("template").replace(" ", "")
+ target_schema = module.params.get("target_schema")
+ target_template = module.params.get("target_template").replace(" ", "")
+ bds = module.params.get("bds")
+ epgs = module.params.get("epgs")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ # Get schema_id
+ schema_id = mso.lookup_schema(schema)
+
+ target_schema_id = mso.lookup_schema(target_schema)
+
+ if state == "present":
+ if schema_id is not None:
+ bds_payload = []
+ if bds is not None:
+ for bd in bds:
+ bds_payload.append(dict(name=bd))
+
+ anp_dict = {}
+ if epgs is not None:
+ for epg in epgs:
+ if epg.get("anp") in anp_dict:
+ anp_dict[epg.get("anp")].append(dict(name=epg.get("epg")))
+ else:
+ anp_dict[epg.get("anp")] = [dict(name=epg.get("epg"))]
+
+ anps_payload = []
+ for anp, epgs_payload in anp_dict.items():
+ anps_payload.append(dict(name=anp, epgs=epgs_payload))
+
+ payload = dict(
+ targetSchemaId=target_schema_id,
+ targetTemplateName=target_template,
+ bds=bds_payload,
+ anps=anps_payload,
+ )
+
+ template = template.replace(" ", "%20")
+
+ target_template = target_template.replace(" ", "%20") # removes API error for extra space
+
+ mso.existing = mso.request(path="migrate/schema/{0}/template/{1}".format(schema_id, template), method="POST", data=payload)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_service_graph.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_service_graph.py
new file mode 100644
index 000000000..70fadd804
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_service_graph.py
@@ -0,0 +1,270 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2022, Shreyas Srish (@shrsr) <ssrish@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_template_service_graph
+short_description: Manage Service Graph in schema templates
+description:
+- Manage Service Graph in schema templates on Cisco ACI Multi-Site.
+author:
+- Shreyas Srish (@shrsr)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: true
+ service_graph:
+ description:
+ - The name of the Service Graph to manage.
+ type: str
+ aliases: [ name ]
+ description:
+ description:
+ - The description of Service Graph.
+ type: str
+ default: ''
+ display_name:
+ description:
+ - The name as displayed on the MSO web interface.
+ type: str
+ service_nodes:
+ description:
+ - A list of node types to be associated with the Service Graph.
+ type: list
+ elements: dict
+ suboptions:
+ type:
+ description:
+ - The type of node
+ required: true
+ type: str
+ filter_after_first_node:
+ description:
+ - The filter applied after the first node.
+ type: str
+ choices: [ allow_all, filters_from_contract ]
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new Service Graph
+ cisco.mso.mso_schema_template_service_graph:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ template: Template1
+ service_graph: graph1
+ service_nodes:
+ - type: firewall
+ - type: other
+ - type: load-balancer
+ state: present
+ delegate_to: localhost
+
+- name: Remove a Service Graph
+ cisco.mso.mso_schema_template_service_graph:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ template: Template1
+ service_graph: graph1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific Service Graph
+ cisco.mso.mso_schema_template_service_graph:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ template: Template1
+ service_graph: graph1
+ state: query
+ delegate_to: localhost
+
+- name: Query all Service Graphs
+ cisco.mso.mso_schema_template_service_graph:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ template: Template1
+ state: query
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_service_graph_node_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ service_graph=dict(type="str", aliases=["name"]),
+ description=dict(type="str", default=""),
+ display_name=dict(type="str"),
+ service_nodes=dict(type="list", elements="dict", options=mso_service_graph_node_spec()),
+ filter_after_first_node=dict(type="str", choices=["allow_all", "filters_from_contract"]),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["service_graph"]],
+ ["state", "present", ["service_graph", "service_nodes"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ template = module.params.get("template").replace(" ", "")
+ service_graph = module.params.get("service_graph")
+ display_name = module.params.get("display_name")
+ description = module.params.get("description")
+ service_nodes = module.params.get("service_nodes")
+ filter_after_first_node = module.params.get("filter_after_first_node")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ # Get schema
+ schema_id, schema_path, schema_obj = mso.query_schema(schema)
+
+ # Get template
+ templates = [t.get("name") for t in schema_obj.get("templates")]
+ if template not in templates:
+ mso.fail_json(
+ msg="Provided template '{template}' does not exist. Existing templates: {templates}".format(template=template, templates=", ".join(templates))
+ )
+ template_idx = templates.index(template)
+
+ mso.existing = {}
+ service_graph_idx = None
+
+ # Get Service Graphs
+ service_graphs = [f.get("name") for f in schema_obj.get("templates")[template_idx]["serviceGraphs"]]
+ if service_graph in service_graphs:
+ service_graph_idx = service_graphs.index(service_graph)
+ mso.existing = schema_obj.get("templates")[template_idx]["serviceGraphs"][service_graph_idx]
+
+ if state == "query":
+ if service_graph is None:
+ mso.existing = schema_obj.get("templates")[template_idx]["serviceGraphs"]
+ if service_graph is not None and service_graph_idx is None:
+ mso.fail_json(msg="Service Graph '{service_graph}' not found".format(service_graph=service_graph))
+ mso.exit_json()
+
+ service_graphs_path = "/templates/{0}/serviceGraphs/-".format(template)
+ service_graph_path = "/templates/{0}/serviceGraphs/{1}".format(template, service_graph)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == "absent":
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op="remove", path=service_graph_path))
+
+ elif state == "present":
+ nodes_payload = []
+ service_node_index = 0
+
+ if filter_after_first_node == "allow_all":
+ filter_after_first_node = "allow-all"
+ elif filter_after_first_node == "filters_from_contract":
+ filter_after_first_node = "filters-from-contract"
+
+ if display_name is None:
+ display_name = service_graph
+
+ # Get service nodes
+ query_node_data = mso.query_service_node_types()
+ service_node_types = [f.get("name") for f in query_node_data]
+ if service_nodes is not None:
+ for node in service_nodes:
+ node_name = node.get("type")
+ if node_name in service_node_types:
+ service_node_index = service_node_index + 1
+ for node_data in query_node_data:
+ if node_data["name"] == node_name:
+ payload = dict(
+ name=node_name,
+ serviceNodeTypeId=node_data.get("id"),
+ index=service_node_index,
+ serviceNodeRef=dict(
+ serviceNodeName=node_name,
+ serviceGraphName=service_graph,
+ templateName=template,
+ schemaId=schema_id,
+ ),
+ )
+ if node_data.get("uuid"):
+ payload.update(uuid=node_data.get("uuid"))
+ nodes_payload.append(payload)
+ else:
+ mso.fail_json(
+ "Provided service node type '{node_name}' does not exist. Existing service node types include: {node_types}".format(
+ node_name=node_name, node_types=", ".join(service_node_types)
+ )
+ )
+
+ payload = dict(
+ name=service_graph,
+ displayName=display_name,
+ description=description,
+ nodeFilter=filter_after_first_node,
+ serviceGraphRef=dict(
+ serviceGraphName=service_graph,
+ templateName=template,
+ schemaId=schema_id,
+ ),
+ serviceNodes=nodes_payload,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if not mso.existing:
+ ops.append(dict(op="add", path=service_graphs_path, value=payload))
+ else:
+ ops.append(dict(op="replace", path=service_graph_path, value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_vrf.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_vrf.py
new file mode 100644
index 000000000..efafd2387
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_vrf.py
@@ -0,0 +1,228 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2023, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_template_vrf
+short_description: Manage VRFs in schema templates
+description:
+- Manage VRFs in schema templates on Cisco ACI Multi-Site.
+author:
+- Anvitha Jain (@anvitha-jain)
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: true
+ vrf:
+ description:
+ - The name of the VRF to manage.
+ type: str
+ aliases: [ name ]
+ display_name:
+ description:
+ - The name as displayed on the MSO web interface.
+ type: str
+ layer3_multicast:
+ description:
+ - Whether to enable L3 multicast.
+ type: bool
+ vzany:
+ description:
+ - Whether to enable vzAny.
+ type: bool
+ ip_data_plane_learning:
+ description:
+ - Whether IP data plane learning is enabled or disabled.
+ - The APIC defaults to C(enabled) when unset during creation.
+ type: str
+ choices: [ disabled, enabled ]
+ preferred_group:
+ description:
+ - Whether to enable preferred Endpoint Group.
+ - The APIC defaults to C(false) when unset during creation.
+ type: bool
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new VRF
+ cisco.mso.mso_schema_template_vrf:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ vrf: VRF 1
+ state: present
+ delegate_to: localhost
+
+- name: Remove an VRF
+ cisco.mso.mso_schema_template_vrf:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ vrf: VRF1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific VRFs
+ cisco.mso.mso_schema_template_vrf:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ vrf: VRF1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all VRFs
+ cisco.mso.mso_schema_template_vrf:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ vrf=dict(type="str", aliases=["name"]), # This parameter is not required for querying all objects
+ display_name=dict(type="str"),
+ layer3_multicast=dict(type="bool"),
+ vzany=dict(type="bool"),
+ preferred_group=dict(type="bool"),
+ ip_data_plane_learning=dict(type="str", choices=["enabled", "disabled"]),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["vrf"]],
+ ["state", "present", ["vrf"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ template = module.params.get("template").replace(" ", "")
+ vrf = module.params.get("vrf")
+ display_name = module.params.get("display_name")
+ layer3_multicast = module.params.get("layer3_multicast")
+ vzany = module.params.get("vzany")
+ ip_data_plane_learning = module.params.get("ip_data_plane_learning")
+ preferred_group = module.params.get("preferred_group")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ # Get schema
+ schema_id, schema_path, schema_obj = mso.query_schema(schema)
+
+ # Get template
+ templates = [t.get("name") for t in schema_obj.get("templates")]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates)))
+ template_idx = templates.index(template)
+
+ # Get ANP
+ vrfs = [v.get("name") for v in schema_obj.get("templates")[template_idx]["vrfs"]]
+
+ if vrf is not None and vrf in vrfs:
+ vrf_idx = vrfs.index(vrf)
+ mso.existing = schema_obj.get("templates")[template_idx]["vrfs"][vrf_idx]
+
+ if state == "query":
+ if vrf is None:
+ mso.existing = schema_obj.get("templates")[template_idx]["vrfs"]
+ elif not mso.existing:
+ mso.fail_json(msg="VRF '{vrf}' not found".format(vrf=vrf))
+ mso.exit_json()
+
+ vrfs_path = "/templates/{0}/vrfs".format(template)
+ vrf_path = "/templates/{0}/vrfs/{1}".format(template, vrf)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == "absent":
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op="remove", path=vrf_path))
+
+ elif state == "present":
+ if display_name is None and not mso.existing:
+ display_name = vrf
+
+ payload = dict(
+ name=vrf,
+ displayName=display_name,
+ l3MCast=layer3_multicast,
+ vzAnyEnabled=vzany,
+ preferredGroup=preferred_group,
+ ipDataPlaneLearning=ip_data_plane_learning,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ # clean contractRef to fix api issue
+ for contract in mso.sent.get("vzAnyConsumerContracts"):
+ contract["contractRef"] = mso.dict_from_ref(contract.get("contractRef"))
+ for contract in mso.sent.get("vzAnyProviderContracts"):
+ contract["contractRef"] = mso.dict_from_ref(contract.get("contractRef"))
+ ops.append(dict(op="replace", path=vrf_path, value=mso.sent))
+ else:
+ ops.append(dict(op="add", path=vrfs_path + "/-", value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_vrf_contract.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_vrf_contract.py
new file mode 100644
index 000000000..eaef8235c
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_vrf_contract.py
@@ -0,0 +1,263 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# 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 = r"""
+---
+module: mso_schema_template_vrf_contract
+short_description: Manage vrf contracts in schema templates
+description:
+- Manage vrf contracts in schema templates on Cisco ACI Multi-Site.
+author:
+- Cindy Zhao (@cizhao)
+version_added: '0.0.8'
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template to change.
+ type: str
+ required: true
+ vrf:
+ description:
+ - The name of the VRF.
+ type: str
+ required: true
+ contract:
+ description:
+ - A contract associated to this VRF.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The name of the Contract to associate with.
+ required: true
+ type: str
+ schema:
+ description:
+ - The schema that defines the referenced contract.
+ - If this parameter is unspecified, it defaults to the current schema.
+ type: str
+ template:
+ description:
+ - The template that defines the referenced contract.
+ type: str
+ type:
+ description:
+ - The type of contract.
+ type: str
+ required: true
+ choices: [ consumer, provider ]
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+seealso:
+- module: cisco.mso.mso_schema_template_vrf
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a contract to a VRF
+ cisco.mso.mso_schema_template_vrf_contract:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ vrf: VRF 1
+ contract:
+ name: Contract 1
+ type: consumer
+ state: present
+ delegate_to: localhost
+
+- name: Remove a Contract
+ cisco.mso.mso_schema_template_vrf_contract:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ vrf: VRF 1
+ contract:
+ name: Contract 1
+ type: consumer
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific Contract
+ cisco.mso.mso_schema_template_vrf_contract:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ vrf: VRF 1
+ contract:
+ name: Contract 1
+ type: consumer
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all Contracts
+ cisco.mso.mso_schema_template_vrf_contract:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ vrf: VRF 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_contractref_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ vrf=dict(type="str", required=True),
+ contract=dict(type="dict", options=mso_contractref_spec()),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["contract"]],
+ ["state", "present", ["contract"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ template = module.params.get("template").replace(" ", "")
+ vrf = module.params.get("vrf")
+ contract = module.params.get("contract")
+ if contract is not None and contract.get("template") is not None:
+ contract["template"] = contract.get("template").replace(" ", "")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+ if contract:
+ if contract.get("schema") is None:
+ contract["schema"] = schema
+ contract["schema_id"] = mso.lookup_schema(contract.get("schema"))
+ if contract.get("template") is None:
+ contract["template"] = template
+
+ # Get schema
+ schema_id, schema_path, schema_obj = mso.query_schema(schema)
+
+ # Get template
+ templates = [t.get("name") for t in schema_obj.get("templates")]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates)))
+ template_idx = templates.index(template)
+
+ # Get VRF
+ vrfs = [e.get("name") for e in schema_obj.get("templates")[template_idx]["vrfs"]]
+ if vrf not in vrfs:
+ mso.fail_json(msg="Provided vrf '{vrf}' does not exist. Existing vrfs: {vrfs}".format(vrf=vrf, vrfs=", ".join(vrfs)))
+ vrf_idx = vrfs.index(vrf)
+ vrf_obj = schema_obj.get("templates")[template_idx]["vrfs"][vrf_idx]
+
+ if not vrf_obj.get("vzAnyEnabled"):
+ mso.fail_json(msg="vzAny attribute on vrf '{0}' is disabled.".format(vrf))
+
+ # Get Contract
+ if contract:
+ provider_contracts = [c.get("contractRef") for c in schema_obj.get("templates")[template_idx]["vrfs"][vrf_idx]["vzAnyProviderContracts"]]
+ consumer_contracts = [c.get("contractRef") for c in schema_obj.get("templates")[template_idx]["vrfs"][vrf_idx]["vzAnyConsumerContracts"]]
+ contract_ref = mso.contract_ref(**contract)
+ if contract_ref in provider_contracts and contract.get("type") == "provider":
+ contract_idx = provider_contracts.index(contract_ref)
+ contract_path = "/templates/{0}/vrfs/{1}/vzAnyProviderContracts/{2}".format(template, vrf, contract_idx)
+ mso.existing = schema_obj.get("templates")[template_idx]["vrfs"][vrf_idx]["vzAnyProviderContracts"][contract_idx]
+ if contract_ref in consumer_contracts and contract.get("type") == "consumer":
+ contract_idx = consumer_contracts.index(contract_ref)
+ contract_path = "/templates/{0}/vrfs/{1}/vzAnyConsumerContracts/{2}".format(template, vrf, contract_idx)
+ mso.existing = schema_obj.get("templates")[template_idx]["vrfs"][vrf_idx]["vzAnyConsumerContracts"][contract_idx]
+ if mso.existing.get("contractRef"):
+ mso.existing["contractRef"] = mso.dict_from_ref(mso.existing.get("contractRef"))
+ mso.existing["relationshipType"] = contract.get("type")
+
+ if state == "query":
+ if not contract:
+ provider_contracts = [
+ dict(contractRef=mso.dict_from_ref(c.get("contractRef")), relationshipType="provider")
+ for c in schema_obj.get("templates")[template_idx]["vrfs"][vrf_idx]["vzAnyProviderContracts"]
+ ]
+ consumer_contracts = [
+ dict(contractRef=mso.dict_from_ref(c.get("contractRef")), relationshipType="consumer")
+ for c in schema_obj.get("templates")[template_idx]["vrfs"][vrf_idx]["vzAnyConsumerContracts"]
+ ]
+ mso.existing = provider_contracts + consumer_contracts
+ elif not mso.existing:
+ mso.fail_json(msg="Contract '{0}' not found".format(contract.get("name")))
+
+ mso.exit_json()
+
+ if contract.get("type") == "provider":
+ contracts_path = "/templates/{0}/vrfs/{1}/vzAnyProviderContracts/-".format(template, vrf)
+ if contract.get("type") == "consumer":
+ contracts_path = "/templates/{0}/vrfs/{1}/vzAnyConsumerContracts/-".format(template, vrf)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == "absent":
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op="remove", path=contract_path))
+
+ elif state == "present":
+ payload = dict(
+ contractRef=dict(
+ contractName=contract.get("name"),
+ templateName=contract.get("template"),
+ schemaId=contract.get("schema_id"),
+ ),
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op="replace", path=contract_path, value=mso.sent))
+ else:
+ ops.append(dict(op="add", path=contracts_path, value=mso.sent))
+
+ mso.existing = mso.proposed
+ mso.existing["relationshipType"] = contract.get("type")
+
+ if not module.check_mode and mso.proposed != mso.previous:
+ mso.request(schema_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_validate.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_validate.py
new file mode 100644
index 000000000..a4a4f6cd0
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_validate.py
@@ -0,0 +1,74 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_schema_validate
+short_description: Validate the schema before deploying it to site
+description:
+- This module is used to verify if a schema can be deployed to site without any error.
+- This module can only be used on versions of MSO that are 3.3 or greater.
+- Starting with MSO 3.3, the schema modules in this collection will skip some validation checks to allow part of the schema to be updated more easily.
+- This module will check those validation after all changes have been made.
+author:
+- Anvitha Jain (@anvitha-jain)
+version_added: "1.3.0"
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ state:
+ description:
+ - Use C(query) to validate deploying the schema.
+ type: str
+ default: query
+ choices: [ query ]
+seealso:
+- module: cisco.mso.mso_schema_template_external_epg
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ state=dict(type="str", default="query", choices=["query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ )
+
+ schema = module.params.get("schema")
+
+ mso = MSOModule(module)
+
+ mso.existing = mso.validate_schema(schema_id=mso.lookup_schema(schema))
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_service_node_type.py b/ansible_collections/cisco/mso/plugins/modules/mso_service_node_type.py
new file mode 100644
index 000000000..b8319256b
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_service_node_type.py
@@ -0,0 +1,162 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2022, Shreyas Srish (@shrsr) <ssrish@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_service_node_type
+short_description: Manage Service Node Types
+description:
+- Manage Service Node Types on Cisco ACI Multi-Site.
+author:
+- Shreyas Srish (@shrsr)
+options:
+ name:
+ description:
+ - The name of the node type.
+ type: str
+ display_name:
+ description:
+ - The name of the node type as displayed on the MSO web interface.
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new Service Node Type
+ cisco.mso.mso_schema_service_node:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ name: ips
+ display_name: ips
+ state: present
+ delegate_to: localhost
+
+- name: Remove a Service Node Type
+ cisco.mso.mso_schema_service_node:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ name: ips
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific Service Node Type
+ cisco.mso.mso_schema_service_node:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ name: ips
+ state: query
+ delegate_to: localhost
+
+- name: Query all Service Node Types
+ cisco.mso.mso_schema_service_node:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ name=dict(type="str"),
+ display_name=dict(type="str"),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["name"]],
+ ["state", "present", ["name"]],
+ ],
+ )
+
+ name = module.params.get("name")
+ display_name = module.params.get("display_name")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ mso.existing = {}
+ service_node_id = None
+
+ # Get service node id
+ query_node_data = mso.query_service_node_types()
+ service_nodes = [f.get("name") for f in query_node_data]
+ if name in service_nodes:
+ for node_data in query_node_data:
+ if node_data.get("name") == name:
+ service_node_id = node_data.get("id")
+ mso.existing = node_data
+
+ if state == "query":
+ if name is None:
+ mso.existing = query_node_data
+ if name is not None and service_node_id is None:
+ mso.fail_json(msg="Service Node Type '{service_node_type}' not found".format(service_node_type=name))
+ mso.exit_json()
+
+ service_nodes_path = "schemas/service-node-types"
+ service_node_path = "schemas/service-node-types/{0}".format(service_node_id)
+
+ mso.previous = mso.existing
+ if state == "absent":
+ if mso.existing:
+ if module.check_mode:
+ mso.existing = {}
+ else:
+ mso.existing = mso.request(service_node_path, method="DELETE")
+
+ elif state == "present":
+ if display_name is None:
+ display_name = name
+
+ payload = dict(
+ name=name,
+ displayName=display_name,
+ )
+ mso.sanitize(payload, collate=True)
+ if not module.check_mode:
+ if not mso.existing:
+ mso.request(service_nodes_path, method="POST", data=payload)
+ elif mso.existing.get("displayName") != display_name:
+ mso.fail_json(
+ msg="Service Node Type '{0}' already exists with display name '{1}' which is different from provided display name '{2}'.".format(
+ name, mso.existing.get("displayName"), display_name
+ )
+ )
+ mso.existing = mso.proposed
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_site.py b/ansible_collections/cisco/mso/plugins/modules/mso_site.py
new file mode 100644
index 000000000..a3778d28a
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_site.py
@@ -0,0 +1,290 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_site
+short_description: Manage sites
+description:
+- Manage sites on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ apic_password:
+ description:
+ - The password for the APICs.
+ - The apic_password attribute is not supported when using with ND platform.
+ - See the ND collection for complete site management.
+ type: str
+ apic_site_id:
+ description:
+ - The site ID of the APICs.
+ type: str
+ apic_username:
+ description:
+ - The username for the APICs.
+ - The apic_username attribute is not supported when using with ND platform.
+ - See the ND collection for complete site management.
+ type: str
+ default: admin
+ apic_login_domain:
+ description:
+ - The AAA login domain for the username for the APICs.
+ - The apic_login_domain attribute is not supported when using with ND platform.
+ - See the ND collection for complete site management.
+ type: str
+ site:
+ description:
+ - The name of the site.
+ type: str
+ aliases: [ name ]
+ labels:
+ description:
+ - The labels for this site.
+ - Labels that do not already exist will be automatically created.
+ - The labels attribute is not supported when using with ND platform.
+ - See the ND collection for complete site management.
+ type: list
+ elements: str
+ location:
+ description:
+ - Location of the site.
+ - The location attribute is not supported when using with ND platform.
+ - See the ND collection for complete site management.
+ type: dict
+ suboptions:
+ latitude:
+ description:
+ - The latitude of the location of the site.
+ type: float
+ longitude:
+ description:
+ - The longitude of the location of the site.
+ type: float
+ urls:
+ description:
+ - A list of URLs to reference the APICs.
+ - The urls attribute is not supported when using with ND platform.
+ - See the ND collection for complete site management.
+ type: list
+ elements: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new site
+ cisco.mso.mso_site:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ site: north_europe
+ description: North European Datacenter
+ apic_username: mso_admin
+ apic_password: AnotherSecretPassword
+ apic_site_id: 12
+ urls:
+ - 10.2.3.4
+ - 10.2.4.5
+ - 10.3.5.6
+ labels:
+ - NEDC
+ - Europe
+ - Diegem
+ location:
+ latitude: 50.887318
+ longitude: 4.447084
+ state: present
+ delegate_to: localhost
+
+- name: Remove a site
+ cisco.mso.mso_site:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ site: north_europe
+ state: absent
+ delegate_to: localhost
+
+- name: Query a site
+ cisco.mso.mso_site:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ site: north_europe
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all sites
+ cisco.mso.mso_site:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ location_arg_spec = dict(
+ latitude=dict(type="float"),
+ longitude=dict(type="float"),
+ )
+
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ apic_password=dict(type="str", no_log=True),
+ apic_site_id=dict(type="str"),
+ apic_username=dict(type="str", default="admin"),
+ apic_login_domain=dict(type="str"),
+ labels=dict(type="list", elements="str"),
+ location=dict(type="dict", options=location_arg_spec),
+ site=dict(type="str", aliases=["name"]),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ urls=dict(type="list", elements="str"),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["site"]],
+ ["state", "present", ["apic_site_id", "site"]],
+ ],
+ )
+
+ apic_username = module.params.get("apic_username")
+ apic_password = module.params.get("apic_password")
+ apic_site_id = module.params.get("apic_site_id")
+ site = module.params.get("site")
+ location = module.params.get("location")
+ if location is not None:
+ latitude = module.params.get("location")["latitude"]
+ longitude = module.params.get("location")["longitude"]
+ state = module.params.get("state")
+ urls = module.params.get("urls")
+ apic_login_domain = module.params.get("apic_login_domain")
+
+ mso = MSOModule(module)
+
+ site_id = None
+ path = "sites"
+ api_version = "v1"
+ if mso.platform == "nd":
+ api_version = "v2"
+
+ # Convert labels
+ labels = mso.lookup_labels(module.params.get("labels"), "site")
+
+ # Query for mso.existing object(s)
+ if site:
+ if mso.platform == "nd":
+ site_info = mso.get_obj(path, api_version=api_version, common=dict(name=site))
+ path = "sites/manage"
+ if site_info:
+ # If we found an existing object, continue with it
+ site_id = site_info.get("id")
+ if site_id is not None and site_id != "":
+ # Checking if site is managed by MSO
+ mso.existing = site_info
+ path = "sites/manage/{id}".format(id=site_id)
+ else:
+ mso.existing = mso.get_obj(path, name=site)
+ if mso.existing:
+ # If we found an existing object, continue with it
+ site_id = mso.existing.get("id")
+ path = "sites/{id}".format(id=site_id)
+
+ else:
+ mso.existing = mso.query_objs(path, api_version=api_version)
+
+ if state == "query":
+ pass
+
+ elif state == "absent":
+ mso.previous = mso.existing
+ if mso.existing:
+ if module.check_mode:
+ mso.existing = {}
+ else:
+ mso.request(path, method="DELETE", qs=dict(force="true"), api_version=api_version)
+ mso.existing = {}
+
+ elif state == "present":
+ mso.previous = mso.existing
+
+ if mso.platform == "nd":
+ if mso.existing:
+ payload = mso.existing
+ else:
+ if site_info:
+ payload = site_info
+ payload["common"]["siteId"] = apic_site_id
+ else:
+ mso.fail_json(msg="Site '{0}' is not a valid Site configured at ND-level. Add Site to ND first.".format(site))
+
+ else:
+ payload = dict(
+ apicSiteId=apic_site_id,
+ id=site_id,
+ name=site,
+ urls=urls,
+ labels=labels,
+ username=apic_username,
+ password=apic_password,
+ )
+
+ if location is not None:
+ payload["location"] = dict(
+ lat=latitude,
+ long=longitude,
+ )
+
+ if apic_login_domain is not None and apic_login_domain not in ["", "local", "Local"]:
+ payload["username"] = "apic#{0}\\{1}".format(apic_login_domain, apic_username)
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ if mso.check_changed():
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request(path, method="PUT", data=mso.sent, api_version=api_version)
+ else:
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request(path, method="POST", data=mso.sent, api_version=api_version)
+
+ if "password" in mso.existing:
+ mso.existing["password"] = "******"
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_tenant.py b/ansible_collections/cisco/mso/plugins/modules/mso_tenant.py
new file mode 100644
index 000000000..17aa457e3
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_tenant.py
@@ -0,0 +1,218 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_tenant
+short_description: Manage tenants
+description:
+- Manage tenants on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ tenant:
+ description:
+ - The name of the tenant.
+ type: str
+ aliases: [ name ]
+ display_name:
+ description:
+ - The name of the tenant to be displayed in the web UI.
+ type: str
+ description:
+ description:
+ - The description for this tenant.
+ type: str
+ users:
+ description:
+ - A list of associated users for this tenant.
+ - Using this property will replace any existing associated users.
+ - Admin user is always added to the associated user list irrespective of this parameter being used.
+ type: list
+ elements: str
+ sites:
+ description:
+ - A list of associated sites for this tenant.
+ - Using this property will replace any existing associated sites.
+ type: list
+ elements: str
+ orchestrator_only:
+ description:
+ - Orchestrator Only C(no) is used to delete the tenant from the MSO and Sites/APIC.
+ - C(yes) is used to remove the tenant only from the MSO.
+ type: str
+ choices: [ 'yes', 'no' ]
+ default: 'yes'
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new tenant
+ cisco.mso.mso_tenant:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ tenant: north_europe
+ display_name: North European Datacenter
+ description: This tenant manages the NEDC environment.
+ state: present
+ delegate_to: localhost
+
+- name: Remove a tenant from MSO and Site/APIC
+ cisco.mso.mso_tenant:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ tenant: north_europe
+ orchestrator_only: no
+ state: absent
+ delegate_to: localhost
+
+- name: Remove a tenant from MSO only
+ cisco.mso.mso_tenant:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ tenant: north_europe
+ orchestrator_only: yes
+ state: absent
+ delegate_to: localhost
+
+- name: Query a tenant
+ cisco.mso.mso_tenant:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ tenant: north_europe
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all tenants
+ cisco.mso.mso_tenant:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+from ansible_collections.cisco.mso.plugins.module_utils.constants import YES_OR_NO_TO_BOOL_STRING_MAP
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ description=dict(type="str"),
+ display_name=dict(type="str"),
+ tenant=dict(type="str", aliases=["name"]),
+ users=dict(type="list", elements="str"),
+ sites=dict(type="list", elements="str"),
+ orchestrator_only=dict(type="str", default="yes", choices=["yes", "no"]),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["tenant"]],
+ ["state", "present", ["tenant"]],
+ ],
+ )
+
+ description = module.params.get("description")
+ display_name = module.params.get("display_name")
+ tenant = module.params.get("tenant")
+ orchestrator_only = module.params.get("orchestrator_only")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ # Convert sites and users
+ sites = mso.lookup_sites(module.params.get("sites"))
+ users = mso.lookup_users(module.params.get("users"))
+
+ tenant_id = None
+ path = "tenants"
+
+ # Query for existing object(s)
+ if tenant:
+ mso.existing = mso.get_obj(path, name=tenant)
+ if mso.existing:
+ tenant_id = mso.existing.get("id")
+ # If we found an existing object, continue with it
+ path = "tenants/{id}".format(id=tenant_id)
+ else:
+ mso.existing = mso.query_objs(path)
+
+ if state == "query":
+ pass
+
+ elif state == "absent":
+ mso.previous = mso.existing
+ if mso.existing:
+ if module.check_mode:
+ mso.existing = {}
+ else:
+ path = "{0}?msc-only={1}".format(path, YES_OR_NO_TO_BOOL_STRING_MAP.get(orchestrator_only))
+ mso.existing = mso.request(path, method="DELETE")
+
+ elif state == "present":
+ mso.previous = mso.existing
+
+ payload = dict(
+ description=description,
+ id=tenant_id,
+ name=tenant,
+ displayName=display_name,
+ siteAssociations=sites,
+ userAssociations=users,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ # Ensure displayName is not undefined
+ if mso.sent.get("displayName") is None:
+ mso.sent["displayName"] = tenant
+
+ if mso.existing:
+ if mso.check_changed():
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request(path, method="PUT", data=mso.sent)
+ else:
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request(path, method="POST", data=mso.sent)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_tenant_site.py b/ansible_collections/cisco/mso/plugins/modules/mso_tenant_site.py
new file mode 100644
index 000000000..735f85b13
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_tenant_site.py
@@ -0,0 +1,387 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_tenant_site
+short_description: Manage tenants with cloud sites.
+description:
+- Manage tenants with cloud sites on Cisco ACI Multi-Site.
+author:
+- Shreyas Srish (@shrsr)
+options:
+ tenant:
+ description:
+ - The name of the tenant.
+ type: str
+ required: true
+ site:
+ description:
+ - The name of the site.
+ - This can either be cloud site or non-cloud site.
+ type: str
+ aliases: [ name ]
+ cloud_account:
+ description:
+ - Required for cloud site.
+ - Account id of AWS in the form '000000000000'.
+ - Account id of Azure in the form 'uni/tn-(tenant_name)/act-[(subscription_id)]-azure_vendor-azure'.
+ - Example values inside account id of Azure '(tenant_name)=tenant_test and (subscription_id)=10'.
+ type: str
+ security_domains:
+ description:
+ - List of security domains for sites.
+ type: list
+ elements: str
+ default: []
+ aws_account_org:
+ description:
+ - AWS account for organization.
+ default: false
+ type: bool
+ aws_trusted:
+ description:
+ - AWS account's access in trusted mode. Credentials are required, when set to false.
+ type: bool
+ aws_access_key:
+ description:
+ - AWS account's access key id. This is required when aws_trusted is set to false.
+ type: str
+ azure_access_type:
+ description:
+ - Managed mode for Azure.
+ - Unmanaged mode for Azure.
+ - Shared mode if the attribute is not specified.
+ choices: [ managed, unmanaged, shared ]
+ default: shared
+ type: str
+ azure_active_directory_id:
+ description:
+ - Azure account's active directory id.
+ - This attribute is required when azure_access_type is in unmanaged mode.
+ type: str
+ azure_active_directory_name:
+ description:
+ - Azure account's active directory name. Example being 'CiscoINSBUAd' as active directory name.
+ - This attribute is required when azure_access_type is in unmanaged mode.
+ type: str
+ azure_subscription_id:
+ description:
+ - Azure account's subscription id.
+ - This attribute is required when azure_access_type is either in managed mode or unmanaged mode.
+ type: str
+ azure_application_id:
+ description:
+ - Azure account's application id.
+ - This attribute is required when azure_access_type is either in managed mode or unmanaged mode.
+ type: str
+ azure_credential_name:
+ description:
+ - Azure account's credential name.
+ - This attribute is required when azure_access_type is in unmanaged mode.
+ type: str
+ secret_key:
+ description:
+ - secret key of AWS for untrusted account. Required when aws_trusted is set to false.
+ - secret key of Azure account for unmanaged identity. Required in unmanaged mode of Azure account.
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Associate a non-cloud site with a tenant
+ cisco.mso.mso_tenant_site:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ tenant: tenant_name
+ site: site_name
+ state: present
+ delegate_to: localhost
+
+- name: Associate AWS site with a tenant, with aws_trusted set to true
+ cisco.mso.mso_tenant_site:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ tenant: tenant_name
+ site: site_name
+ cloud_account: '000000000000'
+ aws_trusted: true
+ state: present
+ delegate_to: localhost
+
+- name: Associate AWS site with a tenant, with aws_trusted set to false
+ cisco.mso.mso_tenant_site:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ tenant: tenant_name
+ site: AWS
+ cloud_account: '000000000000'
+ aws_trusted: false
+ aws_access_key: '1'
+ secret_key: '0'
+ aws_account_org: false
+ state: present
+ delegate_to: localhost
+
+- name: Associate Azure site in managed mode
+ mso.cisco.mso_tenant_site:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ tenant: tenant_name
+ site: site_name
+ cloud_account: uni/tn-ansible_test/act-[9]-azure_vendor-azure
+ azure_access_type: managed
+ azure_subscription_id: '9'
+ azure_application_id: '100'
+ state: present
+ delegate_to: localhost
+
+- name: Associate Azure site in unmanaged mode
+ mso.cisco.mso_tenant_site:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ tenant: tenant_name
+ site: site_name
+ cloud_account: uni/tn-ansible_test/act-[9]-azure_vendor-azure
+ azure_access_type: unmanaged
+ azure_subscription_id: '9'
+ azure_application_id: '100'
+ azure_credential_name: cApicApp
+ secret_key: iins
+ azure_active_directory_id: '32'
+ azure_active_directory_name: CiscoINSBUAd
+ state: present
+ delegate_to: localhost
+
+- name: Dissociate a site
+ cisco.mso.mso_tenant_site:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ tenant: tenant_name
+ site: site_name
+ state: absent
+ delegate_to: localhost
+
+- name: Query a site
+ cisco.mso.mso_tenant_site:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ tenant: tenant_name
+ site: site_name
+ state: query
+ delegate_to: localhost
+
+- name: Query all sites of a tenant
+ cisco.mso.mso_tenant_site:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ tenant: tenant_name
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ tenant=dict(type="str", aliases=["name"], required=True),
+ site=dict(type="str", aliases=["name"]),
+ cloud_account=dict(type="str"),
+ security_domains=dict(type="list", elements="str", default=[]),
+ aws_trusted=dict(type="bool"),
+ azure_access_type=dict(type="str", default="shared", choices=["managed", "unmanaged", "shared"]),
+ azure_active_directory_id=dict(type="str"),
+ aws_access_key=dict(type="str", no_log=True),
+ aws_account_org=dict(type="bool", default="false"),
+ azure_active_directory_name=dict(type="str"),
+ azure_subscription_id=dict(type="str"),
+ azure_application_id=dict(type="str"),
+ azure_credential_name=dict(type="str"),
+ secret_key=dict(type="str", no_log=True),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["tenant", "site"]],
+ ["state", "present", ["tenant", "site"]],
+ ],
+ )
+
+ state = module.params.get("state")
+ security_domains = module.params.get("security_domains")
+ cloud_account = module.params.get("cloud_account")
+ azure_access_type = module.params.get("azure_access_type")
+ azure_credential_name = module.params.get("azure_credential_name")
+ azure_application_id = module.params.get("azure_application_id")
+ azure_active_directory_id = module.params.get("azure_active_directory_id")
+ azure_active_directory_name = module.params.get("azure_active_directory_name")
+ azure_subscription_id = module.params.get("azure_subscription_id")
+ secret_key = module.params.get("secret_key")
+ aws_account_org = module.params.get("aws_account_org")
+ aws_access_key = module.params.get("aws_access_key")
+ aws_trusted = module.params.get("aws_trusted")
+
+ mso = MSOModule(module)
+
+ # Get tenant_id and site_id
+ tenant_id = mso.lookup_tenant(module.params.get("tenant"))
+ site_id = mso.lookup_site(module.params.get("site"))
+ tenants = [(t.get("id")) for t in mso.query_objs("tenants")]
+ tenant_idx = tenants.index((tenant_id))
+
+ # set tenent and port paths
+ tenant_path = "tenants/{0}".format(tenant_id)
+ ops = []
+ ports_path = "/siteAssociations/-"
+ port_path = "/siteAssociations/{0}".format(site_id)
+
+ payload = dict(
+ siteId=site_id,
+ securityDomains=security_domains,
+ cloudAccount=cloud_account,
+ )
+
+ if cloud_account:
+ if "azure" in cloud_account:
+ azure_account = dict(
+ accessType=azure_access_type,
+ securityDomains=security_domains,
+ vendor="azure",
+ )
+
+ payload["azureAccount"] = [azure_account]
+
+ cloudSubscription = dict(
+ cloudSubscriptionId=azure_subscription_id,
+ cloudApplicationId=azure_application_id,
+ )
+
+ payload["azureAccount"][0]["cloudSubscription"] = cloudSubscription
+
+ if azure_access_type == "shared":
+ payload["azureAccount"] = []
+
+ if azure_access_type == "managed":
+ if not azure_subscription_id:
+ mso.fail_json(msg="azure_susbscription_id is required when in managed mode.")
+ if not azure_application_id:
+ mso.fail_json(msg="azure_application_id is required when in managed mode.")
+ payload["azureAccount"][0]["cloudApplication"] = []
+ payload["azureAccount"][0]["cloudActiveDirectory"] = []
+
+ if azure_access_type == "unmanaged":
+ if not azure_subscription_id:
+ mso.fail_json(msg="azure_subscription_id is required when in unmanaged mode.")
+ if not azure_application_id:
+ mso.fail_json(msg="azure_application_id is required when in unmanaged mode.")
+ if not secret_key:
+ mso.fail_json(msg="secret_key is required when in unmanaged mode.")
+ if not azure_active_directory_id:
+ mso.fail_json(msg="azure_active_directory_id is required when in unmanaged mode.")
+ if not azure_active_directory_name:
+ mso.fail_json(msg="azure_active_directory_name is required when in unmanaged mode.")
+ if not azure_credential_name:
+ mso.fail_json(msg="azure_credential_name is required when in unmanaged mode.")
+ azure_account.update(
+ accessType="credentials",
+ )
+ cloudApplication = dict(
+ cloudApplicationId=azure_application_id,
+ cloudCredentialName=azure_credential_name,
+ secretKey=secret_key,
+ cloudActiveDirectoryId=azure_active_directory_id,
+ )
+ cloudActiveDirectory = dict(cloudActiveDirectoryId=azure_active_directory_id, cloudActiveDirectoryName=azure_active_directory_name)
+ payload["azureAccount"][0]["cloudApplication"] = [cloudApplication]
+ payload["azureAccount"][0]["cloudActiveDirectory"] = [cloudActiveDirectory]
+
+ else:
+ aws_account = dict(
+ accountId=cloud_account,
+ isTrusted=aws_trusted,
+ accessKeyId=aws_access_key,
+ secretKey=secret_key,
+ isAccountInOrg=aws_account_org,
+ )
+
+ if not aws_trusted:
+ if not aws_access_key:
+ mso.fail_json(msg="aws_access_key is a required field in untrusted mode.")
+ if not secret_key:
+ mso.fail_json(msg="secret_key is a required field in untrusted mode.")
+ payload["awsAccount"] = [aws_account]
+
+ sites = [(s.get("siteId")) for s in mso.query_objs("tenants")[tenant_idx]["siteAssociations"]]
+
+ if site_id in sites:
+ site_idx = sites.index((site_id))
+ mso.existing = mso.query_objs("tenants")[tenant_idx]["siteAssociations"][site_idx]
+
+ if state == "query":
+ if len(sites) == 0:
+ mso.fail_json(msg="No site associated with tenant Id {0}".format(tenant_id))
+ elif site_id not in sites and site_id is not None:
+ mso.fail_json(msg="Site Id {0} not associated with tenant Id {1}".format(site_id, tenant_id))
+ elif site_id is None:
+ mso.existing = mso.query_objs("tenants")[tenant_idx]["siteAssociations"]
+ mso.exit_json()
+
+ mso.previous = mso.existing
+
+ if state == "absent":
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op="remove", path=port_path))
+ if state == "present":
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op="replace", path=port_path, value=mso.sent))
+ else:
+ ops.append(dict(op="add", path=ports_path, value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode and mso.proposed != mso.previous:
+ mso.request(tenant_path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_user.py b/ansible_collections/cisco/mso/plugins/modules/mso_user.py
new file mode 100644
index 000000000..37127a7f1
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_user.py
@@ -0,0 +1,283 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_user
+short_description: Manage users
+description:
+- Manage users on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ user:
+ description:
+ - The name of the user.
+ type: str
+ aliases: [ name ]
+ user_password:
+ description:
+ - The password of the user.
+ type: str
+ first_name:
+ description:
+ - The first name of the user.
+ - This parameter is required when creating new users.
+ type: str
+ last_name:
+ description:
+ - The last name of the user.
+ - This parameter is required when creating new users.
+ type: str
+ email:
+ description:
+ - The email address of the user.
+ - This parameter is required when creating new users.
+ type: str
+ phone:
+ description:
+ - The phone number of the user.
+ - This parameter is required when creating new users.
+ type: str
+ account_status:
+ description:
+ - The status of the user account.
+ type: str
+ choices: [ active, inactive ]
+ domain:
+ description:
+ - The domain this user belongs to.
+ - When creating new users, this defaults to C(Local).
+ type: str
+ roles:
+ description:
+ - The roles for this user and their access types (read or write).
+ - Access type defaults to C(write).
+ type: list
+ elements: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+notes:
+- A default installation of ACI Multi-Site ships with admin password 'we1come!' which requires a password change on first login.
+ See the examples of how to change the 'admin' password using Ansible.
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Update initial admin password
+ cisco.mso.mso_user:
+ host: mso_host
+ username: admin
+ password: initialPassword
+ validate_certs: false
+ user: admin
+ user_password: newPassword
+ state: present
+ delegate_to: localhost
+
+- name: Add a new user
+ cisco.mso.mso_user:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ validate_certs: false
+ user: dag
+ user_password: userPassword
+ first_name: Dag
+ last_name: Wieers
+ email: dag@wieers.com
+ phone: +32 478 436 299
+ roles:
+ - name: siteManager
+ access_type: write
+ - name: schemaManager
+ access_type: read
+ state: present
+ delegate_to: localhost
+
+- name: Add a new user
+ cisco.mso.mso_user:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ validate_certs: false
+ user: dag
+ first_name: Dag
+ last_name: Wieers
+ email: dag@wieers.com
+ phone: +32 478 436 299
+ roles:
+ - powerUser
+ delegate_to: localhost
+
+- name: Remove a user
+ cisco.mso.mso_user:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ validate_certs: false
+ user: dag
+ state: absent
+ delegate_to: localhost
+
+- name: Query a user
+ cisco.mso.mso_user:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ validate_certs: false
+ user: dag
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all users
+ cisco.mso.mso_user:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ validate_certs: false
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r""" # """
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, issubset
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ user=dict(type="str", aliases=["name"]),
+ user_password=dict(type="str", no_log=True),
+ first_name=dict(type="str"),
+ last_name=dict(type="str"),
+ email=dict(type="str"),
+ phone=dict(type="str"),
+ # TODO: What possible options do we have ?
+ account_status=dict(type="str", choices=["active", "inactive"]),
+ domain=dict(type="str"),
+ roles=dict(type="list", elements="str"),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["user"]],
+ ["state", "present", ["user"]],
+ ],
+ )
+
+ user_name = module.params.get("user")
+ user_password = module.params.get("user_password")
+ first_name = module.params.get("first_name")
+ last_name = module.params.get("last_name")
+ email = module.params.get("email")
+ phone = module.params.get("phone")
+ account_status = module.params.get("account_status")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ roles = mso.lookup_roles(module.params.get("roles"))
+ domain = mso.lookup_domain(module.params.get("domain"))
+
+ user_id = None
+ path = "users"
+
+ # Query for existing object(s)
+ if user_name:
+ if mso.module._socket_path and mso.connection.get_platform() == "cisco.nd":
+ mso.existing = mso.get_obj(path, loginID=user_name, api_version="v2")
+ if mso.existing:
+ mso.existing["id"] = mso.existing.get("userID")
+ mso.existing["username"] = mso.existing.get("loginID")
+ else:
+ mso.existing = mso.get_obj(path, username=user_name)
+ if mso.existing:
+ user_id = mso.existing.get("id")
+ # If we found an existing object, continue with it
+ path = "users/{id}".format(id=user_id)
+ else:
+ mso.existing = mso.query_objs(path)
+
+ if state == "query":
+ pass
+
+ elif state == "absent":
+ mso.previous = mso.existing
+ if mso.existing:
+ if module.check_mode:
+ mso.existing = {}
+ else:
+ mso.existing = mso.request(path, method="DELETE")
+
+ elif state == "present":
+ mso.previous = mso.existing
+
+ payload = dict(
+ id=user_id,
+ username=user_name,
+ firstName=first_name,
+ lastName=last_name,
+ emailAddress=email,
+ phoneNumber=phone,
+ accountStatus=account_status,
+ domainId=domain,
+ roles=roles,
+ # active=True,
+ # remote=True,
+ )
+
+ if user_password is not None:
+ payload.update(password=user_password)
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.sent.get("accountStatus") is None:
+ mso.sent["accountStatus"] = "active"
+
+ if mso.existing:
+ if not issubset(mso.sent, mso.existing):
+ # NOTE: Since MSO always returns '******' as password, we need to assume a change
+ if "password" in mso.proposed:
+ mso.module.warn("A password change is assumed, as the MSO REST API does not return passwords we do not know.")
+ mso.result["changed"] = True
+
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request(path, method="PUT", data=mso.sent)
+
+ else:
+ if user_password is None:
+ mso.fail_json("The user {0} does not exist. The 'user_password' attribute is required to create a new user.".format(user_name))
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request(path, method="POST", data=mso.sent)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_version.py b/ansible_collections/cisco/mso/plugins/modules/mso_version.py
new file mode 100644
index 000000000..19668afb4
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_version.py
@@ -0,0 +1,65 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: mso_version
+short_description: Get version of MSO
+description:
+- Retrieve the code version of Cisco Multi-Site Orchestrator.
+author:
+- Lionel Hercot (@lhercot)
+options:
+ state:
+ description:
+ - Use C(query) for retrieving the version object.
+ type: str
+ choices: [ query ]
+ default: query
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Get MSO version
+ cisco.mso.mso_version:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(state=dict(type="str", default="query", choices=["query"]))
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ mso = MSOModule(module)
+
+ path = "platform/version"
+
+ # Query for mso.existing object
+ mso.existing = mso.query_obj(path)
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/ndo_schema_template_deploy.py b/ansible_collections/cisco/mso/plugins/modules/ndo_schema_template_deploy.py
new file mode 100644
index 000000000..b8bdb63ae
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/ndo_schema_template_deploy.py
@@ -0,0 +1,153 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2022, Akini Ross (@akinross) <akinross@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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 = r"""
+---
+module: ndo_schema_template_deploy
+short_description: Deploy schema templates to sites for NDO v3.7 and higher
+description:
+- Deploy schema templates to sites.
+- Prior to deploy or redeploy a schema validation is executed.
+- When schema validation fails, M(cisco.mso.ndo_schema_template_deploy) fails and deploy or redeploy will not be executed.
+- Only supports NDO v3.7 and higher
+author:
+- Akini Ross (@akinross)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: true
+ sites:
+ description:
+ - The name of the site(s).
+ type: list
+ elements: str
+ state:
+ description:
+ - Use C(deploy) to deploy schema template.
+ - Use C(redeploy) to redeploy schema template.
+ - Use C(undeploy) to undeploy schema template from a site.
+ - Use C(query) to get deployment status.
+ type: str
+ choices: [ deploy, redeploy, undeploy, query ]
+ default: deploy
+seealso:
+- module: cisco.mso.mso_schema_site
+- module: cisco.mso.mso_schema_template
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Deploy a schema template
+ cisco.mso.ndo_schema_template_deploy:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ state: deploy
+ delegate_to: localhost
+
+- name: Redeploy a schema template
+ cisco.mso.ndo_schema_template_deploy:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ state: redeploy
+ delegate_to: localhost
+
+- name: Undeploy a schema template
+ cisco.mso.ndo_schema_template_deploy:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ sites: [ Site1, Site2 ]
+ state: undeploy
+ delegate_to: localhost
+
+- name: Query a schema template deploy status
+ cisco.mso.ndo_schema_template_deploy:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ state: query
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ sites=dict(type="list", elements="str"),
+ state=dict(type="str", default="deploy", choices=["deploy", "redeploy", "undeploy", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "undeploy", ["sites"]],
+ ],
+ )
+
+ schema = module.params.get("schema")
+ template = module.params.get("template").replace(" ", "")
+ sites = module.params.get("sites")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+ schema_id = mso.lookup_schema(schema)
+
+ if state == "query":
+ path = "status/schema/{0}/template/{1}".format(schema_id, template)
+ method = "GET"
+ payload = None
+ else:
+ path = "task"
+ method = "POST"
+ payload = dict(schemaId=schema_id, templateName=template)
+ if state == "deploy":
+ mso.validate_schema(schema_id)
+ payload.update(isRedeploy=False)
+ elif state == "redeploy":
+ mso.validate_schema(schema_id)
+ payload.update(isRedeploy=True)
+ elif state == "undeploy":
+ payload.update(undeploy=[site.get("siteId") for site in mso.lookup_sites(sites)])
+
+ if not module.check_mode:
+ mso.existing = mso.request(path, method=method, data=payload)
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/requirements.txt b/ansible_collections/cisco/mso/requirements.txt
new file mode 100644
index 000000000..976bdfe94
--- /dev/null
+++ b/ansible_collections/cisco/mso/requirements.txt
@@ -0,0 +1 @@
+requests_toolbelt
diff --git a/ansible_collections/cisco/mso/tests/.DS_Store b/ansible_collections/cisco/mso/tests/.DS_Store
new file mode 100644
index 000000000..8249f4cab
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/.DS_Store
Binary files differ
diff --git a/ansible_collections/cisco/mso/tests/.gitignore b/ansible_collections/cisco/mso/tests/.gitignore
new file mode 100644
index 000000000..ea1472ec1
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/.gitignore
@@ -0,0 +1 @@
+output/
diff --git a/ansible_collections/cisco/mso/tests/integration/inventory.networking b/ansible_collections/cisco/mso/tests/integration/inventory.networking
new file mode 100644
index 000000000..e39d51abb
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/inventory.networking
@@ -0,0 +1,36 @@
+[mso]
+lh-dmz1-pod1-mso-v224 ansible_host=173.36.219.29 ansible_connection=local mso_hostname=173.36.219.29
+lh-dmz1-pod1-mso-v311 ansible_host=173.36.219.11 ansible_connection=local mso_hostname=173.36.219.11
+
+[mso:children]
+ndo
+
+[ndo]
+lh-dmz1-pod1-ndo-v371 ansible_host=173.36.219.30 ansible_connection=ansible.netcommon.httpapi ansible_network_os=cisco.nd.nd mso_hostname=173.36.219.30
+lh-dmz1-pod1-ndo-v411 ansible_host=173.36.219.32 ansible_connection=ansible.netcommon.httpapi ansible_network_os=cisco.nd.nd mso_hostname=173.36.219.32
+
+[all:vars]
+mso_username=ansible_github_ci
+mso_password="sJ94G92#8dq2hx*K4qh"
+ansible_user=ansible_github_ci
+ansible_ssh_pass="sJ94G92#8dq2hx*K4qh"
+ansible_network_os=cisco.mso.mso
+ansible_httpapi_validate_certs=False
+ansible_httpapi_use_ssl=True
+apic_hostname=173.36.219.190
+apic_username=ansible_github_ci
+apic_password="sJ94G92#8dq2hx*K4qh"
+apic_site_id=101
+aws_apic_hostname=52.52.20.121
+aws_apic_username=ansible_github_ci
+aws_apic_password="sJ94G92#8dq2hx*K4qh"
+aws_site_id=20
+azure_apic_hostname=20.245.236.136
+azure_apic_username=ansible_github_ci
+azure_apic_password="sJ94G92#8dq2hx*K4qh"
+azure_site_id=10
+ansible_python_interpreter=/usr/bin/python3.9
+mso_remote_location=173.36.219.190
+mso_radius_server=173.36.219.128
+ansible_command_timeout=300
+ansible_connect_timeoout=300 \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/network-integration.requirements.txt b/ansible_collections/cisco/mso/tests/integration/network-integration.requirements.txt
new file mode 100644
index 000000000..1ecd96b24
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/network-integration.requirements.txt
@@ -0,0 +1 @@
+requests-toolbelt \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/target-prefixes.network b/ansible_collections/cisco/mso/tests/integration/target-prefixes.network
new file mode 100644
index 000000000..74f40ea75
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/target-prefixes.network
@@ -0,0 +1,2 @@
+mso
+ndo \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_backup/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_backup/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_backup/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_backup/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_backup/tasks/main.yml
new file mode 100644
index 000000000..9fe4d1a9a
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_backup/tasks/main.yml
@@ -0,0 +1,546 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com>
+# Copyright: (c) 2023, Sabari Jaganathan (@sajagana) <sajagana@cisco.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Clear tenant
+ mso_tenant: &tenant1_absent
+ <<: *mso_info
+ tenant: Tenant1
+ display_name: Test_Tenant
+ state: absent
+
+- name: Ensure remote location is absent
+ cisco.mso.mso_remote_location: &remote_location_absent
+ <<: *mso_info
+ remote_location: ansible_test
+ remote_protocol: scp
+ remote_host: '{{ mso_remote_location }}'
+ remote_path: '{{ mso_remote_location_path | default("/tmp") }}'
+ authentication_type: password
+ remote_username: '{{ mso_remote_location_user | default(mso_username) }}'
+ remote_password: '{{ mso_remote_location_password | default(mso_password) }}'
+ state: absent
+ ignore_errors: true
+
+- name: Ensure remote location is present
+ cisco.mso.mso_remote_location:
+ <<: *remote_location_absent
+ state: present
+
+- name: Query all backups
+ mso_backup: &query_all_backups
+ <<: *mso_info
+ state: query
+ register: query_ansibleBackup_for_delete
+
+- name: Remove all backups
+ mso_backup:
+ <<: *mso_info
+ backup_id: '{{ item.id }}'
+ state: absent
+ loop: '{{ query_ansibleBackup_for_delete.current | list | sort(attribute="name", reverse=True) }}'
+
+# Create local backups - Only for version < 3.2
+- name: Execute tasks only for MSO version < 3.2
+ when: version.current.version is version('3.2', '<')
+ block:
+ - name: Create local ansibleBackup1 in check mode
+ mso_backup:
+ <<: *mso_info
+ backup: ansibleBackup1
+ description: via Ansible
+ location_type: local
+ state: present
+ register: cm_add_ansibleBackup1
+ check_mode: true
+
+ - name: Verify local cm_add_ansibleBackup1
+ assert:
+ that:
+ - cm_add_ansibleBackup1 is changed
+
+ - name: Create local ansibleBackup1 in normal mode
+ mso_backup:
+ <<: *mso_info
+ backup: ansibleBackup1
+ description: via Ansible
+ location_type: local
+ state: present
+ register: nm_add_ansibleBackup1
+
+ - name: Query ansibleBackup1 to check if it exists
+ mso_backup:
+ <<: *mso_info
+ backup: ansibleBackup1
+ state: query
+ register: check_ansibleBackup1
+ until:
+ - check_ansibleBackup1.current is defined
+ - check_ansibleBackup1.current[0] is defined
+ - check_ansibleBackup1.current[0].status.statusType == "success"
+ retries: 50
+ delay: 10
+
+ - name: Verify nm_add_ansibleBackup1
+ assert:
+ that:
+ - nm_add_ansibleBackup1 is changed
+ - check_ansibleBackup1.current[0].backupEntry.metadata.name is match("ansibleBackup1_[0-9a-zA-Z]*")
+
+ - name: Create local ansibleBackup3 in normal mode
+ mso_backup:
+ <<: *mso_info
+ backup: ansibleBackup3
+ description: via Ansible
+ location_type: local
+ state: present
+ register: nm_add_ansibleBackup3
+
+ - name: Query ansibleBackup3 to check if it exists
+ mso_backup:
+ <<: *mso_info
+ backup: ansibleBackup3
+ state: query
+ register: query_ansibleBackup3
+ until:
+ - query_ansibleBackup3.current is defined
+ - query_ansibleBackup3.current[0] is defined
+ - query_ansibleBackup3.current[0].status.statusType == "success"
+ - query_ansibleBackup3.current[0].backupEntry.metadata.name is match ("ansibleBackup3_[0-9a-zA-Z]*")
+ retries: 50
+ delay: 10
+
+ - name: Assertions check for create local ansibleBackup3 in normal mode
+ assert:
+ that:
+ - query_ansibleBackup3 is not changed
+ - query_ansibleBackup3.current.0.status.statusType == "success"
+ - query_ansibleBackup3.current.0.backupEntry.metadata.name is match ("ansibleBackup3_[0-9a-zA-Z]*")
+ - query_ansibleBackup3.current.0.location.locationType == "local"
+
+ # Creating duplicate backups with the name of ansibleBackup3 - to validate "Multiple backups with same name found" error message
+ - name: Create local ansibleBackup3 in normal mode again
+ mso_backup:
+ <<: *mso_info
+ backup: ansibleBackup3
+ description: via Ansible
+ location_type: local
+ state: present
+ register: nm_add_ansibleBackup3_again
+
+ - name: Query duplicate ansibleBackup3 to check if it exists
+ mso_backup:
+ <<: *mso_info
+ backup: ansibleBackup3
+ state: query
+ register: query_ansibleBackup3_again
+ until:
+ - query_ansibleBackup3_again.current is defined
+ - query_ansibleBackup3_again.current[1] is defined
+ - query_ansibleBackup3_again.current[1].status.statusType == "success"
+ - query_ansibleBackup3_again.current[1].backupEntry.metadata.name is match ("ansibleBackup3_[0-9a-zA-Z]*")
+ retries: 50
+ delay: 10
+
+ - name: Assertions check for create local ansibleBackup3 in normal mode again
+ assert:
+ that:
+ - query_ansibleBackup3_again is not changed
+ - query_ansibleBackup3_again.current.1.status.statusType == "success"
+ - query_ansibleBackup3_again.current.1.backupEntry.metadata.name is match ("ansibleBackup3_[0-9a-zA-Z]*")
+ - query_ansibleBackup3_again.current.1.location.locationType == "local"
+
+# Move Local backup to Remote - Only for version < 3.2
+- name: Execute tasks only for MSO version < 3.2
+ when: version.current.version is version('3.2', '<')
+ block:
+ - name: Move ansibleBackup1 from local to remote location in check mode
+ mso_backup: &move_ab1_cm
+ <<: *mso_info
+ remote_location: ansible_test
+ remote_path: ""
+ backup: ansibleBackup1
+ description: Local to Remote via Ansible
+ state: move
+ check_mode: true
+ register: move_ab1_cm
+
+ - name: Move ansibleBackup1 from local to remote location in normal mode
+ mso_backup:
+ <<: *move_ab1_cm
+ register: move_ab1_nm
+
+ - name: Move a non existent backup from local location to remote
+ mso_backup:
+ <<: *mso_info
+ backup: non_existent_backup
+ remote_location: ansible_test
+ remote_path: ""
+ state: move
+ register: move_non_existent_backup
+ ignore_errors: true
+
+ - name: Move a ansibleBackup3 from local location to remote - check mode
+ mso_backup:
+ <<: *mso_info
+ backup: ansibleBackup3
+ remote_location: ansible_test
+ remote_path: ""
+ state: move
+ register: move_backup_cm_ab3
+ ignore_errors: true
+
+ - name: Move a ansibleBackup3 from local location to remote - normal mode
+ mso_backup:
+ <<: *mso_info
+ backup: ansibleBackup3
+ remote_location: ansible_test
+ remote_path: ""
+ state: move
+ register: move_backup_nm_ab3
+ ignore_errors: true
+
+ - name: Assertions check for move a backup from local location to remote location
+ assert:
+ that:
+ - move_ab1_cm is changed
+ - move_ab1_nm is changed
+ - move_non_existent_backup.msg is match ("Backup 'non_existent_backup' does not exist")
+ - move_backup_cm_ab3.msg is match ("Multiple backups with same name found. Existing backups with similar names{{':'}} ansibleBackup3_[0-9]*, ansibleBackup3_[0-9]*")
+ - move_backup_nm_ab3.msg is match ("Multiple backups with same name found. Existing backups with similar names{{':'}} ansibleBackup3_[0-9]*, ansibleBackup3_[0-9]*")
+
+# Create Remote backup - For all version
+- name: Create ansibleBackupRemote1 in check mode
+ mso_backup:
+ <<: *mso_info
+ backup: ansibleBackupRemote1
+ description: Remote via Ansible
+ location_type: remote
+ remote_location: ansible_test
+ remote_path: "tmp"
+ state: present
+ check_mode: true
+ register: cm_add_ansibleBackupRemote1
+
+- name: Create ansibleBackupRemote1 in normal mode
+ mso_backup:
+ <<: *mso_info
+ backup: ansibleBackupRemote1
+ description: Remote via Ansible
+ location_type: remote
+ remote_location: ansible_test
+ remote_path: "tmp"
+ state: present
+ register: nm_add_ansibleBackupRemote1
+
+- name: Query ansibleBackupRemote1 to check if it exists
+ mso_backup:
+ <<: *mso_info
+ backup: ansibleBackupRemote1
+ state: query
+ register: query_ansibleBackupRemote1
+ until:
+ - query_ansibleBackupRemote1.current is defined
+ - query_ansibleBackupRemote1.current[0] is defined
+ - query_ansibleBackupRemote1.current[0].status.statusType == "success"
+ retries: 50
+ delay: 10
+
+- name: Assertions check for the backups present on the remote location
+ assert:
+ that:
+ - cm_add_ansibleBackupRemote1 is changed
+ - nm_add_ansibleBackupRemote1 is changed
+ - query_ansibleBackupRemote1.current.0.backupEntry.metadata.name is match ("ansibleBackupRemote1_[0-9a-zA-Z]*")
+
+# Remove additional backups - to handle "Multiple backups with same name" during the download
+- name: Query all backups to handle "Multiple backups with same name" during the download
+ mso_backup:
+ <<: *mso_info
+ state: query
+ register: query_ansibleBackup_for_delete
+
+- name: Delete all backups except for one to handle "Multiple backups with same name" during the download
+ mso_backup:
+ <<: *mso_info
+ backup_id: '{{ item.id }}'
+ state: absent
+ loop: '{{ query_ansibleBackup_for_delete.current[0:-1] | list | sort(attribute="name", reverse=True) }}'
+ when: query_ansibleBackup_for_delete.current | length >= 2
+
+# Download Backup
+- name: Create a directory if it does not exist
+ ansible.builtin.file:
+ path: './{{mso_hostname}}'
+ state: directory
+ mode: '0755'
+
+- name: Download non existent backup
+ mso_backup:
+ <<: *mso_info
+ backup: non_existent_backup
+ destination: './{{mso_hostname}}'
+ state: download
+ ignore_errors: true
+ register: download_non_existent_backup
+
+- name: Download ansibleBackupRemote1 in check mode
+ mso_backup: &download_ab1_cm
+ <<: *mso_info
+ backup: ansibleBackupRemote1
+ destination: './{{mso_hostname}}'
+ state: download
+ check_mode: true
+ register: download_ab1_cm
+
+- name: Download ansibleBackupRemote1 in normal mode
+ mso_backup:
+ <<: *download_ab1_cm
+ register: download_ab1_nm
+
+- name: Assertions check for download a backup form MSO/NDO
+ assert:
+ that:
+ - download_ab1_cm is changed
+ - download_ab1_nm is changed
+ - download_non_existent_backup.msg is match ("Backup 'non_existent_backup' does not exist")
+
+# Find Backup
+- name: Find backup
+ find:
+ paths: './{{mso_hostname}}'
+ patterns: "*.tar.gz"
+ register: backup_match
+
+# Upload a backup from local machine to local location only for MSO - version < 3.2
+- name: Execute tasks only for MSO version < 3.2
+ when: version.current.version is version('3.2', '<')
+ block:
+ - name: Upload a backup from local machine to local location in check mode
+ mso_backup: &upload_backup_to_local_cm
+ <<: *mso_info
+ backup: "{{ backup_match.files[0].path }}"
+ state: upload
+ register: upload_backup_to_local_cm
+ check_mode: true
+
+ - name: Upload a backup from local machine to local location in normal mode
+ mso_backup:
+ <<: *upload_backup_to_local_cm
+ register: upload_backup_to_local_nm
+
+ - name: Upload a non existent backup from local machine to local location
+ mso_backup:
+ <<: *mso_info
+ backup: non_existent_backup
+ state: upload
+ register: upload_non_existent_backup
+ ignore_errors: true
+
+ - name: Assertions check for the upload a backup form local machine to local location
+ assert:
+ that:
+ - upload_backup_to_local_cm is changed
+ - upload_backup_to_local_nm is changed
+ - "{{ 'Upload failed due to' in upload_non_existent_backup.msg }}"
+
+# Upload backup to remote location for all platforms
+# To Support NDO Backup upload
+- name: Check present working directory with Shell command
+ shell:
+ "pwd"
+ register: present_working_directory
+
+- name: Upload an existing backup from local machine to remote location - check mode
+ mso_backup: &upload_backup_cm
+ <<: *mso_info
+ backup: "{{present_working_directory.stdout}}/{{ backup_match.files[0].path }}"
+ location_type: remote
+ remote_location: ansible_test
+ remote_path: "tmp"
+ state: upload
+ check_mode: true
+ register: upload_backup_cm
+
+- name: Upload an existing backup from local machine to remote location - normal mode
+ mso_backup:
+ <<: *upload_backup_cm
+ register: upload_backup_nm
+ ignore_errors: true
+
+# Block to handle "Backup already existing issue"
+- name: Upload backup from local machine to remote again
+ when:
+ - upload_backup_nm is not changed
+ - upload_backup_nm.current == []
+ block:
+ - name: Query all backups - clear existing backups before uploading - to handle "Backup already existing issue"
+ mso_backup:
+ <<: *mso_info
+ state: query
+ register: query_ansibleBackup_for_delete
+
+ - name: Delete all backups from MSO/NDO before uploading the backup form local machine
+ mso_backup:
+ <<: *mso_info
+ backup_id: '{{ item.id }}'
+ state: absent
+ loop: '{{ query_ansibleBackup_for_delete.current | list | sort(attribute="name", reverse=True) }}'
+
+ - name: Upload an existing backup from local machine to remote location - normal mode - after removing existing backups
+ mso_backup:
+ <<: *upload_backup_cm
+ register: upload_backup_nm
+
+- name: Upload a non existent backup from local location
+ mso_backup:
+ <<: *mso_info
+ backup: non_existent_backup
+ location_type: remote
+ remote_location: ansible_test
+ remote_path: "tmp"
+ state: upload
+ register: upload_non_existent_backup
+ ignore_errors: true
+
+- name: Assertions check for the upload a backup form local machine to remote location
+ assert:
+ that:
+ - upload_non_existent_backup is not changed
+ - "{{ 'Upload failed due to' in upload_non_existent_backup.msg }}"
+ - upload_backup_cm is changed
+ - upload_backup_cm.current == {}
+ - upload_backup_nm is changed
+ - upload_backup_nm.current != {}
+
+- name: Set Uploaded Backup Name for MSO
+ when:
+ - version.current.version is version('2.2.4e', '!=')
+ - version.current.version is version('3.2', '<')
+ set_fact:
+ uploaded_backup_name: '{{ upload_backup_nm.current.backupEntry.metadata.name.split("_") | first }}'
+
+- name: Set Uploaded Backup Name for NDO
+ when:
+ - version.current.version is version('2.2.4e', '!=')
+ - version.current.version is version('3.2', '>=')
+ set_fact:
+ uploaded_backup_name: '{{ upload_backup_nm.current.body.backupEntry.metadata.name.split("_") | first }}'
+
+- name: Restore backup check
+ when: version.current.version is version('2.2.4e', '!=')
+ block:
+ - name: Restore non existent backup
+ mso_backup:
+ <<: *mso_info
+ backup: non_existent_backup
+ state: restore
+ timeout: 1000
+ ignore_errors: true
+ register: restore_non_existent_backup
+
+ - name: Add a new tenant
+ mso_tenant:
+ <<: *tenant1_absent
+ state: present
+ register: tenant1_present
+
+ - name: Restore {{ uploaded_backup_name }} in check mode
+ mso_backup: &restore_backup_cm
+ <<: *mso_info
+ backup: '{{ uploaded_backup_name }}'
+ state: restore
+ timeout: 1000
+ check_mode: true
+ register: restore_backup_cm
+
+ - name: Restore {{ uploaded_backup_name }} in normal mode
+ mso_backup:
+ <<: *restore_backup_cm
+ register: restore_backup_nm
+
+ - name: Query Tenant1 after restoring the backup
+ mso_tenant:
+ <<: *tenant1_absent
+ state: query
+ register: query_tenant1
+
+ - name: Restore {{ uploaded_backup_name }} in normal mode - idempotency check
+ mso_backup:
+ <<: *restore_backup_cm
+ register: idm_restore_backup_nm
+
+ - name: Assertions check for the backup restore
+ assert:
+ that:
+ - restore_non_existent_backup.msg is match ("Backup 'non_existent_backup' does not exist")
+ - restore_backup_cm is changed
+ - restore_backup_nm is changed
+ - idm_restore_backup_nm is changed
+ - tenant1_present.current != {}
+ - query_tenant1 is not changed
+ - query_tenant1.current == {}
+
+# Cleaning part
+# Remove all other backups
+- name: Query all backups before deleting
+ mso_backup:
+ <<: *query_all_backups
+ state: query
+ register: query_all_backups_bf_delete
+
+- name: Ensure backups does not exists
+ mso_backup:
+ <<: *mso_info
+ backup_id: '{{ item.id }}'
+ state: absent
+ loop: '{{ query_all_backups_bf_delete.current | list | sort(attribute="name", reverse=True) }}'
+
+- name: Query all backups after deleting - to ensure all are absent
+ mso_backup:
+ <<: *query_all_backups
+ register: query_all_backups_af_delete
+
+- name: Assertions check for the query and remove all backups
+ assert:
+ that:
+ - query_all_backups_bf_delete is not changed
+ - query_all_backups_af_delete is not changed
+ - query_all_backups_bf_delete.current | selectattr("name", "match", "^ansibleBackup.*") | list | length != 0
+ - query_all_backups_af_delete.current | selectattr("name", "match", "^ansibleBackup.*") | list | length == 0
+
+# Clear Remote Location
+- name: Ensure remote location is absent
+ cisco.mso.mso_remote_location:
+ <<: *remote_location_absent
+
+# Remove Tenant1
+- name: Ensure Tenant1 is absent
+ mso_tenant:
+ <<: *tenant1_absent
+ state: absent
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_backup_schedule/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_backup_schedule/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_backup_schedule/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_backup_schedule/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_backup_schedule/tasks/main.yml
new file mode 100644
index 000000000..1a50284f8
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_backup_schedule/tasks/main.yml
@@ -0,0 +1,266 @@
+# Test code for the MSO modules
+# Copyright: (c) 2022, Akini Ross (@akinross) <akinross@cisco.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+# CLEAN ENVIRONMENT
+
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Delete existing backup schedule
+ cisco.mso.mso_backup_schedule:
+ <<: *mso_info
+ state: absent
+ delegate_to: localhost
+
+- name: Ensure remote location is present
+ cisco.mso.mso_remote_location:
+ <<: *mso_info
+ remote_location: ansible_test
+ remote_protocol: scp
+ remote_host: '{{ mso_remote_location }}'
+ remote_path: '{{ mso_remote_location_path | default("/tmp") }}'
+ authentication_type: password
+ remote_username: '{{ mso_remote_location_user | default(mso_username) }}'
+ remote_password: '{{ mso_remote_location_password | default(mso_password) }}'
+ state: present
+
+# TESTS
+
+- name: Get empty backup schedule
+ cisco.mso.mso_backup_schedule:
+ <<: *mso_info
+ state: query
+ delegate_to: localhost
+ register: no_schedule
+
+- name: Verify empty backup schedule
+ assert:
+ that:
+ - no_schedule is not changed
+ - no_schedule.current == {}
+
+- name: Set backup schedule (check mode)
+ cisco.mso.mso_backup_schedule:
+ <<: *mso_info
+ frequency_unit: hours
+ frequency_length: 7
+ remote_location: ansible_test
+ state: present
+ check_mode: true
+ delegate_to: localhost
+ register: cm_schedule_create
+
+- name: Set backup schedule (normal mode)
+ cisco.mso.mso_backup_schedule:
+ <<: *mso_info
+ frequency_unit: hours
+ frequency_length: 7
+ remote_location: ansible_test
+ state: present
+ delegate_to: localhost
+ register: nm_schedule_create
+
+# add date tests
+- name: Verify success set backup schedule
+ assert:
+ that:
+ - cm_schedule_create is changed
+ - cm_schedule_create.current.intervalLength == 7
+ - cm_schedule_create.current.intervalTimeUnit == "HOURS"
+ - cm_schedule_create.current.locationType == "remote"
+ - nm_schedule_create is changed
+ - nm_schedule_create.current.timeInterval.length == 7
+ - nm_schedule_create.current.timeInterval.unit == "HOURS"
+ - nm_schedule_create.current.callbackWSRequest.methodBody.locationType == "remote"
+ - "'00:00:00' in nm_schedule_create.current.firstScheduledAt"
+
+- name: Adjust backup schedule (check mode)
+ cisco.mso.mso_backup_schedule:
+ <<: *mso_info
+ frequency_unit: days
+ frequency_length: 1
+ remote_location: ansible_test
+ state: present
+ check_mode: true
+ delegate_to: localhost
+ register: cm_schedule_adjust
+
+- name: Adjust backup schedule (normal mode)
+ cisco.mso.mso_backup_schedule:
+ <<: *mso_info
+ frequency_unit: days
+ frequency_length: 1
+ remote_location: ansible_test
+ state: present
+ delegate_to: localhost
+ register: nm_schedule_adjust
+
+- name: Verify success set backup schedule
+ assert:
+ that:
+ - cm_schedule_adjust is changed
+ - cm_schedule_adjust.current.intervalLength == 1
+ - cm_schedule_adjust.current.intervalTimeUnit == "DAYS"
+ - cm_schedule_adjust.current.locationType == "remote"
+ - nm_schedule_adjust is changed
+ - nm_schedule_adjust.current.timeInterval.length == 1
+ - nm_schedule_adjust.current.timeInterval.unit == "DAYS"
+ - nm_schedule_adjust.current.callbackWSRequest.methodBody.locationType == "remote"
+
+- name: Get backup schedule
+ cisco.mso.mso_backup_schedule:
+ <<: *mso_info
+ state: query
+ delegate_to: localhost
+ register: schedule
+
+- name: Verify success get backup schedule
+ assert:
+ that:
+ - schedule is not changed
+ - nm_schedule_adjust.current.timeInterval.length == 1
+ - nm_schedule_adjust.current.timeInterval.unit == "DAYS"
+ - nm_schedule_adjust.current.callbackWSRequest.methodBody.locationType == "remote"
+
+- name: Delete backup schedule (check mode)
+ cisco.mso.mso_backup_schedule:
+ <<: *mso_info
+ state: absent
+ check_mode: true
+ delegate_to: localhost
+
+- name: Delete backup schedule (normal mode)
+ cisco.mso.mso_backup_schedule:
+ <<: *mso_info
+ state: absent
+ delegate_to: localhost
+
+- name: Get empty backup schedule
+ cisco.mso.mso_backup_schedule:
+ <<: *mso_info
+ state: query
+ delegate_to: localhost
+ register: no_schedule
+
+- name: Verify empty backup schedule
+ assert:
+ that:
+ - no_schedule is not changed
+ - no_schedule.current == {}
+
+- name: Set backup schedule incorrect time to parse (normal mode)
+ cisco.mso.mso_backup_schedule:
+ <<: *mso_info
+ start_time: no_time
+ frequency_unit: hours
+ frequency_length: 7
+ remote_location: ansible_test
+ state: present
+ delegate_to: localhost
+ register: nm_schedule_incorrect_time
+ ignore_errors: true
+
+- name: Set backup schedule incorrect date to parse (normal mode)
+ cisco.mso.mso_backup_schedule:
+ <<: *mso_info
+ start_date: no_date
+ frequency_unit: hours
+ frequency_length: 7
+ remote_location: ansible_test
+ state: present
+ delegate_to: localhost
+ register: nm_schedule_incorrect_date
+ ignore_errors: true
+
+- name: Set backup schedule incorrect date object create from start_date (normal mode)
+ cisco.mso.mso_backup_schedule:
+ <<: *mso_info
+ start_date: "2030-15-45"
+ frequency_unit: hours
+ frequency_length: 7
+ remote_location: ansible_test
+ state: present
+ delegate_to: localhost
+ register: nm_schedule_incorrect_date_from_start_date
+ ignore_errors: true
+
+- name: Set backup schedule incorrect date object create from start_time (normal mode)
+ cisco.mso.mso_backup_schedule:
+ <<: *mso_info
+ start_time: "2030:15:45"
+ frequency_unit: hours
+ frequency_length: 7
+ remote_location: ansible_test
+ state: present
+ delegate_to: localhost
+ register: nm_schedule_incorrect_time_from_start_time
+ ignore_errors: true
+
+- name: Verify error messages
+ assert:
+ that:
+ - nm_schedule_incorrect_time is failed
+ - nm_schedule_incorrect_time.msg.startswith("Failed to parse time format")
+ - nm_schedule_incorrect_date is failed
+ - nm_schedule_incorrect_date.msg.startswith("Failed to parse date format")
+ - nm_schedule_incorrect_date_from_start_date is failed
+ - nm_schedule_incorrect_date_from_start_date.msg.startswith("Failed to create datetime object")
+ - nm_schedule_incorrect_time_from_start_time is failed
+ - nm_schedule_incorrect_time_from_start_time.msg.startswith("Failed to create datetime object")
+
+- name: Set backup schedule full (normal mode)
+ cisco.mso.mso_backup_schedule:
+ <<: *mso_info
+ start_date: "2030-11-12"
+ start_time: "00:00:01"
+ frequency_unit: days
+ frequency_length: 1
+ remote_location: ansible_test
+ remote_path: remote_add
+ state: present
+ delegate_to: localhost
+ register: nm_schedule_full
+
+- name: Verify success set backup schedule
+ assert:
+ that:
+ - nm_schedule_full is changed
+ - nm_schedule_full.current.timeInterval.length == 1
+ - nm_schedule_full.current.timeInterval.unit == "DAYS"
+ - nm_schedule_full.current.callbackWSRequest.methodBody.locationType == "remote"
+ - nm_schedule_full.current.firstScheduledAt == "2030-11-12T00:00:01.000Z"
+ - nm_schedule_full.current.nextScheduleAt == "2030-11-13T00:00:01.000Z"
+
+- name: Delete backup schedule (normal mode)
+ cisco.mso.mso_backup_schedule:
+ <<: *mso_info
+ state: absent
+ delegate_to: localhost
+
+- name: Get empty backup schedule
+ cisco.mso.mso_backup_schedule:
+ <<: *mso_info
+ state: query
+ delegate_to: localhost
+ register: no_schedule
+
+- name: Verify empty backup schedule
+ assert:
+ that:
+ - no_schedule is not changed
+ - no_schedule.current == {} \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_option_policy/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_option_policy/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_option_policy/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_option_policy/tasks/main.yaml b/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_option_policy/tasks/main.yaml
new file mode 100644
index 000000000..b24749eb6
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_option_policy/tasks/main.yaml
@@ -0,0 +1,298 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+# Copyright: (c) 2020, Jorge Gomez (@jgomezve) <jgomezve@cisco.com> (based on mso_dhcp_relay_policy test case)
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+#CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Execute tasks only for MSO version < 4.0
+ when: version.current.version is version('4.0', '<')
+ block:
+ - name: Ensure tenant ansible_test exist
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ state: present
+ register: ansible_tenant
+
+ - name: Stop consuming DHCP Policy CLIENT_BD
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ bd: CLIENT_BD
+ vrf:
+ name: VRF1
+ state: absent
+ ignore_errors: true
+
+ - name: Stop consuming DHCP Policy ansible_test_2
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ bd: ansible_test_2
+ state: absent
+ ignore_errors: true
+
+ - name: Stop consuming DHCP Policy ansible_test_multiple_dhcp
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ bd: ansible_test_multiple_dhcp
+ state: absent
+ ignore_errors: true
+
+ - name: Stop consuming DHCP Policy ansible_test_5
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test_2") }}'
+ template: Template 5
+ bd: ansible_test_5
+ state: absent
+ ignore_errors: true
+
+ - name: Remove DHCP Option Policies
+ mso_dhcp_option_policy:
+ <<: *mso_info
+ dhcp_option_policy: '{{ item }}'
+ state: absent
+ loop:
+ - ansible_dhcp_option_1
+ - ansible_dhcp_option_2
+ - ansible_test_dhcp_policy_option1
+ - ansible_test_dhcp_policy_option2
+ - ansible_test_dhcp_policy_option3
+
+ # ADD DHCP Policy
+ - name: Add a new DHCP Option Policy 1 (check mode)
+ mso_dhcp_option_policy: &create_dhcp
+ <<: *mso_info
+ dhcp_option_policy: ansible_dhcp_option_1
+ description: "My Test DHCP Policy 1"
+ tenant: ansible_test
+ state: present
+ check_mode: true
+ register: dhcp_pol1_cm
+
+ - name: Add a new DHCP Option Policy 1 (normal mode)
+ mso_dhcp_option_policy:
+ <<: *create_dhcp
+ register: dhcp_pol1_nm
+
+ - name: Verify dhcp_pol1_cm and dhcp_pol1_nm
+ assert:
+ that:
+ - dhcp_pol1_cm is changed
+ - dhcp_pol1_nm is changed
+ - dhcp_pol1_cm.current.name == dhcp_pol1_nm.current.name == 'ansible_dhcp_option_1'
+ - dhcp_pol1_cm.current.desc == dhcp_pol1_nm.current.desc == 'My Test DHCP Policy 1'
+ - dhcp_pol1_cm.current.policySubtype == dhcp_pol1_nm.current.policySubtype == 'option'
+ - dhcp_pol1_cm.current.policyType == dhcp_pol1_nm.current.policyType == 'dhcp'
+ - dhcp_pol1_cm.current.tenantId == dhcp_pol1_nm.current.tenantId == ansible_tenant.current.id
+
+ - name: Add a new DHCP Option Policy 1 again (check mode)
+ mso_dhcp_option_policy:
+ <<: *create_dhcp
+ check_mode: true
+ register: dhcp_pol1_again_cm
+
+ - name: Add a new DHCP Option Policy 1 again (normal mode)
+ mso_dhcp_option_policy:
+ <<: *create_dhcp
+ register: dhcp_pol1_again_nm
+
+ - name: Verify dhcp_pol1_again_cm and dhcp_pol1_again_nm
+ assert:
+ that:
+ - dhcp_pol1_again_cm is not changed
+ - dhcp_pol1_again_nm is not changed
+ - dhcp_pol1_again_cm.current.name == dhcp_pol1_again_nm.current.name == 'ansible_dhcp_option_1'
+ - dhcp_pol1_again_cm.current.desc == dhcp_pol1_again_nm.current.desc == 'My Test DHCP Policy 1'
+ - dhcp_pol1_again_cm.current.policySubtype == dhcp_pol1_again_nm.current.policySubtype == 'option'
+ - dhcp_pol1_again_cm.current.policyType == dhcp_pol1_again_nm.current.policyType == 'dhcp'
+ - dhcp_pol1_again_cm.current.tenantId == dhcp_pol1_again_nm.current.tenantId == ansible_tenant.current.id
+
+ - name: Add a new DHCP Option Policy 2 (normal mode)
+ mso_dhcp_option_policy:
+ <<: *create_dhcp
+ dhcp_option_policy: ansible_dhcp_option_2
+
+ - name: Change DHCP Option Policy 1 description (check mode)
+ mso_dhcp_option_policy:
+ <<: *create_dhcp
+ description: "My Changed Test DHCP Policy 1"
+ check_mode: true
+ register: change_dhcp_pol1_cm
+
+ - name: Change DHCP Option Policy 1 description (normal mode)
+ mso_dhcp_option_policy:
+ <<: *create_dhcp
+ description: "My Changed Test DHCP Policy 1"
+ register: change_dhcp_pol1_nm
+
+ - name: Verify change_dhcp_pol1_cm and change_dhcp_pol1_nm
+ assert:
+ that:
+ - change_dhcp_pol1_cm is changed
+ - change_dhcp_pol1_nm is changed
+ - change_dhcp_pol1_cm.current.name == change_dhcp_pol1_nm.current.name == 'ansible_dhcp_option_1'
+ - change_dhcp_pol1_cm.current.desc == change_dhcp_pol1_nm.current.desc == 'My Changed Test DHCP Policy 1'
+ - change_dhcp_pol1_cm.current.policySubtype == change_dhcp_pol1_nm.current.policySubtype == 'option'
+ - change_dhcp_pol1_cm.current.policyType == change_dhcp_pol1_nm.current.policyType == 'dhcp'
+ - change_dhcp_pol1_cm.current.tenantId == change_dhcp_pol1_nm.current.tenantId == ansible_tenant.current.id
+
+ # QUERY A DHCP OPTION POLICY
+ - name: Query DHCP Option Policy 1 (check mode)
+ mso_dhcp_option_policy: &query_dhcp
+ <<: *mso_info
+ dhcp_option_policy: ansible_dhcp_option_1
+ state: query
+ check_mode: true
+ register: dhcp_pol1_query_cm
+
+ - name: Query DHCP Option Policy 1 (normal mode)
+ mso_dhcp_option_policy:
+ <<: *query_dhcp
+ register: dhcp_pol1_query_nm
+
+ - name: Verify dhcp_pol1_query
+ assert:
+ that:
+ - dhcp_pol1_query_cm is not changed
+ - dhcp_pol1_query_nm is not changed
+ - dhcp_pol1_query_cm.current.name == dhcp_pol1_query_nm.current.name == 'ansible_dhcp_option_1'
+ - dhcp_pol1_query_cm.current.desc == dhcp_pol1_query_nm.current.desc == 'My Changed Test DHCP Policy 1'
+ - dhcp_pol1_query_cm.current.policySubtype == dhcp_pol1_query_nm.current.policySubtype == 'option'
+ - dhcp_pol1_query_cm.current.policyType == dhcp_pol1_query_nm.current.policyType == 'dhcp'
+
+ # QUERY A NON-EXISTING DHCP OPTION POLICY
+ - name: Query non-existing DHCP Option Policy (normal mode)
+ mso_dhcp_option_policy:
+ <<: *mso_info
+ dhcp_option_policy: non_existing
+ state: query
+ register: quey_non_dhcp_pol
+
+ - name: Verify quey_non_dhcp_pol
+ assert:
+ that:
+ - quey_non_dhcp_pol is not changed
+
+ # QUERY ALL DHCP OPTION POLICIES
+ - name: Query all DHCP Option Policies (normal mode)
+ mso_dhcp_option_policy:
+ <<: *mso_info
+ state: query
+ register: dhcp_policies_query
+
+ - name: Verify dhcp_policies_query
+ assert:
+ that:
+ - dhcp_policies_query is not changed
+ - dhcp_policies_query.current | length == 2
+
+ # REMOVE DHCP POLICY
+ - name: Remove DHCP Option Policy 1 (check mode)
+ mso_dhcp_option_policy: &remove_dhcp
+ <<: *mso_info
+ dhcp_option_policy: ansible_dhcp_option_1
+ state: absent
+ check_mode: true
+ register: dhcp_pol1_removed_cm
+
+ - name: Remove DHCP Option Policy 1 (normal mode)
+ mso_dhcp_option_policy:
+ <<: *remove_dhcp
+ register: dhcp_pol1_removed_nm
+
+ - name: Verify dhcp_policies_removed
+ assert:
+ that:
+ - dhcp_pol1_removed_cm is changed
+ - dhcp_pol1_removed_nm is changed
+ - dhcp_pol1_removed_cm.current == dhcp_pol1_removed_nm.current == {}
+
+ # REMOVE DHCP POLICY AGAIN
+ - name: Remove DHCP Option Policy 1 again (check mode)
+ mso_dhcp_option_policy:
+ <<: *remove_dhcp
+ check_mode: true
+ register: dhcp_pol1_removed_again_cm
+
+ - name: Remove DHCP Option Policy 1 again (normal mode)
+ mso_dhcp_option_policy:
+ <<: *remove_dhcp
+ register: dhcp_pol1_removed_again_nm
+
+ - name: Verify dhcp_pol1_removed_again
+ assert:
+ that:
+ - dhcp_pol1_removed_again_cm is not changed
+ - dhcp_pol1_removed_again_nm is not changed
+ - dhcp_pol1_removed_again_cm.current == dhcp_pol1_removed_again_nm.current == {}
+ - dhcp_pol1_removed_again_cm.previous == dhcp_pol1_removed_again_nm.previous == {}
+
+
+ # USE A NON-EXISTING TENANT
+ - name: Non Existing Tenant for DHCP Option Policy 3 (normal mode)
+ mso_dhcp_option_policy:
+ <<: *mso_info
+ dhcp_option_policy: ansible_dhcp_option_3
+ description: "My Test DHCP Policy 3"
+ tenant: non_existing
+ state: present
+ ignore_errors: true
+ register: nm_non_existing_tenant
+
+ - name: Verify nm_non_existing_tenant
+ assert:
+ that:
+ - nm_non_existing_tenant is not changed
+ - nm_non_existing_tenant.msg == "Tenant 'non_existing' is not valid tenant name."
+
+ # CLEAN UP
+ - name: Stop consuming DHCP Policy
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ bd: CLIENT_BD
+ vrf:
+ name: VRF1
+ state: absent
+ ignore_errors: true
+
+ - name: Remove DHCP Option Policies
+ mso_dhcp_option_policy:
+ <<: *mso_info
+ dhcp_option_policy: '{{ item }}'
+ state: absent
+ loop:
+ - ansible_dhcp_option_1
+ - ansible_dhcp_option_2 \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_option_policy_option/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_option_policy_option/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_option_policy_option/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_option_policy_option/tasks/main.yaml b/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_option_policy_option/tasks/main.yaml
new file mode 100644
index 000000000..f6a79204f
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_option_policy_option/tasks/main.yaml
@@ -0,0 +1,444 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+# Copyright: (c) 2020, Jorge Gomez (@jgomezve) <jgomezve@cisco.com> (based on mso_dhcp_relay_policy test case)
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+#CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Execute tasks only for MSO version < 4.0
+ when: version.current.version is version('4.0', '<')
+ block:
+ - name: Remove options from DHCP Option Policy
+ mso_dhcp_option_policy_option:
+ <<: *mso_info
+ dhcp_option_policy: ansible_dhcp_option_1
+ name: "{{ item }}"
+ state: absent
+ loop:
+ - ansibletest
+ - ansibletest2
+ ignore_errors: true
+
+ - name: Stop consuming DHCP Policy
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ bd: CLIENT_BD
+ vrf:
+ name: VRF1
+ state: present
+ ignore_errors: true
+
+ - name: Remove DHCP Relay Policy 1
+ mso_dhcp_relay_policy:
+ <<: *mso_info
+ dhcp_relay_policy: '{{ item }}'
+ state: absent
+ loop:
+ - ansible_dhcp_relay_1
+ - ansible_dhcp_relay_2
+
+ - name: Remove DHCP Option Policies
+ mso_dhcp_option_policy:
+ <<: *mso_info
+ dhcp_option_policy: '{{ item }}'
+ state: absent
+ ignore_errors: true
+ loop:
+ - ansible_dhcp_option_1
+ - ansible_dhcp_option_2
+
+ - name: Undeploy sites in schema 1 template 1
+ mso_schema_template_deploy:
+ <<: *mso_info
+ template: Template 1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ item }}'
+ state: undeploy
+ ignore_errors: true
+ loop:
+ - '{{ mso_site | default("ansible_test") }}'
+ - '{{ mso_site | default("ansible_test") }}_2'
+ - 'aws_{{ mso_site | default("ansible_test") }}'
+ - 'azure_{{ mso_site | default("ansible_test") }}'
+
+ - name: Undeploy sites in schema 1 template 2
+ mso_schema_template_deploy:
+ <<: *mso_info
+ template: Template 2
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ item }}'
+ state: undeploy
+ ignore_errors: true
+ loop:
+ - '{{ mso_site | default("ansible_test") }}'
+ - '{{ mso_site | default("ansible_test") }}_2'
+ - 'aws_{{ mso_site | default("ansible_test") }}'
+ - 'azure_{{ mso_site | default("ansible_test") }}'
+
+ - name: Remove schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+ - name: Ensure tenant ansible_test exist
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ state: present
+ register: tenant_ansible
+
+ - name: Ensure schema 1 with Template 1 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: Template 1
+ state: present
+
+ - name: Add a new VRF
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF1
+ state: present
+
+ - name: Add BD
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ bd: CLIENT_BD
+ vrf:
+ name: VRF1
+ state: present
+
+ # ADD DHCP RELAY AND OPTION POLICY
+ - name: Add a new DHCP Option Policy 1 (Normal mode)
+ mso_dhcp_option_policy:
+ <<: *mso_info
+ dhcp_option_policy: ansible_dhcp_option_1
+ description: "My Test DHCP Policy 1"
+ tenant: ansible_test
+ state: present
+
+ - name: Add a new DHCP Relay Policy 1 (Normal mode)
+ mso_dhcp_relay_policy:
+ <<: *mso_info
+ dhcp_relay_policy: ansible_dhcp_relay_1
+ description: "My Test DHCP Policy 1"
+ tenant: ansible_test
+ state: present
+
+ # ADD OPTION TO DHCP OPTION POLICY
+ - name: Add Option to DHCP Option Policy (check mode)
+ mso_dhcp_option_policy_option: &create_option
+ <<: *mso_info
+ dhcp_option_policy: ansible_dhcp_option_1
+ name: ansibletest
+ id: 1
+ data: DHCP Data
+ state: present
+ check_mode: true
+ register: dhcp_pol1_opt1_cm
+
+ - name: Add Option to DHCP Option Policy (normal mode)
+ mso_dhcp_option_policy_option:
+ <<: *create_option
+ register: dhcp_pol1_opt1_nm
+
+ - name: Verify dhcp_pol1_opt1
+ assert:
+ that:
+ - dhcp_pol1_opt1_cm is changed
+ - dhcp_pol1_opt1_nm is changed
+ - dhcp_pol1_opt1_cm.current.name == dhcp_pol1_opt1_nm.current.name == 'ansibletest'
+ - dhcp_pol1_opt1_cm.current.id == dhcp_pol1_opt1_nm.current.id == '1'
+ - dhcp_pol1_opt1_cm.current.data == dhcp_pol1_opt1_nm.current.data == 'DHCP Data'
+
+ - name: Add Option to DHCP Option Policy again (check mode)
+ mso_dhcp_option_policy_option:
+ <<: *create_option
+ check_mode: true
+ register: dhcp_pol1_opt1_again_cm
+
+ - name: Add Option to DHCP Option Policy again (normal mode)
+ mso_dhcp_option_policy_option:
+ <<: *create_option
+ register: dhcp_pol1_opt1_again_nm
+
+ - name: Verify dhcp_pol1_opt1_again
+ assert:
+ that:
+ - dhcp_pol1_opt1_again_cm is not changed
+ - dhcp_pol1_opt1_again_nm is not changed
+ - dhcp_pol1_opt1_again_cm.current.name == dhcp_pol1_opt1_again_nm.current.name == 'ansibletest'
+ - dhcp_pol1_opt1_again_cm.current.id == dhcp_pol1_opt1_again_nm.current.id == '1'
+ - dhcp_pol1_opt1_again_cm.current.data == dhcp_pol1_opt1_again_nm.current.data == 'DHCP Data'
+
+ - name: Change Option IP to DHCP Option Policy (check mode)
+ mso_dhcp_option_policy_option:
+ <<: *create_option
+ data: Changed DHCP Data
+ check_mode: true
+ register: dhcp_pol1_opt1_change_cm
+
+ - name: Change Option IP to DHCP Option Policy (normal mode)
+ mso_dhcp_option_policy_option:
+ <<: *create_option
+ data: Changed DHCP Data
+ register: dhcp_pol1_opt1_change_nm
+
+ - name: Verify dhcp_pol1_opt1_change
+ assert:
+ that:
+ - dhcp_pol1_opt1_change_cm is changed
+ - dhcp_pol1_opt1_change_nm is changed
+ - dhcp_pol1_opt1_change_cm.current.name == dhcp_pol1_opt1_change_nm.current.name == 'ansibletest'
+ - dhcp_pol1_opt1_change_cm.current.id == dhcp_pol1_opt1_change_nm.current.id == '1'
+ - dhcp_pol1_opt1_change_cm.current.data == dhcp_pol1_opt1_change_nm.current.data == 'Changed DHCP Data'
+
+ - name: Add 2nd Option to DHCP Option Policy (check mode)
+ mso_dhcp_option_policy_option:
+ <<: *create_option
+ name: ansibletest2
+ check_mode: true
+ register: dhcp_pol1_opt2_cm
+
+ - name: Add 2nd Option to DHCP Option Policy (normal mode)
+ mso_dhcp_option_policy_option:
+ <<: *create_option
+ name: ansibletest2
+ register: dhcp_pol1_opt2_nm
+
+ - name: Verify dhcp_pol1_opt2
+ assert:
+ that:
+ - dhcp_pol1_opt2_cm is changed
+ - dhcp_pol1_opt2_nm is changed
+ - dhcp_pol1_opt2_cm.current.name == dhcp_pol1_opt2_nm.current.name == 'ansibletest2'
+ - dhcp_pol1_opt2_cm.current.id == dhcp_pol1_opt2_nm.current.id == '1'
+ - dhcp_pol1_opt2_cm.current.data == dhcp_pol1_opt2_nm.current.data == 'DHCP Data'
+
+ # QUERY OPTION FROM DHCP OPTION POLICY
+ - name: Query Option from DHCP Option Policy (check mode)
+ mso_dhcp_option_policy_option: &query_option
+ <<: *mso_info
+ dhcp_option_policy: ansible_dhcp_option_1
+ name: ansibletest
+ state: query
+ register: dhcp_pol1_opt1_query_cm
+
+ - name: Query Option from DHCP Option Policy (normal mode)
+ mso_dhcp_option_policy_option:
+ <<: *query_option
+ register: dhcp_pol1_opt1_query_nm
+
+ - name: Query nonexisting Option from DHCP Option Policy
+ mso_dhcp_option_policy_option:
+ <<: *query_option
+ name: nonexisting
+ state: query
+ register: dhcp_pol1_opt1_query_non_existing
+
+ - name: Query all Options from a DHCP Option Policy
+ mso_dhcp_option_policy_option:
+ <<: *mso_info
+ dhcp_option_policy: ansible_dhcp_option_1
+ state: query
+ register: dhcp_pol1_query_all
+
+ - name: Verify all query variables
+ assert:
+ that:
+ - dhcp_pol1_opt1_query_cm is not changed
+ - dhcp_pol1_opt1_query_nm is not changed
+ - dhcp_pol1_opt1_query_non_existing is not changed
+ - dhcp_pol1_query_all is not changed
+ - dhcp_pol1_opt1_query_cm.current.name == dhcp_pol1_opt1_query_nm.current.name == 'ansibletest'
+ - dhcp_pol1_opt1_query_cm.current.id == dhcp_pol1_opt1_query_nm.current.id == '1'
+ - dhcp_pol1_opt1_query_cm.current.data == dhcp_pol1_opt1_query_nm.current.data == 'Changed DHCP Data'
+ - dhcp_pol1_opt1_query_non_existing.current == {}
+ - dhcp_pol1_query_all.current | length == 2
+
+ # REMOVE OPTION FROM DHCP OPTION POLICY
+ - name: Remove Option from DHCP Option Policy (check mode)
+ mso_dhcp_option_policy_option: &delete_option
+ <<: *mso_info
+ dhcp_option_policy: ansible_dhcp_option_1
+ name: ansibletest
+ state: absent
+ check_mode: true
+ register: dhcp_pol1_opt1_del_cm
+
+ - name: Remove Option from DHCP Option Policy (normal mode)
+ mso_dhcp_option_policy_option:
+ <<: *delete_option
+ register: dhcp_pol1_opt1_del_nm
+
+ - name: Verify dhcp_pol1_opt1_del
+ assert:
+ that:
+ - dhcp_pol1_opt1_del_cm is changed
+ - dhcp_pol1_opt1_del_nm is changed
+ - dhcp_pol1_opt1_del_cm.current == dhcp_pol1_opt1_del_nm.current == {}
+
+ - name: Remove Option from DHCP Option Policy again (check mode)
+ mso_dhcp_option_policy_option:
+ <<: *delete_option
+ check_mode: true
+ register: dhcp_pol1_opt1_del_again_cm
+
+ - name: Remove Option from DHCP Option Policy again (normal mode)
+ mso_dhcp_option_policy_option:
+ <<: *delete_option
+ register: dhcp_pol1_opt1_del_again_nm
+
+ - name: Verify dhcp_pol1_opt1_again_del
+ assert:
+ that:
+ - dhcp_pol1_opt1_del_again_cm is not changed
+ - dhcp_pol1_opt1_del_again_nm is not changed
+ - dhcp_pol1_opt1_del_again_cm.current == dhcp_pol1_opt1_del_again_nm.current == {}
+
+ - name: Remove Non-Existing Option
+ mso_dhcp_option_policy_option:
+ <<: *delete_option
+ name: nonexisting
+ register: dhcp_pol1_opt1_del_nm_non_existing
+
+ - name: Verify dhcp_pol1_opt1_del_nm_non_existing
+ assert:
+ that:
+ - dhcp_pol1_opt1_del_nm_non_existing is not changed
+ - dhcp_pol1_opt1_del_nm_non_existing.current == {}
+
+ # CONSUME DHCP POLICIES
+ - name: Get DHCP Relay Policy version
+ mso_dhcp_relay_policy:
+ <<: *mso_info
+ dhcp_relay_policy: ansible_dhcp_relay_1
+ state: query
+ register: dhcp_relay_policy_version
+
+ - name: Get DHCP Option Policy version
+ mso_dhcp_option_policy:
+ <<: *mso_info
+ dhcp_option_policy: ansible_dhcp_option_1
+ state: query
+ register: dhcp_option_policy_version
+
+ - name: Consume DHCP Policy
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ bd: CLIENT_BD
+ vrf:
+ name: VRF1
+ dhcp_policy:
+ name: "{{ dhcp_relay_policy_version.current.name }}"
+ version: "{{ dhcp_relay_policy_version.current.version | int }}"
+ dhcp_option_policy:
+ name: "{{ dhcp_option_policy_version.current.name }}"
+ version: "{{ dhcp_option_policy_version.current.version | int }}"
+ state: present
+ register: bd_dhcp_policy
+
+ - name: Stop consuming DHCP Policy
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ bd: CLIENT_BD
+ vrf:
+ name: VRF1
+ state: present
+ register: bd_dhcp_policy
+
+ # QUERY OPTION FROM non_existing DHCP OPTION POLICY
+ - name: Query Option from DHCP Option Policy (check mode)
+ mso_dhcp_option_policy_option:
+ <<: *mso_info
+ dhcp_option_policy: nonexisting
+ state: query
+ ignore_errors: true
+ register: dhcp_non_existing
+
+ - name: Verify dhcp_non_existing
+ assert:
+ that:
+ - dhcp_non_existing is not changed
+ - dhcp_non_existing.msg == "DHCP Option Policy 'nonexisting' is not a valid DHCP Option Policy name."
+
+ # CLEAN UP
+ - name: Remove options from DHCP Option Policy
+ mso_dhcp_option_policy_option:
+ <<: *mso_info
+ dhcp_option_policy: ansible_dhcp_option_1
+ name: "{{ item }}"
+ state: absent
+ loop:
+ - ansibletest
+ - ansibletest2
+ ignore_errors: true
+
+ - name: Stop consuming DHCP Policy
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ bd: CLIENT_BD
+ vrf:
+ name: VRF1
+ state: absent
+ ignore_errors: true
+
+ - name: Remove DHCP Relay Policy 1
+ mso_dhcp_relay_policy:
+ <<: *mso_info
+ dhcp_relay_policy: '{{ item }}'
+ state: absent
+ loop:
+ - ansible_dhcp_relay_1
+ - ansible_dhcp_relay_2
+
+ - name: Remove DHCP Option Policies
+ mso_dhcp_option_policy:
+ <<: *mso_info
+ dhcp_option_policy: '{{ item }}'
+ state: absent
+ ignore_errors: true
+ loop:
+ - ansible_dhcp_option_1
+ - ansible_dhcp_option_2 \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_relay_policy/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_relay_policy/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_relay_policy/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_relay_policy/tasks/main.yaml b/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_relay_policy/tasks/main.yaml
new file mode 100644
index 000000000..4a7db2dca
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_relay_policy/tasks/main.yaml
@@ -0,0 +1,270 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Jorge Gomez (@jgomezve) <jgomezve@cisco.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+#CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Execute tasks only for MSO version < 4.0
+ when: version.current.version is version('4.0', '<')
+ block:
+ - name: Ensure tenant ansible_test exist
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ state: present
+ register: ansible_tenant
+
+ - name: Stop consuming DHCP Policy
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ bd: CLIENT_BD
+ vrf:
+ name: VRF1
+ state: absent
+ ignore_errors: true
+
+ - name: Remove DHCP Relay Policy 1
+ mso_dhcp_relay_policy:
+ <<: *mso_info
+ dhcp_relay_policy: '{{ item }}'
+ state: absent
+ loop:
+ - ansible_dhcp_relay_1
+ - ansible_dhcp_relay_2
+ - ansible_test_dhcp_policy1
+ - ansible_test_dhcp_policy2
+ - ansible_test_dhcp_policy3
+
+ # ADD DHCP Policy
+ - name: Add a new DHCP Relay Policy 1 (check mode)
+ mso_dhcp_relay_policy: &create_dhcp
+ <<: *mso_info
+ dhcp_relay_policy: ansible_dhcp_relay_1
+ description: "My Test DHCP Policy 1"
+ tenant: ansible_test
+ state: present
+ check_mode: true
+ register: dhcp_pol1_cm
+
+ - name: Add a new DHCP Relay Policy 1 (normal mode)
+ mso_dhcp_relay_policy:
+ <<: *create_dhcp
+ register: dhcp_pol1_nm
+
+ - name: Verify dhcp_pol1_cm and dhcp_pol1_nm
+ assert:
+ that:
+ - dhcp_pol1_cm is changed
+ - dhcp_pol1_nm is changed
+ - dhcp_pol1_cm.current.name == dhcp_pol1_nm.current.name == 'ansible_dhcp_relay_1'
+ - dhcp_pol1_cm.current.desc == dhcp_pol1_nm.current.desc == 'My Test DHCP Policy 1'
+ - dhcp_pol1_cm.current.policySubtype == dhcp_pol1_nm.current.policySubtype == 'relay'
+ - dhcp_pol1_cm.current.policyType == dhcp_pol1_nm.current.policyType == 'dhcp'
+ - dhcp_pol1_cm.current.tenantId == dhcp_pol1_nm.current.tenantId == ansible_tenant.current.id
+
+ - name: Add a new DHCP Relay Policy 1 again (check mode)
+ mso_dhcp_relay_policy:
+ <<: *create_dhcp
+ check_mode: true
+ register: dhcp_pol1_again_cm
+
+ - name: Add a new DHCP Relay Policy 1 again (normal mode)
+ mso_dhcp_relay_policy:
+ <<: *create_dhcp
+ register: dhcp_pol1_again_nm
+
+ - name: Verify dhcp_pol1_again_cm and dhcp_pol1_again_nm
+ assert:
+ that:
+ - dhcp_pol1_again_cm is not changed
+ - dhcp_pol1_again_nm is not changed
+ - dhcp_pol1_again_cm.current.name == dhcp_pol1_again_nm.current.name == 'ansible_dhcp_relay_1'
+ - dhcp_pol1_again_cm.current.desc == dhcp_pol1_again_nm.current.desc == 'My Test DHCP Policy 1'
+ - dhcp_pol1_again_cm.current.policySubtype == dhcp_pol1_again_nm.current.policySubtype == 'relay'
+ - dhcp_pol1_again_cm.current.policyType == dhcp_pol1_again_nm.current.policyType == 'dhcp'
+ - dhcp_pol1_again_cm.current.tenantId == dhcp_pol1_again_nm.current.tenantId == ansible_tenant.current.id
+
+ - name: Add a new DHCP Relay Policy 2 (normal mode)
+ mso_dhcp_relay_policy:
+ <<: *create_dhcp
+ dhcp_relay_policy: ansible_dhcp_relay_2
+
+ - name: Change DHCP Relay Policy 1 description (check mode)
+ mso_dhcp_relay_policy:
+ <<: *create_dhcp
+ description: "My Changed Test DHCP Policy 1"
+ check_mode: true
+ register: change_dhcp_pol1_cm
+
+ - name: Change DHCP Relay Policy 1 description (normal mode)
+ mso_dhcp_relay_policy:
+ <<: *create_dhcp
+ description: "My Changed Test DHCP Policy 1"
+ register: change_dhcp_pol1_nm
+
+ - name: Verify change_dhcp_pol1_cm and change_dhcp_pol1_nm
+ assert:
+ that:
+ - change_dhcp_pol1_cm is changed
+ - change_dhcp_pol1_nm is changed
+ - change_dhcp_pol1_cm.current.name == change_dhcp_pol1_nm.current.name == 'ansible_dhcp_relay_1'
+ - change_dhcp_pol1_cm.current.desc == change_dhcp_pol1_nm.current.desc == 'My Changed Test DHCP Policy 1'
+ - change_dhcp_pol1_cm.current.policySubtype == change_dhcp_pol1_nm.current.policySubtype == 'relay'
+ - change_dhcp_pol1_cm.current.policyType == change_dhcp_pol1_nm.current.policyType == 'dhcp'
+ - change_dhcp_pol1_cm.current.tenantId == change_dhcp_pol1_nm.current.tenantId == ansible_tenant.current.id
+
+ # QUERY A DHCP RELAY POLICY
+ - name: Query DHCP Relay Policy 1 (check mode)
+ mso_dhcp_relay_policy: &query_dhcp
+ <<: *mso_info
+ dhcp_relay_policy: ansible_dhcp_relay_1
+ state: query
+ check_mode: true
+ register: dhcp_pol1_query_cm
+
+ - name: Query DHCP Relay Policy 1 (normal mode)
+ mso_dhcp_relay_policy:
+ <<: *query_dhcp
+ register: dhcp_pol1_query_nm
+
+ - name: Verify dhcp_pol1_query
+ assert:
+ that:
+ - dhcp_pol1_query_cm is not changed
+ - dhcp_pol1_query_nm is not changed
+ - dhcp_pol1_query_cm.current.name == dhcp_pol1_query_nm.current.name == 'ansible_dhcp_relay_1'
+ - dhcp_pol1_query_cm.current.desc == dhcp_pol1_query_nm.current.desc == 'My Changed Test DHCP Policy 1'
+ - dhcp_pol1_query_cm.current.policySubtype == dhcp_pol1_query_nm.current.policySubtype == 'relay'
+ - dhcp_pol1_query_cm.current.policyType == dhcp_pol1_query_nm.current.policyType == 'dhcp'
+
+ # QUERY A NON-EXISTING DHCP RELAY POLICY
+ - name: Query non-existing DHCP Relay Policy (normal mode)
+ mso_dhcp_relay_policy:
+ <<: *mso_info
+ dhcp_relay_policy: non_existing
+ state: query
+ register: quey_non_dhcp_pol
+
+ - name: Verify quey_non_dhcp_pol
+ assert:
+ that:
+ - quey_non_dhcp_pol is not changed
+
+ # QUERY ALL DHCP RELAY POLICIES
+ - name: Query all DHCP Relay Policies (normal mode)
+ mso_dhcp_relay_policy:
+ <<: *mso_info
+ state: query
+ register: dhcp_policies_query
+
+ - name: Verify dhcp_policies_query
+ assert:
+ that:
+ - dhcp_policies_query is not changed
+ - dhcp_policies_query.current | length == 2
+
+ # REMOVE DHCP POLICY
+ - name: Remove DHCP Relay Policy 1 (check mode)
+ mso_dhcp_relay_policy: &remove_dhcp
+ <<: *mso_info
+ dhcp_relay_policy: ansible_dhcp_relay_1
+ state: absent
+ check_mode: true
+ register: dhcp_pol1_removed_cm
+
+ - name: Remove DHCP Relay Policy 1 (normal mode)
+ mso_dhcp_relay_policy:
+ <<: *remove_dhcp
+ register: dhcp_pol1_removed_nm
+
+ - name: Verify dhcp_policies_removed
+ assert:
+ that:
+ - dhcp_pol1_removed_cm is changed
+ - dhcp_pol1_removed_nm is changed
+ - dhcp_pol1_removed_cm.current == dhcp_pol1_removed_nm.current == {}
+
+ # REMOVE DHCP POLICY AGAIN
+ - name: Remove DHCP Relay Policy 1 again (check mode)
+ mso_dhcp_relay_policy:
+ <<: *remove_dhcp
+ check_mode: true
+ register: dhcp_pol1_removed_again_cm
+
+ - name: Remove DHCP Relay Policy 1 again (normal mode)
+ mso_dhcp_relay_policy:
+ <<: *remove_dhcp
+ register: dhcp_pol1_removed_again_nm
+
+ - name: Verify dhcp_pol1_removed_again
+ assert:
+ that:
+ - dhcp_pol1_removed_again_cm is not changed
+ - dhcp_pol1_removed_again_nm is not changed
+ - dhcp_pol1_removed_again_cm.current == dhcp_pol1_removed_again_nm.current == {}
+ - dhcp_pol1_removed_again_cm.previous == dhcp_pol1_removed_again_nm.previous == {}
+
+
+ # USE A NON-EXISTING TENANT
+ - name: Non Existing Tenant for DHCP Relay Policy 3 (normal mode)
+ mso_dhcp_relay_policy:
+ <<: *mso_info
+ dhcp_relay_policy: ansible_dhcp_relay_3
+ description: "My Test DHCP Policy 3"
+ tenant: non_existing
+ state: present
+ ignore_errors: true
+ register: nm_non_existing_tenant
+
+ - name: Verify nm_non_existing_tenant
+ assert:
+ that:
+ - nm_non_existing_tenant is not changed
+ - nm_non_existing_tenant.msg == "Tenant 'non_existing' is not valid tenant name."
+
+ # CLEAN UP DHCP POLICIES
+ - name: Stop consuming DHCP Policy
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ bd: CLIENT_BD
+ vrf:
+ name: VRF1
+ state: absent
+ ignore_errors: true
+
+ - name: Remove DHCP Relay Policy 1
+ mso_dhcp_relay_policy:
+ <<: *mso_info
+ dhcp_relay_policy: '{{ item }}'
+ state: absent
+ loop:
+ - ansible_dhcp_relay_1
+ - ansible_dhcp_relay_2 \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_relay_policy_provider/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_relay_policy_provider/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_relay_policy_provider/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_relay_policy_provider/tasks/main.yaml b/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_relay_policy_provider/tasks/main.yaml
new file mode 100644
index 000000000..828a34424
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_dhcp_relay_policy_provider/tasks/main.yaml
@@ -0,0 +1,662 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Jorge Gomez (@jgomezve) <cizhao@jgomezve.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+#CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Execute tasks only for MSO version < 4.0
+ when: version.current.version is version('4.0', '<')
+ block:
+ - name: Remove EXT_EPGs Providers from DHCP Relay Policy
+ mso_dhcp_relay_policy_provider:
+ <<: *mso_info
+ dhcp_relay_policy: ansible_dhcp_relay_1
+ tenant: ansible_test
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_endpoint_group: "{{ item }}"
+ state: absent
+ ignore_errors: true
+ loop:
+ - EXT_EPG_1
+ - EXT_EPG_2
+
+ - name: Remove EXT_EPGs Providers from DHCP Relay Policy
+ mso_dhcp_relay_policy_provider:
+ <<: *mso_info
+ dhcp_relay_policy: ansible_dhcp_relay_1
+ tenant: ansible_test
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ endpoint_group: "{{ item }}"
+ application_profile: "ANP_1"
+ state: absent
+ ignore_errors: true
+ loop:
+ - EPG_1
+ - EPG_2
+
+ - name: Stop consuming DHCP Policy
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ bd: CLIENT_BD
+ vrf:
+ name: VRF1
+ state: present
+ ignore_errors: true
+
+ - name: Remove DHCP Relay Policies
+ mso_dhcp_relay_policy:
+ <<: *mso_info
+ dhcp_relay_policy: '{{ item }}'
+ state: absent
+ ignore_errors: true
+ loop:
+ - ansible_dhcp_relay_1
+ - ansible_dhcp_relay_2
+
+ - name: Undeploy sites in schema 1 template 1
+ mso_schema_template_deploy:
+ <<: *mso_info
+ template: Template 1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ item }}'
+ state: undeploy
+ ignore_errors: true
+ loop:
+ - '{{ mso_site | default("ansible_test") }}'
+ - '{{ mso_site | default("ansible_test") }}_2'
+ - 'aws_{{ mso_site | default("ansible_test") }}'
+ - 'azure_{{ mso_site | default("ansible_test") }}'
+
+ - name: Undeploy sites in schema 1 template 2
+ mso_schema_template_deploy:
+ <<: *mso_info
+ template: Template 2
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ item }}'
+ state: undeploy
+ ignore_errors: true
+ loop:
+ - '{{ mso_site | default("ansible_test") }}'
+ - '{{ mso_site | default("ansible_test") }}_2'
+ - 'aws_{{ mso_site | default("ansible_test") }}'
+ - 'azure_{{ mso_site | default("ansible_test") }}'
+
+ - name: Remove schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+ - name: Ensure tenant ansible_test exist
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ state: present
+ register: tenant_ansible
+
+ - name: Ensure schema 1 with Template 1 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: Template 1
+ state: present
+
+ # CREATE EPG PROVIDER
+ - name: Add a new VRF
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF1
+ state: present
+
+ - name: Add a new BD
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ bd: BD_1
+ vrf:
+ name: VRF1
+ state: present
+
+ - name: Add 2nd BD
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ bd: CLIENT_BD
+ vrf:
+ name: VRF1
+ state: present
+
+ - name: Add a new ANP
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP_1
+ state: present
+
+ - name: Add a new EPG
+ mso_schema_template_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP_1
+ epg: EPG_1
+ bd:
+ name: BD_1
+ vrf:
+ name: VRF1
+ state: present
+
+ - name: Add 2nd EPG
+ mso_schema_template_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP_1
+ epg: EPG_2
+ bd:
+ name: BD_1
+ vrf:
+ name: VRF1
+ state: present
+
+ - name: Add a new L3out
+ mso_schema_template_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ l3out: L3OUT_1
+ vrf:
+ name: VRF1
+ state: present
+
+ - name: Add a new external EPG
+ cisco.mso.mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: EXT_EPG_1
+ vrf:
+ name: VRF1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ l3out:
+ name: L3OUT_1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ state: present
+
+ - name: Add 2nd external EPG
+ cisco.mso.mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: EXT_EPG_2
+ vrf:
+ name: VRF1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ l3out:
+ name: L3OUT_1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ state: present
+
+ # ADD DHCP RELAY POLICY
+ - name: Add a new DHCP Relay Policy 1 (Normal mode)
+ mso_dhcp_relay_policy:
+ <<: *mso_info
+ dhcp_relay_policy: ansible_dhcp_relay_1
+ description: "My Test DHCP Policy 1"
+ tenant: ansible_test
+ state: present
+
+ # ADD PROVIDER TO DHCP RELAY POLICY
+ - name: Add Provider to DHCP Relay Policy (check mode)
+ mso_dhcp_relay_policy_provider: &create_provider
+ <<: *mso_info
+ dhcp_relay_policy: ansible_dhcp_relay_1
+ ip: "1.1.1.1"
+ tenant: ansible_test
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ application_profile: ANP_1
+ endpoint_group: EPG_1
+ state: present
+ check_mode: true
+ register: dhcp_pol1_prov1_cm
+
+ - name: Add Provider to DHCP Relay Policy (normal mode)
+ mso_dhcp_relay_policy_provider:
+ <<: *create_provider
+ register: dhcp_pol1_prov1_nm
+
+ - name: Verify dhcp_pol1_prov1
+ assert:
+ that:
+ - dhcp_pol1_prov1_cm is changed
+ - dhcp_pol1_prov1_nm is changed
+ - dhcp_pol1_prov1_cm.current.addr == dhcp_pol1_prov1_nm.current.addr == '1.1.1.1'
+ - "'EPG_1' in dhcp_pol1_prov1_cm.current.epgRef"
+ - "'EPG_1' in dhcp_pol1_prov1_nm.current.epgRef"
+ - "'ANP_1' in dhcp_pol1_prov1_cm.current.epgRef"
+ - "'ANP_1' in dhcp_pol1_prov1_nm.current.epgRef"
+ - "'Template1' in dhcp_pol1_prov1_cm.current.epgRef"
+ - "'Template1' in dhcp_pol1_prov1_nm.current.epgRef"
+ - dhcp_pol1_prov1_cm.current.tenantId == tenant_ansible.current.id
+ - dhcp_pol1_prov1_nm.current.tenantId == tenant_ansible.current.id
+
+ - name: Add Provider to DHCP Relay Policy again (check mode)
+ mso_dhcp_relay_policy_provider:
+ <<: *create_provider
+ check_mode: true
+ register: dhcp_pol1_prov1_again_cm
+
+ - name: Add Provider to DHCP Relay Policy again (normal mode)
+ mso_dhcp_relay_policy_provider:
+ <<: *create_provider
+ register: dhcp_pol1_prov1_again_nm
+
+ - name: Verify dhcp_pol1_prov1_again
+ assert:
+ that:
+ - dhcp_pol1_prov1_again_cm is not changed
+ - dhcp_pol1_prov1_again_nm is not changed
+ - dhcp_pol1_prov1_again_cm.current.addr == dhcp_pol1_prov1_again_nm.current.addr == '1.1.1.1'
+ - "'EPG_1' in dhcp_pol1_prov1_again_cm.current.epgRef"
+ - "'EPG_1' in dhcp_pol1_prov1_again_nm.current.epgRef"
+ - "'ANP_1' in dhcp_pol1_prov1_again_cm.current.epgRef"
+ - "'ANP_1' in dhcp_pol1_prov1_again_nm.current.epgRef"
+ - "'Template1' in dhcp_pol1_prov1_again_cm.current.epgRef"
+ - "'Template1' in dhcp_pol1_prov1_again_nm.current.epgRef"
+ - dhcp_pol1_prov1_again_cm.current.tenantId == tenant_ansible.current.id
+ - dhcp_pol1_prov1_again_nm.current.tenantId == tenant_ansible.current.id
+
+ - name: Change Provider IP to DHCP Relay Policy (check mode)
+ mso_dhcp_relay_policy_provider:
+ <<: *create_provider
+ ip: "2.2.2.2"
+ check_mode: true
+ register: dhcp_pol1_prov1_change_cm
+
+ - name: Change Provider IP to DHCP Relay Policy (normal mode)
+ mso_dhcp_relay_policy_provider:
+ <<: *create_provider
+ ip: "2.2.2.2"
+ register: dhcp_pol1_prov1_change_nm
+
+ - name: Verify dhcp_pol1_prov1_change
+ assert:
+ that:
+ - dhcp_pol1_prov1_change_cm is changed
+ - dhcp_pol1_prov1_change_nm is changed
+ - dhcp_pol1_prov1_change_cm.current.addr == dhcp_pol1_prov1_change_nm.current.addr == '2.2.2.2'
+ - "'EPG_1' in dhcp_pol1_prov1_change_cm.current.epgRef"
+ - "'EPG_1' in dhcp_pol1_prov1_change_nm.current.epgRef"
+ - "'ANP_1' in dhcp_pol1_prov1_change_cm.current.epgRef"
+ - "'ANP_1' in dhcp_pol1_prov1_change_nm.current.epgRef"
+ - "'Template1' in dhcp_pol1_prov1_change_cm.current.epgRef"
+ - "'Template1' in dhcp_pol1_prov1_change_nm.current.epgRef"
+ - dhcp_pol1_prov1_change_cm.current.tenantId == tenant_ansible.current.id
+ - dhcp_pol1_prov1_change_nm.current.tenantId == tenant_ansible.current.id
+
+ - name: Add 2nd Provider (EPG_2) to DHCP Relay Policy (check mode)
+ mso_dhcp_relay_policy_provider:
+ <<: *create_provider
+ ip: "2.2.2.2"
+ endpoint_group: EPG_2
+ check_mode: true
+ register: dhcp_pol1_prov2_cm
+
+ - name: Add 2nd Provider (EPG_2) to DHCP Relay Policy (normal mode)
+ mso_dhcp_relay_policy_provider:
+ <<: *create_provider
+ ip: "2.2.2.2"
+ endpoint_group: EPG_2
+ register: dhcp_pol1_prov2_nm
+
+ - name: Add 3rd Provider (EXT_EPG_1) to DHCP Relay Policy (check mode)
+ mso_dhcp_relay_policy_provider: &create_provider_external_epg
+ <<: *create_provider
+ ip: "2.2.2.2"
+ external_endpoint_group: EXT_EPG_1
+ application_profile: null
+ endpoint_group: null
+ check_mode: true
+ register: dhcp_pol1_prov3_cm
+
+ - name: Add 3rd Provider (EXT_EPG_1) to DHCP Relay Policy (normal mode)
+ mso_dhcp_relay_policy_provider:
+ <<: *create_provider_external_epg
+ external_endpoint_group: EXT_EPG_1
+ register: dhcp_pol1_prov3_nm
+
+ - name: Add 4th Provider (EXT_EPG_2) to DHCP Relay Policy (check mode)
+ mso_dhcp_relay_policy_provider:
+ <<: *create_provider_external_epg
+ external_endpoint_group: EXT_EPG_2
+ check_mode: true
+ register: dhcp_pol1_prov4_cm
+
+ - name: Add 4th Provider (EXT_EPG_2) to DHCP Relay Policy (normal mode)
+ mso_dhcp_relay_policy_provider:
+ <<: *create_provider_external_epg
+ external_endpoint_group: EXT_EPG_2
+ register: dhcp_pol1_prov4_nm
+
+ - name: Verify dhcp_pol1_prov2, dhcp_pol1_prov3 and dhcp_pol1_prov4
+ assert:
+ that:
+ - dhcp_pol1_prov2_cm is changed
+ - dhcp_pol1_prov2_nm is changed
+ - dhcp_pol1_prov3_cm is changed
+ - dhcp_pol1_prov3_nm is changed
+ - dhcp_pol1_prov4_cm is changed
+ - dhcp_pol1_prov4_nm is changed
+ - dhcp_pol1_prov2_cm.current.addr == dhcp_pol1_prov2_nm.current.addr == '2.2.2.2'
+ - dhcp_pol1_prov3_cm.current.addr == dhcp_pol1_prov3_nm.current.addr == '2.2.2.2'
+ - dhcp_pol1_prov4_cm.current.addr == dhcp_pol1_prov4_nm.current.addr == '2.2.2.2'
+ - "'EPG_2' in dhcp_pol1_prov2_cm.current.epgRef"
+ - "'EPG_2' in dhcp_pol1_prov2_nm.current.epgRef"
+ - "'ANP_1' in dhcp_pol1_prov2_cm.current.epgRef"
+ - "'ANP_1' in dhcp_pol1_prov2_nm.current.epgRef"
+ - "'Template1' in dhcp_pol1_prov2_cm.current.epgRef"
+ - "'Template1' in dhcp_pol1_prov2_nm.current.epgRef"
+ - "'EXT_EPG_1' in dhcp_pol1_prov3_cm.current.externalEpgRef"
+ - "'EXT_EPG_1' in dhcp_pol1_prov3_nm.current.externalEpgRef"
+ - "'EXT_EPG_2' in dhcp_pol1_prov4_cm.current.externalEpgRef"
+ - "'EXT_EPG_2' in dhcp_pol1_prov4_nm.current.externalEpgRef"
+ - dhcp_pol1_prov3_cm.current.tenantId == tenant_ansible.current.id
+ - dhcp_pol1_prov3_nm.current.tenantId == tenant_ansible.current.id
+ - dhcp_pol1_prov4_cm.current.tenantId == tenant_ansible.current.id
+ - dhcp_pol1_prov4_nm.current.tenantId == tenant_ansible.current.id
+
+ # ADD DHCP RELAY PROVIDER WITH WRONG Attributes
+ - name: Add Provider to DHCP Relay Policy - wrong tenant (Normal mode)
+ mso_dhcp_relay_policy_provider:
+ <<: *mso_info
+ dhcp_relay_policy: ansible_dhcp_relay_1
+ ip: "2.2.2.2"
+ tenant: ansible_test_wrong
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ application_profile: ANP_1
+ endpoint_group: EPG_1
+ state: present
+ ignore_errors: true
+ register: dhcp_pol1_prov2_nm_ten_wrong
+
+ - name: Add Provider to DHCP Relay Policy - wrong Schema (Normal mode)
+ mso_dhcp_relay_policy_provider:
+ <<: *mso_info
+ dhcp_relay_policy: ansible_dhcp_relay_1
+ ip: "2.2.2.2"
+ tenant: ansible_test
+ schema: schema_wrong
+ template: Template 1
+ application_profile: ANP_1
+ endpoint_group: EPG_1
+ state: present
+ ignore_errors: true
+ register: dhcp_pol1_prov2_nm_sch_wrong
+
+ - name: Verify dhcp_pol1_prov2_nm_ten_wrong, dhcp_pol1_prov2_nm_sch_wrong & dhcp_pol1_prov2_nm_tmp_wrong
+ assert:
+ that:
+ - dhcp_pol1_prov2_nm_ten_wrong is not changed
+ - dhcp_pol1_prov2_nm_ten_wrong.msg == "Tenant 'ansible_test_wrong' is not valid tenant name."
+ - dhcp_pol1_prov2_nm_sch_wrong is not changed
+ - dhcp_pol1_prov2_nm_sch_wrong.msg == "Provided schema 'schema_wrong' does not exist."
+ # MSO API allows to create provider in non-existing/wrong templates/epgs/ext_epgs
+
+ # QUERY PROVIDER FROM DHCP RELAY POLICY
+ - name: Query Provider from DHCP Relay Policy (check mode)
+ mso_dhcp_relay_policy_provider: &query_provider
+ <<: *mso_info
+ dhcp_relay_policy: ansible_dhcp_relay_1
+ tenant: ansible_test
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ application_profile: ANP_1
+ endpoint_group: EPG_1
+ state: query
+ register: dhcp_pol1_prov1_query_cm
+
+ - name: Query Provider from DHCP Relay Policy (normal mode)
+ mso_dhcp_relay_policy_provider:
+ <<: *query_provider
+ register: dhcp_pol1_prov1_query_nm
+
+ - name: Query non_existing Provider from DHCP Relay Policy
+ mso_dhcp_relay_policy_provider:
+ <<: *query_provider
+ endpoint_group: non_existing
+ state: query
+ register: dhcp_pol1_prov1_query_non_existing
+
+ - name: Query all Providers from a DHCP Relay Policy
+ mso_dhcp_relay_policy_provider:
+ <<: *mso_info
+ dhcp_relay_policy: ansible_dhcp_relay_1
+ state: query
+ register: dhcp_pol1_query_all
+
+ - name: Verify all query variables
+ assert:
+ that:
+ - dhcp_pol1_prov1_query_cm is not changed
+ - dhcp_pol1_prov1_query_nm is not changed
+ - dhcp_pol1_prov1_query_non_existing is not changed
+ - dhcp_pol1_query_all is not changed
+ - dhcp_pol1_prov1_query_cm.current.addr == dhcp_pol1_prov1_query_nm.current.addr == '2.2.2.2'
+ - "'EPG_1' in dhcp_pol1_prov1_query_cm.current.epgRef"
+ - "'EPG_1' in dhcp_pol1_prov1_query_nm.current.epgRef"
+ - "'ANP_1' in dhcp_pol1_prov1_query_cm.current.epgRef"
+ - "'ANP_1' in dhcp_pol1_prov1_query_nm.current.epgRef"
+ - "'Template1' in dhcp_pol1_prov1_query_cm.current.epgRef"
+ - "'Template1' in dhcp_pol1_prov1_query_nm.current.epgRef"
+ - dhcp_pol1_prov1_query_non_existing.current == {}
+ - dhcp_pol1_query_all.current | length == 4
+
+ # REMOVE PROVIDER FROM DHCP RELAY POLICY
+ - name: Remove Provider (EXT_EPG) from DHCP Relay Policy (check mode)
+ mso_dhcp_relay_policy_provider: &delete_provider
+ <<: *mso_info
+ dhcp_relay_policy: ansible_dhcp_relay_1
+ tenant: ansible_test
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_endpoint_group: EXT_EPG_1
+ state: absent
+ check_mode: true
+ register: dhcp_pol1_prov1_del_cm
+
+ - name: Remove Provider (EXT_EPG) from DHCP Relay Policy (normal mode)
+ mso_dhcp_relay_policy_provider:
+ <<: *delete_provider
+ register: dhcp_pol1_prov1_del_nm
+
+ - name: Verify dhcp_pol1_prov1_del
+ assert:
+ that:
+ - dhcp_pol1_prov1_del_cm is changed
+ - dhcp_pol1_prov1_del_nm is changed
+ - dhcp_pol1_prov1_del_cm.current == dhcp_pol1_prov1_del_nm.current == {}
+
+ - name: Remove Provider (EXT_EPG) from DHCP Relay Policy again (check mode)
+ mso_dhcp_relay_policy_provider:
+ <<: *delete_provider
+ check_mode: true
+ register: dhcp_pol1_prov1_del_again_cm
+
+ - name: Remove Provider (EXT_EPG) from DHCP Relay Policy again (normal mode)
+ mso_dhcp_relay_policy_provider:
+ <<: *delete_provider
+ register: dhcp_pol1_prov1_del_again_nm
+
+ - name: Verify dhcp_pol1_prov1_again_del
+ assert:
+ that:
+ - dhcp_pol1_prov1_del_again_cm is not changed
+ - dhcp_pol1_prov1_del_again_nm is not changed
+ - dhcp_pol1_prov1_del_again_cm.current == dhcp_pol1_prov1_del_again_nm.current == {}
+
+ - name: Remove Non-Existing Provider (EXT_EPG)
+ mso_dhcp_relay_policy_provider:
+ <<: *delete_provider
+ external_endpoint_group: non_existing
+ register: dhcp_pol1_prov1_del_nm_non_existing
+
+ - name: Remove Provider without epg or ext_epg
+ mso_dhcp_relay_policy_provider:
+ <<: *mso_info
+ dhcp_relay_policy: ansible_dhcp_relay_1
+ tenant: ansible_test
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ state: absent
+ ignore_errors: true
+ register: dhcp_pol1_prov1_del_none
+
+ - name: Verify dhcp_pol1_prov1_del_nm_non_existing
+ assert:
+ that:
+ - dhcp_pol1_prov1_del_nm_non_existing is not changed
+ - dhcp_pol1_prov1_del_none is not changed
+ - dhcp_pol1_prov1_del_nm_non_existing.current == {}
+ - dhcp_pol1_prov1_del_none.msg == 'Missing either endpoint_group or external_endpoint_group required attribute.'
+
+ # CONSUME DHCP POLICIES
+ - name: Get DHCP Relay Policy version
+ mso_dhcp_relay_policy:
+ <<: *mso_info
+ dhcp_relay_policy: ansible_dhcp_relay_1
+ state: query
+ register: dhcp_relay_policy_version
+
+ - name: Consume DHCP Policy
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ bd: CLIENT_BD
+ vrf:
+ name: VRF1
+ dhcp_policy:
+ name: "{{ dhcp_relay_policy_version.current.name }}"
+ version: "{{ dhcp_relay_policy_version.current.version | int }}"
+ state: present
+ register: bd_dhcp_policy
+
+ - name: Stop consuming DHCP Policy
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ bd: CLIENT_BD
+ vrf:
+ name: VRF1
+ state: present
+ register: bd_dhcp_policy
+
+ # QUERY PROVIDER FROM non_existing DHCP RELAY POLICY
+ - name: Query Provider from DHCP Relay Policy (check mode)
+ mso_dhcp_relay_policy_provider:
+ <<: *mso_info
+ dhcp_relay_policy: non_existing
+ state: query
+ ignore_errors: true
+ register: dhcp_non_existing
+
+ - name: Verify dhcp_non_existing
+ assert:
+ that:
+ - dhcp_non_existing is not changed
+ - dhcp_non_existing.msg == "DHCP Relay Policy 'non_existing' is not a valid DHCP Relay Policy name."
+
+ # CLEAN UP
+ - name: Remove EXT_EPGs Providers from DHCP Relay Policy
+ mso_dhcp_relay_policy_provider:
+ <<: *mso_info
+ dhcp_relay_policy: ansible_dhcp_relay_1
+ tenant: ansible_test
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_endpoint_group: "{{ item }}"
+ state: absent
+ ignore_errors: true
+ loop:
+ - EXT_EPG_1
+ - EXT_EPG_2
+
+ - name: Remove EXT_EPGs Providers from DHCP Relay Policy
+ mso_dhcp_relay_policy_provider:
+ <<: *mso_info
+ dhcp_relay_policy: ansible_dhcp_relay_1
+ tenant: ansible_test
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ endpoint_group: "{{ item }}"
+ application_profile: "ANP_1"
+ state: absent
+ ignore_errors: true
+ loop:
+ - EPG_1
+ - EPG_2
+
+ - name: Stop consuming DHCP Policy
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ bd: CLIENT_BD
+ vrf:
+ name: VRF1
+ state: absent
+ ignore_errors: true
+
+ - name: Remove DHCP Relay Policies
+ mso_dhcp_relay_policy:
+ <<: *mso_info
+ dhcp_relay_policy: '{{ item }}'
+ state: absent
+ ignore_errors: true
+ loop:
+ - ansible_dhcp_relay_1
+ - ansible_dhcp_relay_2 \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_label/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_label/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_label/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_label/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_label/tasks/main.yml
new file mode 100644
index 000000000..fffb0ce78
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_label/tasks/main.yml
@@ -0,0 +1,411 @@
+# Test code for the MSO modules
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Execute tasks only for MSO version < 3.2
+ when: version.current.version is version('3.2', '<')
+ block:
+ - name: GET auth radius providers
+ mso_rest:
+ <<: *mso_info
+ path: /api/v1/auth/providers/radius
+ method: get
+ register: radius_providers
+
+ - name: Add auth radius provider
+ mso_rest:
+ <<: *mso_info
+ path: /api/v1/auth/providers/radius
+ method: post
+ content:
+ {
+ "host": "{{ mso_radius_server }}",
+ "description": "",
+ "port": 1812,
+ "providerType": "radius",
+ "sharedSecret": "{{ mso_radius_secret | default('radius-secret') }}",
+ "timeoutInSeconds": 5,
+ "retries": 3,
+ "protocol": "pap"
+ }
+ register: radius_provider
+ when: mso_radius_server not in (radius_providers.jsondata.radiusProviders | map(attribute='host'))
+
+ - name: GET login domains
+ mso_rest:
+ <<: *mso_info
+ path: /api/v1/auth/domains
+ method: get
+ register: login_domains
+
+ - name: GET auth radius providers again after creation
+ mso_rest:
+ <<: *mso_info
+ path: /api/v1/auth/providers/radius
+ method: get
+ register: radius_providers
+
+ - name: GET auth radius provider ID
+ set_fact:
+ radius_provider_id: "{{ (radius_providers.jsondata.radiusProviders | selectattr('host', 'eq', mso_radius_server) | first)['id'] }}"
+
+ - name: Add test login domain
+ mso_rest:
+ <<: *mso_info
+ path: /api/v1/auth/domains
+ method: post
+ content:
+ {
+ "name": "{{ mso_login_domain | default('test') }}",
+ "description": "",
+ "realm": "radius",
+ "providerAssociations": [{
+ "priority": 1,
+ "providerId": "{{ radius_provider_id }}"
+ }],
+ "status": "active",
+ "isDefault": false
+ }
+ when: (mso_login_domain | default('test')) not in (login_domains.jsondata.domains | map(attribute='name'))
+
+# REMOVE DHCP POLICY
+- name: Remove DHCP Option Policy
+ mso_dhcp_option_policy: &remove_dhcp
+ <<: *mso_info
+ dhcp_option_policy: '{{ item }}'
+ state: absent
+ loop:
+ - ansible_dhcp_option_1
+ - ansible_dhcp_option_2
+
+- name: Remove DHCP Relay Policy
+ mso_dhcp_relay_policy:
+ <<: *mso_info
+ dhcp_relay_policy: '{{ item }}'
+ state: absent
+ loop:
+ - ansible_dhcp_relay_1
+ - ansible_dhcp_relay_2
+
+- name: Remove label ansible_test
+ mso_label: &label_absent
+ <<: *mso_info
+ label: ansible_test
+ state: absent
+
+- name: Remove label ansible_test2
+ mso_label:
+ <<: *label_absent
+ label: ansible_test2
+ register: cm_remove_label
+
+- name: Remove label ansible_test3
+ mso_label: &domain_label_absent
+ <<: *mso_info
+ state: absent
+ label: ansible_test3
+ login_domain: Local
+ register: nm_remove_label3
+
+- name: Remove label ansible_test4
+ mso_label:
+ <<: *domain_label_absent
+ label: ansible_test4
+ login_domain: '{{ mso_login_domain | default("test") }}'
+
+# ADD LABEL
+- name: Add label (check_mode)
+ mso_label: &label_present
+ <<: *mso_info
+ label: ansible_test
+ state: present
+ check_mode: true
+ register: cm_add_label
+
+- name: Verify cm_add_label
+ assert:
+ that:
+ - cm_add_label is changed
+ - cm_add_label.previous == {}
+ - cm_add_label.current.displayName == 'ansible_test'
+ - cm_add_label.current.id is not defined
+ - cm_add_label.current.type == 'site'
+
+- name: Add label (normal mode)
+ mso_label: *label_present
+ register: nm_add_label
+
+- name: Verify nm_add_label
+ assert:
+ that:
+ - nm_add_label is changed
+ - nm_add_label.previous == {}
+ - nm_add_label.current.displayName == 'ansible_test'
+ - nm_add_label.current.id is defined
+ - nm_add_label.current.type == 'site'
+
+- name: Add label again (check_mode)
+ mso_label: *label_present
+ check_mode: true
+ register: cm_add_label_again
+
+- name: Verify cm_add_label_again
+ assert:
+ that:
+ - cm_add_label_again is not changed
+ - cm_add_label_again.previous.displayName == 'ansible_test'
+ - cm_add_label_again.previous.type == 'site'
+ - cm_add_label_again.current.displayName == 'ansible_test'
+ - cm_add_label_again.current.id == nm_add_label.current.id
+ - cm_add_label_again.current.type == 'site'
+
+- name: Add label again (normal mode)
+ mso_label: *label_present
+ register: nm_add_label_again
+
+- name: Verify nm_add_label_again
+ assert:
+ that:
+ - nm_add_label_again is not changed
+ - nm_add_label_again.previous.displayName == 'ansible_test'
+ - nm_add_label_again.previous.type == 'site'
+ - nm_add_label_again.current.displayName == 'ansible_test'
+ - nm_add_label_again.current.id == nm_add_label.current.id
+ - nm_add_label_again.current.type == 'site'
+
+
+# CHANGE LABEL
+# - name: Change label (check_mode)
+# mso_label:
+# <<: *label_present
+# label_id: '{{ nm_add_label.current.id }}'
+# label: ansible_test2
+# check_mode: true
+# register: cm_change_label
+
+# - name: Verify cm_change_label
+# assert:
+# that:
+# - cm_change_label is changed
+# - cm_change_label.current.displayName == 'ansible_test2'
+# - cm_change_label.current.id == nm_add_label.current.id
+# - cm_change_label.current.type == 'site'
+
+# - name: Change label (normal mode)
+# mso_label:
+# <<: *label_present
+# label_id: '{{ nm_add_label.current.id }}'
+# label: ansible_test2
+# output_level: debug
+# register: nm_change_label
+
+# - name: Verify nm_change_label
+# assert:
+# that:
+# - nm_change_label is changed
+# - cm_change_label.current.displayName == 'ansible_test2'
+# - nm_change_label.current.id == nm_add_label.current.id
+# - nm_change_label.current.type == 'site'
+
+# - name: Change label again (check_mode)
+# mso_label:
+# <<: *label_present
+# label_id: '{{ nm_add_label.current.id }}'
+# label: ansible_test2
+# check_mode: true
+# register: cm_change_label_again
+
+# - name: Verify cm_change_label_again
+# assert:
+# that:
+# - cm_change_label_again is not changed
+# - cm_change_label_again.current.displayName == 'ansible_test2'
+# - cm_change_label_again.current.id == nm_add_label.current.id
+# - cm_change_label_again.current.type == 'site'
+
+# - name: Change label again (normal mode)
+# mso_label:
+# <<: *label_present
+# label_id: '{{ nm_add_label.current.id }}'
+# label: ansible_test2
+# register: nm_change_label_again
+
+# - name: Verify nm_change_label_again
+# assert:
+# that:
+# - nm_change_label_again is not changed
+# - nm_change_label_again.current.displayName == 'ansible_test2'
+# - nm_change_label_again.current.id == nm_add_label.current.id
+# - nm_change_label_again.current.type == 'site'
+
+
+# QUERY ALL LABELS
+- name: Query all labels (check_mode)
+ mso_label: &label_query
+ <<: *mso_info
+ state: query
+ check_mode: true
+ register: cm_query_all_labels
+
+- name: Query all labels (normal mode)
+ mso_label: *label_query
+ register: nm_query_all_labels
+
+- name: Verify query_all_labels
+ assert:
+ that:
+ - cm_query_all_labels is not changed
+ - nm_query_all_labels is not changed
+ # NOTE: Order of labels is not stable between calls
+ # FIXME:
+ #- cm_query_all_labels == nm_query_all_labels
+
+
+# QUERY A LABEL
+- name: Query our label (check mode)
+ mso_label:
+ <<: *label_query
+ label: ansible_test
+ check_mode: true
+ register: cm_query_label
+
+- name: Query our label (normal mode)
+ mso_label:
+ <<: *label_query
+ label: ansible_test
+ register: nm_query_label
+
+- name: Verify query_label
+ assert:
+ that:
+ - cm_query_label is not changed
+ - cm_query_label.current.displayName == 'ansible_test'
+ - cm_query_label.current.id == nm_add_label.current.id
+ - cm_query_label.current.type == 'site'
+ - nm_query_label is not changed
+ - nm_query_label.current.displayName == 'ansible_test'
+ - nm_query_label.current.id == nm_add_label.current.id
+ - nm_query_label.current.type == 'site'
+ - cm_query_label == nm_query_label
+
+
+# REMOVE LABEL
+- name: Remove label (check_mode)
+ mso_label: *label_absent
+ check_mode: true
+ register: cm_remove_label
+
+- name: Verify cm_remove_label
+ assert:
+ that:
+ - cm_remove_label is changed
+ - cm_remove_label.current == {}
+
+- name: Remove label (normal mode)
+ mso_label: *label_absent
+ register: nm_remove_label
+
+- name: Verify nm_remove_label
+ assert:
+ that:
+ - nm_remove_label is changed
+ - nm_remove_label.current == {}
+
+- name: Remove label again (check_mode)
+ mso_label: *label_absent
+ check_mode: true
+ register: cm_remove_label_again
+
+- name: Verify cm_remove_label_again
+ assert:
+ that:
+ - cm_remove_label_again is not changed
+ - cm_remove_label_again.current == {}
+
+- name: Remove label again (normal mode)
+ mso_label: *label_absent
+ register: nm_remove_label_again
+
+- name: Verify nm_remove_label_again
+ assert:
+ that:
+ - nm_remove_label_again is not changed
+ - nm_remove_label_again.current == {}
+
+
+# QUERY NON-EXISTING LABEL
+- name: Query non-existing label (check_mode)
+ mso_label:
+ <<: *label_query
+ label: ansible_test
+ check_mode: true
+ register: cm_query_non_label
+
+- name: Query non-existing label (normal mode)
+ mso_label:
+ <<: *label_query
+ label: ansible_test
+ register: nm_query_non_label
+
+# TODO: Implement more tests
+- name: Verify query_non_label
+ assert:
+ that:
+ - cm_query_non_label is not changed
+ - nm_query_non_label is not changed
+ - cm_query_non_label == nm_query_non_label
+
+# add label with login domain
+- name: Add label local domain(normal mode)
+ mso_label: &domain_label_present
+ <<: *mso_info
+ state: present
+ label: ansible_test3
+ login_domain: Local
+ register: label_local_domain
+
+- name: Verify label_local_domain
+ assert:
+ that:
+ - label_local_domain is changed
+ - label_local_domain.current.displayName == 'ansible_test3'
+ - label_local_domain.current.type == 'site'
+
+- name: Add label test domain(normal mode)
+ mso_label:
+ <<: *domain_label_present
+ label: ansible_test4
+ login_domain: '{{ mso_login_domain | default("test") }}'
+ register: label_test_domain
+
+- name: Verify label_test_domain
+ assert:
+ that:
+ - label_test_domain is changed
+ - label_test_domain.current.displayName == 'ansible_test4'
+ - label_test_domain.current.type == 'site'
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/pki/rsa b/ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/pki/rsa
new file mode 100644
index 000000000..1c3cede15
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/pki/rsa
@@ -0,0 +1,39 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIG5QIBAAKCAYEAn/kFOjXlF4NV5aO/V0EQV1Z0Wqnss5fLpQ/fbBHhq98aalpJ
+v9tSnfjlBDOp9n/MvxudqWRQPNMfvQkUyxcN1NbSeiy3QMX+iWjFt/C9q9oij5m4
+c8dk+oJ9qfha65EFVrlUGPc3+ydEIofnsLJbIIwWqNMlPKqjqhC/iMWW7BOkXliR
+Go2GCYLYBdA00W59gI4xkbsc4Q0Br6vTLIX+BzJHXuTl4SFZFFvCPYSjW+j8twhQ
+2NUjaVblOLZtWbEVyb5hlKJ73alS/fndIJKH1I31qGXqSwvnYQwpLx2ufPTMTVBM
+q4l+0qfgIiv6SJVwiMVHxG6MP/fWUATJNE0DFjFGX61qJAqAuoa/PyOM6N01/dF9
+5jvljd5Wafut3JGGxbJ4f8T4fAeEvi3EX+axlgjVtbUjuqeyC03YpS/iMu8A6P34
+/dENbMHu1Xh6UjlTaqREb7KZl/Nsp0+vLFUrFr/8QHhHs19C54WtQsEwD73/pqo5
+Pn1/gTIRux2w23e5AgMBAAECggGAUBjxQxolIMbDxX1dmqSbN/+ztomKWMnST01J
+QuUZJ2NH6KRYdNWt4ibzFE2B9kg7Dh0Xre7qNepH4/CeFqnuZPlC3aVyA96e+dIZ
+3WWOsnNABsKjFmVp6/xWSzps27H7CFc3AmEWCIy6kseVfGVxNzStS86cwGl4FPjZ
+zfORA5c6H3sc/DyMNkrrOs3rBEncUPfhXeRgK1bF112jGJHmhVfpYFwftb9qyMTA
+1uiImsZncoWZZVgiqOW3U9QToGsHgRYhg73hQuPHhmQ869z/O5pkQ2rcP5XpC0aP
+uUGLRQ/U51v5QQ05uAa9a4rejaQr6Sp2UteWr0QkKcZzLAUNstEqOHcRnXedI6py
+Ls5kABZxPJhdMxHtrCUwX0XIKMIiKpGxE+vZMZQIPbD8jJE56nrFkQ3d/CA0E4fw
+LALavs6x6o3f7LMUQpBCWoVQy9D9udVqx+5+TgbHhpnG9W3J957Y2g1NX/xVCIfU
+KzPzmRH/7Bv1FFSWpkR7GrxtlMABAoHBAM0fNBQ0pGUFFn8mQVT1gdpASiqDZNJB
+VVFQy9SaBuWIxJcJycJ/ILOhkAi2qYcqBBtS3WdTH1oplccgkFn0YijxnaE7T1rw
+K29+J9pQXBQsNFWoaOhlzMNU1taXjIEhnDzTJWDPy7XNpKWsWucZH3CM5lWZEmZ4
+6Rr+MCu1BOAdiC7klGr9K91WAYe3mp9IShloWrQSJYLRtEgew+wR/kiDPfwQwR7C
+siIEzWGO3DOsypIJQdJ6S0sKpVBL7jIcAQKBwQDHpvGCPmaDsi6ndXr/MqYDlxVg
+Nu/Nr75xaQzvx3oqiPViet7d8WWN2ZFJd3vI70Xwmoj3gB2okpscgfJGxTZ3xCcN
+ywKsshWLYkt1nAMMMC1OZiN1xL2LLos6R9ioe6R4pXChIIr1hmzycKMFA6HAdt0b
+Bgcr2Odl5V9D5BIAkCbBD089WJfylz/mgqNuVh3oL2ECEJdPbpmjecXzQVqq78z5
+UjNj7qU8bGnVjkU3STr2TBuKdO1h5XFimaYxO7kCgcEAxKii9LBX4OaU4AjcYEkV
+WxuCP+pDknXDB7gwBEA8VnrfCHQA9TGPN8mxXzlJpeY5k7zJutNt3rK5//UPkL8G
+EX09BKTpeyWCb12DdgLPlSOgdXOGSTG4tJm1dH5N3kxMD+DcGEqBY2eq8JAjgyeK
+Bg2AlBazFn3b9942buEZsIl/1H2gckcSdB2OUAFPBGF5cYykUbqILjlB4FdmvgGu
+SvVRS0cA8K33vTffdSZTplOGz6aCbfqED4lAX5C86VwBAoHBAIbcePSejAbXnHYX
+gE7T+pogOxsz4MZSuVTIPinV1+rVetPb5aGMBypLVb2HjUEMh3TgHjb4/o+5ADfA
+e1RcsM8z26GQiSz4Wl89tXUrPk/EV0ZG7hsGG3bhqMBkebBNXKr2Ld9ZKSRyejNF
+7IhdjKyCXhZ7+uoeaShGSRSGAbcJqHPukHsC1hjTHCHsCtNkLm2BW4jWhi7sqbFo
+d1M6yTEALLgZU4dkU48+ODs+D/kpaT+n506ebx8aqn2NBlrpWQKBwQCtCCntlu2U
+p0ZcBjXnliGJpxfEg1w6R/dj3w1Sju0M45UGqIoLFBmNpFSadBWE2JPBxuwUgqf/
+/eJoNUl6aIAr8NGF8EWvu16Hxg0Qx1vJkeBO9/EwoHZ8PmwXFzM2dW+P4yXmaDMI
+Q+rvn7gI1UJk/1MxhfXpEWTxIyPTDhvHDb4iC6OPxt8+qNO/MRHCUyQ4grUOkeyQ
+eSPW4pVejih5Kd+k5uFah8+PyoL7csVrt3RaOZiOCa7qq6L0CRmvXeI=
+-----END RSA PRIVATE KEY-----
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/pki/rsa-passphrase b/ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/pki/rsa-passphrase
new file mode 100644
index 000000000..fce397fb2
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/pki/rsa-passphrase
@@ -0,0 +1,42 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-128-CBC,234BAAD9A19386249918BB2C07874AD1
+
+CUqdVbGGnu9XRtHfdVzZ0SvLGaEgkDTvBk5kNnkkSHXjlJTdpjzccD7K8sWKfSR+
+LpuTf4yGfkAQMFZBvTbrMuFYrkARxNh65i6U8YTp2punE0LzgI59ykOquz9XJKMz
+c4fo4xQLqnEf3+XypwDLCrSaWLcWJ/5GDjDL0B+GexomOhj+07D3QPALtmj/4qWq
+QC3Q3xb8hMQJelyC39Z4I3Gb2VIMxzvZi6rOeLtXd7OdrP5ZWT43XIC8U1Uo+Pp7
+g58XXDS1JmYDbVUqgVE73dvEfM3vtDmS3XpP8+8Wvm2ld5Ky55G+eG3xE2iellLT
+q2THpzDvgQcvxIL+/E+kazCMv44aWuqeo2+BIRa2yKPicc9SXMyDibmvF5MjSiVb
+/FdWd2pYzUl1z2lkhMv23H53o6Yu+3y+aOfyWPSSJHObZ+CNblKrnIP6Vj/oZ6XI
+uwEqe9bwUwakrLUybgthFxw77fLr5k8miQk58sdp7fPl4l+6MRO9by+gXKD6SEmk
+LK5OiqztiMHJrRb79WarCgiqelIXL4jz6dM/vNVZLcgqNLVAmxzMHm/p0bK3W2cU
++LQYeW51d9laQ1QeXY57jIaOMQhkoHqnkUqifrIWIzxdcl+lCtn3+MFZAXVI0tik
+q9LqDVbDoxn0SuF1EELHx6AUFNJU5Rn/56DMYFUlWWXs22uKq7g1ci8NShSX1S2J
+s/HkLFgHWE4IvvWmyyolRG5vh3VgUwdVdU4h+g8nkUaQm1OstbxmcfPOuYfsQpmY
+jlhk/d4WZ3l3Et/Kjb4c25789xMfBgB6KReLapoE6nr7vg9oKfsFXIEiakwWRwHK
+f/O9fiZZr55IDRX7herp3dYJqY5kINanl1s8FoFSoUc9opgY4ENg5FXer4RFLdWI
+ZPAY9DPxXYY+MjGpEXrlA0W/kuTqNkUH3fMrX+0kEO0OCDDRcsUTJ8J//oRx//4O
+UggbCK6DuZJLzlosRoabshkhCPHQxsujk5jeIZCI7Yae+UZCFXqgluXdFcRslFuR
+BP1etL4qh3BFxmKpwq3dSwT3iqEd+kPK2HZXeWmy2E8XJhuL6+gc4ULohAKAjWWm
+HrehCQjrQVG1dIOMuPFAXb2d4uhBZFIEf0yzs9HDNKlW2E0npr0yf7XHuI40E+mP
+V70Wpb49Z1vhmtXp84k6Xu+QG9GPYceZG4jDaQupKunQbgaH1zSglExPLy1wAKIc
+IX2nO8xXanMl6yx87eQtq+/Rvlrv8PyjbbLQlZt6TMKNWJlmH20OG5nA6E+L4sye
+LGzkd/vA0b3jL0tNIXkteDT1oIS/OPW/7smWwNOeKbutdGPCxfuW52agLoxJwac+
+a3Z2G1TE9fIIJE9GnBjTp2IO5AZ/cUNsqkQhrPMb2F7w3lu+wBFV53KoJlkHpryJ
+PHRP+can696UiauwHiq8C1ufcqnaNdktH2Gl1nFN5urDEkOLQ3bT+3Zpr9DJvhS7
+LekjXImMIsvjKgbEb16Hz9ZuQ2BPU1KNHv3KSrxR7f4RIA9KVoSsKd0NsqR47EVz
+TyN1Aci2eu5ngeCNeQ2w8DYzx7ZS0tsL8xBTJa0dm10XmI1N+lLOYMZHeiBmaXXN
+Rw5RRBqk22ulConecg44M78eLvWTxvNE5CQoO/fPRaaKu8f6zU/rig5AEsv+BPOO
+HGEtRzpITF8icS+p8rHCd4YwiU+fJhtl77GA0zMcN/pPFavtW3nmL4jKtnOxq7Js
+m5QNBp4dKllo70jT7/f6IlZD8CQibA1h34M4tn3NyTR7UI/2A0IsbyXEgriw0Pl+
+wKyPJ0vkkL8/0UoJ8Z+0QqrKZbH1a77myjDrLoGzlGzFmesPvmNIhWpPKEqnIJrx
+JvEgxEET4Ry1/5dqovxmE20TMap4EX6LHuORJTqjCFtUn8y0m6+019EmW7Jvo7vs
+uo/xMUXmbkAtQhYnN/bQkfV6L0QxwFAFHGDvC/8AzJdw146cjlZ1ReCWca6uEs1H
+QoKwjIt4B+9WfNjCOLYe3iReDkTL/FW6Tkn15mYGSOql9Zza+sVvvxKYRbnyVh0g
+gaRtundjZVtUPMkIYMSB8XEqdJ2pHJm1g+KJR+QsL21qeoWJ7D2MTqOY9ErDgh/c
+mvrLWyajGVr6SsbwhOVfltCuR2bBeRwC75duqif3dnEWXipUhvTB/SR6u1m1SJ0p
+IJfcPJI1KiRII4L0ihFk2WzXPQA3XbWVVWZ2wN9AqhtV6/e19xP2L1rgpfVwAf/i
+Q/8mv5ACzo0V8UJRWgO4CA1yZqhUqUQHRUm6WjRJrGheQKdU3ZK9ZOAWY+yW/2La
+QvCyfRIP8jW8EDic5m7VXWEE9vVxyLhTtH0BOgZNTfzCYhdhHeLXKehS9qqhmoy2
+-----END RSA PRIVATE KEY-----
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/pki/rsa-passphrase.pub b/ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/pki/rsa-passphrase.pub
new file mode 100644
index 000000000..0c9e393c5
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/pki/rsa-passphrase.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQColnrKInjNCJXLcAiEOLbTTiP9plzevk+VgMa++0vL7flNhbLJYFJMWgnZLYglUb3crJvR5grxn98iVrWcopLskk1p8yz1BR6Hal6Pv5ZFxKe1w487oQk2ShrV/vX9xC/86SHET0zjFrNNg8bjsYg/do+FzBDBuy/V5sQ6S427bK5h4z8/ld3VAbBopIEPLeuVg3z69aFhnMwNCSpbPjvCc2dJk1avUwLQj2Ol0FaWiYtE1ug9RAFVIWPpLYexkJh7l9OZR+BhgmspUq4w4QjPlhCGs85GpbQAo8fdoT+ukN4oOiLfhL62CAuCfaOEfO9//i7Kk7xQDpf0I5/qA2eGjve1AaTPjGRSk4l90tn5bt+655GIz62zUKSJKaT8Cn2gMbtNxXt1qNQqXxn4bnzVQjdfWKZpSIfTdrC7DgmWObWNMJQlhbaN4brvi4Z5mU3A0FukKDRgPCFykKvbVFtVhUWsBv+rBX0QToaCEwIYYvicLPBA2igYcVmx2NJAu0M= akinross@AKINROSS-M-M4WR
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/pki/rsa.pub b/ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/pki/rsa.pub
new file mode 100644
index 000000000..104b840e1
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/pki/rsa.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCf+QU6NeUXg1Xlo79XQRBXVnRaqeyzl8ulD99sEeGr3xpqWkm/21Kd+OUEM6n2f8y/G52pZFA80x+9CRTLFw3U1tJ6LLdAxf6JaMW38L2r2iKPmbhzx2T6gn2p+FrrkQVWuVQY9zf7J0Qih+ewslsgjBao0yU8qqOqEL+IxZbsE6ReWJEajYYJgtgF0DTRbn2AjjGRuxzhDQGvq9Mshf4HMkde5OXhIVkUW8I9hKNb6Py3CFDY1SNpVuU4tm1ZsRXJvmGUonvdqVL9+d0gkofUjfWoZepLC+dhDCkvHa589MxNUEyriX7Sp+AiK/pIlXCIxUfEbow/99ZQBMk0TQMWMUZfrWokCoC6hr8/I4zo3TX90X3mO+WN3lZp+63ckYbFsnh/xPh8B4S+LcRf5rGWCNW1tSO6p7ILTdilL+Iy7wDo/fj90Q1swe7VeHpSOVNqpERvspmX82ynT68sVSsWv/xAeEezX0Lnha1CwTAPvf+mqjk+fX+BMhG7HbDbd7k= akinross@AKINROSS-M-M4WR
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/tasks/main.yml
new file mode 100644
index 000000000..aa543f959
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_remote_location/tasks/main.yml
@@ -0,0 +1,303 @@
+# Test code for the MSO modules
+# Copyright: (c) 2022, Akini Ross (@akinross) <akinross@cisco.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+# CLEAN ENVIRONMENT
+
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query all backups
+ cisco.mso.mso_backup:
+ <<: *mso_info
+ state: query
+ delegate_to: localhost
+ register: backups
+
+- name: Ensure all backups with link to remote location ansible_test are removed
+ cisco.mso.mso_backup:
+ <<: *mso_info
+ backup_id: "{{ item.id }}"
+ state: absent
+ when:
+ - item.location is defined
+ - item.location.locationType is defined
+ - item.location.locationType == "remote"
+ loop: "{{ backups.current | sort(attribute='name', reverse=True) }}"
+ delegate_to: localhost
+
+- name: Ensure remote location ansible_test is removed
+ cisco.mso.mso_remote_location:
+ <<: *mso_info
+ remote_location: ansible_test
+ state: absent
+ delegate_to: localhost
+
+- name: Configure remote location scp (check mode)
+ cisco.mso.mso_remote_location: &remote_location
+ <<: *mso_info
+ remote_location: ansible_test
+ remote_protocol: scp
+ remote_host: '{{ mso_remote_location }}'
+ remote_path: '{{ mso_remote_location_path | default("/tmp") }}'
+ authentication_type: password
+ remote_username: '{{ mso_remote_location_user | default(mso_username) }}'
+ remote_password: '{{ mso_remote_location_password | default(mso_password) }}'
+ state: present
+ check_mode: true
+ delegate_to: localhost
+ register: cm_config_remote
+
+- name: Configure remote location scp
+ cisco.mso.mso_remote_location:
+ <<: *remote_location
+ delegate_to: localhost
+ register: nm_config_remote
+
+- name: Verify configuration
+ assert:
+ that:
+ - cm_config_remote is changed
+ - cm_config_remote.current.name == "ansible_test"
+ - cm_config_remote.current.credential.authType == "password"
+ - cm_config_remote.current.credential.port == 22
+ - cm_config_remote.current.credential.protocolType == "scp"
+ - cm_config_remote.current.credential.hostname == '{{ mso_remote_location }}'
+ - cm_config_remote.current.credential.remotePath == '/tmp'
+ - cm_config_remote.current.credential.username == '{{ mso_remote_location_user | default(mso_username) }}'
+ - nm_config_remote is changed
+ - nm_config_remote.current.name == "ansible_test"
+ - nm_config_remote.current.credential.authType == "password"
+ - nm_config_remote.current.credential.port == 22
+ - nm_config_remote.current.credential.protocolType == "scp"
+ - nm_config_remote.current.credential.hostname == '{{ mso_remote_location }}'
+ - nm_config_remote.current.credential.remotePath == '/tmp'
+ - nm_config_remote.current.credential.username == '{{ mso_remote_location_user | default(mso_username) }}'
+ - nm_config_remote.current.id is defined
+
+- name: Configure remote location again
+ cisco.mso.mso_remote_location:
+ <<: *remote_location
+ delegate_to: localhost
+ register: nm_config_remote_again
+
+- name: Verify configuration after again
+ assert:
+ that:
+ - nm_config_remote_again is not changed
+
+- name: Change remote location description (check mode)
+ cisco.mso.mso_remote_location:
+ <<: *remote_location
+ description: changed_description
+ check_mode: true
+ delegate_to: localhost
+ register: cm_change_config_remote_description
+
+- name: Change remote location description
+ cisco.mso.mso_remote_location:
+ <<: *remote_location
+ description: changed_description
+ delegate_to: localhost
+ register: nm_change_config_remote_description
+
+- name: Verify configuration change
+ assert:
+ that:
+ - cm_change_config_remote_description is changed
+ - cm_change_config_remote_description.current.description == "changed_description"
+ - nm_change_config_remote_description is changed
+ - nm_change_config_remote_description.current.description == "changed_description"
+
+- name: Query remote location
+ cisco.mso.mso_remote_location:
+ <<: *remote_location
+ state: query
+ delegate_to: localhost
+ register: nm_query_remote
+
+- name: Query all remote locations
+ cisco.mso.mso_remote_location:
+ <<: *mso_info
+ state: query
+ delegate_to: localhost
+ register: nm_query_all_remotes
+
+- name: Query non existing remote location
+ cisco.mso.mso_remote_location:
+ <<: *mso_info
+ remote_location: non_existing
+ state: query
+ ignore_errors: true
+ delegate_to: localhost
+ register: nm_query_non_existing
+
+- name: Verify queries
+ assert:
+ that:
+ - nm_query_remote is not changed
+ - nm_query_remote.current | type_debug == "dict"
+ - nm_query_all_remotes is not changed
+ - nm_query_all_remotes.current | type_debug == "list"
+ - nm_query_non_existing is not changed
+ - 'nm_query_non_existing.msg == "Remote location non_existing not found. Remote locations configured: ansible_test"'
+
+- name: Remove remote location (check mode)
+ cisco.mso.mso_remote_location:
+ <<: *remote_location
+ state: absent
+ check_mode: true
+ delegate_to: localhost
+ register: cm_delete_config_remote
+
+- name: Remove remote location
+ cisco.mso.mso_remote_location:
+ <<: *remote_location
+ state: absent
+ delegate_to: localhost
+ register: nm_delete_config_remote
+
+- name: Verify delete
+ assert:
+ that:
+ - cm_delete_config_remote is changed
+ - cm_delete_config_remote.current == {}
+ - nm_delete_config_remote is changed
+ - nm_delete_config_remote.current == {}
+
+- name: Create remote location different path directory if it does not exist
+ ansible.builtin.file:
+ path: '{{ mso_remote_location_alternate_path | default("/home/"~mso_username) }}'
+ state: directory
+ mode: '0755'
+
+- name: Configure remote location different path (check mode)
+ cisco.mso.mso_remote_location:
+ <<: *remote_location
+ remote_path: '{{ mso_remote_location_alternate_path | default("/home/"~mso_username) }}'
+ check_mode: true
+ delegate_to: localhost
+ register: cm_config_remote_different_path
+
+- name: Configure remote location different path
+ cisco.mso.mso_remote_location:
+ <<: *remote_location
+ remote_path: '{{ mso_remote_location_alternate_path | default("/home/"~mso_username) }}'
+ delegate_to: localhost
+ register: nm_config_remote_different_path
+
+- name: Verify configuration different path
+ assert:
+ that:
+ - cm_config_remote_different_path is changed
+ - cm_config_remote_different_path.current.credential.remotePath == '{{ mso_remote_location_alternate_path | default("/home/"~mso_username) }}'
+ - nm_config_remote_different_path is changed
+ - nm_config_remote_different_path.current.credential.remotePath == '{{ mso_remote_location_alternate_path | default("/home/"~mso_username) }}'
+
+- name: Remove remote location
+ cisco.mso.mso_remote_location:
+ <<: *remote_location
+ state: absent
+ delegate_to: localhost
+
+- name: Configure remote location sftp (check mode)
+ cisco.mso.mso_remote_location:
+ <<: *remote_location
+ remote_protocol: sftp
+ check_mode: true
+ delegate_to: localhost
+ register: cm_config_remote_sftp
+
+- name: Configure remote location sftp
+ cisco.mso.mso_remote_location:
+ <<: *remote_location
+ remote_protocol: sftp
+ delegate_to: localhost
+ register: nm_config_remote_sftp
+
+- name: Verify configuration sftp
+ assert:
+ that:
+ - cm_config_remote_sftp is changed
+ - cm_config_remote_sftp.current.credential.protocolType == "sftp"
+ - nm_config_remote_sftp is changed
+ - nm_config_remote_sftp.current.credential.protocolType == "sftp"
+
+- name: Remove remote location
+ cisco.mso.mso_remote_location:
+ <<: *remote_location
+ state: absent
+ delegate_to: localhost
+
+- name: Configure remote location ssh (check mode)
+ cisco.mso.mso_remote_location: &remote_location_ssh
+ <<: *remote_location
+ authentication_type: ssh
+ remote_ssh_key: "{{ lookup('file', 'pki/rsa') }}"
+ check_mode: true
+ delegate_to: localhost
+ register: cm_config_remote_ssh
+
+- name: Configure remote location ssh
+ cisco.mso.mso_remote_location:
+ <<: *remote_location_ssh
+ delegate_to: localhost
+ register: nm_config_remote_ssh
+
+- name: Verify configuration ssh
+ assert:
+ that:
+ - cm_config_remote_ssh is changed
+ - cm_config_remote_ssh.current.credential.authType == "sshKey"
+ - nm_config_remote_ssh is changed
+ - nm_config_remote_ssh.current.credential.authType == "sshKey"
+
+- name: Remove remote location
+ cisco.mso.mso_remote_location:
+ <<: *remote_location_ssh
+ state: absent
+ delegate_to: localhost
+
+- name: Configure remote location ssh with passphrase (check mode)
+ cisco.mso.mso_remote_location: &remote_location_ssh_pass
+ <<: *remote_location
+ authentication_type: ssh
+ remote_ssh_key: "{{ lookup('file', 'pki/rsa-passphrase') }}"
+ remote_ssh_passphrase: '{{ mso_output_level | default("ansible") }}'
+ check_mode: true
+ delegate_to: localhost
+ register: cm_config_remote_ssh_pass
+
+- name: Configure remote location ssh with passphrase
+ cisco.mso.mso_remote_location:
+ <<: *remote_location_ssh_pass
+ delegate_to: localhost
+ register: nm_config_remote_ssh_pass
+
+- name: Verify configuration ssh
+ assert:
+ that:
+ - cm_config_remote_ssh_pass is changed
+ - cm_config_remote_ssh_pass.current.credential.authType == "sshKey"
+ - nm_config_remote_ssh_pass is changed
+ - nm_config_remote_ssh_pass.current.credential.authType == "sshKey"
+
+- name: Remove remote location
+ cisco.mso.mso_remote_location:
+ <<: *remote_location_ssh_pass
+ state: absent
+ delegate_to: localhost \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/error_handling.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/error_handling.yml
new file mode 100644
index 000000000..63a241918
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/error_handling.yml
@@ -0,0 +1,153 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+#
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+
+# SET VARs
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+# PROVOKE ERRORS
+- name: Error when required parameter is missing
+ cisco.mso.mso_rest:
+ <<: *mso_info
+ output_level: debug
+ method: post
+ content:
+ displayName: mso_tenant
+ name: mso_tenant
+ description: MSO tenant
+ siteAssociations: []
+ userAssociations: []
+ _updateVersion: 0
+ ignore_errors: true
+ register: error_on_missing_required_param
+
+- name: Verify error_on_missing_required_param
+ assert:
+ that:
+ - error_on_missing_required_param is failed
+ - 'error_on_missing_required_param.msg == "missing required arguments: path"'
+
+- name: Error on name resolution
+ cisco.mso.mso_rest:
+ host: foo.bar.cisco.com
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+ path: /mso/api/v1/tenants
+ method: post
+ content:
+ fvFoobar:
+ displayName: mso_tenant
+ name: mso_tenant
+ description: This is description
+ siteAssociations: []
+ userAssociations: []
+ _updateVersion: 0
+ ignore_errors: true
+ register: error_on_name_resolution
+
+- name: Verify error_on_name_resolution
+ assert:
+ that:
+ - error_on_name_resolution is failed
+
+- name: Verify error_on_name_resolution
+ assert:
+ that:
+ - error_on_name_resolution.msg is search("Name or service not known")
+ when:
+ - version.current.version is version('3.7', '>=')
+
+- name: Error on invalid path
+ mso_rest:
+ <<: *mso_info
+ path: /mso/api/v1/tenant
+ method: post
+ content:
+ displayName: mso_tenant
+ name: mso_tenant
+ description: MSO tenant
+ siteAssociations: []
+ userAssociations: []
+ _updateVersion: 0
+ ignore_errors: true
+ register: error_on_invalid_path
+
+- name: Verify error_on_invalid_path
+ assert:
+ that:
+ - error_on_invalid_path is failed
+ - error_on_invalid_path.status == 404
+ when: version.current.version is version('3.0.0a', '<') or version.current.version is version('3.2', '>=')
+
+- name: Verify error_on_invalid_path
+ assert:
+ that:
+ - error_on_invalid_path is failed
+ - error_on_invalid_path.status == 405
+ when:
+ - version.current.version is version('3.0.0a', '>=')
+ - version.current.version is version('3.2', '<')
+
+- name: Error when attributes are missing
+ cisco.mso.mso_rest:
+ <<: *mso_info
+ path: /mso/api/v1/tenants
+ method: post
+ content:
+ children:
+ ignore_errors: true
+ register: error_on_missing_attributes
+
+- name: Verify error_on_missing_attributes
+ assert:
+ that:
+ - error_on_missing_attributes is failed
+ - error_on_missing_attributes.status == 400
+
+- name: Error when input does not validate
+ cisco.mso.mso_rest:
+ <<: *mso_info
+ path: /mso/api/v1/tenants
+ method: post
+ content:
+ displayName: 0
+ name: 0
+ descr: This is an [invalid] description
+ siteAssociations: []
+ userAssociations: []
+ _updateVersion: 0
+ ignore_errors: true
+ register: error_on_input_validation
+
+- name: Verify error_on_input_validation
+ assert:
+ that:
+ - error_on_input_validation is failed
+ - error_on_input_validation.status == 400
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/json_inline.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/json_inline.yml
new file mode 100644
index 000000000..252733c29
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/json_inline.yml
@@ -0,0 +1,289 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+#
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Remove EXT_EPGs Providers from DHCP Relay Policy
+ mso_dhcp_relay_policy_provider:
+ <<: *mso_info
+ dhcp_relay_policy: ansible_dhcp_relay_1
+ tenant: ansible_test
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_endpoint_group: "{{ item }}"
+ state: absent
+ ignore_errors: true
+ loop:
+ - EXT_EPG_1
+ - EXT_EPG_2
+
+- name: Remove EXT_EPGs Providers from DHCP Relay Policy
+ mso_dhcp_relay_policy_provider:
+ <<: *mso_info
+ dhcp_relay_policy: ansible_dhcp_relay_1
+ tenant: ansible_test
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ endpoint_group: "{{ item }}"
+ application_profile: "ANP_1"
+ state: absent
+ ignore_errors: true
+ loop:
+ - EPG_1
+ - EPG_2
+
+- name: Stop consuming DHCP Policy
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ bd: CLIENT_BD
+ vrf:
+ name: VRF1
+ state: absent
+ ignore_errors: true
+
+- name: Remove DHCP Relay Policies
+ mso_dhcp_relay_policy:
+ <<: *mso_info
+ dhcp_relay_policy: '{{ item }}'
+ state: absent
+ ignore_errors: true
+ loop:
+ - ansible_dhcp_relay_1
+ - ansible_dhcp_relay_2
+
+- name: Remove DHCP Option Policies
+ mso_dhcp_option_policy:
+ <<: *mso_info
+ dhcp_option_policy: '{{ item }}'
+ state: absent
+ ignore_errors: true
+ loop:
+ - ansible_dhcp_option_1
+ - ansible_dhcp_option_2
+
+- name: Remove schemas
+ cisco.mso.mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ ignore_errors: true
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+ - 'aws_{{ mso_site | default("ansible_test") }}'
+ - 'azure_{{ mso_site | default("ansible_test") }}'
+
+- name: Remove tenant ansible_test
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ state: absent
+ ignore_errors: true
+
+# QUERY SCHEMAS
+- name: Query schema
+ mso_rest:
+ <<: *mso_info
+ path: /mso/api/v1/schemas
+ method: get
+ register: query_all_schema
+
+- name: Verify query_all_schema in json_inline
+ assert:
+ that:
+ - query_all_schema is not changed
+
+# QUERY A USER
+- name: Query our user
+ mso_user:
+ <<: *mso_info
+ state: query
+ user: '{{ mso_username }}'
+ check_mode: true
+ register: query_user_id
+
+- name: Verify query_user_id
+ assert:
+ that:
+ - query_user_id is not changed
+ - query_user_id.current.username == '{{ mso_username }}'
+
+# ADD tenant
+- name: Add tenant
+ mso_rest:
+ <<: *mso_info
+ path: /api/v1/tenants
+ method: post
+ content:
+ {
+ "displayName": "ansible_test",
+ "name": "ansible_test",
+ "description": "",
+ "siteAssociations": [],
+ "userAssociations": [{
+ "userId": "{{ query_user_id.current.id }}"
+ }],
+ "_updateVersion": 0,
+ }
+ register: add_tenant
+
+- name: Verify add_tenant in json_inline
+ assert:
+ that:
+ - add_tenant is changed
+ - add_tenant.jsondata.displayName == 'ansible_test'
+
+# ADD schema
+- name: Add schema
+ mso_rest:
+ <<: *mso_info
+ path: /mso/api/v1/schemas
+ method: post
+ content:
+ {
+ "displayName": "{{ mso_schema | default('ansible_test') }}",
+ "templates": [{
+ "name": "Template_1",
+ "tenantId": "{{ add_tenant.jsondata.id }}",
+ "displayName": "Template_1",
+ "templateSubType": [],
+ "templateType": "stretched-template",
+ "anps": [],
+ "contracts": [],
+ "vrfs": [],
+ "bds": [],
+ "filters": [],
+ "externalEpgs": [],
+ "serviceGraphs": [],
+ "intersiteL3outs": []
+ }],
+ "sites": [],
+ "_updateVersion": 0
+ }
+ register: add_schema
+
+- name: Verify add_schema in json_inline
+ assert:
+ that:
+ - add_schema is changed
+ - add_schema.jsondata.displayName == 'ansible_test'
+
+# PUT schema
+- name: Put schema
+ mso_rest:
+ <<: *mso_info
+ port: 443
+ path: "/mso/api/v1/schemas/{{ add_schema.jsondata.id }}"
+ method: put
+ content:
+ {
+ "displayName": "ansible_test_2",
+ "templates": [{
+ "name": "Template_1",
+ "tenantId": "{{ add_tenant.jsondata.id }}",
+ "displayName": "Template_1",
+ "templateSubType": [],
+ "templateType": "stretched-template",
+ "anps": [],
+ "contracts": [],
+ "vrfs": [],
+ "bds": [],
+ "filters": [],
+ "externalEpgs": [],
+ "serviceGraphs": [],
+ "intersiteL3outs": []
+ }],
+ "sites": [],
+ "_updateVersion": 0
+ }
+ register: put_schema
+
+- name: Verify put_schema in json_inline
+ assert:
+ that:
+ - put_schema is changed
+ - put_schema.jsondata.displayName == 'ansible_test_2'
+
+# PATCH schema
+- name: Patch schema
+ mso_rest:
+ <<: *mso_info
+ path: "/mso/api/v1/schemas/{{ add_schema.jsondata.id }}"
+ method: patch
+ content:
+ [
+ {
+ "op": "add",
+ "path": "/templates/Template_1/anps/-",
+ "value": { "name": "AP2", "displayName": "AP2", "epgs": [] },
+ "_updateVersion": 0
+ }
+ ]
+ register: patch_schema
+
+- name: Verify patch_schema in json_inline
+ assert:
+ that:
+ - patch_schema is changed
+
+- name: Verify patch_schema in json_inline
+ assert:
+ that:
+ - patch_schema.jsondata.templates[0].anps[0].displayName == 'AP2'
+ # MSO 3.3 PATCH does not return anything anymore.
+ when: version.current.version is version('3.3', '<')
+
+# DELETE the schema
+- name: Delete the schema
+ mso_rest:
+ <<: *mso_info
+ path: "/mso/api/v1/schemas/{{ add_schema.jsondata.id }}"
+ method: delete
+ register: delete_schema
+
+- name: Verify delete_schema in json_inline
+ assert:
+ that:
+ - delete_schema is changed
+ - delete_schema.jsondata == None
+
+# DELETE TENANT
+- name: Delete the tenant
+ mso_rest:
+ <<: *mso_info
+ path: "/mso/api/v1/tenants/{{ add_tenant.jsondata.id }}"
+ method: delete
+ register: delete_tenant
+
+- name: Verify delete_tenant in json_inline
+ assert:
+ that:
+ - delete_tenant is changed
+ - delete_tenant.jsondata == None \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/json_string.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/json_string.yml
new file mode 100644
index 000000000..9a9df1ea7
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/json_string.yml
@@ -0,0 +1,224 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+#
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Remove schemas
+ cisco.mso.mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Remove tenant ansible_test
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ state: absent
+
+# QUERY SCHEMAS
+- name: Query schema
+ mso_rest:
+ <<: *mso_info
+ path: /mso/api/v1/schemas
+ method: get
+ register: query_all_schema
+
+- name: Verify query_all_schema
+ assert:
+ that:
+ - query_all_schema is not changed
+
+# QUERY A USER
+- name: Query our user
+ mso_user:
+ <<: *mso_info
+ state: query
+ user: '{{ mso_username }}'
+ check_mode: true
+ register: query_user_id
+
+- name: Verify query_user_id
+ assert:
+ that:
+ - query_user_id is not changed
+ - query_user_id.current.username == '{{ mso_username }}'
+
+# ADD tenant
+- name: Add tenant
+ mso_rest:
+ <<: *mso_info
+ path: /api/v1/tenants
+ method: post
+ content:
+ {
+ "displayName": "ansible_test",
+ "name": "ansible_test",
+ "description": "",
+ "siteAssociations": [],
+ "userAssociations": [{
+ "userId": "{{ query_user_id.current.id }}"
+ }],
+ "_updateVersion": 0,
+ }
+ register: add_tenant
+
+- name: Verify add_tenant in json_string
+ assert:
+ that:
+ - add_tenant is changed
+ - add_tenant.jsondata.displayName == 'ansible_test'
+
+# ADD schema
+- name: Add schema
+ mso_rest:
+ <<: *mso_info
+ path: /mso/api/v1/schemas
+ method: post
+ content: |
+ {
+ "displayName": "{{ mso_schema | default('ansible_test') }}",
+ "templates": [{
+ "name": "Template_1",
+ "tenantId": "{{ add_tenant.jsondata.id }}",
+ "displayName": "Template_1",
+ "templateSubType": [],
+ "templateType": "stretched-template",
+ "anps": [],
+ "contracts": [],
+ "vrfs": [],
+ "bds": [],
+ "filters": [],
+ "externalEpgs": [],
+ "serviceGraphs": [],
+ "intersiteL3outs": []
+ }],
+ "sites": [],
+ "_updateVersion": 0
+ }
+ register: add_schema
+
+- name: Verify add_schema in json_string
+ assert:
+ that:
+ - add_schema is changed
+ - add_schema.jsondata.displayName == 'ansible_test'
+
+# PUT schema
+- name: Put schema
+ mso_rest:
+ <<: *mso_info
+ path: "/mso/api/v1/schemas/{{ add_schema.jsondata.id }}"
+ method: put
+ content: |
+ {
+ "displayName": "ansible_test_2",
+ "templates": [{
+ "name": "Template_1",
+ "tenantId": "{{ add_tenant.jsondata.id }}",
+ "displayName": "Template_1",
+ "templateSubType": [],
+ "templateType": "stretched-template",
+ "anps": [],
+ "contracts": [],
+ "vrfs": [],
+ "bds": [],
+ "filters": [],
+ "externalEpgs": [],
+ "serviceGraphs": [],
+ "intersiteL3outs": []
+ }],
+ "sites": [],
+ "_updateVersion": 0
+ }
+ register: put_schema
+
+- name: Verify put_schema in json_string
+ assert:
+ that:
+ - put_schema is changed
+ - put_schema.jsondata.displayName == 'ansible_test_2'
+
+# PATCH schema
+- name: Patch schema
+ mso_rest:
+ <<: *mso_info
+ path: "/mso/api/v1/schemas/{{ add_schema.jsondata.id }}"
+ method: patch
+ content: |
+ [
+ {
+ "op": "add",
+ "path": "/templates/Template_1/anps/-",
+ "value": { "name": "AP2", "displayName": "AP2", "epgs": [] },
+ "_updateVersion": 0
+ }
+ ]
+ register: patch_schema
+
+- name: Verify patch_schema in json_string
+ assert:
+ that:
+ - patch_schema is changed
+
+- name: Verify patch_schema in json_string
+ assert:
+ that:
+ - patch_schema.jsondata.templates[0].anps[0].displayName == 'AP2'
+ # MSO 3.3 PATCH does not return anything anymore.
+ when: version.current.version is version('3.3', '<')
+
+# DELETE the schema
+- name: Delete the schema
+ mso_rest:
+ <<: *mso_info
+ path: "/mso/api/v1/schemas/{{ add_schema.jsondata.id }}"
+ method: delete
+ register: delete_schema
+
+- name: Verify delete_schema in json_string
+ assert:
+ that:
+ - delete_schema is changed
+ - delete_schema.jsondata == None
+
+# DELETE TENANT
+- name: Delete the tenant
+ mso_rest:
+ <<: *mso_info
+ path: "/mso/api/v1/tenants/{{ add_tenant.jsondata.id }}"
+ method: delete
+ register: delete_tenant
+
+- name: Verify delete_tenant in json_string
+ assert:
+ that:
+ - delete_tenant is changed
+ - delete_tenant.jsondata == None \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/json_template.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/json_template.yml
new file mode 100644
index 000000000..622b3d21d
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/json_template.yml
@@ -0,0 +1,67 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+#
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Remove schemas
+ cisco.mso.mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_1'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Remove tenant ansible_test
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ state: absent
+
+# QUERY A USER
+- name: Query our user
+ mso_user:
+ <<: *mso_info
+ state: query
+ user: '{{ mso_username }}'
+ check_mode: true
+ register: query_user_id
+
+- name: Verify query_user_id
+ assert:
+ that:
+ - query_user_id is not changed
+ - query_user_id.current.username == '{{ mso_username }}'
+
+- name: Add a tenant from a templated payload file from templates
+ mso_rest:
+ <<: *mso_info
+ path: /api/v1/tenants
+ method: post
+ content: "{{ lookup('template', 'tenant.json.j2') }}"
+ register: add_tenant
+
+- name: Verify add_tenant in json_string
+ assert:
+ that:
+ - add_tenant is changed
+ - add_tenant.jsondata.displayName == 'ansible_test' \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/main.yml
new file mode 100644
index 000000000..22851bf72
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/main.yml
@@ -0,0 +1,28 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+#
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+- include_tasks: json_inline.yml
+ tags: json_inline
+
+- include_tasks: json_string.yml
+ tags: json_string
+
+- include_tasks: json_template.yml
+ tags: json_template
+
+- include_tasks: yaml_inline.yml
+ tags: yaml_inline
+
+- include_tasks: yaml_string.yml
+ tags: yaml_string
+
+- include_tasks: error_handling.yml
+ tags: error_handling
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/tenant.json.j2 b/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/tenant.json.j2
new file mode 100644
index 000000000..2d91ee76c
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/tenant.json.j2
@@ -0,0 +1,10 @@
+{
+ "displayName": "ansible_test",
+ "name": "ansible_test",
+ "description": "",
+ "siteAssociations": [],
+ "userAssociations": [{
+ "userId": "{{ query_user_id.current.id }}"
+ }],
+ "_updateVersion": 0,
+} \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/yaml_inline.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/yaml_inline.yml
new file mode 100644
index 000000000..1fe44f283
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/yaml_inline.yml
@@ -0,0 +1,214 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+#
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Remove schemas
+ cisco.mso.mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Remove tenant ansible_test
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ state: absent
+
+# QUERY SCHEMAS
+- name: Query schema
+ mso_rest:
+ <<: *mso_info
+ path: /mso/api/v1/schemas
+ method: get
+ register: query_all_schema
+
+- name: Verify query_all_schema
+ assert:
+ that:
+ - query_all_schema is not changed
+
+# QUERY A USER
+- name: Query our user
+ mso_user:
+ <<: *mso_info
+ state: query
+ user: '{{ mso_username }}'
+ check_mode: true
+ register: query_user_id
+
+- name: Verify query_user_id
+ assert:
+ that:
+ - query_user_id is not changed
+ - query_user_id.current.username == '{{ mso_username }}'
+
+# ADD tenant
+- name: Add tenant
+ mso_rest:
+ <<: *mso_info
+ path: /mso/api/v1/tenants
+ method: post
+ content:
+ displayName: ansible_test
+ name: ansible_test
+ description: MSO tenant
+ siteAssociations: []
+ userAssociations:
+ - userId: '{{ query_user_id.current.id }}'
+ _updateVersion: 0
+ register: add_tenant
+
+- name: Verify add_tenant in yaml_inline
+ assert:
+ that:
+ - add_tenant is changed
+ - add_tenant.jsondata.displayName == 'ansible_test'
+
+# ADD schema
+- name: Add schema
+ mso_rest:
+ <<: *mso_info
+ path: /mso/api/v1/schemas
+ method: post
+ content:
+ displayName: '{{ mso_schema | default("ansible_test") }}'
+ templates:
+ - name: Template_1
+ tenantId: '{{ add_tenant.jsondata.id }}'
+ displayName: Template_1
+ templateSubType: []
+ templateType: stretched-template
+ anps: []
+ contracts: []
+ vrfs: []
+ bds: []
+ filters: []
+ externalEpgs: []
+ serviceGraphs: []
+ intersiteL3outs: []
+ sites: []
+ _updateVersion: 0
+ register: add_schema
+
+- name: Verify add_schema in yaml_inline
+ assert:
+ that:
+ - add_schema is changed
+ - add_schema.jsondata.displayName == 'ansible_test'
+
+# PUT schema
+- name: Put schema
+ mso_rest:
+ <<: *mso_info
+ path: "/mso/api/v1/schemas/{{ add_schema.jsondata.id }}"
+ method: put
+ content:
+ displayName: ansible_test_2
+ templates:
+ - name: Template_1
+ tenantId: '{{ add_tenant.jsondata.id }}'
+ displayName: Template_1
+ templateSubType: []
+ templateType: stretched-template
+ anps: []
+ contracts: []
+ vrfs: []
+ bds: []
+ filters: []
+ externalEpgs: []
+ serviceGraphs: []
+ intersiteL3outs: []
+ sites: []
+ _updateVersion: 0
+ register: put_schema
+
+- name: Verify put_schema in yaml_inline
+ assert:
+ that:
+ - put_schema is changed
+ - put_schema.jsondata.displayName == 'ansible_test_2'
+
+# PATCH schema
+- name: Patch schema
+ mso_rest:
+ <<: *mso_info
+ path: "/mso/api/v1/schemas/{{ add_schema.jsondata.id }}"
+ method: patch
+ content:
+ - op: add
+ path: /templates/Template_1/anps/-
+ value:
+ name: AP2
+ displayName: AP2
+ epgs: []
+ _updateVersion: 0
+ register: patch_schema
+
+- name: Verify patch_schema in yaml_inline
+ assert:
+ that:
+ - patch_schema is changed
+
+- name: Verify patch_schema in yaml_inline
+ assert:
+ that:
+ - patch_schema.jsondata.templates[0].anps[0].displayName == 'AP2'
+ # MSO 3.3 PATCH does not return anything anymore.
+ when: version.current.version is version('3.3', '<')
+
+# DELETE the schema
+- name: Delete the schema
+ mso_rest:
+ <<: *mso_info
+ path: "/mso/api/v1/schemas/{{ add_schema.jsondata.id }}"
+ method: delete
+ register: delete_schema
+
+- name: Verify delete_schema in yaml_inline
+ assert:
+ that:
+ - delete_schema is changed
+ - delete_schema.jsondata == None
+
+# DELETE TENANT
+- name: Delete the tenant
+ mso_rest:
+ <<: *mso_info
+ path: "/mso/api/v1/tenants/{{ add_tenant.jsondata.id }}"
+ method: delete
+ register: delete_tenant
+
+- name: Verify delete_tenant in yaml_inline
+ assert:
+ that:
+ - delete_tenant is changed
+ - delete_tenant.jsondata == None \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/yaml_string.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/yaml_string.yml
new file mode 100644
index 000000000..5d9cfb05e
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_rest/tasks/yaml_string.yml
@@ -0,0 +1,214 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+#
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Remove schemas
+ cisco.mso.mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Remove tenant ansible_test
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ state: absent
+
+# QUERY SCHEMAS
+- name: Query schema
+ mso_rest:
+ <<: *mso_info
+ path: /mso/api/v1/schemas
+ method: get
+ register: query_all_schema
+
+- name: Verify query_all_schema
+ assert:
+ that:
+ - query_all_schema is not changed
+
+# QUERY A USER
+- name: Query our user
+ mso_user:
+ <<: *mso_info
+ state: query
+ user: '{{ mso_username }}'
+ check_mode: true
+ register: query_user_id
+
+- name: Verify query_user_id
+ assert:
+ that:
+ - query_user_id is not changed
+ - query_user_id.current.username == '{{ mso_username }}'
+
+# ADD tenant
+- name: Add tenant
+ mso_rest:
+ <<: *mso_info
+ path: /mso/api/v1/tenants
+ method: post
+ content:
+ displayName: ansible_test
+ name: ansible_test
+ description: MSO tenant
+ siteAssociations: []
+ userAssociations:
+ - userId: '{{ query_user_id.current.id }}'
+ _updateVersion: 0
+ register: add_tenant
+
+- name: Verify add_tenant in yaml_string
+ assert:
+ that:
+ - add_tenant is changed
+ - add_tenant.jsondata.displayName == 'ansible_test'
+
+# ADD schema
+- name: Add schema
+ mso_rest:
+ <<: *mso_info
+ path: /mso/api/v1/schemas
+ method: post
+ content:
+ displayName: '{{ mso_schema | default("ansible_test") }}'
+ templates:
+ - name: Template_1
+ tenantId: '{{ add_tenant.jsondata.id }}'
+ displayName: Template_1
+ templateSubType: []
+ templateType: stretched-template
+ anps: []
+ contracts: []
+ vrfs: []
+ bds: []
+ filters: []
+ externalEpgs: []
+ serviceGraphs: []
+ intersiteL3outs: []
+ sites: []
+ _updateVersion: 0
+ register: add_schema
+
+- name: Verify add_schema in yaml_string
+ assert:
+ that:
+ - add_schema is changed
+ - add_schema.jsondata.displayName == 'ansible_test'
+
+# PUT schema
+- name: Put schema
+ mso_rest:
+ <<: *mso_info
+ path: "/mso/api/v1/schemas/{{ add_schema.jsondata.id }}"
+ method: put
+ content:
+ displayName: ansible_test_2
+ templates:
+ - name: Template_1
+ tenantId: '{{ add_tenant.jsondata.id }}'
+ displayName: Template_1
+ templateSubType: []
+ templateType: stretched-template
+ anps: []
+ contracts: []
+ vrfs: []
+ bds: []
+ filters: []
+ externalEpgs: []
+ serviceGraphs: []
+ intersiteL3outs: []
+ sites: []
+ _updateVersion: 0
+ register: put_schema
+
+- name: Verify put_schema in yaml_string
+ assert:
+ that:
+ - put_schema is changed
+ - put_schema.jsondata.displayName == 'ansible_test_2'
+
+# PATCH schema
+- name: Patch schema
+ mso_rest:
+ <<: *mso_info
+ path: "/mso/api/v1/schemas/{{ add_schema.jsondata.id }}"
+ method: patch
+ content:
+ - op: add
+ path: /templates/Template_1/anps/-
+ value:
+ name: AP2
+ displayName: AP2
+ epgs: []
+ _updateVersion: 0
+ register: patch_schema
+
+- name: Verify patch_schema in yaml_string
+ assert:
+ that:
+ - patch_schema is changed
+
+- name: Verify patch_schema in yaml_string
+ assert:
+ that:
+ - patch_schema.jsondata.templates[0].anps[0].displayName == 'AP2'
+ # MSO 3.3 PATCH does not return anything anymore.
+ when: version.current.version is version('3.3', '<')
+
+# DELETE the schema
+- name: Delete the schema
+ mso_rest:
+ <<: *mso_info
+ path: "/mso/api/v1/schemas/{{ add_schema.jsondata.id }}"
+ method: delete
+ register: delete_schema
+
+- name: Verify delete_schema in yaml_string
+ assert:
+ that:
+ - delete_schema is changed
+ - delete_schema.jsondata == None
+
+# DELETE TENANT
+- name: Delete the tenant
+ mso_rest:
+ <<: *mso_info
+ path: "/mso/api/v1/tenants/{{ add_tenant.jsondata.id }}"
+ method: delete
+ register: delete_tenant
+
+- name: Verify delete_tenant in yaml_string
+ assert:
+ that:
+ - delete_tenant is changed
+ - delete_tenant.jsondata == None \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_role/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_role/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_role/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_role/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_role/tasks/main.yml
new file mode 100644
index 000000000..a27a0e166
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_role/tasks/main.yml
@@ -0,0 +1,44 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Set version vars
+ set_fact:
+ mso_rw: true
+ when:
+ - version.current.version is version('2.2.4', '<')
+
+- name: Import tasks if RW of role in this MSO version
+ import_tasks: role-rw.yml
+ when: mso_rw is defined
+
+- name: Import tasks if RO of role in this MSO version
+ import_tasks: role-ro.yml
+ when:
+ - mso_rw is not defined
+ - version.current.version is version('3.2', '<') \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_role/tasks/role-ro.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_role/tasks/role-ro.yml
new file mode 100644
index 000000000..730202a9d
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_role/tasks/role-ro.yml
@@ -0,0 +1,88 @@
+# Test code for the MSO modules
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+
+# QUERY ALL ROLES
+- name: Query all roles (check_mode)
+ mso_role: &role_query
+ <<: *mso_info
+ state: query
+ check_mode: true
+ register: cm_query_all_roles
+
+- name: Query all roles (normal mode)
+ mso_role: *role_query
+ register: nm_query_all_roles
+
+- name: Verify query_all_roles
+ assert:
+ that:
+ - cm_query_all_roles is not changed
+ - nm_query_all_roles is not changed
+ # NOTE: Order of roles is not stable between calls
+ #- cm_query_all_roles == nm_query_all_roles
+
+
+# QUERY A ROLE
+- name: Query our role
+ mso_role:
+ <<: *role_query
+ role: powerUser
+ check_mode: true
+ register: cm_query_role
+
+- name: Query our role
+ mso_role:
+ <<: *role_query
+ role: powerUser
+ register: nm_query_role
+
+- name: Verify query_role
+ assert:
+ that:
+ - cm_query_role is not changed
+ - cm_query_role.current.description == 'Elevates this user to \"admin\"'
+ - cm_query_role.current.displayName == 'Power User'
+ - nm_query_role is not changed
+ - nm_query_role.current.description == 'Elevates this user to \"admin\"'
+ - nm_query_role.current.displayName == 'Power User'
+ - cm_query_role == nm_query_role
+
+
+# QUERY NON-EXISTING ROLE
+- name: Query non-existing role (check_mode)
+ mso_role:
+ <<: *role_query
+ role: non-existing-role
+ check_mode: true
+ register: cm_query_non_role
+
+- name: Query non-existing role (normal mode)
+ mso_role:
+ <<: *role_query
+ role: non-existing-role
+ register: nm_query_non_role
+
+# TODO: Implement more tests
+- name: Verify query_non_role
+ assert:
+ that:
+ - cm_query_non_role is not changed
+ - nm_query_non_role is not changed
+ - cm_query_non_role == nm_query_non_role
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_role/tasks/role-rw.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_role/tasks/role-rw.yml
new file mode 100644
index 000000000..82db67640
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_role/tasks/role-rw.yml
@@ -0,0 +1,275 @@
+# Test code for the MSO modules
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Remove role ansible_test
+ mso_role: &role_absent
+ <<: *mso_info
+ role: ansible_test
+ state: absent
+
+- name: Remove role ansible_test2
+ mso_role:
+ <<: *role_absent
+ role: ansible_test2
+ register: cm_remove_role
+
+
+# ADD ROLE
+- name: Add role (check_mode)
+ mso_role: &role_present
+ <<: *mso_info
+ role: ansible_test
+ description: Ansible test role
+ read_permissions: view-sites
+ write_permissions: manage-sites
+ state: present
+ check_mode: true
+ register: cm_add_role
+
+- name: Verify cm_add_role
+ assert:
+ that:
+ - cm_add_role is changed
+ - cm_add_role.previous == {}
+ - cm_add_role.current.description == 'Ansible test role'
+ - cm_add_role.current.displayName == 'ansible_test'
+ - cm_add_role.current.id is not defined
+
+- name: Add role (normal mode)
+ mso_role: *role_present
+ register: nm_add_role
+
+- name: Verify nm_add_role
+ assert:
+ that:
+ - nm_add_role is changed
+ - nm_add_role.previous == {}
+ - nm_add_role.current.description == 'Ansible test role'
+ - nm_add_role.current.displayName == 'ansible_test'
+ - nm_add_role.current.id is defined
+
+- name: Add role again (check_mode)
+ mso_role: *role_present
+ check_mode: true
+ register: cm_add_role_again
+
+- name: Verify cm_add_role_again
+ assert:
+ that:
+ - cm_add_role_again is not changed
+ - cm_add_role_again.previous.description == 'Ansible test role'
+ - cm_add_role_again.previous.displayName == 'ansible_test'
+ - cm_add_role_again.current.description == 'Ansible test role'
+ - cm_add_role_again.current.displayName == 'ansible_test'
+ - cm_add_role_again.current.id == nm_add_role.current.id
+
+- name: Add role again (normal mode)
+ mso_role: *role_present
+ register: nm_add_role_again
+
+- name: Verify nm_add_role_again
+ assert:
+ that:
+ - nm_add_role_again is not changed
+ - nm_add_role_again.previous.description == 'Ansible test role'
+ - nm_add_role_again.previous.displayName == 'ansible_test'
+ - nm_add_role_again.current.description == 'Ansible test role'
+ - nm_add_role_again.current.displayName == 'ansible_test'
+ - nm_add_role_again.current.id == nm_add_role.current.id
+
+
+# CHANGE ROLE
+- name: Change role (check_mode)
+ mso_role:
+ <<: *role_present
+ role: ansible_test
+ description: Ansible test role 2
+ check_mode: true
+ register: cm_change_role
+
+- name: Verify cm_change_role
+ assert:
+ that:
+ - cm_change_role is changed
+ - cm_change_role.current.description == 'Ansible test role 2'
+ - cm_change_role.current.displayName == 'ansible_test'
+ - cm_change_role.current.id == nm_add_role.current.id
+
+- name: Change role (normal mode)
+ mso_role:
+ <<: *role_present
+ role: ansible_test
+ description: Ansible test role 2
+ output_level: debug
+ register: nm_change_role
+
+- name: Verify nm_change_role
+ assert:
+ that:
+ - nm_change_role is changed
+ - nm_change_role.current.description == 'Ansible test role 2'
+ #- nm_change_role.current.displayName == 'ansible_test2'
+ - nm_change_role.current.id == nm_add_role.current.id
+
+- name: Change role again (check_mode)
+ mso_role:
+ <<: *role_present
+ role: ansible_test
+ description: Ansible test role 2
+ check_mode: true
+ register: cm_change_role_again
+
+- name: Verify cm_change_role_again
+ assert:
+ that:
+ - cm_change_role_again is not changed
+ - cm_change_role_again.current.description == 'Ansible test role 2'
+ - cm_change_role_again.current.displayName == 'ansible_test'
+ - cm_change_role_again.current.id == nm_add_role.current.id
+
+- name: Change role again (normal mode)
+ mso_role:
+ <<: *role_present
+ role: ansible_test
+ description: Ansible test role 2
+ register: nm_change_role_again
+
+- name: Verify nm_change_role_again
+ assert:
+ that:
+ - nm_change_role_again is not changed
+ - nm_change_role_again.current.description == 'Ansible test role 2'
+ - nm_change_role_again.current.displayName == 'ansible_test'
+ - nm_change_role_again.current.id == nm_add_role.current.id
+
+
+# QUERY ALL ROLES
+- name: Query all roles (check_mode)
+ mso_role: &role_query
+ <<: *mso_info
+ state: query
+ check_mode: true
+ register: cm_query_all_roles
+
+- name: Query all roles (normal mode)
+ mso_role: *role_query
+ register: nm_query_all_roles
+
+- name: Verify query_all_roles
+ assert:
+ that:
+ - cm_query_all_roles is not changed
+ - nm_query_all_roles is not changed
+ # NOTE: Order of roles is not stable between calls
+ #- cm_query_all_roles == nm_query_all_roles
+
+
+# QUERY A ROLE
+- name: Query our role
+ mso_role:
+ <<: *role_query
+ role: ansible_test
+ check_mode: true
+ register: cm_query_role
+
+- name: Query our role
+ mso_role:
+ <<: *role_query
+ role: ansible_test
+ register: nm_query_role
+
+- name: Verify query_role
+ assert:
+ that:
+ - cm_query_role is not changed
+ - cm_query_role.current.description == 'Ansible test role 2'
+ - cm_query_role.current.displayName == 'ansible_test'
+ - cm_query_role.current.id == nm_add_role.current.id
+ - nm_query_role is not changed
+ - nm_query_role.current.description == 'Ansible test role 2'
+ - nm_query_role.current.displayName == 'ansible_test'
+ - nm_query_role.current.id == nm_add_role.current.id
+ - cm_query_role == nm_query_role
+
+
+# REMOVE ROLE
+- name: Remove role (check_mode)
+ mso_role: *role_absent
+ check_mode: true
+ register: cm_remove_role
+
+- name: Verify cm_remove_role
+ assert:
+ that:
+ - cm_remove_role is changed
+ - cm_remove_role.current == {}
+
+- name: Remove role (normal mode)
+ mso_role: *role_absent
+ register: nm_remove_role
+
+- name: Verify nm_remove_role
+ assert:
+ that:
+ - nm_remove_role is changed
+ - nm_remove_role.current == {}
+
+- name: Remove role again (check_mode)
+ mso_role: *role_absent
+ check_mode: true
+ register: cm_remove_role_again
+
+- name: Verify cm_remove_role_again
+ assert:
+ that:
+ - cm_remove_role_again is not changed
+ - cm_remove_role_again.current == {}
+
+- name: Remove role again (normal mode)
+ mso_role: *role_absent
+ register: nm_remove_role_again
+
+- name: Verify nm_remove_role_again
+ assert:
+ that:
+ - nm_remove_role_again is not changed
+ - nm_remove_role_again.current == {}
+
+
+# QUERY NON-EXISTING ROLE
+- name: Query non-existing role (check_mode)
+ mso_role:
+ <<: *role_query
+ role: non-existing-role
+ check_mode: true
+ register: cm_query_non_role
+
+- name: Query non-existing role (normal mode)
+ mso_role:
+ <<: *role_query
+ role: non-existing-role
+ register: nm_query_non_role
+
+# TODO: Implement more tests
+- name: Verify query_non_role
+ assert:
+ that:
+ - cm_query_non_role is not changed
+ - nm_query_non_role is not changed
+ - cm_query_non_role == nm_query_non_role
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema/tasks/main.yml
new file mode 100644
index 000000000..84613d63a
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema/tasks/main.yml
@@ -0,0 +1,117 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Remove schemas
+ cisco.mso.mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Ensure tenant ansible_test exists
+ cisco.mso.mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ state: present
+
+- name: Create schema 1 with Template 1, and Template 2, Template 3 exist
+ cisco.mso.mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: '{{item.template}}'
+ state: present
+ loop:
+ - { template: Template 1}
+ - { template: Template 2}
+ - { template: Template 3}
+
+- name: Create schema 2 with Template 4
+ cisco.mso.mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template 4
+ state: present
+
+- name: Query for all schemas
+ cisco.mso.mso_schema:
+ <<: *mso_info
+ state: query
+ register: query_all
+
+- name: Query a schema
+ cisco.mso.mso_schema:
+ <<: *mso_info
+ schema: ansible_test
+ state: query
+ register: query_one
+
+- name: Verify query_all and query_one
+ assert:
+ that:
+ - query_all is not changed
+ - query_one is not changed
+ - query_all.current | length >= 2
+ - query_one.current.displayName == "ansible_test"
+
+- name: Remove schema (check_mode)
+ cisco.mso.mso_schema:
+ <<: *mso_info
+ schema: ansible_test
+ state: absent
+ check_mode: true
+ register: cm_rm_schema
+
+- name: Remove schema (normal_mode)
+ cisco.mso.mso_schema:
+ <<: *mso_info
+ schema: ansible_test
+ state: absent
+ register: nm_rm_schema
+
+- name: Verify rm_schema
+ assert:
+ that:
+ - cm_rm_schema is changed
+ - cm_rm_schema.previous.displayName == "ansible_test"
+ - cm_rm_schema.current == {}
+ - nm_rm_schema is changed
+ - nm_rm_schema.current == {}
+ - nm_rm_schema.previous.displayName == "ansible_test"
+
+- name: Query non_existing schema
+ cisco.mso.mso_schema:
+ <<: *mso_info
+ schema: non_existing
+ state: query
+ register: query_non_existing
+
+- name: Verify query_non_existing
+ assert:
+ that:
+ - query_non_existing is not changed
+ - query_non_existing.current == {}
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_clone/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_clone/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_clone/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_clone/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_clone/tasks/main.yml
new file mode 100644
index 000000000..e26bf45d5
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_clone/tasks/main.yml
@@ -0,0 +1,158 @@
+# Test code for the MSO modules
+# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Remove schemas
+ cisco.mso.mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ ignore_errors: true
+ loop:
+ - Destination_Schema
+ - Source_Schema
+
+- name: Ensure site exists
+ mso_site:
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ apic_username }}'
+ apic_password: '{{ apic_password }}'
+ apic_site_id: '{{ apic_site_id | default(101) }}'
+ urls:
+ - https://{{ apic_hostname }}
+ state: present
+
+- name: Ensure tenant ansible_test exists
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ state: present
+
+- name: Create Source schema with Template 1, and Template 2, Template 3 exist
+ cisco.mso.mso_schema_template:
+ <<: *mso_info
+ schema: Source_Schema
+ tenant: ansible_test
+ template: '{{item.template}}'
+ state: present
+ loop:
+ - { template: Template 1}
+ - { template: Template 2}
+ - { template: Template 3}
+
+- name: Ensure VRF exist
+ mso_schema_template_vrf: &vrf_present
+ <<: *mso_info
+ schema: Source_Schema
+ template: Template1
+ vrf: VRF1
+ state: present
+
+- name: Add bd in Source schema
+ mso_schema_template_bd: &bd_present
+ <<: *mso_info
+ schema: Source_Schema
+ template: Template1
+ bd: BD_1
+ vrf:
+ name: VRF1
+ state: present
+
+- name: Clone schema
+ cisco.mso.mso_schema_clone:
+ <<: *mso_info
+ source_schema: Source_Schema
+ destination_schema: Destination_Schema
+ state: clone
+ register: add_schema
+
+- name: Verify add_schema
+ assert:
+ that:
+ - add_schema is changed
+ - add_schema.previous == {}
+ - add_schema.current.displayName == 'Destination_Schema'
+
+- name: Clone schema with same name
+ cisco.mso.mso_schema_clone:
+ <<: *mso_info
+ source_schema: Source_Schema
+ destination_schema: Source_Schema
+ state: clone
+ ignore_errors: true
+ register: add_same_schema
+
+- name: Verify add_same_schema
+ assert:
+ that:
+ - add_same_schema is not changed
+ - add_same_schema.current == {}
+ - add_same_schema.msg == "Source and Destination schema cannot have same names."
+
+- name: Clone schema when destination schema exists
+ cisco.mso.mso_schema_clone:
+ <<: *mso_info
+ source_schema: Source_Schema
+ destination_schema: Destination_Schema
+ state: clone
+ ignore_errors: true
+ register: add_existing_schema
+
+- name: Verify add_existing_schema
+ assert:
+ that:
+ - add_existing_schema is not changed
+ - add_existing_schema.msg == "Schema with the name 'Destination_Schema' already exists. Please use another name."
+
+- name: Clone schema when source schema does not exist
+ cisco.mso.mso_schema_clone:
+ <<: *mso_info
+ source_schema: Source_Schema_1
+ destination_schema: Destination_Schema_2
+ state: clone
+ ignore_errors: true
+ register: add_existing_schema
+
+- name: Verify add_existing_schema
+ assert:
+ that:
+ - add_existing_schema is not changed
+ - add_existing_schema.msg == "Provided schema 'Source_Schema_1' does not exist."
+
+- name: Remove schemas
+ cisco.mso.mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ register: rm_schema
+ loop:
+ - Destination_Schema
+ - Source_Schema
+
+- name: Verify rm_schema
+ assert:
+ that:
+ - rm_schema is changed \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site/tasks/main.yml
new file mode 100644
index 000000000..6eede75ae
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site/tasks/main.yml
@@ -0,0 +1,273 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Ensure site exists
+ mso_site:
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ apic_username }}'
+ apic_password: '{{ apic_password }}'
+ apic_site_id: '{{ apic_site_id | default(101) }}'
+ urls:
+ - https://{{ apic_hostname }}
+ state: present
+ ignore_errors: true
+
+- name: Remove schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Ensure sites removed from tenant ansible_test
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_site | default("ansible_test") }}'
+ - 'aws_{{ mso_site | default("ansible_test") }}'
+ - 'azure_{{ mso_site | default("ansible_test") }}'
+
+- name: Ensure tenant ansible_test exists
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ state: present
+
+- name: Ensure schema 1 with Template 1 and Template 2 exists
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: '{{item.template}}'
+ state: present
+ loop:
+ - { template: Template 1}
+ - { template: Template 2}
+
+- name: Add a new site to a schema with Template 1 in check mode
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ state: present
+ check_mode: true
+ register: add_site_cm
+
+- name: Verify add_site_cm
+ assert:
+ that:
+ - add_site_cm.current.siteId is match ("[0-9a-zA-Z]*")
+ - add_site_cm.current.templateName == "Template1"
+
+- name: Add a new site to a schema with Template 1 in normal mode
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ state: present
+ register: add_site_nm
+
+- name: Verify add_site_nm
+ assert:
+ that:
+ - add_site_nm.current.siteId is match ("[0-9a-zA-Z]*")
+ - add_site_nm.current.templateName == "Template1"
+
+- name: Add a new site to a schema in normal mode again
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ state: present
+ register: add_site_nm_again
+
+- name: Verify add_site_nm_again
+ assert:
+ that:
+ - add_site_nm_again is not changed
+ - add_site_nm_again.current.siteId is match ("[0-9a-zA-Z]*")
+
+- name: Add a new site to a schema with Template 2 in normal mode
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 2
+ state: present
+ output_level: debug
+ register: add_site_temp2_nm
+
+- name: Verify add_site_temp2_nm
+ assert:
+ that:
+ - add_site_temp2_nm.current.siteId is match ("[0-9a-zA-Z]*")
+ - add_site_temp2_nm.current.templateName == "Template2"
+ - add_site_temp2_nm.method == "PATCH"
+ - add_site_temp2_nm.patch_operation != []
+ - add_site_temp2_nm.patch_operation.0.value.templateName == "Template2"
+ - add_site_temp2_nm.previous == {}
+ - add_site_temp2_nm.proposed != {}
+ - add_site_temp2_nm.proposed.templateName == "Template2"
+ - add_site_temp2_nm.sent != {}
+ - add_site_temp2_nm.sent.templateName == "Template2"
+
+- name: Query a schema site
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ state: query
+ register: query_site
+
+- name: Query all schema sites
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ state: query
+ register: query_all_sites
+
+- name: Verify query_site and query_all_sites
+ assert:
+ that:
+ - query_site is not changed
+ - query_all_sites is not changed
+ - query_all_sites.current | length == 2
+
+- name: Remove a site from a schema with Template1
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ state: absent
+ register: rm_site_temp1
+
+- name: Remove a site from a schema with Template2
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 2
+ state: absent
+ register: rm_site_temp2
+
+- name: Verify rm_site_temp1 and rm_site_temp2
+ assert:
+ that:
+ - rm_site_temp1 is changed
+ - rm_site_temp1.current == {}
+ - rm_site_temp2 is changed
+ - rm_site_temp2.current == {}
+
+- name: Remove a site from a schema with Template2 again
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 2
+ state: absent
+ register: rm_site_again
+
+- name: Verify rm_site_again
+ assert:
+ that:
+ - rm_site_again is not changed
+
+# USE NON-EXISTING STATE
+- name: non_existing_state state
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ state: non_existing_state
+ ignore_errors: true
+ register: non_existing_state
+
+- name: Verify non_existing_state
+ assert:
+ that:
+ - non_existing_state is not changed
+ - non_existing_state.msg == "value of state must be one of{{':'}} absent, present, query, got{{':'}} non_existing_state"
+
+# USE A NON_EXISTING_SCHEMA
+- name: non_existing_schema
+ mso_schema_site:
+ <<: *mso_info
+ schema: non_existing_schema
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ state: query
+ ignore_errors: true
+ register: non_existing_schema
+
+- name: Verify non_existing_schema
+ assert:
+ that:
+ - non_existing_schema is not changed
+ - non_existing_schema.msg == "Provided schema 'non_existing_schema' does not exist."
+
+# USE A NON_EXISTING_TEMPLATE
+- name: non_existing_template
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: non_existing_template
+ state: query
+ ignore_errors: true
+ register: non_existing_template
+
+- name: Verify non_existing_template
+ assert:
+ that:
+ - non_existing_template is not changed
+ - non_existing_template.msg == "Template 'non_existing_template' not found"
+
+- name: Template attribute absent in task
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: query
+ ignore_errors: true
+ register: absent_template
+
+- name: Verify absent_template
+ assert:
+ that:
+ - absent_template is not changed
+ - absent_template.current == [] \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp/tasks/main.yml
new file mode 100644
index 000000000..9faffb376
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp/tasks/main.yml
@@ -0,0 +1,481 @@
+# Test code for the MSO modules
+# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Remove schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Ensure physical site exists
+ mso_site:
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ apic_username }}'
+ apic_password: '{{ apic_password }}'
+ apic_site_id: '{{ apic_site_id | default(101) }}'
+ urls:
+ - https://{{ apic_hostname }}
+ state: present
+
+- name: Ensure aws site exists
+ mso_site:
+ <<: *mso_info
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ aws_apic_username }}'
+ apic_password: '{{ aws_apic_password }}'
+ apic_site_id: '{{ aws_site_id | default(102) }}'
+ urls:
+ - https://{{ aws_apic_hostname }}
+ state: present
+
+- name: Ensure azure site exists
+ mso_site:
+ <<: *mso_info
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ azure_apic_username }}'
+ apic_password: '{{ azure_apic_password }}'
+ apic_site_id: '{{ azure_site_id | default(103) }}'
+ urls:
+ - https://{{ azure_apic_hostname }}
+ state: present
+
+- name: Ensure sites removed from tenant ansible_test
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_site | default("ansible_test") }}'
+ - 'aws_{{ mso_site | default("ansible_test") }}'
+ - 'azure_{{ mso_site | default("ansible_test") }}'
+
+- name: Ensure tenant ansible_test exists
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ state: present
+
+- name: Associate non-cloud site with ansible_test again in normal mode
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: present
+
+- name: Associate aws site with ansible_test in normal mode
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ cloud_account: "000000000000"
+ aws_trusted: false
+ aws_access_key: "1"
+ secret_key: "0"
+ state: present
+ register: aaws_nm
+
+- name: Associate azure site with access_type not present, with ansible_test in normal mode
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ cloud_account: uni/tn-ansible_test/act-[100]-vendor-azure
+ state: present
+ register: aazure_shared_nm
+
+- name: Ensure schema 1 with Template 1, and Template 2 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: '{{item.template}}'
+ state: present
+ loop:
+ - { template: Template 1}
+ - { template: Template 2}
+ - { template: Template 3}
+
+- name: Ensure schema 2 with Template 4 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template 4
+ state: present
+
+- name: Add cloud site to a schema
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{item.site}}'
+ template: '{{item.template}}'
+ state: present
+ loop:
+ - { site: 'azure_{{ mso_site | default("ansible_test") }}', template: 'Template 1' }
+ - { site: 'aws_{{ mso_site | default("ansible_test") }}', template: 'Template 2' }
+
+- name: Add physical site to a schema
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 3
+ state: present
+
+- name: Ensure VRF1 exists
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 3
+ vrf: VRF1
+ state: present
+
+- name: Add BD1
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 3
+ bd: BD1
+ vrf:
+ name: VRF1
+ state: present
+
+- name: Ensure ANPs exist at template level
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{item.schema}}'
+ template: '{{ item.template }}'
+ anp: '{{ item.anp }}'
+ state: present
+ loop:
+ - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 1', anp: 'ANP' }
+ - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 2', anp: 'ANP_2' }
+ - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 3', anp: 'ANP_3' }
+ - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 3', anp: 'ANP_3_1' }
+ - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 3', anp: 'ANP_3_2' }
+ - { schema: '{{ mso_schema | default("ansible_test") }}_2', template: 'Template 4', anp: 'ANP_4' }
+
+- name: Ensure EPGs exist at template level
+ mso_schema_template_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 3
+ anp: ANP_3
+ epg: ansible_test_3
+ vrf:
+ name: VRF1
+ schema: ansible_test
+ template: Template 3
+ bd:
+ name: BD1
+ schema: ansible_test
+ template: Template 3
+ state: present
+
+- name: Add ANP to site azure (check_mode)
+ mso_schema_site_anp:
+ <<: *mso_info
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ state: present
+ check_mode: true
+ register: cm_add_anp
+
+- name: Add ANP to site azure (normal mode)
+ mso_schema_site_anp:
+ <<: *mso_info
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ state: present
+ register: nm_add_anp
+
+- name: Verify add_anp values
+ assert:
+ that:
+ - cm_add_anp.current.anpRef.anpName == 'ANP'
+ - nm_add_anp.current.anpRef.anpName == 'ANP'
+
+- name: Verify add_anp change
+ assert:
+ that:
+ - cm_add_anp is changed
+ - nm_add_anp is changed
+ when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined
+
+- name: Add ANP to site aws
+ mso_schema_site_anp:
+ <<: *mso_info
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ anp: ANP_2
+ state: present
+ register: add_anp
+
+- name: Verify add_anp value
+ assert:
+ that:
+ - add_anp.current.anpRef.anpName == 'ANP_2'
+
+- name: Verify add_anp change
+ assert:
+ that:
+ - add_anp is changed
+ when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined
+
+- name: Add ANPs to site
+ mso_schema_site_anp:
+ <<: *mso_info
+ site: '{{ item.site }}'
+ schema: '{{ item.schema }}'
+ template: '{{ item.template }}'
+ anp: '{{ item.anp }}'
+ state: present
+ loop:
+ - { site: '{{ mso_site | default("ansible_test") }}', schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 3', anp: 'ANP_3' }
+ - { site: '{{ mso_site | default("ansible_test") }}', schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 3', anp: 'ANP_3_1' }
+ - { site: '{{ mso_site | default("ansible_test") }}', schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 3', anp: 'ANP_3_2' }
+
+- name: Add a new site EPG for idempotency check
+ mso_schema_site_anp_epg: &idempotency_vmm
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: 'Template 3'
+ anp: 'ANP_3'
+ site: '{{ mso_site | default("ansible_test") }}'
+ epg: ansible_test_3
+ state: present
+
+# Test due to inconsistency in attributes REQUEST/RESPONSE API
+# MSO Error 400: Bad Request: (0)(0)(0)(0)/deploymentImmediacy error.path.missing
+- name: Add new site domain to site EPG for idempotency check
+ mso_schema_site_anp_epg_domain:
+ <<: *idempotency_vmm
+ domain_association_type: vmmDomain
+ domain_profile: 'VMware-VMM'
+ deployment_immediacy: lazy
+ resolution_immediacy: pre-provision
+ state: present
+
+- name: Add ANPs to site again
+ mso_schema_site_anp:
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: 'Template 3'
+ anp: 'ANP_3'
+ state: present
+ register: add_anp_again
+
+- name: Verify add_anp_again
+ assert:
+ that:
+ - add_anp_again is not changed
+ - add_anp_again.current.anpRef.anpName == 'ANP_3'
+
+# QUERY ANPs
+- name: Query specific ANP (normal mode)
+ mso_schema_site_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 3
+ anp: ANP_3
+ state: query
+ register: query_anp
+
+- name: Verify query_anp
+ assert:
+ that:
+ - query_anp is not changed
+
+- name: Query all ANPs (normal mode)
+ mso_schema_site_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 3
+ state: query
+ register: query_all
+
+- name: Verify query_all
+ assert:
+ that:
+ - query_all is not changed
+ - query_all | length >= 3
+
+# DELETE the ANP
+- name: Delete ANP3 (normal mode)
+ mso_schema_site_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 3
+ anp: ANP_3
+ state: absent
+ register: delete_anp
+
+- name: Verify delete_anp
+ assert:
+ that:
+ - delete_anp is changed
+ - delete_anp.current == {}
+
+- name: Delete ANP1 again
+ mso_schema_site_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 3
+ anp: ANP_3
+ state: absent
+ register: delete_anp_again
+
+- name: Verify delete_anp_again
+ assert:
+ that:
+ - delete_anp_again is not changed
+ - delete_anp_again.current == {}
+
+# QUERY NON-EXISTING ANP
+- name: Query non-existing ANP (normal mode)
+ mso_schema_site_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 3
+ anp: non_existing_anp
+ state: query
+ ignore_errors: true
+ register: nm_query_non_anp
+
+- name: Verify query_non_anp
+ assert:
+ that:
+ - nm_query_non_anp is not changed
+ - nm_query_non_anp.msg == "ANP 'non_existing_anp' not found"
+
+# USE A NON-EXISTING STATE
+- name: Non-existing state (normal_mode)
+ mso_schema_site_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ state: non-existing-state
+ ignore_errors: true
+ register: nm_non_existing_state
+
+- name: Verify non_existing_state
+ assert:
+ that:
+ - nm_non_existing_state is not changed
+ - nm_non_existing_state.msg == "value of state must be one of{{':'}} absent, present, query, got{{':'}} non-existing-state"
+
+# USE A NON-EXISTING SCHEMA
+- name: Non-existing schema (normal_mode)
+ mso_schema_site_anp:
+ <<: *mso_info
+ schema: non-existing-schema
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_schema
+
+- name: Verify non_existing_schema
+ assert:
+ that:
+ - nm_non_existing_schema is not changed
+ - nm_non_existing_schema.msg == "Provided schema 'non-existing-schema' does not exist."
+
+# USE A NON-EXISTING SITE
+- name: Non-existing site (normal_mode)
+ mso_schema_site_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: non-existing-site
+ template: Template 1
+ anp: ANP
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_site
+
+- name: Verify non_existing_site
+ assert:
+ that:
+ - nm_non_existing_site is not changed
+ - nm_non_existing_site.msg == "Site 'non-existing-site' is not a valid site name."
+
+# USE A TEMPLATE WITHOUT ANY SITE
+- name: Add ANP to Template without any site associated (normal mode)
+ mso_schema_site_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ template: Template 4
+ anp: ANP_4
+ state: present
+ ignore_errors: true
+ register: nm_no_site_associated
+
+- name: Verify cm_no_site_associated and nm_no_site_associated
+ assert:
+ that:
+ - nm_no_site_associated is not changed
+ - nm_no_site_associated.msg == "No site associated with template 'Template4'. Associate the site with the template using mso_schema_site."
+
+# USE A NON-EXISTING SITE-TEMPLATE
+- name: Non-existing site-template (normal_mode)
+ mso_schema_site_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 2
+ anp: ANP_2
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_site_template
+
+- name: Verify non_existing_site_template
+ assert:
+ that:
+ - nm_non_existing_site_template is not changed
+ - nm_non_existing_site_template.msg is match("Provided site/siteId/template 'ansible_test/[0-9a-zA-Z]*/Template2' does not exist. Existing siteIds/templates{{':'}} [0-9a-zA-Z]*/Template1, [0-9a-zA-Z]*/Template2, [0-9a-zA-Z]*/Template3") \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg/tasks/main.yml
new file mode 100644
index 000000000..8373a629f
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg/tasks/main.yml
@@ -0,0 +1,705 @@
+# Test code for the MSO modules
+# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Remove schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Ensure site exists
+ mso_site:
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ apic_username }}'
+ apic_password: '{{ apic_password }}'
+ apic_site_id: '{{ apic_site_id | default(101) }}'
+ urls:
+ - https://{{ apic_hostname }}
+ state: present
+ ignore_errors: true
+
+- name: Ensure tenant ansible_test exists
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ state: present
+
+- name: Ensure sites removed from tenant ansible_test
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_site | default("ansible_test") }}'
+ - 'aws_{{ mso_site | default("ansible_test") }}'
+ - 'azure_{{ mso_site | default("ansible_test") }}'
+
+- name: Associate non-cloud site with ansible_test again in normal mode
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: present
+
+- name: Associate aws site with ansible_test in normal mode
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ cloud_account: "000000000000"
+ aws_trusted: false
+ aws_access_key: "1"
+ secret_key: "0"
+ state: present
+ register: aaws_nm
+
+- name: Associate azure site with access_type not present, with ansible_test in normal mode
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ cloud_account: uni/tn-ansible_test/act-[100]-vendor-azure
+ state: present
+ register: aazure_shared_nm
+
+- name: Ensure schema 1 with Template 1, and Template 2 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: '{{item.template}}'
+ state: present
+ loop:
+ - { template: Template 1}
+ - { template: Template 2}
+ - { template: Template 3}
+
+- name: Ensure schema 2 with Template 4 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template 4
+ state: present
+
+- name: Add cloud site to a schema
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{item.site}}'
+ template: '{{item.template}}'
+ state: present
+ loop:
+ - { site: 'azure_{{ mso_site | default("ansible_test") }}', template: 'Template 1' }
+ - { site: 'aws_{{ mso_site | default("ansible_test") }}', template: 'Template 2' }
+ when: version.current.version is version('3', '<') or version.current.version is version('3.2', '>=')
+
+- name: Add physical site to a schema
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: '{{ item.template }}'
+ state: present
+ loop:
+ - { template: 'Template 3' }
+ - { template: 'Template 1' }
+
+- name: Ensure VRF1 exists
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: '{{ item.template }}'
+ vrf: VRF1
+ state: present
+ loop:
+ - { template: 'Template 1' }
+ - { template: 'Template 3' }
+
+- name: Add BD1
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: '{{ item.template }}'
+ bd: BD1
+ vrf:
+ name: VRF1
+ state: present
+ loop:
+ - { template: 'Template 1' }
+ - { template: 'Template 3' }
+
+- name: Ensure ANPs exist at template level
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{item.schema}}'
+ template: '{{ item.template }}'
+ anp: '{{ item.anp }}'
+ state: present
+ loop:
+ - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 1', anp: 'ANP' }
+ - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 2', anp: 'ANP_2' }
+ - { schema: '{{ mso_schema | default("ansible_test") }}_2', template: 'Template 4', anp: 'ANP_4' }
+
+- name: Ensure ANP exist at template level
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 3
+ anp: ANP_3
+ state: present
+
+- name: Ensure EPGs exist at template level
+ mso_schema_template_anp_epg:
+ <<: *mso_info
+ schema: '{{ item.schema }}'
+ template: '{{ item.template }}'
+ anp: '{{ item.anp }}'
+ epg: '{{ item.epg }}'
+ vrf:
+ name: VRF1
+ schema: ansible_test
+ template: Template 1
+ bd:
+ name: BD1
+ schema: ansible_test
+ template: Template 1
+ state: present
+ loop:
+ - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 1', anp: 'ANP', epg: 'ansible_test_1' }
+ - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 2', anp: 'ANP_2', epg: 'ansible_test_2' }
+ - { schema: '{{ mso_schema | default("ansible_test") }}_2', template: 'Template 4', anp: 'ANP_4', epg: 'ansible_test_4' }
+
+- name: Ensure EPGs exist at template level
+ mso_schema_template_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 3
+ anp: ANP_3
+ epg: ansible_test_3
+ vrf:
+ name: VRF1
+ schema: ansible_test
+ template: Template 3
+ bd:
+ name: BD1
+ schema: ansible_test
+ template: Template 3
+ state: present
+
+- name: Add ANP to site
+ mso_schema_site_anp:
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 3
+ anp: ANP_3
+ state: present
+
+# ADD ANP EPGs to SITE
+- name: Add new EPG to site after adding ANP to site (check_mode)
+ mso_schema_site_anp_epg:
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 3
+ anp: ANP_3
+ epg: ansible_test_3
+ state: present
+ check_mode: true
+ register: cm_add_epg
+
+- name: Add new EPG to site after adding ANP to site (normal mode)
+ mso_schema_site_anp_epg:
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 3
+ anp: ANP_3
+ epg: ansible_test_3
+ state: present
+ register: nm_add_epg
+
+- name: Verify add_epg
+ assert:
+ that:
+ - cm_add_epg is changed
+ - nm_add_epg is changed
+ - cm_add_epg.current.epgRef.anpName == 'ANP_3'
+ - nm_add_epg.current.epgRef.anpName == 'ANP_3'
+ - cm_add_epg.current.epgRef.epgName == 'ansible_test_3'
+ - nm_add_epg.current.epgRef.epgName == 'ansible_test_3'
+ when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined
+
+- name: Add new EPG to site after adding ANP to site again
+ mso_schema_site_anp_epg: &idempotency_vmm
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 3
+ anp: ANP_3
+ epg: ansible_test_3
+ state: present
+ register: add_epg_again
+
+- name: Verify add_epg_again
+ assert:
+ that:
+ - add_epg_again is not changed
+ - add_epg_again.current.epgRef.anpName == 'ANP_3'
+ - add_epg_again.current.epgRef.epgName == 'ansible_test_3'
+
+# Test due to inconsistency in attributes REQUEST/RESPONSE API
+# MSO Error 400: Bad Request: (0)(0)(0)(0)/deploymentImmediacy error.path.missing
+- name: Add new site domain to site EPG for idempotency check
+ mso_schema_site_anp_epg_domain:
+ <<: *idempotency_vmm
+ domain_association_type: vmmDomain
+ domain_profile: 'VMware-VMM'
+ deployment_immediacy: lazy
+ resolution_immediacy: pre-provision
+ state: present
+
+- name: Add new EPG to site after adding ANP to site again
+ mso_schema_site_anp_epg:
+ <<: *idempotency_vmm
+ register: add_epg_again_with_vmm
+
+- name: Verify add_epg_again with vmm
+ assert:
+ that:
+ - add_epg_again is not changed
+ - add_epg_again.current.epgRef.anpName == 'ANP_3'
+ - add_epg_again.current.epgRef.epgName == 'ansible_test_3'
+
+- name: Add new EPG to site without adding ANPs to site
+ mso_schema_site_anp_epg:
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ state: present
+ register: add_epg_no_anp
+
+- name: Verify add_epg_no_anp
+ assert:
+ that:
+ - add_epg_no_anp is changed
+ - add_epg_no_anp.current.epgs.0.epgRef.anpName == 'ANP'
+ - add_epg_no_anp.current.epgs.0.epgRef.epgName == 'ansible_test_1'
+ when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined
+
+- name: Add new EPG to site without adding ANPs to site again (ANP already exists from previous run)
+ mso_schema_site_anp_epg:
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ state: present
+ register: add_epg_no_anp_again
+
+- name: Verify add_epg_no_anp_again
+ assert:
+ that:
+ - add_epg_no_anp_again is not changed
+ - add_epg_no_anp_again.current.epgRef.anpName == 'ANP'
+ - add_epg_no_anp_again.current.epgRef.epgName == 'ansible_test_1'
+
+# QUERY EPGs
+- name: Query all EPGs with ANP (normal mode)
+ mso_schema_site_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 3
+ anp: ANP_3
+ state: query
+ register: query_all
+
+- name: Verify query_all
+ assert:
+ that:
+ - query_all is not changed
+
+- name: Query specific EPG1 (normal mode)
+ mso_schema_site_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ state: query
+ register: query_epg
+
+- name: Verify query_epg
+ assert:
+ that:
+ - query_epg is not changed
+
+# DELETE the EPG
+- name: Delete EPG1 (normal mode)
+ mso_schema_site_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ state: absent
+ register: delete_epg
+
+- name: Verify delete_epg
+ assert:
+ that:
+ - delete_epg is changed
+ - delete_epg.current == {}
+ when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined
+
+- name: Delete EPG1 again
+ mso_schema_site_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ state: absent
+ register: delete_epg_again
+
+- name: Verify delete_epg_again
+ assert:
+ that:
+ - delete_epg_again is not changed
+ - delete_epg_again.current == {}
+
+# QUERY NON-EXISTING EPG
+- name: Query non-existing EPG in template (normal mode)
+ mso_schema_site_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 3
+ anp: ANP_3
+ epg: non_existing_epg
+ state: query
+ ignore_errors: true
+ register: nm_query_non_epg
+
+- name: Verify query_non_epg
+ assert:
+ that:
+ - nm_query_non_epg is not changed
+ - nm_query_non_epg.msg == "Provided EPG 'non_existing_epg' does not exist. Existing EPGs{{':'}} ansible_test_3"
+
+# QUERY NON-EXISTING EPG
+- name: Query non-existing EPG in site level
+ mso_schema_site_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ state: query
+ ignore_errors: true
+ register: query_non_epg
+
+- name: Verify query_non_epg
+ assert:
+ that:
+ - query_non_epg is not changed
+ - query_non_epg.msg == "EPG 'ansible_test_1' not found"
+ when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined
+
+- name: Delete anp
+ mso_schema_site_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 3
+ anp: ANP_3
+ state: absent
+
+# QUERY NON-EXISTING ANP
+- name: Query non-existing ANP in template(normal mode)
+ mso_schema_site_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 3
+ anp: non_existing_anp
+ epg: ansible_test_3
+ state: query
+ ignore_errors: true
+ register: nm_query_non_anp
+
+- name: Verify query_non_anp
+ assert:
+ that:
+ - nm_query_non_anp is not changed
+ - nm_query_non_anp.msg == "Provided anp 'non_existing_anp' does not exist. Existing anps{{':'}} ANP_3"
+
+# QUERY NON-EXISTING ANP
+- name: Query non-existing ANP at site level(normal mode)
+ mso_schema_site_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 3
+ anp: ANP_3
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: query
+ ignore_errors: true
+ register: nm_query_non_anp
+
+- name: Verify query_non_anp
+ assert:
+ that:
+ - nm_query_non_anp is not changed
+ - nm_query_non_anp.msg == "Provided anp 'ANP_3' does not exist at site level."
+ when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined
+
+# USE A NON-EXISTING STATE
+- name: Non-existing state (normal_mode)
+ mso_schema_site_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ state: non-existing-state
+ ignore_errors: true
+ register: nm_non_existing_state
+
+- name: Verify non_existing_state
+ assert:
+ that:
+ - nm_non_existing_state is not changed
+ - nm_non_existing_state.msg == "value of state must be one of{{':'}} absent, present, query, got{{':'}} non-existing-state"
+
+# # USE A NON-EXISTING TEMPLATE
+- name: Non-existing template (normal_mode)
+ mso_schema_site_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: non-existing-template
+ anp: ANP
+ epg: ansible_test_1
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_template
+
+- name: Verify non_existing_template
+ assert:
+ that:
+ - nm_non_existing_template is not changed
+ - nm_non_existing_template.msg == "Provided template 'non-existing-template' does not exist. Existing templates{{':'}} Template1, Template2, Template3"
+
+# USE A NON-EXISTING SCHEMA
+- name: Non-existing schema (normal_mode)
+ mso_schema_site_anp_epg:
+ <<: *mso_info
+ schema: non-existing-schema
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_schema
+
+- name: Verify non_existing_schema
+ assert:
+ that:
+ - nm_non_existing_schema is not changed
+ - nm_non_existing_schema.msg == "Provided schema 'non-existing-schema' does not exist."
+
+# USE A NON-EXISTING SITE
+- name: Non-existing site (normal_mode)
+ mso_schema_site_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: non-existing-site
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_site
+
+- name: Verify non_existing_site
+ assert:
+ that:
+ - nm_non_existing_site is not changed
+ - nm_non_existing_site.msg == "Site 'non-existing-site' is not a valid site name."
+
+# USE A NON-EXISTING SITE-TEMPLATE
+- name: Non-existing site-template (normal_mode)
+ mso_schema_site_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 2
+ anp: ANP_2
+ epg: ansible_test_2
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_site_template
+
+- name: Verify non_existing_site_template
+ assert:
+ that:
+ - nm_non_existing_site_template is not changed
+ - nm_non_existing_site_template.msg is match("Provided site/siteId/template 'ansible_test/[0-9a-zA-Z]*/Template2' does not exist. Existing siteIds/templates{{':'}} [0-9a-zA-Z]*/Template1, [0-9a-zA-Z]*/Template2, [0-9a-zA-Z]*/Template3, [0-9a-zA-Z]*/Template1")
+ when: version.current.version is version('3', '<') or version.current.version is version('3.2', '>=')
+
+- name: Verify non_existing_site_template
+ assert:
+ that:
+ - nm_non_existing_site_template is not changed
+ - nm_non_existing_site_template.msg is match("Provided site/siteId/template 'ansible_test/[0-9a-zA-Z]*/Template2' does not exist. Existing siteIds/templates{{':'}} [0-9a-zA-Z]*/Template3, [0-9a-zA-Z]*/Template1")
+ when: version.current.version is version('3', '>=') and version.current.version is version('3.2', '<')
+
+# USE A TEMPLATE WITHOUT ANY SITE
+- name: Add EPG to Template without any site associated (normal mode)
+ mso_schema_site_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ template: Template 4
+ anp: ANP_4
+ epg: ansible_test_1
+ state: present
+ ignore_errors: true
+ register: nm_no_site_associated
+
+- name: Verify cm_no_site_associated and nm_no_site_associated
+ assert:
+ that:
+ - nm_no_site_associated is not changed
+ - nm_no_site_associated.msg == "No site associated with template 'Template4'. Associate the site with the template using mso_schema_site."
+
+# ADDING PRIVATE LINK LABEL
+# Add private link label when MSO version >= 3.3
+- name: Execute tasks only for MSO version >= 3.3
+ when: version.current.version is version('3.3', '>=')
+ block:
+ - name: Remove physical site from schema
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: 'Template 1'
+ state: absent
+
+ - name: Ensure region for VRF1 at site level exists
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ cidr: 10.0.0.0/16
+ state: present
+
+ - name: Ensure Private Link Label in Azure VRF subnet exist (MSO >3.3)
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ cidr: 10.0.0.0/16
+ subnet: 10.0.0.0/24
+ private_link_label: 'New_Private_Link_Label'
+ zone: null
+
+ - name: Ensure another Private Link Label in Azure VRF subnet exist (MSO >3.3)
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ cidr: 10.0.0.0/16
+ subnet: 10.0.1.0/26
+ private_link_label: 'PLL'
+
+ - name: Add new EPG service type parameters (for version greater than 3.3)
+ mso_schema_template_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ vrf:
+ name: VRF1
+ schema: ansible_test
+ template: Template 1
+ epg_type: 'service'
+ deployment_type: 'cloud_native'
+ service_type: 'Azure-Storage'
+ access_type: 'private'
+ state: present
+
+ - name: Add private link label to the EPG (for version greater than 3.3)
+ mso_schema_site_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ private_link_label: 'New_Private_Link_Label'
+ state: present
+
+ - name: Change private link label in the EPG (for version greater than 3.3)
+ mso_schema_site_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ private_link_label: 'PLL'
+ state: present \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_bulk_staticport/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_bulk_staticport/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_bulk_staticport/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_bulk_staticport/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_bulk_staticport/tasks/main.yml
new file mode 100644
index 000000000..0fc8943fd
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_bulk_staticport/tasks/main.yml
@@ -0,0 +1,774 @@
+# Test code for the MSO modules
+# Copyright: (c) 2023, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("debug") }}'
+
+- name: Remove Schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Ensure site exists
+ mso_site:
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ apic_username }}'
+ apic_password: '{{ apic_password }}'
+ apic_site_id: '{{ apic_site_id | default(101) }}'
+ urls:
+ - https://{{ apic_hostname }}
+ state: present
+
+- name: Ensure sites removed from tenant ansible_test
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_site | default("ansible_test") }}'
+ - 'aws_{{ mso_site | default("ansible_test") }}'
+ - 'azure_{{ mso_site | default("ansible_test") }}'
+
+- name: Ensure tenant ansible_test exist
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ state: present
+
+- name: Ensure schema 1 with Template 1 and 2 exists
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: '{{ item }}'
+ state: present
+ loop:
+ - Template 1
+ - Template 2
+
+- name: Ensure schema 2 with Template 3 exists
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template 3
+ state: present
+
+- name: Add a new site to a schema
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ state: present
+
+- name: Ensure VRF1 exists
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF1
+ state: present
+
+- name: Add BD1
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ bd: BD1
+ vrf:
+ name: VRF1
+ state: present
+
+- name: Ensure Template 1 with AP1 exists
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ state: present
+
+- name: Ensure Template 1 and AP1 with EPG1 exists
+ mso_schema_template_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG1
+ bd:
+ name: BD1
+ vrf:
+ name: VRF1
+ state: present
+
+- name: Ensure Template 1 and AP1 with EPG3 exists
+ mso_schema_template_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG3
+ bd:
+ name: BD1
+ vrf:
+ name: VRF1
+ state: present
+
+- name: Ensure Template 1 with AP2 exists
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: AP2
+ state: present
+
+- name: Ensure Template 1 and AP2 with EPG2 exists
+ mso_schema_template_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: AP2
+ epg: EPG2
+ bd:
+ name: BD1
+ vrf:
+ name: VRF1
+ state: present
+
+- name: Ensure Template 1 and AP2 with EPG4 exists
+ mso_schema_template_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: AP2
+ epg: EPG4
+ bd:
+ name: BD1
+ vrf:
+ name: VRF1
+ state: present
+
+- name: Ensure Template 1 and AP2 with EPG6 exists
+ mso_schema_template_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: AP2
+ epg: EPG6
+ bd:
+ name: BD1
+ vrf:
+ name: VRF1
+ state: present
+
+# ADD STATIC PORTS
+- name: Add static port 1 to site EPG1 of AP1 (check mode)
+ mso_schema_site_anp_epg_bulk_staticport:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG1
+ pod: pod-1
+ leaf: 101
+ path: eth1/1
+ vlan: 126
+ mode: native
+ type: port
+ deployment_immediacy: immediate
+ static_ports:
+ - path: eth1/2
+ leaf: 102
+ - path: eth1/3
+ vlan: 124
+ - leaf: 102
+ path: eth1/3
+ vlan: 124
+ state: present
+ check_mode: true
+ register: cm_add_stat1e1
+
+- name: Verify cm_add_stat1e1
+ assert:
+ that:
+ - cm_add_stat1e1 is changed
+ - cm_add_stat1e1.previous == []
+ - cm_add_stat1e1.current[0].deploymentImmediacy == 'immediate'
+ - cm_add_stat1e1.current[0].portEncapVlan == 126
+ - cm_add_stat1e1.current[0].path == 'topology/pod-1/paths-102/pathep-[eth1/2]'
+ - cm_add_stat1e1.current[0].mode == 'native'
+ - cm_add_stat1e1.current[0].type == 'port'
+ - cm_add_stat1e1.current[1].deploymentImmediacy == 'immediate'
+ - cm_add_stat1e1.current[1].portEncapVlan == 124
+ - cm_add_stat1e1.current[1].path == 'topology/pod-1/paths-101/pathep-[eth1/3]'
+ - cm_add_stat1e1.current[1].mode == 'native'
+ - cm_add_stat1e1.current[1].type == 'port'
+ - cm_add_stat1e1.current[2].portEncapVlan == 124
+ - cm_add_stat1e1.current[2].path == 'topology/pod-1/paths-102/pathep-[eth1/3]'
+ - cm_add_stat1e1.current|length == 3
+
+- name: Add static port 1 to site EPG1 of AP1 (normal mode)
+ mso_schema_site_anp_epg_bulk_staticport: &add_static_port_1
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG1
+ pod: pod-1
+ leaf: 101
+ path: eth1/1
+ vlan: 126
+ mode: native
+ type: port
+ deployment_immediacy: immediate
+ static_ports:
+ - path: eth1/2
+ leaf: 102
+ - path: eth1/3
+ vlan: 124
+ - leaf: 102
+ path: eth1/3
+ vlan: 124
+ state: present
+ register: nm_add_stat1e1
+
+- name: Verify nm_add_stat1e1
+ assert:
+ that:
+ - nm_add_stat1e1 is changed
+ - nm_add_stat1e1.previous == []
+ - nm_add_stat1e1.current[0].deploymentImmediacy == 'immediate'
+ - nm_add_stat1e1.current[0].portEncapVlan == 126
+ - nm_add_stat1e1.current[0].path == 'topology/pod-1/paths-102/pathep-[eth1/2]'
+ - nm_add_stat1e1.current[0].mode == 'native'
+ - nm_add_stat1e1.current[0].type == 'port'
+ - nm_add_stat1e1.current[1].deploymentImmediacy == 'immediate'
+ - nm_add_stat1e1.current[1].portEncapVlan == 124
+ - nm_add_stat1e1.current[1].path == 'topology/pod-1/paths-101/pathep-[eth1/3]'
+ - nm_add_stat1e1.current[1].mode == 'native'
+ - nm_add_stat1e1.current[1].type == 'port'
+ - nm_add_stat1e1.current[2].portEncapVlan == 124
+ - nm_add_stat1e1.current[2].path == 'topology/pod-1/paths-102/pathep-[eth1/3]'
+ - nm_add_stat1e1.current|length ==3
+
+- name: Add static port 3 (vpc) to site EPG1 of AP1 (normal mode)
+ mso_schema_site_anp_epg_bulk_staticport:
+ <<: *add_static_port_1
+ static_ports:
+ - path: eth1/2
+ leaf: 102
+ - path: eth1/3
+ vlan: 124
+ - leaf: 102
+ path: eth1/3
+ vlan: 124
+ - path: ansible_polgrp
+ type: vpc
+ leaf: 103-104
+ vlan: 105
+ state: present
+ register: nm_add_stat3e1
+
+- name: Verify nm_add_stat3e1
+ assert:
+ that:
+ - nm_add_stat3e1 is changed
+ - nm_add_stat3e1.previous != []
+ - nm_add_stat3e1.current[3].deploymentImmediacy == 'immediate'
+ - nm_add_stat3e1.current[3].portEncapVlan == 105
+ - nm_add_stat3e1.current[3].path == 'topology/pod-1/protpaths-103-104/pathep-[ansible_polgrp]'
+ - nm_add_stat3e1.current[3].mode == 'native'
+ - nm_add_stat3e1.current[3].type == 'vpc'
+ - nm_add_stat3e1.current|length > nm_add_stat1e1.current|length # verifying length of current task (nm_add_stat3e1) is greater than the previous task (nm_add_stat1e1)
+
+- name: Add static port 2 (dpc) to EPG6 of AP2 (normal mode)
+ mso_schema_site_anp_epg_bulk_staticport:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP2
+ epg: EPG6
+ pod: pod-1
+ leaf: 101
+ path: eth1/1
+ vlan: 126
+ static_ports:
+ - path: eth1/2
+ pod: pod-2
+ leaf: 102
+ vlan: 100
+ deployment_immediacy: lazy
+ mode: regular
+ type: dpc
+ primary_micro_segment_vlan: 199
+ state: present
+ register: nm_add_stat2e6
+
+- name: Verify nm_add_stat2e6
+ assert:
+ that:
+ - nm_add_stat2e6 is changed
+ - nm_add_stat2e6.previous == []
+ - nm_add_stat2e6.current[0].deploymentImmediacy == 'lazy'
+ - nm_add_stat2e6.current[0].portEncapVlan == 100
+ - nm_add_stat2e6.current[0].microSegVlan == 199
+ - nm_add_stat2e6.current[0].path == 'topology/pod-2/paths-102/pathep-[eth1/2]'
+ - nm_add_stat2e6.current[0].mode == 'regular'
+ - nm_add_stat2e6.current[0].type == 'dpc'
+
+- name: Add static port 2 to site EPG2 (normal mode)
+ mso_schema_site_anp_epg_bulk_staticport: &static_port_2
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP2
+ epg: EPG2
+ pod: pod-1
+ leaf: 101
+ path: eth1/1
+ vlan: 100
+ mode: regular
+ type: port
+ deployment_immediacy: immediate
+ static_ports:
+ - path: eth1/2
+ pod: pod-2
+ leaf: 102
+ state: present
+ register: nm_add_stat2e2
+
+- name: Verify nm_add_stat2e2
+ assert:
+ that:
+ - nm_add_stat2e2 is changed
+ - nm_add_stat2e2.previous == []
+ - nm_add_stat2e2.current[0].deploymentImmediacy == 'immediate'
+ - nm_add_stat2e2.current[0].portEncapVlan == 100
+ - nm_add_stat2e2.current[0].path == 'topology/pod-2/paths-102/pathep-[eth1/2]'
+ - nm_add_stat2e2.current[0].mode == 'regular'
+ - nm_add_stat2e2.current[0].type == 'port'
+
+# ADD EXISTING STATIC PORT
+- name: Add static port 1 to site EPG1 again (normal mode)
+ mso_schema_site_anp_epg_bulk_staticport:
+ <<: *static_port_2
+ state: present
+ register: nm_add_stat1e1_2
+
+- name: Verify nm_add_stat1e1_2
+ assert:
+ that:
+ - nm_add_stat1e1_2 is not changed
+
+# ADD STATIC FEX PORT
+- name: Add static fex port to site EPG1 with AP1 (normal mode)
+ mso_schema_site_anp_epg_bulk_staticport:
+ <<: *static_port_2
+ static_ports:
+ - path: eth1/2
+ pod: pod-2
+ leaf: 102
+ - pod: pod-4
+ vlan: 126
+ fex: 151
+ mode: native
+ deployment_immediacy: lazy
+ state: present
+ register: nm_add_statfex
+
+- name: Verify nm_add_statfex
+ assert:
+ that:
+ - nm_add_statfex is changed
+ - nm_add_statfex.previous != []
+ - nm_add_statfex.current[1].deploymentImmediacy == 'lazy'
+ - nm_add_statfex.current[1].portEncapVlan == 126
+ - nm_add_statfex.current[1].path == 'topology/pod-4/paths-101/extpaths-151/pathep-[eth1/1]'
+ - nm_add_statfex.current[1].mode == 'native'
+ - nm_add_statfex.current[1].type == 'port'
+
+# QUERY STATIC PORTS
+- name: Query STATIC PORTS of site EPG1 with AP1 (normal mode)
+ mso_schema_site_anp_epg_bulk_staticport:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG1
+ state: query
+ register: nm_query_statse1
+
+- name: Verify nm_query_statse1
+ assert:
+ that:
+ - nm_query_statse1 is not changed
+
+#REMOVE STATIC PORT
+- name: Remove all static ports from EPG2 (normal mode)
+ mso_schema_site_anp_epg_bulk_staticport:
+ <<: *static_port_2
+ state: absent
+ register: nm_remove_stat2e2
+
+- name: Verify nm_remove_stat2e2
+ assert:
+ that:
+ - nm_remove_stat2e2 is changed
+
+- name: Remove all static ports from EPG2 again(normal mode)
+ mso_schema_site_anp_epg_bulk_staticport:
+ <<: *static_port_2
+ state: absent
+ ignore_errors: true
+ register: nm_remove_again_stat2e2
+
+- name: Verify nm_remove_again_stat2e2
+ assert:
+ that:
+ - nm_remove_again_stat2e2 is not changed
+
+# VERIFY NON EXISTENT 'DEPLOYMENT IMMEDIACY', 'TYPE' AND 'MODE'
+- name: Add static port 1 to site EPG4 with AP2 with no deployment immediacy, type and mode (normal mode)
+ mso_schema_site_anp_epg_bulk_staticport: &add_static_port
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP2
+ epg: EPG4
+ pod: pod-4
+ leaf: 101
+ path: eth1/1
+ vlan: 126
+ static_ports:
+ - path: eth1/2
+ leaf: 101
+ state: present
+ register: nm_add_stat_no_di_type_mode
+
+- name: Verify nm_add_stat_no_di_type_mode
+ assert:
+ that:
+ - nm_add_stat_no_di_type_mode.current[0].deploymentImmediacy == 'lazy'
+ - nm_add_stat_no_di_type_mode.current[0].mode == 'untagged'
+ - nm_add_stat_no_di_type_mode.current[0].type == 'port'
+
+# VERIFY NON EXISTENT parent values.
+- name: Add static port 1 to site EPG4 with AP2 with no parent values (normal mode)
+ mso_schema_site_anp_epg_bulk_staticport:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP2
+ epg: EPG4
+ static_ports:
+ - path: eth1/2
+ pod: pod-4
+ leaf: 101
+ vlan: 126
+ state: present
+ register: nm_add_stat_no_parent
+
+- name: Verify nm_add_stat_no_parent
+ assert:
+ that:
+ - nm_add_stat_no_parent.current[0].deploymentImmediacy == 'lazy'
+ - nm_add_stat_no_parent.current[0].mode == 'untagged'
+ - nm_add_stat_no_parent.current[0].type == 'port'
+
+# VERIFY NON EXISTENT required values.
+- name: Add static port 1 to site EPG4 with AP2 with missing required values (normal mode)
+ mso_schema_site_anp_epg_bulk_staticport:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP2
+ epg: EPG4
+ static_ports:
+ - mode: regular
+ state: present
+ ignore_errors: true
+ register: nm_add_stat_no_req
+
+- name: Verify nm_add_stat_no_req
+ assert:
+ that:
+ - nm_add_stat_no_req is not changed
+ - nm_add_stat_no_req.msg == "state is present but all of the following are missing{{':'}} pod, leaf, path, vlan."
+
+# VERIFY path in each leaf within a pod is unique (pod->leaf->path)
+- name: Add static port 1 to site EPG4 with AP2 with missing required values (normal mode)
+ mso_schema_site_anp_epg_bulk_staticport:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP2
+ epg: EPG4
+ static_ports:
+ - path: eth1/2
+ pod: pod-1
+ leaf: 101
+ vlan: 126
+ - path: eth1/2
+ pod: pod-1
+ leaf: 101
+ vlan: 127
+ state: present
+ ignore_errors: true
+ register: nm_add_unique_path
+
+- name: Verify nm_add_unique_path
+ assert:
+ that:
+ - nm_add_unique_path is not changed
+ - nm_add_unique_path.msg == "Each leaf in a pod of a static port should have an unique path."
+
+# USE NON-EXISTING EPG and ANP AT TEMPLATE LEVEL
+- name: Add static port 1 to non-existent site EPG5 (normal mode)
+ mso_schema_site_anp_epg_bulk_staticport:
+ <<: *add_static_port
+ anp: AP5
+ epg: EPG5
+ state: present
+ ignore_errors: true
+ register: nm_add_stat1e5
+
+- name: Verify nm_add_stat1e5
+ assert:
+ that:
+ - nm_add_stat1e5 is not changed
+
+# USE NON-EXISTING EPG AT TEMPLATE LEVEL
+- name: Add static port 1 to non-existent site EPG5 (normal mode)
+ mso_schema_site_anp_epg_bulk_staticport:
+ <<: *add_static_port
+ anp: AP1
+ epg: EPG6
+ state: present
+ ignore_errors: true
+ register: nm_add_stat1e6
+
+- name: Verify nm_add_stat1e6
+ assert:
+ that:
+ - nm_add_stat1e6 is not changed
+
+# USE A NON-EXISTING SCHEMA
+- name: Non-existing schema for static port
+ mso_schema_site_anp_epg_bulk_staticport:
+ <<: *add_static_port
+ schema: non_existing_schema
+ ignore_errors: true
+ register: nm_non_existing_schema
+
+- name: Verify non_existing_schema
+ assert:
+ that:
+ - nm_non_existing_schema is not changed
+ - nm_non_existing_schema.msg == "Provided schema 'non_existing_schema' does not exist."
+
+# USE A NON-EXISTING TEMPLATE
+- name: Non-existing template for static port (normal_mode)
+ mso_schema_site_anp_epg_bulk_staticport:
+ <<: *add_static_port
+ template: non_existing_template
+ ignore_errors: true
+ register: nm_non_existing_template
+
+- name: Verify non_existing_template
+ assert:
+ that:
+ - nm_non_existing_template is not changed
+ - nm_non_existing_template.msg == "Provided template 'non_existing_template' not matching existing template(s){{':'}} Template1, Template2"
+
+# USE A NON-EXISTING SITE
+- name: Non-existing site for static port (normal_mode)
+ mso_schema_site_anp_epg_bulk_staticport:
+ <<: *add_static_port
+ site: 'non_existing_site'
+ ignore_errors: true
+ register: nm_non_existing_site
+
+- name: Verify non_existing_site
+ assert:
+ that:
+ - nm_non_existing_site is not changed
+ - nm_non_existing_site.msg == "Site 'non_existing_site' is not a valid site name."
+
+# USE A TEMPLATE WITHOUT ANY SITE
+- name: Add site EPG static port association to Template 3 without any site associated (normal mode)
+ mso_schema_site_anp_epg_bulk_staticport:
+ <<: *add_static_port
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 3
+ ignore_errors: true
+ register: nm_no_site_associated
+
+- name: Verify cm_no_site_associated and nm_no_site_associated
+ assert:
+ that:
+ - nm_no_site_associated is not changed
+ - nm_no_site_associated.msg == "No sites associated with schema 'ansible_test_2'. Associate the site with the schema using (M) mso_schema_site."
+
+# Add static port 3 after adding ANP and EPG to site
+- name: Add a new site to a schema 2
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 3
+ state: present
+
+- name: Ensure VRF1 exists
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ vrf: VRF1
+ state: present
+
+- name: Add BD1
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ bd: BD1
+ vrf:
+ name: VRF1
+ state: present
+
+- name: Ensure Template 3 with AP1 exists
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ anp: AP1
+ state: present
+
+- name: Ensure Template 3 and AP1 with EPG1 exists
+ mso_schema_template_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ anp: AP1
+ epg: EPG1
+ bd:
+ name: BD1
+ vrf:
+ name: VRF1
+ state: present
+
+- name: Add new ANP to site
+ mso_schema_site_anp:
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ anp: AP1
+ state: present
+ register: cm_add_epg
+
+- name: Add new EPG to site after adding ANP to site
+ mso_schema_site_anp_epg:
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ anp: AP1
+ epg: EPG1
+ state: present
+ register: cm_add_epg
+
+- name: Add static port to site EPG1 in schema 2 (normal mode)
+ mso_schema_site_anp_epg_bulk_staticport:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 3
+ anp: AP1
+ epg: EPG1
+ pod: pod-1
+ leaf: 101
+ path: eth1/1
+ vlan: 100
+ mode: regular
+ type: port
+ deployment_immediacy: immediate
+ static_ports:
+ - path: eth1/2
+ pod: pod-2
+ leaf: 102
+ - vlan: 101
+ state: present
+ register: nm_add_stat3
+
+- name: Verify nm_add_stat3
+ assert:
+ that:
+ - nm_add_stat3 is changed
+ - nm_add_stat3.previous == []
+ - nm_add_stat3.current[0].deploymentImmediacy == 'immediate'
+ - nm_add_stat3.current[0].portEncapVlan == 100
+ - nm_add_stat3.current[0].path == 'topology/pod-2/paths-102/pathep-[eth1/2]'
+ - nm_add_stat3.current[0].mode == 'regular'
+ - nm_add_stat3.current[0].type == 'port'
+
+- name: Add static port to site EPG1 in schema 2 again without static_ports(normal mode)
+ mso_schema_site_anp_epg_bulk_staticport:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 3
+ anp: AP1
+ epg: EPG1
+ pod: pod-1
+ leaf: 101
+ path: eth1/1
+ vlan: 100
+ mode: regular
+ type: port
+ deployment_immediacy: immediate
+ state: present
+ ignore_errors: true
+ register: nm_add_no_static_port
+
+- name: Verify nm_add_no_static_port
+ assert:
+ that:
+ - nm_add_no_static_port is not changed
+ - nm_add_no_static_port.msg == "state is present but all of the following are missing{{':'}} static_ports"
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_domain/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_domain/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_domain/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_domain/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_domain/tasks/main.yml
new file mode 100644
index 000000000..24b953a0f
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_domain/tasks/main.yml
@@ -0,0 +1,1068 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case)
+# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Remove Schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Ensure site exists
+ mso_site:
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ apic_username }}'
+ apic_password: '{{ apic_password }}'
+ apic_site_id: '{{ apic_site_id | default(101) }}'
+ urls:
+ - https://{{ apic_hostname }}
+ state: present
+
+- name: Ensure sites removed from tenant ansible_test
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_site | default("ansible_test") }}'
+ - 'aws_{{ mso_site | default("ansible_test") }}'
+ - 'azure_{{ mso_site | default("ansible_test") }}'
+ ignore_errors: true
+
+- name: Ensure tenant ansible_test exist
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ state: present
+
+- name: Ensure schema 1 with Template 1 and 2 exists
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: '{{ item }}'
+ state: present
+ loop:
+ - Template 1
+ - Template 2
+
+- name: Ensure schema 2 with Template 3 exists
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template 3
+ state: present
+
+- name: Add a new site to a schema
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ state: present
+
+- name: Ensure VRF1 exists
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF1
+ state: present
+
+- name: Add BD1
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ bd: BD1
+ vrf:
+ name: VRF1
+ state: present
+
+- name: Ensure Template 1 with AP1 exists
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ state: present
+
+- name: Ensure Template 1 and AP1 with EPG1 exists
+ mso_schema_template_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG1
+ bd:
+ name: BD1
+ vrf:
+ name: VRF1
+ state: present
+
+- name: Ensure Template 1 and AP1 with EPG3 exists
+ mso_schema_template_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG3
+ bd:
+ name: BD1
+ vrf:
+ name: VRF1
+ state: present
+
+- name: Ensure Template 1 with AP2 exists
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: AP2
+ state: present
+
+- name: Ensure Template 1 and AP2 with EPG2 exists
+ mso_schema_template_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: AP2
+ epg: EPG2
+ bd:
+ name: BD1
+ vrf:
+ name: VRF1
+ state: present
+
+- name: Ensure Template 1 and AP2 with EPG4 exists
+ mso_schema_template_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: AP2
+ epg: EPG4
+ bd:
+ name: BD1
+ vrf:
+ name: VRF1
+ state: present
+
+# ADD DOMAINS
+- name: Add domain 1 to site EPG1 with AP1 (check mode)
+ mso_schema_site_anp_epg_domain:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG1
+ domain_association_type: vmmDomain
+ domain_profile: VMware-VMM
+ deployment_immediacy: lazy
+ resolution_immediacy: pre-provision
+ state: present
+ check_mode: true
+ register: cm_add_dom1e1
+
+- name: Verify cm_add_dom1e1
+ assert:
+ that:
+ - cm_add_dom1e1 is changed
+ - cm_add_dom1e1.previous == {}
+ - cm_add_dom1e1.current.deploymentImmediacy == 'lazy'
+ - cm_add_dom1e1.current.domainType == 'vmmDomain'
+ - cm_add_dom1e1.current.dn == 'uni/vmmp-VMware/dom-VMware-VMM'
+ - cm_add_dom1e1.current.resolutionImmediacy == 'pre-provision'
+
+- name: Add domain 1 to site EPG1 with AP1 (normal mode)
+ mso_schema_site_anp_epg_domain:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG1
+ domain_association_type: vmmDomain
+ domain_profile: 'VMware-VMM'
+ deployment_immediacy: lazy
+ resolution_immediacy: pre-provision
+ state: present
+ register: nm_add_dom1e1
+
+- name: Verify nm_add_dom1e1
+ assert:
+ that:
+ - nm_add_dom1e1 is changed
+ - nm_add_dom1e1.previous == {}
+ - nm_add_dom1e1.current.deploymentImmediacy == 'lazy'
+ - nm_add_dom1e1.current.domainType == 'vmmDomain'
+ - nm_add_dom1e1.current.dn == 'uni/vmmp-VMware/dom-VMware-VMM'
+ - nm_add_dom1e1.current.resolutionImmediacy == 'pre-provision'
+
+- name: Add domain 2 to site EPG1 with AP1 (normal mode)
+ mso_schema_site_anp_epg_domain:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG1
+ domain_association_type: physicalDomain
+ domain_profile: phys
+ deployment_immediacy: lazy
+ resolution_immediacy: pre-provision
+ state: present
+ register: nm_add_dom2e1
+
+- name: Verify nm_add_dom2e1
+ assert:
+ that:
+ - nm_add_dom2e1 is changed
+ - nm_add_dom2e1.previous == {}
+ - nm_add_dom2e1.current.deploymentImmediacy == 'lazy'
+ - nm_add_dom2e1.current.domainType == 'physicalDomain'
+ - nm_add_dom2e1.current.dn == 'uni/phys-phys'
+ - nm_add_dom2e1.current.resolutionImmediacy == 'pre-provision'
+
+- name: Add domain 3 to site EPG1 with AP1 (normal mode)
+ mso_schema_site_anp_epg_domain:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG1
+ domain_association_type: physicalDomain
+ domain_profile: phys
+ deployment_immediacy: lazy
+ resolution_immediacy: lazy
+ state: present
+ register: nm_add_dom3e1
+
+- name: Verify nm_add_dom3e1
+ assert:
+ that:
+ - nm_add_dom3e1 is changed
+ - nm_add_dom3e1.previous != {}
+ - nm_add_dom3e1.current.deploymentImmediacy == 'lazy'
+ - nm_add_dom3e1.current.domainType == 'physicalDomain'
+ - nm_add_dom3e1.current.dn == 'uni/phys-phys'
+ - nm_add_dom3e1.current.resolutionImmediacy == 'lazy'
+
+- name: Add domain1 to site EPG3 with AP1 (normal mode)
+ mso_schema_site_anp_epg_domain:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG3
+ domain_association_type: vmmDomain
+ domain_profile: 'VMware-VMM'
+ deployment_immediacy: lazy
+ resolution_immediacy: lazy
+ state: present
+ register: nm_add_dom1e3
+
+- name: Verify nm_add_dom1e2
+ assert:
+ that:
+ - nm_add_dom1e3 is changed
+ - nm_add_dom1e3.previous == {}
+ - nm_add_dom1e3.current.deploymentImmediacy == 'lazy'
+ - nm_add_dom1e3.current.domainType == 'vmmDomain'
+ - nm_add_dom1e3.current.dn == 'uni/vmmp-VMware/dom-VMware-VMM'
+ - nm_add_dom1e3.current.resolutionImmediacy == 'lazy'
+
+- name: Add domain 2 to EPG2 (check mode)
+ mso_schema_site_anp_epg_domain:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP2
+ epg: EPG2
+ domain_association_type: physicalDomain
+ domain_profile: phys
+ deployment_immediacy: lazy
+ resolution_immediacy: pre-provision
+ state: present
+ check_mode: true
+ register: cm_add_dom2e2
+
+- name: Verify cm_add_dom2e2
+ assert:
+ that:
+ - cm_add_dom2e2 is changed
+ - cm_add_dom2e2.previous == {}
+ - cm_add_dom2e2.current.deploymentImmediacy == 'lazy'
+ - cm_add_dom2e2.current.domainType == 'physicalDomain'
+ - cm_add_dom2e2.current.dn == 'uni/phys-phys'
+ - cm_add_dom2e2.current.resolutionImmediacy == 'pre-provision'
+
+# QUERY DOMAINS
+- name: Query domains of site EPG1 with AP1 (normal mode)
+ mso_schema_site_anp_epg_domain:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG1
+ state: query
+ register: nm_query_domse1
+
+- name: Verify nm_query_domse1
+ assert:
+ that:
+ - nm_query_domse1 is not changed
+
+# QUERY A DOMAIN
+- name: Query domain3 of site EPG1 with AP1 (normal mode)
+ mso_schema_site_anp_epg_domain:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG1
+ domain_association_type: physicalDomain
+ domain_profile: phys
+ deployment_immediacy: lazy
+ resolution_immediacy: pre-provision
+ state: query
+ register: nm_query_dom3e1
+
+- name: Verify nm_query_dom3e1
+ assert:
+ that:
+ - nm_query_dom3e1 is not changed
+
+# QUERY REMOVED DOMAIN
+- name: Add domain 2 to site EPG2 (normal mode)
+ mso_schema_site_anp_epg_domain:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP2
+ epg: EPG2
+ domain_association_type: physicalDomain
+ domain_profile: phys
+ deployment_immediacy: lazy
+ resolution_immediacy: pre-provision
+ state: present
+ register: nm_add_dom2e2
+
+- name: Verify nm_add_dom2e2
+ assert:
+ that:
+ - nm_add_dom2e2 is changed
+ - nm_add_dom2e2.previous == {}
+ - nm_add_dom2e2.current.deploymentImmediacy == 'lazy'
+ - nm_add_dom2e2.current.domainType == 'physicalDomain'
+ - nm_add_dom2e2.current.dn == 'uni/phys-phys'
+ - nm_add_dom2e2.current.resolutionImmediacy == 'pre-provision'
+
+- name: Remove domain2 from EPG2 (normal mode)
+ mso_schema_site_anp_epg_domain:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP2
+ epg: EPG2
+ domain_association_type: physicalDomain
+ domain_profile: phys
+ deployment_immediacy: lazy
+ resolution_immediacy: pre-provision
+ state: absent
+ register: nm_remove_dom2e2
+
+- name: Verify nm_remove_dom2e2
+ assert:
+ that:
+ - nm_remove_dom2e2 is changed
+
+- name: Query removed domain2 from EPG2 (normal mode)
+ mso_schema_site_anp_epg_domain:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP2
+ epg: EPG2
+ domain_association_type: physicalDomain
+ domain_profile: phys
+ deployment_immediacy: lazy
+ resolution_immediacy: pre-provision
+ state: query
+ ignore_errors: true
+ register: nm_non_existent_dom2e2
+
+- name: Verify non_existing_domain
+ assert:
+ that:
+ - nm_non_existent_dom2e2 is not changed
+ - nm_non_existent_dom2e2.msg == "Domain association 'physicalDomain/phys' not found"
+
+- name: Remove domain2 from EPG2 again(normal mode)
+ mso_schema_site_anp_epg_domain:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP2
+ epg: EPG2
+ domain_association_type: physicalDomain
+ domain_profile: phys
+ deployment_immediacy: lazy
+ resolution_immediacy: pre-provision
+ state: absent
+ ignore_errors: true
+ register: nm_remove_again_dom2e2
+
+- name: Verify nm_remove_again_dom2e2
+ assert:
+ that:
+ - nm_remove_again_dom2e2 is not changed
+
+# ADD EXISTING DOMAIN
+- name: Add domain 1 to site EPG1 again (normal mode)
+ mso_schema_site_anp_epg_domain:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG1
+ domain_association_type: vmmDomain
+ domain_profile: 'VMware-VMM'
+ deployment_immediacy: lazy
+ resolution_immediacy: pre-provision
+ state: present
+ register: nm_add_dom1e1_2
+
+- name: Verify nm_add_dom1e1_2
+ assert:
+ that:
+ - nm_add_dom1e1_2 is not changed
+
+# ADD DOMAIN WITH NO STATE
+- name: Add domain 1 to site EPG1 again with no state (normal mode)
+ mso_schema_site_anp_epg_domain:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG1
+ domain_association_type: vmmDomain
+ domain_profile: 'VMware-VMM'
+ deployment_immediacy: lazy
+ resolution_immediacy: pre-provision
+ ignore_errors: true
+ register: nm_add_stateless_dom1e1_2
+
+- name: Verify nm_add_stateless_dom1e1_2
+ assert:
+ that:
+ - nm_add_stateless_dom1e1_2 is not changed
+
+# ADD OTHER DOMAIN OPTIONS
+- name: Add domain l3ExtDomain to site EPG1 with AP1 (normal mode)
+ mso_schema_site_anp_epg_domain:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG1
+ domain_association_type: l3ExtDomain
+ domain_profile: 'ansible_l3domain'
+ deployment_immediacy: lazy
+ resolution_immediacy: lazy
+ state: present
+ register: nm_add_doml3
+
+- name: Verify nm_add_doml3
+ assert:
+ that:
+ - nm_add_doml3 is changed
+ - nm_add_doml3.previous == {}
+ - nm_add_doml3.current.deploymentImmediacy == 'lazy'
+ - nm_add_doml3.current.domainType == 'l3ExtDomain'
+ - nm_add_doml3.current.dn =='uni/l3dom-ansible_l3domain'
+ - nm_add_doml3.current.resolutionImmediacy == 'lazy'
+
+- name: Add domain l2ExtDomain to site EPG1 with AP1 (normal mode)
+ mso_schema_site_anp_epg_domain:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG1
+ domain_association_type: l2ExtDomain
+ domain_profile: 'ansible_l2domain'
+ deployment_immediacy: lazy
+ resolution_immediacy: lazy
+ state: present
+ ignore_errors: true
+ register: nm_add_doml2
+
+- name: Verify nm_add_doml2
+ assert:
+ that:
+ - nm_add_doml2 is changed
+ - nm_add_doml2.previous == {}
+ - nm_add_doml2.current.deploymentImmediacy == 'lazy'
+ - nm_add_doml2.current.domainType == 'l2ExtDomain'
+ - nm_add_doml2.current.dn =='uni/l2dom-ansible_l2domain'
+ - nm_add_doml2.current.resolutionImmediacy == 'lazy'
+
+- name: Add domain fibreChannel to site EPG1 with AP1 (normal mode)
+ mso_schema_site_anp_epg_domain:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG1
+ domain_association_type: fibreChannelDomain
+ domain_profile: 'ansible_fibreChanneldomain'
+ deployment_immediacy: lazy
+ resolution_immediacy: lazy
+ state: present
+ register: nm_add_domfc
+
+- name: Verify nm_add_domfc
+ assert:
+ that:
+ - nm_add_domfc is changed
+ - nm_add_domfc.previous == {}
+ - nm_add_domfc.current.domainType == 'fibreChannelDomain'
+ - nm_add_domfc.current.dn =='uni/fc-ansible_fibreChanneldomain'
+ - nm_add_domfc.current.resolutionImmediacy == 'lazy'
+ - nm_add_domfc.current.deploymentImmediacy == 'lazy'
+
+# USE OTHER ATTRIBUTES OF VMM DOMAIN
+- name: Add domain vmm to site EPG2 with AP2 (normal mode)
+ mso_schema_site_anp_epg_domain: &domain_ap1_epg2
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP2
+ epg: EPG2
+ domain_association_type: vmmDomain
+ domain_profile: 'VMware-VMM'
+ deployment_immediacy: immediate
+ resolution_immediacy: lazy
+ micro_seg_vlan_type: 'vlan'
+ micro_seg_vlan: 100
+ port_encap_vlan_type: 'vlan'
+ port_encap_vlan: 100
+ vlan_encap_mode: static
+ allow_micro_segmentation: true
+ switch_type: 'default'
+ switching_mode: native
+ enhanced_lagpolicy_name: 'ansible_check_name'
+ enhanced_lagpolicy_dn: 'ansible_check'
+ state: present
+ register: nm_add_domvmprop
+
+- name: Query domain vmms attached to site EPG2 with AP2
+ mso_schema_site_anp_epg_domain:
+ <<: *domain_ap1_epg2
+ state: query
+ register: nm_query_domvmprop
+
+- name: Verify domain vmm to site EPG2 with AP2
+ assert:
+ that:
+ - nm_add_domvmprop is changed
+ - nm_add_domvmprop.previous == {}
+ - nm_add_domvmprop.current.allowMicroSegmentation == true
+ - nm_add_domvmprop.current.deploymentImmediacy == "immediate"
+ - nm_add_domvmprop.current.deployImmediacy == "immediate"
+ - nm_add_domvmprop.current.dn == "uni/vmmp-VMware/dom-VMware-VMM"
+ - nm_add_domvmprop.current.epgLagPol.enhancedLagPol.dn == "ansible_check"
+ - nm_add_domvmprop.current.epgLagPol.enhancedLagPol.name == "ansible_check_name"
+ - nm_add_domvmprop.current.microSegVlan.vlan == 100
+ - nm_add_domvmprop.current.microSegVlan.vlanType == "vlan"
+ - nm_add_domvmprop.current.portEncapVlan.vlan == 100
+ - nm_add_domvmprop.current.portEncapVlan.vlanType == "vlan"
+ - nm_add_domvmprop.current.resolutionImmediacy == "lazy"
+ - nm_add_domvmprop.current.switchType == "default"
+ - nm_add_domvmprop.current.switchingMode == "native"
+ - nm_add_domvmprop.current.vlanEncapMode == "static"
+ - nm_add_domvmprop.current.vmmDomainProperties.allowMicroSegmentation == true
+ - nm_add_domvmprop.current.vmmDomainProperties.epgLagPol.enhancedLagPol.dn == "ansible_check"
+ - nm_add_domvmprop.current.vmmDomainProperties.epgLagPol.enhancedLagPol.name == "ansible_check_name"
+ - nm_add_domvmprop.current.vmmDomainProperties.microSegVlan.vlan == 100
+ - nm_add_domvmprop.current.vmmDomainProperties.microSegVlan.vlanType == "vlan"
+ - nm_add_domvmprop.current.vmmDomainProperties.portEncapVlan.vlan == 100
+ - nm_add_domvmprop.current.vmmDomainProperties.portEncapVlan.vlanType == "vlan"
+ - nm_add_domvmprop.current.vmmDomainProperties.switchType == "default"
+ - nm_add_domvmprop.current.vmmDomainProperties.switchingMode == "native"
+ - nm_add_domvmprop.current.vmmDomainProperties.vlanEncapMode == "static"
+ - nm_query_domvmprop.current.allowMicroSegmentation == true
+ - nm_query_domvmprop.current.deployImmediacy == "immediate"
+ - nm_query_domvmprop.current.dn == "uni/vmmp-VMware/dom-VMware-VMM"
+ - nm_query_domvmprop.current.epgLagPol.enhancedLagPol.dn == "ansible_check"
+ - nm_query_domvmprop.current.epgLagPol.enhancedLagPol.name == "ansible_check_name"
+ - nm_query_domvmprop.current.microSegVlan.vlan == 100
+ - nm_query_domvmprop.current.microSegVlan.vlanType == "vlan"
+ - nm_query_domvmprop.current.portEncapVlan.vlan == 100
+ - nm_query_domvmprop.current.portEncapVlan.vlanType == "vlan"
+ - nm_query_domvmprop.current.resolutionImmediacy == "lazy"
+ - nm_query_domvmprop.current.switchType == "default"
+ - nm_query_domvmprop.current.switchingMode == "native"
+ - nm_query_domvmprop.current.vlanEncapMode == "static"
+
+- name: Add another domain vmm to site EPG2 with AP2 (normal mode)
+ mso_schema_site_anp_epg_domain:
+ <<: *domain_ap1_epg2
+ domain_profile: 'TEST'
+ state: present
+ register: nm_add_another_domvmprop
+
+- name: Query all domains vmms attached to site EPG2 with AP2
+ mso_schema_site_anp_epg_domain:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP2
+ epg: EPG2
+ state: query
+ register: nm_query_another_domvmprop
+
+- name: Verify domain vmm to site EPG2 with AP2
+ assert:
+ that:
+ - nm_add_another_domvmprop is changed
+ - nm_query_another_domvmprop is not changed
+ - nm_query_another_domvmprop.current | length == 2
+
+- name: Add domain vmm to site EPG4 with AP2 (normal mode)
+ mso_schema_site_anp_epg_domain:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP2
+ epg: EPG4
+ domain_association_type: vmmDomain
+ domain_profile: 'VMware-VMM'
+ deployment_immediacy: lazy
+ resolution_immediacy: lazy
+ micro_seg_vlan: 100
+ port_encap_vlan_type: 'vlan'
+ port_encap_vlan: 100
+ vlan_encap_mode: static
+ allow_micro_segmentation: true
+ switch_type: 'default'
+ switching_mode: native
+ enhanced_lagpolicy_name: 'ansible_check'
+ enhanced_lagpolicy_dn: 'ansible_check'
+ state: present
+ ignore_errors: true
+ register: nm_add_domvmprop1
+
+- name: Verify nm_add_domvmprop1
+ assert:
+ that:
+ - nm_add_domvmprop1 is not changed
+ - nm_add_domvmprop1.previous == {}
+ - nm_add_domvmprop1.msg == "micro_seg_vlan_type is required when micro_seg_vlan is provided."
+
+- name: Add domain vmm to site EPG4 with AP2 (normal mode)
+ mso_schema_site_anp_epg_domain:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP2
+ epg: EPG4
+ domain_association_type: vmmDomain
+ domain_profile: 'VMware-VMM'
+ deployment_immediacy: lazy
+ resolution_immediacy: lazy
+ micro_seg_vlan_type: 'vlan'
+ port_encap_vlan_type: 'vlan'
+ port_encap_vlan: 100
+ vlan_encap_mode: static
+ allow_micro_segmentation: true
+ switch_type: 'default'
+ switching_mode: native
+ enhanced_lagpolicy_name: 'ansible_check'
+ enhanced_lagpolicy_dn: 'ansible_check'
+ state: present
+ ignore_errors: true
+ register: nm_add_domvmprop2
+
+- name: Verify nm_add_domvmprop2
+ assert:
+ that:
+ - nm_add_domvmprop2 is not changed
+ - nm_add_domvmprop2.msg == "micro_seg_vlan is required when micro_seg_vlan_type is provided."
+
+- name: Add domain vmm to site EPG4 with AP2 (normal mode)
+ mso_schema_site_anp_epg_domain:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP2
+ epg: EPG4
+ domain_association_type: vmmDomain
+ domain_profile: 'VMware-VMM'
+ deployment_immediacy: lazy
+ resolution_immediacy: lazy
+ micro_seg_vlan_type: 'vlan'
+ micro_seg_vlan: 100
+ port_encap_vlan: 100
+ vlan_encap_mode: static
+ allow_micro_segmentation: true
+ switch_type: 'default'
+ switching_mode: native
+ enhanced_lagpolicy_name: 'ansible_check'
+ enhanced_lagpolicy_dn: ''
+ state: present
+ ignore_errors: true
+ register: nm_add_domvmprop3
+
+- name: Verify nm_add_domvmprop3
+ assert:
+ that:
+ - nm_add_domvmprop3 is not changed
+ - nm_add_domvmprop3.msg == "port_encap_vlan_type is required when port_encap_vlan is provided."
+
+- name: Add domain vmm to site EPG4 with AP2 (normal mode)
+ mso_schema_site_anp_epg_domain:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP2
+ epg: EPG4
+ domain_association_type: vmmDomain
+ domain_profile: 'VMware-VMM'
+ deployment_immediacy: lazy
+ resolution_immediacy: lazy
+ micro_seg_vlan_type: 'vlan'
+ micro_seg_vlan: 100
+ port_encap_vlan_type: 'vlan'
+ vlan_encap_mode: static
+ allow_micro_segmentation: true
+ switch_type: 'default'
+ switching_mode: native
+ enhanced_lagpolicy_name: 'ansible_check'
+ enhanced_lagpolicy_dn: 'ansible_check'
+ state: present
+ ignore_errors: true
+ register: nm_add_domvmprop4
+
+- name: Verify nm_add_domvmprop4
+ assert:
+ that:
+ - nm_add_domvmprop4 is not changed
+ - nm_add_domvmprop4.msg == "port_encap_vlan is required when port_encap_vlan_type is provided."
+
+- name: Add domain vmm to site EPG4 with AP2 (normal mode)
+ mso_schema_site_anp_epg_domain:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP2
+ epg: EPG4
+ domain_association_type: vmmDomain
+ domain_profile: 'VMware-VMM'
+ deployment_immediacy: lazy
+ resolution_immediacy: lazy
+ micro_seg_vlan_type: 'vlan'
+ micro_seg_vlan: 100
+ port_encap_vlan_type: 'vlan'
+ port_encap_vlan: 100
+ vlan_encap_mode: static
+ allow_micro_segmentation: true
+ switch_type: 'default'
+ switching_mode: native
+ enhanced_lagpolicy_dn: 'ansible_check'
+ state: present
+ ignore_errors: true
+ register: nm_add_domvmprop5
+
+- name: Verify nm_add_domvmprop5
+ assert:
+ that:
+ - nm_add_domvmprop5 is not changed
+ - nm_add_domvmprop5.msg == "enhanced_lagpolicy_name is required when enhanced_lagpolicy_dn is provided."
+
+- name: Add domain vmm to site EPG4 with AP2 (normal mode)
+ mso_schema_site_anp_epg_domain:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP2
+ epg: EPG4
+ domain_association_type: vmmDomain
+ domain_profile: 'VMware-VMM'
+ deployment_immediacy: lazy
+ resolution_immediacy: lazy
+ micro_seg_vlan_type: 'vlan'
+ micro_seg_vlan: 100
+ port_encap_vlan_type: 'vlan'
+ port_encap_vlan: 100
+ vlan_encap_mode: static
+ allow_micro_segmentation: true
+ switch_type: 'default'
+ switching_mode: native
+ enhanced_lagpolicy_name: 'ansible_check'
+ state: present
+ ignore_errors: true
+ register: nm_add_domvmprop6
+
+- name: Verify nm_add_domvmprop6
+ assert:
+ that:
+ - nm_add_domvmprop6 is not changed
+ - nm_add_domvmprop6.msg == "enhanced_lagpolicy_dn is required when enhanced_lagpolicy_name is provided."
+
+# USE NON-EXISTING EPG and ANP AT TEMPLATE LEVEL
+- name: Add domain 1 to non-existent site EPG5 (normal mode)
+ mso_schema_site_anp_epg_domain:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP5
+ epg: EPG5
+ domain_association_type: vmmDomain
+ domain_profile: 'VMware-VMM'
+ deployment_immediacy: lazy
+ resolution_immediacy: pre-provision
+ state: present
+ ignore_errors: true
+ register: nm_add_dom1e5
+
+- name: Verify nm_add_dom1e5
+ assert:
+ that:
+ - nm_add_dom1e5 is not changed
+
+# USE NON-EXISTING EPG AT TEMPLATE LEVEL
+- name: Add domain 1 to non-existent site EPG5 (normal mode)
+ mso_schema_site_anp_epg_domain:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG6
+ domain_association_type: vmmDomain
+ domain_profile: 'VMware-VMM'
+ deployment_immediacy: lazy
+ resolution_immediacy: pre-provision
+ state: present
+ ignore_errors: true
+ register: nm_add_dom1e6
+
+- name: Verify nm_add_dom1e6
+ assert:
+ that:
+ - nm_add_dom1e6 is not changed
+
+# USE A NON-EXISTING SCHEMA
+- name: Non-existing schema for domain (check_mode)
+ mso_schema_site_anp_epg_domain:
+ <<: *mso_info
+ schema: non_existing_schema
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG1
+ domain_association_type: vmmDomain
+ domain_profile: 'VMware-VMM'
+ deployment_immediacy: lazy
+ resolution_immediacy: pre-provision
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_schema
+
+- name: Non-existing schema for domain (normal_mode)
+ mso_schema_site_anp_epg_domain:
+ <<: *mso_info
+ schema: non_existing_schema
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG1
+ domain_association_type: vmmDomain
+ domain_profile: 'VMware-VMM'
+ deployment_immediacy: lazy
+ resolution_immediacy: pre-provision
+ ignore_errors: true
+ register: nm_non_existing_schema
+
+- name: Verify non_existing_schema
+ assert:
+ that:
+ - cm_non_existing_schema is not changed
+ - nm_non_existing_schema is not changed
+ - cm_non_existing_schema == nm_non_existing_schema
+ - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non_existing_schema' does not exist."
+
+# USE A NON-EXISTING TEMPLATE
+- name: Non-existing template for domain (check_mode)
+ mso_schema_site_anp_epg_domain:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: non_existing_template
+ anp: AP1
+ epg: EPG1
+ domain_association_type: vmmDomain
+ domain_profile: 'VMware-VMM'
+ deployment_immediacy: lazy
+ resolution_immediacy: pre-provision
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_template
+
+- name: Non-existing template for domain (normal_mode)
+ mso_schema_site_anp_epg_domain:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: non_existing_template
+ anp: AP1
+ epg: EPG1
+ domain_association_type: vmmDomain
+ domain_profile: 'VMware-VMM'
+ deployment_immediacy: lazy
+ resolution_immediacy: pre-provision
+ ignore_errors: true
+ register: nm_non_existing_template
+
+- name: Verify non_existing_template
+ assert:
+ that:
+ - cm_non_existing_template is not changed
+ - nm_non_existing_template is not changed
+ - cm_non_existing_template == nm_non_existing_template
+ - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non_existing_template' does not exist. Existing templates{{':'}} Template1, Template2"
+
+# USE A NON-EXISTING SITE
+- name: Non-existing site for domain (check_mode)
+ mso_schema_site_anp_epg_domain:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 2
+ anp: AP1
+ epg: EPG1
+ domain_association_type: vmmDomain
+ domain_profile: 'VMware-VMM'
+ deployment_immediacy: lazy
+ resolution_immediacy: pre-provision
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_site
+
+- name: Non-existing site for domain (normal_mode)
+ mso_schema_site_anp_epg_domain:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 2
+ anp: AP1
+ epg: EPG1
+ domain_association_type: vmmDomain
+ domain_profile: 'VMware-VMM'
+ deployment_immediacy: lazy
+ resolution_immediacy: pre-provision
+ ignore_errors: true
+ register: nm_non_existing_site
+
+- name: Verify non_existing_site
+ assert:
+ that:
+ - cm_non_existing_site is not changed
+ - nm_non_existing_site is not changed
+ - cm_non_existing_site == nm_non_existing_site
+ - cm_non_existing_site.msg is match("Provided site/siteId/template 'ansible_test/[0-9a-zA-Z]*/Template2' does not exist. Existing siteIds/templates{{':'}} [0-9a-zA-Z]*/Template1")
+ - nm_non_existing_site.msg is match("Provided site/siteId/template 'ansible_test/[0-9a-zA-Z]*/Template2' does not exist. Existing siteIds/templates{{':'}} [0-9a-zA-Z]*/Template1")
+
+# USE A TEMPLATE WITHOUT ANY SITE
+- name: Add site EPG domain association to Schema 2 Template 3 without any site associated (check mode)
+ mso_schema_site_anp_epg_domain:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 3
+ anp: AP1
+ epg: EPG1
+ domain_association_type: vmmDomain
+ domain_profile: 'VMware-VMM'
+ deployment_immediacy: lazy
+ resolution_immediacy: pre-provision
+ ignore_errors: true
+ check_mode: true
+ register: cm_no_site_associated
+
+- name: Add site EPG domain association to Template 3 without any site associated (normal mode)
+ mso_schema_site_anp_epg_domain:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 3
+ anp: AP1
+ epg: EPG1
+ domain_association_type: vmmDomain
+ domain_profile: 'VMware-VMM'
+ deployment_immediacy: lazy
+ resolution_immediacy: pre-provision
+ ignore_errors: true
+ register: nm_no_site_associated
+
+- name: Verify cm_no_site_associated and nm_no_site_associated
+ assert:
+ that:
+ - cm_no_site_associated is not changed
+ - nm_no_site_associated is not changed
+ - cm_no_site_associated.msg == nm_no_site_associated.msg == "No site associated with template 'Template3'. Associate the site with the template using mso_schema_site." \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_selector/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_selector/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_selector/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_selector/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_selector/tasks/main.yml
new file mode 100644
index 000000000..ec69a7c47
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_selector/tasks/main.yml
@@ -0,0 +1,1103 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com>
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case)
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Remove schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Ensure azure site exists
+ mso_site:
+ <<: *mso_info
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ azure_apic_username }}'
+ apic_password: '{{ azure_apic_password }}'
+ apic_site_id: '{{ azure_site_id | default(103) }}'
+ urls:
+ - https://{{ azure_apic_hostname }}
+ state: present
+
+- name: Ensure aws site exists
+ mso_site:
+ <<: *mso_info
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ aws_apic_username }}'
+ apic_password: '{{ aws_apic_password }}'
+ apic_site_id: '{{ aws_site_id | default(102) }}'
+ urls:
+ - https://{{ aws_apic_hostname }}
+ state: present
+
+- name: Ensure sites removed from tenant ansible_test
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_site | default("ansible_test") }}'
+ - 'aws_{{ mso_site | default("ansible_test") }}'
+ - 'azure_{{ mso_site | default("ansible_test") }}'
+
+- name: Ensure tenant ansible_test exists
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ # sites:
+ # - '{{ mso_site | default("ansible_test") }}'
+ state: present
+
+- name: Associate aws site with ansible_test in normal mode
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ cloud_account: "000000000000"
+ aws_trusted: false
+ aws_access_key: "1"
+ secret_key: "0"
+ state: present
+ register: aaws_nm
+
+- name: Associate azure site with access_type not present, with ansible_test in normal mode
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ cloud_account: uni/tn-ansible_test/act-[100]-vendor-azure
+ state: present
+ register: aazure_shared_nm
+
+- name: Ensure schema 1 with Template 1, and Template 2, Template 3 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: '{{item.template}}'
+ state: present
+ loop:
+ - { template: Template 1}
+ - { template: Template 2}
+ - { template: Template 3}
+
+- name: Ensure schema 2 with Template 3 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template 3
+ state: present
+
+- name: Add aws site to a schema
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: '{{item.template}}'
+ state: present
+ loop:
+ - { template: Template 1}
+ - { template: Template 2}
+ when: version.current.version is version('3', '<')
+
+- name: Add azure site to a schema
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ template: '{{item.template}}'
+ state: present
+ loop:
+ - { template: Template 1}
+ - { template: Template 2}
+ when: version.current.version is version('3', '<')
+
+- name: Ensure VRF1 exists
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF1
+ state: present
+
+- name: Ensure ANP exist
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ item.schema }}'
+ template: '{{ item.template }}'
+ anp: ANP
+ state: present
+ loop:
+ - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 1' }
+ - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 2' }
+
+- name: Add a new CIDR in VRF1 at site level
+ mso_schema_site_vrf_region_cidr: &mso_present
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: '{{ item }}'
+ vrf: VRF1
+ region: us-west-1
+ cidr: 10.0.0.0/16
+ primary: true
+ state: present
+ loop:
+ - 'aws_{{ mso_site | default("ansible_test") }}'
+ - 'azure_{{ mso_site | default("ansible_test") }}'
+
+# ADD EPGs
+- name: Ensure EPGs exist
+ mso_schema_template_anp_epg:
+ <<: *mso_info
+ schema: '{{ item.schema }}'
+ template: '{{ item.template }}'
+ anp: ANP
+ epg: '{{ item.epg }}'
+ vrf:
+ name: VRF1
+ schema: ansible_test
+ template: Template 1
+ state: present
+ loop:
+ - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 1', epg: 'ansible_test_1' }
+ - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 1', epg: 'ansible_test_2' }
+
+- name: Add Selector to EPG (normal_mode)
+ mso_schema_template_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: selector_1
+ state: present
+ register: nm_add_selector_1
+
+- name: Verify nm_add_selector_1
+ assert:
+ that:
+ - nm_add_selector_1 is changed
+ - nm_add_selector_1.previous == {}
+ - nm_add_selector_1.current.name == "selector_1"
+ - nm_add_selector_1.current.expressions == []
+
+- name: Add Selector 2 to EPG (normal_mode)
+ mso_schema_template_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: selector_2
+ expressions:
+ - type: expression_2_template
+ operator: in
+ value: test
+ state: present
+ register: nm_add_selector_2
+
+- name: Verify nm_add_selector_2
+ assert:
+ that:
+ - nm_add_selector_2 is changed
+ - nm_add_selector_2.previous == {}
+ - nm_add_selector_2.current.name == "selector_2"
+ - nm_add_selector_2.current.expressions[0].key == "Custom:expression_2_template"
+ - nm_add_selector_2.current.expressions[0].operator == "in"
+ - nm_add_selector_2.current.expressions[0].value == "test"
+
+# ADD SELECTORS to site EPG
+- name: Add selector site_selector_1 to site EPG ansible_test_1 with ANP (check_mode)
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: site_selector_1
+ state: present
+ check_mode: true
+ register: cm_add_site_selector_1
+
+- name: Verify cm_add_site_selector_1
+ assert:
+ that:
+ - cm_add_site_selector_1.current.name == "site_selector_1"
+ - cm_add_site_selector_1.current.expressions == []
+ - cm_add_site_selector_1 is changed
+ - cm_add_site_selector_1.previous == {}
+
+- name: Add selector site_selector_1 to site EPG ansible_test_1 with ANP (normal_mode)
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: site_selector_1
+ state: present
+ register: nm_add_site_selector_1
+
+- name: Verify nm_add_site_selector_1
+ assert:
+ that:
+ - nm_add_site_selector_1.current.name == "site_selector_1"
+ - nm_add_site_selector_1.current.expressions == []
+ - nm_add_site_selector_1 is changed
+ - nm_add_site_selector_1.previous == {}
+
+# Add selector 1 again
+- name: Add selector site_selector_1 to site EPG ansible_test_1 with ANP again (normal_mode)
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: site_selector_1
+ state: present
+ register: nm_add_site_selector_1_again
+
+- name: Verify nm_add_site_selector_1_again
+ assert:
+ that:
+ - nm_add_site_selector_1_again is not changed
+ - nm_add_site_selector_1_again.current.name == "site_selector_1" == nm_add_site_selector_1_again.previous.name
+ - nm_add_site_selector_1_again.current.expressions == [] == nm_add_site_selector_1_again.previous.expressions
+
+- name: Add Selector 1 to site EPG with space in selector name (normal_mode)
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: selector 1
+ state: present
+ ignore_errors: true
+ register: nm_add_selector1_with_space_in_name
+
+- name: Verify nm_add_selector1_with_space_in_name
+ assert:
+ that:
+ - nm_add_selector1_with_space_in_name is not changed
+ - nm_add_selector1_with_space_in_name.msg == "There should not be any space in selector name."
+
+- name: Add Selector 2 to site EPG with space in expression type (normal_mode)
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: selector_2
+ expressions:
+ - type: expression 2
+ operator: in
+ value: test
+ state: present
+ ignore_errors: true
+ register: nm_add_selector2_with_space_in_expression_type
+
+- name: Verify nm_add_selector2_with_space_in_expression_type
+ assert:
+ that:
+ - nm_add_selector2_with_space_in_expression_type is not changed
+ - nm_add_selector2_with_space_in_expression_type.msg == "There should not be any space in 'type' attribute of expression 'expression 2'"
+
+- name: Add Selector 2 to site EPG (check_mode)
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: site_selector_2
+ expressions:
+ - type: expression_2
+ operator: in
+ value: test
+ state: present
+ check_mode: true
+ register: cm_add_site_selector_2
+
+- name: Verify cm_add_selector_2
+ assert:
+ that:
+ - cm_add_site_selector_2 is changed
+ - cm_add_site_selector_2.previous == {}
+ - cm_add_site_selector_2.current.name == "site_selector_2"
+ - cm_add_site_selector_2.current.expressions[0].key == "Custom:expression_2"
+ - cm_add_site_selector_2.current.expressions[0].operator == "in"
+ - cm_add_site_selector_2.current.expressions[0].value == "test"
+
+- name: Add Selector_2 to site EPG (normal_mode)
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: site_selector_2
+ expressions:
+ - type: expression_2
+ operator: in
+ value: test
+ state: present
+ register: nm_add_site_selector_2
+
+- name: Verify nm_add_site_selector_2
+ assert:
+ that:
+ - nm_add_site_selector_2 is changed
+ - nm_add_site_selector_2.previous == {}
+ - nm_add_site_selector_2.current.name == "site_selector_2"
+ - nm_add_site_selector_2.current.expressions[0].key == "Custom:expression_2"
+ - nm_add_site_selector_2.current.expressions[0].operator == "in"
+ - nm_add_site_selector_2.current.expressions[0].value == "test"
+
+- name: Change Selector 2 - keyExist(normal_mode)
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: site_selector_2
+ expressions:
+ - type: expression_5
+ operator: has_key
+ value: test
+ state: present
+ ignore_errors: true
+ register: nm_change_site_selector_2_key_exist
+
+- name: Verify nm_change_site_selector_2_key_exist
+ assert:
+ that:
+ - nm_change_site_selector_2_key_exist is not changed
+ - nm_change_site_selector_2_key_exist.msg == "Attribute 'value' is not supported for operator 'has_key' in expression 'expression_5'"
+
+- name: Change Selector 2 - keyNotExist (normal_mode)
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: site_selector_2
+ expressions:
+ - type: expression_6
+ operator: does_not_have_key
+ value: test
+ state: present
+ ignore_errors: true
+ register: nm_change_site_selector_2_key_not_exist
+
+- name: Verify nm_change_site_selector_2_key_not_exist
+ assert:
+ that:
+ - nm_change_site_selector_2_key_not_exist is not changed
+ - nm_change_site_selector_2_key_not_exist.msg == "Attribute 'value' is not supported for operator 'does_not_have_key' in expression 'expression_6'"
+
+- name: Change Selector 2 - equals (normal_mode)
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: site_selector_2
+ expressions:
+ - type: expression_6
+ operator: equals
+ state: present
+ ignore_errors: true
+ register: nm_change_site_selector_2_equals
+
+- name: Verify nm_change_site_selector_2_equals
+ assert:
+ that:
+ - nm_change_site_selector_2_equals is not changed
+ - nm_change_site_selector_2_equals.msg == "Attribute 'value' needed for operator 'equals' in expression 'expression_6'"
+
+# Remove site ANP
+- name: Remove site ANP (normal_mode)
+ mso_schema_site_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ state: absent
+
+- name: Query site ANP
+ mso_schema_site_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ state: query
+ ignore_errors: true
+ register: query_site_ANP
+
+- name: Verify query_site_ANP
+ assert:
+ that:
+ - query_site_ANP.msg == "ANP 'ANP' not found"
+ when: version.current.version is version('4.0', '<') # no error msg is returned in NDO4.0 because site will be present when template is defined
+
+# Query without site ANP
+- name: Query site_selectors without site ANP
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ state: query
+ ignore_errors: true
+ register: query_without_site_ANP
+
+- name: Verify query_without_site_ANP
+ assert:
+ that:
+ - query_without_site_ANP is not changed
+ - query_without_site_ANP.msg == "Anp 'ANP' does not exist in site level."
+ when: version.current.version is version('4.0', '<') # no error msg is returned in NDO4.0 because site will be present when template is defined
+
+# - name: Add selector without ANP exist in site level
+- name: Add site selector 3 without site ANP exist (normal_mode)
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: site_selector_3
+ expressions:
+ - type: expression_3
+ operator: in
+ value: test
+ state: present
+ register: nm_add_site_selector_3_without_anp
+
+- name: Verify nm_add_site_selector_3_without_anp
+ assert:
+ that:
+ - nm_add_site_selector_3_without_anp is changed
+ - nm_add_site_selector_3_without_anp.previous == {}
+ - nm_add_site_selector_3_without_anp.current.name == "site_selector_3"
+ - nm_add_site_selector_3_without_anp.current.expressions[0].key == "Custom:expression_3"
+ - nm_add_site_selector_3_without_anp.current.expressions[0].operator == "in"
+ - nm_add_site_selector_3_without_anp.current.expressions[0].value == "test"
+
+# Remove site level EPG
+- name: Remove site EPG
+ mso_schema_site_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ state: absent
+
+# Query without site level EPG
+- name: Query site_selectors without site EPG
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ state: query
+ ignore_errors: true
+ register: query_without_site_EPG
+
+- name: Verify query_without_site_EPG
+ assert:
+ that:
+ - query_without_site_EPG is not changed
+ - query_without_site_EPG.msg == "Epg 'ansible_test_1' does not exist in site level."
+ when: version.current.version is version('4.0', '<') # no error msg is returned in NDO4.0 because site will be present when template is defined
+
+- name: Add site selector 1 without site EPG exist (normal_mode)
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: site_selector_1
+ state: present
+ register: nm_add_site_selector_1_without_epg
+
+- name: Verify nm_add_site_selector_1_without_epg
+ assert:
+ that:
+ - nm_add_site_selector_1_without_epg is changed
+ - nm_add_site_selector_1_without_epg.previous == {}
+ - nm_add_site_selector_1_without_epg.current.name == "site_selector_1"
+ - nm_add_site_selector_1_without_epg.current.expressions == []
+
+- name: Add site_selector_1 site_selector_2 site_selector_3 again
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: '{{ item.selector }}'
+ expressions:
+ - type: '{{ item.type }}'
+ operator: in
+ value: test
+ state: present
+ loop:
+ - {selector: 'site_selector_1', type: 'expression_1'}
+ - {selector: 'site_selector_2', type: 'expression_2'}
+ - {selector: 'site_selector_3', type: 'expression_3'}
+ register: nm_add_site_selectors_again
+
+- name: Verify nm_add_site_selectors_again
+ assert:
+ that:
+ - nm_add_site_selectors_again is changed
+
+# Query all selectors
+- name: Query selectors to site EPG
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ state: query
+ register: query_all_site_selectors
+
+- name: Verify query_all_site_selectors
+ assert:
+ that:
+ - query_all_site_selectors is not changed
+ - query_all_site_selectors.current | length == 3
+ - query_all_site_selectors.current[0].name == "site_selector_1"
+ - query_all_site_selectors.current[0].expressions[0].key == "Custom:expression_1"
+ - query_all_site_selectors.current[0].expressions[0].operator == "in"
+ - query_all_site_selectors.current[0].expressions[0].value == "test"
+ - query_all_site_selectors.current[1].name == "site_selector_2"
+ - query_all_site_selectors.current[1].expressions[0].key == "Custom:expression_2"
+ - query_all_site_selectors.current[1].expressions[0].operator == "in"
+ - query_all_site_selectors.current[1].expressions[0].value == "test"
+ - query_all_site_selectors.current[2].name == "site_selector_3"
+ - query_all_site_selectors.current[2].expressions[0].key == "Custom:expression_3"
+ - query_all_site_selectors.current[2].expressions[0].operator == "in"
+ - query_all_site_selectors.current[2].expressions[0].value == "test"
+
+# Query sepecific seletor to site EPG
+- name: Query selector to site EPG
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: site_selector_1
+ state: query
+ register: query_site_selector_1
+
+- name: Verify query_site_selector_1
+ assert:
+ that:
+ - query_site_selector_1 is not changed
+ - query_site_selector_1.current.name == "site_selector_1"
+ - query_site_selector_1.current.expressions[0].key == "Custom:expression_1"
+ - query_site_selector_1.current.expressions[0].operator == "in"
+ - query_site_selector_1.current.expressions[0].value == "test"
+
+- name: Remove site selector 3 (normal_mode)
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: site_selector_3
+ state: absent
+ register: nm_remove_site_selector_3
+
+- name: Verify nm_remove_site_selector_3
+ assert:
+ that:
+ - nm_remove_site_selector_3 is changed
+ - nm_remove_site_selector_3.current == {}
+
+- name: Remove site selector 3 again (normal_mode)
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: site_selector_3
+ state: absent
+ register: nm_remove_site_selector_3_again
+
+- name: Verify nm_remove_site_selector_3_again
+ assert:
+ that:
+ - nm_remove_site_selector_3_again is not changed
+ - nm_remove_site_selector_3_again.current == {}
+
+# QUERY NON-EXISTING Selector to EPG
+- name: Query non-existing selector (check_mode)
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: non_existing_selector
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_query_non_selector
+
+- name: Query non-existing selector (normal mode)
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: non_existing_selector
+ state: query
+ ignore_errors: true
+ register: nm_query_non_selector
+
+- name: Verify cm_query_non_selector and nm_query_non_selector
+ assert:
+ that:
+ - cm_query_non_selector is not changed
+ - nm_query_non_selector is not changed
+ - cm_query_non_selector == nm_query_non_selector
+ - cm_query_non_selector.msg == "Selector 'non_existing_selector' not found"
+ - nm_query_non_selector.msg == "Selector 'non_existing_selector' not found"
+
+# QUERY NON-EXISTING EPG
+- name: Query non-existing EPG (check_mode)
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: non_existing_epg
+ selector: site_selector_1
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_query_non_epg
+
+- name: Query non-existing EPG (normal mode)
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: non_existing_epg
+ selector: site_selector_1
+ state: query
+ ignore_errors: true
+ register: nm_query_non_epg
+
+- name: Verify query_non_epg
+ assert:
+ that:
+ - cm_query_non_epg is not changed
+ - nm_query_non_epg is not changed
+ - cm_query_non_epg == nm_query_non_epg
+ - cm_query_non_epg.msg == nm_query_non_epg.msg == "Provided EPG 'non_existing_epg' does not exist. Existing EPGs{{':'}} ansible_test_1, ansible_test_2"
+
+# QUERY NON-EXISTING ANP
+- name: Query non-existing ANP (check_mode)
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: non_existing_anp
+ epg: ansible_test_1
+ selector: site_selector_1
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_query_non_anp
+
+- name: Query non-existing ANP (normal mode)
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: non_existing_anp
+ epg: ansible_test_1
+ selector: site_selector_1
+ state: query
+ ignore_errors: true
+ register: nm_query_non_anp
+
+- name: Verify query_non_anp
+ assert:
+ that:
+ - cm_query_non_anp is not changed
+ - nm_query_non_anp is not changed
+ - cm_query_non_anp == nm_query_non_anp
+ - cm_query_non_anp.msg == nm_query_non_anp.msg == "Provided anp 'non_existing_anp' does not exist. Existing anps{{':'}} ANP"
+
+# USE A NON-EXISTING STATE
+- name: Non-existing state (check_mode)
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: site_selector_1
+ state: non-existing-state
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_state
+
+- name: Non-existing state (normal_mode)
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: site_selector_1
+ state: non-existing-state
+ ignore_errors: true
+ register: nm_non_existing_state
+
+- name: Verify non_existing_state
+ assert:
+ that:
+ - cm_non_existing_state is not changed
+ - nm_non_existing_state is not changed
+ - cm_non_existing_state == nm_non_existing_state
+ - cm_non_existing_state.msg == nm_non_existing_state.msg == "value of state must be one of{{':'}} absent, present, query, got{{':'}} non-existing-state"
+
+# USE A NON-EXISTING TEMPLATE
+- name: Non-existing template (check_mode)
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: non-existing-template
+ anp: ANP
+ epg: ansible_test_1
+ selector: site_selector_1
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_template
+
+- name: Non-existing template (normal_mode)
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: non-existing-template
+ anp: ANP
+ epg: ansible_test_1
+ selector: site_selector_1
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_template
+
+- name: Verify non_existing_template
+ assert:
+ that:
+ - cm_non_existing_template is not changed
+ - nm_non_existing_template is not changed
+ - cm_non_existing_template == nm_non_existing_template
+ - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non-existing-template' does not exist. Existing templates{{':'}} Template1, Template2, Template3"
+
+# USE A NON-EXISTING SCHEMA
+- name: Non-existing schema (check_mode)
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: non-existing-schema
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: site_selector_1
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_schema
+
+- name: Non-existing schema (normal_mode)
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: non-existing-schema
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: site_selector_1
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_schema
+
+- name: Verify non_existing_schema
+ assert:
+ that:
+ - cm_non_existing_schema is not changed
+ - nm_non_existing_schema is not changed
+ - cm_non_existing_schema == nm_non_existing_schema
+ - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non-existing-schema' does not exist."
+
+# USE A NON-EXISTING SITE
+- name: Non-existing site (check_mode)
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: non-existing-site
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: site_selector_1
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_site
+
+- name: Non-existing site (normal_mode)
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: non-existing-site
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: site_selector_1
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_site
+
+- name: Verify non_existing_site
+ assert:
+ that:
+ - cm_non_existing_site is not changed
+ - nm_non_existing_site is not changed
+ - cm_non_existing_site == nm_non_existing_site
+ - cm_non_existing_site.msg == nm_non_existing_site.msg == "Site 'non-existing-site' is not a valid site name."
+
+# USE A NON-EXISTING SITE-TEMPLATE
+- name: Non-existing site-template (check_mode)
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 3
+ anp: ANP
+ epg: ansible_test_1
+ selector: site_selector_1
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_site_template
+
+- name: Non-existing site-template (normal_mode)
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 3
+ anp: ANP
+ epg: ansible_test_1
+ selector: site_selector_1
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_site_template
+
+- name: Verify non_existing_site_template
+ assert:
+ that:
+ - cm_non_existing_site_template is not changed
+ - nm_non_existing_site_template is not changed
+ - cm_non_existing_site_template == nm_non_existing_site_template
+ - cm_non_existing_site_template.msg == nm_non_existing_site_template.msg == "Provided site-template association 'aws_{{ mso_site | default("ansible_test") }}-Template3' does not exist."
+
+- name: Add Selector_4 to site EPG (normal_mode)
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: site_selector_4
+ expressions:
+ - type: ip_address
+ operator: has_key
+ state: present
+ ignore_errors: true
+ register: nm_add_site_selector_4
+
+- name: Verify nm_add_site_selector_4
+ assert:
+ that:
+ - nm_add_site_selector_4 is not changed
+ - nm_add_site_selector_4.msg == "Operator 'has_key' is not supported when expression type is 'ip_address'"
+
+- name: Add Selector_4 to site EPG again (normal_mode)
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: site_selector_4
+ expressions:
+ - type: ip_address
+ operator: in
+ value: test
+ state: present
+ register: nm_add_site_selector_4_again
+
+- name: Verify nm_add_site_selector_4
+ assert:
+ that:
+ - nm_add_site_selector_4_again is changed
+ - nm_add_site_selector_4_again.current.name == "site_selector_4"
+
+- name: Add azure site_selector_1 to site EPG (normal_mode)
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: site_selector_1
+ expressions:
+ - type: zone
+ operator: in
+ value: test
+ state: present
+ ignore_errors: true
+ register: nm_add_azure_site_selector_1
+
+- name: Verify nm_add_azure_site_selector_1
+ assert:
+ that:
+ - nm_add_azure_site_selector_1 is not changed
+ - nm_add_azure_site_selector_1.msg == "Type 'zone' is only supported for aws"
+
+# USE A TEMPLATE WITHOUT ANY SITE
+- name: Add site EPG selector to Schema 2 Template 3 without any site associated (check mode)
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ template: Template 3
+ anp: ANP
+ epg: ansible_test_1
+ selector: site_selector_1
+ expressions:
+ - type: zone
+ operator: in
+ value: test
+ state: present
+ ignore_errors: true
+ check_mode: true
+ register: cm_no_site_associated
+
+- name: Add site EPG selector to Template 3 without any site associated (normal mode)
+ mso_schema_site_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ template: Template 3
+ anp: ANP
+ epg: ansible_test_1
+ selector: site_selector_1
+ expressions:
+ - type: zone
+ operator: in
+ value: test
+ state: present
+ ignore_errors: true
+ register: nm_no_site_associated
+
+- name: Verify cm_no_site_associated and nm_no_site_associated
+ assert:
+ that:
+ - cm_no_site_associated is not changed
+ - nm_no_site_associated is not changed
+ - cm_no_site_associated.msg == nm_no_site_associated.msg == "No site associated with template 'Template3'. Associate the site with the template using mso_schema_site." \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_staticport/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_staticport/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_staticport/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_staticport/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_staticport/tasks/main.yml
new file mode 100644
index 000000000..cb50a64e6
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_anp_epg_staticport/tasks/main.yml
@@ -0,0 +1,870 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case)
+# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Remove Schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Ensure site exists
+ mso_site:
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ apic_username }}'
+ apic_password: '{{ apic_password }}'
+ apic_site_id: '{{ apic_site_id | default(101) }}'
+ urls:
+ - https://{{ apic_hostname }}
+ state: present
+
+- name: Ensure sites removed from tenant ansible_test
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_site | default("ansible_test") }}'
+ - 'aws_{{ mso_site | default("ansible_test") }}'
+ - 'azure_{{ mso_site | default("ansible_test") }}'
+
+- name: Ensure tenant ansible_test exist
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ state: present
+
+- name: Ensure schema 1 with Template 1 and 2 exists
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: '{{ item }}'
+ state: present
+ loop:
+ - Template 1
+ - Template 2
+
+- name: Ensure schema 2 with Template 3 exists
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template 3
+ state: present
+
+- name: Add a new site to a schema
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ state: present
+
+- name: Ensure VRF1 exists
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF1
+ state: present
+
+- name: Add BD1
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ bd: BD1
+ vrf:
+ name: VRF1
+ state: present
+
+- name: Ensure Template 1 with AP1 exists
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ state: present
+
+- name: Ensure Template 1 and AP1 with EPG1 exists
+ mso_schema_template_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG1
+ bd:
+ name: BD1
+ vrf:
+ name: VRF1
+ state: present
+
+- name: Ensure Template 1 and AP1 with EPG3 exists
+ mso_schema_template_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG3
+ bd:
+ name: BD1
+ vrf:
+ name: VRF1
+ state: present
+
+- name: Ensure Template 1 with AP2 exists
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: AP2
+ state: present
+
+- name: Ensure Template 1 and AP2 with EPG2 exists
+ mso_schema_template_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: AP2
+ epg: EPG2
+ bd:
+ name: BD1
+ vrf:
+ name: VRF1
+ state: present
+
+- name: Ensure Template 1 and AP2 with EPG4 exists
+ mso_schema_template_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: AP2
+ epg: EPG4
+ bd:
+ name: BD1
+ vrf:
+ name: VRF1
+ state: present
+
+- name: Ensure Template 1 and AP2 with EPG6 exists
+ mso_schema_template_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: AP2
+ epg: EPG6
+ bd:
+ name: BD1
+ vrf:
+ name: VRF1
+ state: present
+
+# ADD STATIC PORTS
+- name: Add static port 1 to site EPG1 of AP1 (check mode)
+ mso_schema_site_anp_epg_staticport:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG1
+ pod: pod-1
+ leaf: 101
+ path: eth1/1
+ vlan: 126
+ mode: 'native'
+ type: port
+ deployment_immediacy: immediate
+ state: present
+ check_mode: true
+ register: cm_add_stat1e1
+
+- name: Verify cm_add_stat1e1
+ assert:
+ that:
+ - cm_add_stat1e1 is changed
+ - cm_add_stat1e1.previous == {}
+ - cm_add_stat1e1.current.deploymentImmediacy == 'immediate'
+ - cm_add_stat1e1.current.portEncapVlan == 126
+ - cm_add_stat1e1.current.path == 'topology/pod-1/paths-101/pathep-[eth1/1]'
+ - cm_add_stat1e1.current.mode == 'native'
+ - cm_add_stat1e1.current.type == 'port'
+
+- name: Add static port 1 to site EPG1 of AP1 (normal mode)
+ mso_schema_site_anp_epg_staticport:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG1
+ pod: pod-1
+ leaf: 101
+ path: eth1/1
+ vlan: 126
+ mode: 'native'
+ deployment_immediacy: immediate
+ state: present
+ register: nm_add_stat1e1
+
+- name: Verify nm_add_stat1e1
+ assert:
+ that:
+ - nm_add_stat1e1 is changed
+ - nm_add_stat1e1.previous == {}
+ - nm_add_stat1e1.current.deploymentImmediacy == 'immediate'
+ - nm_add_stat1e1.current.portEncapVlan == 126
+ - nm_add_stat1e1.current.path == 'topology/pod-1/paths-101/pathep-[eth1/1]'
+ - nm_add_stat1e1.current.mode == 'native'
+ - nm_add_stat1e1.current.type == 'port'
+
+- name: Add static port 2 to site EPG1 of AP1 (normal mode)
+ mso_schema_site_anp_epg_staticport:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG1
+ pod: pod-2
+ leaf: 102
+ path: eth1/2
+ vlan: 100
+ mode: 'regular'
+ type: port
+ primary_micro_segment_vlan: 199
+ deployment_immediacy: immediate
+ state: present
+ register: nm_add_stat2e1
+
+- name: Verify nm_add_stat2e1
+ assert:
+ that:
+ - nm_add_stat2e1 is changed
+ - nm_add_stat2e1.previous == {}
+ - nm_add_stat2e1.current.deploymentImmediacy == 'immediate'
+ - nm_add_stat2e1.current.portEncapVlan == 100
+ - nm_add_stat2e1.current.path == 'topology/pod-2/paths-102/pathep-[eth1/2]'
+ - nm_add_stat2e1.current.mode == 'regular'
+ - nm_add_stat2e1.current.type == 'port'
+
+
+- name: Add static port 3 (vpc) to site EPG1 of AP1 (normal mode)
+ mso_schema_site_anp_epg_staticport:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG1
+ pod: pod-3
+ leaf: 103-104
+ path: ansible_polgrp
+ vlan: 101
+ type: vpc
+ mode: untagged
+ deployment_immediacy: lazy
+ state: present
+ register: nm_add_stat3e1
+
+- name: Verify nm_add_stat3e1
+ assert:
+ that:
+ - nm_add_stat3e1 is changed
+ - nm_add_stat3e1.previous == {}
+ - nm_add_stat3e1.current.deploymentImmediacy == 'lazy'
+ - nm_add_stat3e1.current.portEncapVlan == 101
+ - nm_add_stat3e1.current.path == 'topology/pod-3/protpaths-103-104/pathep-[ansible_polgrp]'
+ - nm_add_stat3e1.current.mode == 'untagged'
+ - nm_add_stat3e1.current.type == 'vpc'
+
+- name: Add static port 1 to site EPG3 of AP1 (normal mode)
+ mso_schema_site_anp_epg_staticport:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG3
+ pod: pod-1
+ leaf: 101
+ path: eth1/1
+ vlan: 126
+ mode: 'native'
+ type: port
+ deployment_immediacy: immediate
+ state: present
+ register: nm_add_stat1e3
+
+- name: Verify nm_add_stat1e3
+ assert:
+ that:
+ - nm_add_stat1e3 is changed
+ - nm_add_stat1e3.previous == {}
+ - nm_add_stat1e3.current.deploymentImmediacy == 'immediate'
+ - nm_add_stat1e3.current.portEncapVlan == 126
+ - nm_add_stat1e3.current.path == 'topology/pod-1/paths-101/pathep-[eth1/1]'
+ - nm_add_stat1e3.current.mode == 'native'
+ - nm_add_stat1e3.current.type == 'port'
+
+- name: Add static port 2 (dpc) to EPG6 of AP2 (normal mode)
+ mso_schema_site_anp_epg_staticport:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP2
+ epg: EPG6
+ pod: pod-2
+ leaf: 102
+ path: eth1/2
+ vlan: 100
+ deployment_immediacy: lazy
+ mode: regular
+ type: dpc
+ primary_micro_segment_vlan: 199
+ state: present
+ register: nm_add_stat2e6
+
+- name: Verify nm_add_stat2e6
+ assert:
+ that:
+ - nm_add_stat2e6 is changed
+ - nm_add_stat2e6.previous == {}
+ - nm_add_stat2e6.current.deploymentImmediacy == 'lazy'
+ - nm_add_stat2e6.current.portEncapVlan == 100
+ - nm_add_stat2e6.current.microSegVlan == 199
+ - nm_add_stat2e6.current.path == 'topology/pod-2/paths-102/pathep-[eth1/2]'
+ - nm_add_stat2e6.current.mode == 'regular'
+ - nm_add_stat2e6.current.type == 'dpc'
+
+# QUERY STATIC PORTS
+- name: Query STATIC PORTS of site EPG1 with AP1 (normal mode)
+ mso_schema_site_anp_epg_staticport:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG1
+ state: query
+ register: nm_query_statse1
+
+- name: Verify nm_query_statse1
+ assert:
+ that:
+ - nm_query_statse1 is not changed
+
+# QUERY A STATIC PORT
+- name: Query static port 3 (vpc) of site EPG1 with AP1 (normal mode)
+ mso_schema_site_anp_epg_staticport:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG1
+ pod: pod-3
+ leaf: 103-104
+ path: ansible_polgrp
+ vlan: 101
+ mode: untagged
+ type: vpc
+ deployment_immediacy: immediate
+ state: query
+ register: nm_query_stat3e1
+
+- name: Verify nm_query_stat3e1
+ assert:
+ that:
+ - nm_query_stat3e1 is not changed
+
+# QUERY REMOVED STATIC PORT
+- name: Add static port 2 to site EPG2 (normal mode)
+ mso_schema_site_anp_epg_staticport:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP2
+ epg: EPG2
+ pod: pod-2
+ leaf: 102
+ path: eth1/2
+ vlan: 100
+ mode: regular
+ type: port
+ deployment_immediacy: immediate
+ state: present
+ register: nm_add_stat2e2
+
+- name: Verify nm_add_stat2e2
+ assert:
+ that:
+ - nm_add_stat2e2 is changed
+ - nm_add_stat2e2.previous == {}
+ - nm_add_stat2e2.current.deploymentImmediacy == 'immediate'
+ - nm_add_stat2e2.current.portEncapVlan == 100
+ - nm_add_stat2e2.current.path == 'topology/pod-2/paths-102/pathep-[eth1/2]'
+ - nm_add_stat2e2.current.mode == 'regular'
+ - nm_add_stat2e2.current.type == 'port'
+
+- name: Remove static port 2 from EPG2 (normal mode)
+ mso_schema_site_anp_epg_staticport:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP2
+ epg: EPG2
+ pod: pod-2
+ leaf: 102
+ path: eth1/2
+ vlan: 100
+ mode: regular
+ type: port
+ deployment_immediacy: immediate
+ state: absent
+ register: nm_remove_stat2e2
+
+- name: Verify nm_remove_stat2e2
+ assert:
+ that:
+ - nm_remove_stat2e2 is changed
+
+- name: Query removed static port 2 from EPG2 (normal mode)
+ mso_schema_site_anp_epg_staticport:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP2
+ epg: EPG2
+ pod: pod-2
+ leaf: 102
+ path: eth1/2
+ vlan: 100
+ mode: regular
+ type: port
+ deployment_immediacy: immediate
+ state: query
+ ignore_errors: true
+ register: nm_non_existent_dom2e2
+
+- name: Verify non_existing_domain
+ assert:
+ that:
+ - nm_non_existent_dom2e2 is not changed
+ - nm_non_existent_dom2e2.msg == "Static port 'topology/pod-2/paths-102/pathep-[eth1/2]' not found"
+
+- name: Remove static port 2 from EPG2 again(normal mode)
+ mso_schema_site_anp_epg_staticport:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP2
+ epg: EPG2
+ pod: pod-2
+ leaf: 101
+ path: eth1/2
+ vlan: 100
+ mode: regular
+ type: port
+ deployment_immediacy: immediate
+ state: absent
+ ignore_errors: true
+ register: nm_remove_again_stat2e2
+
+- name: Verify nm_remove_again_stat2e2
+ assert:
+ that:
+ - nm_remove_again_stat2e2 is not changed
+
+# ADD EXISTING STATIC PORT
+- name: Add static port 1 to site EPG1 again (normal mode)
+ mso_schema_site_anp_epg_staticport:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG1
+ pod: pod-1
+ leaf: 101
+ path: eth1/1
+ vlan: 126
+ mode: 'native'
+ type: port
+ deployment_immediacy: immediate
+ state: present
+ register: nm_add_stat1e1_2
+
+- name: Verify nm_add_stat1e1_2
+ assert:
+ that:
+ - nm_add_stat1e1_2 is not changed
+
+# ADD STATIC PORT WITH NO STATE
+- name: Add static port 1 to site EPG1 again with no state (normal mode)
+ mso_schema_site_anp_epg_staticport:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG1
+ pod: pod-1
+ leaf: 101
+ path: eth1/1
+ vlan: 126
+ mode: native
+ type: port
+ deployment_immediacy: immediate
+ ignore_errors: true
+ register: nm_add_stateless_stat1e1_2
+
+- name: Verify nm_add_stateless_stat1e1_2
+ assert:
+ that:
+ - nm_add_stateless_stat1e1_2 is not changed
+
+# ADD STATIC FEX PORT
+- name: Add static fex port to site EPG1 with AP1 (normal mode)
+ mso_schema_site_anp_epg_staticport:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG1
+ pod: pod-4
+ leaf: 101
+ path: eth1/1
+ vlan: 126
+ fex: 151
+ type: port
+ mode: native
+ deployment_immediacy: lazy
+ state: present
+ register: nm_add_statfex
+
+- name: Verify nm_add_statfex
+ assert:
+ that:
+ - nm_add_statfex is changed
+ - nm_add_statfex.previous == {}
+ - nm_add_statfex.current.deploymentImmediacy == 'lazy'
+ - nm_add_statfex.current.portEncapVlan == 126
+ - nm_add_statfex.current.path == 'topology/pod-4/paths-101/extpaths-151/pathep-[eth1/1]'
+ - nm_add_statfex.current.mode == 'native'
+ - nm_add_statfex.current.type == 'port'
+
+# VERIFY NON EXISTENT DEPLOYMENT IMMEDIACY
+- name: Add static port 1 to site EPG4 with AP2 with no deployment immediacy (normal mode)
+ mso_schema_site_anp_epg_staticport:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP2
+ epg: EPG4
+ pod: pod-4
+ leaf: 101
+ path: eth1/1
+ vlan: 126
+ type: port
+ mode: native
+ state: present
+ register: nm_add_stat_di
+
+- name: Verify nm_add_stat_di
+ assert:
+ that:
+ - nm_add_stat_di.current.deploymentImmediacy == 'lazy'
+
+# VERIFY NON EXISTENT MODE
+- name: Add static port 1 to site EPG4 with AP2 with no mode (normal mode)
+ mso_schema_site_anp_epg_staticport:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP2
+ epg: EPG4
+ pod: pod-4
+ leaf: 101
+ path: eth1/1
+ vlan: 126
+ type: port
+ deployment_immediacy: lazy
+ state: present
+ register: nm_add_stat_mode
+
+- name: Verify nm_add_stat_mode
+ assert:
+ that:
+ - nm_add_stat_mode.current.mode == 'untagged'
+
+# USE NON-EXISTING EPG and ANP AT TEMPLATE LEVEL
+- name: Add static port 1 to non-existent site EPG5 (normal mode)
+ mso_schema_site_anp_epg_staticport:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP5
+ epg: EPG5
+ pod: pod-1
+ leaf: 101
+ path: eth1/1
+ vlan: 126
+ mode: native
+ type: port
+ deployment_immediacy: immediate
+ state: present
+ ignore_errors: true
+ register: nm_add_stat1e5
+
+- name: Verify nm_add_stat1e5
+ assert:
+ that:
+ - nm_add_stat1e5 is not changed
+
+# USE NON-EXISTING EPG AT TEMPLATE LEVEL
+- name: Add static port 1 to non-existent site EPG5 (normal mode)
+ mso_schema_site_anp_epg_staticport:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG6
+ pod: pod-1
+ leaf: 101
+ path: eth1/1
+ vlan: 126
+ mode: native
+ deployment_immediacy: immediate
+ state: present
+ ignore_errors: true
+ register: nm_add_stat1e6
+
+- name: Verify nm_add_stat1e6
+ assert:
+ that:
+ - nm_add_stat1e6 is not changed
+
+# USE A NON-EXISTING SCHEMA
+- name: Non-existing schema for static port (check_mode)
+ mso_schema_site_anp_epg_staticport:
+ <<: *mso_info
+ schema: non_existing_schema
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG1
+ pod: pod-1
+ leaf: 101
+ path: eth1/1
+ vlan: 126
+ mode: native
+ type: port
+ deployment_immediacy: immediate
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_schema
+
+- name: Non-existing schema for static port (normal_mode)
+ mso_schema_site_anp_epg_staticport:
+ <<: *mso_info
+ schema: non_existing_schema
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG1
+ pod: pod-1
+ leaf: 101
+ path: eth1/1
+ vlan: 126
+ type: port
+ mode: native
+ deployment_immediacy: immediate
+ ignore_errors: true
+ register: nm_non_existing_schema
+
+- name: Verify non_existing_schema
+ assert:
+ that:
+ - cm_non_existing_schema is not changed
+ - nm_non_existing_schema is not changed
+ - cm_non_existing_schema == nm_non_existing_schema
+ - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non_existing_schema' does not exist."
+
+# USE A NON-EXISTING TEMPLATE
+- name: Non-existing template for static port (check_mode)
+ mso_schema_site_anp_epg_staticport:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: non_existing_template
+ anp: AP1
+ epg: EPG1
+ pod: pod-1
+ leaf: 101
+ path: eth1/1
+ vlan: 126
+ type: port
+ mode: native
+ deployment_immediacy: immediate
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_template
+
+- name: Non-existing template for static port (normal_mode)
+ mso_schema_site_anp_epg_staticport:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: non_existing_template
+ anp: AP1
+ epg: EPG1
+ pod: pod-1
+ leaf: 101
+ path: eth1/1
+ vlan: 126
+ type: port
+ mode: native
+ deployment_immediacy: immediate
+ ignore_errors: true
+ register: nm_non_existing_template
+
+- name: Verify non_existing_template
+ assert:
+ that:
+ - cm_non_existing_template is not changed
+ - nm_non_existing_template is not changed
+ - cm_non_existing_template == nm_non_existing_template
+ - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non_existing_template' does not exist. Existing templates{{':'}} Template1, Template2"
+
+# USE A NON-EXISTING SITE
+- name: Non-existing site for static port (check_mode)
+ mso_schema_site_anp_epg_staticport:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 2
+ anp: AP1
+ epg: EPG1
+ pod: pod-1
+ leaf: 101
+ path: eth1/1
+ vlan: 126
+ type: port
+ mode: native
+ deployment_immediacy: immediate
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_site
+
+- name: Non-existing site for static port (normal_mode)
+ mso_schema_site_anp_epg_staticport:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 2
+ anp: AP1
+ epg: EPG1
+ pod: pod-1
+ leaf: 101
+ path: eth1/1
+ vlan: 126
+ type: port
+ mode: native
+ deployment_immediacy: immediate
+ ignore_errors: true
+ register: nm_non_existing_site
+
+- name: Verify non_existing_site
+ assert:
+ that:
+ - cm_non_existing_site is not changed
+ - nm_non_existing_site is not changed
+ - cm_non_existing_site == nm_non_existing_site
+ - cm_non_existing_site.msg is match("Provided site/siteId/template 'ansible_test/[0-9a-zA-Z]*/Template2' does not exist. Existing siteIds/templates{{':'}} [0-9a-zA-Z]*/Template1")
+ - nm_non_existing_site.msg is match("Provided site/siteId/template 'ansible_test/[0-9a-zA-Z]*/Template2' does not exist. Existing siteIds/templates{{':'}} [0-9a-zA-Z]*/Template1")
+
+# USE A TEMPLATE WITHOUT ANY SITE
+- name: Add site EPG static port association to Schema 2 Template 3 without any site associated (check mode)
+ mso_schema_site_anp_epg_staticport:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 3
+ anp: AP1
+ epg: EPG1
+ pod: pod-1
+ leaf: 101
+ path: eth1/1
+ vlan: 126
+ type: port
+ mode: native
+ deployment_immediacy: immediate
+ ignore_errors: true
+ check_mode: true
+ register: cm_no_site_associated
+
+- name: Add site EPG static port association to Template 3 without any site associated (normal mode)
+ mso_schema_site_anp_epg_staticport:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 3
+ anp: AP1
+ epg: EPG1
+ pod: pod-1
+ leaf: 101
+ path: eth1/1
+ vlan: 126
+ type: port
+ mode: native
+ deployment_immediacy: immediate
+ ignore_errors: true
+ register: nm_no_site_associated
+
+- name: Verify cm_no_site_associated and nm_no_site_associated
+ assert:
+ that:
+ - cm_no_site_associated is not changed
+ - nm_no_site_associated is not changed
+ - cm_no_site_associated.msg == nm_no_site_associated.msg == "No site associated with template 'Template3'. Associate the site with the template using mso_schema_site." \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd/tasks/main.yml
new file mode 100644
index 000000000..a7de528ea
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd/tasks/main.yml
@@ -0,0 +1,749 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case)
+# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com>
+# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Ensure site exists
+ mso_site:
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ apic_username }}'
+ apic_password: '{{ apic_password }}'
+ apic_site_id: '{{ apic_site_id | default(101) }}'
+ urls:
+ - https://{{ apic_hostname }}
+ state: present
+ ignore_errors: true
+
+- name: Remove schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Ensure sites removed from tenant ansible_test
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_site | default("ansible_test") }}'
+ - 'aws_{{ mso_site | default("ansible_test") }}'
+ - 'azure_{{ mso_site | default("ansible_test") }}'
+
+- name: Ensure tenant ansible_test exists
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ state: present
+
+- name: Ensure schema 1 with Template 1, and Template 2, Template 3 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: '{{item.template}}'
+ state: present
+ loop:
+ - { template: Template 1}
+ - { template: Template 2}
+ - { template: Template 3}
+
+- name: Ensure schema 2 with Template 3 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template 3
+ state: present
+
+- name: Add physical site to a schema
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: '{{item.template}}'
+ state: present
+ loop:
+ - { template: Template 1}
+ - { template: Template 2}
+
+- name: Ensure VRF1 exists
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF1
+ layer3_multicast: true
+ state: present
+
+- name: Ensure ansible_test_1 BD does not exist
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ bd: ansible_test_1
+ vrf:
+ name: VRF1
+ state: absent
+
+- name: Add template BD
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ bd: ansible_test_1
+ vrf:
+ name: VRF1
+ state: present
+ register: nm_add_bd
+
+- name: Verify nm_add_bd
+ assert:
+ that:
+ - nm_add_bd is changed
+ - nm_add_bd.previous == {}
+ - nm_add_bd.current.name == "ansible_test_1"
+ - nm_add_bd.current.vrfRef.templateName == "Template1"
+ - nm_add_bd.current.vrfRef.vrfName == "VRF1"
+
+- name: Add template BD 2
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ bd: ansible_test_2
+ vrf:
+ name: VRF1
+ template: Template 1
+ state: present
+ register: nm_add_bd_2
+
+- name: Verify nm_add_bd_2
+ assert:
+ that:
+ - nm_add_bd_2 is changed
+ - nm_add_bd_2.previous == {}
+ - nm_add_bd_2.current.name == "ansible_test_2"
+ - nm_add_bd_2.current.vrfRef.templateName == "Template1"
+ - nm_add_bd_2.current.vrfRef.vrfName == "VRF1"
+
+- name: Add template BD 3
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ bd: ansible_test_3
+ vrf:
+ name: VRF1
+ template: Template 1
+ state: present
+ register: nm_add_bd_3
+
+- name: Verify nm_add_bd_3
+ assert:
+ that:
+ - nm_add_bd_3 is changed
+ - nm_add_bd_3.previous == {}
+ - nm_add_bd_3.current.name == "ansible_test_3"
+ - nm_add_bd_3.current.vrfRef.templateName == "Template1"
+ - nm_add_bd_3.current.vrfRef.vrfName == "VRF1"
+
+- name: Add template BD 4
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ bd: ansible_test_4
+ vrf:
+ name: VRF1
+ template: Template 1
+ state: present
+ register: nm_add_bd_4
+
+- name: Verify nm_add_bd_4
+ assert:
+ that:
+ - nm_add_bd_4 is changed
+ - nm_add_bd_4.previous == {}
+ - nm_add_bd_4.current.name == "ansible_test_4"
+ - nm_add_bd_4.current.vrfRef.templateName == "Template1"
+ - nm_add_bd_4.current.vrfRef.vrfName == "VRF1"
+
+- name: Add site BD (check_mode)
+ mso_schema_site_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ bd: ansible_test_1
+ state: present
+ check_mode: true
+ register: cm_add_site_bd
+
+- name: Verify cm_add_site_bd
+ assert:
+ that:
+ - cm_add_site_bd.current.bdRef.bdName == "ansible_test_1"
+ - cm_add_site_bd.current.bdRef.templateName == "Template1"
+ - cm_add_site_bd.current.hostBasedRouting == false
+
+- name: Verify cm_add_site_bd
+ assert:
+ that:
+ - cm_add_site_bd is changed
+ - cm_add_site_bd.previous == {}
+ when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined
+
+- name: Add site BD (normal_mode)
+ mso_schema_site_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ bd: ansible_test_1
+ state: present
+ register: nm_add_site_bd
+
+- name: Verify nm_add_site_bd
+ assert:
+ that:
+ - nm_add_site_bd.current.bdRef.bdName == "ansible_test_1"
+ - nm_add_site_bd.current.bdRef.templateName == "Template1"
+ - nm_add_site_bd.current.hostBasedRouting == false
+ - cm_add_site_bd.current.bdRef.schemaId == nm_add_site_bd.current.bdRef.schemaId
+
+- name: Verify nm_add_site_bd
+ assert:
+ that:
+ - nm_add_site_bd is changed
+ - nm_add_site_bd.previous == {}
+ when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined
+
+- name: Add site BD again (check_mode)
+ mso_schema_site_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ bd: ansible_test_1
+ state: present
+ check_mode: true
+ register: cm_add_site_bd_again
+
+- name: Add site BD again (normal_mode)
+ mso_schema_site_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ bd: ansible_test_1
+ state: present
+ register: nm_add_site_bd_again
+
+- name: Verify cm_add_site_bd_again and nm_add_site_bd_again
+ assert:
+ that:
+ - cm_add_site_bd_again is not changed
+ - nm_add_site_bd_again is not changed
+ - cm_add_site_bd_again.previous.bdRef.bdName == nm_add_site_bd_again.previous.bdRef.bdName == cm_add_site_bd_again.current.bdRef.bdName == nm_add_site_bd_again.current.bdRef.bdName == "ansible_test_1"
+
+- name: Change site BD (check_mode)
+ mso_schema_site_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ bd: ansible_test_1
+ host_route: true
+ state: present
+ check_mode: true
+ register: cm_change_site_bd
+
+- name: Change site BD (normal_mode)
+ mso_schema_site_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ bd: ansible_test_1
+ host_route: true
+ state: present
+ register: nm_change_site_bd
+
+- name: Verify cm_change_site_bd and nm_change_site_bd
+ assert:
+ that:
+ - cm_change_site_bd is changed
+ - nm_change_site_bd is changed
+ - cm_change_site_bd.previous.bdRef == cm_change_site_bd.current.bdRef
+ - nm_change_site_bd.previous.bdRef == nm_change_site_bd.current.bdRef
+ - cm_change_site_bd.previous.hostBasedRouting == false
+ - cm_change_site_bd.current.hostBasedRouting == true
+ - nm_change_site_bd.previous.hostBasedRouting == false
+ - nm_change_site_bd.current.hostBasedRouting == true
+
+- name: Add site BD with host_route (check_mode)
+ mso_schema_site_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 2
+ bd: ansible_test_2
+ host_route: true
+ state: present
+ check_mode: true
+ register: cm_add_site_bd_with_host_route
+
+- name: Add site BD with host_route (normal_mode)
+ mso_schema_site_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 2
+ bd: ansible_test_2
+ host_route: true
+ state: present
+ register: nm_add_site_bd_with_host_route
+
+- name: Verify cm_add_site_bd_with_host_route and nm_add_site_bd_with_host_route
+ assert:
+ that:
+ - cm_add_site_bd_with_host_route is changed
+ - nm_add_site_bd_with_host_route is changed
+ - cm_add_site_bd_with_host_route.current.hostBasedRouting == true
+ - nm_add_site_bd_with_host_route.current.hostBasedRouting == true
+
+- name: Verify cm_add_site_bd_with_host_route and nm_add_site_bd_with_host_route
+ assert:
+ that:
+ - cm_add_site_bd_with_host_route.previous == {}
+ - nm_add_site_bd_with_host_route.previous == {}
+ when: version.current.version is version('4.0', '<') # already present when template is defined, thus previous exists
+
+- name: Add site BD 3 (normal_mode)
+ mso_schema_site_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ bd: ansible_test_3
+ host_route: true
+ state: present
+ register: nm_add_site_bd_3
+
+- name: Verify nm_add_site_bd_3
+ assert:
+ that:
+ - nm_add_site_bd_3 is changed
+ - nm_add_site_bd_3.current.hostBasedRouting == true
+ - nm_add_site_bd_3.current.bdRef.bdName == "ansible_test_3"
+ - nm_add_site_bd_3.current.bdRef.templateName == "Template1"
+
+- name: Verify nm_add_site_bd_3
+ assert:
+ that:
+ - nm_add_site_bd_3.previous == {}
+ when: version.current.version is version('4.0', '<') # already be present when template is defined, thus previous exists
+
+- name: Add site BD 4 (normal_mode)
+ mso_schema_site_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ bd: ansible_test_4
+ svi_mac: 00:22:23:F1:21:F9
+ state: present
+ register: nm_add_site_bd_4
+
+- name: Verify nm_add_site_bd_4
+ assert:
+ that:
+ - nm_add_site_bd_4 is changed
+ - nm_add_site_bd_4.current.mac == "00:22:23:F1:21:F9"
+ - nm_add_site_bd_4.current.bdRef.bdName == "ansible_test_4"
+ - nm_add_site_bd_4.current.bdRef.templateName == "Template1"
+
+- name: Verify nm_add_site_bd_4
+ assert:
+ that:
+ - nm_add_site_bd_4.previous == {}
+ when: version.current.version is version('4.0', '<') # already be present when template is defined, thus previous exists
+
+- name: Query a specific BD (check_mode)
+ mso_schema_site_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 2
+ bd: ansible_test_2
+ state: query
+ check_mode: true
+ register: cm_query_bd_2
+
+- name: Query a specific BD (normal_mode)
+ mso_schema_site_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 2
+ bd: ansible_test_2
+ state: query
+ register: nm_query_bd_2
+
+- name: Verify cm_query_bd_2 and nm_query_bd_2
+ assert:
+ that:
+ - cm_query_bd_2 is not changed
+ - nm_query_bd_2 is not changed
+ - cm_query_bd_2.current.bdRef.bdName == "ansible_test_2" == nm_query_bd_2.current.bdRef.bdName
+ - cm_query_bd_2.current.bdRef.schemaId == nm_query_bd_2.current.bdRef.schemaId
+ - cm_query_bd_2.current.bdRef.templateName == nm_query_bd_2.current.bdRef.templateName == "Template2"
+ - cm_query_bd_2.current.hostBasedRouting == nm_query_bd_2.current.hostBasedRouting == true
+
+- name: Query all BDs (check_mode)
+ mso_schema_site_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ state: query
+ check_mode: true
+ register: cm_query_all_bd
+
+- name: Query all BDs (normal_mode)
+ mso_schema_site_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ state: query
+ register: nm_query_all_bd
+
+- name: Verify cm_query_all_bd and cm_query_all_bd
+ assert:
+ that:
+ - cm_query_all_bd is not changed
+ - nm_query_all_bd is not changed
+ - cm_query_all_bd.current[0].bdRef.bdName == nm_query_all_bd.current[0].bdRef.bdName == "ansible_test_1"
+ - cm_query_all_bd.current[0].bdRef.schemaId == nm_query_all_bd.current[0].bdRef.schemaId
+ - cm_query_all_bd.current[0].bdRef.templateName == nm_query_all_bd.current[0].bdRef.templateName == "Template1"
+ - cm_query_all_bd.current[1].bdRef.bdName == nm_query_all_bd.current[1].bdRef.bdName == "ansible_test_3"
+ - cm_query_all_bd.current[1].bdRef.schemaId == nm_query_all_bd.current[1].bdRef.schemaId
+ - cm_query_all_bd.current[1].bdRef.templateName == nm_query_all_bd.current[1].bdRef.templateName == "Template1"
+
+- name: Remove BD 2 (check_mode)
+ mso_schema_site_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 2
+ bd: ansible_test_2
+ state: absent
+ check_mode: true
+ register: cm_remove_site_bd_2
+
+- name: Remove BD 2 (normal_mode)
+ mso_schema_site_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 2
+ bd: ansible_test_2
+ state: absent
+ register: nm_remove_site_bd_2
+
+- name: Verify cm_remove_site_bd_2 and nm_remove_site_bd_2
+ assert:
+ that:
+ - cm_remove_site_bd_2 is changed
+ - nm_remove_site_bd_2 is changed
+ - cm_remove_site_bd_2.previous.bdRef.bdName == nm_remove_site_bd_2.previous.bdRef.bdName == "ansible_test_2"
+ - cm_remove_site_bd_2.previous.bdRef.schemaId == nm_remove_site_bd_2.previous.bdRef.schemaId
+ - cm_remove_site_bd_2.previous.bdRef.templateName == nm_remove_site_bd_2.previous.bdRef.templateName == "Template2"
+ - cm_remove_site_bd_2.current == nm_remove_site_bd_2.current == {}
+
+- name: Remove BD 2 again(normal_mode)
+ mso_schema_site_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 2
+ bd: ansible_test_2
+ state: absent
+ register: nm_remove_site_bd_2_again
+
+- name: Verify nm_remove_site_bd_2_again
+ assert:
+ that:
+ - nm_remove_site_bd_2_again is not changed
+ - nm_remove_site_bd_2_again.previous == nm_remove_site_bd_2_again.current == {}
+ when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined
+
+- name: Query site without BD (normal_mode)
+ mso_schema_site_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 2
+ state: query
+ register: nm_query_without_bd
+
+- name: Verify nm_query_without_bd
+ assert:
+ that:
+ - nm_query_without_bd is not changed
+ - nm_query_without_bd.current == []
+ when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined
+
+
+# QUERY NON-EXISTING BD
+- name: Query non-existing BD (check_mode)
+ mso_schema_site_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ bd: non_existing_bd
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_query_non_bd
+
+- name: Query non-existing BD (normal_mode)
+ mso_schema_site_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ bd: non_existing_bd
+ state: query
+ ignore_errors: true
+ register: nm_query_non_bd
+
+- name: Verify cm_query_non_bd and nm_query_non_bd
+ assert:
+ that:
+ - cm_query_non_bd is not changed
+ - nm_query_non_bd is not changed
+ - cm_query_non_bd.msg == nm_query_non_bd.msg == "BD 'non_existing_bd' not found"
+
+# USE NON-EXISTING STATE
+- name: non_existing_state state (check_mode)
+ mso_schema_site_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ bd: ansible_test_1
+ state: non_existing_state
+ ignore_errors: true
+ register: cm_non_existing_state
+
+- name: non_existing_state state (normal_mode)
+ mso_schema_site_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ bd: ansible_test_1
+ state: non_existing_state
+ ignore_errors: true
+ register: nm_non_existing_state
+
+- name: Verify cm_non_existing_state and nm_non_existing_state
+ assert:
+ that:
+ - cm_non_existing_state is not changed
+ - nm_non_existing_state is not changed
+ - cm_non_existing_state.msg == nm_non_existing_state.msg == "value of state must be one of{{':'}} absent, present, query, got{{':'}} non_existing_state"
+
+# USE A NON_EXISTING_TEMPLATE
+- name: non_existing_template (check_mode)
+ mso_schema_site_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: non_existing_template
+ bd: ansible_test_1
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_template
+
+- name: non_existing_template (normal_mode)
+ mso_schema_site_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: non_existing_template
+ bd: ansible_test_1
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_template
+
+- name: Verify cm_non_existing_template and nm_non_existing_template
+ assert:
+ that:
+ - cm_non_existing_template is not changed
+ - nm_non_existing_template is not changed
+ - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non_existing_template' does not exist. Existing templates{{':'}} Template1, Template2, Template3"
+
+# USE A NON_EXISTING_SCHEMA
+- name: non_existing_schema (check_mode)
+ mso_schema_site_bd:
+ <<: *mso_info
+ schema: non_existing_schema
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ bd: ansible_test_1
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_schema
+
+- name: non_existing_schema (normal_mode)
+ mso_schema_site_bd:
+ <<: *mso_info
+ schema: non_existing_schema
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ bd: ansible_test_1
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_schema
+
+- name: Verify cm_non_existing_schema and nm_non_existing_schema
+ assert:
+ that:
+ - cm_non_existing_schema is not changed
+ - nm_non_existing_schema is not changed
+ - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non_existing_schema' does not exist."
+
+# USE A NON_EXISTING_SITE
+- name: non_existing_site (check_mode)
+ mso_schema_site_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: non_existing_site
+ template: Template 1
+ bd: ansible_test_1
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_site
+
+- name: non_existing_site (normal_mode)
+ mso_schema_site_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: non_existing_site
+ template: Template 1
+ bd: ansible_test_1
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_site
+
+- name: Verify cm_non_existing_site and nm_non_existing_site
+ assert:
+ that:
+ - cm_non_existing_site is not changed
+ - nm_non_existing_site is not changed
+ - cm_non_existing_site.msg == nm_non_existing_site.msg == "Site 'non_existing_site' is not a valid site name."
+
+# USE A NON_EXISTING_SITE_TEMPLATE
+- name: non_existing_site_template (check_mode)
+ mso_schema_site_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 3
+ bd: ansible_test_1
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_site_template
+
+- name: non_existing_site_template (normal_mode)
+ mso_schema_site_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 3
+ bd: ansible_test_1
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_site_template
+
+- name: Verify cm_non_existing_site_template and nm_non_existing_site_template
+ assert:
+ that:
+ - cm_non_existing_site_template is not changed
+ - nm_non_existing_site_template is not changed
+ - cm_non_existing_site_template.msg == nm_non_existing_site_template.msg == "Provided site-template association 'ansible_test-Template3' does not exist."
+
+# USE A TEMPLATE WITHOUT ANY SITE
+- name: Add site BD to Schema 2 Template 3 without any site associated (check mode)
+ mso_schema_site_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 3
+ bd: ansible_test_1
+ state: present
+ check_mode: true
+ ignore_errors: true
+ register: cm_no_site_associated
+
+- name: Add site BD to Template 3 without any site associated (normal mode)
+ mso_schema_site_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 3
+ bd: ansible_test_1
+ state: present
+ ignore_errors: true
+ register: nm_no_site_associated
+
+- name: Verify cm_no_site_associated and nm_no_site_associated
+ assert:
+ that:
+ - cm_no_site_associated is not changed
+ - nm_no_site_associated is not changed
+ - cm_no_site_associated.msg == nm_no_site_associated.msg == "No site associated with template 'Template3'. Associate the site with the template using mso_schema_site." \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd_l3out/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd_l3out/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd_l3out/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd_l3out/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd_l3out/tasks/main.yml
new file mode 100644
index 000000000..1e1914522
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd_l3out/tasks/main.yml
@@ -0,0 +1,447 @@
+# Test code for the MSO modules
+# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+# Copyright: (c) 2022, Akini Ross (@akinross) <akinross@cisco.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Remove schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ ignore_errors: true
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Ensure site exists
+ mso_site:
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ apic_username }}'
+ apic_password: '{{ apic_password }}'
+ apic_site_id: '{{ apic_site_id | default(101) }}'
+ urls:
+ - https://{{ apic_hostname }}
+ state: present
+
+- name: Ensure sites removed from tenant ansible_test
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_site | default("ansible_test") }}'
+ - 'aws_{{ mso_site | default("ansible_test") }}'
+ - 'azure_{{ mso_site | default("ansible_test") }}'
+
+- name: Ensure tenant ansible_test exists
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ state: present
+
+- name: Ensure schema with Template1 and Template2 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: '{{item.template}}'
+ state: present
+ loop:
+ - { template: Template1}
+ - { template: Template2}
+
+- name: Ensure schema 2 with Template3 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template3
+ state: present
+
+- name: Add physical site to a schema
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ state: present
+
+- name: Add physical site to a schema2
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template3
+ state: present
+
+- name: Ensure VRF1 exists
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ vrf: VRF1
+ state: present
+
+- name: Ensure VRF2 exists
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template2
+ vrf: VRF2
+ state: present
+
+- name: Ensure VRF3 exists
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template3
+ vrf: VRF3
+ state: present
+
+- name: Add template BD
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ vrf:
+ name: VRF1
+ state: present
+ register: nm_add_bd
+
+- name: Verify nm_add_bd
+ assert:
+ that:
+ - nm_add_bd is changed
+ - nm_add_bd.previous == {}
+ - nm_add_bd.current.name == "ansible_test_1"
+ - nm_add_bd.current.vrfRef.templateName == "Template1"
+ - nm_add_bd.current.vrfRef.vrfName == "VRF1"
+
+- name: Add a new L3out
+ mso_schema_template_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ l3out: L3out1
+ vrf:
+ name: VRF1
+ state: present
+ register: nm_add_l3out
+
+- name: Add a new L3out2 in different template
+ mso_schema_template_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template2
+ l3out: L3out2
+ vrf:
+ name: VRF2
+ state: present
+ register: nm_add_l3out
+
+- name: Add a new L3out3 in different schema
+ mso_schema_template_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template3
+ l3out: L3out3
+ vrf:
+ name: VRF3
+ state: present
+ register: nm_add_l3out
+
+- name: Verify nm_add_l3out
+ assert:
+ that:
+ - nm_add_l3out is changed
+ - nm_add_l3out.previous == {}
+
+- name: Add BD to site
+ mso_schema_site_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ state: present
+ register: nm_add_site_bd
+
+- name: Verify nm_add_site_bd
+ assert:
+ that:
+ - nm_add_site_bd.current.bdRef.bdName == "ansible_test_1"
+ - nm_add_site_bd.current.bdRef.templateName == "Template1"
+ - nm_add_site_bd.current.hostBasedRouting == false
+
+- name: Verify nm_add_site_bd
+ assert:
+ that:
+ - nm_add_site_bd is changed
+ - nm_add_site_bd.previous == {}
+ when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined
+
+- name: Add l3out to BD Site
+ mso_schema_site_bd_l3out: &site_bd_l3out_again
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ l3out:
+ name: L3out1
+ state: present
+ register: nm_bd_site_l3out
+
+- name: Add l3out to BD Site again
+ mso_schema_site_bd_l3out:
+ <<: *site_bd_l3out_again
+ register: nm_bd_site_l3out_again
+
+- name: Verify nm_bd_site_l3out
+ assert:
+ that:
+ - nm_bd_site_l3out is changed
+ - nm_bd_site_l3out.previous == {}
+ - nm_bd_site_l3out.current.l3outName == "L3out1"
+ - nm_bd_site_l3out_again is not changed
+
+- name: Query a specific BD site l3out
+ mso_schema_site_bd_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ l3out:
+ name: L3out1
+ state: query
+ register: query_result
+
+- name: Verify query_result
+ assert:
+ that:
+ - query_result is not changed
+ - nm_bd_site_l3out.current.l3outName == "L3out1"
+
+- name: Add l3out2 from different template to BD Site
+ mso_schema_site_bd_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ l3out:
+ name: L3out2
+ template: Template2
+ state: present
+ register: nm_bd_site_l3out2
+
+- name: Add l3out3 from different schema to BD Site
+ mso_schema_site_bd_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ l3out:
+ name: L3out3
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template3
+ state: present
+ register: nm_bd_site_l3out3
+
+- name: Verify nm_bd_site_l3out2 and nm_bd_site_l3out3
+ assert:
+ that:
+ - nm_bd_site_l3out2 is changed
+ - nm_bd_site_l3out2.previous == {}
+ - nm_bd_site_l3out2.current.l3outName == "L3out2"
+ - nm_bd_site_l3out3 is changed
+ - nm_bd_site_l3out3.previous == {}
+ - nm_bd_site_l3out3.current.l3outName == "L3out3"
+
+- name: Query all BD site l3outs
+ mso_schema_site_bd_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ state: query
+ register: query_all
+
+- name: Verify query_all
+ assert:
+ that:
+ - query_all is not changed
+ - query_all.current.0.l3outName == "L3out1"
+ - query_all.current.1.l3outName == "L3out2"
+ - query_all.current.2.l3outName == "L3out3"
+
+# Checking error conditions
+- name: Use non_existing template
+ mso_schema_site_bd_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: non_existing_template
+ bd: ansible_test_1
+ l3out:
+ name: L3out1
+ state: query
+ ignore_errors: true
+ register: non_existing_template
+
+- name: Use non_existing BD
+ mso_schema_site_bd_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ bd: BD1
+ state: query
+ ignore_errors: true
+ register: non_existing_bd
+
+- name: Query non_existing BD site L3out
+ mso_schema_site_bd_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ l3out:
+ name: non_existing_L3out
+ state: query
+ ignore_errors: true
+ register: non_existing_l3out
+
+- name: Verify error query
+ assert:
+ that:
+ - non_existing_template is not changed
+ - non_existing_template.msg == "Provided template 'non_existing_template' not matching existing template(s){{':'}} Template1, Template2"
+ - non_existing_bd is not changed
+ - non_existing_bd.msg == "Provided BD 'BD1' not matching existing bd(s){{':'}} ansible_test_1"
+ - non_existing_l3out is not changed
+ - non_existing_l3out.msg == "L3out 'non_existing_L3out' not found"
+
+# Check addition of l3out to Site BD without adding BD to site
+- name: Remove l3out from BD Site
+ mso_schema_site_bd_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ l3out:
+ name: L3out1
+ state: absent
+ register: remove_bd_site_l3out
+
+- name: Verify remove_bd_site_l3out
+ assert:
+ that:
+ - remove_bd_site_l3out is changed
+ - remove_bd_site_l3out.previous.l3outName == "L3out1"
+ - remove_bd_site_l3out.current == {}
+
+- name: Remove BD from site
+ mso_schema_site_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ state: absent
+ register: nm_remove_site_bd
+
+- name: Verify nm_remove_site_bd
+ assert:
+ that:
+ - nm_remove_site_bd is changed
+ - nm_remove_site_bd.previous.bdRef.bdName == "ansible_test_1"
+ - nm_remove_site_bd.previous.bdRef.templateName == "Template1"
+ - nm_remove_site_bd.current == {}
+
+- name: Remove template BD
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ state: absent
+ register: nm_remove_bd
+
+- name: Verify nm_remove_bd
+ assert:
+ that:
+ - nm_remove_bd is changed
+ - nm_remove_bd.current == {}
+
+- name: Add new template BD
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_bd
+ vrf:
+ name: VRF1
+ state: present
+ register: nm_add_bd_template
+
+- name: Verify nm_add_bd_template
+ assert:
+ that:
+ - nm_add_bd_template is changed
+ - nm_add_bd_template.previous == {}
+
+- name: Add a new l3 out to BD (BD not associated to Site)
+ mso_schema_site_bd_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_bd
+ l3out:
+ name: L3out1
+ state: present
+ register: add_bd_site_l3out
+
+- name: Verify add_bd_site_l3out
+ assert:
+ that:
+ - add_bd_site_l3out is changed
+ - add_bd_site_l3out.previous == {}
+ - add_bd_site_l3out.current.l3outName == "L3out1"
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd_subnet/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd_subnet/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd_subnet/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd_subnet/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd_subnet/tasks/main.yml
new file mode 100644
index 000000000..1f6f94c02
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_bd_subnet/tasks/main.yml
@@ -0,0 +1,665 @@
+# Test code for the MSO modules
+# Copyright: (c) 2022, Akini Ross (@akinross) <akinross@cisco.com>
+# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Remove schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Ensure site exists
+ mso_site:
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ apic_username }}'
+ apic_password: '{{ apic_password }}'
+ apic_site_id: '{{ apic_site_id | default(101) }}'
+ urls:
+ - https://{{ apic_hostname }}
+ state: present
+
+- name: Ensure sites removed from tenant ansible_test
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_site | default("ansible_test") }}'
+ - 'aws_{{ mso_site | default("ansible_test") }}'
+ - 'azure_{{ mso_site | default("ansible_test") }}'
+
+- name: Ensure tenant ansible_test exists
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ state: present
+
+- name: Ensure schema 1 with Template1, Template2, Template4 and Template5
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: '{{ item }}'
+ state: present
+ loop:
+ - Template1
+ - Template2
+ - Template4
+ - Template5
+
+- name: Ensure schema 2 with Template3 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template3
+ state: present
+
+- name: Add physical site to templates
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: '{{ item }}'
+ state: present
+ loop:
+ - Template1
+ - Template2
+ - Template5
+
+- name: Ensure VRF1 exists
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ vrf: VRF1
+ layer3_multicast: true
+ state: present
+
+- name: Add template BD to Template1 for create in site subnet
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_site_bd_from_subnet
+ layer2_stretch: false
+ vrf:
+ name: VRF1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ state: present
+
+- name: Add template BD to Template3
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template3
+ bd: ansible_test_3
+ vrf:
+ name: VRF1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ state: present
+ register: nm_add_bd_template_3
+
+- name: Add template BD to Template2
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template2
+ bd: ansible_test_2
+ vrf:
+ name: VRF1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ state: present
+ register: nm_add_bd_template_2
+
+- name: Add template BD to Template4
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template4
+ bd: ansible_test_4
+ vrf:
+ name: VRF1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ state: present
+ register: nm_add_bd_template_4
+
+- name: Add template BD to Template1 without disabling layer2_stretch
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ vrf:
+ name: VRF1
+ state: present
+ register: nm_add_bd
+
+- name: Add site BD
+ mso_schema_site_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ state: present
+ register: nm_add_site_bd
+
+- name: Add site BD subnet with layer2_stretch enabled
+ mso_schema_site_bd_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ subnet: 10.1.0.1/16
+ state: present
+ ignore_errors: true
+ register: add_site_bd_subnet_with_l2Stretch_enabled
+
+- name: Verify add_site_bd_subnet_with_l2Stretch_enabled
+ assert:
+ that:
+ - add_site_bd_subnet_with_l2Stretch_enabled.msg == "The l2Stretch of template bd should be false in order to create a site bd subnet. Set l2Stretch as false using mso_schema_template_bd"
+
+- name: Disable layer2_stretch in template BD
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ layer2_stretch: false
+ vrf:
+ name: VRF1
+ state: present
+ register: nm_add_bd
+
+- name: Add site BD subnet with layer2_stretch disabled (check_mode)
+ mso_schema_site_bd_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ subnet: 10.1.0.1/16
+ state: present
+ check_mode: true
+ register: cm_add_site_bd_subnet
+
+- name: Verify cm_add_site_bd_subnet
+ assert:
+ that:
+ - cm_add_site_bd_subnet is changed
+ - cm_add_site_bd_subnet.previous == {}
+ - cm_add_site_bd_subnet.current.ip == "10.1.0.1/16"
+ - cm_add_site_bd_subnet.current.scope == "private"
+ - cm_add_site_bd_subnet.current.description == "10.1.0.1/16"
+ - cm_add_site_bd_subnet.current.shared == False
+ - cm_add_site_bd_subnet.current.noDefaultGateway == False
+ - cm_add_site_bd_subnet.current.querier == False
+
+- name: Add site BD subnet with layer2_stretch disabled (normal_mode)
+ mso_schema_site_bd_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ subnet: 10.1.0.1/16
+ state: present
+ register: nm_add_site_bd_subnet
+
+- name: Verify nm_add_site_bd_subnet
+ assert:
+ that:
+ - nm_add_site_bd_subnet is changed
+ - nm_add_site_bd_subnet.previous == {}
+ - nm_add_site_bd_subnet.current.ip == "10.1.0.1/16"
+ - nm_add_site_bd_subnet.current.scope == "private"
+ - nm_add_site_bd_subnet.current.description == "10.1.0.1/16"
+ - nm_add_site_bd_subnet.current.shared == False
+ - nm_add_site_bd_subnet.current.noDefaultGateway == False
+ - nm_add_site_bd_subnet.current.querier == False
+
+- name: Add site BD subnet again
+ mso_schema_site_bd_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ subnet: 10.1.0.1/16
+ state: present
+ register: nm_add_site_bd_subnet_again
+
+- name: Verify nm_add_site_bd_subnet_again
+ assert:
+ that:
+ - nm_add_site_bd_subnet_again is not changed
+
+- name: Add another site BD subnet
+ mso_schema_site_bd_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ subnet: 10.10.10.1/16
+ description: another subnet
+ scope: public
+ shared: true
+ no_default_gateway: true
+ querier: true
+ state: present
+ register: nm_add_another_site_bd_subnet
+
+- name: Verify nm_add_another_site_bd_subnet
+ assert:
+ that:
+ - nm_add_another_site_bd_subnet is changed
+ - nm_add_another_site_bd_subnet.previous == {}
+ - nm_add_another_site_bd_subnet.current.description == "another subnet"
+ - nm_add_another_site_bd_subnet.current.scope == "public"
+ - nm_add_another_site_bd_subnet.current.shared == true
+ - nm_add_another_site_bd_subnet.current.noDefaultGateway == true
+ - nm_add_another_site_bd_subnet.current.querier == true
+
+- name: Add BD ansible_test_5 to Schema1, template5
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template5
+ bd: ansible_test_5
+ layer2_stretch: false
+ vrf:
+ name: VRF1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ state: present
+
+- name: Add site BD5
+ mso_schema_site_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template5
+ bd: ansible_test_5
+ state: present
+
+- name: Add site BD5 subnet with layer2_stretch disabled (normal_mode)
+ mso_schema_site_bd_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template5
+ bd: ansible_test_5
+ subnet: 10.1.0.5/16
+ is_virtual_ip: true
+ scope: public
+ shared: true
+ no_default_gateway: true
+ querier: true
+ primary: true
+ state: present
+ register: nm_add_site_bd_subnet5
+
+- name: Verify nm_add_site_bd_subnet5 for a version that's < 3.1
+ assert:
+ that:
+ - nm_add_site_bd_subnet5 is changed
+ - nm_add_site_bd_subnet5.previous == {}
+ - nm_add_site_bd_subnet5.current.ip == "10.1.0.5/16"
+ - nm_add_site_bd_subnet5.current.scope == "public"
+ - nm_add_site_bd_subnet5.current.description == "10.1.0.5/16"
+ - nm_add_site_bd_subnet5.current.shared == True
+ - nm_add_site_bd_subnet5.current.noDefaultGateway == True
+ - nm_add_site_bd_subnet5.current.querier == True
+ when: version.current.version is version('3.1.1g', '<')
+
+- name: Verify nm_add_site_bd_subnet5 for a version that's >= 3.1
+ assert:
+ that:
+ - nm_add_site_bd_subnet5 is changed
+ - nm_add_site_bd_subnet5.previous == {}
+ - nm_add_site_bd_subnet5.current.ip == "10.1.0.5/16"
+ - nm_add_site_bd_subnet5.current.scope == "public"
+ - nm_add_site_bd_subnet5.current.description == "10.1.0.5/16"
+ - nm_add_site_bd_subnet5.current.shared == True
+ - nm_add_site_bd_subnet5.current.noDefaultGateway == True
+ - nm_add_site_bd_subnet5.current.querier == True
+ - nm_add_site_bd_subnet5.current.virtual == True
+ when: version.current.version is version('3.1.1g', '>=')
+
+- name: Verify nm_add_site_bd_subnet5 for a version that's >= 3.1.1h
+ assert:
+ that:
+ - nm_add_site_bd_subnet5.current.primary == True
+ when: version.current.version is version('3.1.1h', '>=')
+
+- name: Add site BD subnet with non existing site bd (normal_mode)
+ mso_schema_site_bd_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_site_bd_from_subnet
+ subnet: 10.1.0.1/16
+ state: present
+ register: nm_add_site_bd_subnet_and_site_bd
+
+- name: Verify nm_add_site_bd_subnet_and_site_bd
+ assert:
+ that:
+ - nm_add_site_bd_subnet_and_site_bd is changed
+ - nm_add_site_bd_subnet_and_site_bd.previous == {}
+ - nm_add_site_bd_subnet_and_site_bd.current.ip == "10.1.0.1/16"
+ - nm_add_site_bd_subnet_and_site_bd.current.scope == "private"
+ - nm_add_site_bd_subnet_and_site_bd.current.description == "10.1.0.1/16"
+ - nm_add_site_bd_subnet_and_site_bd.current.shared == False
+ - nm_add_site_bd_subnet_and_site_bd.current.noDefaultGateway == False
+ - nm_add_site_bd_subnet_and_site_bd.current.querier == False
+
+- name: Query site bd
+ mso_schema_site_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_site_bd_from_subnet
+ state: query
+ register: query_site_bd
+
+- name: Verify query_site_bd
+ assert:
+ that:
+ - query_site_bd.current.bdRef.bdName == "ansible_test_site_bd_from_subnet"
+
+- name: Add site BD subnet with now existing site bd again (normal_mode)
+ mso_schema_site_bd_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_site_bd_from_subnet
+ subnet: 10.1.0.1/16
+ state: present
+ register: nm_add_site_bd_subnet_and_site_bd_again
+
+- name: Verify nm_add_site_bd_subnet_and_site_bd_again
+ assert:
+ that:
+ - nm_add_site_bd_subnet_and_site_bd_again is not changed
+
+- name: Query all subnets
+ mso_schema_site_bd_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ state: query
+ register: query_all
+
+- name: Verify query_all
+ assert:
+ that:
+ - query_all is not changed
+ - query_all.current | length == 2
+ - query_all.current.0.ip == "10.1.0.1/16"
+ - query_all.current.1.ip == "10.10.10.1/16"
+
+- name: Query a specific site BD subnet
+ mso_schema_site_bd_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ subnet: 10.1.0.1/16
+ state: query
+ register: query_subnet
+
+- name: Verify query_subnet
+ assert:
+ that:
+ - query_subnet is not changed
+ - query_subnet.current.ip == "10.1.0.1/16"
+
+- name: Query a specific site BD5 subnet
+ mso_schema_site_bd_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template5
+ bd: ansible_test_5
+ subnet: 10.1.0.5/16
+ state: query
+ register: query_subnet5
+
+- name: Verify query_subnet5 for version before 3.1
+ assert:
+ that:
+ - query_subnet5 is not changed
+ - query_subnet5.current.ip == "10.1.0.5/16"
+ when: version.current.version is version('3.1.1g', '<')
+
+- name: Verify query_subnet5 for 3.1 version and later
+ assert:
+ that:
+ - query_subnet5 is not changed
+ - query_subnet5.current.ip == "10.1.0.5/16"
+ - query_subnet5.current.virtual == true
+ when: version.current.version is version('3.1.1g', '>=')
+
+- name: Verify query_subnet5 for 3.1.1h version and later
+ assert:
+ that:
+ - query_subnet5.current.primary == true
+ when: version.current.version is version('3.1.1h', '>=')
+
+- name: Remove a site BD subnet
+ mso_schema_site_bd_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ subnet: 10.1.0.1/16
+ state: absent
+ register: rm_subnet
+
+- name: Verify rm_subnet
+ assert:
+ that:
+ - rm_subnet is changed
+ - rm_subnet.current == {}
+ - rm_subnet.previous.ip == "10.1.0.1/16"
+
+- name: Remove the site BD subnet again
+ mso_schema_site_bd_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ subnet: 10.1.0.1/16
+ state: absent
+ register: rm_subnet_again
+
+- name: Verify rm_subnet_again
+ assert:
+ that:
+ - rm_subnet_again is not changed
+ - rm_subnet_again.previous == rm_subnet_again.current == {}
+
+- name: Remove a site BD 5 subnet
+ mso_schema_site_bd_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template5
+ bd: ansible_test_5
+ subnet: 10.1.0.5/16
+ state: absent
+ register: rm_subnet5
+
+- name: Verify rm_subnet5
+ assert:
+ that:
+ - rm_subnet5 is changed
+ - rm_subnet5.current == {}
+ - rm_subnet5.previous.ip == "10.1.0.5/16"
+
+# Use non_existing_schema
+- name: Query subnet by non_existing_schema
+ mso_schema_site_bd_subnet:
+ <<: *mso_info
+ schema: non_existing_schema
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ subnet: 10.1.0.1/16
+ state: query
+ ignore_errors: true
+ register: non_existing_schema
+
+- name: Verify non_existing_schema
+ assert:
+ that:
+ - non_existing_schema.msg == "Provided schema 'non_existing_schema' does not exist."
+
+# Use non_existing_template
+- name: Query subnet by non_existing_template
+ mso_schema_site_bd_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: non_existing_template
+ bd: ansible_test_1
+ subnet: 10.1.0.1/16
+ state: query
+ ignore_errors: true
+ register: non_existing_template
+
+- name: Verify non_existing_template
+ assert:
+ that:
+ - non_existing_template.msg == "Provided template 'non_existing_template' not matching existing template(s){{':'}} Template1, Template2, Template4, Template5"
+
+# Use non_existing_template_bd
+- name: Query subnet by non_existing_template_bd
+ mso_schema_site_bd_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ bd: non_existing_template_bd
+ subnet: 10.1.0.1/16
+ state: query
+ ignore_errors: true
+ register: non_existing_template_bd
+
+- name: Verify non_existing_template_bd
+ assert:
+ that:
+ - non_existing_template_bd.msg == "Provided BD 'non_existing_template_bd' not matching existing bd(s){{':'}} ansible_test_site_bd_from_subnet, ansible_test_1"
+
+# Use template without site associated
+- name: Query with no site associated to template
+ mso_schema_site_bd_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template3
+ bd: ansible_test_3
+ subnet: 10.1.0.1/16
+ state: query
+ ignore_errors: true
+ register: template_without_sites
+
+- name: Verify template_without_sites
+ assert:
+ that:
+ - template_without_sites.msg == "No sites associated with schema 'ansible_test_2'. Associate the site with the schema using (M) mso_schema_site."
+
+# Use non_existing_subnet
+- name: Query with non_existing_subnet
+ mso_schema_site_bd_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ subnet: non_existing_subnet
+ state: query
+ ignore_errors: true
+ register: non_existing_subnet
+
+- name: Verify non_existing_subnet
+ assert:
+ that:
+ - non_existing_subnet.msg.startswith("Provided subnet 'non_existing_subnet' not matching existing site bd subnet(s){{':'}}")
+
+# Use non_existing_site_template_association
+- name: Query with non_existing_site_template_association
+ mso_schema_site_bd_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template4
+ bd: ansible_test_4
+ subnet: 10.1.0.1/16
+ state: query
+ ignore_errors: true
+ register: non_existing_site_template_association
+
+- name: Verify non_existing_site_template_association
+ assert:
+ that:
+ - non_existing_site_template_association.msg == "Provided site 'ansible_test' not associated with template 'Template4'. Site is currently associated with template(s){{':'}} Template1, Template2, Template5"
+
+- name: Remove schemas for next ci test case
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_external_epg/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_external_epg/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_external_epg/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_external_epg/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_external_epg/tasks/main.yml
new file mode 100644
index 000000000..5b8c799db
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_external_epg/tasks/main.yml
@@ -0,0 +1,833 @@
+# Test code for the MSO modules
+# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+#
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Remove schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Ensure site exists
+ mso_site:
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ apic_username }}'
+ apic_password: '{{ apic_password }}'
+ apic_site_id: '{{ apic_site_id | default(101) }}'
+ urls:
+ - https://{{ apic_hostname }}
+ state: present
+
+- name: Ensure sites removed from tenant ansible_test
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_site | default("ansible_test") }}'
+ - 'aws_{{ mso_site | default("ansible_test") }}'
+ - 'azure_{{ mso_site | default("ansible_test") }}'
+
+- name: Ensure tenant ansible_test exist
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ state: present
+ ignore_errors: true
+
+- name: Associate non-cloud site with ansible_test
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: present
+ register: add_ncs
+
+- name: Verify add_ncs
+ assert:
+ that:
+ - add_ncs is not changed
+
+- name: Remove a site from a schema with Template1 and Template2
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: '{{ item }}'
+ state: absent
+ ignore_errors: true
+ loop:
+ - Template2
+ - Template1
+
+- name: Remove schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+# Ensure pre requisites exist
+- name: Ensure schema 1 with Template1 and 2 exists
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: '{{ item }}'
+ state: present
+ loop:
+ - Template1
+ - Template2
+ - Template3
+
+- name: Ensure VRF1 exists
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ vrf: VRF1
+ state: present
+
+- name: Ensure VRF2 exists
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template2
+ vrf: VRF2
+ state: present
+
+- name: Ensure L3Out1 Exists
+ mso_schema_template_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ vrf:
+ name: VRF1
+ l3out: L3out1
+ state: present
+
+- name: Ensure L3Out2 Exists
+ mso_schema_template_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template2
+ vrf:
+ name: VRF2
+ l3out: L3out2
+ state: present
+
+# ADD external EPG to template
+- name: Add external EPG at template level(check_mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ external_epg: ansible_test_1
+ vrf:
+ name: VRF1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ l3out:
+ name: L3out1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ state: present
+ check_mode: true
+ register: cm_add_epg
+
+- name: Verify cm_add_epg
+ assert:
+ that:
+ - cm_add_epg is changed
+ - cm_add_epg.previous == {}
+ - cm_add_epg.current.name == "ansible_test_1"
+ - cm_add_epg.current.vrfRef.templateName == "Template1"
+ - cm_add_epg.current.vrfRef.vrfName == "VRF1"
+
+- name: Add external EPG at template level(normal mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ external_epg: ansible_test_1
+ vrf:
+ name: VRF1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ l3out:
+ name: L3out1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ state: present
+ register: nm_add_epg
+
+- name: Verify nm_add_epg
+ assert:
+ that:
+ - nm_add_epg is changed
+ - nm_add_epg.previous == {}
+ - nm_add_epg.current.name == "ansible_test_1"
+ - nm_add_epg.current.vrfRef.templateName == "Template1"
+ - nm_add_epg.current.vrfRef.vrfName == "VRF1"
+ - cm_add_epg.current.vrfRef.schemaId == nm_add_epg.current.vrfRef.schemaId
+
+# Add External EPG to Site when MSO version >= 3.3
+- name: Execute tasks only for MSO version >= 3.3
+ when: version.current.version is version('3.3', '>=')
+ block:
+ # Associate site to schema/template after creating External EPG
+ - name: Add non-cloud site to a schema
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ state: present
+ register: add_site
+
+ - name: Verify add_site
+ assert:
+ that:
+ - add_site.current.siteId is match ("[0-9a-zA-Z]*")
+ - add_site.current.templateName == "Template1"
+
+ - name: Add site L3Out (normal_mode)
+ mso_schema_site_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ l3out: '{{item.l3out}}'
+ vrf:
+ name: VRF1
+ template: Template1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ state: present
+ loop:
+ - { l3out: L3out1}
+
+ # ADD External EPGs to site
+ - name: ADD External EPG1 to site (check_mode)
+ mso_schema_site_external_epg:
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ external_epg: ansible_test_1
+ l3out: L3out1
+ state: present
+ check_mode: true
+ register: cm_add_epg
+
+ - name: Verify cm_add_epg
+ assert:
+ that:
+ - cm_add_epg.current.externalEpgRef.externalEpgName == "ansible_test_1"
+ - cm_add_epg.current.externalEpgRef.templateName == "Template1"
+
+ - name: Verify cm_add_epg
+ assert:
+ that:
+ - cm_add_epg is changed
+ - cm_add_epg.previous == {}
+ when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined
+
+ - name: Add external EPG to site (normal mode)
+ mso_schema_site_external_epg:
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ external_epg: ansible_test_1
+ l3out: L3out1
+ state: present
+ register: nm_add_epg
+
+ - name: Verify nm_add_epg
+ assert:
+ that:
+ - nm_add_epg.current.externalEpgRef.externalEpgName == "ansible_test_1"
+ - nm_add_epg.current.externalEpgRef.templateName == "Template1"
+ - cm_add_epg.current.externalEpgRef.schemaId == nm_add_epg.current.externalEpgRef.schemaId
+
+ - name: Verify nm_add_epg
+ assert:
+ that:
+ - nm_add_epg is changed
+ - nm_add_epg.previous == {}
+ when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined
+
+ - name: ADD External EPG1 to site again
+ mso_schema_site_external_epg:
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ external_epg: ansible_test_1
+ l3out: L3out1
+ state: present
+ register: add_epg_again
+
+ - name: Verify add_epg_again
+ assert:
+ that:
+ - add_epg_again is not changed
+ - add_epg_again.current.externalEpgRef.externalEpgName == "ansible_test_1"
+ - add_epg_again.current.externalEpgRef.templateName == "Template1"
+
+ # QUERY ALL EPG
+ - name: Query all external EPGs in site (check_mode)
+ mso_schema_site_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ state: query
+ check_mode: true
+ register: cm_query_all_epgs
+
+ - name: Query all EPG (normal mode)
+ mso_schema_site_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ state: query
+ register: nm_query_all_epgs
+
+ - name: Verify query_all_epgs
+ assert:
+ that:
+ - cm_query_all_epgs is not changed
+ - nm_query_all_epgs is not changed
+
+ # QUERY AN EPG
+ - name: Query epg 1(check_mode)
+ mso_schema_site_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ external_epg: ansible_test_1
+ l3out: L3out1
+ state: query
+ check_mode: true
+ register: cm_query_epg_1
+
+ - name: Query epg 1(normal_mode)
+ mso_schema_site_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ external_epg: ansible_test_1
+ l3out: L3out1
+ state: query
+ register: nm_query_epg_1
+
+ - name: Verify cm_query_epg_1 and nm_query_epg_1
+ assert:
+ that:
+ - cm_query_epg_1 is not changed
+ - nm_query_epg_1 is not changed
+
+ - name: Query epg 1 without l3Out
+ mso_schema_site_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ external_epg: ansible_test_1
+ state: query
+ register: nm_query_epg_l3out
+
+ - name: Verify nm_query_epg_l3out
+ assert:
+ that:
+ - nm_query_epg_l3out is not changed
+
+ # REMOVE EPG
+ - name: Remove EPG (check_mode)
+ mso_schema_site_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ external_epg: ansible_test_1
+ l3out: L3out1
+ state: absent
+ check_mode: true
+ register: cm_remove_epg
+
+ - name: Verify cm_remove_epg
+ assert:
+ that:
+ - cm_remove_epg is changed
+ - cm_remove_epg.current == {}
+
+ - name: Remove EPG (normal_mode)
+ mso_schema_site_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ external_epg: ansible_test_1
+ l3out: L3out1
+ state: absent
+ register: nm_remove_epg
+
+ - name: Add site external EPG without L3Out when template external EPG type is on-premise (normal_mode)
+ mso_schema_site_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ external_epg: ansible_test_1
+ state: present
+ ignore_errors: true
+ register: nm_add_epg_no_l3out
+
+ - name: Verify nm_add_epg_no_l3out
+ assert:
+ that:
+ - nm_add_epg_no_l3out is not changed
+ - nm_add_epg_no_l3out.msg == "L3Out cannot be empty when template external EPG type is 'on-premise'."
+
+ - name: Remove external EPG at template level
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ external_epg: ansible_test_1
+ state: absent
+ register: nm_remove_epg
+
+ - name: Verify nm_remove_epg
+ assert:
+ that:
+ - nm_remove_epg is changed
+ - nm_remove_epg.current == {}
+
+ # Associate site to schema/template before creating External EPG
+ - name: Add non-cloud site to a schema
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template2
+ state: present
+ when: version.current.version is version('3.3', '>=')
+ register: add_site
+
+ - name: Verify add_site
+ assert:
+ that:
+ - add_site.current.siteId is match ("[0-9a-zA-Z]*")
+ - add_site.current.templateName == "Template2"
+
+ # Create template External EPG after site association
+ - name: Add external EPG (at template level)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template2
+ external_epg: ansible_test_2
+ vrf:
+ name: VRF2
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template2
+ l3out:
+ name: L3out2
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template2
+ state: present
+ register: nm_add_ex_epg
+
+ - name: Verify nm_add_ex_epg
+ assert:
+ that:
+ - nm_add_ex_epg is changed
+ - nm_add_ex_epg.previous == {}
+ - nm_add_ex_epg.current.name == "ansible_test_2"
+ - nm_add_ex_epg.current.vrfRef.vrfName == "VRF2"
+
+ - name: Add external EPG to site (normal mode)
+ mso_schema_site_external_epg:
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template2
+ external_epg: ansible_test_2
+ l3out: L3out2
+ state: present
+ register: nm_add_epg
+
+ - name: Verify nm_add_epg
+ assert:
+ that:
+ - nm_add_epg.current.externalEpgRef.externalEpgName == "ansible_test_2"
+ - nm_add_epg.current.externalEpgRef.templateName == "Template2"
+
+ - name: Verify nm_add_epg
+ assert:
+ that:
+ - nm_add_epg is changed
+ - nm_add_epg.previous == {}
+ when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined
+
+ # QUERY NON-EXISTING external EPG
+ - name: Query non-existing External EPG (check_mode)
+ mso_schema_site_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ external_epg: non_existing_epg
+ l3out: L3out1
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_query_non_external_epg
+
+ - name: Query non-existing External EPG (normal_mode)
+ mso_schema_site_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ external_epg: non_existing_epg
+ l3out: L3out1
+ state: query
+ ignore_errors: true
+ register: nm_query_non_external_epg
+
+ - name: Verify cm_query_non_external_epg and nm_query_non_external_epg
+ assert:
+ that:
+ - cm_query_non_external_epg is not changed
+ - nm_query_non_external_epg is not changed
+ - cm_query_non_external_epg.msg == nm_query_non_external_epg.msg == "External EPG 'non_existing_epg' not found"
+
+ # USE NON-EXISTING STATE
+ - name: non_existing_state state (check_mode)
+ mso_schema_site_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ external_epg: ansible_test_2
+ l3out: L3out1
+ state: non_existing_state
+ ignore_errors: true
+ register: cm_non_existing_state
+
+ - name: non_existing_state state (normal_mode)
+ mso_schema_site_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ external_epg: ansible_test_2
+ l3out: L3out1
+ state: non_existing_state
+ ignore_errors: true
+ register: nm_non_existing_state
+
+ - name: Verify cm_non_existing_state and nm_non_existing_state
+ assert:
+ that:
+ - cm_non_existing_state is not changed
+ - nm_non_existing_state is not changed
+ - cm_non_existing_state.msg == nm_non_existing_state.msg == "value of state must be one of{{':'}} absent, present, query, got{{':'}} non_existing_state"
+
+ # USE A NON_EXISTING_TEMPLATE
+ - name: non_existing_template (check_mode)
+ mso_schema_site_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: non_existing_template
+ external_epg: ansible_test_2
+ l3out: L3out1
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_template
+
+ - name: non_existing_template site association(normal_mode)
+ mso_schema_site_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: non_existing_template
+ external_epg: ansible_test_2
+ l3out: L3out1
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_template
+
+ - name: Verify cm_non_existing_template and nm_non_existing_template
+ assert:
+ that:
+ - cm_non_existing_template is not changed
+ - nm_non_existing_template is not changed
+ - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non_existing_template' not matching existing template(s){{':'}} Template1, Template2, Template3"
+
+ # USE A NON_EXISTING_SCHEMA
+ - name: non_existing_schema (check_mode)
+ mso_schema_site_external_epg:
+ <<: *mso_info
+ schema: non_existing_schema
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ external_epg: ansible_test_2
+ l3out: L3out1
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_schema
+
+ - name: non_existing_schema (normal_mode)
+ mso_schema_site_external_epg:
+ <<: *mso_info
+ schema: non_existing_schema
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ external_epg: ansible_test_2
+ l3out: L3out1
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_schema
+
+ - name: Verify cm_non_existing_schema and nm_non_existing_schema
+ assert:
+ that:
+ - cm_non_existing_schema is not changed
+ - nm_non_existing_schema is not changed
+ - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non_existing_schema' does not exist."
+
+ # USE A NON_EXISTING_SITE
+ - name: non_existing_site (check_mode)
+ mso_schema_site_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: non_existing_site
+ template: Template1
+ external_epg: ansible_test_2
+ l3out: L3out1
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_site
+
+ - name: non_existing_site (normal_mode)
+ mso_schema_site_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: non_existing_site
+ template: Template1
+ external_epg: ansible_test_2
+ l3out: L3out1
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_site
+
+ - name: Verify cm_non_existing_site and nm_non_existing_site
+ assert:
+ that:
+ - cm_non_existing_site is not changed
+ - nm_non_existing_site is not changed
+ - cm_non_existing_site.msg == nm_non_existing_site.msg == "Site 'non_existing_site' is not a valid site name."
+
+ # USE A NON_EXISTING_SITE_TEMPLATE
+ - name: non_existing_site_template (check_mode)
+ mso_schema_site_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template3
+ external_epg: ansible_test_2
+ l3out: L3out1
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_site_template
+
+ - name: non_existing_site_template (normal_mode)
+ mso_schema_site_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template3
+ external_epg: ansible_test_2
+ l3out: L3out1
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_site_template
+
+ - name: Verify cm_non_existing_site_template and nm_non_existing_site_template
+ assert:
+ that:
+ - cm_non_existing_site_template is not changed
+ - nm_non_existing_site_template is not changed
+ - cm_non_existing_site_template.msg == nm_non_existing_site_template.msg == "Provided site 'ansible_test' not associated with template 'Template3'. Site is currently associated with template(s){{':'}} Template1, Template2"
+
+ # USE A TEMPLATE WITHOUT ANY SITE
+ - name: Add site L3Out to Schema Template2 without any site associated (check mode)
+ mso_schema_site_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template3
+ external_epg: ansible_test_2
+ l3out: L3out1
+ state: present
+ check_mode: true
+ ignore_errors: true
+ register: cm_no_site_associated
+
+ - name: Add site L3Out to Template2 without any site associated (normal mode)
+ mso_schema_site_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template3
+ external_epg: ansible_test_2
+ l3out: L3out1
+ state: present
+ ignore_errors: true
+ register: nm_no_site_associated
+
+ - name: Verify cm_no_site_associated and nm_no_site_associated
+ assert:
+ that:
+ - cm_no_site_associated is not changed
+ - nm_no_site_associated is not changed
+ - cm_no_site_associated.msg == nm_no_site_associated.msg == "Provided site 'ansible_test' not associated with template 'Template3'. Site is currently associated with template(s){{':'}} Template1, Template2"
+
+# Verify route_reachability argument when template_external_epg is associated with Azure site and
+# template_external_epg type argument is set to cloud
+- name: Ensure ANP exists
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template2
+ anp: ANP2
+ state: present
+
+- name: Ensure VRF3 exists
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template3
+ vrf: VRF3
+ state: present
+
+- name: Ensure L3Out3 Exists
+ mso_schema_template_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template3
+ vrf:
+ name: VRF3
+ l3out: L3out3
+ state: present
+
+- name: Add external EPG3 at template3 level type cloud (normal mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template3
+ external_epg: ext_epg_3
+ type: cloud
+ vrf:
+ name: VRF3
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template3
+ anp:
+ name: ANP3
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template3
+ state: present
+ register: nm_add_ext_epg_3
+
+- name: Add azure site to a schema Template3
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ template: Template3
+ state: present
+ when: version.current.version is version('3.3', '>=')
+ register: add_cloud_site
+
+- name: Add external EPG to site (normal mode)
+ mso_schema_site_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ template: Template3
+ external_epg: ext_epg_3
+ route_reachability: site-ext
+ state: present
+ register: nm_add_ext_epg_site
+
+- name: Verify nm_add_ext_epg_site
+ assert:
+ that:
+ - nm_add_ext_epg_site.current.externalEpgRef.externalEpgName == "ext_epg_3"
+ - nm_add_ext_epg_site.current.routeReachabilityInternetType == "site-ext"
+
+- name: Add external EPG to site again(normal mode)
+ mso_schema_site_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ template: Template3
+ external_epg: ext_epg_3
+ route_reachability: site-ext
+ state: present
+ register: nm_add_ext_epg_site_again
+
+- name: Verify nm_add_ext_epg_site_again
+ assert:
+ that:
+ - nm_add_ext_epg_site_again is not changed
+ - nm_add_ext_epg_site_again.current.externalEpgRef.externalEpgName == "ext_epg_3"
+ - nm_add_ext_epg_site_again.current.routeReachabilityInternetType == "site-ext" \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_external_epg_selector/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_external_epg_selector/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_external_epg_selector/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_external_epg_selector/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_external_epg_selector/tasks/main.yml
new file mode 100644
index 000000000..3701ba781
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_external_epg_selector/tasks/main.yml
@@ -0,0 +1,533 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case)
+# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Ensure azure site exists
+ mso_site:
+ <<: *mso_info
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ azure_apic_username }}'
+ apic_password: '{{ azure_apic_password }}'
+ apic_site_id: '{{ azure_site_id | default(103) }}'
+ urls:
+ - https://{{ azure_apic_hostname }}
+ state: present
+
+- name: Ensure aws site exists
+ mso_site:
+ <<: *mso_info
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ aws_apic_username }}'
+ apic_password: '{{ aws_apic_password }}'
+ apic_site_id: '{{ aws_site_id | default(102) }}'
+ urls:
+ - https://{{ aws_apic_hostname }}
+ state: present
+
+- name: Remove schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Ensure sites removed from tenant ansible_test
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_site | default("ansible_test") }}'
+ - 'aws_{{ mso_site | default("ansible_test") }}'
+ - 'azure_{{ mso_site | default("ansible_test") }}'
+
+- name: Ensure tenant ansible_test exist
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ state: present
+
+- name: Associate aws site with ansible_test
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ cloud_account: "000000000000"
+ aws_trusted: false
+ aws_access_key: "1"
+ secret_key: "0"
+ state: present
+
+- name: Associate azure site with ansible_test
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ cloud_account: uni/tn-ansible_test/act-[100]-vendor-azure
+ state: present
+
+- name: Ensure schema 1 with Template 1 and 2 exists
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: '{{ item }}'
+ state: present
+ loop:
+ - Template 1
+ - Template 2
+
+- name: Ensure schema 2 with Template 3 exists
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template 3
+ state: present
+
+- name: Ensure VRF1 exists
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF1
+ state: present
+
+- name: Ensure Template 1 with AP1 exists
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ state: present
+
+- name: Ensure L3Out Exists
+ mso_schema_template_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf:
+ name: VRF1
+ l3out: L3out1
+ state: present
+
+- name: Ensure External EPG1 exists
+ mso_schema_template_externalepg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ externalepg: extEPG1
+ vrf:
+ name: VRF1
+ template: Template 1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ l3out:
+ name: L3out1
+ template: Template 1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ anp:
+ name: AP1
+ template: Template 1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ state: present
+
+- name: Ensure External EPG2 exists
+ mso_schema_template_externalepg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ externalepg: extEPG2
+ vrf:
+ name: VRF1
+ template: Template 1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ l3out:
+ name: L3out1
+ template: Template 1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ anp:
+ name: AP1
+ template: Template 1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ state: present
+
+- name: Add Azure site to a schema
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ state: present
+ when: version.current.version is version('3', '<')
+
+- name: Add AWS site to a schema
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ state: present
+ when: version.current.version is version('3', '<')
+
+- name: Add a new CIDR in VRF1 at site level
+ mso_schema_site_vrf_region_cidr: &mso_present
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: '{{ item }}'
+ vrf: VRF1
+ region: us-west-1
+ cidr: 10.0.0.0/16
+ primary: true
+ state: present
+ loop:
+ - 'aws_{{ mso_site | default("ansible_test") }}'
+ - 'azure_{{ mso_site | default("ansible_test") }}'
+
+# ADD SELECTORS
+- name: Add a selector to Azure in check mode
+ mso_schema_site_external_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ external_epg: extEPG1
+ selector: e1
+ expressions:
+ - type: ip_address
+ operator: equals
+ value: 10.0.0.0
+ state: present
+ check_mode: true
+ register: cm_azure_e1
+
+- name: Verify cm_azure_e1
+ assert:
+ that:
+ - cm_azure_e1 is changed
+ - cm_azure_e1.previous == {}
+ - cm_azure_e1.current.subnets[0].ip == '10.0.0.0'
+ - cm_azure_e1.current.subnets[0].name == 'e1'
+
+
+- name: Add a selector to Azure in normal mode
+ mso_schema_site_external_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ external_epg: extEPG1
+ selector: e1
+ expressions:
+ - type: ip_address
+ operator: equals
+ value: 10.0.0.0
+ state: present
+ register: nm_azure_e1
+
+- name: Verify nm_azure_e1
+ assert:
+ that:
+ - nm_azure_e1 is changed
+ - nm_azure_e1.previous == {}
+ - nm_azure_e1.current.subnets[0].ip == '10.0.0.0'
+ - nm_azure_e1.current.subnets[0].name == 'e1'
+
+- name: Add a selector to AWS in normal mode
+ mso_schema_site_external_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ external_epg: extEPG2
+ selector: e2
+ expressions:
+ - type: ip_address
+ operator: equals
+ value: 10.1.1.1
+ state: present
+ register: nm_aws_e2
+
+- name: Verify nm_aws_e2
+ assert:
+ that:
+ - nm_aws_e2 is changed
+ - nm_aws_e2.previous == {}
+ - nm_aws_e2.current.subnets[0].ip == '10.1.1.1'
+ - nm_aws_e2.current.subnets[0].name == 'e2'
+
+- name: Add a selector to AWS in normal mode again
+ mso_schema_site_external_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ external_epg: extEPG2
+ selector: e2
+ expressions:
+ - type: ip_address
+ operator: equals
+ value: 10.1.1.1
+ state: present
+ register: nm_aws_e1_again
+
+- name: Verify nm_aws_e1_again
+ assert:
+ that:
+ - nm_aws_e1_again is not changed
+
+- name: Add a selector to AWS in normal mode again with no expressions
+ mso_schema_site_external_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ external_epg: extEPG2
+ selector: e2
+ state: present
+ ignore_errors: true
+ register: nm_aws_e1_again_noexp
+
+- name: Verify nm_aws_e1_again_noexp
+ assert:
+ that:
+ - nm_aws_e1_again_noexp is not changed
+ - nm_aws_e1_again_noexp.msg == "Missing expressions in selector"
+
+# QUERY A SELECTOR
+- name: Query a selector of Azure
+ mso_schema_site_external_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ external_epg: extEPG1
+ selector: e1
+ state: query
+ register: query_azure_e1
+
+- name: Verify query_azure_e1
+ assert:
+ that:
+ - query_azure_e1 is not changed
+
+# QUERY ALL
+- name: Query all selectors of Azure
+ mso_schema_site_external_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ external_epg: extEPG1
+ state: query
+ register: query_all
+
+- name: Verify query_all
+ assert:
+ that:
+ - query_all is not changed
+
+# REMOVE A SELECTOR
+- name: Remove a selector of Azure
+ mso_schema_site_external_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ external_epg: extEPG1
+ selector: e1
+ state: absent
+ register: remove_azure_e1
+
+- name: Verify remove_azure_e1
+ assert:
+ that:
+ - remove_azure_e1 is changed
+
+- name: Remove a selector of Azure again
+ mso_schema_site_external_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ external_epg: extEPG1
+ selector: e1
+ state: absent
+ ignore_errors: true
+ register: remove_azure_e1_again
+
+- name: Verify remove_azure_e1_again
+ assert:
+ that:
+ - remove_azure_e1_again is not changed
+
+# QUERY REMOVED SELECTOR
+- name: Query a removed selector of Azure
+ mso_schema_site_external_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ external_epg: extEPG1
+ selector: e1
+ state: query
+ ignore_errors: true
+ register: query_removed_azure_e1
+
+- name: Verify query_removed_azure_e1
+ assert:
+ that:
+ - query_removed_azure_e1 is not changed
+
+# USE A NON-EXISTING SCHEMA
+- name: Non-existing schema for selector (check_mode)
+ mso_schema_site_external_epg_selector:
+ <<: *mso_info
+ schema: non_existing_schema
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ external_epg: extEPG2
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_schema
+
+- name: Non-existing schema for selector (normal_mode)
+ mso_schema_site_external_epg_selector:
+ <<: *mso_info
+ schema: non_existing_schema
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ external_epg: extEPG2
+ ignore_errors: true
+ register: nm_non_existing_schema
+
+- name: Verify non_existing_schema
+ assert:
+ that:
+ - cm_non_existing_schema is not changed
+ - nm_non_existing_schema is not changed
+ - cm_non_existing_schema == nm_non_existing_schema
+ - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non_existing_schema' does not exist."
+
+# USE A NON-EXISTING TEMPLATE
+- name: Non-existing template for selector (check_mode)
+ mso_schema_site_external_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: non_existing_template
+ external_epg: extEPG2
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_template
+
+- name: Non-existing template for selector (normal_mode)
+ mso_schema_site_external_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: non_existing_template
+ external_epg: extEPG2
+ ignore_errors: true
+ register: nm_non_existing_template
+
+- name: Verify non_existing_template
+ assert:
+ that:
+ - cm_non_existing_template is not changed
+ - nm_non_existing_template is not changed
+ - cm_non_existing_template == nm_non_existing_template
+ - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non_existing_template' does not exist. Existing templates{{':'}} Template1, Template2"
+
+# USE A NON-EXISTING SITE
+- name: Non-existing site for static port (check_mode)
+ mso_schema_site_external_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ template: Template 2
+ external_epg: extEPG3
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_site
+
+- name: Non-existing site for static port (normal_mode)
+ mso_schema_site_external_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ template: Template 2
+ external_epg: extEPG3
+ ignore_errors: true
+ register: nm_non_existing_site
+
+- name: Verify non_existing_site
+ assert:
+ that:
+ - cm_non_existing_site is not changed
+ - nm_non_existing_site is not changed
+ - cm_non_existing_site == nm_non_existing_site
+ - cm_non_existing_site.msg is match("Provided site/siteId/template 'azure_ansible_test/[0-9a-zA-Z]*/Template2' does not exist. Existing siteIds/templates{{':'}} [0-9a-zA-Z]*/Template1")
+ - nm_non_existing_site.msg is match("Provided site/siteId/template 'azure_ansible_test/[0-9a-zA-Z]*/Template2' does not exist. Existing siteIds/templates{{':'}} [0-9a-zA-Z]*/Template1")
+
+# USE A TEMPLATE WITHOUT ANY SITE
+- name: Add site external EPG selector to Schema 2 Template 3 without any site associated (check mode)
+ mso_schema_site_external_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ template: Template 3
+ external_epg: extEPG3
+ ignore_errors: true
+ check_mode: true
+ register: cm_no_site_associated
+
+- name: Add site external EPG selector to Template 3 without any site associated (normal mode)
+ mso_schema_site_external_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ template: Template 3
+ external_epg: extEPG3
+ ignore_errors: true
+ register: nm_no_site_associated
+
+- name: Verify cm_no_site_associated and nm_no_site_associated
+ assert:
+ that:
+ - cm_no_site_associated is not changed
+ - nm_no_site_associated is not changed
+ - cm_no_site_associated.msg == nm_no_site_associated.msg == "No site associated with template 'Template3'. Associate the site with the template using mso_schema_site." \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_l3out/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_l3out/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_l3out/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_l3out/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_l3out/tasks/main.yml
new file mode 100644
index 000000000..1ed86e254
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_l3out/tasks/main.yml
@@ -0,0 +1,614 @@
+# Test code for the MSO modules
+# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ ansible.builtin.fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ ansible.builtin.set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+ l3outs: [ "L3out1", "L3out2"]
+
+- name: Query MSO version
+ cisco.mso.mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Ensure site exists
+ cisco.mso.mso_site:
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ apic_username }}'
+ apic_password: '{{ apic_password }}'
+ apic_site_id: '{{ apic_site_id | default(101) }}'
+ urls:
+ - https://{{ apic_hostname }}
+ state: present
+ ignore_errors: true
+
+- name: Remove schemas
+ cisco.mso.mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Ensure sites removed from tenant ansible_test
+ cisco.mso.mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_site | default("ansible_test") }}'
+ - 'aws_{{ mso_site | default("ansible_test") }}'
+ - 'azure_{{ mso_site | default("ansible_test") }}'
+
+- name: Ensure tenant ansible_test exists
+ cisco.mso.mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ state: present
+
+- name: Ensure schema 1 with Template1, and Template2 exist
+ cisco.mso.mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: '{{item.template}}'
+ state: present
+ loop:
+ - { template: Template1}
+ - { template: Template2}
+
+- name: Ensure schema 2 with Template3 exist
+ cisco.mso.mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template3
+ state: present
+
+- name: Ensure VRF1 exists
+ cisco.mso.mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ vrf: VRF1
+ state: present
+
+- name: Add new L3Out
+ cisco.mso.mso_schema_template_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ l3out: '{{ item }}'
+ vrf:
+ name: VRF1
+ state: present
+ register: add_l3out
+ loop: '{{ l3outs }}'
+
+- name: Verify add l3out (template level)
+ ansible.builtin.assert:
+ that:
+ - add_l3out is changed
+
+- name: Add physical site to a schema
+ cisco.mso.mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ state: present
+
+# Add L3out to Site when MSO version >= 3.0
+- name: Execute tasks only for MSO version >= 3.0
+ when: version.current.version is version('3.0', '>=')
+ block:
+ # Add l3out to site
+ - name: Add site L3Out (check_mode)
+ cisco.mso.mso_schema_site_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ l3out: L3out1
+ vrf:
+ name: VRF1
+ template: Template1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ state: present
+ check_mode: true
+ register: cm_add_site_l3out
+
+ - name: Verify cm_add_site_l3out
+ ansible.builtin.assert:
+ that:
+ - cm_add_site_l3out.current.vrfRef.vrfName == "VRF1"
+ - cm_add_site_l3out.current.vrfRef.templateName == "Template1"
+
+ - name: Verify cm_add_site_l3out
+ ansible.builtin.assert:
+ that:
+ - cm_add_site_l3out is changed
+ - cm_add_site_l3out.previous == {}
+ when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined
+
+ - name: Add site L3Out (normal_mode)
+ cisco.mso.mso_schema_site_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ l3out: L3out1
+ vrf:
+ name: VRF1
+ template: Template1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ state: present
+ register: nm_add_site_l3out
+
+ - name: Verify nm_add_site_l3out
+ ansible.builtin.assert:
+ that:
+ - nm_add_site_l3out.current.vrfRef.vrfName == "VRF1"
+ - nm_add_site_l3out.current.vrfRef.templateName == "Template1"
+ - cm_add_site_l3out.current.vrfRef.schemaId == nm_add_site_l3out.current.vrfRef.schemaId
+
+ - name: Verify nm_add_site_l3out
+ ansible.builtin.assert:
+ that:
+ - nm_add_site_l3out is changed
+ - nm_add_site_l3out.previous == {}
+ when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined
+
+ - name: Add site L3Out 2 (normal_mode)
+ cisco.mso.mso_schema_site_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ l3out: L3out2
+ vrf:
+ name: VRF1
+ template: Template1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ state: present
+
+ - name: Add site L3Out again (normal_mode)
+ cisco.mso.mso_schema_site_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ l3out: L3out2
+ vrf:
+ name: VRF1
+ template: Template1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ state: present
+ register: nm_add_site_l3out_again
+
+ - name: Verify cm_add_site_l3out_again and nm_add_site_l3out_again
+ ansible.builtin.assert:
+ that:
+ - nm_add_site_l3out_again is not changed
+ - nm_add_site_l3out_again.current.vrfRef.vrfName == "VRF1"
+
+ # No options to do changes in site level yet
+
+ - name: Query all L3Outs (check_mode)
+ cisco.mso.mso_schema_site_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ state: query
+ check_mode: true
+ register: cm_query_all_l3out
+
+ - name: Query all L3Outs (normal_mode)
+ cisco.mso.mso_schema_site_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ state: query
+ register: nm_query_all_l3out
+
+ - name: Verify cm_query_all_l3out and cm_query_all_l3out
+ ansible.builtin.assert:
+ that:
+ - cm_query_all_l3out is not changed
+ - nm_query_all_l3out is not changed
+ - cm_query_all_l3out.current | length == 2
+ - nm_query_all_l3out.current | length == 2
+ - "'{{cm_query_all_l3out.current[0].l3outRef.l3outName}}' in l3outs"
+ - "'{{cm_query_all_l3out.current[1].l3outRef.l3outName}}' in l3outs"
+ - "'{{nm_query_all_l3out.current[0].l3outRef.l3outName}}' in l3outs"
+ - "'{{nm_query_all_l3out.current[1].l3outRef.l3outName}}' in l3outs"
+ - cm_query_all_l3out.current[0].l3outRef.schemaId == nm_query_all_l3out.current[0].l3outRef.schemaId
+ - cm_query_all_l3out.current[0].l3outRef.templateName == nm_query_all_l3out.current[0].l3outRef.templateName == "Template1"
+
+ - name: Query a specific L3Out (check_mode)
+ cisco.mso.mso_schema_site_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ l3out: L3out1
+ vrf:
+ name: VRF1
+ template: Template1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ state: query
+ check_mode: true
+ register: cm_query_l3out
+
+ - name: Query a specific L3Out (normal_mode)
+ cisco.mso.mso_schema_site_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ l3out: L3out1
+ state: query
+ register: nm_query_l3out
+
+ - name: Verify cm_query_l3out and nm_query_l3out
+ ansible.builtin.assert:
+ that:
+ - cm_query_l3out is not changed
+ - nm_query_l3out is not changed
+
+ - name: Remove L3Out (check_mode)
+ cisco.mso.mso_schema_site_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ l3out: L3out1
+ state: absent
+ check_mode: true
+ register: cm_remove_site_l3out
+
+ - name: Remove L3Out (normal_mode)
+ cisco.mso.mso_schema_site_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ l3out: L3out1
+ state: absent
+ register: nm_remove_site_l3out
+
+ - name: Verify cm_remove_site_l3out and nm_remove_site_l3out
+ ansible.builtin.assert:
+ that:
+ - cm_remove_site_l3out is changed
+ - nm_remove_site_l3out is changed
+ - cm_remove_site_l3out.current == nm_remove_site_l3out.current == {}
+ when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined
+
+ - name: Remove L3Out again(normal_mode)
+ cisco.mso.mso_schema_site_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ l3out: L3out1
+ state: absent
+ register: nm_remove_site_l3out_again
+
+ - name: Verify nm_remove_site_l3out_again
+ ansible.builtin.assert:
+ that:
+ - nm_remove_site_l3out_again is not changed
+ - nm_remove_site_l3out_again.previous == nm_remove_site_l3out_again.current == {}
+ when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined
+
+
+ # QUERY NON-EXISTING L3Out
+ - name: Query non-existing L3Out (check_mode)
+ cisco.mso.mso_schema_site_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ l3out: non_existing_l3out
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_query_non_l3out
+
+ - name: Query non-existing L3Out (normal_mode)
+ cisco.mso.mso_schema_site_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ l3out: non_existing_l3out
+ state: query
+ ignore_errors: true
+ register: nm_query_non_l3out
+
+ - name: Verify cm_query_non_l3out and nm_query_non_l3out
+ ansible.builtin.assert:
+ that:
+ - cm_query_non_l3out is not changed
+ - nm_query_non_l3out is not changed
+ - cm_query_non_l3out.msg == nm_query_non_l3out.msg == "L3Out 'non_existing_l3out' not found"
+
+ # USE NON-EXISTING STATE
+ - name: non_existing_state state (check_mode)
+ cisco.mso.mso_schema_site_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ l3out: L3out1
+ vrf:
+ name: VRF1
+ template: Template1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ state: non_existing_state
+ ignore_errors: true
+ register: cm_non_existing_state
+
+ - name: non_existing_state state (normal_mode)
+ cisco.mso.mso_schema_site_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ l3out: L3out1
+ vrf:
+ name: VRF1
+ template: Template1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ state: non_existing_state
+ ignore_errors: true
+ register: nm_non_existing_state
+
+ - name: Verify cm_non_existing_state and nm_non_existing_state
+ ansible.builtin.assert:
+ that:
+ - cm_non_existing_state is not changed
+ - nm_non_existing_state is not changed
+ - cm_non_existing_state.msg == nm_non_existing_state.msg == "value of state must be one of{{':'}} absent, present, query, got{{':'}} non_existing_state"
+
+ # USE A NON_EXISTING_TEMPLATE
+ - name: non_existing_template (check_mode)
+ cisco.mso.mso_schema_site_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: non_existing_template
+ l3out: L3out1
+ vrf:
+ name: VRF1
+ template: Template1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_template
+
+ - name: non_existing_template (normal_mode)
+ cisco.mso.mso_schema_site_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: non_existing_template
+ l3out: L3out1
+ vrf:
+ name: VRF1
+ template: Template1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_template
+
+ - name: Verify cm_non_existing_template and nm_non_existing_template
+ ansible.builtin.assert:
+ that:
+ - cm_non_existing_template is not changed
+ - nm_non_existing_template is not changed
+ - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non_existing_template' does not exist. Existing templates{{':'}} Template1, Template2"
+
+ # USE A NON_EXISTING_SCHEMA
+ - name: non_existing_schema (check_mode)
+ cisco.mso.mso_schema_site_l3out:
+ <<: *mso_info
+ schema: non_existing_schema
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ l3out: L3out1
+ vrf:
+ name: VRF1
+ template: Template1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_schema
+
+ - name: non_existing_schema (normal_mode)
+ cisco.mso.mso_schema_site_l3out:
+ <<: *mso_info
+ schema: non_existing_schema
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ l3out: L3out1
+ vrf:
+ name: VRF1
+ template: Template1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_schema
+
+ - name: Verify cm_non_existing_schema and nm_non_existing_schema
+ ansible.builtin.assert:
+ that:
+ - cm_non_existing_schema is not changed
+ - nm_non_existing_schema is not changed
+ - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non_existing_schema' does not exist."
+
+ # USE A NON_EXISTING_SITE
+ - name: non_existing_site (check_mode)
+ cisco.mso.mso_schema_site_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: non_existing_site
+ template: Template1
+ l3out: L3out1
+ vrf:
+ name: VRF1
+ template: Template1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_site
+
+ - name: non_existing_site (normal_mode)
+ cisco.mso.mso_schema_site_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: non_existing_site
+ template: Template1
+ l3out: L3out1
+ vrf:
+ name: VRF1
+ template: Template1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_site
+
+ - name: Verify cm_non_existing_site and nm_non_existing_site
+ ansible.builtin.assert:
+ that:
+ - cm_non_existing_site is not changed
+ - nm_non_existing_site is not changed
+ - cm_non_existing_site.msg == nm_non_existing_site.msg == "Site 'non_existing_site' is not a valid site name."
+
+ # USE A NON_EXISTING_SITE_TEMPLATE
+ - name: non_existing_site_template (check_mode)
+ cisco.mso.mso_schema_site_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template5
+ l3out: L3out1
+ vrf:
+ name: VRF1
+ template: Template1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_site_template
+
+ - name: non_existing_site_template (normal_mode)
+ cisco.mso.mso_schema_site_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template5
+ l3out: L3out1
+ vrf:
+ name: VRF1
+ template: Template1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_site_template
+
+ - name: Verify cm_non_existing_site_template and nm_non_existing_site_template
+ ansible.builtin.assert:
+ that:
+ - cm_non_existing_site_template is not changed
+ - nm_non_existing_site_template is not changed
+ - cm_non_existing_site_template.msg == nm_non_existing_site_template.msg == "Provided template 'Template5' does not exist. Existing templates{{':'}} Template1, Template2"
+
+ - name: nm_non_existing_template_site (normal_mode)
+ cisco.mso.mso_schema_site_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template2
+ l3out: L3out1
+ vrf:
+ name: VRF1
+ template: Template1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_template_site
+
+ - name: Verify nm_non_existing_template_site
+ ansible.builtin.assert:
+ that:
+ - nm_non_existing_template_site is not changed
+ - nm_non_existing_template_site.msg == "Provided template 'Template2' is not associated to site"
+
+ # USE A TEMPLATE WITHOUT ANY SITE
+ - name: Add site L3Out to Schema Template3 without any site associated (check mode)
+ cisco.mso.mso_schema_site_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template3
+ l3out: L3out1
+ vrf:
+ name: VRF1
+ template: Template1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ state: present
+ check_mode: true
+ ignore_errors: true
+ register: cm_no_site_associated
+
+ - name: Add site L3Out to Template3 without any site associated (normal mode)
+ cisco.mso.mso_schema_site_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template3
+ l3out: L3out1
+ vrf:
+ name: VRF1
+ template: Template1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ state: present
+ ignore_errors: true
+ register: nm_no_site_associated
+
+ - name: Verify cm_no_site_associated and nm_no_site_associated
+ ansible.builtin.assert:
+ that:
+ - cm_no_site_associated is not changed
+ - nm_no_site_associated is not changed
+ - cm_no_site_associated.msg == nm_no_site_associated.msg == "No site associated with template 'Template3'. Associate the site with the template using mso_schema_site." \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_service_graph/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_service_graph/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_service_graph/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_service_graph/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_service_graph/tasks/main.yml
new file mode 100644
index 000000000..a9b60bab6
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_service_graph/tasks/main.yml
@@ -0,0 +1,795 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(false) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+# Service Graph at Site Level is blocked by validations in MSO/NDO before v3.3.
+# It is supported after v3.3 by using validate=false.
+- name: Execute tasks only for MSO version >= 3.3
+ when: version.current.version is version('3.3', '>=')
+ block:
+ - name: Ensure site exists
+ cisco.mso.mso_site:
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ apic_username }}'
+ apic_password: '{{ apic_password }}'
+ apic_site_id: '{{ apic_site_id | default(101) }}'
+ urls:
+ - https://{{ apic_hostname }}
+ state: present
+ ignore_errors: true
+
+ - name: Remove schemas
+ cisco.mso.mso_schema:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ state: absent
+
+ - name: Ensure sites removed from tenant ansible_test
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_site | default("ansible_test") }}'
+ - 'aws_{{ mso_site | default("ansible_test") }}'
+ - 'azure_{{ mso_site | default("ansible_test") }}'
+
+ - name: Ensure tenant ansible_test exist
+ cisco.mso.mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ state: present
+
+ - name: Associate site with ansible_test
+ cisco.mso.mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: present
+
+ - name: Add a tenant on APIC
+ cisco.aci.aci_tenant:
+ host: '{{ apic_hostname }}'
+ username: '{{ apic_username }}'
+ password: '{{ apic_password }}'
+ validate_certs: no
+ name: "ansible_test"
+
+ - name: Add devices to APIC
+ cisco.aci.aci_rest:
+ host: '{{ apic_hostname }}'
+ username: '{{ apic_username }}'
+ password: '{{ apic_password }}'
+ validate_certs: no
+ path: /api/node/mo/uni/tn-ansible_test.json
+ method: post
+ content:
+ vnsLDevVip:
+ attributes:
+ svcType: '{{ item.type }}'
+ managed: 'false'
+ name: '{{ item.name }}'
+ children:
+ - vnsCDev:
+ attributes:
+ name: '{{ item.name }}'
+ loop:
+ - { type: FW, name: ansible_test_firewall1 }
+ - { type: FW, name: ansible_test_firewall2 }
+ - { type: ADC, name: ansible_test_adc }
+ - { type: OTHERS, name: ansible_test_other }
+
+ - name: Ensure schema 1 with Template1 and 2 exist
+ cisco.mso.mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: '{{ item }}'
+ state: present
+ loop:
+ - 'Template1'
+ - 'Template2'
+
+ - name: Ensure schema 2 with Template1 exists
+ cisco.mso.mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template1
+ state: present
+
+ - name: Add Node1
+ cisco.mso.mso_service_node_type:
+ <<: *mso_info
+ name: ansible_test_other1
+ display_name: ansible_test_other1
+ state: present
+
+ - name: Add Node2
+ cisco.mso.mso_service_node_type:
+ <<: *mso_info
+ name: ansible_test_other2
+ display_name: ansible_test_other2
+ state: present
+
+ - name: Create a service graph 1 at Template level
+ cisco.mso.mso_schema_template_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG1
+ display_name: sg1
+ service_nodes:
+ - type: firewall
+ filter_after_first_node: allow_all
+ state: present
+
+ - name: Create service graph 2 at Template level
+ cisco.mso.mso_schema_template_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG2
+ display_name: sg2
+ service_nodes:
+ - type: firewall
+ - type: load-balancer
+ filter_after_first_node: allow_all
+ state: present
+ register: sg1_again
+
+ - name: Create service graph 3 at Template level
+ cisco.mso.mso_schema_template_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG3
+ display_name: sg3
+ service_nodes:
+ - type: firewall
+ - type: load-balancer
+ - type: other
+ filter_after_first_node: allow_all
+ state: present
+ register: sg1_again
+
+ - name: Create a service graph 4 at Template level
+ cisco.mso.mso_schema_template_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG4
+ display_name: sg4
+ service_nodes:
+ - type: other
+ - type: load-balancer
+ - type: firewall
+ filter_after_first_node: filters_from_contract
+ state: present
+
+ - name: Create a service graph 5 at Template level
+ cisco.mso.mso_schema_template_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG5
+ display_name: sg5
+ service_nodes:
+ - type: other
+ - type: firewall
+ - type: firewall
+ filter_after_first_node: filters_from_contract
+ state: present
+
+ - name: Create a service graph 6 at Template level
+ cisco.mso.mso_schema_template_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG6
+ display_name: sg6
+ service_nodes:
+ - type: other
+ - type: other
+ - type: other
+ filter_after_first_node: filters_from_contract
+ state: present
+
+ - name: Create a service graph 7 at Template level
+ cisco.mso.mso_schema_template_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG7
+ display_name: sg7
+ service_nodes:
+ - type: load-balancer
+ - type: load-balancer
+ filter_after_first_node: filters_from_contract
+ state: present
+
+ - name: Add physical site to a schema
+ cisco.mso.mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ state: present
+
+ - name: Add a new Graph at site level (check mode)
+ cisco.mso.mso_schema_site_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG1
+ site: '{{ mso_site | default("ansible_test") }}'
+ tenant: ansible_test
+ devices:
+ - name: ansible_test_firewall1
+ state: present
+ register: cm_sg1
+ check_mode: true
+
+ - name: Verify cm_sg1
+ assert:
+ that:
+ - cm_sg1 is changed
+ - cm_sg1.current.serviceGraphRef.serviceGraphName == "SG1"
+ - cm_sg1.current.serviceGraphRef.templateName == "Template1"
+ - cm_sg1.current.serviceNodes | length == 1
+ - cm_sg1.current.serviceNodes.0.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_firewall1"
+
+ - name: Add a new Graph SG1 at site level (normal mode)
+ cisco.mso.mso_schema_site_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG1
+ site: '{{ mso_site | default("ansible_test") }}'
+ tenant: ansible_test
+ devices:
+ - name: ansible_test_firewall1
+ state: present
+ register: nm_sg1
+
+ - name: Verify change_sg1
+ assert:
+ that:
+ - nm_sg1 is changed
+ - nm_sg1.current.serviceGraphRef.serviceGraphName == "SG1"
+ - nm_sg1.current.serviceGraphRef.templateName == "Template1"
+ - nm_sg1.current.serviceNodes | length == 1
+ - nm_sg1.current.serviceNodes.0.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_firewall1"
+
+ - name: Add Graph SG1 at site level again
+ cisco.mso.mso_schema_site_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG1
+ site: '{{ mso_site | default("ansible_test") }}'
+ tenant: ansible_test
+ devices:
+ - name: ansible_test_firewall1
+ state: present
+ register: nm_sg1_again
+
+ - name: Verify change_sg1
+ assert:
+ that:
+ - nm_sg1_again is not changed
+ - nm_sg1_again.current.serviceGraphRef.serviceGraphName == "SG1"
+ - nm_sg1_again.current.serviceGraphRef.templateName == "Template1"
+ - nm_sg1_again.current.serviceNodes | length == 1
+ - nm_sg1_again.current.serviceNodes.0.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_firewall1"
+
+ - name: Change service graph SG1 at site level
+ cisco.mso.mso_schema_site_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG1
+ site: '{{ mso_site | default("ansible_test") }}'
+ tenant: ansible_test
+ devices:
+ - name: ansible_test_firewall2
+ state: present
+ register: change_sg1
+
+ - name: Verify change_sg1
+ assert:
+ that:
+ - change_sg1 is changed
+ - change_sg1.current.serviceGraphRef.serviceGraphName == "SG1"
+ - change_sg1.current.serviceGraphRef.templateName == "Template1"
+ - change_sg1.current.serviceNodes | length == 1
+ - change_sg1.current.serviceNodes.0.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_firewall2"
+
+ - name: Add a new Graph SG2 at site level
+ cisco.mso.mso_schema_site_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG2
+ site: '{{ mso_site | default("ansible_test") }}'
+ tenant: ansible_test
+ devices:
+ - name: ansible_test_firewall1
+ - name: ansible_test_adc
+ state: present
+ register: sg2
+
+ - name: Verify sg2
+ assert:
+ that:
+ - sg2 is changed
+ - sg2.current.serviceGraphRef.serviceGraphName == "SG2"
+ - sg2.current.serviceGraphRef.templateName == "Template1"
+ - sg2.current.serviceNodes | length == 2
+ - sg2.current.serviceNodes.0.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_firewall1"
+ - sg2.current.serviceNodes.1.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_adc"
+
+ - name: Add a new Graph SG3 at site level
+ cisco.mso.mso_schema_site_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG3
+ site: '{{ mso_site | default("ansible_test") }}'
+ tenant: ansible_test
+ devices:
+ - name: ansible_test_firewall1
+ - name: ansible_test_adc
+ - name: ansible_test_other
+ state: present
+ register: sg3
+
+ - name: Verify sg3
+ assert:
+ that:
+ - sg3 is changed
+ - sg3.current.serviceGraphRef.serviceGraphName == "SG3"
+ - sg3.current.serviceGraphRef.templateName == "Template1"
+ - sg3.current.serviceNodes | length == 3
+ - sg3.current.serviceNodes.0.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_firewall1"
+ - sg3.current.serviceNodes.1.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_adc"
+ - sg3.current.serviceNodes.2.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_other"
+
+ - name: Add a new Graph SG4 at site level
+ cisco.mso.mso_schema_site_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG4
+ site: '{{ mso_site | default("ansible_test") }}'
+ tenant: ansible_test
+ devices:
+ - name: ansible_test_other
+ - name: ansible_test_adc
+ - name: ansible_test_firewall1
+ state: present
+ register: sg4
+
+ - name: Verify sg4
+ assert:
+ that:
+ - sg4 is changed
+ - sg4.current.serviceGraphRef.serviceGraphName == "SG4"
+ - sg4.current.serviceGraphRef.templateName == "Template1"
+ - sg4.current.serviceNodes | length == 3
+ - sg4.current.serviceNodes.0.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_other"
+ - sg4.current.serviceNodes.1.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_adc"
+ - sg4.current.serviceNodes.2.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_firewall1"
+
+ - name: Change Graph SG4 at site level
+ cisco.mso.mso_schema_site_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG4
+ site: '{{ mso_site | default("ansible_test") }}'
+ tenant: ansible_test
+ devices:
+ - name: ansible_test_other
+ - name: ansible_test_adc
+ - name: ansible_test_firewall2
+ state: present
+ register: change1_sg4
+
+ - name: Verify change1_sg4
+ assert:
+ that:
+ - change1_sg4 is changed
+ - change1_sg4.current.serviceGraphRef.serviceGraphName == "SG4"
+ - change1_sg4.current.serviceGraphRef.templateName == "Template1"
+ - change1_sg4.current.serviceNodes | length == 3
+ - change1_sg4.current.serviceNodes.0.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_other"
+ - change1_sg4.current.serviceNodes.1.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_adc"
+ - change1_sg4.current.serviceNodes.2.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_firewall2"
+
+ - name: Change Graph SG4 at site level
+ cisco.mso.mso_schema_site_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG4
+ site: '{{ mso_site | default("ansible_test") }}'
+ tenant: ansible_test
+ devices:
+ - name: ansible_test_other
+ state: present
+ ignore_errors: true
+ register: change2_sg4
+
+ - name: Verify change2_sg4
+ assert:
+ that:
+ - change2_sg4 is not changed
+ - change2_sg4.msg == "Service Graph 'SG4' has '3' service node type(s) but '1' service node(s) were given for the service graph"
+
+ - name: Change Graph SG4 at site level
+ cisco.mso.mso_schema_site_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG4
+ site: '{{ mso_site | default("ansible_test") }}'
+ tenant: ansible_test
+ devices:
+ - name: ansible_test_adc
+ state: present
+ ignore_errors: true
+ register: change3_sg4
+
+ - name: Verify change3_sg4
+ assert:
+ that:
+ - change3_sg4 is not changed
+ - change3_sg4.msg == "Service Graph 'SG4' has '3' service node type(s) but '1' service node(s) were given for the service graph"
+
+ - name: Change Graph SG4 at site level
+ cisco.mso.mso_schema_site_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG4
+ site: '{{ mso_site | default("ansible_test") }}'
+ tenant: ansible_test
+ devices:
+ - name: ansible_test_firewall1
+ state: present
+ ignore_errors: true
+ register: change4_sg4
+
+ - name: Verify change4_sg4
+ assert:
+ that:
+ - change4_sg4 is not changed
+ - change4_sg4.msg == "Service Graph 'SG4' has '3' service node type(s) but '1' service node(s) were given for the service graph"
+
+ - name: Change Graph SG4 at site level
+ cisco.mso.mso_schema_site_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG4
+ site: '{{ mso_site | default("ansible_test") }}'
+ tenant: ansible_test
+ devices:
+ - name: ansible_test_other
+ - name: ansible_test_adc
+ state: present
+ ignore_errors: true
+ register: change5_sg4
+
+ - name: Verify change5_sg4
+ assert:
+ that:
+ - change5_sg4 is not changed
+ - change5_sg4.msg == "Service Graph 'SG4' has '3' service node type(s) but '2' service node(s) were given for the service graph"
+
+ - name: Change Graph SG4 at site level
+ cisco.mso.mso_schema_site_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG4
+ site: '{{ mso_site | default("ansible_test") }}'
+ tenant: ansible_test
+ devices:
+ - name: ansible_test_other
+ - name: ansible_test_other
+ - name: ansible_test_adc
+ state: present
+ ignore_errors: true
+ register: change6_sg4
+
+ - name: Verify change6_sg4
+ assert:
+ that:
+ - change6_sg4 is not changed
+ - change6_sg4.msg == "Provided device 'ansible_test_other' of type 'ADC' does not exist."
+
+ - name: Add Graph SG5 at site level
+ cisco.mso.mso_schema_site_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG5
+ site: '{{ mso_site | default("ansible_test") }}'
+ tenant: ansible_test
+ devices:
+ - name: ansible_test_other
+ - name: ansible_test_firewall1
+ - name: ansible_test_firewall1
+ state: present
+ register: sg5
+
+ - name: Verify sg5
+ assert:
+ that:
+ - sg5 is changed
+ - sg5.current.serviceGraphRef.serviceGraphName == "SG5"
+ - sg5.current.serviceGraphRef.templateName == "Template1"
+ - sg5.current.serviceNodes | length == 3
+ - sg5.current.serviceNodes.0.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_other"
+ - sg5.current.serviceNodes.1.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_firewall1"
+ - sg5.current.serviceNodes.2.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_firewall1"
+
+ - name: Add Graph SG6 at site level
+ cisco.mso.mso_schema_site_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG6
+ site: '{{ mso_site | default("ansible_test") }}'
+ tenant: ansible_test
+ devices:
+ - name: ansible_test_other
+ - name: ansible_test_other
+ - name: ansible_test_other
+ state: present
+ register: sg6
+
+ - name: Verify sg6
+ assert:
+ that:
+ - sg6 is changed
+ - sg6.current.serviceGraphRef.serviceGraphName == "SG6"
+ - sg6.current.serviceGraphRef.templateName == "Template1"
+ - sg6.current.serviceNodes | length == 3
+ - sg6.current.serviceNodes.0.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_other"
+ - sg6.current.serviceNodes.1.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_other"
+ - sg6.current.serviceNodes.2.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_other"
+
+ - name: Add Graph SG7 at site level
+ cisco.mso.mso_schema_site_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG7
+ site: '{{ mso_site | default("ansible_test") }}'
+ tenant: ansible_test
+ devices:
+ - name: ansible_test_adc
+ - name: ansible_test_adc
+ state: present
+ register: sg7
+
+ - name: Verify sg7
+ assert:
+ that:
+ - sg7 is changed
+ - sg7.current.serviceGraphRef.serviceGraphName == "SG7"
+ - sg7.current.serviceGraphRef.templateName == "Template1"
+ - sg7.current.serviceNodes | length == 2
+ - sg7.current.serviceNodes.0.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_adc"
+ - sg7.current.serviceNodes.1.device.dn == "uni/tn-ansible_test/lDevVip-ansible_test_adc"
+
+ - name: Query service graph SG at site level
+ cisco.mso.mso_schema_site_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG1
+ site: '{{ mso_site | default("ansible_test") }}'
+ tenant: ansible_test
+ state: query
+ register: query_sg
+
+ - name: Verify query_sg
+ assert:
+ that:
+ - query_sg is not changed
+ - query_sg.current.serviceGraphRef is match("/schemas/[0-9a-zA-Z]*/templates/Template1/serviceGraphs/SG1")
+
+ - name: Query all service graphs at site level
+ cisco.mso.mso_schema_site_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ site: '{{ mso_site | default("ansible_test") }}'
+ tenant: ansible_test
+ state: query
+ register: query_all
+
+ - name: Verify query_all
+ assert:
+ that:
+ - query_all is not changed
+ - query_all.current | length == 7
+
+ - name: Query non_existing service graph at site level
+ cisco.mso.mso_schema_site_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ site: '{{ mso_site | default("ansible_test") }}'
+ service_graph: non_existent
+ tenant: ansible_test
+ state: query
+ ignore_errors: true
+ register: query_non_existing_sg
+
+ - name: Verify query_non_existing_sg
+ assert:
+ that:
+ - query_non_existing_sg.msg == "Service Graph 'non_existent' not found"
+
+ - name: Use non_existing schema
+ cisco.mso.mso_schema_site_service_graph:
+ <<: *mso_info
+ schema: non_existing_schema
+ template: Template1
+ service_graph: SG
+ tenant: ansible_test
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: query
+ ignore_errors: true
+ register: query_non_existing_schema
+
+ - name: Verify non_existing_schema
+ assert:
+ that:
+ - query_non_existing_schema is not changed
+ - query_non_existing_schema.msg == "Provided schema 'non_existing_schema' does not exist."
+
+ - name: Use non_existing template
+ cisco.mso.mso_schema_site_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: non_existing_template
+ service_graph: SG
+ tenant: ansible_test
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: query
+ ignore_errors: true
+ register: query_non_existing_template
+
+ - name: Verify query_non_existing_template
+ assert:
+ that:
+ - query_non_existing_template.msg == "Provided template 'non_existing_template' does not exist. Existing templates{{':'}} Template1, Template2"
+
+ - name: Use non_existing_site_template
+ cisco.mso.mso_schema_site_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ service_graph: SG
+ template: Template2
+ tenant: ansible_test
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_site_template
+
+ - name: Verify cm_non_existing_site_template and nm_non_existing_site_template
+ assert:
+ that:
+ - nm_non_existing_site_template is not changed
+ - nm_non_existing_site_template.msg == "Provided site-template association 'ansible_test-Template2' does not exist."
+
+ - name: Add site Service Graph to Template2 without any site association
+ cisco.mso.mso_schema_site_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG1
+ tenant: ansible_test
+ devices:
+ - name: ansible_test_firewall2
+ state: present
+ ignore_errors: true
+ register: nm_no_site_associated
+
+ - name: Verify nm_no_site_associated
+ assert:
+ that:
+ - nm_no_site_associated is not changed
+ - nm_no_site_associated.msg == "No site associated with template 'Template1'. Associate the site with the template using mso_schema_site."
+
+ - name: Remove service graph from site level(check mode)
+ cisco.mso.mso_schema_site_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG1
+ site: '{{ mso_site | default("ansible_test") }}'
+ tenant: ansible_test
+ state: absent
+ register: rm_sg_cm
+ check_mode: true
+
+ - name: Verify rm_sg_cm
+ assert:
+ that:
+ - rm_sg_cm is changed
+ - rm_sg_cm.current == {}
+ - rm_sg_cm.previous.serviceGraphRef is match("/schemas/[0-9a-zA-Z]*/templates/Template1/serviceGraphs/SG1")
+
+ - name: Remove service graph from site level (normal mode)
+ cisco.mso.mso_schema_site_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG1
+ tenant: ansible_test
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: absent
+ register: rm_sg
+
+ - name: Verify rm_sg
+ assert:
+ that:
+ - rm_sg is changed
+ - rm_sg.current == {}
+ - rm_sg.previous.serviceGraphRef is match("/schemas/[0-9a-zA-Z]*/templates/Template1/serviceGraphs/SG1")
+
+ - name: Remove service graph again
+ cisco.mso.mso_schema_site_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG1
+ tenant: ansible_test
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: absent
+ register: rm_sg_again
+
+ - name: Verify rm_sg_again
+ assert:
+ that:
+ - rm_sg_again is not changed
+ - rm_sg_again.current == {}
+ - rm_sg_again.previous == {}
+ when: version.current.version is version('4.0', '<') # no change in NDO4.0 because site will already be present when template is defined
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region/tasks/main.yml
new file mode 100644
index 000000000..c31a31eeb
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region/tasks/main.yml
@@ -0,0 +1,588 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com>
+# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+ sites: "['aws_{{ mso_site | default(\"ansible_test\") }}',
+ 'azure_{{ mso_site | default(\"ansible_test\") }}',
+ '{{ mso_site | default(\"ansible_test\") }}']"
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Ensure aws site exists
+ mso_site:
+ <<: *mso_info
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ aws_apic_username }}'
+ apic_password: '{{ aws_apic_password }}'
+ apic_site_id: '{{ aws_site_id | default(102) }}'
+ urls:
+ - https://{{ aws_apic_hostname }}
+ state: present
+
+- name: Ensure azure site exists
+ mso_site:
+ <<: *mso_info
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ azure_apic_username }}'
+ apic_password: '{{ azure_apic_password }}'
+ apic_site_id: '{{ azure_site_id | default(103) }}'
+ urls:
+ - https://{{ azure_apic_hostname }}
+ state: present
+
+- name: Remove Schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Ensure sites removed from tenant ansible_test
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_site | default("ansible_test") }}'
+ - 'aws_{{ mso_site | default("ansible_test") }}'
+ - 'azure_{{ mso_site | default("ansible_test") }}'
+
+- name: Ensure tenant ansible_test exist
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ state: present
+
+- name: Ensure AWS site is present under tenant ansible_test
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ cloud_account: '000000000000'
+ aws_access_key: 1
+ secret_key: 0
+ state: present
+
+- name: Ensure Azure site is present under tenant ansible_test
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ cloud_account: uni/tn-ansible_test/act-[9]-vendor-azure
+ state: present
+
+- name: Ensure schema 1 with Template 1 and 2 exists
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: '{{ item }}'
+ state: present
+ loop:
+ - Template 1
+ - Template 2
+
+- name: Ensure schema 2 with Template 3 exists
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template 3
+ state: present
+
+- name: Ensure VRF1 exists
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF1
+ state: present
+
+- name: Add region and cidr in VRF1 at AWS site level
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ cidr: 10.0.0.0/16
+ primary: true
+ state: present
+ register: aws_add_region_cidr
+
+- name: Add region and cidr in VRF1 at Azure site level
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ cidr: 10.0.0.0/16
+ primary: true
+ state: present
+ register: azure_add_region_cidr
+
+- name: Add another region and cidr in VRF1 at AWS site level
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-east-1
+ cidr: 10.10.0.0/16
+ primary: true
+ state: present
+ register: aws_add_another_region_cidr
+
+- name: Add another region and cidr in VRF1 at Azure site level
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-east-1
+ cidr: 10.10.0.0/16
+ primary: true
+ state: present
+ register: azure_add_another_region_cidr
+
+# Execute context underlay parameters only when when MSO version >= 3.3
+- name: Execute tasks only for MSO version >= 3.3
+ when: version.current.version is version('3.3', '>=')
+ block:
+ - name: Ensure VRF2 exists
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF2
+ state: present
+
+ - name: Add region and cidr in VRF2 at Azure site level
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF2
+ region: westus
+ cidr: 10.10.0.0/16
+ primary: true
+ state: present
+ register: azure_add_another_region_cidr
+
+ - name: Add region and cidr in VRF2 at AWS site level
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF2
+ region: westus
+ cidr: 10.10.0.0/16
+ primary: true
+ state: present
+ register: azure_add_another_region_cidr
+
+ - name: Add context underlay parameters in VRF1 at Azure site level
+ mso_schema_site_vrf_region:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ container_overlay: true
+ underlay_context_profile:
+ vrf: VRF2
+ region: westus
+ state: present
+ register: azure_add_context_underlay
+
+ - name: Add context underlay parameters in VRF1 at AWS site level
+ mso_schema_site_vrf_region:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-east-1
+ container_overlay: true
+ underlay_context_profile:
+ vrf: VRF2
+ region: westus
+ state: present
+ register: aws_add_context_underlay
+
+ - name: Verify add_context_underlay
+ assert:
+ that:
+ - azure_add_context_underlay is changed
+ - azure_add_context_underlay.current.contextProfileType == "container-overlay"
+ - aws_add_context_underlay is changed
+ - aws_add_context_underlay.current.contextProfileType == "container-overlay"
+
+ - name: Get Validation status
+ mso_schema_validate:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ state: query
+ register: query_validate
+
+ - name: Verify query_validate
+ assert:
+ that:
+ - query_validate is not changed
+
+ - name: Verify query_validate result < 4.0
+ assert:
+ that:
+ - query_validate.current.result == "true"
+ when: version.current.version is version('4.0', '<')
+
+ - name: Verify query_validate result => 4.0
+ assert:
+ that:
+ - query_validate.current.result == true
+ when: version.current.version is version('4.0', '>=')
+
+- name: Query all aws regions
+ mso_schema_site_vrf_region:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ state: query
+ register: aws_query_all
+
+- name: Query all azure regions
+ mso_schema_site_vrf_region:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ state: query
+ register: azure_query_all
+
+- name: Query specific aws region
+ mso_schema_site_vrf_region:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ state: query
+ register: aws_query_region
+
+- name: Query specific azure region
+ mso_schema_site_vrf_region:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ state: query
+ register: azure_query_region
+
+- name: Verify query
+ assert:
+ that:
+ - aws_query_all is not changed
+ - azure_query_all is not changed
+ - aws_query_region is not changed
+ - azure_query_region is not changed
+ - aws_query_all.current | length == 2
+ - azure_query_all.current | length == 2
+ - aws_query_region.current.name == "us-west-1"
+ - aws_query_region.current.cidrs.0.ip == "10.0.0.0/16"
+ - aws_query_region.current.cidrs.0.primary == true
+ - aws_query_region.current.cidrs.0.subnets == []
+ - azure_query_region.current.name == "us-west-1"
+ - azure_query_region.current.cidrs.0.ip == "10.0.0.0/16"
+ - azure_query_region.current.cidrs.0.primary == true
+ - azure_query_region.current.cidrs.0.subnets == []
+
+- name: Remove aws VRF region (check mode)
+ mso_schema_site_vrf_region:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-east-1
+ state: absent
+ check_mode: true
+ register: cm_rm_aws_region
+
+- name: Remove aws VRF region (normal mode)
+ mso_schema_site_vrf_region:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-east-1
+ state: absent
+ register: nm_rm_aws_region
+
+- name: Remove azure VRF region (check mode)
+ mso_schema_site_vrf_region:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ state: absent
+ check_mode: true
+ register: cm_rm_azure_region
+
+- name: Remove azure VRF region (normal mode)
+ mso_schema_site_vrf_region:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ state: absent
+ register: nm_rm_azure_region
+
+- name: Verify deletion
+ assert:
+ that:
+ - cm_rm_aws_region is changed
+ - nm_rm_aws_region is changed
+ - cm_rm_azure_region is changed
+ - nm_rm_azure_region is changed
+ - cm_rm_aws_region.previous == nm_rm_aws_region.previous
+ - cm_rm_azure_region.previous == nm_rm_azure_region.previous
+ - cm_rm_aws_region.current == nm_rm_aws_region.current == {}
+ - cm_rm_azure_region.current == nm_rm_azure_region.current == {}
+ - cm_rm_aws_region.previous.name == nm_rm_aws_region.previous.name == "us-east-1"
+ - cm_rm_azure_region.previous.name == nm_rm_azure_region.previous.name == "us-west-1"
+
+- name: Add VPN Gateway Router to Region for AWS
+ cisco.mso.mso_schema_site_vrf_region:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ vpn_gateway_router: true
+ state: present
+ when: version.current.version is version('3.0.0a', '>=')
+ register: add_vpn_gateway_router_aws
+
+- name: Add VPN Gateway Router to Region for Azure
+ cisco.mso.mso_schema_site_vrf_region:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-east-1
+ vpn_gateway_router: true
+ state: present
+ when: version.current.version is version('3.0.0a', '>=')
+ register: add_vpn_gateway_router_azure
+
+- name: Verify adding VPN Gateway Router
+ assert:
+ that:
+ - add_vpn_gateway_router_aws.current.isVpnGatewayRouter == add_vpn_gateway_router_azure.current.isVpnGatewayRouter == true
+ when: version.current.version is version('3.0.0a', '>=')
+
+- name: Remove VPN Gateway Router at Region for AWS (check mode)
+ cisco.mso.mso_schema_site_vrf_region:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ vpn_gateway_router: false
+ state: present
+ check_mode: true
+ when: version.current.version is version('3.0.0a', '>=')
+ register: cm_rm_vpn_gateway_router_aws
+
+- name: Remove VPN Gateway Router at Region for AWS (normal mode)
+ cisco.mso.mso_schema_site_vrf_region:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ vpn_gateway_router: false
+ state: present
+ when: version.current.version is version('3.0.0a', '>=')
+ register: nm_rm_vpn_gateway_router_aws
+
+- name: Remove VPN Gateway Router at Region for Azure (check mode)
+ cisco.mso.mso_schema_site_vrf_region:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-east-1
+ vpn_gateway_router: false
+ state: present
+ check_mode: true
+ when: version.current.version is version('3.0.0a', '>=')
+ register: cm_rm_vpn_gateway_router_azure
+
+- name: Remove VPN Gateway Router at Region for Azure (normal mode)
+ cisco.mso.mso_schema_site_vrf_region:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-east-1
+ vpn_gateway_router: false
+ state: present
+ when: version.current.version is version('3.0.0a', '>=')
+ register: nm_rm_vpn_gateway_router_azure
+
+- name: Verify removing VPN Gateway Router
+ assert:
+ that:
+ - cm_rm_vpn_gateway_router_aws is changed
+ - nm_rm_vpn_gateway_router_aws is changed
+ - cm_rm_vpn_gateway_router_azure is changed
+ - nm_rm_vpn_gateway_router_azure is changed
+ - cm_rm_vpn_gateway_router_aws.previous.isVpnGatewayRouter == nm_rm_vpn_gateway_router_aws.previous.isVpnGatewayRouter == true
+ - cm_rm_vpn_gateway_router_azure.previous.isVpnGatewayRouter == nm_rm_vpn_gateway_router_azure.previous.isVpnGatewayRouter == true
+ - cm_rm_vpn_gateway_router_aws.current.isVpnGatewayRouter == nm_rm_vpn_gateway_router_aws.current.isVpnGatewayRouter == false
+ - cm_rm_vpn_gateway_router_azure.current.isVpnGatewayRouter == nm_rm_vpn_gateway_router_azure.current.isVpnGatewayRouter == false
+ when: version.current.version is version('3.0.0a', '>=')
+
+- name: Use non_existing schema
+ mso_schema_site_vrf_region:
+ <<: *mso_info
+ schema: non_existing
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ state: query
+ register: non_existing_schema
+ ignore_errors: true
+
+- name: Use non_existing site
+ mso_schema_site_vrf_region:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: non_existing
+ vrf: VRF1
+ region: us-west-1
+ state: query
+ register: non_existing_site
+ ignore_errors: true
+
+- name: Use non_existing site/template association
+ mso_schema_site_vrf_region:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ state: query
+ register: non_existing_site_template
+ ignore_errors: true
+
+- name: Use non_existing VRF
+ mso_schema_site_vrf_region:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: non_existing
+ region: us-west-1
+ state: query
+ register: non_existing_vrf
+ ignore_errors: true
+
+- name: Use non_existing region
+ mso_schema_site_vrf_region:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: non_existing
+ state: query
+ register: non_existing_region
+ ignore_errors: true
+
+- name: Verify non_existing
+ assert:
+ that:
+ - non_existing_schema.msg == "Provided schema 'non_existing' does not exist."
+ - non_existing_site.msg == "Site 'non_existing' is not a valid site name."
+ - non_existing_site_template.msg == "Provided site-template association 'aws_ansible_test-Template2' does not exist."
+ - non_existing_region.msg == "Region 'non_existing' not found"
+
+- name: Verify non_existing (version < 3.3)
+ assert:
+ that:
+ - non_existing_vrf.msg == "Provided vrf 'non_existing' does not exist. Existing vrfs{{':'}} VRF1"
+ when: version.current.version is version('3.3', '<')
+
+- name: Verify non_existing (version >= 3.3)
+ assert:
+ that:
+ - non_existing_vrf.msg == "Provided vrf 'non_existing' does not exist. Existing vrfs{{':'}} VRF1, VRF2"
+ when: version.current.version is version('3.3', '>=')
+
+- name: Delete non_existing region
+ mso_schema_site_vrf_region:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: non_existing
+ state: absent
+ register: rm_non_existing_region
+
+- name: Verify rm_non_existing_region
+ assert:
+ that:
+ - rm_non_existing_region is not changed
+ - rm_non_existing_region.previous == rm_non_existing_region.current == {} \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_cidr/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_cidr/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_cidr/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_cidr/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_cidr/tasks/main.yml
new file mode 100644
index 000000000..64d08821c
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_cidr/tasks/main.yml
@@ -0,0 +1,721 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com> (based on mso_schema_anp_epg_domain)
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case)
+
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+ sites: "['aws_{{ mso_site | default(\"ansible_test\") }}',
+ 'azure_{{ mso_site | default(\"ansible_test\") }}',
+ '{{ mso_site | default(\"ansible_test\") }}']"
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Ensure site exists
+ mso_site:
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ apic_username }}'
+ apic_password: '{{ apic_password }}'
+ apic_site_id: '{{ apic_site_id | default(101) }}'
+ urls:
+ - https://{{ apic_hostname }}
+ state: present
+
+- name: Ensure site exists
+ mso_site:
+ <<: *mso_info
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ aws_apic_username }}'
+ apic_password: '{{ aws_apic_password }}'
+ apic_site_id: '{{ aws_site_id | default(102) }}'
+ urls:
+ - https://{{ aws_apic_hostname }}
+ state: present
+
+- name: Ensure site exists
+ mso_site:
+ <<: *mso_info
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ azure_apic_username }}'
+ apic_password: '{{ azure_apic_password }}'
+ apic_site_id: '{{ azure_site_id | default(103) }}'
+ urls:
+ - https://{{ azure_apic_hostname }}
+ state: present
+
+- name: Remove Schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Ensure sites removed from tenant ansible_test
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_site | default("ansible_test") }}'
+ - 'aws_{{ mso_site | default("ansible_test") }}'
+ - 'azure_{{ mso_site | default("ansible_test") }}'
+
+- name: Ensure tenant ansible_test exist
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ users:
+ - '{{ mso_username }}'
+ state: present
+
+- name: Ensure AWS site is present under tenant ansible_test
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ cloud_account: '000000000000'
+ aws_access_key: 1
+ secret_key: 0
+ state: present
+
+- name: Ensure Azure site is present under tenant ansible_test
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ cloud_account: uni/tn-ansible_test/act-[9]-vendor-azure
+ state: present
+
+- name: Ensure schema 1 with Template 1 and 2 exists
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: '{{ item }}'
+ state: present
+ loop:
+ - Template 1
+ - Template 2
+
+- name: Ensure schema 2 with Template 3 exists
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template 3
+ state: present
+
+- name: Add physical site to Template 1
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ state: present
+
+- name: Ensure VRF1 exists
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ item.schema }}'
+ template: '{{ item.template }}'
+ vrf: VRF1
+ state: present
+ loop:
+ - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 1' }
+ - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 2' }
+ - { schema: '{{ mso_schema | default("ansible_test") }}_2', template: 'Template 3' }
+
+- name: Ensure VRF2 exists
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF2
+ state: present
+ when: version.current.version is version('3', '<')
+
+- name: Ensure VRF1 exists at Site level for the physical site
+ mso_schema_site_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: '{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ state: present
+
+# ADD SUBNET
+- name: Add a new CIDR in VRF1 at AWS site level (check mode)
+ mso_schema_site_vrf_region_cidr: &mso_present
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ cidr: 10.0.0.0/16
+ primary: true
+ state: present
+ check_mode: true
+ register: cm_add_cidr
+
+- name: Verify cm_add_cidr
+ assert:
+ that:
+ - cm_add_cidr is changed
+ - cm_add_cidr.previous == {}
+ - cm_add_cidr.current.ip == '10.0.0.0/16'
+ - cm_add_cidr.current.primary == true
+
+- name: Add a new CIDR in VRF1 at AWS site level (normal mode)
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_present
+ register: nm_add_cidr
+
+- name: Verify nm_add_cidr
+ assert:
+ that:
+ - nm_add_cidr is changed
+ - nm_add_cidr.previous == {}
+ - nm_add_cidr.current.ip == '10.0.0.0/16'
+ - nm_add_cidr.current.primary == true
+
+- name: Add same CIDR in VRF1 at AWS site level (check mode)
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_present
+ register: cm_add_cidr_again
+
+- name: Verify cm_add_cidr_again
+ assert:
+ that:
+ - cm_add_cidr_again is not changed
+ - cm_add_cidr_again.current.ip == cm_add_cidr_again.previous.ip == '10.0.0.0/16'
+ - cm_add_cidr_again.current.primary == cm_add_cidr_again.previous.primary == true
+
+- name: Add same CIDR in VRF1 at AWS site level (normal mode)
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_present
+ register: nm_add_cidr_again
+
+- name: Verify nm_add_cidr_again
+ assert:
+ that:
+ - nm_add_cidr_again is not changed
+ - nm_add_cidr_again.current.ip == nm_add_cidr_again.previous.ip == '10.0.0.0/16'
+ - nm_add_cidr_again.current.primary == nm_add_cidr_again.previous.primary == true
+
+- name: Add a CIDR in VRF1 at Azure site level (check mode)
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_present
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ region: westus
+ cidr: 10.1.0.0/16
+ primary: true
+ check_mode: true
+ register: cm_add_cidr_2
+
+- name: Verify cm_add_cidr_2
+ assert:
+ that:
+ - cm_add_cidr_2 is changed
+ - cm_add_cidr_2.previous == {}
+ - cm_add_cidr_2.current.ip == '10.1.0.0/16'
+ - cm_add_cidr_2.current.primary == true
+
+- name: Add a CIDR in VRF1 at Azure site level (normal mode)
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_present
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ region: westus
+ cidr: 10.1.0.0/16
+ primary: true
+ register: nm_add_cidr_2
+
+- name: Verify nm_add_cidr_2
+ assert:
+ that:
+ - nm_add_cidr_2 is changed
+ - nm_add_cidr_2.previous == {}
+ - nm_add_cidr_2.current.ip == '10.1.0.0/16'
+ - nm_add_cidr_2.current.primary == true
+
+- name: Add a second CIDR in VRF1 at AWS site level (check mode)
+ mso_schema_site_vrf_region_cidr: &mso_present_2
+ <<: *mso_present
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ region: us-west-1
+ cidr: 10.2.0.0/16
+ primary: false
+ check_mode: true
+ register: cm_add_cidr_3
+
+- name: Verify cm_add_cidr_3
+ assert:
+ that:
+ - cm_add_cidr_3 is changed
+ - cm_add_cidr_3.previous == {}
+ - cm_add_cidr_3.current.ip == '10.2.0.0/16'
+ - cm_add_cidr_3.current.primary == false
+
+- name: Add a second CIDR in VRF1 at AWS site level (normal mode)
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_present_2
+ register: nm_add_cidr_3
+
+- name: Verify nm_add_cidr_3
+ assert:
+ that:
+ - nm_add_cidr_3 is changed
+ - nm_add_cidr_3.previous == {}
+ - nm_add_cidr_3.current.ip == '10.2.0.0/16'
+ - nm_add_cidr_3.current.primary == false
+
+- name: Add a second CIDR in VRF1 at Azure site level (check mode)
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_present
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ region: westus
+ cidr: 10.3.0.0/16
+ primary: false
+ check_mode: true
+ register: cm_add_cidr_4
+
+- name: Verify cm_add_cidr_4
+ assert:
+ that:
+ - cm_add_cidr_4 is changed
+ - cm_add_cidr_4.previous == {}
+ - cm_add_cidr_4.current.ip == '10.3.0.0/16'
+ - cm_add_cidr_4.current.primary == false
+
+- name: Add a second CIDR in VRF1 at Azure site level (normal mode)
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_present
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ region: westus
+ cidr: 10.3.0.0/16
+ primary: false
+ register: nm_add_cidr_4
+
+- name: Verify nm_add_cidr_4
+ assert:
+ that:
+ - nm_add_cidr_4 is changed
+ - nm_add_cidr_4.previous == {}
+ - nm_add_cidr_4.current.ip == '10.3.0.0/16'
+ - nm_add_cidr_4.current.primary == false
+
+# QUERY CIDR
+- name: Query CIDR in VRF1 at AWS site level (check mode)
+ mso_schema_site_vrf_region_cidr: &mso_query
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ cidr: 10.0.0.0/16
+ state: query
+ check_mode: true
+ register: cm_query_cidr
+
+- name: Verify cm_query_cidr
+ assert:
+ that:
+ - cm_query_cidr is not changed
+ - cm_query_cidr.current.ip == '10.0.0.0/16'
+ - cm_query_cidr.current.primary == true
+
+- name: Query CIDR in VRF1 at AWS site level (normal mode)
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_query
+ register: nm_query_cidr
+
+- name: Query CIDR in VRF1 at Azure site level (check mode)
+ mso_schema_site_vrf_region_cidr: &mso_query_2
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: westus
+ cidr: 10.1.0.0/16
+ state: query
+ check_mode: true
+ register: cm_query_cidr_2
+
+- name: Verify cm_query_cidr_2
+ assert:
+ that:
+ - cm_query_cidr_2 is not changed
+ - cm_query_cidr_2.current.ip == '10.1.0.0/16'
+ - cm_query_cidr_2.current.primary == true
+
+- name: Query CIDR in VRF1 at Azure site level (normal mode)
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_query_2
+ register: nm_query_cidr_2
+
+- name: Verify nm_query_cidr_2
+ assert:
+ that:
+ - nm_query_cidr_2 is not changed
+ - nm_query_cidr_2.current.ip == '10.1.0.0/16'
+ - nm_query_cidr_2.current.primary == true
+
+# QUERY ALL CIDR
+- name: Query all CIDR in VRF1 at AWS site level (check mode)
+ mso_schema_site_vrf_region_cidr: &mso_query_all
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ state: query
+ check_mode: true
+ register: cm_query_cidr_all_aws
+
+- name: Query CIDR in VRF1 at Azure site level (check mode)
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_query_all
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ region: westus
+ state: query
+ check_mode: true
+ register: cm_query_cidr_all_azure
+
+- name: Verify cm_query_cidr_all_aws and cm_query_cidr_all_azure
+ assert:
+ that:
+ - cm_query_cidr_all_aws is not changed
+ - cm_query_cidr_all_aws.current[0].ip == '10.0.0.0/16'
+ - cm_query_cidr_all_aws.current[0].primary == true
+ - cm_query_cidr_all_aws.current[1].ip == '10.2.0.0/16'
+ - cm_query_cidr_all_aws.current[1].primary == false
+ - cm_query_cidr_all_azure is not changed
+ - cm_query_cidr_all_azure.current[0].ip == '10.1.0.0/16'
+ - cm_query_cidr_all_azure.current[0].primary == true
+ - cm_query_cidr_all_azure.current[1].ip == '10.3.0.0/16'
+ - cm_query_cidr_all_azure.current[1].primary == false
+
+- name: Query CIDR in VRF1 at AWS site level (normal mode)
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_query_all
+ register: nm_query_cidr_all_aws
+
+- name: Query CIDR in VRF1 at Azure site level (normal mode)
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_query_all
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ region: westus
+ register: nm_query_cidr_all_azure
+
+- name: Verify nm_query_cidr_all_aws and nm_query_cidr_all_azure
+ assert:
+ that:
+ - nm_query_cidr_all_aws is not changed
+ - nm_query_cidr_all_aws.current[0].ip == '10.0.0.0/16'
+ - nm_query_cidr_all_aws.current[0].primary == true
+ - nm_query_cidr_all_aws.current[1].ip == '10.2.0.0/16'
+ - nm_query_cidr_all_aws.current[1].primary == false
+ - nm_query_cidr_all_azure is not changed
+ - nm_query_cidr_all_azure.current[0].ip == '10.1.0.0/16'
+ - nm_query_cidr_all_azure.current[0].primary == true
+ - nm_query_cidr_all_azure.current[1].ip == '10.3.0.0/16'
+ - nm_query_cidr_all_azure.current[1].primary == false
+
+- name: Query CIDR in VRF2 (not present a Site level) for AWS Site (normal mode)
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_query_all
+ vrf: VRF2
+ ignore_errors: true
+ register: nm_query_cidr_all_aws_2
+
+- name: Query CIDR in VRF1 (with VRF present a Site level) for Physical Site (normal mode)
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_query_all
+ site: '{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ ignore_errors: true
+ register: nm_query_cidr_all_aws_3
+
+- name: Verify nm_query_cidr_all_aws_2 and nm_query_cidr_all_aws_3
+ assert:
+ that:
+ - nm_query_cidr_all_aws_2.msg == "Provided vrf 'VRF2' does not exist at site level."
+ - nm_query_cidr_all_aws_3.msg == "Provided region 'us-west-1' does not exist. Existing regions{{':'}} "
+ when: version.current.version is version('3', '<')
+
+# REMOVE CIDR
+- name: Remove CIDR (check_mode)
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_present_2
+ state: absent
+ check_mode: true
+ register: cm_remove_cidr
+
+- name: Verify cm_remove_cidr
+ assert:
+ that:
+ - cm_remove_cidr is changed
+ - cm_remove_cidr.current == {}
+
+- name: Remove CIDR (normal mode)
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_present_2
+ state: absent
+ register: nm_remove_cidr
+
+- name: Verify nm_remove_cidr
+ assert:
+ that:
+ - nm_remove_cidr is changed
+ - nm_remove_cidr.current == {}
+
+- name: Remove CIDR again (check_mode)
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_present_2
+ state: absent
+ check_mode: true
+ register: cm_remove_cidr_again
+
+- name: Verify cm_remove_cidr_again
+ assert:
+ that:
+ - cm_remove_cidr_again is not changed
+ - cm_remove_cidr_again.current == {}
+
+- name: Remove CIDR again (normal mode)
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_present_2
+ state: absent
+ register: nm_remove_cidr_again
+
+- name: Verify nm_remove_cidr_again
+ assert:
+ that:
+ - nm_remove_cidr_again is not changed
+ - nm_remove_cidr_again.current == {}
+
+# QUERY NON-EXISTING CIDR
+- name: Query non-existing CIDR (check_mode)
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_query
+ cidr: non_existing_cidr
+ check_mode: true
+ ignore_errors: true
+ register: cm_query_non_cidr
+
+- name: Query non-existing CIDR (normal mode)
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_query
+ cidr: non_existing_cidr
+ ignore_errors: true
+ register: nm_query_non_cidr
+
+- name: Verify query_non_cidr
+ assert:
+ that:
+ - cm_query_non_cidr is not changed
+ - nm_query_non_cidr is not changed
+ - cm_query_non_cidr == nm_query_non_cidr
+ - cm_query_non_cidr.msg == nm_query_non_cidr.msg == "CIDR IP 'non_existing_cidr' not found"
+
+# QUERY NON-EXISTING region
+- name: Query non-existing region (check_mode)
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_query
+ region: non_existing_region
+ check_mode: true
+ ignore_errors: true
+ register: cm_query_non_region
+
+- name: Query non-existing region (normal mode)
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_query
+ region: non_existing_region
+ ignore_errors: true
+ register: nm_query_non_region
+
+- name: Verify query_non_region
+ assert:
+ that:
+ - cm_query_non_region is not changed
+ - nm_query_non_region is not changed
+ - cm_query_non_region == nm_query_non_region
+ - cm_query_non_region.msg == nm_query_non_region.msg == "Provided region 'non_existing_region' does not exist. Existing regions{{':'}} us-west-1"
+
+# QUERY NON-EXISTING VRF
+- name: Query non-existing VRF (check_mode)
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_query
+ vrf: non_existing_vrf
+ check_mode: true
+ ignore_errors: true
+ register: cm_query_non_vrf
+
+- name: Query non-existing VRF (normal mode)
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_query
+ vrf: non_existing_vrf
+ ignore_errors: true
+ register: nm_query_non_vrf
+
+- name: Verify query_non_vrf
+ assert:
+ that:
+ - cm_query_non_vrf is not changed
+ - nm_query_non_vrf is not changed
+ - cm_query_non_vrf == nm_query_non_vrf
+
+- name: Verify query_non_vrf (version < 3.0)
+ assert:
+ that:
+ - cm_query_non_vrf.msg == nm_query_non_vrf.msg == "Provided vrf 'non_existing_vrf' does not exist. Existing vrfs{{':'}} VRF1, VRF2"
+ when: version.current.version is version('3', '<')
+
+# USE A NON-EXISTING STATE
+- name: Non-existing state for site cidr (check_mode)
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_query
+ state: non-existing-state
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_state
+
+- name: Non-existing state for site cidr (normal_mode)
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_query
+ state: non-existing-state
+ ignore_errors: true
+ register: nm_non_existing_state
+
+- name: Verify non_existing_state
+ assert:
+ that:
+ - cm_non_existing_state is not changed
+ - nm_non_existing_state is not changed
+ - cm_non_existing_state == nm_non_existing_state
+ - cm_non_existing_state.msg == nm_non_existing_state.msg == "value of state must be one of{{':'}} absent, present, query, got{{':'}} non-existing-state"
+
+# USE A NON-EXISTING SCHEMA
+- name: Non-existing schema for site cidr (check_mode)
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_query
+ schema: non-existing-schema
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_schema
+
+- name: Non-existing schema for site cidr (normal_mode)
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_query
+ schema: non-existing-schema
+ ignore_errors: true
+ register: nm_non_existing_schema
+
+- name: Verify non_existing_schema
+ assert:
+ that:
+ - cm_non_existing_schema is not changed
+ - nm_non_existing_schema is not changed
+ - cm_non_existing_schema == nm_non_existing_schema
+ - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non-existing-schema' does not exist."
+
+# USE A NON-EXISTING TEMPLATE
+- name: Non-existing template for site cidr (check_mode)
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_query
+ template: non-existing-template
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_template
+
+- name: Non-existing template for site cidr (normal_mode)
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_query
+ template: non-existing-template
+ ignore_errors: true
+ register: nm_non_existing_template
+
+- name: Verify non_existing_template
+ assert:
+ that:
+ - cm_non_existing_template is not changed
+ - nm_non_existing_template is not changed
+ - cm_non_existing_template == nm_non_existing_template
+ - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non-existing-template' does not exist. Existing templates{{':'}} Template1, Template2"
+
+# USE A NON-ASSOCIATED TEMPLATE
+- name: Non-associated template for site cidr (check_mode)
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_query
+ template: Template 2
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_associated_template
+
+- name: Non-associated template for site cidr (normal_mode)
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_query
+ template: Template 2
+ ignore_errors: true
+ register: nm_non_associated_template
+
+- name: Verify non_associated_template
+ assert:
+ that:
+ - cm_non_associated_template is not changed
+ - nm_non_associated_template is not changed
+ - cm_non_associated_template == nm_non_associated_template
+ - cm_non_associated_template.msg == "Provided site-template association 'aws_{{ mso_site | default("ansible_test") }}-Template2' does not exist."
+ - nm_non_associated_template.msg == "Provided site-template association 'aws_{{ mso_site | default("ansible_test") }}-Template2' does not exist."
+
+# USE A TEMPLATE WITHOUT ANY SITE
+- name: Add site VRF region cidr to Schema 2 Template 3 without any site associated (check mode)
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_present
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ check_mode: true
+ register: cm_no_site_associated
+
+- name: Add site VRF region cidr to Template 3 without any site associated (normal mode)
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_present
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ register: nm_no_site_associated
+
+- name: Verify cm_no_site_associated and nm_no_site_associated
+ assert:
+ that:
+ - cm_no_site_associated is changed
+ - nm_no_site_associated is changed \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_cidr_subnet/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_cidr_subnet/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_cidr_subnet/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_cidr_subnet/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_cidr_subnet/tasks/main.yml
new file mode 100644
index 000000000..e5e8d3ca7
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_cidr_subnet/tasks/main.yml
@@ -0,0 +1,886 @@
+# Test code for the MSO modules
+# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com> (based on mso_schema_anp_epg_domain)
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case)
+
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+ sites: "[ { 'site': 'aws_{{ mso_site | default(\"ansible_test\") }}', 'region': 'us-west-1', 'cidr': '10.0.0.0/16'},
+ { 'site': 'azure_{{ mso_site | default(\"ansible_test\") }}', 'region': 'westus', 'cidr': '10.1.0.0/16'}]"
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Ensure site exists
+ mso_site:
+ <<: *mso_info
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ aws_apic_username }}'
+ apic_password: '{{ aws_apic_password }}'
+ apic_site_id: '{{ aws_site_id | default(102) }}'
+ urls:
+ - https://{{ aws_apic_hostname }}
+ state: present
+
+- name: Ensure site exists
+ mso_site:
+ <<: *mso_info
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ azure_apic_username }}'
+ apic_password: '{{ azure_apic_password }}'
+ apic_site_id: '{{ azure_site_id | default(103) }}'
+ urls:
+ - https://{{ azure_apic_hostname }}
+ state: present
+
+- name: Remove Schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Ensure sites removed from tenant ansible_test
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_site | default("ansible_test") }}'
+ - 'aws_{{ mso_site | default("ansible_test") }}'
+ - 'azure_{{ mso_site | default("ansible_test") }}'
+
+- name: Ensure tenant ansible_test exist
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ state: present
+
+- name: Ensure AWS site is present under tenant ansible_test
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ cloud_account: '000000000000'
+ aws_access_key: 1
+ secret_key: 0
+ state: present
+
+- name: Ensure Azure site is present under tenant ansible_test
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ cloud_account: uni/tn-ansible_test/act-[9]-vendor-azure
+ state: present
+
+- name: Ensure schema 1 with Template 1 and 2 exists
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: '{{ item }}'
+ state: present
+ loop:
+ - Template 1
+ - Template 2
+
+- name: Ensure schema 2 with Template 3 exists
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template 3
+ state: present
+
+- name: Add a new sites to a Template 1
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ item.site }}'
+ template: Template 1
+ state: present
+ loop: '{{ sites }}'
+ when: version.current.version is version('3', '<')
+
+- name: Ensure VRF1 exists
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF1
+ state: present
+
+- name: Ensure region for VRF1 at site level exists
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: '{{ item.site }}'
+ vrf: VRF1
+ region: '{{ item.region }}'
+ cidr: '{{ item.cidr }}'
+ state: present
+ loop: '{{ sites }}'
+
+
+# ADD SUBNET
+- name: Add a new subnet to AWS CIDR in VRF1 at site level (check mode)
+ mso_schema_site_vrf_region_cidr_subnet: &mso_present
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ cidr: 10.0.0.0/16
+ subnet: 10.0.0.0/24
+ zone: us-west-1a
+ state: present
+ check_mode: true
+ register: cm_add_subnet
+
+- name: Verify cm_add_subnet
+ assert:
+ that:
+ - cm_add_subnet is changed
+ - cm_add_subnet.previous == {}
+ - cm_add_subnet.current.ip == '10.0.0.0/24'
+ - cm_add_subnet.current.zone == 'us-west-1a'
+
+- name: Add a new subnet to AWS CIDR in VRF1 at site level (normal mode)
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_present
+ register: nm_add_subnet
+
+- name: Verify nm_add_subnet
+ assert:
+ that:
+ - nm_add_subnet is changed
+ - nm_add_subnet.previous == {}
+ - nm_add_subnet.current.ip == '10.0.0.0/24'
+ - nm_add_subnet.current.zone == 'us-west-1a'
+
+- name: Add same subnet again to AWS CIDR in VRF1 at site level (check mode)
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_present
+ register: cm_add_subnet_again
+
+- name: Verify cm_add_subnet_again
+ assert:
+ that:
+ - cm_add_subnet_again is not changed
+ - cm_add_subnet_again.current.ip == cm_add_subnet_again.previous.ip == '10.0.0.0/24'
+ - cm_add_subnet_again.current.zone == cm_add_subnet_again.previous.zone == 'us-west-1a'
+
+- name: Add same subnet again to AWS CIDR in VRF1 at site level (normal mode)
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_present
+ register: nm_add_subnet_again
+
+- name: Verify nm_add_subnet_again
+ assert:
+ that:
+ - nm_add_subnet_again is not changed
+ - nm_add_subnet_again.current.ip == nm_add_subnet_again.previous.ip == '10.0.0.0/24'
+ - nm_add_subnet_again.current.zone == nm_add_subnet_again.previous.zone == 'us-west-1a'
+
+- name: Add a new subnet to Azure CIDR in VRF1 at site level (check mode)
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_present
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ region: westus
+ cidr: 10.1.0.0/16
+ subnet: 10.1.0.0/24
+ zone: null
+ check_mode: true
+ register: cm_add_subnet_2
+
+- name: Verify cm_add_subnet_2
+ assert:
+ that:
+ - cm_add_subnet_2 is changed
+ - cm_add_subnet_2.previous == {}
+ - cm_add_subnet_2.current.ip == '10.1.0.0/24'
+ - cm_add_subnet_2.current.zone == ''
+
+- name: Add a new subnet to Azure CIDR in VRF1 at site level (normal mode)
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_present
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ region: westus
+ cidr: 10.1.0.0/16
+ subnet: 10.1.0.0/24
+ zone: null
+ register: nm_add_subnet_2
+
+- name: Verify nm_add_subnet_2
+ assert:
+ that:
+ - nm_add_subnet_2 is changed
+ - nm_add_subnet_2.previous == {}
+ - nm_add_subnet_2.current.ip == '10.1.0.0/24'
+ - nm_add_subnet_2.current.zone == ''
+
+- name: Add a second subnet to Azure CIDR in VRF1 at site level for VGW (check mode)
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_present
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ region: westus
+ cidr: 10.1.0.0/16
+ subnet: 10.1.1.0/24
+ zone: null
+ vgw: true
+ check_mode: true
+ register: cm_add_subnet_3
+
+- name: Verify cm_add_subnet_3
+ assert:
+ that:
+ - cm_add_subnet_3 is changed
+ - cm_add_subnet_3.previous == {}
+ - cm_add_subnet_3.current.ip == '10.1.1.0/24'
+ - cm_add_subnet_3.current.zone == ''
+ - cm_add_subnet_3.current.usage == 'gateway'
+
+# VGW
+- name: Add a second subnet to Azure CIDR in VRF1 at site level for VGW (normal mode)
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_present
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ region: westus
+ cidr: 10.1.0.0/16
+ subnet: 10.1.1.0/24
+ zone: null
+ vgw: true
+ register: nm_add_subnet_3
+
+- name: Verify nm_add_subnet_3
+ assert:
+ that:
+ - nm_add_subnet_3 is changed
+ - nm_add_subnet_3.previous == {}
+ - nm_add_subnet_3.current.ip == '10.1.1.0/24'
+ - nm_add_subnet_3.current.zone == ''
+ - nm_add_subnet_3.current.usage == 'gateway'
+
+# Private Link Label
+- name: Add a new subnet to Azure CIDR in VRF1 at site level for Private Link Label (MSO >3.3)
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_present
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ region: westus
+ cidr: 10.1.0.0/16
+ subnet: 10.1.0.0/24
+ private_link_label: 'New_Private_Link_Label'
+ zone: null
+ register: nm_add_subnet_4
+ when: version.current.version is version('3.3', '>=')
+
+- name: Verify nm_add_subnet_4
+ assert:
+ that:
+ - nm_add_subnet_4 is changed
+ - nm_add_subnet_4.current.ip == '10.1.0.0/24'
+ - nm_add_subnet_4.current.privateLinkLabel.name == 'New_Private_Link_Label'
+ when: version.current.version is version('3.3', '>=')
+
+# QUERY SUBNETS
+- name: Query subnet to AWS CIDR in VRF1 at site level (check mode)
+ mso_schema_site_vrf_region_cidr_subnet: &mso_query
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ cidr: 10.0.0.0/16
+ subnet: 10.0.0.0/24
+ state: query
+ check_mode: true
+ register: cm_query_subnet
+
+- name: Verify cm_query_subnet
+ assert:
+ that:
+ - cm_query_subnet is not changed
+ - cm_query_subnet.current.ip == '10.0.0.0/24'
+ - cm_query_subnet.current.zone == 'us-west-1a'
+
+- name: Query subnet to AWS CIDR in VRF1 at site level (normal mode)
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_query
+ register: nm_query_subnet
+
+- name: Verify nm_query_subnet
+ assert:
+ that:
+ - nm_query_subnet is not changed
+
+# QUERY ALL SUBNETS
+- name: Query all subnets to AWS CIDR in VRF1 at site level (check mode)
+ mso_schema_site_vrf_region_cidr_subnet: &mso_query_all
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ cidr: 10.0.0.0/16
+ state: query
+ check_mode: true
+ register: cm_query_subnet_all_aws
+
+- name: Query all subnets to Azure CIDR in VRF1 at site level (check mode)
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_query_all
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ region: westus
+ cidr: 10.1.0.0/16
+ state: query
+ check_mode: true
+ register: cm_query_subnet_all_azure
+
+- name: Verify cm_query_subnet_all_aws and cm_query_subnet_all_azure
+ assert:
+ that:
+ - cm_query_subnet_all_aws is not changed
+ - cm_query_subnet_all_aws.current[0].ip == '10.0.0.0/24'
+ - cm_query_subnet_all_aws.current[0].zone == 'us-west-1a'
+ - cm_query_subnet_all_azure is not changed
+ - cm_query_subnet_all_azure.current[0].ip == '10.1.0.0/24'
+ - cm_query_subnet_all_azure.current[1].ip == '10.1.1.0/24'
+
+- name: Verify cm_query_subnet_all_aws and cm_query_subnet_all_azure zone
+ assert:
+ that:
+ - cm_query_subnet_all_azure.current[0].zone == ''
+ - cm_query_subnet_all_azure.current[1].zone == ''
+ when: version.current.version is version('4.0', '<')
+
+
+- name: Verify cm_query_subnet_all_aws and cm_query_subnet_all_azure zone
+ assert:
+ that:
+ - cm_query_subnet_all_azure.current[0].zone == 'default'
+ - cm_query_subnet_all_azure.current[1].zone == 'default'
+ when: version.current.version is version('4.0', '>=')
+
+- name: Query subnet to AWS CIDR in VRF1 at site level (normal mode)
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_query_all
+ register: nm_query_subnet_all_aws
+
+- name: Query subnet to AWS CIDR in VRF1 at site level (normal mode)
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_query_all
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ region: westus
+ cidr: 10.1.0.0/16
+ state: query
+ register: nm_query_subnet_all_azure
+
+- name: Verify nm_query_subnet_all_aws and nm_query_subnet_all_azure
+ assert:
+ that:
+ - nm_query_subnet_all_aws is not changed
+ - nm_query_subnet_all_aws.current[0].ip == '10.0.0.0/24'
+ - nm_query_subnet_all_aws.current[0].zone == 'us-west-1a'
+ - nm_query_subnet_all_azure is not changed
+ - nm_query_subnet_all_azure.current[0].ip == '10.1.0.0/24'
+ - nm_query_subnet_all_azure.current[1].ip == '10.1.1.0/24'
+
+- name: Verify cm_query_subnet_all_aws and cm_query_subnet_all_azure zone
+ assert:
+ that:
+ - nm_query_subnet_all_azure.current[0].zone == ''
+ - nm_query_subnet_all_azure.current[1].zone == ''
+ when: version.current.version is version('4.0', '<')
+
+
+- name: Verify cm_query_subnet_all_aws and cm_query_subnet_all_azure zone
+ assert:
+ that:
+ - nm_query_subnet_all_azure.current[0].zone == 'default'
+ - nm_query_subnet_all_azure.current[1].zone == 'default'
+ when: version.current.version is version('4.0', '>=')
+
+# Execute Hosted VRF parameters only when when MSO version >= 3.3
+- name: Execute tasks only for MSO version >= 3.3
+ when: version.current.version is version('3.3', '>=')
+ block:
+ - name: Ensure VRF2 exists
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF2
+ state: present
+
+ - name: Add a secondary CIDR in VRF1 at AWS site level
+ mso_schema_site_vrf_region_cidr: &secondary_cidr
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ cidr: 10.2.0.0/16
+ primary: false
+ register: nm_add_cidr_3
+
+ - name: Verify nm_add_cidr_3
+ assert:
+ that:
+ - nm_add_cidr_3 is changed
+ - nm_add_cidr_3.previous == {}
+ - nm_add_cidr_3.current.ip == '10.2.0.0/16'
+ - nm_add_cidr_3.current.primary == false
+
+ - name: Add a secondary CIDR in VRF1 at Azure site level
+ mso_schema_site_vrf_region_cidr:
+ <<: *secondary_cidr
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: westus
+ cidr: 10.3.0.0/16
+ primary: false
+ register: nm_add_cidr_4
+
+ - name: Verify nm_add_cidr_4
+ assert:
+ that:
+ - nm_add_cidr_4 is changed
+ - nm_add_cidr_4.previous == {}
+ - nm_add_cidr_4.current.ip == '10.3.0.0/16'
+ - nm_add_cidr_4.current.primary == false
+
+ - name: Add hosted vrf parameters in VRF1 at Azure site level
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: westus
+ cidr: 10.3.0.0/16
+ subnet: 10.3.0.0/24
+ zone: westus
+ hosted_vrf: VRF2
+ vgw: true
+ state: present
+ register: azure_add_hosted_vrf
+
+ - name: Verify azure_add_hosted_vrf
+ assert:
+ that:
+ - azure_add_hosted_vrf is changed
+ - azure_add_hosted_vrf.previous == {}
+ - azure_add_hosted_vrf.current.ip == '10.3.0.0/24'
+ - azure_add_hosted_vrf.current.vrfRef.vrfName == 'VRF2'
+
+ - name: Add hosted vrf parameters in VRF1 at AWS site level
+ mso_schema_site_vrf_region_cidr_subnet: &aws_cidr
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ cidr: 10.2.0.0/16
+ subnet: 10.2.0.0/24
+ zone: us-west-1a
+ hosted_vrf: VRF2
+ vgw: true
+ state: present
+ register: aws_add_hosted_vrf
+
+ - name: Verify aws_add_hosted_vrf
+ assert:
+ that:
+ - aws_add_hosted_vrf is changed
+ - aws_add_hosted_vrf.previous == {}
+ - aws_add_hosted_vrf.current.ip == '10.2.0.0/24'
+ - aws_add_hosted_vrf.current.vrfRef.vrfName == 'VRF2'
+
+ - name: Get Validation status
+ mso_schema_validate:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ state: query
+ register: query_validate
+
+ - name: Verify query_validate
+ assert:
+ that:
+ - query_validate is not changed
+
+ - name: Verify query_validate result < 4.0
+ assert:
+ that:
+ - query_validate.current.result == "true"
+ when: version.current.version is version('4.0', '<')
+
+ - name: Verify query_validate result => 4.0
+ assert:
+ that:
+ - query_validate.current.result == true
+ when: version.current.version is version('4.0', '>=')
+
+# REMOVE SUBNETS
+- name: Remove Subnet from CIDR (check_mode)
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_present
+ state: absent
+ check_mode: true
+ register: cm_remove_subnet
+
+- name: Verify cm_remove_subnet
+ assert:
+ that:
+ - cm_remove_subnet is changed
+ - cm_remove_subnet.current == {}
+
+- name: Remove Subnet from CIDR (normal mode)
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_present
+ state: absent
+ register: nm_remove_subnet
+
+- name: Verify nm_remove_subnet
+ assert:
+ that:
+ - nm_remove_subnet is changed
+ - nm_remove_subnet.current == {}
+
+- name: Remove Subnet from CIDR again (check_mode)
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_present
+ state: absent
+ check_mode: true
+ register: cm_remove_subnet_again
+
+- name: Verify cm_remove_subnet_again
+ assert:
+ that:
+ - cm_remove_subnet_again is not changed
+ - cm_remove_subnet_again.current == {}
+
+- name: Remove Subnet from CIDR again (normal mode)
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_present
+ state: absent
+ register: nm_remove_subnet_again
+
+- name: Verify nm_remove_subnet_again
+ assert:
+ that:
+ - nm_remove_subnet_again is not changed
+ - nm_remove_subnet_again.current == {}
+
+- name: Remove Subnet from CIDR (normal mode)
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *aws_cidr
+ state: absent
+ register: remove_subnet
+ when: version.current.version is version('3.3', '>=')
+
+- name: Verify nm_remove_subnet
+ assert:
+ that:
+ - remove_subnet is changed
+ - remove_subnet.current == {}
+ when: version.current.version is version('3.3', '>=')
+
+# QUERY NON-EXISTING subnet in CIDR
+- name: Query non-existing subnet (check_mode)
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_query
+ subnet: non_existing_subnet
+ check_mode: true
+ ignore_errors: true
+ register: cm_query_non_subnet
+
+- name: Query non-existing subnet (normal mode)
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_query
+ subnet: non_existing_subnet
+ ignore_errors: true
+ register: nm_query_non_subnet
+
+- name: Verify query_non_subnet
+ assert:
+ that:
+ - cm_query_non_subnet is not changed
+ - nm_query_non_subnet is not changed
+ - cm_query_non_subnet == nm_query_non_subnet
+ - cm_query_non_subnet.msg is match("Subnet IP 'non_existing_subnet' not found")
+ - nm_query_non_subnet.msg is match("Subnet IP 'non_existing_subnet' not found")
+
+# QUERY NON-EXISTING CIDR
+- name: Query non-existing CIDR (check_mode)
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_query
+ cidr: non_existing_cidr
+ check_mode: true
+ ignore_errors: true
+ register: cm_query_non_cidr
+
+- name: Query non-existing CIDR (normal mode)
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_query
+ cidr: non_existing_cidr
+ ignore_errors: true
+ register: nm_query_non_cidr
+
+- name: Verify query_non_cidr
+ assert:
+ that:
+ - cm_query_non_cidr is not changed
+ - nm_query_non_cidr is not changed
+ - cm_query_non_cidr == nm_query_non_cidr
+
+- name: Verify query_non_cidr value (version < 3.3)
+ assert:
+ that:
+ - cm_query_non_cidr.msg == nm_query_non_cidr.msg == "Provided CIDR IP 'non_existing_cidr' does not exist. Existing CIDR IPs{{':'}} 10.0.0.0/16. Use mso_schema_site_vrf_region_cidr to create it."
+ when: version.current.version is version('3.3', '<')
+
+- name: Verify query_non_cidr value (version >= 3.3)
+ assert:
+ that:
+ - cm_query_non_cidr.msg == nm_query_non_cidr.msg == "Provided CIDR IP 'non_existing_cidr' does not exist. Existing CIDR IPs{{':'}} 10.0.0.0/16, 10.2.0.0/16. Use mso_schema_site_vrf_region_cidr to create it."
+ when: version.current.version is version('3.3', '>=')
+
+# QUERY NON-EXISTING region
+- name: Query non-existing region (check_mode)
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_query
+ region: non_existing_region
+ check_mode: true
+ ignore_errors: true
+ register: cm_query_non_region
+
+- name: Query non-existing region (normal mode)
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_query
+ region: non_existing_region
+ ignore_errors: true
+ register: nm_query_non_region
+
+- name: Verify query_non_region
+ assert:
+ that:
+ - cm_query_non_region is not changed
+ - nm_query_non_region is not changed
+ - cm_query_non_region == nm_query_non_region
+ - cm_query_non_region.msg == nm_query_non_region.msg == "Provided region 'non_existing_region' does not exist. Existing regions{{':'}} us-west-1. Use mso_schema_site_vrf_region_cidr to create it."
+
+# QUERY NON-EXISTING VRF
+- name: Query non-existing VRF (check_mode)
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_query
+ vrf: non_existing_vrf
+ check_mode: true
+ ignore_errors: true
+ register: cm_query_non_vrf
+
+- name: Query non-existing VRF (normal mode)
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_query
+ vrf: non_existing_vrf
+ ignore_errors: true
+ register: nm_query_non_vrf
+
+- name: Verify query_non_vrf
+ assert:
+ that:
+ - cm_query_non_vrf is not changed
+ - nm_query_non_vrf is not changed
+ - cm_query_non_vrf == nm_query_non_vrf
+ - cm_query_non_vrf.msg == nm_query_non_vrf.msg == "Provided vrf 'non_existing_vrf' does not exist at site level. Use mso_schema_site_vrf_region_cidr to create it."
+
+# USE A NON-EXISTING STATE
+- name: Non-existing state for site cidr subnet (check_mode)
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_query
+ state: non-existing-state
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_state
+
+- name: Non-existing state for site cidr subnet (normal_mode)
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_query
+ state: non-existing-state
+ ignore_errors: true
+ register: nm_non_existing_state
+
+- name: Verify non_existing_state
+ assert:
+ that:
+ - cm_non_existing_state is not changed
+ - nm_non_existing_state is not changed
+ - cm_non_existing_state == nm_non_existing_state
+ - cm_non_existing_state.msg == nm_non_existing_state.msg == "value of state must be one of{{':'}} absent, present, query, got{{':'}} non-existing-state"
+
+# USE A NON-EXISTING SCHEMA
+- name: Non-existing schema for site cidr subnet (check_mode)
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_query
+ schema: non-existing-schema
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_schema
+
+- name: Non-existing schema for site cidr subnet (normal_mode)
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_query
+ schema: non-existing-schema
+ ignore_errors: true
+ register: nm_non_existing_schema
+
+- name: Verify non_existing_schema
+ assert:
+ that:
+ - cm_non_existing_schema is not changed
+ - nm_non_existing_schema is not changed
+ - cm_non_existing_schema == nm_non_existing_schema
+ - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non-existing-schema' does not exist."
+
+# USE A NON-EXISTING TEMPLATE
+- name: Non-existing template for site cidr subnet (check_mode)
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_query
+ template: non-existing-template
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_template
+
+- name: Non-existing template for site cidr subnet (normal_mode)
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_query
+ template: non-existing-template
+ ignore_errors: true
+ register: nm_non_existing_template
+
+- name: Verify non_existing_template
+ assert:
+ that:
+ - cm_non_existing_template is not changed
+ - nm_non_existing_template is not changed
+ - cm_non_existing_template == nm_non_existing_template
+ - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non-existing-template' does not exist. Existing templates{{':'}} Template1, Template2"
+
+# USE A NON-ASSOCIATED TEMPLATE
+- name: Non-associated template for site cidr subnet (check_mode)
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_query
+ template: Template 2
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_associated_template
+
+- name: Non-associated template for site cidr subnet (normal_mode)
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_query
+ template: Template 2
+ ignore_errors: true
+ register: nm_non_associated_template
+
+- name: Verify non_associated_template
+ assert:
+ that:
+ - cm_non_associated_template is not changed
+ - nm_non_associated_template is not changed
+ - cm_non_associated_template == nm_non_associated_template
+ - cm_non_associated_template.msg is match("Provided site/siteId/template 'aws_ansible_test/[0-9a-zA-Z]*/Template2' does not exist. Existing siteIds/templates{{':'}} [0-9a-zA-Z]*/Template1")
+ - nm_non_associated_template.msg is match("Provided site/siteId/template 'aws_ansible_test/[0-9a-zA-Z]*/Template2' does not exist. Existing siteIds/templates{{':'}} [0-9a-zA-Z]*/Template1")
+
+# USE A TEMPLATE WITHOUT ANY SITE
+- name: Add site cidr subnet to Schema 2 Template 3 without any site associated (check mode)
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_query
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ check_mode: true
+ ignore_errors: true
+ register: cm_no_site_associated
+
+- name: Add site cidr subnet to Template 3 without any site associated (normal mode)
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_query
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ ignore_errors: true
+ register: nm_no_site_associated
+
+- name: Verify cm_no_site_associated and nm_no_site_associated
+ assert:
+ that:
+ - cm_no_site_associated is not changed
+ - nm_no_site_associated is not changed
+ - cm_no_site_associated.msg == nm_no_site_associated.msg == "No site associated with template 'Template3'. Associate the site with the template using mso_schema_site."
+
+# Checking if issue when adding subnet to Hub Network (#126)
+- name: Add hub network in VRF1 region us-west-1 at AWS site level
+ mso_schema_site_vrf_region_hub_network:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ hub_network:
+ name: hub-test
+ tenant: infra
+ state: present
+
+- name: Add a new subnet to AWS CIDR in VRF1 at site level
+ mso_schema_site_vrf_region_cidr_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ cidr: 10.0.0.0/16
+ subnet: 10.0.0.0/24
+ zone: us-west-1a
+ hub_network: true
+ state: present
+ register: nm_add_subnet_hub_network
+
+- name: Verify nm_add_subnet_hub_network
+ assert:
+ that:
+ - nm_add_subnet_hub_network is changed
+ - nm_add_subnet_hub_network.current.usage == 'gateway' \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_hub_network/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_hub_network/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_hub_network/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_hub_network/tasks/hub_network.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_hub_network/tasks/hub_network.yml
new file mode 100644
index 000000000..33fde6710
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_hub_network/tasks/hub_network.yml
@@ -0,0 +1,719 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+ sites: "['aws_{{ mso_site | default(\"ansible_test\") }}',
+ 'azure_{{ mso_site | default(\"ansible_test\") }}',
+ '{{ mso_site | default(\"ansible_test\") }}']"
+
+- name: Ensure site exists
+ mso_site:
+ <<: *mso_info
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ aws_apic_username }}'
+ apic_password: '{{ aws_apic_password }}'
+ apic_site_id: '{{ aws_site_id | default(102) }}'
+ urls:
+ - https://{{ aws_apic_hostname }}
+ state: present
+
+- name: Ensure site exists
+ mso_site:
+ <<: *mso_info
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ azure_apic_username }}'
+ apic_password: '{{ azure_apic_password }}'
+ apic_site_id: '{{ azure_site_id | default(103) }}'
+ urls:
+ - https://{{ azure_apic_hostname }}
+ state: present
+
+- name: Remove Schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Ensure sites removed from tenant ansible_test
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_site | default("ansible_test") }}'
+ - 'aws_{{ mso_site | default("ansible_test") }}'
+ - 'azure_{{ mso_site | default("ansible_test") }}'
+
+- name: Ensure tenant ansible_test exist
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ # sites:
+ # - '{{ mso_site | default("ansible_test") }}'
+ users:
+ - '{{ mso_username }}'
+ state: present
+
+- name: Ensure AWS site is present under tenant ansible_test
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ cloud_account: '000000000000'
+ aws_access_key: 1
+ secret_key: 0
+ state: present
+
+- name: Ensure Azure site is present under tenant ansible_test
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ cloud_account: uni/tn-ansible_test/act-[9]-vendor-azure
+ state: present
+
+- name: Ensure schema 1 with Template 1 and 2 exists
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: '{{ item }}'
+ state: present
+ loop:
+ - Template 1
+ - Template 2
+
+- name: Ensure schema 2 with Template 3 exists
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template 3
+ state: present
+
+- name: Ensure VRF1 exists
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF1
+ state: present
+
+- name: Add a new CIDR in VRF1 at AWS site level (check mode)
+ mso_schema_site_vrf_region_cidr: &mso_present
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ cidr: 10.0.0.0/16
+ primary: true
+ state: present
+ check_mode: true
+ register: cm_add_cidr
+
+- name: Verify cm_add_cidr
+ assert:
+ that:
+ - cm_add_cidr is changed
+ - cm_add_cidr.previous == {}
+ - cm_add_cidr.current.ip == '10.0.0.0/16'
+ - cm_add_cidr.current.primary == true
+
+- name: Add a new CIDR in VRF1 at AWS site level (normal mode)
+ mso_schema_site_vrf_region_cidr:
+ <<: *mso_present
+ register: nm_add_cidr
+
+- name: Verify nm_add_cidr
+ assert:
+ that:
+ - nm_add_cidr is changed
+ - nm_add_cidr.previous == {}
+ - nm_add_cidr.current.ip == '10.0.0.0/16'
+ - nm_add_cidr.current.primary == true
+
+# ADD Hub Network
+- name: Add hub network in VRF1 region us-west-1 at AWS site level (check mode)
+ mso_schema_site_vrf_region_hub_network:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ hub_network:
+ name: hub-test
+ tenant: infra
+ state: present
+ check_mode: true
+ register: cm_add_hub_network
+
+- name: Add hub network in VRF1 region us-west-1 at AWS site level (normal mode)
+ mso_schema_site_vrf_region_hub_network:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ hub_network:
+ name: hub-test
+ tenant: infra
+ state: present
+ register: nm_add_hub_network
+
+- name: Verify cm_add_hub_network and nm_add_hub_network
+ assert:
+ that:
+ - cm_add_hub_network is changed
+ - nm_add_hub_network is changed
+ - cm_add_hub_network.previous == {}
+ - nm_add_hub_network.previous == {}
+ - cm_add_hub_network.current.name == "hub-test"
+ - cm_add_hub_network.current.tenantName == "infra"
+ - nm_add_hub_network.current.name == "hub-test"
+ - nm_add_hub_network.current.tenantName == "infra"
+
+# Add hub network again
+- name: Add hub network again in VRF1 region us-west-1 at AWS site level (normal mode)
+ mso_schema_site_vrf_region_hub_network:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ hub_network:
+ name: hub-test
+ tenant: infra
+ state: present
+ register: nm_add_hub_network_again
+
+- name: Verify nm_add_hub_network_again
+ assert:
+ that:
+ - nm_add_hub_network_again is not changed
+ - nm_add_hub_network_again.previous.name == nm_add_hub_network_again.current.name == "hub-test"
+ - nm_add_hub_network_again.previous.tenantName == nm_add_hub_network_again.current.tenantName == "infra"
+
+# Update hub network
+- name: Update hub network in VRF1 region us-west-1 at AWS site level (check_mode)
+ mso_schema_site_vrf_region_hub_network:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ hub_network:
+ name: hub-default
+ tenant: infra
+ state: present
+ check_mode: true
+ register: cm_update_hub_network
+
+- name: Update hub network in VRF1 region us-west-1 at AWS site level (normal_mode)
+ mso_schema_site_vrf_region_hub_network:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ hub_network:
+ name: hub-default
+ tenant: infra
+ state: present
+ register: nm_update_hub_network
+
+- name: Verify cm_update_hub_network and nm_update_hub_network
+ assert:
+ that:
+ - cm_update_hub_network is changed
+ - nm_update_hub_network is changed
+ - cm_update_hub_network.previous.name == "hub-test"
+ - cm_update_hub_network.previous.tenantName == "infra"
+ - cm_update_hub_network.current.name == "hub-default"
+ - cm_update_hub_network.current.tenantName == "infra"
+ - nm_update_hub_network.previous.name == "hub-test"
+ - nm_update_hub_network.previous.tenantName == "infra"
+ - nm_update_hub_network.current.name == "hub-default"
+ - nm_update_hub_network.current.tenantName == "infra"
+
+# Query Hub Network
+- name: Query hub network in VRF1 region us-west-1 at AWS site level
+ mso_schema_site_vrf_region_hub_network:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ state: query
+ check_mode: true
+ register: cm_query_hub_network
+
+- name: Verify cm_query_hub_network
+ assert:
+ that:
+ - cm_query_hub_network is not changed
+ - cm_query_hub_network.current.name == "hub-default"
+ - cm_query_hub_network.current.tenantName == "infra"
+
+# Remove Hub Network
+- name: Remove hub network in VRF1 region us-west-1 at AWS site level (check_mode)
+ mso_schema_site_vrf_region_hub_network:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ state: absent
+ check_mode: true
+ register: cm_remove_hub_network
+
+- name: Remove hub network in VRF1 region us-west-1 at AWS site level (normal mode)
+ mso_schema_site_vrf_region_hub_network:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ state: absent
+ register: nm_remove_hub_network
+
+- name: Verify cm_remove_hub_network and nm_remove_hub_network
+ assert:
+ that:
+ - cm_remove_hub_network is changed
+ - cm_remove_hub_network.current == {}
+ - cm_remove_hub_network.previous.name == "hub-default"
+ - cm_remove_hub_network.previous.tenantName == "infra"
+ - nm_remove_hub_network is changed
+ - nm_remove_hub_network.current == {}
+ - nm_remove_hub_network.previous.name == "hub-default"
+ - nm_remove_hub_network.previous.tenantName == "infra"
+
+# Remove Hub Network again
+- name: Remove again hub network in VRF1 region us-west-1 at AWS site level (check_mode)
+ mso_schema_site_vrf_region_hub_network:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ state: absent
+ check_mode: true
+ register: cm_remove_hub_network_again
+
+- name: Remove again hub network in VRF1 region us-west-1 at AWS site level (normal_mode)
+ mso_schema_site_vrf_region_hub_network:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ state: absent
+ register: nm_remove_hub_network_again
+
+- name: Verify cm_remove_hub_network_again and nm_remove_hub_network_again
+ assert:
+ that:
+ - cm_remove_hub_network_again is not changed
+ - nm_remove_hub_network_again is not changed
+ - cm_remove_hub_network_again.previous == cm_remove_hub_network_again.current == {}
+ - nm_remove_hub_network_again.previous == nm_remove_hub_network_again.current == {}
+
+# query when hub network does not exist
+- name: Query non_existing_hub_network
+ mso_schema_site_vrf_region_hub_network:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ state: query
+ ignore_errors: true
+ register: query_non_existing_hub_network
+
+- name: Verify query_non_existing_hub_network
+ assert:
+ that:
+ - query_non_existing_hub_network.msg == "Hub network not found"
+
+# Re-Add hub network
+- name: Re-Add hub network in VRF1 region us-west-1 at AWS site level (normal mode)
+ mso_schema_site_vrf_region_hub_network:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ hub_network:
+ name: hub-test
+ tenant: infra
+ state: present
+ register: re_add_hub_network
+
+- name: Verify re_add_hub_network
+ assert:
+ that:
+ - re_add_hub_network is changed
+ - re_add_hub_network.previous == {}
+ - re_add_hub_network.current.name == "hub-test"
+ - re_add_hub_network.current.tenantName == "infra"
+
+# QUERY NON-EXISTING region
+- name: Query non-existing region (check_mode)
+ mso_schema_site_vrf_region_hub_network:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: non_existing_region
+ state: query
+ ignore_errors: true
+ check_mode: true
+ register: cm_query_non_region
+
+- name: Query non-existing region (normal mode)
+ mso_schema_site_vrf_region_hub_network:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: non_existing_region
+ state: query
+ ignore_errors: true
+ register: nm_query_non_region
+
+- name: Verify query_non_region
+ assert:
+ that:
+ - cm_query_non_region is not changed
+ - nm_query_non_region is not changed
+ - cm_query_non_region == nm_query_non_region
+ - cm_query_non_region.msg == nm_query_non_region.msg == "Provided region 'non_existing_region' does not exist. Existing regions{{':'}} us-west-1"
+
+# QUERY NON-EXISTING VRF
+- name: Query non-existing VRF (check_mode)
+ mso_schema_site_vrf_region_hub_network:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: non_existing_vrf
+ region: us-west-1
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_query_non_vrf
+
+- name: Query non-existing VRF (normal mode)
+ mso_schema_site_vrf_region_hub_network:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: non_existing_vrf
+ region: us-west-1
+ state: query
+ ignore_errors: true
+ register: nm_query_non_vrf
+
+- name: Verify query_non_vrf
+ assert:
+ that:
+ - cm_query_non_vrf is not changed
+ - nm_query_non_vrf is not changed
+ - cm_query_non_vrf == nm_query_non_vrf
+ - cm_query_non_vrf.msg == nm_query_non_vrf.msg == "Provided vrf 'non_existing_vrf' does not exist. Existing vrfs{{':'}} VRF1"
+
+# USE A NON-EXISTING STATE
+- name: Non-existing state for site hub network (check_mode)
+ mso_schema_site_vrf_region_hub_network:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ state: non-existing-state
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_state
+
+- name: Non-existing state for hub network (normal_mode)
+ mso_schema_site_vrf_region_hub_network:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ state: non-existing-state
+ ignore_errors: true
+ register: nm_non_existing_state
+
+- name: Verify non_existing_state
+ assert:
+ that:
+ - cm_non_existing_state is not changed
+ - nm_non_existing_state is not changed
+ - cm_non_existing_state == nm_non_existing_state
+ - cm_non_existing_state.msg == nm_non_existing_state.msg == "value of state must be one of{{':'}} absent, present, query, got{{':'}} non-existing-state"
+
+# USE A NON-EXISTING SCHEMA
+- name: Non-existing schema for site hub network (check_mode)
+ mso_schema_site_vrf_region_hub_network:
+ <<: *mso_info
+ schema: non-existing-schema
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_schema
+
+- name: Non-existing schema for site hub network (normal_mode)
+ mso_schema_site_vrf_region_hub_network:
+ <<: *mso_info
+ schema: non-existing-schema
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_schema
+
+- name: Verify non_existing_schema
+ assert:
+ that:
+ - cm_non_existing_schema is not changed
+ - nm_non_existing_schema is not changed
+ - cm_non_existing_schema == nm_non_existing_schema
+ - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non-existing-schema' does not exist."
+
+# USE A NON-EXISTING TEMPLATE
+- name: Non-existing template for site hub network (check_mode)
+ mso_schema_site_vrf_region_hub_network:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: non-existing-template
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_template
+
+- name: Non-existing template for site hub network (normal_mode)
+ mso_schema_site_vrf_region_hub_network:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: non-existing-template
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_template
+
+- name: Verify non_existing_template
+ assert:
+ that:
+ - cm_non_existing_template is not changed
+ - nm_non_existing_template is not changed
+ - cm_non_existing_template == nm_non_existing_template
+ - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non-existing-template' does not exist. Existing templates{{':'}} Template1, Template2"
+
+# USE A NON_EXISTING_SITE_TEMPLATE
+- name: non_existing_site_template (check_mode)
+ mso_schema_site_vrf_region_hub_network:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_site_template
+
+- name: non_existing_site_template (normal_mode)
+ mso_schema_site_vrf_region_hub_network:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_site_template
+
+- name: Verify cm_non_existing_site_template and nm_non_existing_site_template
+ assert:
+ that:
+ - cm_non_existing_site_template is not changed
+ - nm_non_existing_site_template is not changed
+ - cm_non_existing_site_template.msg == nm_non_existing_site_template.msg == "Provided site-template association 'aws_ansible_test-Template2' does not exist."
+
+# USE A NON_EXISTING_SITE
+- name: non_existing_site (check_mode)
+ mso_schema_site_vrf_region_hub_network:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: non_existing_site
+ vrf: VRF1
+ region: us-west-1
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_site
+
+- name: non_existing_site (normal_mode)
+ mso_schema_site_vrf_region_hub_network:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: non_existing_site
+ vrf: VRF1
+ region: us-west-1
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_site
+
+- name: Verify cm_non_existing_site and nm_non_existing_site
+ assert:
+ that:
+ - cm_non_existing_site is not changed
+ - nm_non_existing_site is not changed
+ - cm_non_existing_site.msg == nm_non_existing_site.msg == "Site 'non_existing_site' is not a valid site name."
+
+# use mso_schema_site_vrf_region_cidr_subnet module to update region
+- name: Add a new CIDR in VRF1 at AWS site level (check mode)
+ mso_schema_site_vrf_region_cidr: &cidr_present
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ cidr: 10.1.0.0/16
+ primary: false
+ state: present
+ check_mode: true
+ register: cm_add_cidr
+
+- name: Verify cm_add_cidr
+ assert:
+ that:
+ - cm_add_cidr is changed
+ - cm_add_cidr.previous == {}
+ - cm_add_cidr.current.ip == '10.1.0.0/16'
+ - cm_add_cidr.current.primary == false
+
+- name: Add a new CIDR in VRF1 at AWS site level (normal mode)
+ mso_schema_site_vrf_region_cidr:
+ <<: *cidr_present
+ register: nm_add_cidr
+
+- name: Verify nm_add_cidr
+ assert:
+ that:
+ - nm_add_cidr is changed
+ - nm_add_cidr.previous == {}
+ - nm_add_cidr.current.ip == '10.1.0.0/16'
+ - nm_add_cidr.current.primary == false
+
+# query hub network after using mso_schema_site_vrf_region_cidr_subnet module to update region
+- name: Query hub_network after region updated
+ mso_schema_site_vrf_region_hub_network:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ state: query
+ register: query_after_region_update
+
+- name: Verify query_after_region_update
+ assert:
+ that:
+ - query_after_region_update is not changed
+ - query_after_region_update.current.name == "hub-test"
+ - query_after_region_update.current.tenantName == "infra"
+
+# USE A TEMPLATE WITHOUT ANY SITE
+- name: Add site VRF region hub network to Schema 2 Template 3 without any site associated (check mode)
+ mso_schema_site_vrf_region_hub_network:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ hub_network:
+ name: hub-test
+ tenant: infra
+ state: present
+ ignore_errors: true
+ check_mode: true
+ register: cm_no_site_associated
+
+- name: Add site VRF region hub network to Template 3 without any site associated (normal mode)
+ mso_schema_site_vrf_region_hub_network:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ vrf: VRF1
+ region: us-west-1
+ hub_network:
+ name: hub-test
+ tenant: infra
+ state: present
+ ignore_errors: true
+ register: nm_no_site_associated
+
+- name: Verify cm_no_site_associated and nm_no_site_associated
+ assert:
+ that:
+ - cm_no_site_associated is not changed
+ - nm_no_site_associated is not changed
+ - cm_no_site_associated.msg == nm_no_site_associated.msg == "No site associated with template 'Template3'. Associate the site with the template using mso_schema_site." \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_hub_network/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_hub_network/tasks/main.yml
new file mode 100644
index 000000000..6eb612ad0
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_site_vrf_region_hub_network/tasks/main.yml
@@ -0,0 +1,32 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Import hub_network tasks if MSO version is higher than 3.0
+ import_tasks: hub_network.yml
+ when: version.current.version is version('3', '>') \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template/tasks/main.yml
new file mode 100644
index 000000000..9823bcfd4
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template/tasks/main.yml
@@ -0,0 +1,391 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Ensure site exists
+ mso_site:
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ apic_username }}'
+ apic_password: '{{ apic_password }}'
+ apic_site_id: '{{ apic_site_id | default(101) }}'
+ urls:
+ - https://{{ apic_hostname }}
+ state: present
+ ignore_errors: true
+
+- name: Remove schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_3'
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Ensure tenant ansible_test exists
+ mso_tenant:
+ <<: *mso_info
+ tenant: '{{ item }}'
+ users:
+ - '{{ mso_username }}'
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ state: present
+ loop:
+ - 'ansible_test'
+ - 'ansible_test_2'
+
+- name: Ensure schema 1 with Template 1 exists in check mode
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: Template 1
+ state: present
+ check_mode: true
+ register: add_template1_schema1_cm
+
+- name: Ensure schema 1 with Template 1 exists
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: Template 1
+ state: present
+ register: add_template1_schema1
+
+- name: Ensure schema 1 with Template 2 exists
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: Template 2
+ state: present
+ register: add_template2_schema1
+
+- name: Ensure schema 2 with Template 3 exists
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template 3
+ state: present
+ register: add_template3_schema2
+
+- name: Ensure schema 2 with Template 3 exists again
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template 3
+ state: present
+ register: add_template3_schema2_again
+
+- name: Ensure schema 3 with Template 1 exists
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_3'
+ tenant: ansible_test
+ template: Template 1
+ state: present
+ register: add_template1_schema3
+
+- name: Ensure schema 3 with Template 2 exists
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_3'
+ tenant: ansible_test_2
+ template: Template 2
+ state: present
+ register: add_template2_schema3
+
+- name: Update display name of Template 3 in schema 2
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template 3
+ display_name: Temp 3
+ state: present
+ register: update_template3_schema2
+
+- name: Verify add
+ assert:
+ that:
+ - add_template1_schema1_cm is changed
+ - add_template1_schema1_cm.current.name == 'Template1'
+ - add_template1_schema1 is changed
+ - add_template1_schema1.current.name == 'Template1'
+ - add_template2_schema1 is changed
+ - add_template2_schema1.current.name == 'Template2'
+ - add_template3_schema2 is changed
+ - add_template3_schema2.current.name == 'Template3'
+ - update_template3_schema2 is changed
+ - add_template3_schema2_again is not changed
+ - add_template1_schema3 is changed
+ - add_template1_schema3.current.name == 'Template1'
+ - add_template2_schema3 is changed
+ - add_template2_schema3.current.name == 'Template2'
+ - update_template3_schema2.current.displayName == 'Temp 3'
+
+- name: Query Template 1 in Schema 1
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: Template 1
+ state: query
+ register: query_template1_schema1
+
+- name: Query all Templates in Schema 1
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ state: query
+ register: query_all_templates_schema1
+
+- name: Query Template 1 in Schema 3
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_3'
+ tenant: ansible_test
+ template: Template 1
+ state: query
+ register: query_template1_schema3
+
+- name: Query Template 2 in Schema 3
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_3'
+ tenant: ansible_test_2
+ template: Template 2
+ state: query
+ register: query_template2_schema3
+
+- name: Verify query
+ assert:
+ that:
+ - query_template1_schema1 is not changed
+ - query_template1_schema1.current.name == 'Template1'
+ - query_all_templates_schema1 is not changed
+ - query_all_templates_schema1.current | length == 2
+ - query_template1_schema3 is not changed
+ - query_template1_schema3.current.name == 'Template1'
+ - query_template2_schema3 is not changed
+ - query_template2_schema3.current.name == 'Template2'
+
+- name: Remove Template 1 of Schema 1
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: Template 1
+ state: absent
+ ignore_errors: true
+ register: remove_template1_schema1
+
+- name: Remove Template 2 of Schema 1
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: Template 2
+ state: absent
+ register: remove_template2_schema1
+
+- name: Remove non_existing_template
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: non_existing_template
+ state: absent
+ ignore_errors: true
+ register: remove_template_non_existing_template
+
+- name: Remove Template 3 in schema 2 in check mode
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template 3
+ state: absent
+ check_mode: true
+ register: remove_template3_schema2_cm
+
+- name: Remove Template 3 in schema 2 in normal mode
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template 3
+ state: absent
+ register: remove_template3_schema2_nm
+
+- name: Remove Template 3 in schema 2 again
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template 3
+ state: absent
+ register: remove_template3_schema2_nm_again
+
+- name: Remove Template 1 of Schema 3
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_3'
+ tenant: ansible_test
+ template: Template 1
+ state: absent
+ register: remove_template1_schema3
+
+- name: Remove Template 2 of Schema 3
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_3'
+ tenant: ansible_test_2
+ template: Template 2
+ state: absent
+ register: remove_template2_schema3
+
+- name: non_existing_schema
+ mso_schema_template:
+ <<: *mso_info
+ schema: non_schema
+ tenant: ansible_test
+ template: Template 4
+ state: absent
+ ignore_errors: true
+ register: remove_template_non_existing_schema
+
+- name: Verify remove
+ assert:
+ that:
+ - remove_template1_schema1.current == {}
+ - remove_template1_schema1.previous.name == 'Template1'
+ - remove_template2_schema1.current == {}
+ - remove_template2_schema1.previous.name == 'Template2'
+ - remove_template3_schema2_cm.current == {}
+ - remove_template3_schema2_cm.previous.name == 'Template3'
+ - remove_template3_schema2_nm.current == {}
+ - remove_template3_schema2_nm.previous.name == 'Template3'
+ - remove_template3_schema2_nm_again is not changed
+ - remove_template_non_existing_schema is not changed
+ - remove_template_non_existing_template is not changed
+ - remove_template1_schema3.current == {}
+ - remove_template2_schema3.current == {}
+ - remove_template1_schema3.previous.name == 'Template1'
+ - remove_template2_schema3.previous.name == 'Template2'
+
+# USE NON-EXISTING STATE
+- name: non_existing_state state
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: Template 2
+ state: non_existing_state
+ ignore_errors: true
+ register: non_existing_state
+
+- name: Verify non_existing_state
+ assert:
+ that:
+ - non_existing_state is not changed
+ - non_existing_state.msg == "value of state must be one of{{':'}} absent, present, query, got{{':'}} non_existing_state"
+
+# USE A NON_EXISTING_TEMPLATE
+- name: non_existing_template
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: non_existing_template
+ state: query
+ ignore_errors: true
+ register: non_existing_template
+
+- name: Verify non_existing_template
+ assert:
+ that:
+ - non_existing_template is not changed
+ - non_existing_template.msg == "Template 'non_existing_template' not found"
+
+- name: Template attribute absent in task
+ mso_schema_template:
+ <<: *mso_info
+ schema: non_schema
+ tenant: ansible_test
+ state: query
+ ignore_errors: true
+ register: absent_template
+
+- name: Verify absent_template
+ assert:
+ that:
+ - absent_template is not changed
+ - absent_template.current == []
+
+- name: Update description schema 1 with Template 1 when version is greater than 3.3
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ schema_description: "this is schema"
+ tenant: ansible_test
+ template: Template 1
+ template_description: "this is template"
+ state: present
+ register: add_description
+ when: version.current.version is version('3.3', '>=')
+
+- name: Verify add description
+ assert:
+ that:
+ - add_description is changed
+ when: version.current.version is version('3.3', '>=')
+
+# REMOVE Schemas for next CI Run
+- name: Remove schemas for next ci test
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_3'
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+# REMOVE Tenant2 specific to this test case
+- name: Remove tenant2
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test_2
+ state: absent \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp/tasks/main.yml
new file mode 100644
index 000000000..bbd5bb1cb
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp/tasks/main.yml
@@ -0,0 +1,311 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(false) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Remove schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_3'
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Remove tenant2
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test_2
+ state: absent
+
+- name: Ensure tenant ansible_test exist
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ state: present
+
+- name: Ensure schema 1 with Template 1 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: Template 1
+ state: present
+
+- name: Ensure schema 1 with Template 2 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: Template 2
+ state: present
+
+- name: Ensure schema 2 with Template 3 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template 3
+ state: present
+
+- name: Ensure ANP exist (check_mode)
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ state: present
+ check_mode: true
+ register: cm_create_anp
+
+- name: Ensure ANP exist (normal_mode)
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ state: present
+ register: nm_create_anp
+
+- name: Create ANP again (normal_mode)
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ state: present
+ register: nm_create_anp_again
+
+- name: Verify cm_create_anp, nm_create_anp and nm_create_anp_again
+ assert:
+ that:
+ - cm_create_anp is changed
+ - nm_create_anp is changed
+ - nm_create_anp_again is not changed
+ - cm_create_anp.previous == {}
+ - cm_create_anp.current.displayName == "ANP"
+ - cm_create_anp.current.name == "ANP"
+ - cm_create_anp.current.epgs == []
+ - nm_create_anp.previous == {}
+ - nm_create_anp.current.displayName == "ANP"
+ - nm_create_anp.current.name == "ANP"
+ - nm_create_anp.current.epgs == []
+ - nm_create_anp_again.previous == nm_create_anp_again.current
+ - nm_create_anp_again.current.displayName == "ANP"
+ - nm_create_anp_again.current.name == "ANP"
+ - nm_create_anp_again.current.epgs == []
+
+- name: Create another anp (normal_mode)
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP_2
+ display_name: another anp
+ state: present
+ register: nm_create_another_anp
+
+- name: Verify nm_create_another_anp
+ assert:
+ that:
+ - nm_create_another_anp is changed
+ - nm_create_another_anp.previous == {}
+ - nm_create_another_anp.current.displayName == "another anp"
+ - nm_create_another_anp.current.name == "ANP_2"
+ - nm_create_another_anp.current.epgs == []
+
+
+# Add description for version >= 3.3
+- name: Create another anp with description(normal_mode)
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP_3
+ display_name: another anp 3
+ description: "Description of an ANP_3"
+ state: present
+ register: nm_create_another_anp
+ when: version.current.version is version('3.3', '>=')
+
+- name: Verify nm_create_another_anp with description
+ assert:
+ that:
+ - nm_create_another_anp is changed
+ - nm_create_another_anp.previous == {}
+ - nm_create_another_anp.current.displayName == "another anp 3"
+ - nm_create_another_anp.current.name == "ANP_3"
+ - nm_create_another_anp.current.description == "Description of an ANP_3"
+ - nm_create_another_anp.current.epgs == []
+ when: version.current.version is version('3.3', '>=')
+
+- name: Change anp (normal_mode)
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ display_name: displayName for ANP
+ state: present
+ register: nm_change_anp
+
+- name: Verify nm_change_anp
+ assert:
+ that:
+ - nm_change_anp is changed
+ - nm_change_anp.previous.name == nm_change_anp.current.name == "ANP"
+ - nm_change_anp.previous.displayName == "ANP"
+ - nm_change_anp.current.displayName == "displayName for ANP"
+ - nm_change_anp.previous.epgs == nm_change_anp.current.epgs == []
+
+- name: Query anp
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ state: query
+ register: query_anp
+
+- name: Verify query_anp
+ assert:
+ that:
+ - query_anp is not changed
+ - query_anp.current.name == "ANP"
+ - query_anp.current.epgs == []
+ - query_anp.current.displayName == "displayName for ANP"
+
+- name: Query all
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ state: query
+ register: query_all
+
+- name: Verify query_all
+ assert:
+ that:
+ - query_all is not changed
+ - query_all.current | length == 2
+ when: version.current.version is version('3.3', '<')
+
+- name: Verify query_all
+ assert:
+ that:
+ - query_all is not changed
+ - query_all.current | length == 3
+ when: version.current.version is version('3.3', '>=')
+
+- name: Query non_existing anp
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: non_existing_anp
+ state: query
+ ignore_errors: true
+ register: query_non_existing_anp
+
+- name: Verify query_non_existing_anp
+ assert:
+ that:
+ - query_non_existing_anp.msg == "ANP 'non_existing_anp' not found"
+
+- name: Use non_existing schema
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: non_existing_schema
+ template: Template 1
+ anp: ANP
+ state: query
+ ignore_errors: true
+ register: query_non_existing_schema
+
+- name: Use non_existing template
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: non_existing_template
+ anp: ANP
+ state: query
+ ignore_errors: true
+ register: query_non_existing_template
+
+- name: Verify query_non_existing_schema and query_non_existing_template
+ assert:
+ that:
+ - query_non_existing_schema.msg == "Provided schema 'non_existing_schema' does not exist."
+ - query_non_existing_template.msg == "Provided template 'non_existing_template' does not exist. Existing templates{{':'}} Template1, Template2"
+
+- name: Remove anp (check_mode)
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ state: absent
+ check_mode: true
+ register: cm_rm_anp
+
+- name: Remove anp (normal_mode)
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ state: absent
+ register: nm_rm_anp
+
+- name: Verify cm_rm_anp and nm_rm_anp
+ assert:
+ that:
+ - cm_rm_anp is changed
+ - nm_rm_anp is changed
+ - nm_rm_anp.previous == cm_rm_anp.previous
+ - nm_rm_anp.current == cm_rm_anp.current == {}
+ - nm_rm_anp.previous.name == cm_rm_anp.previous.name == "ANP"
+ - nm_rm_anp.previous.displayName == cm_rm_anp.previous.displayName == "displayName for ANP"
+ - nm_rm_anp.previous.epgs == cm_rm_anp.previous.epgs == []
+
+- name: Remove anp again
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ state: absent
+ register: nm_rm_anp_again
+
+- name: Verify nm_rm_anp_again
+ assert:
+ that:
+ - nm_rm_anp_again is not changed
+ - nm_rm_anp_again.previous == nm_rm_anp_again.current == {}
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg/tasks/main.yml
new file mode 100644
index 000000000..a4af6fb6d
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg/tasks/main.yml
@@ -0,0 +1,1263 @@
+# Test code for the MSO modules
+# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case)
+#
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(false) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Remove schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Ensure tenant ansible_test exist
+ mso_tenant: &tenant_present
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ state: present
+
+- name: Ensure schema 1 with Template 1 exist
+ mso_schema_template: &schema_present
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: Template 1
+ state: present
+
+- name: Ensure schema 1 with Template 2 exist
+ mso_schema_template:
+ <<: *schema_present
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: Template 2
+ state: present
+
+- name: Ensure schema 2 with Template 3 exist
+ mso_schema_template:
+ <<: *schema_present
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template 3
+ state: present
+
+- name: Ensure VRF exist
+ mso_schema_template_vrf: &vrf_present
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF
+ layer3_multicast: true
+ state: present
+
+- name: Ensure VRF2 exist
+ mso_schema_template_vrf:
+ <<: *vrf_present
+ vrf: VRF2
+ state: present
+
+- name: Ensure VRF3 exist
+ mso_schema_template_vrf:
+ <<: *vrf_present
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ vrf: VRF3
+ state: present
+
+- name: Ensure VRF4 exist
+ mso_schema_template_vrf:
+ <<: *vrf_present
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ vrf: VRF4
+ state: present
+
+- name: Ensure ANP exist
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ state: present
+
+- name: Ensure ANP2 exist
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ anp: ANP2
+ state: present
+
+- name: Ensure ANP3 exist
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ anp: ANP3
+ state: present
+
+- name: Ensure Filter 1 exist
+ mso_schema_template_filter_entry:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ filter: Filter1
+ entry: Filter1-Entry
+ state: present
+
+- name: Ensure Contract1 exist
+ mso_schema_template_contract_filter: &contract_present
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract1
+ filter: Filter1
+ filter_schema: '{{ mso_schema | default("ansible_test") }}'
+ filter_template: Template 1
+ state: present
+
+- name: Ensure Filter 2 exist
+ mso_schema_template_filter_entry:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ filter: Filter2
+ entry: Filter2-Entry
+ state: present
+
+- name: Ensure Contract2 exist
+ mso_schema_template_contract_filter: &contract2_present
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ contract: Contract2
+ filter: Filter2
+ filter_schema: '{{ mso_schema | default("ansible_test") }}'
+ filter_template: Template 2
+ state: present
+
+- name: Ensure ansible_test_1 BD exist
+ mso_schema_template_bd:
+ <<: *vrf_present
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ bd: ansible_test_1
+ vrf:
+ name: VRF
+ layer3_multicast: true
+ state: present
+
+- name: Ensure ansible_test_2 BD exist
+ mso_schema_template_bd:
+ <<: *vrf_present
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ bd: ansible_test_2
+ vrf:
+ name: VRF2
+ template: Template 1
+ layer3_multicast: true
+ state: present
+
+- name: Ensure ansible_test_3 BD exist
+ mso_schema_template_bd:
+ <<: *vrf_present
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ bd: ansible_test_3
+ vrf:
+ name: VRF3
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ layer3_multicast: true
+ state: present
+
+- name: Ensure ansible_test_4 BD exist
+ mso_schema_template_bd:
+ <<: *vrf_present
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ bd: ansible_test_4
+ vrf:
+ name: VRF4
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ layer3_multicast: true
+ state: present
+
+# ADD EPG
+- name: Add EPG (check_mode)
+ mso_schema_template_anp_epg: &epg_present
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ bd:
+ name: ansible_test_1
+ vrf:
+ name: VRF
+ state: present
+ check_mode: true
+ register: cm_add_epg
+
+- name: Verify cm_add_epg
+ assert:
+ that:
+ - cm_add_epg is changed
+ - cm_add_epg.previous == {}
+ - cm_add_epg.current.name == "ansible_test_1"
+ - cm_add_epg.current.vrfRef.templateName == "Template1"
+ - cm_add_epg.current.vrfRef.vrfName == "VRF"
+ - cm_add_epg.current.bdRef.templateName == "Template1"
+ - cm_add_epg.current.bdRef.bdName == "ansible_test_1"
+
+- name: Add EPG (normal mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_present
+ register: nm_add_epg
+
+- name: Verify nm_add_epg
+ assert:
+ that:
+ - nm_add_epg is changed
+ - nm_add_epg.previous == {}
+ - nm_add_epg.current.name == "ansible_test_1"
+ - nm_add_epg.current.vrfRef.templateName == "Template1"
+ - nm_add_epg.current.vrfRef.vrfName == "VRF"
+ - nm_add_epg.current.bdRef.templateName == "Template1"
+ - nm_add_epg.current.bdRef.bdName == "ansible_test_1"
+ - cm_add_epg.current.vrfRef.schemaId == nm_add_epg.current.vrfRef.schemaId
+ - cm_add_epg.current.bdRef.schemaId == nm_add_epg.current.bdRef.schemaId
+
+- name: Add EPG again (check_mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_present
+ check_mode: true
+ register: cm_add_epg_again
+
+- name: Verify cm_add_epg_again
+ assert:
+ that:
+ - cm_add_epg_again is not changed
+ - cm_add_epg_again.current.name == cm_add_epg_again.previous.name == "ansible_test_1"
+ - cm_add_epg_again.current.vrfRef.templateName == cm_add_epg_again.previous.vrfRef.templateName == "Template1"
+ - cm_add_epg_again.current.vrfRef.vrfName == cm_add_epg_again.previous.vrfRef.vrfName == "VRF"
+ - cm_add_epg_again.current.bdRef.templateName == cm_add_epg_again.previous.bdRef.templateName == "Template1"
+ - cm_add_epg_again.current.bdRef.bdName == cm_add_epg_again.previous.bdRef.bdName == "ansible_test_1"
+ - cm_add_epg_again.previous.vrfRef.schemaId == cm_add_epg_again.current.vrfRef.schemaId
+ - cm_add_epg_again.previous.bdRef.schemaId == cm_add_epg_again.current.bdRef.schemaId
+
+
+- name: Add EPG again (normal mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_present
+ register: nm_add_epg_again
+
+- name: Verify nm_add_epg_again
+ assert:
+ that:
+ - nm_add_epg_again is not changed
+ - nm_add_epg_again.current.name == nm_add_epg_again.previous.name == "ansible_test_1"
+ - nm_add_epg_again.current.vrfRef.templateName == nm_add_epg_again.previous.vrfRef.templateName == "Template1"
+ - nm_add_epg_again.current.vrfRef.vrfName == nm_add_epg_again.previous.vrfRef.vrfName == "VRF"
+ - nm_add_epg_again.current.bdRef.templateName == nm_add_epg_again.previous.bdRef.templateName == "Template1"
+ - nm_add_epg_again.current.bdRef.bdName == nm_add_epg_again.previous.bdRef.bdName == "ansible_test_1"
+ - nm_add_epg_again.previous.vrfRef.schemaId == nm_add_epg_again.current.vrfRef.schemaId
+ - nm_add_epg_again.previous.bdRef.schemaId == nm_add_epg_again.current.bdRef.schemaId
+
+- name: Add EPG 2 (normal mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_present
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ epg: ansible_test_2
+
+- name: Add EPG 3 in template of schema 1(normal mode)
+ mso_schema_template_anp_epg: &epg_schema_1_template_2
+ <<: *epg_present
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ anp: ANP2
+ epg: ansible_test_3
+ bd:
+ name: ansible_test_3
+ template: Template 2
+ vrf:
+ name: VRF3
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ useg_epg: true
+ intra_epg_isolation: enforced
+ intersite_multicast_source: true
+ proxy_arp: true
+ preferred_group: true
+ subnets:
+ - subnet: 10.0.0.128/24
+ - subnet: 10.0.1.254/24
+ description: 1234567890
+ - subnet: 172.16.0.1/24
+ description: "My description for a subnet"
+ scope: public
+ shared: true
+ no_default_gateway: false
+ - ip: 192.168.0.254/24
+ description: "My description for a subnet"
+ scope: private
+ shared: false
+ no_default_gateway: true
+ register: nm_add_epg_3
+
+- name: Add EPG 4 in template3 of schema 2(normal mode)
+ mso_schema_template_anp_epg: &epg_schema_2_template_3
+ <<: *epg_present
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ anp: ANP3
+ epg: ansible_test_4
+ bd:
+ name: ansible_test_4
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ vrf:
+ name: VRF4
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ register: nm_add_epg_4
+
+- name: Verify nm_add_epg_3 and nm_add_epg_4
+ assert:
+ that:
+ - nm_add_epg_3 is changed
+ - nm_add_epg_4 is changed
+ - nm_add_epg_3.current.name == "ansible_test_3"
+ - nm_add_epg_4.current.name == "ansible_test_4"
+ - nm_add_epg_3.current.vrfRef.templateName == "Template2"
+ - nm_add_epg_4.current.vrfRef.templateName == "Template3"
+ - nm_add_epg_3.current.vrfRef.vrfName == "VRF3"
+ - nm_add_epg_4.current.vrfRef.vrfName == "VRF4"
+ - nm_add_epg_3.current.bdRef.templateName == "Template2"
+ - nm_add_epg_4.current.bdRef.templateName == "Template3"
+ - nm_add_epg_3.current.bdRef.bdName == "ansible_test_3"
+ - nm_add_epg_4.current.bdRef.bdName == "ansible_test_4"
+ - nm_add_epg_3.current.uSegEpg == true
+ - nm_add_epg_3.current.intraEpg == 'enforced'
+ - nm_add_epg_3.current.mCastSource == true
+ - nm_add_epg_3.current.proxyArp == true
+ - nm_add_epg_3.current.preferredGroup == true
+ - nm_add_epg_3.current.subnets[0].description == "10.0.0.128/24"
+ - nm_add_epg_3.current.subnets[0].ip == "10.0.0.128/24"
+ - nm_add_epg_3.current.subnets[0].noDefaultGateway == false
+ - nm_add_epg_3.current.subnets[0].scope == "private"
+ - nm_add_epg_3.current.subnets[0].shared == false
+ - nm_add_epg_3.current.subnets[1].description == "1234567890"
+ - nm_add_epg_3.current.subnets[1].ip == "10.0.1.254/24"
+ - nm_add_epg_3.current.subnets[1].noDefaultGateway == false
+ - nm_add_epg_3.current.subnets[1].scope == "private"
+ - nm_add_epg_3.current.subnets[1].shared == false
+ - nm_add_epg_3.current.subnets[2].description == "My description for a subnet"
+ - nm_add_epg_3.current.subnets[2].ip == "172.16.0.1/24"
+ - nm_add_epg_3.current.subnets[2].noDefaultGateway == false
+ - nm_add_epg_3.current.subnets[2].scope == "public"
+ - nm_add_epg_3.current.subnets[2].shared == true
+ - nm_add_epg_3.current.subnets[3].description == "My description for a subnet"
+ - nm_add_epg_3.current.subnets[3].ip == "192.168.0.254/24"
+ - nm_add_epg_3.current.subnets[3].noDefaultGateway == true
+ - nm_add_epg_3.current.subnets[3].scope == "private"
+ - nm_add_epg_3.current.subnets[3].shared == false
+
+# CHANGE EPG
+- name: Change EPG (check_mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_present
+ vrf:
+ name: VRF2
+ bd:
+ name: ansible_test_2
+ check_mode: true
+ register: cm_change_epg
+
+- name: Verify cm_change_epg
+ assert:
+ that:
+ - cm_change_epg is changed
+ - cm_change_epg.current.name == 'ansible_test_1'
+ - cm_change_epg.current.vrfRef.vrfName == 'VRF2'
+ - cm_change_epg.current.bdRef.templateName == cm_change_epg.current.vrfRef.templateName == "Template1"
+ - cm_change_epg.current.vrfRef.schemaId == cm_change_epg.previous.vrfRef.schemaId
+ - cm_change_epg.current.bdRef.bdName == 'ansible_test_2'
+ - cm_change_epg.current.bdRef.schemaId == cm_change_epg.previous.bdRef.schemaId
+
+- name: Change EPG (normal mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_present
+ vrf:
+ name: VRF2
+ bd:
+ name: ansible_test_2
+ output_level: debug
+ register: nm_change_epg
+
+- name: Verify nm_change_epg
+ assert:
+ that:
+ - nm_change_epg is changed
+ - nm_change_epg.current.name == 'ansible_test_1'
+ - nm_change_epg.current.vrfRef.vrfName == 'VRF2'
+ - nm_change_epg.current.bdRef.templateName == nm_change_epg.current.vrfRef.templateName == "Template1"
+ - nm_change_epg.current.vrfRef.schemaId == nm_change_epg.previous.vrfRef.schemaId
+ - nm_change_epg.current.bdRef.bdName == 'ansible_test_2'
+ - nm_change_epg.current.bdRef.schemaId == nm_change_epg.previous.bdRef.schemaId
+
+- name: Change EPG again (check_mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_present
+ vrf:
+ name: VRF2
+ bd:
+ name: ansible_test_2
+ check_mode: true
+ register: cm_change_epg_again
+
+- name: Verify cm_change_epg_again
+ assert:
+ that:
+ - cm_change_epg_again is not changed
+ - cm_change_epg_again.current.name == 'ansible_test_1'
+ - cm_change_epg_again.current.vrfRef.vrfName == 'VRF2'
+ - cm_change_epg_again.current.vrfRef.templateName == cm_change_epg_again.current.bdRef.templateName == "Template1"
+ - cm_change_epg_again.current.vrfRef.schemaId == cm_change_epg_again.previous.vrfRef.schemaId
+ - cm_change_epg_again.current.bdRef.bdName == 'ansible_test_2'
+ - cm_change_epg_again.current.bdRef.schemaId == cm_change_epg_again.previous.bdRef.schemaId
+
+- name: Change EPG again (normal mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_present
+ vrf:
+ name: VRF2
+ bd:
+ name: ansible_test_2
+ register: nm_change_epg_again
+
+- name: Verify nm_change_epg_again
+ assert:
+ that:
+ - nm_change_epg_again is not changed
+ - nm_change_epg_again.current.name == 'ansible_test_1'
+ - nm_change_epg_again.current.vrfRef.vrfName == 'VRF2'
+ - nm_change_epg_again.current.vrfRef.templateName == nm_change_epg_again.current.bdRef.templateName == "Template1"
+ - nm_change_epg_again.current.vrfRef.schemaId == nm_change_epg_again.previous.vrfRef.schemaId
+ - nm_change_epg_again.current.bdRef.bdName == 'ansible_test_2'
+ - nm_change_epg_again.current.bdRef.schemaId == nm_change_epg_again.previous.bdRef.schemaId
+
+- name: Change EPG to VRF in different template (normal mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_schema_1_template_2
+ vrf:
+ name: VRF
+ template: Template 1
+ bd:
+ name: ansible_test_1
+ template: Template 1
+ register: nm_change_epg_vrf3
+
+- name: Change EPG 4 to VRF (normal mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_schema_2_template_3
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ anp: ANP3
+ epg: ansible_test_4
+ vrf:
+ name: VRF
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ bd:
+ name: ansible_test_1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ register: nm_change_epg_vrf4
+
+- name: Verify nm_change_epg_vrf3 and nm_change_epg_vrf4
+ assert:
+ that:
+ - nm_change_epg_vrf3 is changed
+ - nm_change_epg_vrf3.current.name == 'ansible_test_3'
+ - nm_change_epg_vrf4.current.name == 'ansible_test_4'
+ - nm_change_epg_vrf3.current.vrfRef.vrfName == 'VRF'
+ - nm_change_epg_vrf3.current.bdRef.bdName == 'ansible_test_1'
+ - nm_change_epg_vrf3.current.vrfRef.templateName == nm_change_epg_vrf3.current.bdRef.templateName == "Template1"
+ - nm_change_epg_vrf4.current.vrfRef.vrfName == 'VRF'
+ - nm_change_epg_vrf4.current.bdRef.bdName == 'ansible_test_1'
+ - nm_change_epg_vrf4.current.vrfRef.templateName == nm_change_epg_vrf4.current.bdRef.templateName == "Template1"
+
+- name: Change EPG 1 settings(normal mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_present
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ useg_epg: true
+ intra_epg_isolation: enforced
+ intersite_multicast_source: true
+ proxy_arp: true
+ preferred_group: true
+ subnets:
+ - subnet: 10.1.0.128/24
+ - subnet: 10.1.1.254/24
+ description: 1234567890
+ - subnet: 172.17.0.1/24
+ description: "My description for a subnet"
+ scope: public
+ shared: true
+ no_default_gateway: false
+ - ip: 192.168.1.254/24
+ description: "My description for a subnet"
+ scope: private
+ shared: false
+ no_default_gateway: true
+ register: nm_change_epg_1_settings
+
+- name: Change EPG 1 subnets (normal mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_present
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ useg_epg: true
+ intra_epg_isolation: enforced
+ intersite_multicast_source: true
+ proxy_arp: true
+ preferred_group: true
+ subnets:
+ - subnet: 10.1.0.127/24
+ - subnet: 172.17.0.1/24
+ description: "New description for a subnet"
+ scope: private
+ shared: false
+ no_default_gateway: false
+ - ip: 192.168.1.254/24
+ description: "My description for a subnet"
+ scope: private
+ shared: false
+ no_default_gateway: false
+ register: nm_change_epg_1_subnets
+
+- name: Verify nm_change_epg_1_subnets
+ assert:
+ that:
+ - nm_change_epg_1_settings is changed
+ - nm_change_epg_1_settings.current.name == "ansible_test_1"
+ - nm_change_epg_1_settings.current.vrfRef.templateName == nm_change_epg_1_settings.current.bdRef.templateName == "Template1"
+ - nm_change_epg_1_settings.current.vrfRef.vrfName == "VRF"
+ - nm_change_epg_1_settings.current.bdRef.bdName == "ansible_test_1"
+ - nm_change_epg_1_settings.current.uSegEpg == true
+ - nm_change_epg_1_settings.current.intraEpg == 'enforced'
+ - nm_change_epg_1_settings.current.mCastSource == true
+ - nm_change_epg_1_settings.current.proxyArp == true
+ - nm_change_epg_1_settings.current.preferredGroup == true
+ - nm_change_epg_1_settings.current.subnets[0].description == "10.1.0.128/24"
+ - nm_change_epg_1_settings.current.subnets[0].ip == "10.1.0.128/24"
+ - nm_change_epg_1_settings.current.subnets[0].noDefaultGateway == false
+ - nm_change_epg_1_settings.current.subnets[0].scope == "private"
+ - nm_change_epg_1_settings.current.subnets[0].shared == false
+ - nm_change_epg_1_settings.current.subnets[1].description == "1234567890"
+ - nm_change_epg_1_settings.current.subnets[1].ip == "10.1.1.254/24"
+ - nm_change_epg_1_settings.current.subnets[1].noDefaultGateway == false
+ - nm_change_epg_1_settings.current.subnets[1].scope == "private"
+ - nm_change_epg_1_settings.current.subnets[1].shared == false
+ - nm_change_epg_1_settings.current.subnets[2].description == "My description for a subnet"
+ - nm_change_epg_1_settings.current.subnets[2].ip == "172.17.0.1/24"
+ - nm_change_epg_1_settings.current.subnets[2].noDefaultGateway == false
+ - nm_change_epg_1_settings.current.subnets[2].scope == "public"
+ - nm_change_epg_1_settings.current.subnets[2].shared == true
+ - nm_change_epg_1_settings.current.subnets[3].description == "My description for a subnet"
+ - nm_change_epg_1_settings.current.subnets[3].ip == "192.168.1.254/24"
+ - nm_change_epg_1_settings.current.subnets[3].noDefaultGateway == true
+ - nm_change_epg_1_settings.current.subnets[3].scope == "private"
+ - nm_change_epg_1_settings.current.subnets[3].shared == false
+ - nm_change_epg_1_subnets is changed
+ - nm_change_epg_1_subnets.current.subnets | length == 3
+ - nm_change_epg_1_subnets.current.name == "ansible_test_1"
+ - nm_change_epg_1_subnets.current.vrfRef.templateName == nm_change_epg_1_subnets.current.bdRef.templateName == "Template1"
+ - nm_change_epg_1_subnets.current.vrfRef.vrfName == "VRF"
+ - nm_change_epg_1_subnets.current.bdRef.bdName == "ansible_test_1"
+ - nm_change_epg_1_subnets.current.uSegEpg == true
+ - nm_change_epg_1_subnets.current.intraEpg == 'enforced'
+ - nm_change_epg_1_subnets.current.mCastSource == true
+ - nm_change_epg_1_subnets.current.proxyArp == true
+ - nm_change_epg_1_subnets.current.preferredGroup == true
+ - nm_change_epg_1_subnets.current.subnets[0].description == "10.1.0.127/24"
+ - nm_change_epg_1_subnets.current.subnets[0].ip == "10.1.0.127/24"
+ - nm_change_epg_1_subnets.current.subnets[0].noDefaultGateway == false
+ - nm_change_epg_1_subnets.current.subnets[0].scope == "private"
+ - nm_change_epg_1_subnets.current.subnets[0].shared == false
+ - nm_change_epg_1_subnets.current.subnets[1].description == "New description for a subnet"
+ - nm_change_epg_1_subnets.current.subnets[1].ip == "172.17.0.1/24"
+ - nm_change_epg_1_subnets.current.subnets[1].noDefaultGateway == false
+ - nm_change_epg_1_subnets.current.subnets[1].scope == "private"
+ - nm_change_epg_1_subnets.current.subnets[1].shared == false
+ - nm_change_epg_1_subnets.current.subnets[2].description == "My description for a subnet"
+ - nm_change_epg_1_subnets.current.subnets[2].ip == "192.168.1.254/24"
+ - nm_change_epg_1_subnets.current.subnets[2].noDefaultGateway == false
+ - nm_change_epg_1_subnets.current.subnets[2].scope == "private"
+ - nm_change_epg_1_subnets.current.subnets[2].shared == false
+
+
+# # QUERY ALL EPGs
+- name: Query all EPGs in an ANP (check_mode)
+ mso_schema_template_anp_epg: &epg_query
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ state: query
+ check_mode: true
+ register: cm_query_all_epgs
+
+- name: Query all EPGs (normal mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_query
+ register: nm_query_all_epgs
+
+- name: Verify query_all_epgs
+ assert:
+ that:
+ - cm_query_all_epgs is not changed
+ - nm_query_all_epgs is not changed
+ - cm_query_all_epgs.current | length == nm_query_all_epgs.current | length == 2
+
+
+# QUERY AN EPG
+- name: Query EPG 1 (check_mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_query
+ epg: ansible_test_1
+ check_mode: true
+ register: cm_query_epg_1
+
+- name: Query EPG 1 (normal mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_query
+ epg: ansible_test_1
+ register: nm_query_epg_1
+
+- name: Query EPG 3 (normal mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_query
+ template: Template 2
+ anp: ANP2
+ epg: ansible_test_3
+ register: nm_query_epg_3
+
+- name: Query EPG 4 (normal mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_query
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ anp: ANP3
+ epg: ansible_test_4
+ register: nm_query_epg_4
+
+- name: Verify query_epg_x
+ assert:
+ that:
+ - cm_query_epg_1 is not changed
+ - nm_query_epg_1 is not changed
+ - nm_query_epg_3 is not changed
+ - nm_query_epg_4 is not changed
+ - nm_query_epg_1.current.subnets | length == 3
+ - nm_query_epg_1.current.name == "ansible_test_1"
+ - nm_query_epg_1.current.vrfRef.templateName == nm_query_epg_1.current.bdRef.templateName == "Template1"
+ - nm_query_epg_1.current.vrfRef.vrfName == "VRF"
+ - nm_query_epg_1.current.bdRef.bdName == "ansible_test_1"
+ - nm_query_epg_1.current.uSegEpg == true
+ - nm_query_epg_1.current.intraEpg == 'enforced'
+ - nm_query_epg_1.current.mCastSource == true
+ - nm_query_epg_1.current.proxyArp == true
+ - nm_query_epg_1.current.preferredGroup == true
+ - nm_query_epg_1.current.subnets[0].description == "10.1.0.127/24"
+ - nm_query_epg_1.current.subnets[0].ip == "10.1.0.127/24"
+ - nm_query_epg_1.current.subnets[0].noDefaultGateway == false
+ - nm_query_epg_1.current.subnets[0].scope == "private"
+ - nm_query_epg_1.current.subnets[0].shared == false
+ - nm_query_epg_1.current.subnets[1].description == "New description for a subnet"
+ - nm_query_epg_1.current.subnets[1].ip == "172.17.0.1/24"
+ - nm_query_epg_1.current.subnets[1].noDefaultGateway == false
+ - nm_query_epg_1.current.subnets[1].scope == "private"
+ - nm_query_epg_1.current.subnets[1].shared == false
+ - nm_query_epg_1.current.subnets[2].description == "My description for a subnet"
+ - nm_query_epg_1.current.subnets[2].ip == "192.168.1.254/24"
+ - nm_query_epg_1.current.subnets[2].noDefaultGateway == false
+ - nm_query_epg_1.current.subnets[2].scope == "private"
+ - nm_query_epg_1.current.subnets[2].shared == false
+ - nm_query_epg_3.current.name == "ansible_test_3"
+ - nm_query_epg_4.current.name == "ansible_test_4"
+ - nm_query_epg_3.current.vrfRef.templateName == nm_query_epg_4.current.vrfRef.templateName == "Template1"
+ - nm_query_epg_3.current.vrfRef.vrfName == nm_query_epg_4.current.vrfRef.vrfName == "VRF"
+ - nm_query_epg_3.current.bdRef.templateName == nm_query_epg_4.current.bdRef.templateName == "Template1"
+ - nm_query_epg_3.current.bdRef.bdName == nm_query_epg_4.current.bdRef.bdName == "ansible_test_1"
+ - nm_query_epg_3.current.uSegEpg == true
+ - nm_query_epg_3.current.intraEpg == 'enforced'
+ - nm_query_epg_3.current.mCastSource == true
+ - nm_query_epg_3.current.proxyArp == true
+ - nm_query_epg_3.current.preferredGroup == true
+ - nm_query_epg_3.current.subnets[0].description == "10.0.0.128/24"
+ - nm_query_epg_3.current.subnets[0].ip == "10.0.0.128/24"
+ - nm_query_epg_3.current.subnets[0].noDefaultGateway == false
+ - nm_query_epg_3.current.subnets[0].scope == "private"
+ - nm_query_epg_3.current.subnets[0].shared == false
+ - nm_query_epg_3.current.subnets[1].description == "1234567890"
+ - nm_query_epg_3.current.subnets[1].ip == "10.0.1.254/24"
+ - nm_query_epg_3.current.subnets[1].noDefaultGateway == false
+ - nm_query_epg_3.current.subnets[1].scope == "private"
+ - nm_query_epg_3.current.subnets[1].shared == false
+ - nm_query_epg_3.current.subnets[2].description == "My description for a subnet"
+ - nm_query_epg_3.current.subnets[2].ip == "172.16.0.1/24"
+ - nm_query_epg_3.current.subnets[2].noDefaultGateway == false
+ - nm_query_epg_3.current.subnets[2].scope == "public"
+ - nm_query_epg_3.current.subnets[2].shared == true
+ - nm_query_epg_3.current.subnets[3].description == "My description for a subnet"
+ - nm_query_epg_3.current.subnets[3].ip == "192.168.0.254/24"
+ - nm_query_epg_3.current.subnets[3].noDefaultGateway == true
+ - nm_query_epg_3.current.subnets[3].scope == "private"
+ - nm_query_epg_3.current.subnets[3].shared == false
+
+# REMOVE EPG
+- name: Remove EPG (check_mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_present
+ state: absent
+ check_mode: true
+ register: cm_remove_epg
+
+- name: Verify cm_remove_epg
+ assert:
+ that:
+ - cm_remove_epg is changed
+ - cm_remove_epg.current == {}
+
+- name: Remove EPG (normal mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_present
+ state: absent
+ register: nm_remove_epg
+
+- name: Verify nm_remove_epg
+ assert:
+ that:
+ - nm_remove_epg is changed
+ - nm_remove_epg.current == {}
+
+- name: Remove EPG again (check_mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_present
+ state: absent
+ check_mode: true
+ register: cm_remove_epg_again
+
+- name: Verify cm_remove_epg_again
+ assert:
+ that:
+ - cm_remove_epg_again is not changed
+ - cm_remove_epg_again.current == {}
+
+- name: Remove EPG again (normal mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_present
+ state: absent
+ register: nm_remove_epg_again
+
+- name: Verify nm_remove_epg_again
+ assert:
+ that:
+ - nm_remove_epg_again is not changed
+ - nm_remove_epg_again.current == {}
+
+- name: Remove EPG (normal mode)
+ mso_schema_template_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ state: absent
+ ignore_errors: true
+
+# Add EPG when MSO version >= 3.3
+- name: Execute tasks only for MSO version >= 3.3
+ when:
+ - version.current.version is version('3.3', '>=')
+ block:
+ - name: Add EPG (for version greater than 3.3)
+ mso_schema_template_anp_epg: &new_epg
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ description: 'Description of ANP EPG'
+ state: present
+ register: add_epg_desc
+
+ - name: Verify Add description
+ assert:
+ that:
+ - add_epg_desc is changed
+ - add_epg_desc.current.description == "Description of ANP EPG"
+
+ - name: Remove EPG (for version greater than 3.3)
+ mso_schema_template_anp_epg:
+ <<: *new_epg
+ state: absent
+
+- name: Execute tasks only for MSO version >= 4.0
+ when:
+ - version.current.version is version('4.0', '>=')
+ block:
+ - name: Add EPG service type parameters (for version greater than 4.0)
+ mso_schema_template_anp_epg:
+ <<: *new_epg
+ epg_type: 'service'
+ deployment_type: 'third_party'
+ service_type: 'Azure-Storage'
+ access_type: 'private'
+ register: service_type_epg
+
+ - name: Add EPG service type parameters (for version greater than 4.0) with error
+ mso_schema_template_anp_epg:
+ <<: *new_epg
+ epg_type: 'service'
+ deployment_type: 'third_party'
+ service_type: 'Azure-Storage'
+ access_type: 'public_and_private'
+ ignore_errors: true
+ register: service_type_epg_error
+
+ - name: Verify service type error
+ assert:
+ that:
+ - service_type_epg_error.msg == "MSO Error 400{{':'}} EPG{{':'}} ansible_test_1 in Schema{{':'}} ansible_test , Template{{':'}} Template1 DeploymentType{{':'}} saas, AccessType {{':'}}publicAndPrivateType, Combination is not supported"
+
+# Add EPG when MSO version >= 3.3 and < 4.0
+- name: Execute tasks only for MSO version >= 3.3 and < 4.0
+ when:
+ - version.current.version is version('3.3', '>=')
+ - version.current.version is version('4.0', '<')
+ block:
+ - name: Add EPG service type parameters (for version greater than 3.3 and less than 4.0)
+ mso_schema_template_anp_epg:
+ <<: *new_epg
+ epg_type: 'service'
+ deployment_type: 'third_party'
+ service_type: 'Azure-Storage'
+ access_type: 'public_and_private'
+ register: add_epg
+
+ - name: Get Validation status for service type parameters
+ mso_schema_validate:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ state: query
+ ignore_errors: true
+ register: query_validate
+
+ - name: Verify validation
+ assert:
+ that:
+ - query_validate is not changed
+ - query_validate.msg == "MSO Error 400{{':'}} Bad Request{{':'}} Patch Failed, Received{{':'}} For Deployment type 'Third-party' access type 'PublicAndPrivate' is not supported exception while trying to update schema"
+
+ - name: Add EPG service type parameters (for version greater than 3.3 and less than 4.0)
+ mso_schema_template_anp_epg:
+ <<: *new_epg
+ epg_type: 'service'
+ deployment_type: 'cloud_native'
+ service_type: 'Azure-Storage'
+ access_type: 'public'
+ register: add_epg
+
+ - name: Get Validation status for service type parameters
+ mso_schema_validate:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ state: query
+ ignore_errors: true
+ register: query_validate
+
+ - name: Verify validation
+ assert:
+ that:
+ - query_validate is not changed
+ - query_validate.current.result == "true"
+
+ - name: Add new EPG service type parameters (for version greater than 3.3 and less than 4.0)
+ mso_schema_template_anp_epg:
+ <<: *new_epg
+ epg_type: 'service'
+ deployment_type: 'third_party'
+ service_type: 'Azure-Storage'
+ access_type: 'private'
+ register: add_epg
+
+ - name: Get Validation status for service type parameters
+ mso_schema_validate:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ state: query
+ ignore_errors: true
+ register: query_validate
+
+ - name: Verify validation
+ assert:
+ that:
+ - query_validate is not changed
+ - query_validate.current.result == "true"
+
+# Add QoS level to EPG
+- name: Add EPG (for version greater than 3.1)
+ mso_schema_template_anp_epg:
+ <<: *epg_present
+ name: ansible_test_5
+ qos_level: 'level2'
+ register: add_epg
+ when: version.current.version is version('3.1', '>=')
+
+- name: Verify Add contract for version greater than 3.1
+ assert:
+ that:
+ - add_epg is changed
+ when: version.current.version is version('3.1', '>=')
+
+# QUERY NON-EXISTING EPG
+- name: Query non-existing EPG (check_mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_query
+ epg: non_existing_epg
+ check_mode: true
+ ignore_errors: true
+ register: cm_query_non_epg
+
+- name: Query non-existing EPG (normal mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_query
+ epg: non_existing_epg
+ ignore_errors: true
+ register: nm_query_non_epg
+
+- name: Verify query_non_epg
+ assert:
+ that:
+ - cm_query_non_epg is not changed
+ - nm_query_non_epg is not changed
+ - cm_query_non_epg == nm_query_non_epg
+ - cm_query_non_epg.msg == nm_query_non_epg.msg == "EPG 'non_existing_epg' not found"
+
+# QUERY NON-EXISTING ANP
+- name: Query non-existing ANP (check_mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_query
+ anp: non_existing_anp
+ check_mode: true
+ ignore_errors: true
+ register: cm_query_non_anp
+
+- name: Query non-existing ANP (normal mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_query
+ anp: non_existing_anp
+ ignore_errors: true
+ register: nm_query_non_anp
+
+- name: Verify query_non_anp
+ assert:
+ that:
+ - cm_query_non_anp is not changed
+ - nm_query_non_anp is not changed
+ - cm_query_non_anp == nm_query_non_anp
+ - cm_query_non_anp.msg == nm_query_non_anp.msg == "Provided anp 'non_existing_anp' does not exist. Existing anps{{':'}} ANP"
+
+# USE A NON-EXISTING STATE
+- name: Non-existing state for EPG (check_mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_query
+ epg: ansible_test_2
+ state: non-existing-state
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_state
+
+- name: Non-existing state for EPG (normal_mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_query
+ epg: ansible_test_2
+ state: non-existing-state
+ ignore_errors: true
+ register: nm_non_existing_state
+
+- name: Verify non_existing_state
+ assert:
+ that:
+ - cm_non_existing_state is not changed
+ - nm_non_existing_state is not changed
+ - cm_non_existing_state == nm_non_existing_state
+ - cm_non_existing_state.msg == nm_non_existing_state.msg == "value of state must be one of{{':'}} absent, present, query, got{{':'}} non-existing-state"
+
+# USE A NON-EXISTING SCHEMA
+- name: Non-existing schema for EPG (check_mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_present
+ schema: non-existing-schema
+ epg: ansible_test_2
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_schema
+
+- name: Non-existing schema for EPG (normal_mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_present
+ schema: non-existing-schema
+ epg: ansible_test_2
+ ignore_errors: true
+ register: nm_non_existing_schema
+
+- name: Verify non_existing_schema
+ assert:
+ that:
+ - cm_non_existing_schema is not changed
+ - nm_non_existing_schema is not changed
+ - cm_non_existing_schema == nm_non_existing_schema
+ - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non-existing-schema' does not exist."
+
+- name: Non-existing BD schema for EPG (check_mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_present
+ epg: ansible_test_2
+ bd:
+ name: ansible_test_1
+ schema: non-existing-schema
+ template: Template 1
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_bd_schema
+
+- name: Non-existing BD schema for EPG (normal_mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_present
+ epg: ansible_test_2
+ bd:
+ name: ansible_test_1
+ schema: non-existing-schema
+ template: Template 1
+ ignore_errors: true
+ register: nm_non_existing_bd_schema
+
+- name: Verify non_existing_bd_schema
+ assert:
+ that:
+ - cm_non_existing_bd_schema is not changed
+ - nm_non_existing_bd_schema is not changed
+ - cm_non_existing_bd_schema == nm_non_existing_bd_schema
+ - cm_non_existing_bd_schema.msg == nm_non_existing_bd_schema.msg == "Referenced schema 'non-existing-schema' in bdref does not exist"
+
+- name: Non-existing VRF schema for EPG (check_mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_present
+ epg: ansible_test_2
+ vrf:
+ name: VRF
+ schema: non-existing-schema
+ template: Template 1
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_vrf_schema
+
+- name: Non-existing VRF schema for EPG (normal_mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_present
+ epg: ansible_test_2
+ vrf:
+ name: VRF
+ schema: non-existing-schema
+ template: Template 1
+ ignore_errors: true
+ register: nm_non_existing_vrf_schema
+
+- name: Verify non_existing_vrf_schema
+ assert:
+ that:
+ - cm_non_existing_vrf_schema is not changed
+ - nm_non_existing_vrf_schema is not changed
+ - cm_non_existing_vrf_schema == nm_non_existing_vrf_schema
+ - cm_non_existing_vrf_schema.msg == nm_non_existing_vrf_schema.msg == "Referenced schema 'non-existing-schema' in vrfref does not exist"
+
+# USE A NON-EXISTING TEMPLATE
+- name: Non-existing template for EPG (check_mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_present
+ template: non-existing-template
+ epg: ansible_test_2
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_template
+
+- name: Non-existing template for EPG (normal_mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_present
+ template: non-existing-template
+ epg: ansible_test_2
+ ignore_errors: true
+ register: nm_non_existing_template
+
+- name: Verify non_existing_template
+ assert:
+ that:
+ - cm_non_existing_template is not changed
+ - nm_non_existing_template is not changed
+ - cm_non_existing_template == nm_non_existing_template
+ - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non-existing-template' does not exist. Existing templates{{':'}} Template1, Template2"
+
+# Checking if contract are removed after re-applying an EPG. (#13 | #62137)
+- name: Reset EPG 3 in template of schema 1 to avoid cyclic circles (normal mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_schema_1_template_2
+ register: nm_add_epg_3
+
+- name: Add Contracts to EPG 2
+ mso_schema_template_anp_epg_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_2
+ contract:
+ name: '{{ item.name }}'
+ template: '{{ item.template }}'
+ type: '{{ item.type }}'
+ state: present
+ loop:
+ - { name: Contract1, template: Template 1, type: consumer }
+ - { name: Contract1, template: Template 1, type: provider }
+ - { name: Contract2, template: Template 2, type: consumer }
+ - { name: Contract2, template: Template 2, type: provider }
+
+- name: Query EPG 2
+ mso_schema_template_anp_epg:
+ <<: *epg_query
+ epg: ansible_test_2
+ register: nm_query_contract_epg
+
+- name: Verify that 4 contracts are in EPG 2 using nm_query_contract_epg
+ assert:
+ that:
+ - nm_query_contract_epg.current.contractRelationships | length == 4
+
+- name: Add EPG 2 again (normal_mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_present
+ epg: ansible_test_2
+ register: nm_add_epg_2_again
+
+- name: Verify that EPG 2 didn't change
+ assert:
+ that:
+ - nm_add_epg_2_again is not changed
+
+- name: Query EPG 2
+ mso_schema_template_anp_epg:
+ <<: *epg_query
+ epg: ansible_test_2
+ register: nm_query_contract_epg
+
+- name: Verify that 4 contracts are in EPG 2 using nm_query_contract_epg
+ assert:
+ that:
+ - nm_query_contract_epg.current.contractRelationships | length == 4
+
+# Checking if issue when querying EPG and VRF is not defined (#66)
+- name: Add new test EPG 3 (normal mode)
+ mso_schema_template_anp_epg: &epg_present_2
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_3
+ bd:
+ name: ansible_test_1
+ register: nm_add_epg_3
+
+- name: Verify nm_add_epg_3
+ assert:
+ that:
+ - nm_add_epg_3 is changed
+ - nm_add_epg_3.current.name == 'ansible_test_3'
+ - "'vrfRef' not in nm_add_epg_3.current"
+
+- name: Query test EPG 3
+ mso_schema_template_anp_epg:
+ <<: *epg_present_2
+ register: nm_query_epg_3
+
+- name: Verify nm_query_epg_3
+ assert:
+ that:
+ - nm_query_epg_3 is not changed
+ - nm_query_epg_3.current.name == 'ansible_test_3'
+ - "'vrfRef' not in nm_query_epg_3.current"
+
+# Checking if modifying an EPG with existing contracts throw an MSO error. (#82)
+- name: Change EPG 2 to add VRF (normal_mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_present
+ epg: ansible_test_2
+ vrf:
+ name: VRF2
+ bd:
+ name: ansible_test_2
+ register: nm_change_epg_2_vrf
+
+- name: Verify that EPG 2 did change
+ assert:
+ that:
+ - nm_change_epg_2_vrf is changed
+ - nm_change_epg_2_vrf.current.vrfRef.templateName == "Template1"
+ - nm_change_epg_2_vrf.current.vrfRef.vrfName == "VRF2"
+ - nm_change_epg_2_vrf.current.bdRef.bdName == "ansible_test_2"
+
+- name: Query EPG 2
+ mso_schema_template_anp_epg:
+ <<: *epg_query
+ epg: ansible_test_2
+ register: nm_query_contract_epg_2
+
+- name: Verify that 4 contracts are in EPG 2 using nm_query_contract_epg_2
+ assert:
+ that:
+ - nm_query_contract_epg_2.current.contractRelationships | length == 4 \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg_contract/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg_contract/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg_contract/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg_contract/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg_contract/tasks/main.yml
new file mode 100644
index 000000000..0027cfda4
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg_contract/tasks/main.yml
@@ -0,0 +1,620 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case)
+#
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(false) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+# CLEAN ENVIRONMENT
+# - name: Ensure site exist
+# mso_site: &site_present
+# host: '{{ mso_hostname }}'
+# username: '{{ mso_username }}'
+# password: '{{ mso_password }}'
+# validate_certs: '{{ mso_validate_certs | default(false) }}'
+# use_ssl: '{{ mso_use_ssl | default(true) }}'
+# use_proxy: '{{ mso_use_proxy | default(true) }}'
+# output_level: '{{ mso_output_level | default("info") }}'
+# site: '{{ mso_site | default("ansible_test") }}'
+# apic_username: '{{ apic_username }}'
+# apic_password: '{{ apic_password }}'
+# apic_site_id: '{{ apic_site_id | default(101) }}'
+# urls:
+# - https://{{ apic_hostname }}
+# state: present
+
+- name: Remove schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Ensure tenant ansible_test exist
+ mso_tenant: &tenant_present
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ # sites:
+ # - '{{ mso_site | default("ansible_test") }}'
+ state: present
+
+- name: Ensure schema 1 with Template 1 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: Template 1
+ state: present
+
+- name: Ensure schema 1 with Template 2 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: Template 2
+ state: present
+
+- name: Ensure schema 2 with Template 3 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template 3
+ state: present
+
+- name: Ensure ANP exist
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ item.schema }}'
+ template: '{{ item.template }}'
+ anp: ANP
+ state: present
+ loop:
+ - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 1' }
+ - { schema: '{{ mso_schema | default("ansible_test") }}_2', template: 'Template 3' }
+
+- name: Ensure Filter 1 exist
+ mso_schema_template_filter_entry:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ filter: Filter1
+ entry: Filter1-Entry
+ state: present
+
+- name: Ensure Contract1 exist
+ mso_schema_template_contract_filter: &contract_present
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract1
+ filter: Filter1
+ filter_schema: '{{ mso_schema | default("ansible_test") }}'
+ filter_template: Template 1
+ state: present
+
+- name: Ensure Filter 2 exist
+ mso_schema_template_filter_entry:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ filter: Filter2
+ entry: Filter2-Entry
+ state: present
+
+- name: Ensure Contract2 exist
+ mso_schema_template_contract_filter: &contract2_present
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ contract: Contract2
+ filter: Filter2
+ filter_schema: '{{ mso_schema | default("ansible_test") }}'
+ filter_template: Template 2
+ state: present
+
+- name: Ensure EPGs exist
+ mso_schema_template_anp_epg:
+ <<: *mso_info
+ schema: '{{ item.schema }}'
+ template: '{{ item.template }}'
+ anp: ANP
+ epg: '{{ item.epg }}'
+ state: present
+ loop:
+ - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 1', epg: 'ansible_test_1' }
+ - { schema: '{{ mso_schema | default("ansible_test") }}_2', template: 'Template 3', epg: 'ansible_test_3' }
+
+# ADD Contract to EPG
+- name: Add Contract1 to EPG (check_mode)
+ mso_schema_template_anp_epg_contract: &contract_epg_present
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ contract:
+ name: Contract1
+ type: consumer
+ state: present
+ check_mode: true
+ register: cm_add_contract_rel
+
+- name: Verify cm_add_contract_rel
+ assert:
+ that:
+ - cm_add_contract_rel is changed
+ - cm_add_contract_rel.previous == {}
+ - cm_add_contract_rel.current.contractRef.templateName == "Template1"
+ - cm_add_contract_rel.current.contractRef.contractName == "Contract1"
+ - cm_add_contract_rel.current.relationshipType == "consumer"
+
+- name: Add Contract to EPG (normal mode)
+ mso_schema_template_anp_epg_contract:
+ <<: *contract_epg_present
+ register: nm_add_contract_rel
+
+- name: Verify nm_add_contract_rel
+ assert:
+ that:
+ - nm_add_contract_rel is changed
+ - nm_add_contract_rel.previous == {}
+ - nm_add_contract_rel.current.contractRef.templateName == "Template1"
+ - nm_add_contract_rel.current.contractRef.contractName == "Contract1"
+ - nm_add_contract_rel.current.relationshipType == "consumer"
+ - cm_add_contract_rel.current.contractRef.schemaId == nm_add_contract_rel.current.contractRef.schemaId
+
+- name: Add Contract to EPG again (check_mode)
+ mso_schema_template_anp_epg_contract:
+ <<: *contract_epg_present
+ check_mode: true
+ register: cm_add_contract_rel_again
+
+- name: Verify cm_add_contract_rel_again
+ assert:
+ that:
+ - cm_add_contract_rel_again is not changed
+ - cm_add_contract_rel_again.previous.contractRef.templateName == "Template1"
+ - cm_add_contract_rel_again.current.contractRef.templateName == "Template1"
+ - cm_add_contract_rel_again.previous.contractRef.contractName == "Contract1"
+ - cm_add_contract_rel_again.current.contractRef.contractName == "Contract1"
+ - cm_add_contract_rel_again.previous.relationshipType == "consumer"
+ - cm_add_contract_rel_again.current.relationshipType == "consumer"
+ - cm_add_contract_rel_again.previous.contractRef.schemaId == cm_add_contract_rel_again.current.contractRef.schemaId
+
+
+- name: Add Contract to EPG again (normal mode)
+ mso_schema_template_anp_epg_contract:
+ <<: *contract_epg_present
+ register: nm_add_contract_rel_again
+
+- name: Verify nm_add_contract_rel_again
+ assert:
+ that:
+ - nm_add_contract_rel_again is not changed
+ - nm_add_contract_rel_again.previous.contractRef.templateName == "Template1"
+ - nm_add_contract_rel_again.current.contractRef.templateName == "Template1"
+ - nm_add_contract_rel_again.previous.contractRef.contractName == "Contract1"
+ - nm_add_contract_rel_again.current.contractRef.contractName == "Contract1"
+ - nm_add_contract_rel_again.previous.relationshipType == "consumer"
+ - nm_add_contract_rel_again.current.relationshipType == "consumer"
+ - nm_add_contract_rel_again.previous.contractRef.schemaId == nm_add_contract_rel_again.current.contractRef.schemaId
+
+- name: Add Contract1 to EPG - provider (normal mode)
+ mso_schema_template_anp_epg_contract:
+ <<: *contract_epg_present
+ contract:
+ name: Contract1
+ type: provider
+ register: nm_add_contract1_rel_provider
+
+- name: Add Contract2 to EPG - consumer (normal mode)
+ mso_schema_template_anp_epg_contract:
+ <<: *contract_epg_present
+ contract:
+ name: Contract2
+ template: Template 2
+ type: consumer
+ register: nm_add_contract2_rel_consumer
+
+- name: Add Contract1 to EPG 3 - provider (normal mode)
+ mso_schema_template_anp_epg_contract:
+ <<: *contract_epg_present
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ anp: ANP
+ epg: ansible_test_3
+ contract:
+ name: Contract1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ type: provider
+ register: nm_add_contract3_rel_provider
+
+- name: Verify nm_add_contract1_rel_provider, nm_add_contract2_rel_consumer and nm_add_contract3_rel_provider
+ assert:
+ that:
+ - nm_add_contract1_rel_provider is changed
+ - nm_add_contract2_rel_consumer is changed
+ - nm_add_contract3_rel_provider is changed
+ - nm_add_contract1_rel_provider.current.contractRef.contractName == nm_add_contract3_rel_provider.current.contractRef.contractName == "Contract1"
+ - nm_add_contract2_rel_consumer.current.contractRef.contractName == "Contract2"
+ - nm_add_contract1_rel_provider.current.contractRef.templateName == nm_add_contract3_rel_provider.current.contractRef.templateName == "Template1"
+ - nm_add_contract2_rel_consumer.current.contractRef.templateName == "Template2"
+ - nm_add_contract1_rel_provider.current.contractRef.schemaId == nm_add_contract2_rel_consumer.current.contractRef.schemaId == nm_add_contract3_rel_provider.current.contractRef.schemaId
+ - nm_add_contract2_rel_consumer.current.relationshipType == "consumer"
+ - nm_add_contract1_rel_provider.current.relationshipType == nm_add_contract3_rel_provider.current.relationshipType == "provider"
+
+# # QUERY ALL Contract to EPG
+- name: Query all contract relationship for EPG (check_mode)
+ mso_schema_template_anp_epg_contract: &contract_epg_query
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ state: query
+ check_mode: true
+ register: cm_query_all_contract_rels
+
+- name: Query all contract relationship for EPG (normal mode)
+ mso_schema_template_anp_epg_contract:
+ <<: *contract_epg_query
+ register: nm_query_all_contract_rels
+
+- name: Verify query_all_contract_rels
+ assert:
+ that:
+ - cm_query_all_contract_rels is not changed
+ - nm_query_all_contract_rels is not changed
+ - cm_query_all_contract_rels.current | length == nm_query_all_contract_rels.current | length == 3
+
+
+# QUERY A Contract to EPG
+- name: Query Contract1 relationship for EPG - consumer (check_mode)
+ mso_schema_template_anp_epg_contract:
+ <<: *contract_epg_query
+ contract:
+ name: Contract1
+ type: consumer
+ check_mode: true
+ register: cm_query_contract1_consumer_rel
+
+- name: Query Contract1 relationship for EPG - consumer (normal mode)
+ mso_schema_template_anp_epg_contract:
+ <<: *contract_epg_query
+ contract:
+ name: Contract1
+ type: consumer
+ register: nm_query_contract1_consumer_rel
+
+- name: Query Contract1 relationship for EPG - provider (normal mode)
+ mso_schema_template_anp_epg_contract:
+ <<: *contract_epg_query
+ contract:
+ name: Contract1
+ type: provider
+ register: nm_query_contract1_provider_rel
+
+- name: Query Contract1 relationship for EPG - consumer (normal mode)
+ mso_schema_template_anp_epg_contract:
+ <<: *contract_epg_query
+ contract:
+ name: Contract2
+ template: Template 2
+ type: consumer
+ register: nm_query_contract2_consumer_rel
+
+- name: Query Contract1 relationship for EPG - provider (normal mode)
+ mso_schema_template_anp_epg_contract:
+ <<: *contract_epg_query
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ anp: ANP
+ epg: ansible_test_3
+ contract:
+ name: Contract1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ type: provider
+ register: nm_query_contract3_provider_rel
+
+- name: Verify query_contractX_YYYYY_rel
+ assert:
+ that:
+ - cm_query_contract1_consumer_rel is not changed
+ - nm_query_contract1_consumer_rel is not changed
+ - nm_query_contract1_provider_rel is not changed
+ - nm_query_contract2_consumer_rel is not changed
+ - nm_query_contract3_provider_rel is not changed
+ - cm_query_contract1_consumer_rel == nm_query_contract1_consumer_rel
+ - cm_query_contract1_consumer_rel.current.contractRef.contractName == nm_query_contract1_consumer_rel.current.contractRef.contractName == nm_query_contract1_provider_rel.current.contractRef.contractName == nm_query_contract3_provider_rel.current.contractRef.contractName == "Contract1"
+ - nm_query_contract2_consumer_rel.current.contractRef.contractName == "Contract2"
+ - cm_query_contract1_consumer_rel.current.contractRef.templateName == nm_query_contract1_consumer_rel.current.contractRef.templateName == nm_query_contract1_provider_rel.current.contractRef.templateName == nm_query_contract3_provider_rel.current.contractRef.templateName == "Template1"
+ - nm_query_contract2_consumer_rel.current.contractRef.templateName == "Template2"
+ - cm_query_contract1_consumer_rel.current.contractRef.schemaId == nm_query_contract1_consumer_rel.current.contractRef.schemaId == nm_query_contract1_provider_rel.current.contractRef.schemaId == nm_query_contract2_consumer_rel.current.contractRef.schemaId == nm_query_contract3_provider_rel.current.contractRef.schemaId
+ - cm_query_contract1_consumer_rel.current.relationshipType == nm_query_contract1_consumer_rel.current.relationshipType == nm_query_contract2_consumer_rel.current.relationshipType == "consumer"
+ - nm_query_contract1_provider_rel.current.relationshipType == nm_query_contract3_provider_rel.current.relationshipType == "provider"
+
+
+# REMOVE Contract to EPG
+- name: Remove Contract to EPG (check_mode)
+ mso_schema_template_anp_epg_contract:
+ <<: *contract_epg_present
+ state: absent
+ check_mode: true
+ register: cm_remove_contract_rel
+
+- name: Verify cm_remove_contract_rel
+ assert:
+ that:
+ - cm_remove_contract_rel is changed
+ - cm_remove_contract_rel.current == {}
+
+- name: Remove Contract to EPG (normal mode)
+ mso_schema_template_anp_epg_contract:
+ <<: *contract_epg_present
+ state: absent
+ register: nm_remove_contract_rel
+
+- name: Verify nm_remove_contract_rel
+ assert:
+ that:
+ - nm_remove_contract_rel is changed
+ - nm_remove_contract_rel.current == {}
+
+- name: Remove Contract to EPG again (check_mode)
+ mso_schema_template_anp_epg_contract:
+ <<: *contract_epg_present
+ state: absent
+ check_mode: true
+ register: cm_remove_contract_rel_again
+
+- name: Verify cm_remove_contract_rel_again
+ assert:
+ that:
+ - cm_remove_contract_rel_again is not changed
+ - cm_remove_contract_rel_again.current == {}
+
+- name: Remove Contract to EPG again (normal mode)
+ mso_schema_template_anp_epg_contract:
+ <<: *contract_epg_present
+ state: absent
+ register: nm_remove_contract_rel_again
+
+- name: Verify nm_remove_contract_rel_again
+ assert:
+ that:
+ - nm_remove_contract_rel_again is not changed
+ - nm_remove_contract_rel_again.current == {}
+
+
+# QUERY NON-EXISTING Contract to EPG
+- name: Query non-existing contract (check_mode)
+ mso_schema_template_anp_epg_contract:
+ <<: *contract_epg_query
+ contract:
+ name: non_existing_contract
+ type: provider
+ check_mode: true
+ ignore_errors: true
+ register: cm_query_non_contract
+
+- name: Query non-existing contract (normal mode)
+ mso_schema_template_anp_epg_contract:
+ <<: *contract_epg_query
+ contract:
+ name: non_existing_contract
+ type: provider
+ ignore_errors: true
+ register: nm_query_non_contract
+
+- name: Verify query_non_contract
+ assert:
+ that:
+ - cm_query_non_contract is not changed
+ - nm_query_non_contract is not changed
+ - cm_query_non_contract == nm_query_non_contract
+ - cm_query_non_contract.msg is match("Contract '/schemas/[0-9a-zA-Z]*/templates/Template1/contracts/non_existing_contract' not found")
+ - nm_query_non_contract.msg is match("Contract '/schemas/[0-9a-zA-Z]*/templates/Template1/contracts/non_existing_contract' not found")
+
+# QUERY NON-EXISTING EPG
+- name: Query non-existing EPG (check_mode)
+ mso_schema_template_anp_epg_contract:
+ <<: *contract_epg_query
+ epg: non_existing_epg
+ check_mode: true
+ ignore_errors: true
+ register: cm_query_non_epg
+
+- name: Query non-existing EPG (normal mode)
+ mso_schema_template_anp_epg_contract:
+ <<: *contract_epg_query
+ epg: non_existing_epg
+ ignore_errors: true
+ register: nm_query_non_epg
+
+- name: Verify query_non_epg
+ assert:
+ that:
+ - cm_query_non_epg is not changed
+ - nm_query_non_epg is not changed
+ - cm_query_non_epg == nm_query_non_epg
+ - cm_query_non_epg.msg == nm_query_non_epg.msg == "Provided epg 'non_existing_epg' does not exist. Existing epgs{{':'}} ansible_test_1"
+
+# QUERY NON-EXISTING ANP
+- name: Query non-existing ANP (check_mode)
+ mso_schema_template_anp_epg_contract:
+ <<: *contract_epg_query
+ anp: non_existing_anp
+ check_mode: true
+ ignore_errors: true
+ register: cm_query_non_anp
+
+- name: Query non-existing ANP (normal mode)
+ mso_schema_template_anp_epg_contract:
+ <<: *contract_epg_query
+ anp: non_existing_anp
+ ignore_errors: true
+ register: nm_query_non_anp
+
+- name: Verify query_non_anp
+ assert:
+ that:
+ - cm_query_non_anp is not changed
+ - nm_query_non_anp is not changed
+ - cm_query_non_anp == nm_query_non_anp
+ - cm_query_non_anp.msg == nm_query_non_anp.msg == "Provided anp 'non_existing_anp' does not exist. Existing anps{{':'}} ANP"
+
+# USE A NON-EXISTING STATE
+- name: Non-existing state for contract relationship (check_mode)
+ mso_schema_template_anp_epg_contract:
+ <<: *contract_epg_query
+ state: non-existing-state
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_state
+
+- name: Non-existing state for contract relationship (normal_mode)
+ mso_schema_template_anp_epg_contract:
+ <<: *contract_epg_query
+ state: non-existing-state
+ ignore_errors: true
+ register: nm_non_existing_state
+
+- name: Verify non_existing_state
+ assert:
+ that:
+ - cm_non_existing_state is not changed
+ - nm_non_existing_state is not changed
+ - cm_non_existing_state == nm_non_existing_state
+ - cm_non_existing_state.msg == nm_non_existing_state.msg == "value of state must be one of{{':'}} absent, present, query, got{{':'}} non-existing-state"
+
+# USE A NON-EXISTING SCHEMA
+- name: Non-existing schema for contract relationship (check_mode)
+ mso_schema_template_anp_epg_contract:
+ <<: *contract_epg_query
+ schema: non-existing-schema
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_schema
+
+- name: Non-existing schema for contract relationship (normal_mode)
+ mso_schema_template_anp_epg_contract:
+ <<: *contract_epg_query
+ schema: non-existing-schema
+ ignore_errors: true
+ register: nm_non_existing_schema
+
+- name: Verify non_existing_schema
+ assert:
+ that:
+ - cm_non_existing_schema is not changed
+ - nm_non_existing_schema is not changed
+ - cm_non_existing_schema == nm_non_existing_schema
+ - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non-existing-schema' does not exist."
+
+- name: Non-existing contract schema for contract relationship (check_mode)
+ mso_schema_template_anp_epg_contract:
+ <<: *contract_epg_query
+ contract:
+ name: Contract1
+ schema: non-existing-schema
+ template: Template 1
+ type: provider
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_contract_schema
+
+- name: Non-existing contract schema for contract relationship (normal_mode)
+ mso_schema_template_anp_epg_contract:
+ <<: *contract_epg_query
+ contract:
+ name: Contract1
+ schema: non-existing-schema
+ template: Template 1
+ type: provider
+ ignore_errors: true
+ register: nm_non_existing_contract_schema
+
+- name: Verify non_existing_contract_schema
+ assert:
+ that:
+ - cm_non_existing_contract_schema is not changed
+ - nm_non_existing_contract_schema is not changed
+ - cm_non_existing_contract_schema == nm_non_existing_contract_schema
+ - cm_non_existing_contract_schema.msg == nm_non_existing_contract_schema.msg == "Provided schema 'non-existing-schema' does not exist."
+
+# USE A NON-EXISTING TEMPLATE
+- name: Non-existing template for contract relationship (check_mode)
+ mso_schema_template_anp_epg_contract:
+ <<: *contract_epg_query
+ template: non-existing-template
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_template
+
+- name: Non-existing template for contract relationship (normal_mode)
+ mso_schema_template_anp_epg_contract:
+ <<: *contract_epg_query
+ template: non-existing-template
+ ignore_errors: true
+ register: nm_non_existing_template
+
+- name: Verify non_existing_template
+ assert:
+ that:
+ - cm_non_existing_template is not changed
+ - nm_non_existing_template is not changed
+ - cm_non_existing_template == nm_non_existing_template
+ - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non-existing-template' does not exist. Existing templates{{':'}} Template1, Template2"
+
+- name: Non-existing contract template for contract relationship (check_mode)
+ mso_schema_template_anp_epg_contract:
+ <<: *contract_epg_query
+ contract:
+ name: Contract1
+ template: non-existing-template
+ type: provider
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_contract_template
+
+- name: Non-existing contract template for contract relationship (normal_mode)
+ mso_schema_template_anp_epg_contract:
+ <<: *contract_epg_query
+ contract:
+ name: Contract1
+ template: non-existing-template
+ type: provider
+ ignore_errors: true
+ register: nm_non_existing_contract_template
+
+- name: Verify non_existing_contract_template
+ assert:
+ that:
+ - cm_non_existing_contract_template is not changed
+ - nm_non_existing_contract_template is not changed
+ - cm_non_existing_contract_template == nm_non_existing_contract_template
+ - cm_non_existing_contract_template.msg is match("Contract '/schemas/[0-9a-zA-Z]*/templates/non-existing-template/contracts/Contract1' not found")
+ - nm_non_existing_contract_template.msg is match("Contract '/schemas/[0-9a-zA-Z]*/templates/non-existing-template/contracts/Contract1' not found") \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg_selector/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg_selector/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg_selector/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg_selector/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg_selector/tasks/main.yml
new file mode 100644
index 000000000..013b96b71
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_anp_epg_selector/tasks/main.yml
@@ -0,0 +1,794 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case)
+#
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+# - name: Ensure site exist
+# mso_site: &site_present
+# host: '{{ mso_hostname }}'
+# username: '{{ mso_username }}'
+# password: '{{ mso_password }}'
+# validate_certs: '{{ mso_validate_certs | default(false) }}'
+# use_ssl: '{{ mso_use_ssl | default(true) }}'
+# use_proxy: '{{ mso_use_proxy | default(true) }}'
+# output_level: '{{ mso_output_level | default("info") }}'
+# site: '{{ mso_site | default("ansible_test") }}'
+# apic_username: '{{ apic_username }}'
+# apic_password: '{{ apic_password }}'
+# apic_site_id: '{{ apic_site_id | default(101) }}'
+# urls:
+# - https://{{ apic_hostname }}
+# state: present
+
+- name: Remove schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Ensure tenant ansible_test exist
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ # sites:
+ # - '{{ mso_site | default("ansible_test") }}'
+ state: present
+
+- name: Ensure schema 1 with Template 1 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: Template 1
+ state: present
+
+- name: Ensure schema 1 with Template 2 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: Template 2
+ state: present
+
+- name: Ensure schema 2 with Template 3 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template 3
+ state: present
+
+- name: Ensure ANP exist
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ item.schema }}'
+ template: '{{ item.template }}'
+ anp: ANP
+ state: present
+ loop:
+ - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 1' }
+ - { schema: '{{ mso_schema | default("ansible_test") }}_2', template: 'Template 3' }
+
+# ADD EPGs
+- name: Ensure EPGs exist
+ mso_schema_template_anp_epg:
+ <<: *mso_info
+ schema: '{{ item.schema }}'
+ template: '{{ item.template }}'
+ anp: ANP
+ epg: '{{ item.epg }}'
+ state: present
+ loop:
+ - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 1', epg: 'ansible_test_1' }
+ - { schema: '{{ mso_schema | default("ansible_test") }}_2', template: 'Template 3', epg: 'ansible_test_3' }
+
+# ADD Selector to EPG
+- name: Add Selector to EPG (check_mode)
+ mso_schema_template_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: selector_1
+ state: present
+ check_mode: true
+ register: cm_add_selector_1
+
+- name: Verify cm_add_selector_1
+ assert:
+ that:
+ - cm_add_selector_1 is changed
+ - cm_add_selector_1.previous == {}
+ - cm_add_selector_1.current.name == "selector_1"
+ - cm_add_selector_1.current.expressions == []
+
+- name: Add Selector 1 to EPG with space in selector name (normal_mode)
+ mso_schema_template_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: selector 1
+ state: present
+ ignore_errors: true
+ register: nm_add_selector1_with_space_in_name
+
+- name: Verify nm_add_selector1_with_space_in_name
+ assert:
+ that:
+ - nm_add_selector1_with_space_in_name is not changed
+ - nm_add_selector1_with_space_in_name.msg == "There should not be any space in selector name."
+
+- name: Add Selector to EPG (normal_mode)
+ mso_schema_template_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: selector_1
+ state: present
+ register: nm_add_selector_1
+
+- name: Verify nm_add_selector_1
+ assert:
+ that:
+ - nm_add_selector_1 is changed
+ - nm_add_selector_1.previous == {}
+ - nm_add_selector_1.current.name == "selector_1"
+ - nm_add_selector_1.current.expressions == []
+
+- name: Add Selector to EPG again (check_mode)
+ mso_schema_template_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: selector_1
+ state: present
+ check_mode: true
+ register: cm_add_selector_1_again
+
+- name: Verify cm_add_selector_1_again
+ assert:
+ that:
+ - cm_add_selector_1_again is not changed
+ - cm_add_selector_1_again.previous.name == "selector_1"
+ - cm_add_selector_1_again.previous.expressions == []
+ - cm_add_selector_1_again.current.name == "selector_1"
+ - cm_add_selector_1_again.current.expressions == []
+
+- name: Add Selector to EPG again (normal_mode)
+ mso_schema_template_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: selector_1
+ state: present
+ register: nm_add_selector_1_again
+
+- name: Verify nm_add_selector_1_again
+ assert:
+ that:
+ - nm_add_selector_1_again is not changed
+ - nm_add_selector_1_again.previous.name == "selector_1"
+ - nm_add_selector_1_again.previous.expressions == []
+ - nm_add_selector_1_again.current.name == "selector_1"
+ - nm_add_selector_1_again.current.expressions == []
+
+- name: Add Selector 2 to EPG (check_mode)
+ mso_schema_template_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: selector_2
+ expressions:
+ - type: expression_1
+ operator: in
+ value: test
+ state: present
+ check_mode: true
+ register: cm_add_selector_2
+
+- name: Verify cm_add_selector_2
+ assert:
+ that:
+ - cm_add_selector_2 is changed
+ - cm_add_selector_2.previous == {}
+ - cm_add_selector_2.current.name == "selector_2"
+ - cm_add_selector_2.current.expressions[0].key == "Custom:expression_1"
+ - cm_add_selector_2.current.expressions[0].operator == "in"
+ - cm_add_selector_2.current.expressions[0].value == "test"
+
+- name: Add Selector 2 to EPG (normal_mode)
+ mso_schema_template_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: selector_2
+ expressions:
+ - type: expression_1
+ operator: in
+ value: test
+ state: present
+ register: nm_add_selector_2
+
+- name: Verify nm_add_selector_2
+ assert:
+ that:
+ - nm_add_selector_2 is changed
+ - nm_add_selector_2.previous == {}
+ - nm_add_selector_2.current.name == "selector_2"
+ - nm_add_selector_2.current.expressions[0].key == "Custom:expression_1"
+ - nm_add_selector_2.current.expressions[0].operator == "in"
+ - nm_add_selector_2.current.expressions[0].value == "test"
+
+- name: Add Selector 2 to EPG with space in expression type (normal_mode)
+ mso_schema_template_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: selector_2
+ expressions:
+ - type: expression 1
+ operator: in
+ value: test
+ state: present
+ ignore_errors: true
+ register: nm_add_selector2_with_space_in_expression_type
+
+- name: Verify nm_add_selector2_with_space_in_expression_type
+ assert:
+ that:
+ - nm_add_selector2_with_space_in_expression_type is not changed
+ - nm_add_selector2_with_space_in_expression_type.msg == "There should not be any space in 'type' attribute of expression 'expression 1'"
+
+- name: Change Selector 2 - keyExist(normal_mode)
+ mso_schema_template_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: selector_2
+ expressions:
+ - type: expression_5
+ operator: has_key
+ value: test
+ state: present
+ ignore_errors: true
+ register: nm_change_selector_2_key_exist
+
+- name: Verify nm_change_selector_2_key_exist
+ assert:
+ that:
+ - nm_change_selector_2_key_exist is not changed
+ - nm_change_selector_2_key_exist.msg == "Attribute 'value' is not supported for operator 'has_key' in expression 'expression_5'"
+
+- name: Change Selector 2 - keyNotExist (normal_mode)
+ mso_schema_template_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: selector_2
+ expressions:
+ - type: expression_6
+ operator: does_not_have_key
+ value: test
+ state: present
+ ignore_errors: true
+ register: nm_change_selector_2_key_not_exist
+
+- name: Verify nm_change_selector_2_key_not_exist
+ assert:
+ that:
+ - nm_change_selector_2_key_not_exist is not changed
+ - nm_change_selector_2_key_not_exist.msg == "Attribute 'value' is not supported for operator 'does_not_have_key' in expression 'expression_6'"
+
+- name: Change Selector 2 - equals (normal_mode)
+ mso_schema_template_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: selector_2
+ expressions:
+ - type: expression_6
+ operator: equals
+ state: present
+ ignore_errors: true
+ register: nm_change_selector_2_equals
+
+- name: Verify nm_change_selector_2_equals
+ assert:
+ that:
+ - nm_change_selector_2_equals is not changed
+ - nm_change_selector_2_equals.msg == "Attribute 'value' needed for operator 'equals' in expression 'expression_6'"
+
+- name: Change Selector 2 expressions (normal_mode)
+ mso_schema_template_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: selector_2
+ expressions:
+ - type: expression_1
+ operator: in
+ value: test
+ - type: expression_2
+ operator: not_in
+ value: test
+ - type: expression_3
+ operator: equals
+ value: test
+ - type: expression_4
+ operator: not_equals
+ value: test
+ - type: expression_5
+ operator: has_key
+ value:
+ - type: expression_6
+ operator: does_not_have_key
+ state: present
+ register: nm_change_selector_2
+
+- name: Verify nm_change_selector_2
+ assert:
+ that:
+ - nm_change_selector_2 is changed
+ - nm_change_selector_2.current.name == "selector_2"
+ - nm_change_selector_2.current.expressions | length == 6
+
+- name: Change Selector 2 expressions again (normal_mode)
+ mso_schema_template_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: selector_2
+ expressions:
+ - type: expression_1
+ operator: in
+ value: test
+ - type: expression_2
+ operator: not_in
+ value: test
+ - type: expression_3
+ operator: equals
+ value: test
+ - type: expression_4
+ operator: not_equals
+ value: test
+ - type: expression_5
+ operator: has_key
+ value:
+ - type: expression_6
+ operator: does_not_have_key
+ state: present
+ register: nm_change_selector_2_again
+
+- name: Verify nm_change_selector_2_again
+ assert:
+ that:
+ - nm_change_selector_2_again is not changed
+ - nm_change_selector_2_again.current.name == "selector_2"
+ - nm_change_selector_2_again.current.expressions | length == 6
+
+- name: Query all selectors (check_mode)
+ mso_schema_template_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ state: query
+ check_mode: true
+ register: cm_query_all_selectors
+
+- name: Verify cm_query_all_selectors
+ assert:
+ that:
+ - cm_query_all_selectors is not changed
+ - cm_query_all_selectors.current | length == 2
+ - cm_query_all_selectors.current[0].name == "selector_1"
+ - cm_query_all_selectors.current[1].name == "selector_2"
+ - cm_query_all_selectors.current[0].expressions == []
+ - cm_query_all_selectors.current[1].expressions | length == 6
+
+- name: Query all selectors (normal_mode)
+ mso_schema_template_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ state: query
+ register: nm_query_all_selectors
+
+- name: Verify nm_query_all_selectors
+ assert:
+ that:
+ - nm_query_all_selectors is not changed
+ - nm_query_all_selectors.current | length == 2
+ - nm_query_all_selectors.current[0].name == "selector_1"
+ - nm_query_all_selectors.current[1].name == "selector_2"
+ - nm_query_all_selectors.current[0].expressions == []
+ - nm_query_all_selectors.current[1].expressions | length == 6
+
+- name: Query specific selector (check_mode)
+ mso_schema_template_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: selector_1
+ state: query
+ check_mode: true
+ register: cm_query_selector1
+
+- name: Verify cm_query_selector1
+ assert:
+ that:
+ - cm_query_selector1 is not changed
+ - cm_query_selector1.current.name == "selector_1"
+ - cm_query_selector1.current.expressions == []
+
+- name: Query specific selector (normal_mode)
+ mso_schema_template_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: selector_1
+ state: query
+ register: nm_query_selector1
+
+- name: Verify nm_query_selector1
+ assert:
+ that:
+ - nm_query_selector1 is not changed
+ - nm_query_selector1.current.name == "selector_1"
+ - nm_query_selector1.current.expressions == []
+
+- name: Query specific selector2 (normal_mode)
+ mso_schema_template_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: selector_2
+ state: query
+ register: nm_query_selector2
+
+- name: Verify nm_query_selector2
+ assert:
+ that:
+ - nm_query_selector2 is not changed
+ - nm_query_selector2.current.name == "selector_2"
+ - nm_query_selector2.current.expressions | length == 6
+ - nm_query_selector2.current.expressions[0].key == "Custom:expression_1"
+ - nm_query_selector2.current.expressions[0].operator == "in"
+ - nm_query_selector2.current.expressions[0].value == "test"
+ - nm_query_selector2.current.expressions[1].key == "Custom:expression_2"
+ - nm_query_selector2.current.expressions[1].operator == "notIn"
+ - nm_query_selector2.current.expressions[1].value == "test"
+ - nm_query_selector2.current.expressions[2].key == "Custom:expression_3"
+ - nm_query_selector2.current.expressions[2].operator == "equals"
+ - nm_query_selector2.current.expressions[2].value == "test"
+ - nm_query_selector2.current.expressions[3].key == "Custom:expression_4"
+ - nm_query_selector2.current.expressions[3].operator == "notEquals"
+ - nm_query_selector2.current.expressions[3].value == "test"
+ - nm_query_selector2.current.expressions[4].key == "Custom:expression_5"
+ - nm_query_selector2.current.expressions[4].operator == "keyExist"
+ - nm_query_selector2.current.expressions[4].value == ""
+ - nm_query_selector2.current.expressions[5].key == "Custom:expression_6"
+ - nm_query_selector2.current.expressions[5].operator == "keyNotExist"
+ - nm_query_selector2.current.expressions[5].value == ""
+
+# - name: Remove selector 1 (check_mode)
+# mso_schema_template_anp_epg_selector:
+# <<: *mso_info
+# schema: '{{ mso_schema | default("ansible_test") }}'
+# template: Template 1
+# anp: ANP
+# epg: ansible_test_1
+# selector: selector 1
+# state: absent
+# check_mode: true
+# register: cm_remove_selector_1
+
+# - name: Verify cm_remove_selector_1
+# assert:
+# that:
+# - cm_remove_selector_1 is changed
+# - cm_remove_selector_1.current == {}
+
+# - name: Remove selector 1 (normal_mode)
+# mso_schema_template_anp_epg_selector:
+# <<: *mso_info
+# schema: '{{ mso_schema | default("ansible_test") }}'
+# template: Template 1
+# anp: ANP
+# epg: ansible_test_1
+# selector: selector 1
+# state: absent
+# register: nm_remove_selector_1
+
+# - name: Verify nm_remove_selector_1
+# assert:
+# that:
+# - nm_remove_selector_1 is changed
+# - nm_remove_selector_1.current == {}
+
+- name: Remove selector 2 (normal_mode)
+ mso_schema_template_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: selector_2
+ state: absent
+ register: nm_remove_selector_2
+
+- name: Verify nm_remove_selector_2
+ assert:
+ that:
+ - nm_remove_selector_2 is changed
+ - nm_remove_selector_2.current == {}
+
+# QUERY NON-EXISTING Selector to EPG
+- name: Query non-existing selector (check_mode)
+ mso_schema_template_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: non_existing_selector
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_query_non_selector
+
+- name: Query non-existing selector (normal mode)
+ mso_schema_template_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: non_existing_selector
+ state: query
+ ignore_errors: true
+ register: nm_query_non_selector
+
+- name: Verify cm_query_non_selector and nm_query_non_selector
+ assert:
+ that:
+ - cm_query_non_selector is not changed
+ - nm_query_non_selector is not changed
+ - cm_query_non_selector == nm_query_non_selector
+ - cm_query_non_selector.msg == "Selector 'non_existing_selector' not found"
+ - nm_query_non_selector.msg == "Selector 'non_existing_selector' not found"
+
+# QUERY NON-EXISTING EPG
+- name: Query non-existing EPG (check_mode)
+ mso_schema_template_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: non_existing_epg
+ selector: selector_1
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_query_non_epg
+
+- name: Query non-existing EPG (normal mode)
+ mso_schema_template_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: non_existing_epg
+ selector: selector_1
+ state: query
+ ignore_errors: true
+ register: nm_query_non_epg
+
+- name: Verify query_non_epg
+ assert:
+ that:
+ - cm_query_non_epg is not changed
+ - nm_query_non_epg is not changed
+ - cm_query_non_epg == nm_query_non_epg
+ - cm_query_non_epg.msg == nm_query_non_epg.msg == "Provided epg 'non_existing_epg' does not exist. Existing epgs{{':'}} ansible_test_1"
+
+# QUERY NON-EXISTING ANP
+- name: Query non-existing ANP (check_mode)
+ mso_schema_template_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: non_existing_anp
+ epg: ansible_test_1
+ selector: selector_1
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_query_non_anp
+
+- name: Query non-existing ANP (normal mode)
+ mso_schema_template_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: non_existing_anp
+ epg: ansible_test_1
+ selector: selector_1
+ state: query
+ ignore_errors: true
+ register: nm_query_non_anp
+
+- name: Verify query_non_anp
+ assert:
+ that:
+ - cm_query_non_anp is not changed
+ - nm_query_non_anp is not changed
+ - cm_query_non_anp == nm_query_non_anp
+ - cm_query_non_anp.msg == nm_query_non_anp.msg == "Provided anp 'non_existing_anp' does not exist. Existing anps{{':'}} ANP"
+
+# USE A NON-EXISTING STATE
+- name: Non-existing state (check_mode)
+ mso_schema_template_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: selector_1
+ state: non-existing-state
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_state
+
+- name: Non-existing state (normal_mode)
+ mso_schema_template_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: selector_1
+ state: non-existing-state
+ ignore_errors: true
+ register: nm_non_existing_state
+
+- name: Verify non_existing_state
+ assert:
+ that:
+ - cm_non_existing_state is not changed
+ - nm_non_existing_state is not changed
+ - cm_non_existing_state == nm_non_existing_state
+ - cm_non_existing_state.msg == nm_non_existing_state.msg == "value of state must be one of{{':'}} absent, present, query, got{{':'}} non-existing-state"
+
+# USE A NON-EXISTING TEMPLATE
+- name: Non-existing template (check_mode)
+ mso_schema_template_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: non-existing-template
+ anp: ANP
+ epg: ansible_test_1
+ selector: selector_1
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_template
+
+- name: Non-existing template (normal_mode)
+ mso_schema_template_anp_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: non-existing-template
+ anp: ANP
+ epg: ansible_test_1
+ selector: selector_1
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_template
+
+- name: Verify non_existing_template
+ assert:
+ that:
+ - cm_non_existing_template is not changed
+ - nm_non_existing_template is not changed
+ - cm_non_existing_template == nm_non_existing_template
+ - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non-existing-template' does not exist. Existing templates{{':'}} Template1, Template2"
+
+# USE A NON-EXISTING SCHEMA
+- name: Non-existing schema (check_mode)
+ mso_schema_template_anp_epg_selector:
+ <<: *mso_info
+ schema: non-existing-schema
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: selector_1
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_schema
+
+- name: Non-existing schema (normal_mode)
+ mso_schema_template_anp_epg_selector:
+ <<: *mso_info
+ schema: non-existing-schema
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ selector: selector_1
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_schema
+
+- name: Verify non_existing_schema
+ assert:
+ that:
+ - cm_non_existing_schema is not changed
+ - nm_non_existing_schema is not changed
+ - cm_non_existing_schema == nm_non_existing_schema
+ - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non-existing-schema' does not exist." \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd/tasks/main.yml
new file mode 100644
index 000000000..a10ed8e14
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd/tasks/main.yml
@@ -0,0 +1,1907 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case)
+#
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Set version vars
+ set_fact:
+ mso_l3mcast: false
+ when: version.current.version is version('2.2.4', '=')
+
+- name: Ensure site exist
+ mso_site: &site_present
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ apic_username }}'
+ apic_password: '{{ apic_password }}'
+ apic_site_id: '{{ apic_site_id | default(101) }}'
+ urls:
+ - https://{{ apic_hostname }}
+ state: present
+
+- name: Remove schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Remove DHCP policies
+ mso_dhcp_relay_policy:
+ <<: *mso_info
+ dhcp_relay_policy: '{{ item }}'
+ tenant: ansible_test
+ state: absent
+ loop:
+ - 'ansible_test_dhcp_policy1'
+ - 'ansible_test_dhcp_policy2'
+ - 'ansible_test_dhcp_policy3'
+ when:
+ # TODO dhcp policies api endpoint has changed after 4.0, fix when new module is created
+ - version.current.version is version('4.0', '<')
+
+- name: Remove DHCP option policies
+ mso_dhcp_option_policy:
+ <<: *mso_info
+ dhcp_option_policy: '{{ item }}'
+ tenant: ansible_test
+ state: absent
+ loop:
+ - 'ansible_test_dhcp_policy_option1'
+ - 'ansible_test_dhcp_policy_option2'
+ - 'ansible_test_dhcp_policy_option3'
+ when:
+ # TODO dhcp policies api endpoint has changed after 4.0, fix when new module is created
+ - version.current.version is version('4.0', '<')
+
+- name: Ensure tenant ansible_test exist
+ mso_tenant: &tenant_present
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ state: present
+
+- name: Ensure schema 1 with Template1 exist
+ mso_schema_template: &schema_present
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: Template1
+ state: present
+
+- name: Ensure schema 1 with Template2 exists
+ mso_schema_template:
+ <<: *schema_present
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: Template2
+
+- name: Ensure schema 2 with Template3 exists
+ mso_schema_template:
+ <<: *schema_present
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template3
+
+- name: Ensure schema 2 with Template5 exists
+ mso_schema_template:
+ <<: *schema_present
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template5
+
+- name: Ensure VRF exist
+ mso_schema_template_vrf: &vrf_present
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ vrf: VRF
+ layer3_multicast: true
+ state: present
+
+- name: Ensure VRF2 exist
+ mso_schema_template_vrf:
+ <<: *vrf_present
+ vrf: VRF2
+
+- name: Ensure VRF3 exist
+ mso_schema_template_vrf:
+ <<: *vrf_present
+ template: Template2
+ vrf: VRF3
+
+- name: Ensure VRF4 exist
+ mso_schema_template_vrf:
+ <<: *vrf_present
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template3
+ vrf: VRF4
+
+- name: Ensure VRF5 exists
+ mso_schema_template_vrf:
+ <<: *vrf_present
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template5
+ vrf: VRF5
+
+- name: Ensure ansible_test_1 BD does not exist
+ mso_schema_template_bd:
+ <<: *vrf_present
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ vrf:
+ name: VRF
+ state: absent
+
+- name: Ensure ansible_test_2 BD does not exist
+ mso_schema_template_bd:
+ <<: *vrf_present
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template2
+ bd: ansible_test_2
+ vrf:
+ name: VRF
+ state: absent
+
+- name: Ensure ansible_test_3 BD does not exist
+ mso_schema_template_bd:
+ <<: *vrf_present
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template3
+ bd: ansible_test_3
+ vrf:
+ name: VRF
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ state: absent
+
+- name: Ensure ansible_test_4 BD does not exist
+ mso_schema_template_bd:
+ <<: *vrf_present
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_4
+ vrf:
+ name: VRF
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ state: absent
+
+- name: Ensure multiple DHCP policies exist
+ mso_dhcp_relay_policy:
+ <<: *mso_info
+ dhcp_relay_policy: '{{ item }}'
+ description: "My Test DHCP Policies"
+ tenant: ansible_test
+ state: present
+ loop:
+ - 'ansible_test_dhcp_policy1'
+ - 'ansible_test_dhcp_policy2'
+ - 'ansible_test_dhcp_policy3'
+ when:
+ # TODO dhcp policies api endpoint has changed after 4.0, fix when new module is created
+ - version.current.version is version('4.0', '<')
+
+- name: Ensure multiple DHCP option policies exist
+ mso_dhcp_option_policy:
+ <<: *mso_info
+ dhcp_option_policy: '{{ item }}'
+ description: "My Test DHCP Policy Options"
+ tenant: ansible_test
+ state: present
+ loop:
+ - 'ansible_test_dhcp_policy_option1'
+ - 'ansible_test_dhcp_policy_option2'
+ - 'ansible_test_dhcp_policy_option3'
+ when:
+ # TODO dhcp policies api endpoint has changed after 4.0, fix when new module is created
+ - version.current.version is version('4.0', '<')
+
+# ADD BD
+- name: Add bd (check_mode)
+ mso_schema_template_bd: &bd_present
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ vrf:
+ name: VRF
+ state: present
+ check_mode: true
+ register: cm_add_bd
+
+- name: Verify cm_add_bd
+ assert:
+ that:
+ - cm_add_bd is changed
+ - cm_add_bd.previous == {}
+ - cm_add_bd.current.name == "ansible_test_1"
+ - cm_add_bd.current.vrfRef.templateName == "Template1"
+ - cm_add_bd.current.vrfRef.vrfName == "VRF"
+
+- name: Add bd (normal mode)
+ mso_schema_template_bd:
+ <<: *bd_present
+ register: nm_add_bd
+
+- name: Verify nm_add_bd
+ assert:
+ that:
+ - nm_add_bd is changed
+ - nm_add_bd.previous == {}
+ - nm_add_bd.current.name == "ansible_test_1"
+ - nm_add_bd.current.vrfRef.templateName == "Template1"
+ - nm_add_bd.current.vrfRef.vrfName == "VRF"
+ - cm_add_bd.current.vrfRef.schemaId == nm_add_bd.current.vrfRef.schemaId
+
+- name: Add bd again (check_mode)
+ mso_schema_template_bd:
+ <<: *bd_present
+ check_mode: true
+ register: cm_add_bd_again
+
+- name: Verify cm_add_bd_again
+ assert:
+ that:
+ - cm_add_bd_again is not changed
+ - cm_add_bd_again.previous.name == "ansible_test_1"
+ - cm_add_bd_again.current.name == "ansible_test_1"
+ - cm_add_bd_again.previous.vrfRef.templateName == "Template1"
+ - cm_add_bd_again.current.vrfRef.templateName == "Template1"
+ - cm_add_bd_again.previous.vrfRef.vrfName == "VRF"
+ - cm_add_bd_again.current.vrfRef.vrfName == "VRF"
+ - cm_add_bd_again.previous.vrfRef.schemaId == cm_add_bd_again.current.vrfRef.schemaId
+
+- name: Add bd again (normal mode)
+ mso_schema_template_bd:
+ <<: *bd_present
+ register: nm_add_bd_again
+
+- name: Verify nm_add_bd_again
+ assert:
+ that:
+ - nm_add_bd_again is not changed
+ - nm_add_bd_again.previous.name == "ansible_test_1"
+ - nm_add_bd_again.current.name == "ansible_test_1"
+ - nm_add_bd_again.previous.vrfRef.templateName == "Template1"
+ - nm_add_bd_again.current.vrfRef.templateName == "Template1"
+ - nm_add_bd_again.previous.vrfRef.vrfName == "VRF"
+ - nm_add_bd_again.current.vrfRef.vrfName == "VRF"
+ - nm_add_bd_again.previous.vrfRef.schemaId == nm_add_bd_again.current.vrfRef.schemaId
+
+- name: Add bd 2 (normal mode)
+ mso_schema_template_bd:
+ <<: *bd_present
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template2
+ bd: ansible_test_2
+ intersite_bum_traffic: true
+ optimize_wan_bandwidth: true
+ layer2_stretch: true
+ layer2_unknown_unicast: flood
+ layer3_multicast: true
+ subnets:
+ - subnet: 10.0.0.128/24
+ - subnet: 10.0.1.254/24
+ description: 1234567890
+ - subnet: 172.16.0.1/24
+ description: "My description for a subnet"
+ scope: public
+ shared: true
+ no_default_gateway: false
+ querier: true
+ - ip: 192.168.0.254/24
+ description: "My description for a subnet"
+ scope: private
+ shared: false
+ no_default_gateway: true
+ vrf:
+ name: VRF3
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template2
+ dhcp_policy:
+ name: ansible_test
+ version: 1
+ dhcp_option_policy:
+ name: ansible_test_option
+ version: 1
+ register: nm_add_bd_2
+
+- name: Add bd 3 (normal mode)
+ mso_schema_template_bd:
+ <<: *bd_present
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template3
+ bd: ansible_test_3
+ vrf:
+ name: VRF4
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template3
+ register: nm_add_bd_3
+
+- name: Add bd 4 (normal mode)
+ mso_schema_template_bd:
+ <<: *bd_present
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_4
+ vrf:
+ name: VRF
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ register: nm_add_bd_4
+
+- name: Verify nm_add_bd_2 and nm_add_bd_3
+ assert:
+ that:
+ - nm_add_bd_2 is changed
+ - nm_add_bd_3 is changed
+ - nm_add_bd_2.current.name == "ansible_test_2"
+ - nm_add_bd_3.current.name == "ansible_test_3"
+ - nm_add_bd_2.current.vrfRef.templateName == "Template2"
+ - nm_add_bd_3.current.vrfRef.templateName == "Template3"
+ - nm_add_bd_2.current.vrfRef.vrfName == "VRF3"
+ - nm_add_bd_3.current.vrfRef.vrfName == "VRF4"
+ - nm_add_bd_2.current.vrfRef.schemaId == nm_add_bd.current.vrfRef.schemaId
+ - nm_add_bd_2.current.intersiteBumTrafficAllow == true
+ - nm_add_bd_2.current.optimizeWanBandwidth == true
+ - nm_add_bd_2.current.l2Stretch == true
+ - nm_add_bd_2.current.l2UnknownUnicast == "flood"
+ - nm_add_bd_2.current.l3MCast == true
+ - nm_add_bd_2.current.subnets[0].description == "10.0.0.128/24"
+ - nm_add_bd_2.current.subnets[0].ip == "10.0.0.128/24"
+ - nm_add_bd_2.current.subnets[0].noDefaultGateway == false
+ - nm_add_bd_2.current.subnets[0].scope == "private"
+ - nm_add_bd_2.current.subnets[0].shared == false
+ - nm_add_bd_2.current.subnets[0].querier == false
+ - nm_add_bd_2.current.subnets[1].description == "1234567890"
+ - nm_add_bd_2.current.subnets[1].ip == "10.0.1.254/24"
+ - nm_add_bd_2.current.subnets[1].noDefaultGateway == false
+ - nm_add_bd_2.current.subnets[1].scope == "private"
+ - nm_add_bd_2.current.subnets[1].shared == false
+ - nm_add_bd_2.current.subnets[1].querier == false
+ - nm_add_bd_2.current.subnets[2].description == "My description for a subnet"
+ - nm_add_bd_2.current.subnets[2].ip == "172.16.0.1/24"
+ - nm_add_bd_2.current.subnets[2].noDefaultGateway == false
+ - nm_add_bd_2.current.subnets[2].scope == "public"
+ - nm_add_bd_2.current.subnets[2].shared == true
+ - nm_add_bd_2.current.subnets[2].querier == true
+ - nm_add_bd_2.current.subnets[3].description == "My description for a subnet"
+ - nm_add_bd_2.current.subnets[3].ip == "192.168.0.254/24"
+ - nm_add_bd_2.current.subnets[3].noDefaultGateway == true
+ - nm_add_bd_2.current.subnets[3].scope == "private"
+ - nm_add_bd_2.current.subnets[3].shared == false
+ - nm_add_bd_2.current.subnets[3].querier == false
+
+- name: Add bd 5 (normal mode)
+ mso_schema_template_bd:
+ <<: *bd_present
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template5
+ bd: ansible_test_5
+ intersite_bum_traffic: true
+ optimize_wan_bandwidth: true
+ layer2_stretch: true
+ layer2_unknown_unicast: flood
+ layer3_multicast: false
+ unknown_multicast_flooding: flood
+ multi_destination_flooding: drop
+ ipv6_unknown_multicast_flooding: flood
+ arp_flooding: true
+ virtual_mac_address: 00:00:5E:00:01:3C
+ vrf:
+ name: VRF5
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template5
+ dhcp_policy:
+ name: ansible_test
+ version: 1
+ dhcp_option_policy:
+ name: ansible_test_option
+ version: 1
+ register: nm_add_bd_5
+
+- name: Verify nm_add_bd_5 for a version that's before 3.1
+ assert:
+ that:
+ - nm_add_bd_5 is changed
+ - nm_add_bd_5.current.name == "ansible_test_5"
+ - nm_add_bd_5.current.vrfRef.templateName == "Template5"
+ - nm_add_bd_5.current.vrfRef.vrfName == "VRF5"
+ - nm_add_bd_5.current.intersiteBumTrafficAllow == true
+ - nm_add_bd_5.current.optimizeWanBandwidth == true
+ - nm_add_bd_5.current.l2Stretch == true
+ - nm_add_bd_5.current.l2UnknownUnicast == "flood"
+ - nm_add_bd_5.current.l3MCast == false
+ when: version.current.version is version('3.1.1g', '<')
+
+- name: Verify nm_add_bd_5 for a version that's 3.1
+ assert:
+ that:
+ - nm_add_bd_5 is changed
+ - nm_add_bd_5.current.name == "ansible_test_5"
+ - nm_add_bd_5.current.vrfRef.templateName == "Template5"
+ - nm_add_bd_5.current.vrfRef.vrfName == "VRF5"
+ - nm_add_bd_5.current.intersiteBumTrafficAllow == true
+ - nm_add_bd_5.current.optimizeWanBandwidth == true
+ - nm_add_bd_5.current.l2Stretch == true
+ - nm_add_bd_5.current.l2UnknownUnicast == "flood"
+ - nm_add_bd_5.current.l3MCast == false
+ - nm_add_bd_5.current.unkMcastAct == "flood"
+ - nm_add_bd_5.current.v6unkMcastAct == "flood"
+ - nm_add_bd_5.current.vmac == "00:00:5E:00:01:3C"
+ - nm_add_bd_5.current.multiDstPktAct == "drop"
+ - nm_add_bd_5.current.arpFlood == true
+ when: version.current.version is version('3.1.1g', '>=')
+
+- name: Add bd 5 again (normal mode)
+ mso_schema_template_bd:
+ <<: *bd_present
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template5
+ bd: ansible_test_5
+ intersite_bum_traffic: true
+ optimize_wan_bandwidth: true
+ layer2_stretch: true
+ layer2_unknown_unicast: flood
+ layer3_multicast: false
+ unknown_multicast_flooding: flood
+ multi_destination_flooding: drop
+ ipv6_unknown_multicast_flooding: flood
+ arp_flooding: true
+ virtual_mac_address: 00:00:5E:00:01:3C
+ vrf:
+ name: VRF5
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template5
+ dhcp_policy:
+ name: ansible_test
+ version: 1
+ dhcp_option_policy:
+ name: ansible_test_option
+ version: 1
+ register: nm_add_again_bd_5
+
+- name: Verify nm_add_again_bd_5 for a version that's before 3.1
+ assert:
+ that:
+ - nm_add_again_bd_5 is not changed
+ when: version.current.version is version('3.1.1g', '<')
+
+- name: Verify nm_add_again_bd_5 for a version that's between 3.1 and 4.0
+ assert:
+ that:
+ - nm_add_again_bd_5 is not changed
+ when:
+ - version.current.version is version('3.1.1g', '>=')
+
+- name: Add bd 5 with different values for new options (normal mode)
+ mso_schema_template_bd:
+ <<: *bd_present
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template5
+ bd: ansible_test_5
+ intersite_bum_traffic: true
+ optimize_wan_bandwidth: true
+ layer2_stretch: true
+ layer2_unknown_unicast: flood
+ layer3_multicast: false
+ unknown_multicast_flooding: optimized_flooding
+ multi_destination_flooding: flood_in_bd
+ ipv6_unknown_multicast_flooding: optimized_flooding
+ arp_flooding: true
+ virtual_mac_address: 00:00:5E:00:02:3C
+ vrf:
+ name: VRF5
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template5
+ dhcp_policy:
+ name: ansible_test
+ version: 1
+ dhcp_option_policy:
+ name: ansible_test_option
+ version: 1
+ register: nm_bd_5_options
+
+- name: Verify nm_bd_5_options for a version that's before 3.1
+ assert:
+ that:
+ - nm_bd_5_options is not changed
+ when: version.current.version is version('3.1.1g', '<')
+
+- name: Verify nm_bd_5_options for a version that's 3.1
+ assert:
+ that:
+ - nm_bd_5_options is changed
+ - nm_bd_5_options.current.unkMcastAct == "opt-flood"
+ - nm_bd_5_options.current.v6unkMcastAct == "opt-flood"
+ - nm_bd_5_options.current.multiDstPktAct == "bd-flood"
+ - nm_bd_5_options.current.vmac == "00:00:5E:00:02:3C"
+ when: version.current.version is version('3.1.1g', '>=')
+
+- name: Change bd 5_1 (normal mode)
+ mso_schema_template_bd:
+ <<: *bd_present
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template5
+ bd: ansible_test_5
+ intersite_bum_traffic: true
+ optimize_wan_bandwidth: true
+ layer2_stretch: true
+ layer2_unknown_unicast: proxy
+ layer3_multicast: false
+ unknown_multicast_flooding: flood
+ multi_destination_flooding: drop
+ ipv6_unknown_multicast_flooding: flood
+ arp_flooding: true
+ virtual_mac_address: 00:00:5E:00:01:3C
+ vrf:
+ name: VRF5
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template5
+ dhcp_policy:
+ name: ansible_test
+ version: 1
+ dhcp_option_policy:
+ name: ansible_test_option
+ version: 1
+ register: nm_change_bd_5_1
+
+- name: Verify nm_change_bd_5_1 for a version that's before 3.1
+ assert:
+ that:
+ - nm_change_bd_5_1 is changed
+ when: version.current.version is version('3.1.1g', '<')
+
+- name: Verify nm_change_bd_5_1 for a version that's 3.1
+ assert:
+ that:
+ - nm_change_bd_5_1 is changed
+ - nm_change_bd_5_1.current.arpFlood == true
+ when: version.current.version is version('3.1.1g', '>=')
+
+- name: Change bd 5_2 (normal mode)
+ mso_schema_template_bd: &change_bd5_2
+ <<: *bd_present
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template5
+ bd: ansible_test_5
+ intersite_bum_traffic: true
+ optimize_wan_bandwidth: true
+ layer2_stretch: true
+ layer2_unknown_unicast: flood
+ layer3_multicast: false
+ unknown_multicast_flooding: flood
+ multi_destination_flooding: drop
+ ipv6_unknown_multicast_flooding: flood
+ arp_flooding: false
+ virtual_mac_address: 00:00:5E:00:01:3C
+ vrf:
+ name: VRF5
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template5
+ dhcp_policy:
+ name: ansible_test
+ version: 1
+ dhcp_option_policy:
+ name: ansible_test_option
+ version: 1
+ ignore_errors: true
+ register: nm_change_bd_5_2
+
+- name: Verify nm_change_bd_5_2 for a version that's before 3.1
+ assert:
+ that:
+ - nm_change_bd_5_2 is changed
+ - nm_change_bd_5_2.current.l2UnknownUnicast == "flood"
+ when: version.current.version is version('3.1.1g', '<')
+
+- name: Verify nm_change_bd_5_2 for a version that's after 3.1
+ assert:
+ that:
+ - nm_change_bd_5_2 is changed
+ - nm_change_bd_5_2.current.arpFlood == true
+ when: version.current.version is version('3.1.1g', '>=')
+
+- name: Change bd 5_3 (normal mode)
+ mso_schema_template_bd:
+ <<: *bd_present
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template5
+ bd: ansible_test_5
+ intersite_bum_traffic: false
+ optimize_wan_bandwidth: true
+ layer2_stretch: true
+ layer2_unknown_unicast: flood
+ layer3_multicast: false
+ unknown_multicast_flooding: flood
+ multi_destination_flooding: drop
+ ipv6_unknown_multicast_flooding: flood
+ arp_flooding: false
+ virtual_mac_address: 00:00:5E:00:01:3C
+ vrf:
+ name: VRF5
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template5
+ dhcp_policy:
+ name: ansible_test
+ version: 1
+ dhcp_option_policy:
+ name: ansible_test_option
+ version: 1
+ ignore_errors: true
+ register: nm_change_bd_5_3
+
+- name: Verify nm_change_bd_5_3 for a version that's before 3.1
+ assert:
+ that:
+ - nm_change_bd_5_3 is changed
+ - nm_change_bd_5_3.msg is match ("MSO Error 143{{':'}} Invalid Field{{':'}} BD 'ansible_test_5' l2UnknownUnicast cannot be flood when intersiteBumTrafficAllow is off")
+ when: version.current.version is version('3.1.1g', '<')
+
+- name: Verify nm_change_bd_5_3 for a version that's after 3.1 and before 3.3
+ assert:
+ that:
+ - nm_change_bd_5_3 is changed
+ # Inconsistency shown in returned error messages for v3.1.1
+ # Below variants of the message output have occurred randomly, thus commenting out the specific error messages below.
+ # Would need further investigation in to the cause, but due to old version decided to skip this investigation and do skip the message skip.
+ # - "'l2UnknownUnicast cannot be flood when intersiteBumTrafficAllow is off exception while trying to update schema' in nm_change_bd_5_3.msg"
+ # - nm_change_bd_5_3.msg is match ("MSO Error 400{{':'}} Bad Request{{':'}} Patch Failed, Received{{':'}} BD 'ansible_test_5' {{':'}} ARP Flooding has to be disabled if L2 Stretch enabled and BUM traffic disabled exception while trying to update schema")
+ # - nm_change_bd_5_3.msg is match ("MSO Error 400{{':'}} Bad Request{{':'}} Patch Failed, Received{{':'}} Template 'Template5', BD 'ansible_test_5' {{':'}} ARP Flooding has to be disabled if L2 Stretch enabled and BUM traffic disabled exception while trying to update schema")
+ when:
+ - version.current.version is version('3.3', '<')
+ - version.current.version is version('3.1.1g', '>=')
+
+- name: Verify nm_change_bd_5_3 for a version that's after 4.0
+ assert:
+ that:
+ - nm_change_bd_5_3 is changed
+ - nm_change_bd_5_3.msg is match ("MSO Error 400{{':'}} BD{{':'}} ansible_test_5 in Schema{{':'}} ansible_test_2 , Template{{':'}} Template5 BD ansible_test_5 l2UnknownUnicast cannot be flood when intersiteBumTrafficAllow is off")
+ when:
+ - version.current.version is version('4.0', '>=')
+
+- name: Get Validation status
+ mso_schema_validate:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ state: query
+ ignore_errors: true
+ register: query_validate
+ when:
+ - version.current.version is version('3.3', '>=')
+ - version.current.version is version('4.0', '<') # mso_schema_validate not needed after 4.0 because validation is done upon request
+
+- name: Verify query_validate for a version that's after 3.3
+ assert:
+ that:
+ - query_validate is not changed
+ - query_validate.msg is match ("MSO Error 400{{':'}} Bad Request{{':'}} Patch Failed, Received{{':'}} Template 'Template5', BD 'ansible_test_5' {{':'}} ARP Flooding has to be disabled if L2 Stretch enabled and BUM traffic disabled exception while trying to update schema")
+ when:
+ - version.current.version is version('3.3', '>=')
+ - version.current.version is version('3.7', '<')
+
+# Reverting back to bd 5_2
+- name: Change bd 5_3 (normal mode)
+ mso_schema_template_bd:
+ <<: *change_bd5_2
+ register: nm_change_bd_5_3_again
+ when: query_validate is failed
+
+- name: Get Validation status
+ mso_schema_validate:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ state: query
+ register: query_validate
+ when:
+ - version.current.version is version('3.3', '>=')
+ - version.current.version is version('4.0', '<') # mso_schema_validate not needed after 4.0 because validation is done upon request
+
+- name: Change bd 5 for query (normal mode)
+ mso_schema_template_bd:
+ <<: *bd_present
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template5
+ bd: ansible_test_5
+ intersite_bum_traffic: true
+ optimize_wan_bandwidth: true
+ layer2_stretch: true
+ layer2_unknown_unicast: flood
+ layer3_multicast: false
+ unknown_multicast_flooding: flood
+ multi_destination_flooding: drop
+ ipv6_unknown_multicast_flooding: flood
+ arp_flooding: true
+ virtual_mac_address: 00:00:5E:00:01:3C
+ vrf:
+ name: VRF5
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template5
+ dhcp_policy:
+ name: ansible_test
+ version: 1
+ dhcp_option_policy:
+ name: ansible_test_option
+ version: 1
+
+# CHANGE BD
+- name: Change bd (check_mode)
+ mso_schema_template_bd:
+ <<: *bd_present
+ vrf:
+ name: VRF2
+ check_mode: true
+ register: cm_change_bd
+
+- name: Verify cm_change_bd
+ assert:
+ that:
+ - cm_change_bd is changed
+ - cm_change_bd.current.name == 'ansible_test_1'
+ - cm_change_bd.current.vrfRef.vrfName == 'VRF2'
+ - cm_change_bd.current.vrfRef.templateName == "Template1"
+ - cm_change_bd.current.vrfRef.schemaId == cm_change_bd.previous.vrfRef.schemaId
+
+- name: Change bd (normal mode)
+ mso_schema_template_bd:
+ <<: *bd_present
+ vrf:
+ name: VRF2
+ output_level: debug
+ register: nm_change_bd
+
+- name: Verify nm_change_bd
+ assert:
+ that:
+ - nm_change_bd is changed
+ - nm_change_bd.current.name == 'ansible_test_1'
+ - nm_change_bd.current.vrfRef.vrfName == 'VRF2'
+ - nm_change_bd.current.vrfRef.templateName == "Template1"
+ - nm_change_bd.current.vrfRef.schemaId == nm_change_bd.previous.vrfRef.schemaId
+
+- name: Change bd again (check_mode)
+ mso_schema_template_bd:
+ <<: *bd_present
+ vrf:
+ name: VRF2
+ check_mode: true
+ register: cm_change_bd_again
+
+- name: Verify cm_change_bd_again
+ assert:
+ that:
+ - cm_change_bd_again is not changed
+ - cm_change_bd_again.current.name == 'ansible_test_1'
+ - cm_change_bd_again.current.vrfRef.vrfName == 'VRF2'
+ - cm_change_bd_again.current.vrfRef.templateName == "Template1"
+ - cm_change_bd_again.current.vrfRef.schemaId == cm_change_bd_again.previous.vrfRef.schemaId
+
+- name: Change bd again (normal mode)
+ mso_schema_template_bd:
+ <<: *bd_present
+ vrf:
+ name: VRF2
+ register: nm_change_bd_again
+
+- name: Verify nm_change_bd_again
+ assert:
+ that:
+ - nm_change_bd_again is not changed
+ - nm_change_bd_again.current.name == 'ansible_test_1'
+ - nm_change_bd_again.current.vrfRef.vrfName == 'VRF2'
+ - nm_change_bd_again.current.vrfRef.templateName == "Template1"
+ - nm_change_bd_again.current.vrfRef.schemaId == nm_change_bd_again.previous.vrfRef.schemaId
+
+- name: Change bd to VRF3 in other template (normal mode)
+ mso_schema_template_bd:
+ <<: *bd_present
+ vrf:
+ name: VRF3
+ template: Template2
+ register: nm_change_bd_vrf3
+
+- name: Change bd to VRF4 in other schema (normal mode)
+ mso_schema_template_bd:
+ <<: *bd_present
+ vrf:
+ name: VRF4
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template3
+ register: nm_change_bd_vrf4
+
+- name: Verify nm_change_bd_vrf3 and nm_change_bd_vrf4
+ assert:
+ that:
+ - nm_change_bd_vrf3 is changed
+ - nm_change_bd_vrf3.current.name == nm_change_bd_vrf4.current.name == 'ansible_test_1'
+ - nm_change_bd_vrf3.current.vrfRef.vrfName == 'VRF3'
+ - nm_change_bd_vrf3.current.vrfRef.templateName == "Template2"
+ - nm_change_bd_vrf4.current.vrfRef.vrfName == 'VRF4'
+ - nm_change_bd_vrf4.current.vrfRef.templateName == "Template3"
+
+- name: Change bd 1 settings(normal mode)
+ mso_schema_template_bd:
+ <<: *bd_present
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ intersite_bum_traffic: true
+ optimize_wan_bandwidth: true
+ layer2_stretch: true
+ layer2_unknown_unicast: flood
+ layer3_multicast: '{{ mso_l3mcast | default(true) }}'
+ subnets:
+ - subnet: 10.1.0.128/24
+ - subnet: 10.1.1.254/24
+ description: 1234567890
+ - subnet: 172.17.0.1/24
+ description: "My description for a subnet"
+ scope: public
+ shared: true
+ no_default_gateway: false
+ querier: true
+ - ip: 192.168.1.254/24
+ description: "My description for a subnet"
+ scope: private
+ shared: false
+ no_default_gateway: true
+ vrf:
+ name: VRF3
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template2
+ register: nm_change_bd_1_settings
+
+- name: Change bd 1 subnets (normal mode)
+ mso_schema_template_bd:
+ <<: *bd_present
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ intersite_bum_traffic: true
+ optimize_wan_bandwidth: true
+ layer2_stretch: true
+ layer2_unknown_unicast: flood
+ layer3_multicast: '{{ mso_l3mcast | default(true) }}'
+ subnets:
+ - subnet: 10.1.0.127/24
+ - subnet: 172.17.0.1/24
+ description: "New description for a subnet"
+ scope: private
+ shared: false
+ no_default_gateway: false
+ querier: false
+ - ip: 192.168.1.254/24
+ description: "My description for a subnet"
+ scope: private
+ shared: false
+ no_default_gateway: false
+ querier: true
+ vrf:
+ name: VRF3
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template2
+ register: nm_change_bd_1_subnets
+
+- name: Verify nm_change_bd_1_subnets
+ assert:
+ that:
+ - nm_change_bd_1_settings is changed
+ - nm_change_bd_1_settings.current.name == "ansible_test_1"
+ - nm_change_bd_1_settings.current.vrfRef.templateName == "Template2"
+ - nm_change_bd_1_settings.current.vrfRef.vrfName == "VRF3"
+ - nm_change_bd_1_settings.current.intersiteBumTrafficAllow == true
+ - nm_change_bd_1_settings.current.optimizeWanBandwidth == true
+ - nm_change_bd_1_settings.current.l2Stretch == true
+ - nm_change_bd_1_settings.current.l2UnknownUnicast == "flood"
+ - nm_change_bd_1_settings.current.subnets[0].description == "10.1.0.128/24"
+ - nm_change_bd_1_settings.current.subnets[0].ip == "10.1.0.128/24"
+ - nm_change_bd_1_settings.current.subnets[0].noDefaultGateway == false
+ - nm_change_bd_1_settings.current.subnets[0].scope == "private"
+ - nm_change_bd_1_settings.current.subnets[0].shared == false
+ - nm_change_bd_1_settings.current.subnets[0].querier == false
+ - nm_change_bd_1_settings.current.subnets[1].description == "1234567890"
+ - nm_change_bd_1_settings.current.subnets[1].ip == "10.1.1.254/24"
+ - nm_change_bd_1_settings.current.subnets[1].noDefaultGateway == false
+ - nm_change_bd_1_settings.current.subnets[1].scope == "private"
+ - nm_change_bd_1_settings.current.subnets[1].shared == false
+ - nm_change_bd_1_settings.current.subnets[1].querier == false
+ - nm_change_bd_1_settings.current.subnets[2].description == "My description for a subnet"
+ - nm_change_bd_1_settings.current.subnets[2].ip == "172.17.0.1/24"
+ - nm_change_bd_1_settings.current.subnets[2].noDefaultGateway == false
+ - nm_change_bd_1_settings.current.subnets[2].scope == "public"
+ - nm_change_bd_1_settings.current.subnets[2].shared == true
+ - nm_change_bd_1_settings.current.subnets[2].querier == true
+ - nm_change_bd_1_settings.current.subnets[3].description == "My description for a subnet"
+ - nm_change_bd_1_settings.current.subnets[3].ip == "192.168.1.254/24"
+ - nm_change_bd_1_settings.current.subnets[3].noDefaultGateway == true
+ - nm_change_bd_1_settings.current.subnets[3].scope == "private"
+ - nm_change_bd_1_settings.current.subnets[3].shared == false
+ - nm_change_bd_1_settings.current.subnets[3].querier == false
+ - nm_change_bd_1_settings is changed
+ - nm_change_bd_1_subnets.current.subnets | length == 3
+ - nm_change_bd_1_subnets.current.name == "ansible_test_1"
+ - nm_change_bd_1_subnets.current.vrfRef.templateName == "Template2"
+ - nm_change_bd_1_subnets.current.vrfRef.vrfName == "VRF3"
+ - nm_change_bd_1_subnets.current.intersiteBumTrafficAllow == true
+ - nm_change_bd_1_subnets.current.optimizeWanBandwidth == true
+ - nm_change_bd_1_subnets.current.l2Stretch == true
+ - nm_change_bd_1_subnets.current.l2UnknownUnicast == "flood"
+ - nm_change_bd_1_subnets.current.subnets[0].description == "10.1.0.127/24"
+ - nm_change_bd_1_subnets.current.subnets[0].ip == "10.1.0.127/24"
+ - nm_change_bd_1_subnets.current.subnets[0].noDefaultGateway == false
+ - nm_change_bd_1_subnets.current.subnets[0].scope == "private"
+ - nm_change_bd_1_subnets.current.subnets[0].shared == false
+ - nm_change_bd_1_subnets.current.subnets[0].querier == false
+ - nm_change_bd_1_subnets.current.subnets[1].description == "New description for a subnet"
+ - nm_change_bd_1_subnets.current.subnets[1].ip == "172.17.0.1/24"
+ - nm_change_bd_1_subnets.current.subnets[1].noDefaultGateway == false
+ - nm_change_bd_1_subnets.current.subnets[1].scope == "private"
+ - nm_change_bd_1_subnets.current.subnets[1].shared == false
+ - nm_change_bd_1_subnets.current.subnets[1].querier == false
+ - nm_change_bd_1_subnets.current.subnets[2].description == "My description for a subnet"
+ - nm_change_bd_1_subnets.current.subnets[2].ip == "192.168.1.254/24"
+ - nm_change_bd_1_subnets.current.subnets[2].noDefaultGateway == false
+ - nm_change_bd_1_subnets.current.subnets[2].scope == "private"
+ - nm_change_bd_1_subnets.current.subnets[2].shared == false
+ - nm_change_bd_1_subnets.current.subnets[2].querier == true
+
+- name: Verify l3MCast nm_change_bd_1_subnets (version == 2.2.4)
+ assert:
+ that:
+ - nm_change_bd_1_settings.current.l3MCast == false
+ - nm_change_bd_1_subnets.current.l3MCast == false
+ when: version.current.version is version('2.2.4', '=')
+
+- name: Verify l3MCast nm_change_bd_1_subnets (version != 2.2.4)
+ assert:
+ that:
+ - nm_change_bd_1_settings.current.l3MCast == true
+ - nm_change_bd_1_subnets.current.l3MCast == true
+ when: version.current.version is version('2.2.4', '!=')
+
+- name: Add bd with multiple dhcp policies for mso version > 3.1.1g
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_multiple_dhcp
+ intersite_bum_traffic: true
+ optimize_wan_bandwidth: false
+ layer2_stretch: true
+ layer2_unknown_unicast: flood
+ layer3_multicast: false
+ unknown_multicast_flooding: flood
+ multi_destination_flooding: drop
+ ipv6_unknown_multicast_flooding: flood
+ arp_flooding: false
+ virtual_mac_address: 00:00:5E:00:01:3C
+ subnets:
+ - subnet: 10.0.0.128/24
+ - subnet: 10.0.1.254/24
+ description: 1234567890
+ - ip: 192.168.0.254/24
+ description: "My description for a subnet"
+ scope: private
+ shared: false
+ no_default_gateway: true
+ vrf:
+ name: VRF
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ dhcp_policies:
+ - name: ansible_test_dhcp_policy1
+ version: 1
+ dhcp_option_policy:
+ name: ansible_test_dhcp_policy_option1
+ version: 1
+ - name: ansible_test_dhcp_policy2
+ version: 1
+ dhcp_option_policy:
+ name: ansible_test_dhcp_policy_option1
+ version: 1
+ - name: ansible_test_dhcp_policy3
+ version: 1
+ dhcp_option_policy:
+ name: ansible_test_dhcp_policy_option2
+ version: 1
+ state: present
+ register: nm_bd_dhcp_policies
+ when:
+ - version.current.version is version('3.1.1g', '>')
+ # TODO dhcp policies api endpoint has changed after 4.0, fix when new module is created
+ - version.current.version is version('4.0', '<')
+
+- name: Verify addition of DHCP policies for mso version > 3.1.1g
+ assert:
+ that:
+ - nm_bd_dhcp_policies is changed
+ - nm_bd_dhcp_policies.current.dhcpLabels | length == 3
+ - nm_bd_dhcp_policies.current.dhcpLabels[0].name == 'ansible_test_dhcp_policy1'
+ - nm_bd_dhcp_policies.current.dhcpLabels[1].name == 'ansible_test_dhcp_policy2'
+ - nm_bd_dhcp_policies.current.dhcpLabels[2].name == 'ansible_test_dhcp_policy3'
+ - nm_bd_dhcp_policies.current.dhcpLabels[0].version == 1
+ - nm_bd_dhcp_policies.current.dhcpLabels[1].version == 1
+ - nm_bd_dhcp_policies.current.dhcpLabels[2].version == 1
+ - nm_bd_dhcp_policies.current.dhcpLabels[0].dhcpOptionLabel.name == 'ansible_test_dhcp_policy_option1'
+ - nm_bd_dhcp_policies.current.dhcpLabels[1].dhcpOptionLabel.name == 'ansible_test_dhcp_policy_option1'
+ - nm_bd_dhcp_policies.current.dhcpLabels[2].dhcpOptionLabel.name == 'ansible_test_dhcp_policy_option2'
+ - nm_bd_dhcp_policies.current.dhcpLabels[0].dhcpOptionLabel.version == 1
+ - nm_bd_dhcp_policies.current.dhcpLabels[1].dhcpOptionLabel.version == 1
+ - nm_bd_dhcp_policies.current.dhcpLabels[2].dhcpOptionLabel.version == 1
+ when:
+ - version.current.version is version('3.1.1g', '>')
+ # TODO dhcp policies api endpoint has changed after 4.0, fix when new module is created
+ - version.current.version is version('4.0', '<')
+
+- name: Change bd with multiple dhcp policies for mso version > 3.1.1g
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_multiple_dhcp
+ intersite_bum_traffic: true
+ optimize_wan_bandwidth: false
+ layer2_stretch: true
+ layer2_unknown_unicast: flood
+ layer3_multicast: false
+ unknown_multicast_flooding: flood
+ multi_destination_flooding: drop
+ ipv6_unknown_multicast_flooding: flood
+ arp_flooding: false
+ virtual_mac_address: 00:00:5E:00:01:3C
+ subnets:
+ - subnet: 10.0.0.128/24
+ - subnet: 10.0.1.254/24
+ description: 1234567890
+ - ip: 192.168.0.254/24
+ description: "My description for a subnet"
+ scope: private
+ shared: false
+ no_default_gateway: true
+ vrf:
+ name: VRF
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ dhcp_policies:
+ - name: ansible_test_dhcp_policy1
+ version: 1
+ dhcp_option_policy:
+ name: ansible_test_dhcp_policy_option1
+ version: 1
+ - name: ansible_test_dhcp_policy2
+ version: 1
+ dhcp_option_policy:
+ name: ansible_test_dhcp_policy_option1
+ version: 1
+ state: present
+ register: change_nm_bd_dhcp_policies
+ when:
+ - version.current.version is version('3.1.1g', '>')
+ # TODO dhcp policies api endpoint has changed after 4.0, fix when new module is created
+ - version.current.version is version('4.0', '<')
+
+- name: Verify change in DHCP policies for mso version > 3.1.1g
+ assert:
+ that:
+ - change_nm_bd_dhcp_policies is changed
+ - change_nm_bd_dhcp_policies.current.dhcpLabels | length == 2
+ - change_nm_bd_dhcp_policies.current.dhcpLabels[0].name == 'ansible_test_dhcp_policy1'
+ - change_nm_bd_dhcp_policies.current.dhcpLabels[1].name == 'ansible_test_dhcp_policy2'
+ - change_nm_bd_dhcp_policies.current.dhcpLabels[0].version == 1
+ - change_nm_bd_dhcp_policies.current.dhcpLabels[1].version == 1
+ - change_nm_bd_dhcp_policies.current.dhcpLabels[0].dhcpOptionLabel.name == 'ansible_test_dhcp_policy_option1'
+ - change_nm_bd_dhcp_policies.current.dhcpLabels[1].dhcpOptionLabel.name == 'ansible_test_dhcp_policy_option1'
+ - change_nm_bd_dhcp_policies.current.dhcpLabels[0].dhcpOptionLabel.version == 1
+ - change_nm_bd_dhcp_policies.current.dhcpLabels[1].dhcpOptionLabel.version == 1
+ when:
+ - version.current.version is version('3.1.1g', '>')
+ # TODO dhcp policies api endpoint has changed after 4.0, fix when new module is created
+ - version.current.version is version('4.0', '<')
+
+# Add BD with new options for mso version > 3.1.1g
+- name: Add bd with new options available in mso versions > 3.1.1g
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_new_options
+ intersite_bum_traffic: true
+ optimize_wan_bandwidth: false
+ layer2_stretch: true
+ layer2_unknown_unicast: flood
+ layer3_multicast: false
+ unknown_multicast_flooding: flood
+ multi_destination_flooding: drop
+ ipv6_unknown_multicast_flooding: flood
+ arp_flooding: false
+ virtual_mac_address: 00:00:5E:00:01:3C
+ unicast_routing: true
+ subnets:
+ - subnet: 10.0.0.128/24
+ - subnet: 10.0.1.254/24
+ description: 1234567890
+ - ip: 192.168.0.254/24
+ description: "My description for a subnet"
+ scope: private
+ shared: false
+ no_default_gateway: true
+ virtual: true
+ primary: true
+ vrf:
+ name: VRF
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ state: present
+ register: nm_bd_new_subnet_options
+ when: version.current.version is version('3.1.1g', '>')
+
+- name: Verify subnets in nm_bd_new_subnet_options
+ assert:
+ that:
+ - nm_bd_new_subnet_options is changed
+ - nm_bd_new_subnet_options.current.name == 'ansible_test_new_options'
+ - nm_bd_new_subnet_options.current.vrfRef.templateName == "Template1"
+ - nm_bd_new_subnet_options.current.vrfRef.vrfName == "VRF"
+ - nm_bd_new_subnet_options.current.intersiteBumTrafficAllow == true
+ - nm_bd_new_subnet_options.current.optimizeWanBandwidth == false
+ - nm_bd_new_subnet_options.current.l2Stretch == true
+ - nm_bd_new_subnet_options.current.l2UnknownUnicast == "flood"
+ - nm_bd_new_subnet_options.current.unkMcastAct == "flood"
+ - nm_bd_new_subnet_options.current.multiDstPktAct == "drop"
+ - nm_bd_new_subnet_options.current.l3MCast == false
+ - nm_bd_new_subnet_options.current.arpFlood == true
+ - nm_bd_new_subnet_options.current.v6unkMcastAct == "flood"
+ - nm_bd_new_subnet_options.current.vmac == "00:00:5E:00:01:3C"
+ - nm_bd_new_subnet_options.current.unicastRouting == true
+ - nm_bd_new_subnet_options.current.subnets[1].primary == false
+ - nm_bd_new_subnet_options.current.subnets[0].virtual == false
+ - nm_bd_new_subnet_options.current.subnets[2].primary == true
+ - nm_bd_new_subnet_options.current.subnets[2].virtual == true
+ when: version.current.version is version('3.1.1g', '>')
+
+# Change BD with new options for mso version > 3.1.1g
+- name: Try Changing bd with another subnet to primary IP
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_new_options
+ intersite_bum_traffic: true
+ optimize_wan_bandwidth: false
+ layer2_stretch: true
+ layer2_unknown_unicast: flood
+ layer3_multicast: false
+ unknown_multicast_flooding: flood
+ multi_destination_flooding: drop
+ ipv6_unknown_multicast_flooding: flood
+ arp_flooding: false
+ virtual_mac_address: 00:00:5E:00:01:3C
+ unicast_routing: true
+ subnets:
+ - subnet: 10.0.0.128/24
+ - subnet: 10.0.1.254/24
+ description: 1234567890
+ primary: true
+ - ip: 192.168.0.254/24
+ description: "My description for a subnet"
+ scope: private
+ shared: false
+ no_default_gateway: true
+ virtual: true
+ primary: true
+ vrf:
+ name: VRF
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ state: present
+ register: nm_bd_subnet_second_primary
+ ignore_errors: true
+ when: version.current.version is version('3.1.1g', '>')
+
+- name: Verify subnets in nm_bd_subnet_second_primary
+ assert:
+ that:
+ - nm_bd_subnet_second_primary.msg is match ("MSO Error 400{{':'}} Bad Request{{':'}} Patch Failed, Received{{':'}} Only one preferred subnet per address family is allowed under BD ansible_test_new_options of Template Template1 exception while trying to update schema")
+ when:
+ - version.current.version is version('3.1.1g', '>')
+ - version.current.version is version('3.3', '<')
+
+- name: Add bd with new option flood in encap available in mso versions > 3.1.1g with l2 set to true
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_flood_encap
+ intersite_bum_traffic: true
+ optimize_wan_bandwidth: false
+ layer2_stretch: true
+ layer2_unknown_unicast: flood
+ layer3_multicast: false
+ unknown_multicast_flooding: flood
+ multi_destination_flooding: encap-flood
+ ipv6_unknown_multicast_flooding: flood
+ unicast_routing: true
+ vrf:
+ name: VRF
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ state: present
+ ignore_errors: true
+ register: nm_bd_new_encap_flood_non
+ when: version.current.version is version('3.1.1g', '>')
+
+- name: Verify nm_bd_new_encap_flood_non
+ assert:
+ that:
+ - nm_bd_new_encap_flood_non.msg is match ("MSO Error 400{{':'}} Bad Request{{':'}} Patch Failed, Received{{':'}} Template 'Template1', BD 'ansible_test_flood_encap' {{':'}} Multi destination flood in encapsulation is only supported when l2Stretch is disabled exception while trying to update schema")
+ when:
+ - version.current.version is version('3.1.1g', '>')
+ - version.current.version is version('3.3', '<')
+
+- name: Add bd with new option flood in encap available in mso versions > 3.1.1g l2 set to false
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_flood_encap
+ layer2_stretch: false
+ layer2_unknown_unicast: flood
+ unknown_multicast_flooding: flood
+ multi_destination_flooding: encap-flood
+ ipv6_unknown_multicast_flooding: flood
+ unicast_routing: true
+ intersite_bum_traffic: false
+ vrf:
+ name: VRF
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ state: present
+ register: nm_bd_new_encap_flood
+ when: version.current.version is version('3.1.1g', '>')
+
+- name: Verify nm_bd_new_encap_flood
+ assert:
+ that:
+ - nm_bd_new_encap_flood.current.multiDstPktAct == "encap-flood"
+ when: version.current.version is version('3.1.1g', '>')
+
+- name: Add bd with new option description in mso versions >= 3.3
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_description
+ description: ansible_test_bd
+ layer2_stretch: true
+ layer2_unknown_unicast: proxy
+ unknown_multicast_flooding: flood
+ ipv6_unknown_multicast_flooding: flood
+ unicast_routing: true
+ vrf:
+ name: VRF
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ state: present
+ register: nm_bd_desc
+ when: version.current.version is version('3.3', '>=')
+
+- name: Verify nm_bd_desc
+ assert:
+ that:
+ - nm_bd_desc.current.description == "ansible_test_bd"
+ when: version.current.version is version('3.3', '>=')
+
+- name: Add bd with change in description in mso versions >= 3.3
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_description
+ description: ansible_test_bd_again
+ layer2_stretch: true
+ layer2_unknown_unicast: proxy
+ unknown_multicast_flooding: flood
+ ipv6_unknown_multicast_flooding: flood
+ unicast_routing: true
+ vrf:
+ name: VRF
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ state: present
+ register: nm_bd_desc_2
+ when: version.current.version is version('3.3', '>=')
+
+- name: Verify nm_bd_desc_2
+ assert:
+ that:
+ - nm_bd_desc_2.current.description == "ansible_test_bd_again"
+ when: version.current.version is version('3.3', '>=')
+
+- name: Ensure bd ansible_test_unicast_false is removed >= 3.1.1g
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_unicast_false
+ state: absent
+ when: version.current.version is version('3.1.1g', '>')
+
+- name: Ensure bd ansible_test_unicast_true is removed >= 3.1.1g
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_unicast_true
+ state: absent
+ when: version.current.version is version('3.1.1g', '>')
+
+- name: Add bd with change in unicast routing false in mso versions >= 3.1.1g (check mode)
+ mso_schema_template_bd: &unicast_routing_false_present
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_unicast_false
+ unicast_routing: false
+ vrf:
+ name: VRF
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ state: present
+ check_mode: true
+ register: cm_ansible_test_unicast_false
+ when: version.current.version is version('3.1.1g', '>')
+
+- name: Add bd with change in unicast routing false in mso versions >= 3.1.1g
+ mso_schema_template_bd:
+ <<: *unicast_routing_false_present
+ register: nm_ansible_test_unicast_false
+ when: version.current.version is version('3.1.1g', '>')
+
+- name: Add bd again with unicast routing false in mso versions >= 3.1.1g
+ mso_schema_template_bd:
+ <<: *unicast_routing_false_present
+ register: nm_ansible_test_unicast_false_again
+ when: version.current.version is version('3.1.1g', '>')
+
+- name: Change bd with unicast routing to true in mso versions >= 3.1.1g
+ mso_schema_template_bd:
+ <<: *unicast_routing_false_present
+ unicast_routing: true
+ register: nm_ansible_test_unicast_false_to_true
+ when: version.current.version is version('3.1.1g', '>')
+
+- name: Add bd with change in unicast routing true in mso versions >= 3.1.1g (check mode)
+ mso_schema_template_bd: &unicast_routing_true_present
+ <<: *unicast_routing_false_present
+ bd: ansible_test_unicast_true
+ unicast_routing: true
+ check_mode: true
+ register: cm_ansible_test_unicast_true
+ when: version.current.version is version('3.1.1g', '>')
+
+- name: Add bd with change in unicast routing true in mso versions >= 3.1.1g
+ mso_schema_template_bd:
+ <<: *unicast_routing_true_present
+ register: nm_ansible_test_unicast_true
+ when: version.current.version is version('3.1.1g', '>')
+
+- name: Add bd again with unicast routing true in mso versions >= 3.1.1g
+ mso_schema_template_bd:
+ <<: *unicast_routing_true_present
+ register: nm_ansible_test_unicast_true_again
+ when: version.current.version is version('3.1.1g', '>')
+
+- name: Change bd with unicast routing to false in mso versions >= 3.1.1g
+ mso_schema_template_bd:
+ <<: *unicast_routing_true_present
+ unicast_routing: false
+ register: nm_ansible_test_unicast_true_to_false
+ when: version.current.version is version('3.1.1g', '>')
+
+- name: Verify unicast routing
+ assert:
+ that:
+ - cm_ansible_test_unicast_false is changed
+ - cm_ansible_test_unicast_false.current.unicastRouting == false
+ - nm_ansible_test_unicast_false is changed
+ - nm_ansible_test_unicast_false.current.unicastRouting == false
+ - nm_ansible_test_unicast_false_again is not changed
+ - nm_ansible_test_unicast_false_again.previous.unicastRouting == false
+ - nm_ansible_test_unicast_false_again.current.unicastRouting == false
+ - nm_ansible_test_unicast_false_to_true is changed
+ - nm_ansible_test_unicast_false_to_true.current.unicastRouting == true
+ - cm_ansible_test_unicast_true is changed
+ - cm_ansible_test_unicast_true.current.unicastRouting == true
+ - nm_ansible_test_unicast_true is changed
+ - nm_ansible_test_unicast_true.current.unicastRouting == true
+ - nm_ansible_test_unicast_true_again is not changed
+ - nm_ansible_test_unicast_true_again.previous.unicastRouting == true
+ - nm_ansible_test_unicast_true_again.current.unicastRouting == true
+ - nm_ansible_test_unicast_true_to_false is changed
+ - nm_ansible_test_unicast_true_to_false.current.unicastRouting == false
+ when: version.current.version is version('3.1.1g', '>')
+
+- name: Remove bd with change in unicast routing false in mso versions >= 3.1.1g
+ mso_schema_template_bd:
+ <<: *unicast_routing_false_present
+ state: absent
+ register: ansible_test_unicast_false
+ when: version.current.version is version('3.1.1g', '>')
+
+- name: Remove bd with change in unicast routing true in mso versions >= 3.1.1g
+ mso_schema_template_bd:
+ <<: *unicast_routing_true_present
+ state: absent
+ register: ansible_test_unicast_true
+ when: version.current.version is version('3.1.1g', '>')
+
+# FIXME: Add missing DHCP Policy changes and checks (missing DHCP Policy module to make sure it is there.)
+
+# QUERY ALL BD
+- name: Query all bd (check_mode)
+ mso_schema_template_bd: &bd_query
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ state: query
+ check_mode: true
+ register: cm_query_all_bds
+
+- name: Query all bd (normal mode)
+ mso_schema_template_bd:
+ <<: *bd_query
+ register: nm_query_all_bds
+
+- name: Verify query_all_bds for version < 3.1.1g
+ assert:
+ that:
+ - cm_query_all_bds is not changed
+ - nm_query_all_bds is not changed
+ - cm_query_all_bds.current | length == nm_query_all_bds.current | length == 2
+ when: version.current.version is version('3.1.1g', '<')
+
+- name: Verify query_all_bds for version > 3.1.1g and version < 3.3
+ assert:
+ that:
+ - cm_query_all_bds is not changed
+ - nm_query_all_bds is not changed
+ - cm_query_all_bds.current | length == nm_query_all_bds.current | length == 5
+ when:
+ - version.current.version is version('3.1.1g', '>')
+ - version.current.version is version('3.3', '<')
+
+- name: Verify query_all_bds for version >= 3.3 and < 4.0
+ assert:
+ that:
+ - cm_query_all_bds is not changed
+ - nm_query_all_bds is not changed
+ - cm_query_all_bds.current | length == nm_query_all_bds.current | length == 6
+ when:
+ - version.current.version is version('3.3', '>=')
+ - version.current.version is version('3.7', '<')
+
+- name: Verify query_all_bds for version >= 4.0
+ assert:
+ that:
+ - cm_query_all_bds is not changed
+ - nm_query_all_bds is not changed
+ - cm_query_all_bds.current | length == nm_query_all_bds.current | length == 5
+ when: version.current.version is version('4.0', '>=')
+
+# QUERY A BD
+- name: Query bd 1
+ mso_schema_template_bd:
+ <<: *bd_query
+ bd: ansible_test_1
+ check_mode: true
+ register: cm_query_bd
+
+- name: Query bd 1
+ mso_schema_template_bd:
+ <<: *bd_query
+ bd: ansible_test_1
+ register: nm_query_bd
+
+- name: Query bd 2
+ mso_schema_template_bd:
+ <<: *bd_query
+ template: Template2
+ bd: ansible_test_2
+ register: nm_query_bd_2
+
+- name: Query bd 3
+ mso_schema_template_bd:
+ <<: *bd_query
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template3
+ bd: ansible_test_3
+ register: nm_query_bd_3
+
+- name: Query bd 5
+ mso_schema_template_bd:
+ <<: *bd_query
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template5
+ bd: ansible_test_5
+ register: nm_query_bd_5
+
+- name: Verify query_bd
+ assert:
+ that:
+ - cm_query_bd is not changed
+ - nm_query_bd is not changed
+ - cm_query_bd.current.name == nm_query_bd.current.name == "ansible_test_1"
+ - cm_query_bd == nm_query_bd
+ - nm_query_bd_2.current.name == "ansible_test_2"
+ - nm_query_bd_3.current.name == "ansible_test_3"
+ - nm_query_bd_2.current.intersiteBumTrafficAllow == true
+ - nm_query_bd_2.current.optimizeWanBandwidth == true
+ - nm_query_bd_2.current.l2Stretch == true
+ - nm_query_bd_2.current.l2UnknownUnicast == "flood"
+ - nm_query_bd_2.current.l3MCast == true
+ - nm_query_bd_2.current.subnets[0].description == "10.0.0.128/24"
+ - nm_query_bd_2.current.subnets[0].ip == "10.0.0.128/24"
+ - nm_query_bd_2.current.subnets[0].noDefaultGateway == false
+ - nm_query_bd_2.current.subnets[0].scope == "private"
+ - nm_query_bd_2.current.subnets[0].shared == false
+ - nm_query_bd_2.current.subnets[1].description == "1234567890"
+ - nm_query_bd_2.current.subnets[1].ip == "10.0.1.254/24"
+ - nm_query_bd_2.current.subnets[1].noDefaultGateway == false
+ - nm_query_bd_2.current.subnets[1].scope == "private"
+ - nm_query_bd_2.current.subnets[1].shared == false
+ - nm_query_bd_2.current.subnets[2].description == "My description for a subnet"
+ - nm_query_bd_2.current.subnets[2].ip == "172.16.0.1/24"
+ - nm_query_bd_2.current.subnets[2].noDefaultGateway == false
+ - nm_query_bd_2.current.subnets[2].scope == "public"
+ - nm_query_bd_2.current.subnets[2].shared == true
+ - nm_query_bd_2.current.subnets[3].description == "My description for a subnet"
+ - nm_query_bd_2.current.subnets[3].ip == "192.168.0.254/24"
+ - nm_query_bd_2.current.subnets[3].noDefaultGateway == true
+ - nm_query_bd_2.current.subnets[3].scope == "private"
+ - nm_query_bd_2.current.subnets[3].shared == false
+
+- name: Verify nm_query_bd_5 for a version that's before 3.1
+ assert:
+ that:
+ - nm_query_bd_5 is not changed
+ - nm_query_bd_5.current.name == "ansible_test_5"
+ - nm_query_bd_5.current.intersiteBumTrafficAllow == true
+ - nm_query_bd_5.current.optimizeWanBandwidth == true
+ - nm_query_bd_5.current.l2Stretch == true
+ - nm_query_bd_5.current.l2UnknownUnicast == "flood"
+ - nm_query_bd_5.current.l3MCast == false
+ when: version.current.version is version('3.1.1g', '<')
+
+- name: Verify nm_query_bd_5 for a version that's 3.1
+ assert:
+ that:
+ - nm_query_bd_5 is not changed
+ - nm_query_bd_5.current.name == "ansible_test_5"
+ - nm_query_bd_5.current.intersiteBumTrafficAllow == true
+ - nm_query_bd_5.current.optimizeWanBandwidth == true
+ - nm_query_bd_5.current.l2Stretch == true
+ - nm_query_bd_5.current.l2UnknownUnicast == "flood"
+ - nm_query_bd_5.current.l3MCast == false
+ - nm_query_bd_5.current.unkMcastAct == "flood"
+ - nm_query_bd_5.current.v6unkMcastAct == "flood"
+ - nm_query_bd_5.current.vmac == "00:00:5E:00:01:3C"
+ - nm_query_bd_5.current.multiDstPktAct == "drop"
+ - nm_query_bd_5.current.arpFlood == true
+ when: version.current.version is version('3.1.1g', '>=')
+
+- name: Query bd with multiple dhcp policies for mso version > 3.1.1g
+ mso_schema_template_bd:
+ <<: *bd_query
+ bd: ansible_test_multiple_dhcp
+ state: query
+ register: query_nm_bd_dhcp_policies
+ when:
+ - version.current.version is version('3.1.1g', '>')
+ # TODO dhcp policies api endpoint has changed after 4.0, fix when new module is created
+ - version.current.version is version('4.0', '<')
+
+- name: Verify query of DHCP policies for mso version > 3.1.1g
+ assert:
+ that:
+ - query_nm_bd_dhcp_policies is not changed
+ - query_nm_bd_dhcp_policies.current.name == 'ansible_test_multiple_dhcp'
+ - query_nm_bd_dhcp_policies.current.dhcpLabels | length == 2
+ - query_nm_bd_dhcp_policies.current.dhcpLabels[0].name == 'ansible_test_dhcp_policy1'
+ - query_nm_bd_dhcp_policies.current.dhcpLabels[1].name == 'ansible_test_dhcp_policy2'
+ - query_nm_bd_dhcp_policies.current.dhcpLabels[0].version == 1
+ - query_nm_bd_dhcp_policies.current.dhcpLabels[1].version == 1
+ - query_nm_bd_dhcp_policies.current.dhcpLabels[0].dhcpOptionLabel.name == 'ansible_test_dhcp_policy_option1'
+ - query_nm_bd_dhcp_policies.current.dhcpLabels[1].dhcpOptionLabel.name == 'ansible_test_dhcp_policy_option1'
+ - query_nm_bd_dhcp_policies.current.dhcpLabels[0].dhcpOptionLabel.version == 1
+ - query_nm_bd_dhcp_policies.current.dhcpLabels[1].dhcpOptionLabel.version == 1
+ when:
+ - version.current.version is version('3.1.1g', '>')
+ # TODO dhcp policies api endpoint has changed after 4.0, fix when new module is created
+ - version.current.version is version('4.0', '<')
+
+# Query BD with new options for mso version > 3.1.1g
+- name: Query bd with new option flood in encap available in mso versions > 3.1.1g with l2 set to false
+ mso_schema_template_bd:
+ <<: *bd_query
+ bd: ansible_test_flood_encap
+ state: query
+ register: query_bd_new_encap_flood
+ when: version.current.version is version('3.1.1g', '>')
+
+- name: Verify query_bd_new_encap_flood
+ assert:
+ that:
+ - query_bd_new_encap_flood is not changed
+ - query_bd_new_encap_flood.current.name == 'ansible_test_flood_encap'
+ - query_bd_new_encap_flood.current.intersiteBumTrafficAllow == false
+ - query_bd_new_encap_flood.current.optimizeWanBandwidth == false
+ - query_bd_new_encap_flood.current.l2Stretch == false
+ - query_bd_new_encap_flood.current.l2UnknownUnicast == "flood"
+ - query_bd_new_encap_flood.current.unkMcastAct == "flood"
+ - query_bd_new_encap_flood.current.multiDstPktAct == "encap-flood"
+ - query_bd_new_encap_flood.current.arpFlood == true
+ - query_bd_new_encap_flood.current.v6unkMcastAct == "flood"
+ - query_bd_new_encap_flood.current.unicastRouting == true
+ when: version.current.version is version('3.1.1g', '>')
+
+- name: Verify query_bd_new_encap_flood
+ assert:
+ that:
+ - query_bd_new_encap_flood.current.l3MCast == false
+ when: version.current.version is version('3.2', '>=')
+
+# REMOVE BD
+- name: Remove bd (check_mode)
+ mso_schema_template_bd: &bd_absent
+ <<: *bd_query
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ state: absent
+ check_mode: true
+ register: cm_remove_bd
+
+- name: Verify cm_remove_bd
+ assert:
+ that:
+ - cm_remove_bd is changed
+ - cm_remove_bd.current == {}
+
+- name: Remove bd (normal mode)
+ mso_schema_template_bd:
+ <<: *bd_absent
+ register: nm_remove_bd
+
+- name: Verify nm_remove_bd
+ assert:
+ that:
+ - nm_remove_bd is changed
+ - nm_remove_bd.current == {}
+
+- name: Remove bd again (check_mode)
+ mso_schema_template_bd:
+ <<: *bd_absent
+ check_mode: true
+ register: cm_remove_bd_again
+
+- name: Verify cm_remove_bd_again
+ assert:
+ that:
+ - cm_remove_bd_again is not changed
+ - cm_remove_bd_again.current == {}
+
+- name: Remove bd again (normal mode)
+ mso_schema_template_bd:
+ <<: *bd_absent
+ register: nm_remove_bd_again
+
+- name: Verify nm_remove_bd_again
+ assert:
+ that:
+ - nm_remove_bd_again is not changed
+ - nm_remove_bd_again.current == {}
+
+- name: Remove bd 5 (normal mode)
+ mso_schema_template_bd:
+ <<: *bd_absent
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template5
+ bd: ansible_test_5
+ register: nm_remove_bd_5
+
+- name: Verify nm_remove_bd_5
+ assert:
+ that:
+ - nm_remove_bd_5 is changed
+ - nm_remove_bd_5.current == {}
+
+- name: Remove bd ansible_test_multiple_dhcp (normal mode)
+ mso_schema_template_bd:
+ <<: *bd_absent
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_multiple_dhcp
+ register: nm_remove_ansible_test_multiple_dhcp
+ when:
+ - version.current.version is version('3.1.1g', '>')
+ # TODO dhcp policies api endpoint has changed after 4.0, fix when new module is created
+ - version.current.version is version('4.0', '<')
+
+- name: Verify nm_remove_ansible_test_multiple_dhcp
+ assert:
+ that:
+ - nm_remove_ansible_test_multiple_dhcp is changed
+ - nm_remove_ansible_test_multiple_dhcp.current == {}
+ when:
+ - version.current.version is version('3.1.1g', '>')
+ # TODO dhcp policies api endpoint has changed after 4.0, fix when new module is created
+ - version.current.version is version('4.0', '<')
+
+# QUERY NON-EXISTING BD
+- name: Query non-existing bd (check_mode)
+ mso_schema_template_bd:
+ <<: *bd_query
+ bd: ansible_test_1
+ check_mode: true
+ ignore_errors: true
+ register: cm_query_non_bd
+
+- name: Query non-existing bd (normal mode)
+ mso_schema_template_bd:
+ <<: *bd_query
+ bd: ansible_test_1
+ ignore_errors: true
+ register: nm_query_non_bd
+
+- name: Verify query_non_bd
+ assert:
+ that:
+ - cm_query_non_bd is not changed
+ - nm_query_non_bd is not changed
+ - cm_query_non_bd == nm_query_non_bd
+ - cm_query_non_bd.msg == nm_query_non_bd.msg == "BD 'ansible_test_1' not found"
+
+
+# USE A NON-EXISTING STATE
+- name: Non-existing state for bd (check_mode)
+ mso_schema_template_bd:
+ <<: *bd_query
+ state: non-existing-state
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_state
+
+- name: Non-existing state for bd (normal_mode)
+ mso_schema_template_bd:
+ <<: *bd_query
+ state: non-existing-state
+ ignore_errors: true
+ register: nm_non_existing_state
+
+- name: Verify non_existing_state
+ assert:
+ that:
+ - cm_non_existing_state is not changed
+ - nm_non_existing_state is not changed
+ - cm_non_existing_state == nm_non_existing_state
+ - cm_non_existing_state.msg == nm_non_existing_state.msg == "value of state must be one of{{':'}} absent, present, query, got{{':'}} non-existing-state"
+
+# USE A NON-EXISTING SCHEMA
+- name: Non-existing schema for bd (check_mode)
+ mso_schema_template_bd:
+ <<: *bd_query
+ schema: non-existing-schema
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_schema
+
+- name: Non-existing schema for bd (normal_mode)
+ mso_schema_template_bd:
+ <<: *bd_query
+ schema: non-existing-schema
+ ignore_errors: true
+ register: nm_non_existing_schema
+
+- name: Verify non_existing_schema
+ assert:
+ that:
+ - cm_non_existing_schema is not changed
+ - nm_non_existing_schema is not changed
+ - cm_non_existing_schema == nm_non_existing_schema
+ - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non-existing-schema' does not exist."
+
+# USE A NON-EXISTING TEMPLATE
+- name: Non-existing template for bd (check_mode)
+ mso_schema_template_bd:
+ <<: *bd_query
+ template: non-existing-template
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_template
+
+- name: Non-existing template for bd (normal_mode)
+ mso_schema_template_bd:
+ <<: *bd_query
+ template: non-existing-template
+ ignore_errors: true
+ register: nm_non_existing_template
+
+- name: Verify non_existing_template
+ assert:
+ that:
+ - cm_non_existing_template is not changed
+ - nm_non_existing_template is not changed
+ - cm_non_existing_template == nm_non_existing_template
+ - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non-existing-template' does not exist. Existing templates{{':'}} Template1, Template2"
+
+# CLEAN UP DHCP Policies
+- name: Ensure DHCP policies are removed
+ mso_dhcp_relay_policy:
+ <<: *mso_info
+ dhcp_relay_policy: '{{ item }}'
+ tenant: ansible_test
+ state: absent
+ loop:
+ - 'ansible_test_dhcp_policy1'
+ - 'ansible_test_dhcp_policy2'
+ - 'ansible_test_dhcp_policy3'
+ when:
+ # TODO dhcp policies api endpoint has changed after 4.0, fix when new module is created
+ - version.current.version is version('4.0', '<')
+
+- name: Ensure DHCP option policies are removed
+ mso_dhcp_option_policy:
+ <<: *mso_info
+ dhcp_option_policy: '{{ item }}'
+ tenant: ansible_test
+ state: absent
+ loop:
+ - 'ansible_test_dhcp_policy_option1'
+ - 'ansible_test_dhcp_policy_option2'
+ - 'ansible_test_dhcp_policy_option3'
+ when:
+ # TODO dhcp policies api endpoint has changed after 4.0, fix when new module is created
+ - version.current.version is version('4.0', '<')
+
+# REMOVE Schemas for next CI Run
+- name: Remove schemas for next ci test
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}' \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd_dhcp_policy/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd_dhcp_policy/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd_dhcp_policy/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd_dhcp_policy/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd_dhcp_policy/tasks/main.yml
new file mode 100644
index 000000000..b6b2d7a80
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd_dhcp_policy/tasks/main.yml
@@ -0,0 +1,454 @@
+# Test code for the MSO modules
+# Copyright: (c) 2021, Shreyas Srish (@shrsr) <ssrish@cisco.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Execute tasks on MSO version > 3.1.1g
+ when:
+ - version.current.version is version('3.1.1g', '>')
+ # TODO dhcp policies api endpoint has changed after 4.0, fix when new module is created
+ - version.current.version is version('4.0', '<')
+ block:
+ - name: Remove schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+ - name: Remove DHCP policies
+ mso_dhcp_relay_policy:
+ <<: *mso_info
+ dhcp_relay_policy: '{{ item }}'
+ tenant: ansible_test
+ state: absent
+ loop:
+ - 'ansible_test_dhcp_policy1'
+ - 'ansible_test_dhcp_policy2'
+ - 'ansible_test_dhcp_policy3'
+
+ - name: Remove DHCP option policies
+ mso_dhcp_option_policy:
+ <<: *mso_info
+ dhcp_option_policy: '{{ item }}'
+ tenant: ansible_test
+ state: absent
+ loop:
+ - 'ansible_test_dhcp_policy_option1'
+ - 'ansible_test_dhcp_policy_option2'
+
+ - name: Ensure tenant ansible_test exist
+ mso_tenant: &tenant_present
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ state: present
+
+ - name: Ensure schema 1 with Template1 exist
+ mso_schema_template: &schema_present
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: Template1
+ state: present
+
+ - name: Ensure VRF exists
+ mso_schema_template_vrf: &vrf_present
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ vrf: VRF
+ layer3_multicast: true
+ state: present
+
+ - name: Ensure multiple DHCP policies exist
+ mso_dhcp_relay_policy:
+ <<: *mso_info
+ dhcp_relay_policy: '{{ item }}'
+ description: "My Test DHCP Policies"
+ tenant: ansible_test
+ state: present
+ loop:
+ - 'ansible_test_dhcp_policy1'
+ - 'ansible_test_dhcp_policy2'
+ - 'ansible_test_dhcp_policy3'
+
+ - name: Ensure multiple DHCP option policies exist
+ mso_dhcp_option_policy:
+ <<: *mso_info
+ dhcp_option_policy: '{{ item }}'
+ description: "My Test DHCP Policy Options"
+ tenant: ansible_test
+ state: present
+ loop:
+ - 'ansible_test_dhcp_policy_option1'
+ - 'ansible_test_dhcp_policy_option2'
+
+ # ADD BD
+ - name: Add bd
+ mso_schema_template_bd: &bd_present
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ intersite_bum_traffic: true
+ optimize_wan_bandwidth: true
+ layer2_stretch: true
+ layer2_unknown_unicast: proxy
+ vrf:
+ name: VRF
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ state: present
+
+ - name: Add bd2
+ mso_schema_template_bd:
+ <<: *bd_present
+ bd: ansible_test_2
+ state: present
+
+ # Add dhcp policies
+ - name: Add DHCP policy in check mode
+ mso_schema_template_bd_dhcp_policy: &dhcp_present
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ name: ansible_test_dhcp_policy1
+ version: 1
+ dhcp_option_policy:
+ name: ansible_test_dhcp_policy_option1
+ version: 1
+ register: cm_add_dhcp
+ check_mode: true
+
+ - name: Add DHCP policy in normal mode
+ mso_schema_template_bd_dhcp_policy:
+ <<: *dhcp_present
+ register: nm_add_dhcp
+
+ - name: Add DHCP policy again in normal mode
+ mso_schema_template_bd_dhcp_policy:
+ <<: *dhcp_present
+ register: nm_add_dhcp_again
+
+ - name: Add another DHCP policy in normal mode
+ mso_schema_template_bd_dhcp_policy:
+ <<: *dhcp_present
+ name: ansible_test_dhcp_policy2
+ version: 1
+ dhcp_option_policy:
+ name: ansible_test_dhcp_policy_option2
+ version: 1
+ register: nm_add_dhcp2
+
+
+ - name: Add dhcp for query all (normal mode)
+ mso_schema_template_bd_dhcp_policy:
+ <<: *dhcp_present
+ name: ansible_test_dhcp_policy3
+ version: 1
+ register: nm_add_dhcp3
+
+ - name: Verify cm_add_dhcp, nm_add_dhcp, nm_add_dhcp2 and nm_add_dhcp3
+ assert:
+ that:
+ - cm_add_dhcp is changed
+ - nm_add_dhcp is changed
+ - nm_add_dhcp_again is not changed
+ - nm_add_dhcp.current.name == 'ansible_test_dhcp_policy1'
+ - nm_add_dhcp.current.version == 1
+ - nm_add_dhcp.current.dhcpOptionLabel.name == 'ansible_test_dhcp_policy_option1'
+ - nm_add_dhcp.current.dhcpOptionLabel.version == 1
+ - nm_add_dhcp2.current.name == 'ansible_test_dhcp_policy2'
+ - nm_add_dhcp2.current.version == 1
+ - nm_add_dhcp2.current.dhcpOptionLabel.name == 'ansible_test_dhcp_policy_option2'
+ - nm_add_dhcp2.current.dhcpOptionLabel.version == 1
+ - nm_add_dhcp3.current.name == 'ansible_test_dhcp_policy3'
+ - nm_add_dhcp3.current.version == 1
+
+ # CHANGE dhcp policies
+ - name: Change dhcp policy (normal mode)
+ mso_schema_template_bd_dhcp_policy:
+ <<: *dhcp_present
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ name: ansible_test_dhcp_policy1
+ version: 1
+ dhcp_option_policy:
+ name: ansible_test_dhcp_policy_option2
+ version: 1
+ register: nm_change_dhcp
+
+ - name: Verify nm_change_dhcp
+ assert:
+ that:
+ - nm_change_dhcp is changed
+ - nm_change_dhcp.current.name == 'ansible_test_dhcp_policy1'
+ - nm_change_dhcp.current.version == 1
+ - nm_change_dhcp.current.dhcpOptionLabel.name == 'ansible_test_dhcp_policy_option2'
+
+ - name: Change dhcp policy again (normal mode)
+ mso_schema_template_bd_dhcp_policy:
+ <<: *dhcp_present
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ name: ansible_test_dhcp_policy1
+ version: 1
+ dhcp_option_policy:
+ name: ansible_test_dhcp_policy_option1
+ version: 1
+ register: nm_change_dhcp_again
+
+
+ - name: Verify nm_change_dhcp
+ assert:
+ that:
+ - nm_change_dhcp_again is changed
+ - nm_change_dhcp_again.current.name == 'ansible_test_dhcp_policy1'
+ - nm_change_dhcp_again.current.version == 1
+ - nm_change_dhcp_again.current.dhcpOptionLabel.name == 'ansible_test_dhcp_policy_option1'
+
+ # QUERY ALL dhcp policies
+ - name: Query all dhcp (check_mode)
+ mso_schema_template_bd_dhcp_policy: &dhcp_query
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ state: query
+ check_mode: true
+ register: cm_query_all_dhcp
+
+
+ - name: Query all dhcp (normal mode)
+ mso_schema_template_bd_dhcp_policy:
+ <<: *dhcp_query
+ register: nm_query_all_dhcp
+
+
+ - name: Verify query_all_dhcp
+ assert:
+ that:
+ - cm_query_all_dhcp is not changed
+ - nm_query_all_dhcp is not changed
+ - cm_query_all_dhcp.current | length == nm_query_all_dhcp.current | length == 3
+
+ # QUERY a DHCP policy
+ - name: Query single dhcp
+ mso_schema_template_bd_dhcp_policy:
+ <<: *dhcp_query
+ name: ansible_test_dhcp_policy1
+ register: nm_query_dhcp
+
+ - name: Verify nm_query_dhcp
+ assert:
+ that:
+ - nm_query_dhcp is not changed
+ - nm_query_dhcp.current.name == 'ansible_test_dhcp_policy1'
+ - nm_query_dhcp.current.version == 1
+ - nm_query_dhcp.current.dhcpOptionLabel.name == 'ansible_test_dhcp_policy_option1'
+
+ # QUERY a non associated DHCP policy
+ - name: Query non associated dhcp
+ mso_schema_template_bd_dhcp_policy:
+ <<: *dhcp_query
+ bd: ansible_test_2
+ name: ansible_test_dhcp_policy1
+ ignore_errors: true
+ register: non_dhcp
+
+ - name: Verify non_dhcp
+ assert:
+ that:
+ - non_dhcp.msg is match ("DHCP policy not associated with the bd")
+
+ # REMOVE DHCP policy
+ - name: Remove dhcp policy
+ mso_schema_template_bd_dhcp_policy: &dhcp_absent
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ name: ansible_test_dhcp_policy1
+ state: absent
+ register: nm_remove_dhcp
+
+ - name: Verify nm_remove_dhcp
+ assert:
+ that:
+ - nm_remove_dhcp is changed
+ - nm_remove_dhcp.current == {}
+
+ - name: Remove dhcp again (check_mode)
+ mso_schema_template_bd_dhcp_policy:
+ <<: *dhcp_absent
+ register: nm_remove_dhcp_again
+
+
+ - name: Verify nm_remove_dhcp_again
+ assert:
+ that:
+ - nm_remove_dhcp_again is not changed
+ - nm_remove_dhcp_again.current == {}
+
+ # QUERY NON-EXISTING DHCP policy
+ - name: Query non-existing dhcp policy
+ mso_schema_template_bd_dhcp_policy:
+ <<: *dhcp_query
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ name: non_policy
+ version: 1
+ dhcp_option_policy:
+ name: ansible_test_dhcp_policy_option1
+ version: 1
+ ignore_errors: true
+ register: nm_query_non_dhcp
+
+ - name: Verify nm_query_non_dhcp
+ assert:
+ that:
+ - nm_query_non_dhcp is not changed
+ - nm_query_non_dhcp.msg is match ("DHCP policy 'non_policy' does not exist")
+
+ # QUERY NON-EXISTING DHCP policy option
+ - name: Query non-existing dhcp policy option
+ mso_schema_template_bd_dhcp_policy:
+ <<: *dhcp_query
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ name: ansible_test_dhcp_policy1
+ version: 1
+ dhcp_option_policy:
+ name: non_option
+ version: 1
+ ignore_errors: true
+ register: nm_query_non_dhcp_option
+
+ - name: Verify nm_query_non_dhcp
+ assert:
+ that:
+ - nm_query_non_dhcp_option is not changed
+ - nm_query_non_dhcp_option.msg is match ("DHCP option policy 'non_option' does not exist")
+
+ # USE A NON-EXISTING STATE
+ - name: Non-existing state for dhcp
+ mso_schema_template_bd_dhcp_policy:
+ <<: *dhcp_query
+ state: non-existing-state
+ ignore_errors: true
+ register: nm_non_existing_state
+
+ - name: Verify non_existing_state
+ assert:
+ that:
+ - nm_non_existing_state is not changed
+ - nm_non_existing_state.msg is match ("value of state must be one of{{':'}} absent, present, query, got{{':'}} non-existing-state")
+
+ # USE A NON-EXISTING SCHEMA
+ - name: Non-existing schema for dhcp
+ mso_schema_template_bd_dhcp_policy:
+ <<: *dhcp_query
+ schema: non-existing-schema
+ ignore_errors: true
+ register: nm_non_existing_schema
+
+ - name: Verify non_existing_schema
+ assert:
+ that:
+ - nm_non_existing_schema is not changed
+ - nm_non_existing_schema.msg is match ("Provided schema 'non-existing-schema' does not exist.")
+
+ # USE A NON-EXISTING TEMPLATE
+ - name: Non-existing template for dhcp
+ mso_schema_template_bd_dhcp_policy:
+ <<: *dhcp_query
+ template: non-existing-template
+ ignore_errors: true
+ register: nm_non_existing_template
+
+ - name: Verify non_existing_template
+ assert:
+ that:
+ - nm_non_existing_template is not changed
+ - nm_non_existing_template.msg is match ("Provided template 'non-existing-template' does not exist. Existing templates{{':'}} Template1")
+
+ # USE A NON-EXISTING BD
+ - name: Non-existing bd for dhcp
+ mso_schema_template_bd_dhcp_policy:
+ <<: *dhcp_query
+ bd: non-existing-bd
+ ignore_errors: true
+ register: nm_non_existing_bd
+
+ - name: Verify non_existing_bd
+ assert:
+ that:
+ - nm_non_existing_bd is not changed
+ - nm_non_existing_bd.msg is match ("Provided BD 'non-existing-bd' does not exist. Existing BDs{{':'}} ansible_test_1")
+
+ # REMOVE Schemas for next CI Run
+ - name: Remove schemas for next ci test
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+ # CLEAN UP DHCP Policies
+ - name: Ensure DHCP policies are removed
+ mso_dhcp_relay_policy:
+ <<: *mso_info
+ dhcp_relay_policy: '{{ item }}'
+ tenant: ansible_test
+ state: absent
+ loop:
+ - 'ansible_test_dhcp_policy1'
+ - 'ansible_test_dhcp_policy2'
+ - 'ansible_test_dhcp_policy3'
+
+ - name: Ensure DHCP option policies are removed
+ mso_dhcp_option_policy:
+ <<: *mso_info
+ dhcp_option_policy: '{{ item }}'
+ tenant: ansible_test
+ state: absent
+ loop:
+ - 'ansible_test_dhcp_policy_option1'
+ - 'ansible_test_dhcp_policy_option2' \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd_subnet/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd_subnet/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd_subnet/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd_subnet/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd_subnet/tasks/main.yml
new file mode 100644
index 000000000..75a3b4747
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_bd_subnet/tasks/main.yml
@@ -0,0 +1,630 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case)
+#
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Set version vars
+ set_fact:
+ mso_l3mcast: false
+ when: version.current.version is version('2.2.4', '=')
+
+- name: Ensure site exist
+ mso_site: &site_present
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ apic_username }}'
+ apic_password: '{{ apic_password }}'
+ apic_site_id: '{{ apic_site_id | default(101) }}'
+ urls:
+ - https://{{ apic_hostname }}
+ state: present
+
+- name: Remove schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Ensure tenant ansible_test exist
+ mso_tenant: &tenant_present
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ state: present
+
+- name: Ensure schema 1 with Template1 exist
+ mso_schema_template: &schema_present
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: Template1
+ state: present
+
+- name: Ensure schema 2 with Template2 exists
+ mso_schema_template:
+ <<: *schema_present
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template2
+ state: present
+
+- name: Ensure VRF exists
+ mso_schema_template_vrf: &vrf_present
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ vrf: VRF
+ layer3_multicast: true
+ state: present
+
+- name: Ensure VRF2 exists
+ mso_schema_template_vrf:
+ <<: *vrf_present
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template2
+ vrf: VRF2
+
+# ADD BD
+- name: Add bd
+ mso_schema_template_bd: &bd_present
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ intersite_bum_traffic: true
+ optimize_wan_bandwidth: true
+ layer2_stretch: true
+ layer2_unknown_unicast: proxy
+ vrf:
+ name: VRF
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ dhcp_policy:
+ name: ansible_test
+ version: 1
+ dhcp_option_policy:
+ name: ansible_test_option
+ version: 1
+ state: present
+
+- name: Add bd 2
+ mso_schema_template_bd:
+ <<: *bd_present
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template2
+ bd: ansible_test_2
+ intersite_bum_traffic: true
+ optimize_wan_bandwidth: true
+ layer2_stretch: true
+ layer2_unknown_unicast: proxy
+ vrf:
+ name: VRF2
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template2
+ dhcp_policy:
+ name: ansible_test
+ version: 1
+ dhcp_option_policy:
+ name: ansible_test_option
+ version: 1
+
+- name: Add bd
+ mso_schema_template_bd:
+ <<: *bd_present
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_3
+ intersite_bum_traffic: true
+ optimize_wan_bandwidth: true
+ layer2_stretch: true
+ layer2_unknown_unicast: proxy
+ vrf:
+ name: VRF
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ dhcp_policy:
+ name: ansible_test
+ version: 1
+ dhcp_option_policy:
+ name: ansible_test_option
+ version: 1
+ state: present
+
+# Add subnet
+- name: Add subnet in check mode
+ mso_schema_template_bd_subnet: &subnet_present
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ subnet: 172.16.0.1/24
+ description: "My description for a subnet"
+ scope: public
+ shared: true
+ no_default_gateway: false
+ querier: true
+ state: present
+ register: cm_add_subnet
+ check_mode: true
+
+- name: Add subnet (normal mode)
+ mso_schema_template_bd_subnet:
+ <<: *subnet_present
+ register: nm_add_subnet
+
+- name: Add subnet again (normal mode)
+ mso_schema_template_bd_subnet:
+ <<: *subnet_present
+ register: nm_add_subnet_again
+
+- name: Add subnet for query all (normal mode)
+ mso_schema_template_bd_subnet:
+ <<: *subnet_present
+ subnet: 2.16.0.1/24
+
+- name: Verify cm_add_subnet and nm_add_subnet
+ assert:
+ that:
+ - cm_add_subnet is changed
+ - nm_add_subnet is changed
+ - nm_add_subnet_again is not changed
+ - cm_add_subnet.current.description == "My description for a subnet"
+ - cm_add_subnet.current.ip == "172.16.0.1/24"
+ - cm_add_subnet.current.noDefaultGateway == false
+ - cm_add_subnet.current.scope == "public"
+ - cm_add_subnet.current.shared == true
+ - cm_add_subnet.current.querier == true
+ - nm_add_subnet.current.description == "My description for a subnet"
+ - nm_add_subnet.current.ip == "172.16.0.1/24"
+ - nm_add_subnet.current.noDefaultGateway == false
+ - nm_add_subnet.current.scope == "public"
+ - nm_add_subnet.current.shared == true
+ - nm_add_subnet.current.querier == true
+
+- name: Add subnet 2 (normal mode)
+ mso_schema_template_bd_subnet:
+ <<: *subnet_present
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template2
+ bd: ansible_test_2
+ subnet: 10.1.1.1/24
+ description: "My description for a subnet with virtual ip"
+ scope: public
+ shared: true
+ no_default_gateway: false
+ querier: true
+ is_virtual_ip: true
+ register: nm_add_subnet_2
+
+- name: Verify nm_bd_2 for a version that's < 3.1
+ assert:
+ that:
+ - nm_add_subnet_2.current.ip == "10.1.1.1/24"
+ - nm_add_subnet_2.current.noDefaultGateway == false
+ - nm_add_subnet_2.current.scope == "public"
+ - nm_add_subnet_2.current.shared == true
+ - nm_add_subnet_2.current.querier == true
+ when: version.current.version is version('3.1.1g', '<')
+
+- name: Verify nm_bd_2 for a version that's >= 3.1
+ assert:
+ that:
+ - nm_add_subnet_2.current.ip == "10.1.1.1/24"
+ - nm_add_subnet_2.current.noDefaultGateway == false
+ - nm_add_subnet_2.current.scope == "public"
+ - nm_add_subnet_2.current.shared == true
+ - nm_add_subnet_2.current.querier == true
+ - nm_add_subnet_2.current.virtual == true
+ when: version.current.version is version('3.1.1g', '>=')
+
+# CHANGE Subnet
+- name: Change subnet 2 (normal mode)
+ mso_schema_template_bd_subnet:
+ <<: *subnet_present
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template2
+ bd: ansible_test_2
+ subnet: 10.1.1.1/24
+ description: "My description for a subnet with virtual ip"
+ scope: public
+ shared: true
+ no_default_gateway: false
+ querier: true
+ is_virtual_ip: false
+ register: nm_change_subnet2
+
+- name: Verify nm_change_subnet2 for a version < 3.1
+ assert:
+ that:
+ - nm_change_subnet2 is not changed
+ - nm_change_subnet2.current.ip == "10.1.1.1/24"
+ - nm_change_subnet2.current.noDefaultGateway == false
+ - nm_change_subnet2.current.scope == "public"
+ - nm_change_subnet2.current.shared == true
+ - nm_change_subnet2.current.querier == true
+ when: version.current.version is version('3.1.1g', '<')
+
+- name: Verify nm_change_subnet2 for a version >= 3.1
+ assert:
+ that:
+ - nm_change_subnet2 is changed
+ - nm_change_subnet2.current.ip == "10.1.1.1/24"
+ - nm_change_subnet2.current.noDefaultGateway == false
+ - nm_change_subnet2.current.scope == "public"
+ - nm_change_subnet2.current.shared == true
+ - nm_change_subnet2.current.querier == true
+ - nm_change_subnet2.current.virtual == false
+ when: version.current.version is version('3.1.1g', '>=')
+
+- name: Change subnet2 again (normal mode)
+ mso_schema_template_bd_subnet:
+ <<: *subnet_present
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template2
+ bd: ansible_test_2
+ subnet: 10.1.1.1/24
+ description: "My description for a subnet with virtual ip"
+ scope: public
+ shared: true
+ no_default_gateway: false
+ querier: true
+ is_virtual_ip: false
+ register: nm_change_subnet2_again
+
+- name: Verify nm_change_subnet2_again for a version that's < 3.1
+ assert:
+ that:
+ - nm_change_subnet2_again is not changed
+ - nm_change_subnet2_again.current.ip == "10.1.1.1/24"
+ - nm_change_subnet2_again.current.noDefaultGateway == false
+ - nm_change_subnet2_again.current.scope == "public"
+ - nm_change_subnet2_again.current.shared == true
+ - nm_change_subnet2_again.current.querier == true
+ when: version.current.version is version('3.1.1g', '<')
+
+- name: Verify cm_change_subnet2 for a version that's >= 3.1
+ assert:
+ that:
+ - nm_change_subnet2_again is not changed
+ - nm_change_subnet2_again.current.ip == "10.1.1.1/24"
+ - nm_change_subnet2_again.current.noDefaultGateway == false
+ - nm_change_subnet2_again.current.scope == "public"
+ - nm_change_subnet2_again.current.shared == true
+ - nm_change_subnet2_again.current.querier == true
+ - nm_change_subnet2_again.current.virtual == false
+ when: version.current.version is version('3.1.1g', '>=')
+
+# Primary parameter
+- name: Add subnet 3 with primary and querier parameters (normal mode)
+ mso_schema_template_bd_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ subnet: 10.1.1.5/24
+ description: "My description for a subnet with virtual ip"
+ scope: public
+ shared: true
+ no_default_gateway: false
+ querier: true
+ primary: true
+ is_virtual_ip: true
+ state: present
+ register: nm_add_subnet_3
+
+- name: Verify nm_add_subnet_3 for a version that's < 3.1
+ assert:
+ that:
+ - nm_add_subnet_3.current.ip == "10.1.1.5/24"
+ - nm_add_subnet_3.current.noDefaultGateway == false
+ - nm_add_subnet_3.current.scope == "public"
+ - nm_add_subnet_3.current.shared == true
+ - nm_add_subnet_3.current.querier == true
+ when: version.current.version is version('3.1.1g', '<')
+
+- name: Verify nm_add_subnet_3 for a version that's >= 3.1
+ assert:
+ that:
+ - nm_add_subnet_3.current.ip == "10.1.1.5/24"
+ - nm_add_subnet_3.current.noDefaultGateway == false
+ - nm_add_subnet_3.current.scope == "public"
+ - nm_add_subnet_3.current.shared == true
+ - nm_add_subnet_3.current.querier == true
+ - nm_add_subnet_3.current.virtual == true
+ - nm_add_subnet_3.current.primary == true
+ when: version.current.version is version('3.1.1g', '>=')
+
+# CHANGE Subnet
+- name: Change subnet 3 (normal mode)
+ mso_schema_template_bd_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ subnet: 10.1.1.5/24
+ description: "My description for a subnet with virtual ip"
+ scope: public
+ shared: true
+ no_default_gateway: false
+ querier: true
+ is_virtual_ip: false
+ primary: true
+ state: present
+ register: nm_change_subnet3
+
+- name: Verify nm_change_subnet3 for a version < 3.1
+ assert:
+ that:
+ - nm_change_subnet3 is not changed
+ - nm_change_subnet3.current.ip == "10.1.1.5/24"
+ - nm_change_subnet3.current.noDefaultGateway == false
+ - nm_change_subnet3.current.scope == "public"
+ - nm_change_subnet3.current.shared == true
+ - nm_change_subnet3.current.querier == true
+ when: version.current.version is version('3.1.1g', '<')
+
+- name: Verify nm_change_subnet2 for a version >= 3.1
+ assert:
+ that:
+ - nm_change_subnet3 is changed
+ - nm_change_subnet3.current.ip == "10.1.1.5/24"
+ - nm_change_subnet3.current.noDefaultGateway == false
+ - nm_change_subnet3.current.scope == "public"
+ - nm_change_subnet3.current.shared == true
+ - nm_change_subnet3.current.querier == true
+ - nm_change_subnet3.current.virtual == false
+ - nm_change_subnet3.current.primary == true
+ when: version.current.version is version('3.1.1g', '>=')
+
+- name: Change subnet3 again (normal mode)
+ mso_schema_template_bd_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ subnet: 10.1.1.5/24
+ description: "My description for a subnet with virtual ip"
+ scope: public
+ shared: true
+ no_default_gateway: false
+ querier: true
+ is_virtual_ip: false
+ primary: true
+ state: present
+ register: nm_change_subnet3_again
+
+- name: Verify nm_change_subnet2_again for a version that's < 3.1
+ assert:
+ that:
+ - nm_change_subnet3_again is not changed
+ - nm_change_subnet3_again.current.ip == "10.1.1.5/24"
+ - nm_change_subnet3_again.current.noDefaultGateway == false
+ - nm_change_subnet3_again.current.scope == "public"
+ - nm_change_subnet3_again.current.shared == true
+ - nm_change_subnet3_again.current.querier == true
+ when: version.current.version is version('3.1.1g', '<')
+
+- name: Verify cm_change_subnet2 for a version that's >= 3.1
+ assert:
+ that:
+ - nm_change_subnet3_again is not changed
+ - nm_change_subnet3_again.current.ip == "10.1.1.5/24"
+ - nm_change_subnet3_again.current.noDefaultGateway == false
+ - nm_change_subnet3_again.current.scope == "public"
+ - nm_change_subnet3_again.current.shared == true
+ - nm_change_subnet3_again.current.querier == true
+ - nm_change_subnet3_again.current.virtual == false
+ - nm_change_subnet3_again.current.primary == true
+ when: version.current.version is version('3.1.1g', '>=')
+
+# QUERY ALL Subnets
+- name: Query all subnet (check_mode)
+ mso_schema_template_bd_subnet: &subnet_query
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ state: query
+ check_mode: true
+ register: cm_query_all_subnet
+
+- name: Query all subnet (normal mode)
+ mso_schema_template_bd_subnet:
+ <<: *subnet_query
+ register: nm_query_all_subnet
+
+- name: Verify query_all_subnet
+ assert:
+ that:
+ - cm_query_all_subnet is not changed
+ - nm_query_all_subnet is not changed
+ - cm_query_all_subnet.current | length == nm_query_all_subnet.current | length == 3
+
+# QUERY A subnet
+- name: Query subnet2
+ mso_schema_template_bd_subnet:
+ <<: *subnet_query
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template2
+ bd: ansible_test_2
+ subnet: 10.1.1.1/24
+ register: nm_query_subnet2
+
+- name: Verify nm_query_subnet2 for a version that's < 3.1
+ assert:
+ that:
+ - nm_query_subnet2 is not changed
+ - nm_query_subnet2.current.ip == "10.1.1.1/24"
+ - nm_query_subnet2.current.noDefaultGateway == false
+ - nm_query_subnet2.current.scope == "public"
+ - nm_query_subnet2.current.shared == true
+ - nm_query_subnet2.current.querier == true
+ when: version.current.version is version('3.1.1g', '<')
+
+- name: Verify nm_query_subnet2 for a version that's >= 3.1
+ assert:
+ that:
+ - nm_query_subnet2 is not changed
+ - nm_query_subnet2.current.ip == "10.1.1.1/24"
+ - nm_query_subnet2.current.noDefaultGateway == false
+ - nm_query_subnet2.current.scope == "public"
+ - nm_query_subnet2.current.shared == true
+ - nm_query_subnet2.current.querier == true
+ - nm_query_subnet2.current.virtual == false
+ when: version.current.version is version('3.1.1g', '>=')
+
+# REMOVE Subnet
+- name: Remove subnet
+ mso_schema_template_bd_subnet: &subnet_absent
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_1
+ subnet: 172.16.0.1/24
+ state: absent
+ register: nm_remove_subnet
+
+- name: Verify nm_remove_subnet
+ assert:
+ that:
+ - nm_remove_subnet is changed
+ - nm_remove_subnet.current == {}
+
+- name: Remove subnet again (check_mode)
+ mso_schema_template_bd_subnet:
+ <<: *subnet_absent
+ register: nm_remove_subnet_again
+
+- name: Verify nm_remove_subnet_again
+ assert:
+ that:
+ - nm_remove_subnet_again is not changed
+ - nm_remove_subnet_again.current == {}
+
+# QUERY NON-EXISTING Subnet
+- name: Query non-existing subnet
+ mso_schema_template_bd_subnet:
+ <<: *subnet_query
+ bd: ansible_test_1
+ subnet: 172.16.0.3/24
+ ignore_errors: true
+ register: nm_query_non_subnet
+
+- name: Verify nm_query_non_subnet
+ assert:
+ that:
+ - nm_query_non_subnet is not changed
+ - nm_query_non_subnet.msg is match ("Subnet IP '172.16.0.3/24' not found")
+
+# USE A NON-EXISTING STATE
+- name: Non-existing state for subnet
+ mso_schema_template_bd_subnet:
+ <<: *subnet_query
+ state: non-existing-state
+ ignore_errors: true
+ register: nm_non_existing_state
+
+- name: Verify non_existing_state
+ assert:
+ that:
+ - nm_non_existing_state is not changed
+ - nm_non_existing_state.msg is match ("value of state must be one of{{':'}} absent, present, query, got{{':'}} non-existing-state")
+
+# USE A NON-EXISTING SCHEMA
+- name: Non-existing schema for subnet
+ mso_schema_template_bd_subnet:
+ <<: *subnet_query
+ schema: non-existing-schema
+ ignore_errors: true
+ register: nm_non_existing_schema
+
+- name: Verify non_existing_schema
+ assert:
+ that:
+ - nm_non_existing_schema is not changed
+ - nm_non_existing_schema.msg is match ("Provided schema 'non-existing-schema' does not exist.")
+
+# USE A NON-EXISTING TEMPLATE
+- name: Non-existing template for subnet
+ mso_schema_template_bd_subnet:
+ <<: *subnet_query
+ template: non-existing-template
+ ignore_errors: true
+ register: nm_non_existing_template
+
+- name: Verify non_existing_template
+ assert:
+ that:
+ - nm_non_existing_template is not changed
+ - nm_non_existing_template.msg is match ("Provided template 'non-existing-template' does not exist. Existing templates{{':'}} Template1")
+
+# USE NON-EXISTING OPTIONS
+- name: Add subnet with no description
+ mso_schema_template_bd_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: ansible_test_3
+ subnet: 172.16.0.5/24
+ state: present
+ register: nm_add_subnet_no_desc
+
+- name: Verify nm_add_subnet_no_desc
+ assert:
+ that:
+ - nm_add_subnet_no_desc.current.description == "172.16.0.5/24"
+
+# USE A NON-EXISTING BD
+- name: Non-existing bd for subnet
+ mso_schema_template_bd_subnet:
+ <<: *subnet_query
+ bd: non-existing-bd
+ ignore_errors: true
+ register: nm_non_existing_bd
+
+- name: Verify non_existing_bd
+ assert:
+ that:
+ - nm_non_existing_bd is not changed
+ - nm_non_existing_bd.msg is match ("Provided BD 'non-existing-bd' does not exist. Existing BDs{{':'}} ansible_test_1")
+
+- name: Remove schemas for next ci test
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}' \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_clone/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_clone/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_clone/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_clone/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_clone/tasks/main.yml
new file mode 100644
index 000000000..0039a9b02
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_clone/tasks/main.yml
@@ -0,0 +1,293 @@
+# Test code for the MSO modules
+# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Remove schemas
+ cisco.mso.mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ ignore_errors: true
+ loop:
+ - Schema1
+ - Schema2
+
+- name: Ensure tenant ansible_test exists
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ state: present
+
+- name: Ensure user is defined under common tenant
+ mso_tenant:
+ <<: *mso_info
+ tenant: common
+ users:
+ - '{{ mso_username }}'
+ state: present
+ when: version.current.version is version('3.2', '<')
+
+- name: Create Schema1 with Template 1, and Template 2 exist
+ cisco.mso.mso_schema_template:
+ <<: *mso_info
+ schema: Schema1
+ tenant: ansible_test
+ template: '{{ item }}'
+ state: present
+ loop:
+ - Template1
+ - Template2
+
+- name: Create Schema2 with Template 3 exist
+ cisco.mso.mso_schema_template:
+ <<: *mso_info
+ schema: Schema2
+ tenant: ansible_test
+ template: Template3
+ state: present
+
+- name: Ensure ANP exist
+ cisco.mso.mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ item.schema }}'
+ template: '{{ item.template }}'
+ anp: ANP
+ state: present
+ loop:
+ - { schema: 'Schema1', template: 'Template1' }
+ - { schema: 'Schema1', template: 'Template2' }
+ - { schema: 'Schema2', template: 'Template3' }
+
+- name: Ensure EPGs exist
+ cisco.mso.mso_schema_template_anp_epg:
+ <<: *mso_info
+ schema: '{{ item.schema }}'
+ template: '{{ item.template }}'
+ anp: ANP
+ epg: '{{ item.epg }}'
+ state: present
+ loop:
+ - { schema: 'Schema1', template: 'Template1', epg: 'ansible_test_1' }
+ - { schema: 'Schema1', template: 'Template2', epg: 'ansible_test_2' }
+ - { schema: 'Schema2', template: 'Template3', epg: 'ansible_test_3' }
+
+- name: Add Selector to EPG (normal_mode)
+ mso_schema_template_anp_epg_selector:
+ <<: *mso_info
+ schema: 'Schema1'
+ template: Template1
+ anp: ANP
+ epg: ansible_test_1
+ selector: selector_1
+ state: present
+
+- name: Clone template in the same schema (check_mode)
+ cisco.mso.mso_schema_template_clone:
+ <<: *mso_info
+ source_schema: Schema1
+ destination_schema: Schema1
+ destination_tenant: ansible_test
+ source_template_name: Template1
+ destination_template_name: Template1_clone
+ destination_template_display_name: Template1_clone
+ state: clone
+ check_mode: true
+ register: cm_add_template
+
+- name: Clone template in the same schema (normal mode)
+ cisco.mso.mso_schema_template_clone:
+ <<: *mso_info
+ source_schema: Schema1
+ destination_schema: Schema1
+ destination_tenant: ansible_test
+ source_template_name: Template1
+ destination_template_name: Template1_clone
+ destination_template_display_name: Template1_clone
+ state: clone
+ register: add_template
+
+- name: Clone template in the same schema without destination_schema being specified
+ cisco.mso.mso_schema_template_clone:
+ <<: *mso_info
+ source_schema: Schema1
+ source_template_name: Template1
+ destination_template_name: Template1_clone_nodestschema
+ state: clone
+ register: add_template_nodestschema
+
+- name: Clone template to different schema
+ cisco.mso.mso_schema_template_clone:
+ <<: *mso_info
+ source_schema: Schema1
+ destination_schema: Schema2
+ destination_tenant: ansible_test
+ source_template_name: Template2
+ destination_template_name: Cloned_template_1
+ destination_template_display_name: Cloned_template_1
+ state: clone
+ register: add_template_schema
+
+- name: Clone template to different schema but keep template name
+ cisco.mso.mso_schema_template_clone:
+ <<: *mso_info
+ source_schema: Schema1
+ destination_schema: Schema2
+ source_template_name: Template2
+ state: clone
+ register: add_template_schema_2
+
+- name: Clone template in the same schema but different tenant attached
+ cisco.mso.mso_schema_template_clone:
+ <<: *mso_info
+ source_schema: Schema1
+ destination_schema: Schema1
+ destination_tenant: common
+ source_template_name: Template1_clone
+ destination_template_name: Template1_clone_2
+ state: clone
+ register: add_template_tenant
+
+- name: Verify add_templates
+ assert:
+ that:
+ - cm_add_template is not changed
+ - add_template is changed
+ - (add_template.current.templates | selectattr('displayName', 'contains', 'Template1_clone')|first).name == 'Template1_clone'
+ - add_template_nodestschema is changed
+ - (add_template_nodestschema.current.templates | selectattr('displayName', 'contains', 'Template1_clone_nodestschema')|first).name == 'Template1_clone_nodestschema'
+ - add_template_schema is changed
+ - (add_template_schema.current.templates | selectattr('displayName', 'contains', 'Cloned_template_1')|first).name == 'Cloned_template_1'
+ - add_template_schema_2 is changed
+ - (add_template_schema_2.current.templates | selectattr('displayName', 'contains', 'Template2')|first).name == 'Template2'
+ - add_template_tenant is changed
+ - (add_template_tenant.current.templates | selectattr('displayName', 'contains', 'Template1_clone_2')|first).name == 'Template1_clone_2'
+
+# Checking for other cases
+- name: Clone non existing template
+ cisco.mso.mso_schema_template_clone:
+ <<: *mso_info
+ source_schema: Schema2
+ destination_schema: Schema2
+ destination_tenant: common
+ source_template_name: non_existing_template
+ destination_template_name: Cloned_template_2
+ destination_template_display_name: Cloned_template_2
+ state: clone
+ ignore_errors: true
+ register: non_existing_template
+
+- name: Verify non_existing_template
+ assert:
+ that:
+ - non_existing_template is not changed
+ - non_existing_template.msg == "Source template with the name 'non_existing_template' does not exist."
+
+- name: Clone non existing source schema
+ cisco.mso.mso_schema_template_clone:
+ <<: *mso_info
+ source_schema: non_existing_schema
+ destination_schema: Schema2
+ source_template_name: Template2
+ destination_template_name: NewTemplate2
+ state: clone
+ ignore_errors: true
+ register: non_existing_source_schema
+
+- name: Verify non_existing_source_schema
+ assert:
+ that:
+ - non_existing_source_schema is not changed
+ - non_existing_source_schema.msg == "Schema with the name 'non_existing_schema' does not exist."
+
+- name: Clone non existing destination schema
+ cisco.mso.mso_schema_template_clone:
+ <<: *mso_info
+ source_schema: Schema1
+ destination_schema: non_existing_schema
+ destination_tenant: common
+ source_template_name: Template2
+ destination_template_name: Template_clone
+ destination_template_display_name: Template_clone
+ state: clone
+ ignore_errors: true
+ register: non_existing_schema
+
+- name: Verify non_existing_schema
+ assert:
+ that:
+ - non_existing_schema is not changed
+ - non_existing_schema.msg == "Schema with the name 'non_existing_schema' does not exist."
+
+- name: Clone to same schema with same template name
+ cisco.mso.mso_schema_template_clone:
+ <<: *mso_info
+ source_schema: Schema1
+ source_template_name: Template1
+ state: clone
+ ignore_errors: true
+ register: wrong_template_name
+
+- name: Verify wrong_template_name
+ assert:
+ that:
+ - wrong_template_name is not changed
+ - wrong_template_name.msg == "Source and destination templates in the same schema cannot have same names."
+
+- name: Clone schema to schema with existing template with same name
+ cisco.mso.mso_schema_template_clone:
+ <<: *mso_info
+ source_schema: Schema1
+ destination_schema: Schema2
+ source_template_name: Template2
+ state: clone
+ ignore_errors: true
+ register: template_already_exist
+
+- name: Verify template_already_exist
+ assert:
+ that:
+ - template_already_exist is not changed
+ - template_already_exist.msg == "Template with the name 'Template2' already exists. Please use another name."
+
+- name: Remove schemas
+ cisco.mso.mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ register: rm_schema
+ loop:
+ - Schema2
+ - Schema1
+
+- name: Verify rm_schema
+ assert:
+ that:
+ - rm_schema is changed \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_contract_filter/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_contract_filter/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_contract_filter/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_contract_filter/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_contract_filter/tasks/main.yml
new file mode 100644
index 000000000..08f6d5cf5
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_contract_filter/tasks/main.yml
@@ -0,0 +1,1088 @@
+# Test code for the MSO modules
+# Copyright: (c) 2021, Akini Ross (@akinross) <akinross@cisco.com>
+# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com>
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case)
+#
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(false) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Remove schema 2
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ state: absent
+
+- name: Remove schema 1
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ state: absent
+
+- name: Ensure tenant ansible_test exist
+ mso_tenant: &tenant_present
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ state: present
+
+- name: Ensure schema 1 with Template 1 exist
+ mso_schema_template: &schema_present
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: Template 1
+ state: present
+
+- name: Ensure schema 1 with Template 2 exist
+ mso_schema_template:
+ <<: *schema_present
+ template: Template 2
+ state: present
+
+- name: Ensure schema 2 with Template 3 exist
+ mso_schema_template:
+ <<: *schema_present
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template 3
+ state: present
+
+- name: Ensure Filter1 exist
+ cisco.mso.mso_schema_template_filter_entry: &filter_present
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ filter: Filter1
+ #add filter entry
+ entry: Filter1Entry
+ state: present
+ register: add_filter
+
+- name: Ensure Filter2 exist
+ mso_schema_template_filter_entry:
+ <<: *filter_present
+ template: Template 2
+ filter: Filter2
+ entry: Filter2Entry
+ state: present
+
+- name: Ensure Filter3 exist
+ mso_schema_template_filter_entry:
+ <<: *filter_present
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ filter: Filter3
+ entry: Filter3Entry
+ state: present
+
+- name: Ensure Filter4 exist
+ mso_schema_template_filter_entry:
+ <<: *filter_present
+ filter: Filter4
+ entry: Filter4Entry
+ state: present
+
+- name: Ensure Filter5 exist
+ mso_schema_template_filter_entry:
+ <<: *filter_present
+ filter: Filter5
+ entry: Filter5Entry
+ state: present
+
+- name: Ensure Filter-6 exist
+ mso_schema_template_filter_entry:
+ <<: *filter_present
+ filter: Filter-6
+ entry: Filter-6Entry
+ state: present
+
+- name: Ensure Contract_1 contract does not exist
+ mso_schema_template_contract_filter: &contract_present
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract1
+ filter: Filter1
+ filter_schema: '{{ mso_schema | default("ansible_test") }}'
+ filter_template: Template 1
+ state: absent
+
+- name: Ensure Contract_2 contract does not exist
+ mso_schema_template_contract_filter:
+ <<: *contract_present
+ template: Template 2
+ contract: Contract2
+ state: absent
+
+- name: Ensure Contract_3 contract does not exist
+ mso_schema_template_contract_filter:
+ <<: *contract_present
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ contract: Contract3
+ state: absent
+
+- name: Ensure Contract_4 contract does not exist
+ mso_schema_template_contract_filter:
+ <<: *contract_present
+ contract: Contract4
+ state: absent
+
+- name: Ensure Contract_5 contract does not exist
+ mso_schema_template_contract_filter:
+ <<: *contract_present
+ contract: Contract5
+ state: absent
+
+- name: Ensure Contract_6 contract does not exist
+ mso_schema_template_contract_filter:
+ <<: *contract_present
+ contract: Contract-6
+ state: absent
+
+# ADD CONTRACT
+- name: Add contract (check_mode)
+ mso_schema_template_contract_filter:
+ <<: *contract_present
+ contract: Contract1
+ filter: Filter1
+ state: present
+ check_mode: true
+ register: cm_add_contract
+
+- name: Verify cm_add_contract
+ assert:
+ that:
+ - cm_add_contract is changed
+ - cm_add_contract.previous == {}
+ - cm_add_contract.current.filterRef.filterName == "Filter1"
+ - cm_add_contract.current.filterRef.templateName == "Template1"
+
+- name: Add contract (normal mode)
+ mso_schema_template_contract_filter:
+ <<: *contract_present
+ contract: Contract1
+ filter: Filter1
+ state: present
+ register: nm_add_contract
+
+- name: Verify nm_add_contract
+ assert:
+ that:
+ - nm_add_contract is changed
+ - nm_add_contract.previous == {}
+ - nm_add_contract.current.filterRef.filterName == "Filter1"
+ - nm_add_contract.current.filterRef.templateName == "Template1"
+ - cm_add_contract.current.filterRef.schemaId == nm_add_contract.current.filterRef.schemaId
+
+- name: Add contract again (check_mode)
+ mso_schema_template_contract_filter:
+ <<: *contract_present
+ contract: Contract1
+ filter: Filter1
+ state: present
+ check_mode: true
+ register: cm_add_contract_again
+
+- name: Verify cm_add_contract_again
+ assert:
+ that:
+ - cm_add_contract_again is not changed
+ - cm_add_contract_again.current.filterRef.filterName == "Filter1"
+ - cm_add_contract_again.current.filterRef.templateName == "Template1"
+ - cm_add_contract_again.previous.filterRef.filterName == "Filter1"
+ - cm_add_contract_again.previous.filterRef.templateName == "Template1"
+ - cm_add_contract_again.previous.filterRef.schemaId == cm_add_contract_again.current.filterRef.schemaId
+
+- name: Add contract again (normal mode)
+ mso_schema_template_contract_filter:
+ <<: *contract_present
+ contract: Contract1
+ filter: Filter1
+ state: present
+ register: nm_add_contract_again
+
+- name: Verify nm_add_contract_again
+ assert:
+ that:
+ - nm_add_contract_again is not changed
+ - nm_add_contract_again.current.filterRef.filterName == "Filter1"
+ - nm_add_contract_again.current.filterRef.templateName == "Template1"
+ - nm_add_contract_again.current.filterRef.templateName == "Template1"
+ - nm_add_contract_again.previous.filterRef.filterName == "Filter1"
+ - nm_add_contract_again.previous.filterRef.templateName == "Template1"
+ - nm_add_contract_again.previous.filterRef.schemaId == nm_add_contract_again.current.filterRef.schemaId
+
+- name: Add Contract2 (check_mode)
+ mso_schema_template_contract_filter:
+ <<: *contract_present
+ template: Template 2
+ contract: Contract2
+ filter: Filter1
+ filter_template: Template 1
+ state: present
+ check_mode: true
+ register: cm_add_contract_2
+
+- name: Verify cm_add_contract_2
+ assert:
+ that:
+ - cm_add_contract_2 is changed
+ - cm_add_contract_2.current.filterRef.filterName == "Filter1"
+ - cm_add_contract_2.current.filterRef.templateName == "Template1"
+
+- name: Add Contract2 (nomal mode)
+ mso_schema_template_contract_filter:
+ <<: *contract_present
+ template: Template 2
+ contract: Contract2
+ filter: Filter1
+ filter_template: Template 1
+ state: present
+ register: nm_add_contract_2
+
+- name: Verify nm_add_contract_2
+ assert:
+ that:
+ - nm_add_contract_2 is changed
+ - nm_add_contract_2.current.filterRef.filterName == "Filter1"
+ - nm_add_contract_2.current.filterRef.templateName == "Template1"
+ - cm_add_contract_2.current.filterRef.schemaId == nm_add_contract_2.current.filterRef.schemaId
+
+- name: Add Contract3 (nomal mode)
+ mso_schema_template_contract_filter:
+ <<: *contract_present
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ contract: Contract3
+ filter: Filter1
+ filter_template: Template 1
+ filter_schema: '{{ mso_schema | default("ansible_test") }}'
+ state: present
+ register: nm_add_contract_3
+
+- name: Verify nm_add_contract_3
+ assert:
+ that:
+ - nm_add_contract_3 is changed
+ - nm_add_contract_3.current.filterRef.filterName == "Filter1"
+ - nm_add_contract_3.current.filterRef.templateName == "Template1"
+
+- name: Add Contract4 (nomal mode)
+ mso_schema_template_contract_filter:
+ <<: *contract_present
+ contract: Contract4
+ filter: Filter1
+ contract_display_name: displayContract4
+ state: present
+ register: nm_add_contract_4
+
+- name: Verify nm_add_contract_4
+ assert:
+ that:
+ - nm_add_contract_4 is changed
+ - nm_add_contract_4.current.filterRef.filterName == "Filter1"
+ - nm_add_contract_4.current.filterRef.templateName == "Template1"
+ - nm_add_contract_3.current.filterRef.schemaId == nm_add_contract_4.current.filterRef.schemaId == nm_add_contract_2.current.filterRef.schemaId == nm_add_contract.current.filterRef.schemaId
+
+# create CONTRACT FILTER with diff options
+- name: Add Contract filter to both-way(check_mode)
+ mso_schema_template_contract_filter:
+ <<: *contract_present
+ contract: Contract1
+ contract_filter_type: both-way
+ filter: Filter4
+ filter_type: both-way
+ state: present
+ check_mode: true
+ register: cm_add_contract_filter_both_way
+
+- name: Add Contract filter to both-way(normal_mode)
+ mso_schema_template_contract_filter:
+ <<: *contract_present
+ contract: Contract1
+ contract_filter_type: both-way
+ filter: Filter4
+ filter_type: both-way
+ state: present
+ register: nm_add_contract_filter_both_way
+
+- name: Verify cm_change_contract_filter_both_way
+ assert:
+ that:
+ - cm_add_contract_filter_both_way is changed
+ - nm_add_contract_filter_both_way is changed
+ - cm_add_contract_filter_both_way.previous == {}
+ - nm_add_contract_filter_both_way.previous == {}
+ - cm_add_contract_filter_both_way.current.filterRef.filterName == "Filter4"
+ - cm_add_contract_filter_both_way.current.filterRef.templateName == "Template1"
+ - nm_add_contract_filter_both_way.current.filterRef.filterName == "Filter4"
+ - nm_add_contract_filter_both_way.current.filterRef.templateName == "Template1"
+ - cm_add_contract_filter_both_way.current.filterRef.schemaId == nm_add_contract_filter_both_way.current.filterRef.schemaId
+ - cm_add_contract_filter_both_way.current.contractFilterType == "bothWay"
+ - cm_add_contract_filter_both_way.current.contractScope == "context"
+ - cm_add_contract_filter_both_way.current.displayName == "Contract1"
+ - cm_add_contract_filter_both_way.current.filterType == "both-way"
+
+- name: Change Contract type one_way Filter type consumer-to-provider(normal_mode)
+ mso_schema_template_contract_filter:
+ <<: *contract_present
+ contract: Contract5
+ contract_filter_type: one-way
+ filter: Filter5
+ filter_type: consumer-to-provider
+ state: present
+ register: nm_one_way_and_consumer_to_provider
+
+- name: Verify nm_one_way_and_consumer_to_provider
+ assert:
+ that:
+ - nm_one_way_and_consumer_to_provider is changed
+ - nm_one_way_and_consumer_to_provider.previous == {}
+ - nm_one_way_and_consumer_to_provider.current.contractFilterType == "oneWay"
+ - nm_one_way_and_consumer_to_provider.current.contractScope == "context"
+ - nm_one_way_and_consumer_to_provider.current.displayName == "Contract5"
+ - nm_one_way_and_consumer_to_provider.current.filterType == "consumer-to-provider"
+
+- name: Change Contract type one_way Filter type provider-to-consumer(normal_mode)
+ mso_schema_template_contract_filter:
+ <<: *contract_present
+ contract: Contract-6
+ contract_filter_type: one-way
+ filter: Filter-6
+ filter_type: provider-to-consumer
+ state: present
+ register: nm_one_way_and_provider_to_consumer
+
+- name: Verify nm create contract filter with different type
+ assert:
+ that:
+ - nm_one_way_and_provider_to_consumer is changed
+ - nm_one_way_and_provider_to_consumer.current.contractFilterType == "oneWay"
+ - nm_one_way_and_provider_to_consumer.current.contractScope == "context"
+ - nm_one_way_and_provider_to_consumer.current.displayName == "Contract-6"
+ - nm_one_way_and_provider_to_consumer.current.filterType == "provider-to-consumer"
+
+# change contract display name
+- name: change contract display name
+ mso_schema_template_contract_filter:
+ <<: *contract_present
+ contract: Contract4
+ filter: Filter1
+ contract_display_name: newDisplayContract4
+ state: present
+ register: nm_change_display_name
+
+- name: Verify nm_change_display_name
+ assert:
+ that:
+ - nm_change_display_name is changed
+ - nm_change_display_name.current.displayName == "newDisplayContract4"
+ - nm_change_display_name.previous.displayName == "displayContract4"
+
+# change contract filter_directives to log
+- name: change contract filter_directives to log(check_mode)
+ mso_schema_template_contract_filter:
+ <<: *contract_present
+ contract: Contract4
+ filter: Filter1
+ filter_directives: log
+ state: present
+ check_mode: true
+ register: cm_change_filter_directives_log
+
+- name: change contract filter_directives to log(normal_mode)
+ mso_schema_template_contract_filter:
+ <<: *contract_present
+ contract: Contract4
+ filter: Filter1
+ filter_directives: log
+ state: present
+ register: nm_change_filter_directives_log
+
+- name: Verify change_contract_filter_directives to log
+ assert:
+ that:
+ - cm_change_filter_directives_log is changed
+ - nm_change_filter_directives_log is changed
+ - cm_change_filter_directives_log.previous.directives[0] == "none"
+ - nm_change_filter_directives_log.previous.directives[0] == "none"
+ - cm_change_filter_directives_log.current.directives[0] == "log"
+ - nm_change_filter_directives_log.current.directives[0] == "log"
+
+- name: change contract filter_directives to log and none(normal_mode)
+ mso_schema_template_contract_filter:
+ <<: *contract_present
+ contract: Contract4
+ filter: Filter1
+ filter_directives: ['log', 'none']
+ state: present
+ register: nm_change_filter_directives_log_and_none
+
+- name: Verify nm_change_filter_directives_log_and_none
+ assert:
+ that:
+ - nm_change_filter_directives_log_and_none is changed
+ - nm_change_filter_directives_log_and_none.previous.directives[0] == "log"
+ - nm_change_filter_directives_log_and_none.current.directives == ['log', 'none']
+
+# change contract filter_directives to policy_compression
+- name: change contract filter_directives to policy_compression (check_mode)
+ mso_schema_template_contract_filter:
+ <<: *contract_present
+ contract: Contract4
+ filter: Filter1
+ filter_directives: policy_compression
+ state: present
+ check_mode: true
+ register: cm_change_filter_directives_pc
+
+- name: change contract filter_directives to policy_compression (normal_mode)
+ mso_schema_template_contract_filter:
+ <<: *contract_present
+ contract: Contract4
+ filter: Filter1
+ filter_directives: policy_compression
+ state: present
+ register: nm_change_filter_directives_pc
+
+- name: Verify change_contract_filter_directives to pc
+ assert:
+ that:
+ - cm_change_filter_directives_pc is changed
+ - nm_change_filter_directives_pc is changed
+ - cm_change_filter_directives_pc.previous.directives == ['log', 'none']
+ - nm_change_filter_directives_pc.previous.directives == ['log', 'none']
+ - cm_change_filter_directives_pc.current.directives[0] == "no_stats"
+ - nm_change_filter_directives_pc.current.directives[0] == "no_stats"
+
+- name: change contract filter_directives to log, none, policy compression (normal_mode)
+ mso_schema_template_contract_filter:
+ <<: *contract_present
+ contract: Contract4
+ filter: Filter1
+ filter_directives: ['log', 'none', 'policy_compression']
+ state: present
+ register: nm_change_filter_directives_log_and_none_pc
+
+- name: Verify nm_change_filter_directives_log_and_none_pc
+ assert:
+ that:
+ - nm_change_filter_directives_log_and_none_pc is changed
+ - nm_change_filter_directives_log_and_none_pc.previous.directives[0] == "no_stats"
+ - nm_change_filter_directives_log_and_none_pc.current.directives == ["log", "none", "no_stats"]
+
+- name: Change Contract1 scope to global (normal mode)
+ mso_schema_template_contract_filter:
+ <<: *contract_present
+ contract: Contract1
+ contract_scope: global
+ state: present
+ register: nm_change_contract_scope_global
+
+- name: Verify nm_change_contract_scope_global
+ assert:
+ that:
+ - nm_change_contract_scope_global is changed
+ - nm_change_contract_scope_global.current.contractScope == "global"
+ - nm_change_contract_scope_global.previous.contractScope == "context"
+
+- name: Change Contract1 scope to tenant(normal mode)
+ mso_schema_template_contract_filter:
+ <<: *contract_present
+ contract: Contract1
+ contract_scope: tenant
+ state: present
+ register: nm_change_contract_scope_tenant
+
+- name: Verify nm_change_contract_scope_tenant
+ assert:
+ that:
+ - nm_change_contract_scope_tenant is changed
+ - nm_change_contract_scope_tenant.previous.contractScope == "global"
+ - nm_change_contract_scope_tenant.current.contractScope == "tenant"
+
+- name: Change Contract1 scope application_profile(normal mode)
+ mso_schema_template_contract_filter:
+ <<: *contract_present
+ contract: Contract1
+ contract_scope: application-profile
+ state: present
+ register: nm_change_contract_scope_application_profile
+
+- name: Verify nm_change_contract_scope_application_profile
+ assert:
+ that:
+ - nm_change_contract_scope_application_profile is changed
+ - nm_change_contract_scope_application_profile.previous.contractScope == "tenant"
+ - nm_change_contract_scope_application_profile.current.contractScope == "application-profile"
+
+- name: Change Contract1 scope to vrf(normal mode)
+ mso_schema_template_contract_filter:
+ <<: *contract_present
+ contract: Contract1
+ contract_scope: vrf
+ state: present
+ register: nm_change_contract_scope_vrf
+
+- name: Verify nm_change_contract_scope_vrf
+ assert:
+ that:
+ - nm_change_contract_scope_vrf is changed
+ - nm_change_contract_scope_vrf.current.contractScope == "context"
+ - nm_change_contract_scope_vrf.previous.contractScope == "application-profile"
+
+- name: Change Contract1 scope to default(normal mode)
+ mso_schema_template_contract_filter:
+ <<: *contract_present
+ contract: Contract1
+ state: present
+ register: nm_change_contract_scope_default
+
+- name: Verify nm_change_contract_scope_default
+ assert:
+ that:
+ - nm_change_contract_scope_default is not changed
+ - nm_change_contract_scope_default.current.contractScope == "context"
+ - nm_change_contract_scope_default.previous.contractScope == "context"
+
+- name: Change Contract1 description (normal mode)
+ mso_schema_template_contract_filter:
+ <<: *contract_present
+ contract: Contract1
+ description: changed description
+ state: present
+ register: nm_change_contract_description
+ when: version.current.version is version('3.3', '>=')
+
+- name: Verify nm_change_contract_description
+ assert:
+ that:
+ - nm_change_contract_description is changed
+ - nm_change_contract_description.current.description == "changed description"
+ - nm_change_contract_description.previous.description == ""
+ when: version.current.version is version('3.3', '>=')
+
+- name: Change Contract1 description empty (normal mode)
+ mso_schema_template_contract_filter:
+ <<: *contract_present
+ contract: Contract1
+ description: ""
+ state: present
+ register: nm_change_contract_description_empty
+ when: version.current.version is version('3.3', '>=')
+
+- name: Verify nm_change_contract_description_empty
+ assert:
+ that:
+ - nm_change_contract_description_empty is changed
+ - nm_change_contract_description_empty.current.description == ""
+ - nm_change_contract_description_empty.previous.description == "changed description"
+ when: version.current.version is version('3.3', '>=')
+
+- name: Change Contract1 qos_level (normal mode)
+ mso_schema_template_contract_filter:
+ <<: *contract_present
+ contract: Contract1
+ qos_level: level1
+ state: present
+ register: nm_change_contract_qos
+ when: version.current.version is version('3.3', '>=')
+
+- name: Verify nm_change_contract_qos_level
+ assert:
+ that:
+ - nm_change_contract_qos is changed
+ - nm_change_contract_qos.current.prio == "level1"
+ when: version.current.version is version('3.3', '>=')
+
+- name: Change Contract1 qos_level unspecified (normal mode)
+ mso_schema_template_contract_filter:
+ <<: *contract_present
+ contract: Contract1
+ qos_level: unspecified
+ state: present
+ register: nm_change_contract_qos_unspecified
+ when: version.current.version is version('3.3', '>=')
+
+- name: Verify nm_change_contract_qos_level_unspecified
+ assert:
+ that:
+ - nm_change_contract_qos_unspecified is changed
+ - nm_change_contract_qos_unspecified.current.prio == "unspecified"
+ - nm_change_contract_qos_unspecified.previous.prio == "level1"
+ when: version.current.version is version('3.3', '>=')
+
+- name: Ensure contract filter_type set to both-way (normal mode)
+ mso_schema_template_contract_filter:
+ <<: *contract_present
+ contract: Contract1
+ filter: Filter1
+ filter_type: both-way
+ state: present
+ register: nm_contract_filter_both_way
+ when: version.current.version is version('3.3', '>=')
+
+- name: Verify nm_contract_filter_both_way
+ assert:
+ that:
+ - nm_contract_filter_both_way.current.contractFilterType == "bothWay"
+ - nm_contract_filter_both_way.current.filterType == "both-way"
+ when: version.current.version is version('3.3', '>=')
+
+- name: Change contract filter_type set to one-way with consumer-to-provider (normal mode)
+ # Test to check that filter type cannot be changed from two-way to one-way type
+ # changed behaviour due to error handling is now handled in code
+ mso_schema_template_contract_filter:
+ <<: *contract_present
+ contract: Contract1
+ filter: Filter1
+ filter_type: consumer-to-provider
+ state: present
+ register: nm_contract_filter_consumer_to_provider
+ ignore_errors: true
+ when: version.current.version is version('3.3', '>=')
+
+- name: Verify nm_contract_filter_consumer_to_provider
+ assert:
+ that:
+ - nm_contract_filter_consumer_to_provider.msg == "Current filter type 'bothWay' for contract 'Contract1' is not allowed to change to 'oneWay'."
+ when: version.current.version is version('3.3', '>=')
+
+# QUERY ALL CONTRACT
+- name: Query Contract1 filters (check_mode)
+ mso_schema_template_contract_filter: &Contract_query
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract1
+ state: query
+ check_mode: true
+ register: cm_contract1_query_result
+
+- name: Query Contract1 filters (normal_mode)
+ mso_schema_template_contract_filter:
+ <<: *Contract_query
+ register: nm_contract1_query_result
+
+- name: Verify query_contract_1
+ assert:
+ that:
+ - cm_contract1_query_result is not changed
+ - nm_contract1_query_result is not changed
+ - cm_contract1_query_result.current | length == nm_contract1_query_result.current | length == 2
+
+- name: Query Contract2 filters (normal_mode)
+ mso_schema_template_contract_filter:
+ <<: *Contract_query
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ contract: Contract2
+ state: query
+ register: nm_contract2_query_result
+
+- name: Query Contract3 filters (normal_mode)
+ mso_schema_template_contract_filter:
+ <<: *Contract_query
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ contract: Contract3
+ register: nm_contract3_query_result
+
+- name: Query Contract4 filters (normal_mode)
+ mso_schema_template_contract_filter:
+ <<: *Contract_query
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract4
+ register: nm_contract4_query_result
+
+- name: Query Contract5 filters (normal_mode)
+ mso_schema_template_contract_filter:
+ <<: *Contract_query
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract5
+ contract_filter_type: one-way
+ filter_type: consumer-to-provider
+ register: nm_contract5_query_result
+
+- name: Query Contract-6 filters (normal_mode)
+ mso_schema_template_contract_filter:
+ <<: *Contract_query
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract-6
+ contract_filter_type: one-way
+ filter_type: provider-to-consumer
+ register: nm_contract6_query_result
+
+- name: Verify query_contract
+ assert:
+ that:
+ - nm_contract2_query_result is not changed
+ - nm_contract3_query_result is not changed
+ - nm_contract4_query_result is not changed
+ - nm_contract2_query_result.current | length == nm_contract3_query_result.current | length == nm_contract4_query_result.current | length == 1
+ - nm_contract5_query_result is not changed
+ - nm_contract6_query_result is not changed
+ - nm_contract5_query_result.current | length == 1
+ - nm_contract6_query_result.current | length == 1
+
+# QUERY A SPECIFIC CONTRACT FILTER
+- name: Query Contract1 Filter1 (check_mode)
+ mso_schema_template_contract_filter: &Contract_filter_query
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract1
+ filter: Filter1
+ state: query
+ check_mode: true
+ register: cm_contract1_filter1_query_result
+
+- name: Query Contract1 Filter4 (normal_mode)
+ mso_schema_template_contract_filter:
+ <<: *Contract_filter_query
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract1
+ filter: Filter4
+ state: query
+ register: nm_contract1_filter4_query_result
+
+- name: Query Contract2 Filter1 (normal_mode)
+ mso_schema_template_contract_filter:
+ <<: *Contract_filter_query
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ contract: Contract2
+ filter_template: Template 1
+ filter: Filter1
+ state: query
+ register: nm_contract2_filter1_query_result
+
+- name: Query Contract3 Filter1 (normal_mode)
+ mso_schema_template_contract_filter:
+ <<: *Contract_filter_query
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ contract: Contract3
+ filter: Filter1
+ filter_template: Template 1
+ filter_schema: '{{ mso_schema | default("ansible_test") }}'
+ state: query
+ register: nm_contract3_filter1_query_result
+
+- name: Query Contract4 Filter1 (normal_mode)
+ mso_schema_template_contract_filter:
+ <<: *Contract_filter_query
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract4
+ filter: Filter1
+ state: query
+ register: nm_contract4_filter1_query_result
+
+- name: Query Contract5 Filter5 (normal_mode)
+ mso_schema_template_contract_filter:
+ <<: *Contract_filter_query
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract5
+ filter: Filter5
+ contract_filter_type: one-way
+ filter_type: consumer-to-provider
+ state: query
+ register: nm_contract5_filter5_query_result
+
+- name: Query Contract-6 Filter-6 (normal_mode)
+ mso_schema_template_contract_filter:
+ <<: *Contract_filter_query
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract-6
+ filter: Filter-6
+ contract_filter_type: one-way
+ filter_type: provider-to-consumer
+ state: query
+ register: nm_contract6_filter6_query_result
+
+- name: Verify contract1_filter1_query_result
+ assert:
+ that:
+ - cm_contract1_filter1_query_result is not changed
+ - nm_contract1_filter4_query_result is not changed
+ - nm_contract2_filter1_query_result is not changed
+ - nm_contract3_filter1_query_result is not changed
+ - nm_contract4_filter1_query_result is not changed
+ - nm_contract5_filter5_query_result is not changed
+ - nm_contract6_filter6_query_result is not changed
+
+# REMOVE CONTRACT Filter
+- name: Remove contract1 filter1 (check_mode)
+ mso_schema_template_contract_filter: &contract1_filter1_absent
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract1
+ filter: Filter1
+ state: absent
+ check_mode: true
+ register: cm_remove_contract1_filter1
+
+- name: Verify cm_remove_contract1_filter1
+ assert:
+ that:
+ - cm_remove_contract1_filter1 is changed
+ - cm_remove_contract1_filter1.current == {}
+ - cm_remove_contract1_filter1.previous.filterRef.filterName == "Filter1"
+ - cm_remove_contract1_filter1.previous.filterRef.templateName == "Template1"
+
+- name: Remove contract1 filter1 (normal_mode)
+ mso_schema_template_contract_filter:
+ <<: *contract1_filter1_absent
+ register: nm_remove_contract1_filter1
+
+- name: Verify nm_remove_contract1_filter1
+ assert:
+ that:
+ - nm_remove_contract1_filter1 is changed
+ - nm_remove_contract1_filter1.current == {}
+ - nm_remove_contract1_filter1.previous.filterRef.filterName == "Filter1"
+ - nm_remove_contract1_filter1.previous.filterRef.templateName == "Template1"
+
+- name: Remove contract1 filter1 again (check_mode)
+ mso_schema_template_contract_filter:
+ <<: *contract1_filter1_absent
+ check_mode: true
+ register: cm_remove_contract1_filter1_again
+
+- name: Verify cm_remove_contract1_filter1_again
+ assert:
+ that:
+ - cm_remove_contract1_filter1_again is not changed
+ - cm_remove_contract1_filter1_again.current == {}
+ - cm_remove_contract1_filter1_again.previous == {}
+
+- name: Remove contract1 filter1 again (normal_mode)
+ mso_schema_template_contract_filter:
+ <<: *contract1_filter1_absent
+ register: nm_remove_contract1_filter1_again
+
+- name: Verify nm_remove_contract1_filter1_again
+ assert:
+ that:
+ - nm_remove_contract1_filter1_again is not changed
+ - nm_remove_contract1_filter1_again.current == {}
+ - nm_remove_contract1_filter1_again.previous == {}
+
+- name: Remove contract1 filter4 (normal_mode)
+ mso_schema_template_contract_filter:
+ <<: *contract1_filter1_absent
+ filter: Filter4
+ register: nm_remove_contract1_filter4
+
+- name: Verify nm_remove_contract1_filter4
+ assert:
+ that:
+ - nm_remove_contract1_filter4 is changed
+ - nm_remove_contract1_filter4.current == {}
+ - nm_remove_contract1_filter4.previous.filterRef.filterName == "Filter4"
+ - nm_remove_contract1_filter4.previous.filterRef.templateName == "Template1"
+
+- name: Remove contract1 filter4 again (normal_mode)
+ mso_schema_template_contract_filter:
+ <<: *contract1_filter1_absent
+ filter: Filter4
+ register: nm_remove_contract1_filter4_again
+
+- name: Verify nm_remove_contract1_filter4_again
+ assert:
+ that:
+ - nm_remove_contract1_filter4_again is not changed
+ - nm_remove_contract1_filter4_again.previous == nm_remove_contract1_filter4_again.current == {}
+
+# QUERY NON-EXISTING FILTER
+- name: Query non-existing filter (check_mode)
+ mso_schema_template_contract_filter:
+ <<: *Contract_query
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract4
+ filter: non-existing-filter
+ check_mode: true
+ ignore_errors: true
+ register: cm_query_non_filter
+
+- name: Query non-existing filter (normal_mode)
+ mso_schema_template_contract_filter:
+ <<: *Contract_query
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract4
+ filter: non-existing-filter
+ ignore_errors: true
+ register: nm_query_non_filter
+
+- name: Verify query_non_filter
+ assert:
+ that:
+ - cm_query_non_filter is not changed
+ - nm_query_non_filter is not changed
+
+- name: Add contract (for version greater than 3.3)
+ mso_schema_template_contract_filter:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract1
+ description: "This is contract description"
+ filter: Filter1
+ qos_level: level1
+ action: deny
+ priority: 'lowest_priority'
+ state: present
+ register: add_contract
+ when: version.current.version is version('3.3', '>=')
+
+- name: Verify Add contract for version greater than 3.3
+ assert:
+ that:
+ - add_contract is changed
+ - add_contract.current.action == "deny"
+ - add_contract.current.priorityOverride == "level1"
+ when: version.current.version is version('3.3', '>=')
+
+# # QUERY NON-EXISTING CONTRACT
+- name: Query non-existing contract (check_mode)
+ mso_schema_template_contract_filter:
+ <<: *Contract_query
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: non-existing-contract
+ check_mode: true
+ ignore_errors: true
+ register: cm_query_non_contract
+
+- name: Query non-existing contract (normal_mode)
+ mso_schema_template_contract_filter:
+ <<: *Contract_query
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: non-existing-contract
+ ignore_errors: true
+ register: nm_query_non_contract
+
+- name: Verify query_non_contract
+ assert:
+ that:
+ - cm_query_non_contract is not changed
+ - nm_query_non_contract is not changed
+ - nm_query_non_contract == cm_query_non_contract
+ - cm_query_non_contract.msg == nm_query_non_contract.msg == "Provided contract 'non-existing-contract' does not exist. Existing contracts{{':'}} Contract4, Contract5, Contract-6"
+ when: version.current.version is version('3.3', '<')
+
+- name: Verify query_non_contract when version greater than 3.3
+ assert:
+ that:
+ - cm_query_non_contract.msg == nm_query_non_contract.msg == "Provided contract 'non-existing-contract' does not exist. Existing contracts{{':'}} Contract4, Contract5, Contract-6, Contract1"
+ when: version.current.version is version('3.3', '>=')
+
+# USE A NON-EXISTING SCHEMA
+- name: Non-existing schema for contrct (check_mode)
+ mso_schema_template_contract_filter:
+ <<: *Contract_query
+ template: Template 1
+ schema: non-existing-schema
+ check_mode: true
+ ignore_errors: true
+ register: cm_query_non_schema
+
+- name: Non-existing schema for contrct (normal_mode)
+ mso_schema_template_contract_filter:
+ <<: *Contract_query
+ template: Template 1
+ schema: non-existing-schema
+ ignore_errors: true
+ register: nm_query_non_schema
+
+
+- name: Verify non_existing_schema
+ assert:
+ that:
+ - cm_query_non_schema is not changed
+ - nm_query_non_schema is not changed
+ - cm_query_non_schema == nm_query_non_schema
+ - cm_query_non_schema.msg == nm_query_non_schema.msg == "Provided schema 'non-existing-schema' does not exist."
+
+# USE A NON-EXISTING TEMPLATE
+- name: Non-existing template for contract (check_mode)
+ mso_schema_template_contract_filter:
+ <<: *Contract_query
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: non-existing-template
+ check_mode: true
+ ignore_errors: true
+ register: cm_query_non_template
+
+- name: Non-existing template for contract (normal_mode)
+ mso_schema_template_contract_filter:
+ <<: *Contract_query
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: non-existing-template
+ ignore_errors: true
+ register: nm_query_non_template
+
+- name: Verify non_existing_template
+ assert:
+ that:
+ - cm_query_non_template is not changed
+ - nm_query_non_template is not changed
+ - cm_query_non_template == nm_query_non_template
+ - cm_query_non_template.msg == nm_query_non_template.msg == "Provided template 'non-existing-template' does not exist. Existing templates{{':'}} Template1, Template2" \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_contract_service_graph/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_contract_service_graph/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_contract_service_graph/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_contract_service_graph/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_contract_service_graph/tasks/main.yml
new file mode 100644
index 000000000..8bc3ce625
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_contract_service_graph/tasks/main.yml
@@ -0,0 +1,744 @@
+# Test code for the MSO modules
+# Copyright: (c) 2022, Akini Ross (@akinross) <akinross@cisco.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(false) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Remove schema 2
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ state: absent
+
+- name: Remove schema 1
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ state: absent
+
+- name: Ensure tenant ansible_test exist
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ state: present
+
+- name: Ensure schema 1 with Template 1 exist
+ mso_schema_template: &schema_present
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: Template 1
+ state: present
+
+- name: Ensure schema 1 with Template 2 exist
+ mso_schema_template:
+ <<: *schema_present
+ template: Template 2
+ state: present
+
+- name: Ensure schema 2 with Template 1 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template 1
+ state: present
+
+- name: Ensure Filter1 exist
+ cisco.mso.mso_schema_template_filter_entry: &filter_present
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ filter: Filter1
+ entry: Filter1Entry
+ state: present
+
+- name: Ensure Filter2 exist
+ cisco.mso.mso_schema_template_filter_entry:
+ <<: *filter_present
+ filter: Filter2
+ entry: Filter2Entry
+ state: present
+
+- name: Ensure Filter3 exist
+ cisco.mso.mso_schema_template_filter_entry:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 1
+ filter: Filter3
+ entry: Filter3Entry
+ state: present
+
+- name: Ensure Contract_1 contract exist
+ mso_schema_template_contract_filter: &contract_present
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract1
+ filter: Filter1
+ state: present
+
+- name: Ensure Contract_2 contract exist
+ mso_schema_template_contract_filter:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 1
+ contract: Contract2
+ filter: Filter3
+ state: present
+
+- name: Ensure SG_1 service graph exist
+ cisco.mso.mso_schema_template_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ service_graph: SG1
+ display_name: sg1
+ service_nodes:
+ - type: firewall
+ - type: load-balancer
+ state: present
+
+- name: Ensure SG_2 service graph exist
+ cisco.mso.mso_schema_template_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 1
+ service_graph: SG2
+ display_name: sg2
+ service_nodes:
+ - type: firewall
+ - type: load-balancer
+ state: present
+
+- name: Ensure VRF_1 vrf exist
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF1
+ layer3_multicast: true
+ state: present
+
+- name: Ensure BD_1 bd exist
+ mso_schema_template_bd: &bd_present
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ bd: BD1
+ vrf:
+ name: VRF1
+ state: present
+
+- name: Ensure BD_2 bd exist
+ mso_schema_template_bd:
+ <<: *bd_present
+ bd: BD2
+ state: present
+
+- name: Ensure BD_3 bd exist
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 1
+ bd: BD3
+ vrf:
+ name: VRF1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ state: present
+
+- name: Ensure AP1 in Template 1 exists
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ state: present
+
+- name: Ensure AP1 in Template 1 exists
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 1
+ anp: AP2
+ state: present
+
+- name: Ensure EPG1 in AP1 in Template 1 exists
+ mso_schema_template_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG1
+ bd:
+ name: BD1
+ state: present
+
+- name: Ensure EPG2 in AP1 in Template 1 exists
+ mso_schema_template_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 1
+ anp: AP2
+ epg: EPG2
+ bd:
+ name: BD3
+ state: present
+
+- name: Add Contract1 to EPG1 provider
+ mso_schema_template_anp_epg_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG1
+ contract:
+ name: Contract1
+ type: provider
+ state: present
+
+- name: Add Contract1 to EPG1 consumer
+ mso_schema_template_anp_epg_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: AP1
+ epg: EPG1
+ contract:
+ name: Contract1
+ type: consumer
+ state: present
+
+- name: Add Contract2 to EPG2 provider
+ mso_schema_template_anp_epg_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 1
+ anp: AP2
+ epg: EPG2
+ contract:
+ name: Contract2
+ type: provider
+ state: present
+
+- name: Add Contract2 to EPG2 consumer
+ mso_schema_template_anp_epg_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 1
+ anp: AP2
+ epg: EPG2
+ contract:
+ name: Contract2
+ type: consumer
+ state: present
+
+# TESTS
+
+- name: Add service graph 1 to Contract1 (check_mode)
+ mso_schema_template_contract_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract1
+ service_graph: SG1
+ service_nodes:
+ - consumer: BD1
+ provider: BD2
+ - consumer: BD1
+ provider: BD2
+ state: present
+ check_mode: true
+ register: cm_add_sg1_to_c1
+
+- name: Add service graph 1 to Contract1
+ mso_schema_template_contract_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract1
+ service_graph: SG1
+ service_nodes:
+ - consumer: BD1
+ provider: BD2
+ - consumer: BD1
+ provider: BD2
+ state: present
+ register: nm_add_sg1_to_c1
+
+- name: Verify service graph 1 is added to Contract1
+ assert:
+ that:
+ - cm_add_sg1_to_c1 is changed
+ - cm_add_sg1_to_c1.previous == {}
+ - cm_add_sg1_to_c1.current.serviceGraphRef.serviceGraphName == "SG1"
+ - cm_add_sg1_to_c1.current.serviceGraphRef.templateName == "Template1"
+ - cm_add_sg1_to_c1.current.serviceNodesRelationship.0.consumerConnector.bdRef.bdName == "BD1"
+ - cm_add_sg1_to_c1.current.serviceNodesRelationship.0.consumerConnector.connectorType == "general"
+ - cm_add_sg1_to_c1.current.serviceNodesRelationship.0.providerConnector.bdRef.bdName == "BD2"
+ - cm_add_sg1_to_c1.current.serviceNodesRelationship.0.providerConnector.connectorType == "general"
+ - cm_add_sg1_to_c1.current.serviceNodesRelationship.0.serviceNodeRef.serviceGraphName == "SG1"
+ - cm_add_sg1_to_c1.current.serviceNodesRelationship.0.serviceNodeRef.serviceNodeName == "firewall"
+ - cm_add_sg1_to_c1.current.serviceNodesRelationship.1.serviceNodeRef.serviceNodeName == "load-balancer"
+ - nm_add_sg1_to_c1 is changed
+ - nm_add_sg1_to_c1.previous == {}
+ - nm_add_sg1_to_c1.current.serviceGraphRef.serviceGraphName == "SG1"
+ - nm_add_sg1_to_c1.current.serviceGraphRef.templateName == "Template1"
+ - nm_add_sg1_to_c1.current.serviceNodesRelationship.0.consumerConnector.bdRef.bdName == "BD1"
+ - nm_add_sg1_to_c1.current.serviceNodesRelationship.0.consumerConnector.connectorType == "general"
+ - nm_add_sg1_to_c1.current.serviceNodesRelationship.0.providerConnector.bdRef.bdName == "BD2"
+ - nm_add_sg1_to_c1.current.serviceNodesRelationship.0.providerConnector.connectorType == "general"
+ - nm_add_sg1_to_c1.current.serviceNodesRelationship.0.serviceNodeRef.serviceGraphName == "SG1"
+ - nm_add_sg1_to_c1.current.serviceNodesRelationship.0.serviceNodeRef.serviceNodeName == "firewall"
+ - nm_add_sg1_to_c1.current.serviceNodesRelationship.1.serviceNodeRef.serviceNodeName == "load-balancer"
+
+- name: Add service graph 1 to Contract1 again (check_mode)
+ mso_schema_template_contract_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract1
+ service_graph: SG1
+ service_nodes:
+ - consumer: BD1
+ provider: BD2
+ - consumer: BD1
+ provider: BD2
+ state: present
+ check_mode: true
+ register: cm_add_sg1_to_c1_again
+
+- name: Add service graph 1 to Contract1 again
+ mso_schema_template_contract_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract1
+ service_graph: SG1
+ service_nodes:
+ - consumer: BD1
+ provider: BD2
+ - consumer: BD1
+ provider: BD2
+ state: present
+ register: nm_add_sg1_to_c1_again
+
+- name: Verify service graph 1 is added to Contract1 again
+ assert:
+ that:
+ - cm_add_sg1_to_c1_again is not changed
+ - cm_add_sg1_to_c1_again.previous.serviceGraphRef.serviceGraphName == "SG1"
+ - cm_add_sg1_to_c1_again.previous.serviceGraphRef.templateName == "Template1"
+ - cm_add_sg1_to_c1_again.previous.serviceNodesRelationship.0.consumerConnector.bdRef.bdName == "BD1"
+ - cm_add_sg1_to_c1_again.previous.serviceNodesRelationship.0.consumerConnector.connectorType == "general"
+ - cm_add_sg1_to_c1_again.previous.serviceNodesRelationship.0.providerConnector.bdRef.bdName == "BD2"
+ - cm_add_sg1_to_c1_again.previous.serviceNodesRelationship.0.providerConnector.connectorType == "general"
+ - cm_add_sg1_to_c1_again.previous.serviceNodesRelationship.0.serviceNodeRef.serviceGraphName == "SG1"
+ - cm_add_sg1_to_c1_again.previous.serviceNodesRelationship.0.serviceNodeRef.serviceNodeName == "firewall"
+ - cm_add_sg1_to_c1_again.previous.serviceNodesRelationship.1.serviceNodeRef.serviceNodeName == "load-balancer"
+ - nm_add_sg1_to_c1_again is not changed
+ - nm_add_sg1_to_c1_again.previous.serviceGraphRef.serviceGraphName == "SG1"
+ - nm_add_sg1_to_c1_again.previous.serviceGraphRef.templateName == "Template1"
+ - nm_add_sg1_to_c1_again.previous.serviceNodesRelationship.0.consumerConnector.bdRef.bdName == "BD1"
+ - nm_add_sg1_to_c1_again.previous.serviceNodesRelationship.0.consumerConnector.connectorType == "general"
+ - nm_add_sg1_to_c1_again.previous.serviceNodesRelationship.0.providerConnector.bdRef.bdName == "BD2"
+ - nm_add_sg1_to_c1_again.previous.serviceNodesRelationship.0.providerConnector.connectorType == "general"
+ - nm_add_sg1_to_c1_again.previous.serviceNodesRelationship.0.serviceNodeRef.serviceGraphName == "SG1"
+ - nm_add_sg1_to_c1_again.previous.serviceNodesRelationship.0.serviceNodeRef.serviceNodeName == "firewall"
+ - nm_add_sg1_to_c1_again.previous.serviceNodesRelationship.1.serviceNodeRef.serviceNodeName == "load-balancer"
+
+- name: Change service graph 1 node to Contract1 (check_mode)
+ mso_schema_template_contract_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract1
+ service_graph: SG1
+ service_nodes:
+ - consumer: BD2
+ provider: BD1
+ - consumer: BD2
+ provider: BD1
+ state: present
+ check_mode: true
+ register: cm_change_sg1_to_c1
+
+- name: Change service graph 1 node to Contract1
+ mso_schema_template_contract_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract1
+ service_graph: SG1
+ service_nodes:
+ - consumer: BD2
+ provider: BD1
+ - consumer: BD2
+ provider: BD1
+ state: present
+ register: nm_change_sg1_to_c1
+
+- name: Verify service graph 1 is added to Contract1 again
+ assert:
+ that:
+ - cm_change_sg1_to_c1 is changed
+ - cm_change_sg1_to_c1.current.serviceNodesRelationship.0.consumerConnector.bdRef.bdName == "BD2"
+ - cm_change_sg1_to_c1.current.serviceNodesRelationship.0.consumerConnector.connectorType == "general"
+ - cm_change_sg1_to_c1.current.serviceNodesRelationship.0.providerConnector.bdRef.bdName == "BD1"
+ - cm_change_sg1_to_c1.current.serviceNodesRelationship.0.providerConnector.connectorType == "general"
+ - cm_change_sg1_to_c1.previous.serviceNodesRelationship.0.consumerConnector.bdRef.bdName == "BD1"
+ - cm_change_sg1_to_c1.previous.serviceNodesRelationship.0.consumerConnector.connectorType == "general"
+ - cm_change_sg1_to_c1.previous.serviceNodesRelationship.0.providerConnector.bdRef.bdName == "BD2"
+ - cm_change_sg1_to_c1.previous.serviceNodesRelationship.0.providerConnector.connectorType == "general"
+ - cm_change_sg1_to_c1 is changed
+ - nm_change_sg1_to_c1.current.serviceNodesRelationship.0.consumerConnector.bdRef.bdName == "BD2"
+ - nm_change_sg1_to_c1.current.serviceNodesRelationship.0.consumerConnector.connectorType == "general"
+ - nm_change_sg1_to_c1.current.serviceNodesRelationship.0.providerConnector.bdRef.bdName == "BD1"
+ - nm_change_sg1_to_c1.current.serviceNodesRelationship.0.providerConnector.connectorType == "general"
+ - nm_change_sg1_to_c1.previous.serviceNodesRelationship.0.consumerConnector.bdRef.bdName == "BD1"
+ - nm_change_sg1_to_c1.previous.serviceNodesRelationship.0.consumerConnector.connectorType == "general"
+ - nm_change_sg1_to_c1.previous.serviceNodesRelationship.0.providerConnector.bdRef.bdName == "BD2"
+ - nm_change_sg1_to_c1.previous.serviceNodesRelationship.0.providerConnector.connectorType == "general"
+
+- name: Query service graph 1 to Contract1 (check_mode)
+ mso_schema_template_contract_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract1
+ service_graph: SG1
+ state: query
+ register: cm_query_sg1_to_c1
+
+- name: Query service graph 1 to Contract1
+ mso_schema_template_contract_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract1
+ service_graph: SG1
+ state: query
+ register: nm_query_sg1_to_c1
+
+- name: Verify queried service graph 1
+ assert:
+ that:
+ - cm_query_sg1_to_c1 is not changed
+ - cm_query_sg1_to_c1.current.serviceNodesRelationship.0.consumerConnector.bdRef.bdName == "BD2"
+ - cm_query_sg1_to_c1.current.serviceNodesRelationship.0.consumerConnector.bdRef.templateName == "Template1"
+ - cm_query_sg1_to_c1.current.serviceNodesRelationship.0.consumerConnector.connectorType == "general"
+ - cm_query_sg1_to_c1.current.serviceNodesRelationship.0.providerConnector.bdRef.bdName == "BD1"
+ - cm_query_sg1_to_c1.current.serviceNodesRelationship.0.consumerConnector.bdRef.templateName == "Template1"
+ - cm_query_sg1_to_c1.current.serviceNodesRelationship.0.providerConnector.connectorType == "general"
+ - cm_query_sg1_to_c1 is not changed
+ - nm_query_sg1_to_c1.current.serviceNodesRelationship.0.consumerConnector.bdRef.bdName == "BD2"
+ - cm_query_sg1_to_c1.current.serviceNodesRelationship.0.consumerConnector.bdRef.templateName == "Template1"
+ - nm_query_sg1_to_c1.current.serviceNodesRelationship.0.consumerConnector.connectorType == "general"
+ - nm_query_sg1_to_c1.current.serviceNodesRelationship.0.providerConnector.bdRef.bdName == "BD1"
+ - cm_query_sg1_to_c1.current.serviceNodesRelationship.0.consumerConnector.bdRef.templateName == "Template1"
+ - nm_query_sg1_to_c1.current.serviceNodesRelationship.0.providerConnector.connectorType == "general"
+
+- name: Remove service graph 1 from Contract1 (check_mode)
+ mso_schema_template_contract_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract1
+ service_graph: SG1
+ state: absent
+ check_mode: true
+ register: cm_remove_sg1_from_c1
+
+- name: Remove service graph 1 from Contract1
+ mso_schema_template_contract_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract1
+ service_graph: SG1
+ state: absent
+ register: nm_remove_sg1_from_c1
+
+- name: Verify service graph 1 is removed from Contract1
+ assert:
+ that:
+ - cm_remove_sg1_from_c1 is changed
+ - cm_remove_sg1_from_c1.current == {}
+ - cm_remove_sg1_from_c1.previous.serviceGraphRef.serviceGraphName == "SG1"
+ - cm_remove_sg1_from_c1.previous.serviceGraphRef.templateName == "Template1"
+ - cm_remove_sg1_from_c1.previous.serviceNodesRelationship.0.consumerConnector.bdRef.bdName == "BD2"
+ - cm_remove_sg1_from_c1.previous.serviceNodesRelationship.0.consumerConnector.connectorType == "general"
+ - cm_remove_sg1_from_c1.previous.serviceNodesRelationship.0.providerConnector.bdRef.bdName == "BD1"
+ - cm_remove_sg1_from_c1.previous.serviceNodesRelationship.0.providerConnector.connectorType == "general"
+ - cm_remove_sg1_from_c1.previous.serviceNodesRelationship.0.serviceNodeRef.serviceGraphName == "SG1"
+ - cm_remove_sg1_from_c1.previous.serviceNodesRelationship.0.serviceNodeRef.serviceNodeName == "firewall"
+ - cm_remove_sg1_from_c1.previous.serviceNodesRelationship.1.serviceNodeRef.serviceNodeName == "load-balancer"
+ - nm_remove_sg1_from_c1 is changed
+ - nm_remove_sg1_from_c1.current == {}
+ - nm_remove_sg1_from_c1.previous.serviceGraphRef.serviceGraphName == "SG1"
+ - nm_remove_sg1_from_c1.previous.serviceGraphRef.templateName == "Template1"
+ - nm_remove_sg1_from_c1.previous.serviceNodesRelationship.0.consumerConnector.bdRef.bdName == "BD2"
+ - nm_remove_sg1_from_c1.previous.serviceNodesRelationship.0.consumerConnector.connectorType == "general"
+ - nm_remove_sg1_from_c1.previous.serviceNodesRelationship.0.providerConnector.bdRef.bdName == "BD1"
+ - nm_remove_sg1_from_c1.previous.serviceNodesRelationship.0.providerConnector.connectorType == "general"
+ - nm_remove_sg1_from_c1.previous.serviceNodesRelationship.0.serviceNodeRef.serviceGraphName == "SG1"
+ - nm_remove_sg1_from_c1.previous.serviceNodesRelationship.0.serviceNodeRef.serviceNodeName == "firewall"
+ - nm_remove_sg1_from_c1.previous.serviceNodesRelationship.1.serviceNodeRef.serviceNodeName == "load-balancer"
+
+- name: Add service graph 2 to Contract2 with BD in other schema (check_mode)
+ mso_schema_template_contract_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 1
+ contract: Contract2
+ service_graph: SG2
+ service_nodes:
+ - consumer: BD1
+ consumer_template: Template 1
+ consumer_schema: '{{ mso_schema | default("ansible_test") }}'
+ provider: BD3
+ - consumer: BD3
+ provider: BD2
+ provider_template: Template 1
+ provider_schema: '{{ mso_schema | default("ansible_test") }}'
+ state: present
+ check_mode: true
+ register: cm_add_sg2_to_c2
+
+- name: Add service graph 2 to Contract2 with BD in other schema
+ mso_schema_template_contract_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 1
+ contract: Contract2
+ service_graph: SG2
+ service_nodes:
+ - consumer: BD1
+ consumer_template: Template 1
+ consumer_schema: '{{ mso_schema | default("ansible_test") }}'
+ provider: BD3
+ - consumer: BD3
+ provider: BD2
+ provider_template: Template 1
+ provider_schema: '{{ mso_schema | default("ansible_test") }}'
+ state: present
+ register: nm_add_sg2_to_c2
+
+- name: Verify service graph 2 is added to Contract2
+ assert:
+ that:
+ - cm_add_sg2_to_c2 is changed
+ - cm_add_sg2_to_c2.previous == {}
+ - cm_add_sg2_to_c2.current.serviceGraphRef.serviceGraphName == "SG2"
+ - cm_add_sg2_to_c2.current.serviceGraphRef.templateName == "Template1"
+ - cm_add_sg2_to_c2.current.serviceNodesRelationship.0.consumerConnector.bdRef.bdName == "BD1"
+ - cm_add_sg2_to_c2.current.serviceNodesRelationship.0.consumerConnector.connectorType == "general"
+ - cm_add_sg2_to_c2.current.serviceNodesRelationship.0.providerConnector.bdRef.bdName == "BD3"
+ - cm_add_sg2_to_c2.current.serviceNodesRelationship.0.providerConnector.connectorType == "general"
+ - cm_add_sg2_to_c2.current.serviceNodesRelationship.0.serviceNodeRef.serviceGraphName == "SG2"
+ - cm_add_sg2_to_c2.current.serviceNodesRelationship.0.serviceNodeRef.serviceNodeName == "firewall"
+ - cm_add_sg2_to_c2.current.serviceNodesRelationship.1.serviceNodeRef.serviceNodeName == "load-balancer"
+ - nm_add_sg2_to_c2 is changed
+ - nm_add_sg2_to_c2.previous == {}
+ - nm_add_sg2_to_c2.current.serviceGraphRef.serviceGraphName == "SG2"
+ - nm_add_sg2_to_c2.current.serviceGraphRef.templateName == "Template1"
+ - nm_add_sg2_to_c2.current.serviceNodesRelationship.0.consumerConnector.bdRef.bdName == "BD1"
+ - nm_add_sg2_to_c2.current.serviceNodesRelationship.0.consumerConnector.connectorType == "general"
+ - nm_add_sg2_to_c2.current.serviceNodesRelationship.0.providerConnector.bdRef.bdName == "BD3"
+ - nm_add_sg2_to_c2.current.serviceNodesRelationship.0.providerConnector.connectorType == "general"
+ - nm_add_sg2_to_c2.current.serviceNodesRelationship.0.serviceNodeRef.serviceGraphName == "SG2"
+ - nm_add_sg2_to_c2.current.serviceNodesRelationship.0.serviceNodeRef.serviceNodeName == "firewall"
+ - nm_add_sg2_to_c2.current.serviceNodesRelationship.1.serviceNodeRef.serviceNodeName == "load-balancer"
+
+- name: Query service graph 2 to Contract2 (check_mode)
+ mso_schema_template_contract_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 1
+ contract: Contract2
+ service_graph: SG2
+ state: query
+ register: cm_query_sg2_to_c2
+
+- name: Query service graph 2 to Contract2
+ mso_schema_template_contract_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 1
+ contract: Contract2
+ service_graph: SG2
+ state: query
+ register: nm_query_sg2_to_c2
+
+- name: Verify queried service graph 1
+ assert:
+ that:
+ - cm_query_sg2_to_c2 is not changed
+ - cm_query_sg2_to_c2.current.serviceNodesRelationship.0.consumerConnector.bdRef.bdName == "BD1"
+ - cm_query_sg2_to_c2.current.serviceNodesRelationship.0.consumerConnector.bdRef.templateName == "Template1"
+ - cm_query_sg2_to_c2.current.serviceNodesRelationship.0.consumerConnector.connectorType == "general"
+ - cm_query_sg2_to_c2.current.serviceNodesRelationship.0.providerConnector.bdRef.bdName == "BD3"
+ - cm_query_sg2_to_c2.current.serviceNodesRelationship.0.consumerConnector.bdRef.templateName == "Template1"
+ - cm_query_sg2_to_c2.current.serviceNodesRelationship.0.providerConnector.connectorType == "general"
+ - cm_query_sg2_to_c2.current.serviceNodesRelationship.0.consumerConnector.bdRef.schemaId != cm_query_sg2_to_c2.current.serviceGraphRef.schemaId
+ - cm_query_sg2_to_c2 is not changed
+ - nm_query_sg2_to_c2.current.serviceNodesRelationship.0.consumerConnector.bdRef.bdName == "BD1"
+ - cm_query_sg2_to_c2.current.serviceNodesRelationship.0.consumerConnector.bdRef.templateName == "Template1"
+ - nm_query_sg2_to_c2.current.serviceNodesRelationship.0.consumerConnector.connectorType == "general"
+ - nm_query_sg2_to_c2.current.serviceNodesRelationship.0.providerConnector.bdRef.bdName == "BD3"
+ - cm_query_sg2_to_c2.current.serviceNodesRelationship.0.consumerConnector.bdRef.templateName == "Template1"
+ - nm_query_sg2_to_c2.current.serviceNodesRelationship.0.providerConnector.connectorType == "general"
+ - nm_query_sg2_to_c2.current.serviceNodesRelationship.0.consumerConnector.bdRef.schemaId != nm_query_sg2_to_c2.current.serviceGraphRef.schemaId
+
+# NOT EXISTING INPUT
+
+- name: Not existing template provided for absent
+ mso_schema_template_contract_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template False
+ contract: Contract1
+ service_graph: SG1
+ state: absent
+ ignore_errors: true
+ register: not_existing_template_input_absent
+
+- name: Not existing contract provided for absent
+ mso_schema_template_contract_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract False
+ service_graph: SG1
+ state: absent
+ ignore_errors: true
+ register: not_existing_contract_input_absent
+
+- name: Not existing service graph provided for absent
+ mso_schema_template_contract_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract1
+ service_graph: SG False
+ state: absent
+ register: not_existing_service_graph_input_absent
+
+- name: Not existing service graph provided for query
+ mso_schema_template_contract_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract1
+ service_graph: SG False
+ state: query
+ register: not_existing_service_graph_input_query
+
+- name: Not existing template provided for present
+ mso_schema_template_contract_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract1
+ service_graph: SG1
+ service_graph_template: Template False
+ service_nodes:
+ - consumer: BD2
+ provider: BD1
+ - consumer: BD2
+ provider: BD1
+ state: present
+ ignore_errors: true
+ register: not_existing_template_input_present
+
+- name: Not existing service graph provided for present
+ mso_schema_template_contract_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract1
+ service_graph: SG False
+ service_graph_template: Template 2
+ service_nodes:
+ - consumer: BD2
+ provider: BD1
+ - consumer: BD2
+ provider: BD1
+ state: present
+ ignore_errors: true
+ register: not_existing_service_graph_input_present
+
+- name: Verify non_existing_input
+ assert:
+ that:
+ - not_existing_template_input_absent is not changed
+ - not_existing_template_input_absent.msg.startswith("Provided template")
+ - not_existing_contract_input_absent is not changed
+ - not_existing_contract_input_absent.msg.startswith("Provided contract")
+ - not_existing_service_graph_input_absent is not changed
+ - not_existing_service_graph_input_query is not changed
+ - not_existing_template_input_present is not changed
+ - not_existing_template_input_present.msg.startswith("Provided template")
+ - not_existing_service_graph_input_present is not changed
+ - not_existing_service_graph_input_present.msg.startswith("Provided service graph")
+
+# False input
+
+- name: False service graph node amount provided ( less than 2 as provided in SG1 config )
+ mso_schema_template_contract_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract1
+ service_graph: SG1
+ service_nodes:
+ - consumer: BD1
+ provider: BD2
+ state: present
+ ignore_errors: true
+ register: nm_false_sg1_to_c1_less_than_2
+
+- name: False service graph node amount provided ( more than 2 as provided in SG1 config )
+ mso_schema_template_contract_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract1
+ service_graph: SG1
+ service_nodes:
+ - consumer: BD1
+ provider: BD2
+ - consumer: BD1
+ provider: BD2
+ - consumer: BD1
+ provider: BD2
+ state: present
+ ignore_errors: true
+ register: nm_false_sg1_to_c1_more_than_2
+
+- name: Verify false_sg1_to_c1
+ assert:
+ that:
+ - nm_false_sg1_to_c1_less_than_2 is not changed
+ - nm_false_sg1_to_c1_less_than_2.msg.startswith("Not enough service nodes defined")
+ - nm_false_sg1_to_c1_more_than_2 is not changed
+ - nm_false_sg1_to_c1_more_than_2.msg.startswith("Too many service nodes defined")
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_deploy/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_deploy/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_deploy/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_deploy/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_deploy/tasks/main.yml
new file mode 100644
index 000000000..58f043256
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_deploy/tasks/main.yml
@@ -0,0 +1,237 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ ansible.builtin.set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ cisco.mso.mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+# mso_schema_template_deploy is deprecated in MSO/NDO v4.0+, different api endpoint thus different module
+- name: Execute tasks only for MSO version < 4.0
+ when: version.current.version is version('4.0', '<')
+ block:
+ - name: Ensure site exist
+ cisco.mso.mso_site:
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ apic_username }}'
+ apic_password: '{{ apic_password }}'
+ apic_site_id: '{{ apic_site_id | default(101) }}'
+ urls:
+ - https://{{ apic_hostname }}
+ state: present
+
+ - name: Undeploy template
+ cisco.mso.mso_schema_template_deploy:
+ <<: *mso_info
+ schema: ansible_test
+ template: "{{ item }}"
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: undeploy
+ ignore_errors: true
+ loop:
+ - Template 1
+ - Template 2
+ - Template 3
+ - Template 4
+ - Template_5
+ - Template 5
+ - Template5
+
+ - name: Remove schemas
+ cisco.mso.mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+ - name: Ensure tenant ansible_test exists
+ cisco.mso.mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ state: present
+
+ - name: Ensure schema 1 with Template 1, and Template 2, Template 3 exist
+ cisco.mso.mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: '{{ item }}'
+ state: present
+ loop:
+ - Template 1
+ - Template 2
+ - Template 3
+ - Template 4
+ - Template_5
+
+ - name: Add physical site to a schema
+ cisco.mso.mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: '{{ item }}'
+ state: present
+ loop:
+ - Template 1
+ - Template 2
+ - Template 3
+ - Template 4
+ - Template_5
+
+ - name: Deploy templates (check_mode)
+ cisco.mso.mso_schema_template_deploy: &schema_deploy
+ <<: *mso_info
+ schema: ansible_test
+ template: "{{ item }}"
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: deploy
+ check_mode: true
+ register: cm_deploy_template
+ loop:
+ - Template 1
+ - Template 2
+ - Template 3
+ - Template 4
+ - Template_5
+
+ - name: Verify cm_deploy_template
+ ansible.builtin.assert:
+ that:
+ - item is not changed
+ loop: "{{ cm_deploy_template.results }}"
+
+ - name: Deploy templates (normal_mode)
+ cisco.mso.mso_schema_template_deploy:
+ <<: *schema_deploy
+ schema: ansible_test
+ template: "{{ item }}"
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: deploy
+ register: nm_deploy_template
+ loop:
+ - Template 1
+ - Template 2
+ - Template 3
+ - Template 4
+ - Template_5
+
+ - name: Verify nm_deploy_template
+ ansible.builtin.assert:
+ that:
+ - item is not changed
+ - item.msg == "Successfully deployed"
+ loop: "{{ nm_deploy_template.results }}"
+
+ - name: Get deployment status
+ cisco.mso.mso_schema_template_deploy:
+ <<: *schema_deploy
+ schema: ansible_test
+ template: "{{ item }}"
+ state: status
+ register: query_deploy_status
+ loop:
+ - Template 1
+ - Template 2
+ - Template 3
+ - Template 4
+ - Template_5
+
+ - name: Verify query_deploy_status
+ ansible.builtin.assert:
+ that:
+ - item is not changed
+ - item.status.0.status.siteStatus == "Succeeded"
+ loop: "{{ query_deploy_status.results }}"
+
+ - name: Undeploy templates
+ cisco.mso.mso_schema_template_deploy:
+ <<: *schema_deploy
+ schema: ansible_test
+ template: '{{ item }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: undeploy
+ register: undeploy_template
+ loop:
+ - Template 1
+ - Template 2
+ - Template 3
+ - Template 4
+ - Template_5
+
+ - name: Verify undeploy_template
+ ansible.builtin.assert:
+ that:
+ - item is not changed
+ - item.msg == "Successfully Un-deployed"
+ loop: "{{ undeploy_template.results }}"
+ when: version.current.version is version('3.1', '>=')
+
+ - name: Verify undeploy_template
+ ansible.builtin.assert:
+ that:
+ - item is not changed
+ - item.msg == "Successfully deployed"
+ loop: "{{ undeploy_template.results }}"
+ when: version.current.version is version('3.1', '<')
+
+ # Validate schema when MSO version >= 3.3
+ - name: Execute tasks only for MSO version >= 3.3
+ when: version.current.version is version('3.3', '>=')
+ block:
+ - name: Add VRF1 with validation error
+ cisco.mso.mso_schema_template_vrf: &fail_validation
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ vrf: VRF1
+ layer3_multicast: true
+ vzany: true
+ state: present
+
+ - name: Deploy template with validation error
+ cisco.mso.mso_schema_template_deploy:
+ <<: *mso_info
+ schema: ansible_test
+ template: Template 2
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: deploy
+ register: failed_validaton_deploy
+ ignore_errors: true
+
+ - name: Verify validation errors before deploy and redploy
+ ansible.builtin.assert:
+ that:
+ - failed_validaton_deploy.msg == "MSO Error 400{{':'}} Bad Request{{':'}} Patch Failed, Received{{':'}} vzAny contract must be configured if vzAny flag is set. VRF(s) {{':'}} VRF1 exception while trying to update schema"
+
+ - name: Remove VRF1 with validation error
+ cisco.mso.mso_schema_template_vrf:
+ <<: *fail_validation
+ state: absent \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_deploy_status/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_deploy_status/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_deploy_status/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_deploy_status/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_deploy_status/tasks/main.yml
new file mode 100644
index 000000000..fce5cf8e8
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_deploy_status/tasks/main.yml
@@ -0,0 +1,627 @@
+# Test code for the MSO modules
+# Copyright: (c) 2021, Shreyas Srish (@shrsr) <ssrish@cisco.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3[0].txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Ensure site exist
+ mso_site: &site_present
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ apic_username }}'
+ apic_password: '{{ apic_password }}'
+ apic_site_id: '{{ apic_site_id | default(101) }}'
+ urls:
+ - https://{{ apic_hostname }}
+ state: present
+
+- name: Ensure aws site exists
+ mso_site:
+ <<: *mso_info
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ aws_apic_username }}'
+ apic_password: '{{ aws_apic_password }}'
+ apic_site_id: '{{ aws_site_id | default(102) }}'
+ urls:
+ - https://{{ aws_apic_hostname }}
+ state: present
+
+- name: Undeploy templates if deployed to clean the environment before ndo 4.0
+ mso_schema_template_deploy:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: "{{ item }}"
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: undeploy
+ ignore_errors: true
+ loop:
+ - Template 1
+ - Template 2
+ when: version.current.version is version('4.0', '<')
+
+#- name: Undeploy templates if deployed to clean the environment after ndo 4.0
+# <TBD>:
+# <<: *mso_info
+# schema: '{{ mso_schema | default("ansible_test") }}'
+# template: "{{ item }}"
+# site: '{{ mso_site | default("ansible_test") }}'
+# state: undeploy
+# ignore_errors: true
+# loop:
+# - Template 1
+# - Template 2
+# when: version.current.version is version('4.0', '>=')
+
+- name: Remove schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Ensure tenant ansible_test exists
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ state: present
+
+- name: Ensure schema 1 with Template 1, Template 2
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: '{{ item }}'
+ state: present
+ loop:
+ - Template 1
+ - Template 2
+
+- name: Add a new site to a schema with Template 1, Template 2
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: '{{ item }}'
+ state: present
+ loop:
+ - Template 1
+ - Template 2
+
+- name: Ensure VRF1 exists on Template1
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF1
+ state: present
+
+- name: Ensure VRF2 exists on Template2
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ vrf: VRF2
+ state: present
+
+- name: Ensure ANP exists
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ state: present
+
+- name: Ensure ANP2 exists
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ anp: ANP2
+ state: present
+
+- name: Check deployment status of Template 1
+ mso_schema_template_deploy_status:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ state: query
+ register: status1_temp1
+
+- name: Check deployment status of Template 2
+ mso_schema_template_deploy_status:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ state: query
+ register: status1_temp2
+
+- name: Verify status after adding VRFs and ANPs
+ assert:
+ that:
+ - status1_temp1.current[0].anps[0].state == 'created'
+ - status1_temp1.current[0].vrfs[0].state == 'created'
+ - status1_temp2.current[0].anps[0].state == 'created'
+ - status1_temp2.current[0].vrfs[0].state == 'created'
+
+- name: Check deployment status by querying site
+ mso_schema_template_deploy_status:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: query
+ register: status_site
+
+- name: Check deployment status by querying site and Template1
+ mso_schema_template_deploy_status:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: query
+ register: status_site_temp1
+
+- name: Check deployment status by querying site and Template2
+ mso_schema_template_deploy_status:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template2
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: query
+ register: status_site_temp2
+
+- name: Verify status after querying site before deployment
+ assert:
+ that:
+ - status_site.current | length == 2
+ - status_site_temp1.current.anps[0].state == 'created'
+ - status_site_temp1.current.vrfs[0].state == 'created'
+ - status_site_temp2.current.anps[0].state == 'created'
+ - status_site_temp2.current.vrfs[0].state == 'created'
+
+
+- name: Ensure ansible_test_1 BD exists
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ bd: ansible_test_1
+ vrf:
+ name: VRF1
+ template: Template 1
+ state: present
+
+- name: Ensure ansible_test_2 BD exists
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ bd: ansible_test_2
+ vrf:
+ name: VRF2
+ template: Template 2
+ state: present
+
+- name: Add EPG to Template 1
+ mso_schema_template_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ bd:
+ name: ansible_test_1
+ vrf:
+ name: VRF1
+ state: present
+
+- name: Add EPG to Template 2
+ mso_schema_template_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ anp: ANP2
+ epg: ansible_test_2
+ bd:
+ name: ansible_test_2
+ vrf:
+ name: VRF2
+ state: present
+
+- name: Check deployment status of Template 1
+ mso_schema_template_deploy_status:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ state: query
+ register: status2_temp1
+
+- name: Check deployment status of Template 2
+ mso_schema_template_deploy_status:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ state: query
+ register: status2_temp2
+
+- name: Verify status after adding BDs and EPGs
+ assert:
+ that:
+ - status2_temp1.current[0].bds[0].state == 'created'
+ - status2_temp1.current[0].anps[0].epgs[0].state == 'created'
+ - status2_temp2.current[0].bds[0].state == 'created'
+ - status2_temp2.current[0].anps[0].epgs[0].state == 'created'
+
+- name: Add VRF3 exists to Template1
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF3
+ state: present
+
+- name: Add ansible_test_3 BD
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ bd: ansible_test_3
+ vrf:
+ name: VRF3
+ template: Template 1
+ state: present
+
+- name: Check deployment status of Template 1
+ mso_schema_template_deploy_status:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ state: query
+ register: status3_temp1
+
+- name: Verify status after adding new BD and VRF and changing EPG1
+ assert:
+ that:
+ - status3_temp1.current[0].bds[0].state == 'created'
+ - status3_temp1.current[0].vrfs[0].state == 'created'
+
+# mso_schema_template_deploy is deprecated in MSO/NDO v4.0+, different api endpoint thus different module
+# when new module created, remove block and do execution for each mso_schema_template_deploy tasks
+- name: Execute tasks only for MSO version < 4.0
+ when: version.current.version is version('4.0', '<')
+ block:
+ - name: Deploy templates
+ mso_schema_template_deploy:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: "{{ item }}"
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: deploy
+ register: cm_deploy_template
+ loop:
+ - Template 1
+ - Template 2
+
+ - name: Change EPG
+ mso_schema_template_anp_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ bd:
+ name: ansible_test_3
+ vrf:
+ name: VRF3
+ state: present
+
+ - name: Check deployment status of Template 1
+ mso_schema_template_deploy_status:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ state: query
+ register: status_change_temp1
+
+ - name: Verify status after changing EPG
+ assert:
+ that:
+ - status_change_temp1.current[0].anps[0].epgs[0].state == 'modified'
+
+ - name: Delete ansible_test_1 BD
+ mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ bd: ansible_test_1
+ vrf:
+ name: VRF1
+ template: Template 1
+ state: absent
+
+ - name: Delete VRF1
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF1
+ state: absent
+
+ - name: Check deployment status of Template 1
+ mso_schema_template_deploy_status:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ state: query
+ register: status4_temp1
+
+ - name: Verify status after deleting VRF1
+ assert:
+ that:
+ - status4_temp1.current[0].bds[0].state == 'deleted'
+ - status4_temp1.current[0].vrfs[0].state == 'deleted'
+
+ - name: Try deploy and check results
+ block:
+ - name: Deploy templates Template 1
+ mso_schema_template_deploy:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: deploy
+
+ - name: Check deployment status of Template 1
+ mso_schema_template_deploy_status:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ state: query
+ register: status5_temp1
+
+ - name: Increment the retry count
+ set_fact:
+ retry_count: "{{ 0 if retry_count is undefined else retry_count | int + 1 }}"
+
+ rescue:
+ - fail:
+ msg: Status5_temp1 correct value retrieved continuing
+ when:
+ - status5_temp1.current[0] is defined
+ - status5_temp1.current[0].anps == []
+
+ - fail:
+ msg: Maximum retries of deploy and check group for status5_temp1 reached
+ when: retry_count | int == 10
+
+ - debug:
+ msg: "Deploy and check group for status5_temp1 failed, let's give it another shot"
+
+ - name: Reset the retry count
+ set_fact:
+ retry_count:
+
+ - name: Try deploy and check results
+ block:
+ - name: Deploy templates Template 2
+ mso_schema_template_deploy:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: deploy
+
+ - name: Check deployment status of Template 2
+ mso_schema_template_deploy_status:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ state: query
+ register: status5_temp2
+
+ - name: Increment the retry count
+ set_fact:
+ retry_count: "{{ 0 if retry_count is undefined else retry_count | int + 1 }}"
+
+ rescue:
+ - fail:
+ msg: status5_temp2 correct value retrieved continuing
+ when:
+ - status5_temp2.current[0] is defined
+ - status5_temp2.current[0].anps == []
+
+ - fail:
+ msg: Maximum retries of deploy and check group for status5_temp2 reached
+ when: retry_count | int == 10
+
+ - debug:
+ msg: "Deploy and check group for status5_temp2 failed, let's give it another shot"
+
+ - name: Verify status after deploying Templates to site
+ assert:
+ that:
+ - status5_temp1.current[0].anps == []
+ - status5_temp1.current[0].bds == []
+ - status5_temp1.current[0].vrfs == []
+ - status5_temp2.current[0].anps == []
+ - status5_temp2.current[0].bds == []
+ - status5_temp2.current[0].vrfs == []
+
+ - name: Check status of all templates
+ mso_schema_template_deploy_status:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ state: query
+ register: all_templates
+
+ - name: Verify all
+ assert:
+ that:
+ - all_templates.current.policyStates | length == 2
+
+ - name: Check deployment status by querying site
+ mso_schema_template_deploy_status:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: query
+ register: status2_site
+
+ - name: Reset the retry count
+ set_fact:
+ retry_count:
+
+ - name: Try deploy and check results for a site and Template1
+ block:
+ - name: Deploy templates Template1
+ mso_schema_template_deploy:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: deploy
+
+ - name: Check deployment status by querying site and Template1
+ mso_schema_template_deploy_status:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: query
+ register: status2_site_temp1
+
+ - name: Increment the retry count
+ set_fact:
+ retry_count: "{{ 0 if retry_count is undefined else retry_count | int + 1 }}"
+
+ rescue:
+ - fail:
+ msg: status2_site_temp1 correct value retrieved continuing
+ when:
+ - status2_site_temp1.current is defined
+ - status2_site_temp1.current.anps == []
+
+ - fail:
+ msg: Maximum retries of deploy and check group for status2_site_temp1 reached
+ when: retry_count | int == 10
+
+ - debug:
+ msg: "Deploy and check group for status2_site_temp1 failed, let's give it another shot"
+
+ - name: Try deploy and check results for a site and Template2
+ block:
+ - name: Deploy templates Template2
+ mso_schema_template_deploy:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template2
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: deploy
+
+ - name: Check deployment status by querying site and Template2
+ mso_schema_template_deploy_status:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template2
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: query
+ register: status2_site_temp2
+
+ - name: Increment the retry count
+ set_fact:
+ retry_count: "{{ 0 if retry_count is undefined else retry_count | int + 1 }}"
+
+ rescue:
+ - fail:
+ msg: status2_site_temp2 correct value retrieved continuing
+ when:
+ - status2_site_temp2.current is defined
+ - status2_site_temp2.current.anps == []
+
+ - fail:
+ msg: Maximum retries of deploy and check group for status2_site_temp2 reached
+ when: retry_count | int == 10
+
+ - debug:
+ msg: "Deploy and check group for status2_site_temp2 failed, let's give it another shot"
+
+ - name: Verify status after querying site post deployment
+ assert:
+ that:
+ - status2_site.current | length == 2
+ - status2_site_temp1.current.anps == []
+ - status2_site_temp1.current.bds == []
+ - status2_site_temp1.current.vrfs == []
+ - status2_site_temp2.current.anps == []
+ - status2_site_temp2.current.bds == []
+ - status2_site_temp2.current.vrfs == []
+
+ - name: Check deployment status by querying site and non associated Template 1
+ mso_schema_template_deploy_status:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ state: query
+ ignore_errors: true
+ register: status_site_temp3
+
+ - name: Verify status after querying site with non associated Template 1
+ assert:
+ that:
+ - status_site_temp3.msg == "Provided Template 'Template1' not associated with Site 'aws_ansible_test'."
+
+ - name: Check Non-existing schema
+ mso_schema_template_deploy_status:
+ <<: *mso_info
+ schema: non-existing-schema
+ state: query
+ ignore_errors: true
+ register: non_schema
+
+ - name: Verify non_existing_schema
+ assert:
+ that:
+ - non_schema.msg == "Schema 'non-existing-schema' not found."
+
+ - name: Check deployment status of non-existing-template
+ mso_schema_template_deploy_status:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: non-existing-template
+ state: query
+ ignore_errors: true
+ register: non_temp
+
+ - name: Verify non_existing_template
+ assert:
+ that:
+ - non_temp.msg == "Template 'non-existing-template' not found." \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg/tasks/main.yml
new file mode 100644
index 000000000..e76869456
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg/tasks/main.yml
@@ -0,0 +1,1158 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com>
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case)
+#
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+# - name: Ensure site exist
+# mso_site: &site_present
+# host: '{{ mso_hostname }}'
+# username: '{{ mso_username }}'
+# password: '{{ mso_password }}'
+# validate_certs: '{{ mso_validate_certs | default(false) }}'
+# use_ssl: '{{ mso_use_ssl | default(true) }}'
+# use_proxy: '{{ mso_use_proxy | default(true) }}'
+# output_level: '{{ mso_output_level | default("info") }}'
+# site: '{{ mso_site | default("ansible_test") }}'
+# apic_username: '{{ apic_username }}'
+# apic_password: '{{ apic_password }}'
+# apic_site_id: '{{ apic_site_id | default(101) }}'
+# urls:
+# - https://{{ apic_hostname }}
+# state: present
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Undeploy templates if deployed from previous test case
+ mso_schema_template_deploy:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: "{{ item }}"
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: undeploy
+ ignore_errors: true
+ loop:
+ - Template 1
+ - Template 2
+
+- name: Remove schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Ensure tenant ansible_test exist
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ # sites:
+ # - '{{ mso_site | default("ansible_test") }}'
+ state: present
+
+- name: Ensure schema 1 with Template 1 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: Template 1
+ state: present
+
+- name: Ensure schema 1 with Template 2 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: Template 2
+ state: present
+
+- name: Ensure schema 2 with Template 3 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template 3
+ state: present
+
+- name: Ensure Filter 1 exist
+ mso_schema_template_filter_entry:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ filter: Filter1
+ entry: Filter1-Entry
+ state: present
+
+- name: Ensure Contract1 exist
+ mso_schema_template_contract_filter: &contract_present
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract1
+ filter: Filter1
+ filter_schema: '{{ mso_schema | default("ansible_test") }}'
+ filter_template: Template 1
+ state: present
+
+- name: Ensure Filter 2 exist
+ mso_schema_template_filter_entry:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ filter: Filter2
+ entry: Filter2-Entry
+ state: present
+
+- name: Ensure Contract2 exist
+ mso_schema_template_contract_filter: &contract2_present
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ contract: Contract2
+ filter: Filter2
+ filter_schema: '{{ mso_schema | default("ansible_test") }}'
+ filter_template: Template 2
+ state: present
+
+- name: Ensure VRF exist
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF
+ #layer3_multicast: true
+ state: present
+
+- name: Ensure VRF2 exist
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF2
+ state: present
+
+- name: Ensure VRF3 exist
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ vrf: VRF3
+ state: present
+
+- name: Ensure VRF4 exist
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ vrf: VRF4
+ state: present
+
+- name: Ensure L3out exist
+ mso_schema_template_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ l3out: L3out
+ vrf:
+ name: VRF
+ state: present
+
+- name: Ensure L3out2 exist
+ mso_schema_template_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ l3out: L3out2
+ vrf:
+ name: VRF2
+ state: present
+
+- name: Ensure L3out3 exist
+ mso_schema_template_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ l3out: L3out3
+ vrf:
+ name: VRF3
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ state: present
+
+- name: Ensure L3out4 exist
+ mso_schema_template_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ l3out: L3out4
+ vrf:
+ name: VRF4
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ state: present
+
+- name: Ensure ANP exists
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP1
+ state: present
+
+- name: Ensure ANP2 exist
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP2
+ state: present
+
+- name: Ensure ANP3 exist
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP3
+ state: present
+
+- name: Ensure ANP4 exist
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ anp: ANP4
+ state: present
+
+- name: Ensure ansible_test_1 external EPG does not exist
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_1
+ state: absent
+
+- name: Ensure ansible_test_2 external EPG does not exist
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ external_epg: ansible_test_2
+ state: absent
+
+- name: Ensure ansible_test_3 external EPG does not exist
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ external_epg: ansible_test_3
+ state: absent
+
+- name: Ensure ansible_test_4 external EPG does not exist
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_4
+ state: absent
+
+- name: Ensure ansible_test_6 external EPG does not exist
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ external_epg: ansible_test_6
+ state: absent
+
+- name: Ensure ansible_test_7 external EPG does not exist
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ external_epg: ansible_test_7
+ state: absent
+
+# ADD external EPG
+- name: Add external EPG (check_mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_1
+ vrf:
+ name: VRF
+ state: present
+ check_mode: true
+ register: cm_add_epg
+
+- name: Verify cm_add_epg
+ assert:
+ that:
+ - cm_add_epg is changed
+ - cm_add_epg.previous == {}
+ - cm_add_epg.current.name == "ansible_test_1"
+ - cm_add_epg.current.vrfRef.templateName == "Template1"
+ - cm_add_epg.current.vrfRef.vrfName == "VRF"
+
+- name: Add external EPG (normal mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_1
+ vrf:
+ name: VRF
+ state: present
+ register: nm_add_epg
+
+- name: Verify nm_add_epg
+ assert:
+ that:
+ - nm_add_epg is changed
+ - nm_add_epg.previous == {}
+ - nm_add_epg.current.name == "ansible_test_1"
+ - nm_add_epg.current.vrfRef.templateName == "Template1"
+ - nm_add_epg.current.vrfRef.vrfName == "VRF"
+ - cm_add_epg.current.vrfRef.schemaId == nm_add_epg.current.vrfRef.schemaId
+
+- name: Add external EPG again (check_mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_1
+ vrf:
+ name: VRF
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ state: present
+ check_mode: true
+ register: cm_add_epg_again
+
+- name: Verify cm_add_epg_again
+ assert:
+ that:
+ - cm_add_epg_again is not changed
+ - cm_add_epg_again.previous.name == "ansible_test_1"
+ - cm_add_epg_again.current.name == "ansible_test_1"
+ - cm_add_epg_again.previous.vrfRef.templateName == "Template1"
+ - cm_add_epg_again.current.vrfRef.templateName == "Template1"
+ - cm_add_epg_again.previous.vrfRef.vrfName == "VRF"
+ - cm_add_epg_again.current.vrfRef.vrfName == "VRF"
+ - cm_add_epg_again.previous.vrfRef.schemaId == cm_add_epg_again.current.vrfRef.schemaId
+
+
+- name: Add epg again (normal mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_1
+ vrf:
+ name: VRF
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ state: present
+ register: nm_add_epg_again
+
+- name: Verify nm_add_epg_again
+ assert:
+ that:
+ - nm_add_epg_again is not changed
+ - nm_add_epg_again.previous.name == "ansible_test_1"
+ - nm_add_epg_again.current.name == "ansible_test_1"
+ - nm_add_epg_again.previous.vrfRef.templateName == "Template1"
+ - nm_add_epg_again.current.vrfRef.templateName == "Template1"
+ - nm_add_epg_again.previous.vrfRef.vrfName == "VRF"
+ - nm_add_epg_again.current.vrfRef.vrfName == "VRF"
+ - nm_add_epg_again.previous.vrfRef.schemaId == nm_add_epg_again.current.vrfRef.schemaId
+
+- name: Add external EPG 2(normal mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ external_epg: ansible_test_2
+ vrf:
+ name: VRF3
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ l3out:
+ name: L3out3
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ state: present
+ register: nm_add_epg_2
+
+- name: Add external EPG 3 (normal mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ external_epg: ansible_test_3
+ vrf:
+ name: VRF4
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ l3out:
+ name: L3out4
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ state: present
+ register: nm_add_epg_3
+
+- name: Add external EPG 4 (normal mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_4
+ vrf:
+ name: VRF
+ state: present
+ register: nm_add_epg_4
+
+- name: Verify nm_add_epg_2 and nm_add_epg_3
+ assert:
+ that:
+ - nm_add_epg_2 is changed
+ - nm_add_epg_3 is changed
+ - nm_add_epg_2.current.name == "ansible_test_2"
+ - nm_add_epg_3.current.name == "ansible_test_3"
+ - nm_add_epg_2.current.vrfRef.templateName == "Template2"
+ - nm_add_epg_3.current.vrfRef.templateName == "Template3"
+ - nm_add_epg_2.current.vrfRef.vrfName == "VRF3"
+ - nm_add_epg_3.current.vrfRef.vrfName == "VRF4"
+ - nm_add_epg_2.current.vrfRef.schemaId == nm_add_epg.current.vrfRef.schemaId
+ - nm_add_epg_3.current.vrfRef.schemaId != nm_add_epg.current.vrfRef.schemaId
+
+- name: Add external EPG 5 (normal mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ external_epg: ansible_test_5
+ vrf:
+ name: VRF
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp:
+ name: ANP1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ state: present
+ register: nm_add_epg_5
+
+- name: Verify nm_add_epg_5
+ assert:
+ that:
+ - nm_add_epg_5 is changed
+ - nm_add_epg_5.current.name == "ansible_test_5"
+
+- name: Add external EPG 5 again with L3Out (normal mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ external_epg: ansible_test_5
+ vrf:
+ name: VRF
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ l3out:
+ name: L3out
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp:
+ name: ANP1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ state: present
+ register: nm_add_epg_5_again
+
+- name: Verify nm_add_epg_5_again
+ assert:
+ that:
+ - nm_add_epg_5_again is changed
+ - nm_add_epg_5_again.current.name == "ansible_test_5"
+
+- name: Add external EPG 6 with external epg type cloud (normal mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ external_epg: ansible_test_6
+ type: cloud
+ vrf:
+ name: VRF
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp:
+ name: ANP1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ state: present
+ register: nm_add_epg_6
+
+- name: Verify nm_add_epg_6
+ assert:
+ that:
+ - nm_add_epg_6 is changed
+ - nm_add_epg_6.current.name == "ansible_test_6"
+ - nm_add_epg_6.current.vrfRef.templateName == "Template1"
+ - nm_add_epg_6.current.vrfRef.vrfName == "VRF"
+ - nm_add_epg_6.current.anpRef.anpName == "ANP1"
+
+- name: Add external EPG 6 with external epg type cloud again(normal mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ external_epg: ansible_test_6
+ type: cloud
+ vrf:
+ name: VRF
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp:
+ name: ANP1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ state: present
+ register: nm_add_epg_6_again
+
+- name: Verify nm_add_epg_6_again
+ assert:
+ that:
+ - nm_add_epg_6_again is not changed
+ - nm_add_epg_6_again.current.name == "ansible_test_6"
+
+- name: Add external EPG 6 with external epg type cloud with modification(normal mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ external_epg: ansible_test_6
+ type: cloud
+ vrf:
+ name: VRF2
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp:
+ name: ANP1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ state: present
+ register: nm_add_epg_6_again_2
+
+- name: Verify nm_add_epg_6_again
+ assert:
+ that:
+ - nm_add_epg_6_again_2 is changed
+ - nm_add_epg_6_again_2.current.name == "ansible_test_6"
+ - nm_add_epg_6_again_2.current.vrfRef.vrfName == "VRF2"
+ - nm_add_epg_6_again_2.current.anpRef.anpName == "ANP1"
+
+- name: Add external EPG 7 with external epg type on-premise explicitly mentioned again(normal mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ external_epg: ansible_test_7
+ type: on-premise
+ vrf:
+ name: VRF
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ l3out:
+ name: L3out
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ state: present
+ register: nm_add_epg_7
+
+- name: Verify nm_add_epg_7
+ assert:
+ that:
+ - nm_add_epg_7 is changed
+ - nm_add_epg_7.current.name == "ansible_test_7"
+ - nm_add_epg_7.current.vrfRef.templateName == "Template1"
+ - nm_add_epg_7.current.vrfRef.vrfName == "VRF"
+
+- name: Add external EPG 7 with external epg type not mentioned again(normal mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ external_epg: ansible_test_7
+ vrf:
+ name: VRF
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ l3out:
+ name: L3out
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ state: present
+ register: nm_add_epg_7_again
+
+- name: Verify nm_add_epg_7_again
+ assert:
+ that:
+ - nm_add_epg_7_again is not changed
+ - nm_add_epg_7_again.current.name == "ansible_test_7"
+
+# CHANGE external EPG
+- name: Change epg from different template (check_mode)
+ mso_schema_template_external_epg: &change_epg_template
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ external_epg: ansible_test_2
+ vrf:
+ name: VRF
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ state: present
+ check_mode: true
+ register: cm_change_epg
+
+- name: Verify cm_change_epg from different template to own template
+ assert:
+ that:
+ - cm_change_epg is changed
+ - cm_change_epg.current.name == 'ansible_test_2'
+ - cm_change_epg.current.vrfRef.vrfName == 'VRF'
+ - cm_change_epg.current.vrfRef.templateName == "Template1"
+ - cm_change_epg.current.vrfRef.schemaId == cm_change_epg.previous.vrfRef.schemaId
+
+- name: Change epg from different template to own template (normal_mode)
+ mso_schema_template_external_epg:
+ <<: *change_epg_template
+ state: present
+ register: nm_change_epg
+
+- name: Verify nm_change_epg from different template to own template
+ assert:
+ that:
+ - nm_change_epg is changed
+ - nm_change_epg.current.name == 'ansible_test_2'
+ - nm_change_epg.current.vrfRef.vrfName == 'VRF'
+ - nm_change_epg.current.vrfRef.templateName == "Template1"
+ - nm_change_epg.current.vrfRef.schemaId == nm_change_epg.previous.vrfRef.schemaId
+
+- name: Change epg again from different template (check_mode)
+ mso_schema_template_external_epg:
+ <<: *change_epg_template
+ check_mode: true
+ register: cm_change_epg_again
+
+- name: Verify cm_change_epg_again
+ assert:
+ that:
+ - cm_change_epg_again is not changed
+ - cm_change_epg_again.current.name == 'ansible_test_2'
+ - cm_change_epg_again.current.vrfRef.vrfName == 'VRF'
+ - cm_change_epg_again.current.vrfRef.templateName == "Template1"
+ - cm_change_epg_again.current.vrfRef.schemaId == cm_change_epg_again.previous.vrfRef.schemaId
+
+- name: Change epg again from different template (normal_mode)
+ mso_schema_template_external_epg:
+ <<: *change_epg_template
+ state: present
+ register: nm_change_epg_again
+
+- name: Verify nm_change_epg_again from different template to own template
+ assert:
+ that:
+ - nm_change_epg_again is not changed
+ - nm_change_epg_again.current.name == 'ansible_test_2'
+ - nm_change_epg_again.current.vrfRef.vrfName == 'VRF'
+ - nm_change_epg_again.current.vrfRef.templateName == "Template1"
+ - nm_change_epg_again.current.vrfRef.schemaId == nm_change_epg_again.previous.vrfRef.schemaId
+
+- name: Change VRF from different schema (normal mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ external_epg: ansible_test_3
+ vrf:
+ name: VRF
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ state: present
+ register: nm_change_epg_vrf4
+
+- name: Verify nm_change_epg_vrf4 and nm_change_epg_vrf2
+ assert:
+ that:
+ - nm_change_epg_vrf4 is changed
+ - nm_change_epg_vrf4.current.name == 'ansible_test_3'
+ - nm_change_epg_vrf4.current.vrfRef.vrfName == 'VRF'
+ - nm_change_epg_vrf4.current.vrfRef.templateName == "Template1"
+ - nm_change_epg_vrf4.current.vrfRef.schemaId != nm_change_epg_vrf4.previous.vrfRef.schemaId
+
+- name: Change epg 1 l3out(normal mode)
+ mso_schema_template_external_epg: &change_l3out
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_1
+ vrf:
+ name: VRF2
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ l3out:
+ name: L3out2
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ state: present
+ register: nm_change_epg_1_l3out
+
+- name: Change epg 1 settings(normal mode)
+ mso_schema_template_external_epg:
+ <<: *change_l3out
+ vrf:
+ name: VRF
+ l3out:
+ name: L3out
+ state: present
+ register: nm_change_epg_1_settings
+
+- name: Verify nm_change_epg_1_settings and nm_change_epg_1_l3out
+ assert:
+ that:
+ - nm_change_epg_1_settings is changed
+ - nm_change_epg_1_settings.previous.vrfRef.vrfName == 'VRF2'
+ - nm_change_epg_1_settings.previous.vrfRef.templateName == 'Template1'
+ - nm_change_epg_1_settings.current.vrfRef.vrfName == 'VRF'
+ - nm_change_epg_1_settings.current.vrfRef.templateName == 'Template1'
+ - nm_change_epg_1_settings.current.l3outRef.l3outName == 'L3out'
+ - nm_change_epg_1_settings.current.l3outRef.templateName == 'Template1'
+ - nm_change_epg_1_settings.previous.l3outRef.schemaId == nm_change_epg_1_settings.current.l3outRef.schemaId
+ - nm_change_epg_1_l3out is changed
+ - nm_change_epg_1_l3out.previous.vrfRef.vrfName == 'VRF'
+ - nm_change_epg_1_l3out.previous.vrfRef.templateName == 'Template1'
+ - nm_change_epg_1_l3out.current.vrfRef.vrfName == 'VRF2'
+ - nm_change_epg_1_l3out.current.vrfRef.templateName == 'Template1'
+ - nm_change_epg_1_l3out.current.l3outRef.l3outName == 'L3out2'
+ - nm_change_epg_1_l3out.current.l3outRef.templateName == 'Template1'
+
+- name: Change epg 4 preferredGroup(normal mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_4
+ vrf:
+ name: VRF
+ preferred_group: true
+ state: present
+ register: nm_change_epg_4_preferred_group
+
+- name: Change epg 4 preferredGroup again(normal mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_4
+ vrf:
+ name: VRF
+ preferred_group: false
+ state: present
+ register: nm_change_epg_4_preferred_group_again
+
+- name: Verify nm_change_epg_4_preferred_group and nm_change_epg_4_preferred_group_again
+ assert:
+ that:
+ - nm_change_epg_4_preferred_group is changed
+ - nm_change_epg_4_preferred_group_again is changed
+ - nm_change_epg_4_preferred_group.current.preferredGroup == true
+ - nm_change_epg_4_preferred_group_again.current.preferredGroup == false
+
+# QUERY ALL EPG
+- name: Query all EPG (check_mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ state: query
+ check_mode: true
+ register: cm_query_all_epgs
+
+- name: Query all EPG (normal mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ state: query
+ register: nm_query_all_epgs
+
+- name: Verify query_all_epgs
+ assert:
+ that:
+ - cm_query_all_epgs is not changed
+ - nm_query_all_epgs is not changed
+ - cm_query_all_epgs.current | length == nm_query_all_epgs.current | length == 2
+
+# QUERY AN EPG
+- name: Query epg 1(check_mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_1
+ state: query
+ check_mode: true
+ register: cm_query_epg_1
+
+- name: Query epg 1(normal_mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_1
+ state: query
+ register: nm_query_epg_1
+
+- name: Verify cm_query_epg_1 and nm_query_epg_1
+ assert:
+ that:
+ - cm_query_epg_1 is not changed
+ - nm_query_epg_1 is not changed
+ - cm_query_epg_1.current.l3outRef.l3outName == 'L3out' == nm_query_epg_1.current.l3outRef.l3outName
+ - cm_query_epg_1.current.l3outRef.templateName == nm_query_epg_1.current.l3outRef.templateName == 'Template1'
+ - cm_query_epg_1.current.l3outRef.schemaId == nm_query_epg_1.current.l3outRef.schemaId
+ - cm_query_epg_1.current.vrfRef.vrfName == nm_query_epg_1.current.vrfRef.vrfName == 'VRF'
+ - cm_query_epg_1.current.vrfRef.templateName == nm_query_epg_1.current.vrfRef.templateName == 'Template1'
+ - cm_query_epg_1.current.vrfRef.schemaId == nm_query_epg_1.current.vrfRef.schemaId
+ - nm_query_epg_1.current.l3outRef.schemaId == nm_query_epg_1.current.vrfRef.schemaId
+
+- name: Query epg 5(normal_mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ external_epg: ansible_test_5
+ state: query
+ register: nm_query_epg_5
+
+- name: Verify nm_query_epg_5
+ assert:
+ that:
+ - nm_query_epg_5.current.l3outRef.l3outName == 'L3out'
+
+- name: Query epg 6(normal_mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ external_epg: ansible_test_6
+ state: query
+ register: nm_query_epg_6
+
+- name: Verify nm_query_epg_5
+ assert:
+ that:
+ - nm_add_epg_6.current.anpRef.anpName == "ANP1"
+
+# REMOVE EPG
+- name: Remove EPG 4 (check_mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_4
+ state: absent
+ check_mode: true
+ register: cm_remove_epg_4
+
+- name: Verify cm_remove_epg_4
+ assert:
+ that:
+ - cm_remove_epg_4 is changed
+ - cm_remove_epg_4.current == {}
+
+- name: Remove EPG 4 (normal_mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_4
+ state: absent
+ register: nm_remove_epg_4
+
+- name: Verify nm_remove_epg_4
+ assert:
+ that:
+ - nm_remove_epg_4 is changed
+ - nm_remove_epg_4.current == {}
+
+- name: Remove EPG 4 again (normal mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_4
+ state: absent
+ register: nm_remove_epg_4_again
+
+- name: Verify nm_remove_epg_4_again
+ assert:
+ that:
+ - nm_remove_epg_4_again is not changed
+ - nm_remove_epg_4_again.previous == nm_remove_epg_4_again.current == {}
+
+- name: Add external EPG 4 description for version greater than 3.3
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_4
+ description: "Description of an external EPG 4"
+ vrf:
+ name: VRF
+ state: present
+ register: add_epg_4
+ when: version.current.version is version('3.3', '>=')
+
+- name: Verify add_epg_4 for version greater than 3.3
+ assert:
+ that:
+ - add_epg_4 is changed
+ - add_epg_4.current.name == "ansible_test_4"
+ - add_epg_4.current.description == "Description of an external EPG 4"
+ - add_epg_4.current.vrfRef.templateName == "Template1"
+ - add_epg_4.current.vrfRef.vrfName == "VRF"
+ - add_epg_4.current.vrfRef.schemaId == add_epg_4.current.vrfRef.schemaId
+ when: version.current.version is version('3.3', '>=')
+
+
+# QUERY NON-EXISTING EPG
+- name: Query non-existing EPG (check_mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: non-existing-epg
+ state: query
+ ignore_errors: true
+ check_mode: true
+ register: cm_query_non_existing_epg
+
+- name: Query non-existing EPG (normal_mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: non-existing-epg
+ state: query
+ ignore_errors: true
+ register: nm_query_non_existing_epg
+
+- name: Verify cm_query_non_existing_epg and nm_query_non_existing_epg
+ assert:
+ that:
+ - cm_query_non_existing_epg is not changed
+ - nm_query_non_existing_epg is not changed
+ - cm_query_non_existing_epg == nm_query_non_existing_epg
+ - cm_query_non_existing_epg.msg == nm_query_non_existing_epg.msg == "External EPG 'non-existing-epg' not found"
+
+# USE A NON-EXISTING SCHEMA
+- name: Non-existing schema for epg (check_mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: non-existing-schema
+ template: Template 1
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_schema
+
+- name: Non-existing schema for epg (normal_mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: non-existing-schema
+ template: Template 1
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_schema
+
+- name: Verify non_existing_schema
+ assert:
+ that:
+ - cm_non_existing_schema is not changed
+ - nm_non_existing_schema is not changed
+ - cm_non_existing_schema == nm_non_existing_schema
+ - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non-existing-schema' does not exist."
+
+# USE A NON-EXISTING TEMPLATE
+- name: Non-existing template for epg (check_mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: non-existing-template
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_template
+
+- name: Non-existing template for epg (normal_mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: non-existing-template
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_template
+
+- name: Verify non_existing_template
+ assert:
+ that:
+ - cm_non_existing_template is not changed
+ - nm_non_existing_template is not changed
+ - cm_non_existing_template == nm_non_existing_template
+ - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non-existing-template' does not exist. Existing templates{{':'}} Template1, Template2"
+
+# Checking if contract are removed after re-applying an EPG. (#13 | #62137)
+- name: Remove EPG 2/5/6/7 to avoid circle errors (normal mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ external_epg: '{{ item }}'
+ state: absent
+ loop:
+ - ansible_test_2
+ - ansible_test_5
+ - ansible_test_6
+ - ansible_test_7
+
+- name: Add Contracts to EPG 1
+ mso_schema_template_external_epg_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_1
+ contract:
+ name: '{{ item.name }}'
+ template: '{{ item.template }}'
+ type: '{{ item.type }}'
+ state: present
+ loop:
+ - { name: Contract1, template: Template 1, type: consumer }
+ - { name: Contract1, template: Template 1, type: provider }
+ - { name: Contract2, template: Template 2, type: consumer }
+ - { name: Contract2, template: Template 2, type: provider }
+
+- name: Query contract EPG 1(normal mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_1
+ state: query
+ register: nm_query_epg1_contract
+
+- name: Verify nm_query_epg1_contract
+ assert:
+ that:
+ - nm_query_epg1_contract.current.contractRelationships | length == 4
+
+- name: Add EPG 1 again (normal_mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_1
+ vrf:
+ name: VRF
+ l3out:
+ name: L3out
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ state: present
+ register: nm_add_epg_1_again
+
+- name: Verify that EPG 1 didn't change
+ assert:
+ that:
+ - nm_add_epg_1_again is not changed
+
+- name: Query contract EPG 1 again
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_1
+ state: query
+ register: nm_query_epg1_contract_again
+
+- name: Verify that 4 contracts are in EPG 1 using nm_query_epg1_contract_again
+ assert:
+ that:
+ - nm_query_epg1_contract_again.current.contractRelationships | length == 4
+
+# Checking if modifying an external EPG with existing contracts throw an MSO error. (#82)
+- name: Change external EPG 1 VRF (normal_mode)
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_1
+ vrf:
+ name: VRF2
+ l3out:
+ name: L3out2
+ state: present
+ register: nm_change_ext_epg_1_vrf
+
+- name: Verify that external EPG 1 did change
+ assert:
+ that:
+ - nm_change_ext_epg_1_vrf is changed
+ - nm_change_ext_epg_1_vrf.current.vrfRef.templateName == "Template1"
+ - nm_change_ext_epg_1_vrf.current.vrfRef.vrfName == "VRF2"
+ - nm_change_ext_epg_1_vrf.current.l3outRef.l3outName == "L3out2"
+
+- name: Query EPG 1
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_1
+ state: query
+ register: nm_query_contract_ext_epg_1
+
+- name: Verify that 4 contracts are in external EPG 1 using nm_query_contract_ext_epg_1
+ assert:
+ that:
+ - nm_query_contract_ext_epg_1.current.contractRelationships | length == 4 \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_contract/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_contract/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_contract/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_contract/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_contract/tasks/main.yml
new file mode 100644
index 000000000..44eb30623
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_contract/tasks/main.yml
@@ -0,0 +1,645 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case)
+#
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+
+# CLEAN ENVIRONMENT
+# - name: Ensure site exist
+# mso_site: &site_present
+# host: '{{ mso_hostname }}'
+# username: '{{ mso_username }}'
+# password: '{{ mso_password }}'
+# validate_certs: '{{ mso_validate_certs | default(false) }}'
+# use_ssl: '{{ mso_use_ssl | default(true) }}'
+# use_proxy: '{{ mso_use_proxy | default(true) }}'
+# output_level: '{{ mso_output_level | default("info") }}'
+# site: '{{ mso_site | default("ansible_test") }}'
+# apic_username: '{{ apic_username }}'
+# apic_password: '{{ apic_password }}'
+# apic_site_id: '{{ apic_site_id | default(101) }}'
+# urls:
+# - https://{{ apic_hostname }}
+# state: present
+
+- name: Remove schemas
+ mso_schema:
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Ensure tenant ansible_test exist
+ mso_tenant: &tenant_present
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ # sites:
+ # - '{{ mso_site | default("ansible_test") }}'
+ state: present
+
+- name: Ensure schema 1 with Template 1 exist
+ mso_schema_template: &schema_present
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: Template 1
+ state: present
+
+- name: Ensure schema 1 with Template 2 exist
+ mso_schema_template:
+ <<: *schema_present
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: Template 2
+ state: present
+
+- name: Ensure schema 2 with Template 3 exist
+ mso_schema_template:
+ <<: *schema_present
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template 3
+ state: present
+
+- name: Ensure VRF exist
+ mso_schema_template_vrf:
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF
+ state: present
+
+- name: Ensure Filter 1 exist
+ mso_schema_template_filter_entry:
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ filter: Filter1
+ entry: Filter1-Entry
+ state: present
+
+- name: Ensure Filter 2 exist
+ mso_schema_template_filter_entry:
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ filter: Filter2
+ entry: Filter2-Entry
+ state: present
+
+- name: Ensure Contract1 exist
+ mso_schema_template_contract_filter: &contract_present
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract1
+ filter: Filter1
+ filter_schema: '{{ mso_schema | default("ansible_test") }}'
+ filter_template: Template 1
+ state: present
+
+- name: Ensure Contract2 exist
+ mso_schema_template_contract_filter:
+ <<: *contract_present
+ template: Template 2
+ contract: Contract2
+ filter: Filter2
+ filter_schema: '{{ mso_schema | default("ansible_test") }}'
+ filter_template: Template 2
+ state: present
+
+- name: Ensure external EPGs exist
+ mso_schema_template_externalepg:
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+ state: present
+ schema: '{{ item.schema }}'
+ template: '{{ item.template }}'
+ externalepg: '{{ item.externalepg }}'
+ vrf:
+ name: VRF
+ template: Template 1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ loop:
+ - { schema: '{{ mso_schema | default("ansible_test") }}', template: 'Template 1', externalepg: 'ansible_test_1' }
+ - { schema: '{{ mso_schema | default("ansible_test") }}_2', template: 'Template 3', externalepg: 'ansible_test_3' }
+
+# ADD Contract to External EPG
+- name: Add Contract1 to External EPG (check_mode)
+ mso_schema_template_external_epg_contract: &contract_ext_epg_present
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_1
+ contract:
+ name: Contract1
+ type: consumer
+ state: present
+ check_mode: true
+ register: cm_add_contract_rel
+
+- name: Verify cm_add_contract_rel
+ assert:
+ that:
+ - cm_add_contract_rel is changed
+ - cm_add_contract_rel.previous == {}
+ - cm_add_contract_rel.current.contractRef.templateName == "Template1"
+ - cm_add_contract_rel.current.contractRef.contractName == "Contract1"
+ - cm_add_contract_rel.current.relationshipType == "consumer"
+
+- name: Add Contract to External EPG (normal mode)
+ mso_schema_template_external_epg_contract:
+ <<: *contract_ext_epg_present
+ register: nm_add_contract_rel
+
+- name: Verify nm_add_contract_rel
+ assert:
+ that:
+ - nm_add_contract_rel is changed
+ - nm_add_contract_rel.previous == {}
+ - nm_add_contract_rel.current.contractRef.templateName == "Template1"
+ - nm_add_contract_rel.current.contractRef.contractName == "Contract1"
+ - nm_add_contract_rel.current.relationshipType == "consumer"
+ - cm_add_contract_rel.current.contractRef.schemaId == nm_add_contract_rel.current.contractRef.schemaId
+
+- name: Add Contract to External EPG again (check_mode)
+ mso_schema_template_external_epg_contract:
+ <<: *contract_ext_epg_present
+ check_mode: true
+ register: cm_add_contract_rel_again
+
+- name: Verify cm_add_contract_rel_again
+ assert:
+ that:
+ - cm_add_contract_rel_again is not changed
+ - cm_add_contract_rel_again.previous.contractRef.templateName == "Template1"
+ - cm_add_contract_rel_again.current.contractRef.templateName == "Template1"
+ - cm_add_contract_rel_again.previous.contractRef.contractName == "Contract1"
+ - cm_add_contract_rel_again.current.contractRef.contractName == "Contract1"
+ - cm_add_contract_rel_again.previous.relationshipType == "consumer"
+ - cm_add_contract_rel_again.current.relationshipType == "consumer"
+ - cm_add_contract_rel_again.previous.contractRef.schemaId == cm_add_contract_rel_again.current.contractRef.schemaId
+
+
+- name: Add Contract to External EPG again (normal mode)
+ mso_schema_template_external_epg_contract:
+ <<: *contract_ext_epg_present
+ register: nm_add_contract_rel_again
+
+- name: Verify nm_add_contract_rel_again
+ assert:
+ that:
+ - nm_add_contract_rel_again is not changed
+ - nm_add_contract_rel_again.previous.contractRef.templateName == "Template1"
+ - nm_add_contract_rel_again.current.contractRef.templateName == "Template1"
+ - nm_add_contract_rel_again.previous.contractRef.contractName == "Contract1"
+ - nm_add_contract_rel_again.current.contractRef.contractName == "Contract1"
+ - nm_add_contract_rel_again.previous.relationshipType == "consumer"
+ - nm_add_contract_rel_again.current.relationshipType == "consumer"
+ - nm_add_contract_rel_again.previous.contractRef.schemaId == nm_add_contract_rel_again.current.contractRef.schemaId
+
+- name: Add Contract1 to External EPG - provider (normal mode)
+ mso_schema_template_external_epg_contract:
+ <<: *contract_ext_epg_present
+ contract:
+ name: Contract1
+ type: provider
+ register: nm_add_contract1_rel_provider
+
+- name: Add Contract2 to External EPG - consumer (normal mode)
+ mso_schema_template_external_epg_contract:
+ <<: *contract_ext_epg_present
+ contract:
+ name: Contract2
+ template: Template 2
+ type: consumer
+ register: nm_add_contract2_rel_consumer
+
+- name: Add Contract1 to External EPG 3 - provider (normal mode)
+ mso_schema_template_external_epg_contract:
+ <<: *contract_ext_epg_present
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: 'Template 3'
+ external_epg: ansible_test_3
+ contract:
+ name: Contract1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ type: provider
+ register: nm_add_contract3_rel_provider
+
+- name: Verify nm_add_contract1_rel_provider, nm_add_contract2_rel_consumer and nm_add_contract3_rel_provider
+ assert:
+ that:
+ - nm_add_contract1_rel_provider is changed
+ - nm_add_contract2_rel_consumer is changed
+ - nm_add_contract3_rel_provider is changed
+ - nm_add_contract1_rel_provider.current.contractRef.contractName == "Contract1"
+ - nm_add_contract2_rel_consumer.current.contractRef.contractName == "Contract2"
+ - nm_add_contract3_rel_provider.current.contractRef.contractName == "Contract1"
+ - nm_add_contract1_rel_provider.current.contractRef.templateName == "Template1"
+ - nm_add_contract2_rel_consumer.current.contractRef.templateName == "Template2"
+ - nm_add_contract3_rel_provider.current.contractRef.templateName == "Template1"
+ - nm_add_contract1_rel_provider.current.contractRef.schemaId == nm_add_contract2_rel_consumer.current.contractRef.schemaId == nm_add_contract3_rel_provider.current.contractRef.schemaId
+ - nm_add_contract2_rel_consumer.current.relationshipType == "consumer"
+ - nm_add_contract1_rel_provider.current.relationshipType == nm_add_contract3_rel_provider.current.relationshipType == "provider"
+
+# # QUERY ALL Contract to External EPG
+- name: Query all contract relationship for External EPG (check_mode)
+ mso_schema_template_external_epg_contract: &contract_ext_epg_query
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_1
+ state: query
+ check_mode: true
+ register: cm_query_all_contract_rels
+
+- name: Query all contract relationship for External EPG (normal mode)
+ mso_schema_template_external_epg_contract:
+ <<: *contract_ext_epg_query
+ register: nm_query_all_contract_rels
+
+- name: Verify query_all_contract_rels
+ assert:
+ that:
+ - cm_query_all_contract_rels is not changed
+ - nm_query_all_contract_rels is not changed
+ - cm_query_all_contract_rels.current | length == nm_query_all_contract_rels.current | length == 3
+
+
+# QUERY A Contract to External EPG
+- name: Query Contract1 relationship for External EPG - consumer (check_mode)
+ mso_schema_template_external_epg_contract:
+ <<: *contract_ext_epg_query
+ contract:
+ name: Contract1
+ type: consumer
+ check_mode: true
+ register: cm_query_contract1_consumer_rel
+
+- name: Query Contract1 relationship for External EPG - consumer (normal mode)
+ mso_schema_template_external_epg_contract:
+ <<: *contract_ext_epg_query
+ contract:
+ name: Contract1
+ type: consumer
+ register: nm_query_contract1_consumer_rel
+
+- name: Query Contract1 relationship for External EPG - provider (normal mode)
+ mso_schema_template_external_epg_contract:
+ <<: *contract_ext_epg_query
+ contract:
+ name: Contract1
+ type: provider
+ register: nm_query_contract1_provider_rel
+
+- name: Query Contract1 relationship for External EPG - consumer (normal mode)
+ mso_schema_template_external_epg_contract:
+ <<: *contract_ext_epg_query
+ contract:
+ name: Contract2
+ template: Template 2
+ type: consumer
+ register: nm_query_contract2_consumer_rel
+
+- name: Query Contract1 relationship for External EPG - provider (normal mode)
+ mso_schema_template_external_epg_contract:
+ <<: *contract_ext_epg_query
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ external_epg: ansible_test_3
+ contract:
+ name: Contract1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ type: provider
+ register: nm_query_contract3_provider_rel
+
+- name: Verify query_contractX_YYYYY_rel
+ assert:
+ that:
+ - cm_query_contract1_consumer_rel is not changed
+ - nm_query_contract1_consumer_rel is not changed
+ - nm_query_contract1_provider_rel is not changed
+ - nm_query_contract2_consumer_rel is not changed
+ - nm_query_contract3_provider_rel is not changed
+ - cm_query_contract1_consumer_rel == nm_query_contract1_consumer_rel
+ - cm_query_contract1_consumer_rel.current.contractRef.contractName == nm_query_contract1_consumer_rel.current.contractRef.contractName == nm_query_contract1_provider_rel.current.contractRef.contractName == "Contract1"
+ - nm_query_contract2_consumer_rel.current.contractRef.contractName == "Contract2"
+ - nm_query_contract3_provider_rel.current.contractRef.contractName == "Contract1"
+ - cm_query_contract1_consumer_rel.current.contractRef.templateName == nm_query_contract1_consumer_rel.current.contractRef.templateName == nm_query_contract1_provider_rel.current.contractRef.templateName == "Template1"
+ - nm_query_contract2_consumer_rel.current.contractRef.templateName == "Template2"
+ - nm_query_contract3_provider_rel.current.contractRef.templateName == "Template1"
+ - cm_query_contract1_consumer_rel.current.contractRef.schemaId == nm_query_contract1_consumer_rel.current.contractRef.schemaId == nm_query_contract1_provider_rel.current.contractRef.schemaId == nm_query_contract2_consumer_rel.current.contractRef.schemaId == nm_query_contract3_provider_rel.current.contractRef.schemaId
+ - cm_query_contract1_consumer_rel.current.relationshipType == nm_query_contract1_consumer_rel.current.relationshipType == nm_query_contract2_consumer_rel.current.relationshipType == "consumer"
+ - nm_query_contract1_provider_rel.current.relationshipType == nm_query_contract3_provider_rel.current.relationshipType == "provider"
+
+
+# REMOVE Contract to External EPG
+- name: Remove Contract to External EPG (check_mode)
+ mso_schema_template_external_epg_contract:
+ <<: *contract_ext_epg_present
+ state: absent
+ check_mode: true
+ register: cm_remove_contract_rel
+
+- name: Verify cm_remove_contract_rel
+ assert:
+ that:
+ - cm_remove_contract_rel is changed
+ - cm_remove_contract_rel.current == {}
+
+- name: Remove Contract to External EPG (normal mode)
+ mso_schema_template_external_epg_contract:
+ <<: *contract_ext_epg_present
+ state: absent
+ register: nm_remove_contract_rel
+
+- name: Verify nm_remove_contract_rel
+ assert:
+ that:
+ - nm_remove_contract_rel is changed
+ - nm_remove_contract_rel.current == {}
+
+- name: Remove Contract to External EPG again (check_mode)
+ mso_schema_template_external_epg_contract:
+ <<: *contract_ext_epg_present
+ state: absent
+ check_mode: true
+ register: cm_remove_contract_rel_again
+
+- name: Verify cm_remove_contract_rel_again
+ assert:
+ that:
+ - cm_remove_contract_rel_again is not changed
+ - cm_remove_contract_rel_again.current == {}
+
+- name: Remove Contract to External EPG again (normal mode)
+ mso_schema_template_external_epg_contract:
+ <<: *contract_ext_epg_present
+ state: absent
+ register: nm_remove_contract_rel_again
+
+- name: Verify nm_remove_contract_rel_again
+ assert:
+ that:
+ - nm_remove_contract_rel_again is not changed
+ - nm_remove_contract_rel_again.current == {}
+
+
+# QUERY NON-EXISTING Contract to External EPG
+- name: Query non-existing contract (check_mode)
+ mso_schema_template_external_epg_contract:
+ <<: *contract_ext_epg_query
+ contract:
+ name: non_existing_contract
+ type: provider
+ check_mode: true
+ ignore_errors: true
+ register: cm_query_non_contract
+
+- name: Query non-existing contract (normal mode)
+ mso_schema_template_external_epg_contract:
+ <<: *contract_ext_epg_query
+ contract:
+ name: non_existing_contract
+ type: provider
+ ignore_errors: true
+ register: nm_query_non_contract
+
+- name: Verify query_non_contract
+ assert:
+ that:
+ - cm_query_non_contract is not changed
+ - nm_query_non_contract is not changed
+ - cm_query_non_contract == nm_query_non_contract
+ - cm_query_non_contract.msg is match("Contract '/schemas/[0-9a-zA-Z]*/templates/Template1/contracts/non_existing_contract' not found")
+ - nm_query_non_contract.msg is match("Contract '/schemas/[0-9a-zA-Z]*/templates/Template1/contracts/non_existing_contract' not found")
+
+# QUERY NON-EXISTING ExtEPG
+- name: Query non-existing ExtEPG (check_mode)
+ mso_schema_template_external_epg_contract:
+ <<: *contract_ext_epg_query
+ external_epg: non_existing_ext_epg
+ check_mode: true
+ ignore_errors: true
+ register: cm_query_non_ext_epg
+
+- name: Query non-existing ExtEPG (normal mode)
+ mso_schema_template_external_epg_contract:
+ <<: *contract_ext_epg_query
+ external_epg: non_existing_ext_epg
+ ignore_errors: true
+ register: nm_query_non_ext_epg
+
+- name: Verify query_non_ext_epg
+ assert:
+ that:
+ - cm_query_non_ext_epg is not changed
+ - nm_query_non_ext_epg is not changed
+ - cm_query_non_ext_epg == nm_query_non_ext_epg
+ - cm_query_non_ext_epg.msg == nm_query_non_ext_epg.msg == "Provided epg 'non_existing_ext_epg' does not exist. Existing epgs{{':'}} ansible_test_1"
+
+# USE A NON-EXISTING STATE
+- name: Non-existing state for contract relationship (check_mode)
+ mso_schema_template_external_epg_contract:
+ <<: *contract_ext_epg_query
+ state: non-existing-state
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_state
+
+- name: Non-existing state for contract relationship (normal_mode)
+ mso_schema_template_external_epg_contract:
+ <<: *contract_ext_epg_query
+ state: non-existing-state
+ ignore_errors: true
+ register: nm_non_existing_state
+
+- name: Verify non_existing_state
+ assert:
+ that:
+ - cm_non_existing_state is not changed
+ - nm_non_existing_state is not changed
+ - cm_non_existing_state == nm_non_existing_state
+ - cm_non_existing_state.msg == nm_non_existing_state.msg == "value of state must be one of{{':'}} absent, present, query, got{{':'}} non-existing-state"
+
+# USE A NON-EXISTING SCHEMA
+- name: Non-existing schema for contract relationship (check_mode)
+ mso_schema_template_external_epg_contract:
+ <<: *contract_ext_epg_query
+ schema: non-existing-schema
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_schema
+
+- name: Non-existing schema for contract relationship (normal_mode)
+ mso_schema_template_external_epg_contract:
+ <<: *contract_ext_epg_query
+ schema: non-existing-schema
+ ignore_errors: true
+ register: nm_non_existing_schema
+
+- name: Verify non_existing_schema
+ assert:
+ that:
+ - cm_non_existing_schema is not changed
+ - nm_non_existing_schema is not changed
+ - cm_non_existing_schema == nm_non_existing_schema
+ - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non-existing-schema' does not exist."
+
+- name: Non-existing contract schema for contract relationship (check_mode)
+ mso_schema_template_external_epg_contract:
+ <<: *contract_ext_epg_query
+ contract:
+ name: Contract1
+ schema: non-existing-schema
+ template: Template 1
+ type: provider
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_contract_schema
+
+- name: Non-existing contract schema for contract relationship (normal_mode)
+ mso_schema_template_external_epg_contract:
+ <<: *contract_ext_epg_query
+ contract:
+ name: Contract1
+ schema: non-existing-schema
+ template: Template 1
+ type: provider
+ ignore_errors: true
+ register: nm_non_existing_contract_schema
+
+- name: Verify non_existing_contract_schema
+ assert:
+ that:
+ - cm_non_existing_contract_schema is not changed
+ - nm_non_existing_contract_schema is not changed
+ - cm_non_existing_contract_schema == nm_non_existing_contract_schema
+ - cm_non_existing_contract_schema.msg == nm_non_existing_contract_schema.msg == "Provided schema 'non-existing-schema' does not exist."
+
+# USE A NON-EXISTING TEMPLATE
+- name: Non-existing template for contract relationship (check_mode)
+ mso_schema_template_external_epg_contract:
+ <<: *contract_ext_epg_query
+ template: non-existing-template
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_template
+
+- name: Non-existing template for contract relationship (normal_mode)
+ mso_schema_template_external_epg_contract:
+ <<: *contract_ext_epg_query
+ template: non-existing-template
+ ignore_errors: true
+ register: nm_non_existing_template
+
+- name: Verify non_existing_template
+ assert:
+ that:
+ - cm_non_existing_template is not changed
+ - nm_non_existing_template is not changed
+ - cm_non_existing_template == nm_non_existing_template
+ - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non-existing-template' does not exist. Existing templates{{':'}} Template1, Template2"
+
+- name: Non-existing contract template for contract relationship (check_mode)
+ mso_schema_template_external_epg_contract:
+ <<: *contract_ext_epg_query
+ contract:
+ name: Contract1
+ template: non-existing-template
+ type: provider
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_contract_template
+
+- name: Non-existing contract template for contract relationship (normal_mode)
+ mso_schema_template_external_epg_contract:
+ <<: *contract_ext_epg_query
+ contract:
+ name: Contract1
+ template: non-existing-template
+ type: provider
+ ignore_errors: true
+ register: nm_non_existing_contract_template
+
+- name: Verify non_existing_contract_template
+ assert:
+ that:
+ - cm_non_existing_contract_template is not changed
+ - nm_non_existing_contract_template is not changed
+ - cm_non_existing_contract_template == nm_non_existing_contract_template
+ - cm_non_existing_contract_template.msg is match("Contract '/schemas/[0-9a-zA-Z]*/templates/non-existing-template/contracts/Contract1' not found")
+ - nm_non_existing_contract_template.msg is match("Contract '/schemas/[0-9a-zA-Z]*/templates/non-existing-template/contracts/Contract1' not found") \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_selector/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_selector/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_selector/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_selector/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_selector/tasks/main.yml
new file mode 100644
index 000000000..5911f7667
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_selector/tasks/main.yml
@@ -0,0 +1,452 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case)
+# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Remove schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Ensure tenant ansible_test exist
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ # sites:
+ # - '{{ mso_site | default("ansible_test") }}'
+ state: present
+
+- name: Ensure schema 1 with Template 1 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: Template 1
+ state: present
+
+- name: Ensure schema 1 with Template 2 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: Template 2
+ state: present
+
+- name: Ensure schema 2 with Template 3 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template 3
+ state: present
+
+- name: Ensure VRF exists
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF
+ state: present
+
+- name: Ensure VRF2 exists
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF2
+ state: present
+
+- name: Ensure ANP1 exists
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP
+ state: present
+
+- name: Ensure ANP2 exists
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP2
+ state: present
+
+- name: Ensure L3out exist
+ mso_schema_template_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ l3out: L3out
+ vrf:
+ name: VRF
+ state: present
+
+- name: Ensure L3out2 exist
+ mso_schema_template_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ l3out: L3out2
+ vrf:
+ name: VRF2
+ state: present
+
+# ADD External EPGs
+- name: Ensure External EPG1 exists
+ mso_schema_template_externalepg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ externalepg: extEPG1
+ vrf:
+ name: VRF
+ template: Template 1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ l3out:
+ name: L3out
+ template: Template 1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ anp:
+ name: ANP
+ template: Template 1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ state: present
+
+- name: Ensure External EPG2 exists
+ mso_schema_template_externalepg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ externalepg: extEPG2
+ vrf:
+ name: VRF2
+ template: Template 1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ l3out:
+ name: L3out2
+ template: Template 1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ anp:
+ name: ANP2
+ template: Template 1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ state: present
+
+# ADD Selector to EPG
+- name: Add Selector to extEPG1 (check_mode)
+ mso_schema_template_external_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: extEPG1
+ selector: selector_1
+ state: present
+ check_mode: true
+ register: cm_add_selector_1
+
+- name: Verify cm_add_selector_1
+ assert:
+ that:
+ - cm_add_selector_1 is changed
+ - cm_add_selector_1.previous == {}
+ - cm_add_selector_1.current.name == "selector_1"
+ - cm_add_selector_1.current.expressions == []
+
+- name: Add Selector 1 to extEPG1 (normal_mode)
+ mso_schema_template_external_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: extEPG1
+ selector: selector_1
+ state: present
+ ignore_errors: true
+ register: nm_add_selector_1
+
+- name: Verify nm_add_selector_1
+ assert:
+ that:
+ - nm_add_selector_1 is changed
+ - nm_add_selector_1.previous == {}
+ - nm_add_selector_1.current.name == "selector_1"
+ - nm_add_selector_1.current.expressions == []
+
+- name: Add Selector 1 to extEPG1 again(normal_mode)
+ mso_schema_template_external_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: extEPG1
+ selector: selector_1
+ state: present
+ ignore_errors: true
+ register: nm_add_selector_1_again
+
+- name: Verify nm_add_selector_1_again
+ assert:
+ that:
+ - nm_add_selector_1_again is not changed
+
+- name: Add Selector to extEPG1 again (normal_mode)
+ mso_schema_template_external_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: extEPG1
+ selector: selector_1
+ state: present
+ register: nm_add_selector_1_again
+
+- name: Verify nm_add_selector_1_again
+ assert:
+ that:
+ - nm_add_selector_1_again is not changed
+
+- name: Add Selector 2 to extEPG1 (normal_mode)
+ mso_schema_template_external_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: extEPG1
+ selector: selector_2
+ expressions:
+ - type: ip_address
+ operator: equals
+ value: 10.0.0.0
+ state: present
+ register: nm_add_selector_2
+
+- name: Verify nm_add_selector_2
+ assert:
+ that:
+ - nm_add_selector_2 is changed
+ - nm_add_selector_2.previous == {}
+ - nm_add_selector_2.current.name == "selector_2"
+ - nm_add_selector_2.current.expressions[0].key == "ipAddress"
+ - nm_add_selector_2.current.expressions[0].operator == "equals"
+ - nm_add_selector_2.current.expressions[0].value == "10.0.0.0"
+
+- name: Add Selector 3 to extEPG1 (normal_mode)
+ mso_schema_template_external_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: extEPG1
+ selector: selector_3
+ expressions:
+ - type: ip_address
+ operator: equals
+ value: 10.1.1.1
+ state: present
+ register: nm_add_selector_3
+
+- name: Verify nm_add_selector_3
+ assert:
+ that:
+ - nm_add_selector_3 is changed
+ - nm_add_selector_3.previous == {}
+ - nm_add_selector_3.current.name == "selector_3"
+ - nm_add_selector_3.current.expressions[0].value == "10.1.1.1"
+
+- name: Remove slector_1
+ mso_schema_template_external_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: extEPG1
+ selector: selector_1
+ state: absent
+ register: nm_remove_selector_1
+
+- name: Verify nm_remove_selector_1
+ assert:
+ that:
+ - nm_remove_selector_1 is changed
+
+# QUERY selectors
+- name: Query all selectors of extEPG1
+ mso_schema_template_external_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: extEPG1
+ state: query
+ register: nm_query_all
+
+- name: Verify nm_query_all
+ assert:
+ that:
+ - nm_query_all is not changed
+
+- name: Query a selector of extEPG1
+ mso_schema_template_external_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: extEPG1
+ selector: selector_2
+ state: query
+ register: nm_query_selector_2
+
+- name: Verify nm_query_selector_2
+ assert:
+ that:
+ - nm_query_selector_2 is not changed
+ - nm_query_selector_2.current.expressions[0].value == "10.0.0.0"
+
+- name: Query a removed selector_1 of extEPG1
+ mso_schema_template_external_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: extEPG1
+ selector: selector_1
+ state: query
+ ignore_errors: true
+ register: nm_query_removed
+
+- name: Verify nm_query_removed
+ assert:
+ that:
+ - nm_query_removed.msg == "Selector 'selector_1' not found"
+
+# QUERY NON-EXISTING External EPG
+- name: Query non-existing EPG (normal mode)
+ mso_schema_template_external_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: non_extEPG1
+ selector: selector_1
+ state: query
+ ignore_errors: true
+ register: nm_query_non_epg
+
+- name: Verify query_non_epg
+ assert:
+ that:
+ - nm_query_non_epg is not changed
+ - nm_query_non_epg.msg == "Provided external epg 'non_extEPG1' does not exist. Existing epgs{{':'}} extEPG1, extEPG2"
+
+# USE A NON-EXISTING STATE
+- name: Non-existing state (check_mode)
+ mso_schema_template_external_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: extEPG1
+ selector: selector_1
+ state: non-existing-state
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_state
+
+- name: Non-existing state (normal_mode)
+ mso_schema_template_external_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: extEPG1
+ selector: selector_1
+ state: non-existing-state
+ ignore_errors: true
+ register: nm_non_existing_state
+
+- name: Verify non_existing_state
+ assert:
+ that:
+ - cm_non_existing_state is not changed
+ - nm_non_existing_state is not changed
+ - cm_non_existing_state == nm_non_existing_state
+ - cm_non_existing_state.msg == nm_non_existing_state.msg == "value of state must be one of{{':'}} absent, present, query, got{{':'}} non-existing-state"
+
+# USE A NON-EXISTING TEMPLATE
+- name: Non-existing template (check_mode)
+ mso_schema_template_external_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: non-existing-template
+ external_epg: extEPG1
+ selector: selector_1
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_template
+
+- name: Non-existing template (normal_mode)
+ mso_schema_template_external_epg_selector:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: non-existing-template
+ external_epg: extEPG1
+ selector: selector_1
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_template
+
+- name: Verify non_existing_template
+ assert:
+ that:
+ - cm_non_existing_template is not changed
+ - nm_non_existing_template is not changed
+ - cm_non_existing_template == nm_non_existing_template
+ - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non-existing-template' does not exist. Existing templates{{':'}} Template1, Template2"
+
+# USE A NON-EXISTING SCHEMA
+- name: Non-existing schema (check_mode)
+ mso_schema_template_external_epg_selector:
+ <<: *mso_info
+ schema: non-existing-schema
+ template: Template 1
+ external_epg: extEPG1
+ selector: selector_1
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_schema
+
+- name: Non-existing schema (normal_mode)
+ mso_schema_template_external_epg_selector:
+ <<: *mso_info
+ schema: non-existing-schema
+ template: Template 1
+ external_epg: extEPG1
+ selector: selector_1
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_schema
+
+- name: Verify non_existing_schema
+ assert:
+ that:
+ - cm_non_existing_schema is not changed
+ - nm_non_existing_schema is not changed
+ - cm_non_existing_schema == nm_non_existing_schema
+ - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non-existing-schema' does not exist." \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_subnet/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_subnet/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_subnet/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_subnet/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_subnet/tasks/main.yml
new file mode 100644
index 000000000..06b5e65b7
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_external_epg_subnet/tasks/main.yml
@@ -0,0 +1,536 @@
+# Test code for the MSO modules
+# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+#
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Undeploy templates if deployed from previous test case
+ mso_schema_template_deploy:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: "{{ item }}"
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: undeploy
+ ignore_errors: true
+ loop:
+ - Template 1
+ - Template 2
+
+- name: Remove schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Ensure ansible_test_1 external EPG does not exist
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_1
+ state: absent
+ ignore_errors: true
+
+- name: Ensure tenant ansible_test exist
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ state: present
+
+- name: Ensure schema 1 with Template 1 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: Template 1
+ state: present
+
+- name: Ensure VRF exist
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF
+ state: present
+
+- name: Ensure L3out exist
+ mso_schema_template_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ l3out: L3out
+ vrf:
+ name: VRF
+ state: present
+
+- name: Ensure ANP exists
+ mso_schema_template_anp:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ anp: ANP1
+ state: present
+
+- name: Add external EPG
+ mso_schema_template_external_epg:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_1
+ vrf:
+ name: VRF
+ state: present
+
+# ADD external EPG subnet
+- name: Add external EPG subnet (check_mode)
+ mso_schema_template_external_epg_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_1
+ subnet: 10.0.0.0/24
+ state: present
+ check_mode: true
+ register: cm_add_epg_subnet
+
+- name: Verify cm_add_epg_subnet
+ assert:
+ that:
+ - cm_add_epg_subnet is changed
+ - cm_add_epg_subnet.previous == {}
+ - cm_add_epg_subnet.current.ip == "10.0.0.0/24"
+
+- name: Add external EPG subnet (normal mode)
+ mso_schema_template_external_epg_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_1
+ subnet: 10.0.0.0/24
+ state: present
+ register: nm_add_epg_subnet
+
+- name: Verify nm_add_epg_subnet
+ assert:
+ that:
+ - nm_add_epg_subnet is changed
+ - nm_add_epg_subnet.previous == {}
+ - nm_add_epg_subnet.current.ip == "10.0.0.0/24"
+
+- name: Add external EPG subnet again (check_mode)
+ mso_schema_template_external_epg_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_1
+ subnet: 10.0.0.0/24
+ state: present
+ check_mode: true
+ register: cm_add_epg_subnet_again
+
+- name: Verify cm_add_epg_subnet_again
+ assert:
+ that:
+ - cm_add_epg_subnet_again is not changed
+ - cm_add_epg_subnet_again.previous.ip == "10.0.0.0/24"
+ - cm_add_epg_subnet_again.current.ip == "10.0.0.0/24"
+
+- name: Add epg again subnet (normal mode)
+ mso_schema_template_external_epg_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_1
+ subnet: 10.0.0.0/24
+ state: present
+ register: nm_add_epg_subnet_again
+
+- name: Verify nm_add_epg_subnet_again
+ assert:
+ that:
+ - nm_add_epg_subnet_again is not changed
+ - nm_add_epg_subnet_again.previous.ip == "10.0.0.0/24"
+ - nm_add_epg_subnet_again.current.ip == "10.0.0.0/24"
+
+- name: Add external EPG subnet 2
+ mso_schema_template_external_epg_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_1
+ subnet: 10.0.0.2/24
+ state: present
+ register: add_epg_subnet_2
+
+- name: Verify add_epg_subnet_2
+ assert:
+ that:
+ - add_epg_subnet_2 is changed
+ - add_epg_subnet_2.current.ip == "10.0.0.2/24"
+
+# QUERY ALL EPG Subnets
+- name: Query all EPG (check_mode)
+ mso_schema_template_external_epg_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_1
+ state: query
+ check_mode: true
+ register: cm_query_all_epg_subnets
+
+- name: Query all EPG (normal mode)
+ mso_schema_template_external_epg_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_1
+ state: query
+ register: nm_query_all_epg_subnets
+
+- name: Verify query_all_epg_subnets
+ assert:
+ that:
+ - cm_query_all_epg_subnets is not changed
+ - nm_query_all_epg_subnets is not changed
+ - cm_query_all_epg_subnets.current | length == nm_query_all_epg_subnets.current | length == 2
+
+# QUERY AN EPG subnet
+- name: Query epg subnet 1(check_mode)
+ mso_schema_template_external_epg_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_1
+ subnet: 10.0.0.0/24
+ state: query
+ check_mode: true
+ register: cm_query_epg_subnet_1
+
+- name: Query epg subnet 1(normal_mode)
+ mso_schema_template_external_epg_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_1
+ subnet: 10.0.0.0/24
+ state: query
+ register: nm_query_epg_subnet_1
+
+- name: Verify cm_query_epg_subnet_1 and nm_query_epg_subnet_1
+ assert:
+ that:
+ - cm_query_epg_subnet_1 is not changed
+ - nm_query_epg_subnet_1 is not changed
+ - cm_query_epg_subnet_1.current.ip == "10.0.0.0/24" == nm_query_epg_subnet_1.current.ip
+
+# REMOVE EPG
+- name: Remove EPG subnet 1 (check_mode)
+ mso_schema_template_external_epg_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_1
+ subnet: 10.0.0.0/24
+ state: absent
+ check_mode: true
+ register: cm_remove_epg_subnet_1
+
+- name: Verify cm_remove_epg_subnet_1
+ assert:
+ that:
+ - cm_remove_epg_subnet_1 is changed
+ - cm_remove_epg_subnet_1.current == {}
+
+- name: Remove EPG subnet 1 (normal_mode)
+ mso_schema_template_external_epg_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_1
+ subnet: 10.0.0.0/24
+ state: absent
+ register: nm_remove_epg_subnet_1
+
+- name: Verify nm_remove_epg_subnet_1
+ assert:
+ that:
+ - nm_remove_epg_subnet_1 is changed
+ - nm_remove_epg_subnet_1.current == {}
+
+- name: Remove EPG subnet 1 again (normal mode)
+ mso_schema_template_external_epg_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_1
+ subnet: 10.0.0.0/24
+ state: absent
+ register: nm_remove_epg_subnet_1_again
+
+- name: Verify nm_remove_epg_subnet_1_again
+ assert:
+ that:
+ - nm_remove_epg_subnet_1_again is not changed
+ - nm_remove_epg_subnet_1_again.previous == nm_remove_epg_subnet_1_again.current == {}
+
+# Chcek aggregate when scope parameter Shared control is absent and present
+- name: Add aggregate without Shared control scope parameter
+ mso_schema_template_external_epg_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_1
+ subnet: 10.0.0.2/24
+ scope: import-rtctrl
+ aggregate: shared-rtctrl
+ state: present
+ ignore_errors: true
+ register: add_epg_subnet_no_ag
+
+- name: Verify add_epg_subnet_no_ag
+ assert:
+ that:
+ - add_epg_subnet_no_ag is changed
+
+- name: Verify add_epg_subnet_no_ag (3.1.1g to 3.1.1n)
+ assert:
+ that:
+ - add_epg_subnet_no_ag.msg == "MSO Error 400{{':'}} Bad Request{{':'}} Patch Failed, Received{{':'}} Aggregate should be enabled only if shared-rtctrl is enabled in Scope exception while trying to update schema"
+ when:
+ - version.current.version is version('3.1.1g', '>=')
+ - version.current.version is version('3.2', '<')
+
+- name: Verify add_epg_subnet_no_ag (version < 3.1.1g)
+ assert:
+ that:
+ - add_epg_subnet_no_ag.msg == "MSO Error 400{{':'}} Bad Request{{':'}} Aggregate should be enabled only if shared-rtctrl is enabled in Scope"
+ when:
+ - version.current.version is version('3.1.1g', '<')
+
+- name: Verify add_epg_subnet_no_ag (version >= 4.0)
+ assert:
+ that:
+ - add_epg_subnet_no_ag.msg == "MSO Error 400{{':'}} ExternalEPG{{':'}} ansible_test_1 in Schema{{':'}} ansible_test , Template{{':'}} Template1 External EPG validation error{{':'}} aggregate should be enabled only if shared-rtctrl is enabled in Scope for subnet 10.0.0.2/24"
+ when:
+ - version.current.version is version('4.0', '>=')
+
+- name: Execute tasks only for MSO version < 4.0
+ # mso_schema_validate not supported after 4.0, validation executed upon request
+ when: version.current.version is version('4.0', '<')
+ block:
+ - name: Get Validation status
+ mso_schema_validate:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ state: query
+ ignore_errors: true
+ register: query_validate
+ when: version.current.version is version('3.6', '>=')
+
+ - name: Verify query_validate for a version that's after 3.6
+ assert:
+ that:
+ - query_validate is not changed
+ - query_validate.msg is match ("MSO Error 400{{':'}} Bad Request{{':'}} Patch Failed, Received{{':'}} Aggregate should be enabled only if shared-rtctrl is enabled in Scope exception while trying to update schema")
+ when:
+ - version.current.version is version('3.6', '>=')
+
+- name: Add aggregate with Shared control scope parameter
+ mso_schema_template_external_epg_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_1
+ subnet: 10.0.0.2/24
+ scope: shared-rtctrl
+ aggregate: shared-rtctrl
+ state: present
+ ignore_errors: true
+ register: add_epg_subnet_ag
+
+- name: Verify add_epg_subnet_ag
+ assert:
+ that:
+ - add_epg_subnet_ag is changed
+ - add_epg_subnet_ag.current.aggregate[0] == "shared-rtctrl"
+
+- name: Change EPG subnet 2 by changing Route Controls
+ mso_schema_template_external_epg_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_1
+ subnet: 10.0.0.2/24
+ scope:
+ - import-rtctrl
+ - shared-security
+ - export-rtctrl
+ - shared-security
+ - import-security
+ state: present
+ register: change_epg_subnet
+
+- name: Verify change_epg_subnet
+ assert:
+ that:
+ - change_epg_subnet is changed
+ - change_epg_subnet.current.ip == "10.0.0.2/24"
+ - change_epg_subnet.current.scope | length == 5
+
+# QUERY NON-EXISTING EPG subnet
+- name: Query non-existing EPG subnet(check_mode)
+ mso_schema_template_external_epg_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_1
+ subnet: 10.0.0.10/24
+ state: query
+ ignore_errors: true
+ check_mode: true
+ register: cm_query_non_existing_epg_subnet
+
+- name: Query non-existing EPG subnet(normal_mode)
+ mso_schema_template_external_epg_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: ansible_test_1
+ subnet: 10.0.0.10/24
+ state: query
+ ignore_errors: true
+ register: nm_query_non_existing_epg_subnet
+
+- name: Verify cm_query_non_existing_epg_subnet and nm_query_non_existing_epg_subnet
+ assert:
+ that:
+ - cm_query_non_existing_epg_subnet is not changed
+ - nm_query_non_existing_epg_subnet is not changed
+ - cm_query_non_existing_epg_subnet == nm_query_non_existing_epg_subnet
+ - cm_query_non_existing_epg_subnet.msg == nm_query_non_existing_epg_subnet.msg == "Subnet '10.0.0.10/24' not found"
+
+# QUERY NON-EXISTING EPG
+- name: Query non-existing EPG subnet(check_mode)
+ mso_schema_template_external_epg_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: non_existing_epg
+ subnet: 10.0.0.2/24
+ state: query
+ ignore_errors: true
+ check_mode: true
+ register: cm_query_non_existing_epg
+
+- name: Query non-existing EPG subnet(normal_mode)
+ mso_schema_template_external_epg_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ external_epg: non_existing_epg
+ subnet: 10.0.0.2/24
+ state: query
+ ignore_errors: true
+ register: nm_query_non_existing_epg
+
+- name: Verify cm_query_non_existing_epg and nm_query_non_existing_epg
+ assert:
+ that:
+ - cm_query_non_existing_epg is not changed
+ - nm_query_non_existing_epg is not changed
+ - cm_query_non_existing_epg == nm_query_non_existing_epg
+ - cm_query_non_existing_epg.msg == nm_query_non_existing_epg.msg == "Provided External EPG 'non_existing_epg' does not exist. Existing epgs{{':'}} ansible_test_1"
+
+# USE A NON-EXISTING SCHEMA
+- name: Non-existing schema for epg subnet(check_mode)
+ mso_schema_template_external_epg_subnet:
+ <<: *mso_info
+ schema: non-existing-schema
+ template: Template 1
+ external_epg: ansible_test_1
+ subnet: 10.0.0.0/24
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_schema
+
+- name: Non-existing schema for epg subnet(normal_mode)
+ mso_schema_template_external_epg_subnet:
+ <<: *mso_info
+ schema: non-existing-schema
+ template: Template 1
+ external_epg: ansible_test_1
+ subnet: 10.0.0.0/24
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_schema
+
+- name: Verify non_existing_schema
+ assert:
+ that:
+ - cm_non_existing_schema is not changed
+ - nm_non_existing_schema is not changed
+ - cm_non_existing_schema == nm_non_existing_schema
+ - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non-existing-schema' does not exist."
+
+# USE A NON-EXISTING TEMPLATE
+- name: Non-existing template for epg subnet(check_mode)
+ mso_schema_template_external_epg_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: non-existing-template
+ external_epg: ansible_test_1
+ subnet: 10.0.0.0/24
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_template
+
+- name: Non-existing template for epg subnet(normal_mode)
+ mso_schema_template_external_epg_subnet:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: non-existing-template
+ external_epg: ansible_test_1
+ subnet: 10.0.0.0/24
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_template
+
+- name: Verify non_existing_template
+ assert:
+ that:
+ - cm_non_existing_template is not changed
+ - nm_non_existing_template is not changed
+ - cm_non_existing_template == nm_non_existing_template
+ - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non-existing-template' does not exist. Existing templates{{':'}} Template1" \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_filter_entry/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_filter_entry/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_filter_entry/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_filter_entry/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_filter_entry/tasks/main.yml
new file mode 100644
index 000000000..262a1903d
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_filter_entry/tasks/main.yml
@@ -0,0 +1,326 @@
+# Test code for the MSO modules
+# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Ensure site exist
+ mso_site: &site_present
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ apic_username }}'
+ apic_password: '{{ apic_password }}'
+ apic_site_id: '{{ apic_site_id | default(101) }}'
+ urls:
+ - https://{{ apic_hostname }}
+ state: present
+
+- name: Remove schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Ensure tenant ansible_test exist
+ mso_tenant: &tenant_present
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ state: present
+
+- name: Ensure schema 1 with Template1 exist
+ mso_schema_template: &schema_present
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: Template1
+ state: present
+
+- name: Create filter with filter entry (check_model)
+ cisco.mso.mso_schema_template_filter_entry:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ filter: filter1
+ filter_display_name: filter1
+ entry: filter_entry1
+ state: present
+ check_mode: true
+ register: cm_add_filter
+
+- name: Create filter with filter entry (normal mode)
+ cisco.mso.mso_schema_template_filter_entry:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ filter: filter1
+ entry: filter_entry1
+ state: present
+ register: nm_add_filter
+
+- name: Create filter with filter entry again
+ cisco.mso.mso_schema_template_filter_entry:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ filter: filter1
+ entry: filter_entry1
+ state: present
+ register: add_filter_again
+
+- name: Verify add_filter
+ assert:
+ that:
+ - cm_add_filter is changed
+ - cm_add_filter.previous == {}
+ - cm_add_filter.current.name == "filter_entry1"
+ - nm_add_filter is changed
+ - nm_add_filter.previous == {}
+ - nm_add_filter.current.name == "filter_entry1"
+ - add_filter_again is not changed
+ - add_filter_again.previous.name == "filter_entry1"
+ - nm_add_filter.current == add_filter_again.current
+
+- name: Add description to filter and filter entry for version greater than 3.3
+ cisco.mso.mso_schema_template_filter_entry:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ filter: filter1
+ filter_description: "filter description"
+ entry: filter_entry1
+ filter_entry_description: "filter entry description"
+ state: present
+ register: add_filter_descr
+ when: version.current.version is version('3.3', '>=')
+
+- name: Verify add_filter_only
+ assert:
+ that:
+ - add_filter_descr is changed
+ - add_filter_descr.current.description == "filter entry description"
+ when: version.current.version is version('3.3', '>=')
+
+
+- name: Create filter without filter entry
+ cisco.mso.mso_schema_template_filter_entry:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ filter: filter1
+ state: present
+ ignore_errors: true
+ register: add_filter_only
+
+- name: Verify add_filter_only
+ assert:
+ that:
+ - add_filter_only is not changed
+ - add_filter_only.msg == "state is present but all of the following are missing{{':'}} entry"
+
+- name: Create filter with multiple filter entries
+ cisco.mso.mso_schema_template_filter_entry:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ filter: filter1
+ filter_display_name: filter1
+ display_name: '{{ item }}'
+ ethertype: ip
+ ip_protocol: tcp
+ tcp_session_rules:
+ - acknowledgement
+ - established
+ source_from: 22
+ source_to: 22
+ destination_from: 22
+ destination_to: 22
+ arp_flag: request
+ stateful: true
+ fragments_only: false
+ entry: '{{ item }}'
+ state: present
+ register: add_multiple_entries
+ loop:
+ - 'filter_entry2'
+ - 'filter_entry3'
+
+# QUERY the filters
+- name: Query a particular filter entry 1
+ cisco.mso.mso_schema_template_filter_entry:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ filter: filter1
+ entry: filter_entry1
+ state: query
+ register: query_filter
+
+- name: Query all filter entries
+ cisco.mso.mso_schema_template_filter_entry:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ filter: filter1
+ state: query
+ register: query_all
+
+- name: Verify query
+ assert:
+ that:
+ - query_filter is not changed
+ - query_all is not changed
+
+# QUERY cases
+- name: Query existing filter and filter entry
+ cisco.mso.mso_schema_template_filter_entry:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ filter: non_existing_filter
+ entry: non_existing_filter_entry
+ state: query
+ ignore_errors: true
+ register: query_non_existing_filter
+
+- name: Query non-existing filter entry
+ cisco.mso.mso_schema_template_filter_entry:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ filter: filter1
+ entry: non_existing_filter_entry
+ state: query
+ ignore_errors: true
+ register: query_non_existing_entry
+
+- name: Verify query cases
+ assert:
+ that:
+ - query_non_existing_filter is not changed
+ - query_non_existing_entry is not changed
+ - query_non_existing_entry.msg == "Entry 'non_existing_filter_entry' not found"
+
+# Delete filter entries
+- name: Delete filter entry 3
+ cisco.mso.mso_schema_template_filter_entry:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ filter: filter1
+ entry: filter_entry3
+ state: absent
+ register: remove_filter
+
+- name: Verify delete filter_entry3
+ assert:
+ that:
+ - remove_filter is changed
+ - remove_filter.current == {}
+
+# USE A NON_EXISTING_TEMPLATE
+- name: non_existing_template (normal_mode)
+ cisco.mso.mso_schema_template_filter_entry:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: non_existing_template
+ filter: filter1
+ entry: filter_entry1
+ state: present
+ ignore_errors: true
+ register: nm_non_existing_template
+
+- name: Verify nm_non_existing_template
+ assert:
+ that:
+ - nm_non_existing_template is not changed
+ - nm_non_existing_template.msg == "Provided template 'non_existing_template' does not exist. Existing templates{{':'}} Template1"
+
+- name: Query non_existing_filter
+ cisco.mso.mso_schema_template_filter_entry:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ filter: non_existing_filter
+ state: query
+ ignore_errors: true
+ register: query_non_existing_filter
+
+- name: Delete non_existing_filter
+ cisco.mso.mso_schema_template_filter_entry:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ filter: non_existing_filter
+ entry: filter_entry1
+ state: absent
+ ignore_errors: true
+ register: remove_non_existing_filter
+
+- name: Delete non_existing_filter_entry
+ cisco.mso.mso_schema_template_filter_entry:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ filter: filter1
+ entry: non_existing_filter_entry
+ state: absent
+ ignore_errors: true
+ register: remove_non_existing_entry
+
+- name: Verify non_existing
+ assert:
+ that:
+ - query_non_existing_filter is not changed
+ - query_non_existing_filter.msg == "Filter 'non_existing_filter' not found"
+ - remove_non_existing_filter is not changed
+ - remove_non_existing_entry is not changed
+ - nm_non_existing_template.msg == "Provided template 'non_existing_template' does not exist. Existing templates{{':'}} Template1"
+
+
+# Delete filter entries
+- name: Delete filter entry 3
+ cisco.mso.mso_schema_template_filter_entry:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ filter: filter1
+ entry: '{{ item }}'
+ state: absent
+ register: remove_multiple_entries
+ loop:
+ - 'filter_entry1'
+ - 'filter_entry2'
+
+- name: Verify remove_multiple_entries
+ assert:
+ that:
+ - remove_multiple_entries is changed \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_l3out/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_l3out/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_l3out/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_l3out/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_l3out/tasks/main.yml
new file mode 100644
index 000000000..a61752fa6
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_l3out/tasks/main.yml
@@ -0,0 +1,265 @@
+# Test code for the MSO modules
+# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Remove schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Ensure tenant ansible_test exists
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ state: present
+
+- name: Ensure schema 1 with Template1, and Template2 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: '{{item.template}}'
+ state: present
+ loop:
+ - { template: Template1}
+ - { template: Template2}
+
+- name: Ensure VRF1 exists
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ vrf: VRF1
+ state: present
+
+- name: Verify L3Out doesn't exist
+ mso_schema_template_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ l3out: L3out1
+ state: absent
+
+- name: Add new L3Out (check_mode)
+ mso_schema_template_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ l3out: L3out1
+ vrf:
+ name: VRF1
+ state: present
+ check_mode: true
+ register: cm_add_l3out
+
+- name: Add new L3Out (normal mode)
+ mso_schema_template_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ l3out: L3out1
+ vrf:
+ name: VRF1
+ state: present
+ register: nm_add_l3out
+
+- name: Add L3Out again
+ mso_schema_template_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ l3out: L3out1
+ vrf:
+ name: VRF1
+ state: present
+ register: add_l3out_again
+
+- name: Verify add
+ assert:
+ that:
+ - cm_add_l3out is changed
+ - cm_add_l3out.previous == {}
+ - cm_add_l3out.current.name == "L3out1"
+ - cm_add_l3out.current.vrfRef.templateName == "Template1"
+ - cm_add_l3out.current.vrfRef.vrfName == "VRF1"
+ - nm_add_l3out is changed
+ - nm_add_l3out.previous == {}
+ - nm_add_l3out.current.name == "L3out1"
+ - nm_add_l3out.current.vrfRef.templateName == "Template1"
+ - nm_add_l3out.current.vrfRef.vrfName == "VRF1"
+ - add_l3out_again is not changed
+ - add_l3out_again.previous.name == "L3out1"
+ - nm_add_l3out.current.vrfRef.schemaId == add_l3out_again.current.vrfRef.schemaId
+
+- name: Add new L3Outs
+ mso_schema_template_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ l3out: '{{item.l3out}}'
+ vrf:
+ name: VRF1
+ state: present
+ register: new_l3outs
+ loop:
+ - { l3out: L3out2}
+ - { l3out: L3out3}
+
+- name: Verify add
+ assert:
+ that:
+ - new_l3outs is changed
+
+- name: Query a specific L3Out
+ mso_schema_template_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ l3out: L3out1
+ state: query
+ register: query_l3out
+
+- name: Query all L3outs
+ mso_schema_template_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ state: query
+ register: query_all
+
+- name: Verify query
+ assert:
+ that:
+ - query_l3out is not changed
+ - query_all is not changed
+
+- name: Remove an L3Out
+ mso_schema_template_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ l3out: L3out1
+ state: absent
+ register: delete_l3out
+
+- name: Verify delete
+ assert:
+ that:
+ - delete_l3out is changed
+ - delete_l3out.previous.name == "L3out1"
+ - delete_l3out.current == {}
+
+# USE A NON_EXISTING_TEMPLATE
+- name: non_existing_template (check_mode)
+ mso_schema_template_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: non_existing_template
+ l3out: L3out2
+ vrf:
+ name: VRF1
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_template
+
+- name: non_existing_template (normal_mode)
+ mso_schema_template_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: non_existing_template
+ l3out: L3out2
+ vrf:
+ name: VRF1
+ state: query
+ ignore_errors: true
+ register: nm_non_existing_template
+
+- name: Verify cm_non_existing_template and nm_non_existing_template
+ assert:
+ that:
+ - cm_non_existing_template is not changed
+ - nm_non_existing_template is not changed
+ - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non_existing_template' does not exist. Existing templates{{':'}} Template1, Template2"
+
+# QUERY NON-EXISTING L3Out
+- name: Query non-existing L3Out (check_mode)
+ mso_schema_template_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ l3out: non_existing_l3out
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_query_non_l3out
+
+- name: Query non-existing L3Out (normal_mode)
+ mso_schema_template_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ l3out: non_existing_l3out
+ state: query
+ ignore_errors: true
+ register: nm_query_non_l3out
+
+- name: Verify cm_query_non_l3out and nm_query_non_l3out
+ assert:
+ that:
+ - cm_query_non_l3out is not changed
+ - nm_query_non_l3out is not changed
+ - cm_query_non_l3out.msg == nm_query_non_l3out.msg == "L3out 'non_existing_l3out' not found"
+
+# Add description for version >= 3.3
+
+- name: Add new L3Out
+ mso_schema_template_l3out:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ l3out: L3out1
+ description: "A L3out description"
+ vrf:
+ name: VRF1
+ state: present
+ register: add_desc
+ when: version.current.version is version('3.3', '>=')
+
+- name: Verify add description
+ assert:
+ that:
+ - add_desc is changed
+ when: version.current.version is version('3.3', '>=') \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_migrate/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_migrate/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_migrate/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_migrate/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_migrate/tasks/main.yml
new file mode 100644
index 000000000..44940f0d6
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_migrate/tasks/main.yml
@@ -0,0 +1,383 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+#
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+# CLEAN ENVIRONMENT
+- name: Ensure site exist
+ mso_site: &site_present
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ apic_username }}'
+ apic_password: '{{ apic_password }}'
+ apic_site_id: '{{ apic_site_id | default(102) }}'
+ urls:
+ - https://{{ apic_hostname }}
+ state: present
+
+- name: Undeploy a schema 2 template 2
+ mso_schema_template_deploy:
+ <<: *mso_info
+ template: Template 2
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: undeploy
+ ignore_errors: true
+ register: undeploy_template2
+
+- name: Undeploy a schema 1 template 1
+ mso_schema_template_deploy:
+ <<: *mso_info
+ template: Template 1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: undeploy
+ ignore_errors: true
+ register: undeploy_template1
+
+- name: Remove a site from a schema 1 with Template 1
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ state: absent
+ ignore_errors: true
+ register: rm_site_temp1
+
+- name: Remove a site from a schema 2 with Template 2
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 2
+ state: absent
+ ignore_errors: true
+ register: rm_site_temp2
+
+- name: Remove schemas
+ mso_schema:
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+ schema: '{{ item }}'
+ state: absent
+ ignore_errors: true
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Ensure tenant ansible_test exist
+ mso_tenant: &tenant_present
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ state: present
+
+- name: Ensure schemas with Template 1 exist
+ mso_schema_template: &schema_present
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+ schema: '{{ item }}'
+ tenant: ansible_test
+ template: Template 1
+ state: present
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}'
+ - '{{ mso_schema | default("ansible_test") }}_2'
+
+- name: Ensure schema 2 with Template 2 exist
+ mso_schema_template:
+ <<: *schema_present
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template 2
+ state: present
+ register: schema2_template2
+
+- name: Add a new site to a schema 1 with Template 1 in normal mode
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ state: present
+ register: add_site_nm1
+
+- name: Add a new site to a schema 2 with Template 2 in normal mode
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 2
+ state: present
+ register: add_site_nm2
+
+- name: Ensure VRF exist
+ mso_schema_template_vrf: &vrf_present
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF
+ layer3_multicast: true
+ state: present
+
+- name: Ensure ANP exist
+ mso_schema_template_anp:
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 1
+ anp: ANP
+ state: present
+
+- name: Ensure ANP2 exist
+ mso_schema_template_anp:
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 1
+ anp: ANP2
+ state: present
+
+- name: Ensure ansible_test_1 BD exist
+ mso_schema_template_bd:
+ <<: *vrf_present
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 1
+ bd: '{{ item }}'
+ vrf:
+ name: VRF
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ state: present
+ when: version.current.version is version('2.2.4e', '!=')
+ loop:
+ - '{{ BD_1 | default("ansible_test") }}_1'
+ - '{{ BD_2 | default("ansible_test") }}_2'
+
+- name: Ensure EPG exist
+ mso_schema_template_anp_epg: &epg_present
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 1
+ anp: ANP
+ epg: ansible_test_1
+ bd:
+ name: ansible_test_1
+ vrf:
+ name: VRF
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ state: present
+ register: cm_add_epg
+
+- name: Add EPG 2 (normal mode)
+ mso_schema_template_anp_epg:
+ <<: *epg_present
+ anp: ANP2
+ epg: '{{ item }}'
+ loop:
+ - '{{ EPG_2 | default("ansible_test") }}_2'
+ - '{{ EPG_3 | default("ansible_test") }}_3'
+ - '{{ EPG_4 | default("ansible_test") }}_4'
+
+- name: Migration of objects between templates
+ mso_schema_template_migrate:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 1
+ target_schema: '{{ mso_schema | default("ansible_test") }}'
+ target_template: Template 1
+ bds:
+ - ansible_test_1
+ epgs:
+ - epg: ansible_test_1
+ anp: ANP
+ - epg: ansible_test_2
+ anp: ANP2
+ state: present
+ when: version.current.version is version('2.2.4e', '!=')
+ register: object_migrate
+
+- name: Deploy a schema 1 template 1 after version 4.0
+ ndo_schema_template_deploy:
+ <<: *mso_info
+ template: Template 1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ state: deploy
+ when: version.current.version is version('4.0', '>=')
+
+- name: Migration of BD objects between templates
+ mso_schema_template_migrate:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 1
+ target_schema: '{{ mso_schema | default("ansible_test") }}_2'
+ target_template: Template 2
+ bds:
+ - ansible_test_2
+ state: present
+ when: version.current.version is version('2.2.4e', '!=')
+ register: bd_migrate
+
+- name: Deploy a schema 2 template 2 after version 4.0
+ ndo_schema_template_deploy:
+ <<: *mso_info
+ template: Template 2
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ state: deploy
+ when: version.current.version is version('4.0', '>=')
+
+- name: Migration of EPG objects between templates
+ mso_schema_template_migrate:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 1
+ target_schema: '{{ mso_schema | default("ansible_test") }}_2'
+ target_template: Template 2
+ epgs:
+ - epg: ansible_test_3
+ anp: ANP2
+ - epg: ansible_test_4
+ anp: ANP2
+ state: present
+ when: version.current.version is version('2.2.4e', '!=')
+ register: epg_migrate
+
+- name: Undeploy before 4.0
+ when:
+ - version.current.version is version('4.0', '<')
+ block:
+ - name: Undeploy a schema 2 template 2
+ mso_schema_template_deploy:
+ <<: *mso_info
+ template: Template 2
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: undeploy
+ ignore_errors: true
+ register: undeploy_template2
+
+ - name: Undeploy a schema 1 template 1
+ mso_schema_template_deploy:
+ <<: *mso_info
+ template: Template 1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: undeploy
+ ignore_errors: true
+ register: undeploy_template1
+
+- name: Undeploy after 4.0
+ when:
+ - version.current.version is version('4.0', '>=')
+ block:
+ - name: Undeploy a schema 2 template 2
+ ndo_schema_template_deploy:
+ <<: *mso_info
+ template: Template 2
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ state: undeploy
+
+ - name: Undeploy a schema 1 template 1
+ ndo_schema_template_deploy:
+ <<: *mso_info
+ template: Template 1
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ state: undeploy
+
+- name: Remove a site from a schema 2 with Template 2
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 2
+ state: absent
+ register: rm_site_temp2
+
+- name: Remove a site from a schema 1 with Template 1
+ mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ state: absent
+ register: rm_site_temp1
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_service_graph/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_service_graph/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_service_graph/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_service_graph/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_service_graph/tasks/main.yml
new file mode 100644
index 000000000..d11a3f4a2
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_service_graph/tasks/main.yml
@@ -0,0 +1,396 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(false) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Remove schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Ensure tenant ansible_test exist
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ state: present
+
+- name: Ensure schema 1 with Template 1 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: Template1
+ state: present
+
+- name: Create a service graph (check mode)
+ cisco.mso.mso_schema_template_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG1
+ display_name: sg
+ service_nodes:
+ - type: firewall
+ - type: load-balancer
+ - type: other
+ filter_after_first_node: allow_all
+ state: present
+ register: sg1_cm
+ check_mode: true
+
+- name: Verify sg1_cm
+ assert:
+ that:
+ - sg1_cm is changed
+ - sg1_cm.current.name == "SG1"
+ - sg1_cm.current.displayName == "sg"
+ - sg1_cm.current.nodeFilter == "allow-all"
+ - sg1_cm.current.serviceGraphRef.templateName == "Template1"
+ - sg1_cm.current.serviceNodes | length == 3
+ - sg1_cm.current.serviceNodes.0.name == "firewall"
+ - sg1_cm.current.serviceNodes.1.name == "load-balancer"
+ - sg1_cm.current.serviceNodes.2.name == "other"
+
+- name: Create a service graph (normal mode)
+ cisco.mso.mso_schema_template_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG1
+ display_name: sg
+ service_nodes:
+ - type: firewall
+ - type: load-balancer
+ - type: other
+ filter_after_first_node: allow_all
+ state: present
+ register: sg1
+
+- name: Create service graph again
+ cisco.mso.mso_schema_template_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG1
+ display_name: sg
+ service_nodes:
+ - type: firewall
+ - type: load-balancer
+ - type: other
+ filter_after_first_node: allow_all
+ state: present
+ register: sg1_again
+
+- name: Verify sg1 and sg1_again
+ assert:
+ that:
+ - sg1 is changed
+ - sg1_again is not changed
+ - sg1.current.name == "SG1"
+ - sg1.current.displayName == "sg"
+ - sg1.current.nodeFilter == "allow-all"
+ - sg1.current.serviceGraphRef.templateName == "Template1"
+ - sg1.current.serviceNodes | length == 3
+ - sg1.current.serviceNodes.0.name == "firewall"
+ - sg1.current.serviceNodes.1.name == "load-balancer"
+ - sg1.current.serviceNodes.2.name == "other"
+ - sg1_again.current.name == "SG1"
+ - sg1_again.current.displayName == "sg"
+ - sg1_again.current.nodeFilter == "allow-all"
+ - sg1_again.current.serviceGraphRef.templateName == "Template1"
+ - sg1_again.current.serviceNodes | length == 3
+ - sg1_again.current.serviceNodes.0.name == "firewall"
+ - sg1_again.current.serviceNodes.1.name == "load-balancer"
+ - sg1_again.current.serviceNodes.2.name == "other"
+
+- name: Create another service graph SG2
+ cisco.mso.mso_schema_template_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG2
+ display_name: Service_Graph2
+ service_nodes:
+ - type: firewall
+ - type: load-balancer
+ filter_after_first_node: filters_from_contract
+ state: present
+ register: sg2
+
+- name: Verify sg2
+ assert:
+ that:
+ - sg2 is changed
+ - sg2.current.name == "SG2"
+ - sg2.current.displayName == "Service_Graph2"
+ - sg2.current.nodeFilter == "filters-from-contract"
+ - sg2.current.serviceGraphRef.templateName == "Template1"
+ - sg2.current.serviceNodes | length == 2
+ - sg2.current.serviceNodes.0.name == "firewall"
+ - sg2.current.serviceNodes.1.name == "load-balancer"
+
+- name: Change Service Graph SG2
+ cisco.mso.mso_schema_template_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG2
+ display_name: Service_Graph_changed
+ service_nodes:
+ - type: firewall
+ - type: load-balancer
+ filter_after_first_node: filters_from_contract
+ state: present
+ register: sg2_change
+
+- name: Verify sg2_change
+ assert:
+ that:
+ - sg2_change is changed
+ - sg2_change.current.name == "SG2"
+ - sg2_change.current.displayName == "Service_Graph_changed"
+ - sg2_change.current.nodeFilter == "filters-from-contract"
+ - sg2_change.current.serviceGraphRef.templateName == "Template1"
+ - sg2_change.current.serviceNodes | length == 2
+ - sg2_change.current.serviceNodes.0.name == "firewall"
+ - sg2_change.current.serviceNodes.1.name == "load-balancer"
+
+- name: Create another service graph with no display name
+ cisco.mso.mso_schema_template_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG3
+ service_nodes:
+ - type: firewall
+ filter_after_first_node: filters_from_contract
+ state: present
+ register: sg3
+
+- name: Verify sg3
+ assert:
+ that:
+ - sg3 is changed
+ - sg3.current.name == "SG3"
+ - sg3.current.displayName == "SG3"
+ - sg3.current.nodeFilter == "filters-from-contract"
+ - sg3.current.serviceGraphRef.templateName == "Template1"
+ - sg3.current.serviceNodes.0.name == "firewall"
+ - sg3.current.serviceNodes | length == 1
+
+- name: Create service graph SG3 with addition of new service node type
+ cisco.mso.mso_schema_template_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG3
+ service_nodes:
+ - type: firewall
+ - type: other
+ filter_after_first_node: filters_from_contract
+ state: present
+ register: sg3_other
+
+- name: Verify sg3_other
+ assert:
+ that:
+ - sg3_other is changed
+ - sg3_other.current.name == "SG3"
+ - sg3_other.current.displayName == "SG3"
+ - sg3_other.current.nodeFilter == "filters-from-contract"
+ - sg3_other.current.serviceGraphRef.templateName == "Template1"
+ - sg3_other.current.serviceNodes.0.name == "firewall"
+ - sg3_other.current.serviceNodes.1.name == "other"
+ - sg3_other.current.serviceNodes | length == 2
+
+- name: Create service graph SG3 interchanging the index of service node types
+ cisco.mso.mso_schema_template_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG3
+ service_nodes:
+ - type: other
+ - type: firewall
+ filter_after_first_node: filters_from_contract
+ state: present
+ register: sg3_interchange
+
+- name: Verify sg3_interchange
+ assert:
+ that:
+ - sg3_interchange is changed
+ - sg3_interchange.current.name == "SG3"
+ - sg3_interchange.current.displayName == "SG3"
+ - sg3_interchange.current.nodeFilter == "filters-from-contract"
+ - sg3_interchange.current.serviceGraphRef.templateName == "Template1"
+ - sg3_interchange.current.serviceNodes.1.name == "firewall"
+ - sg3_interchange.current.serviceNodes.0.name == "other"
+ - sg3_interchange.current.serviceNodes | length == 2
+
+- name: Create another service graph with non existing node type
+ cisco.mso.mso_schema_template_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG4
+ service_nodes:
+ - type: non_existing_type
+ filter_after_first_node: filters_from_contract
+ state: present
+ register: sg4
+ ignore_errors: true
+
+- name: Verify sg4
+ assert:
+ that:
+ - sg4.msg == "Provided service node type 'non_existing_type' does not exist. Existing node types include{{':'}} firewall, load-balancer, other",
+
+- name: Query service graph SG
+ cisco.mso.mso_schema_template_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG1
+ state: query
+ register: query_sg
+
+- name: Verify query_sg
+ assert:
+ that:
+ - query_sg is not changed
+ - query_sg.current.name == "SG1"
+ - query_sg.current.displayName == "sg"
+ - query_sg.current.nodeFilter == "allow-all"
+ - query_sg.current.serviceNodes | length == 3
+ - query_sg.current.serviceNodes.0.name == "firewall"
+ - query_sg.current.serviceNodes.1.name == "load-balancer"
+ - query_sg.current.serviceNodes.2.name == "other"
+
+- name: Query all service graphs
+ cisco.mso.mso_schema_template_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ state: query
+ register: query_all
+
+- name: Verify query_all
+ assert:
+ that:
+ - query_all is not changed
+ - query_all.current | length == 3
+
+- name: Query non_existing service graph
+ cisco.mso.mso_schema_template_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ service_graph: non_existent
+ state: query
+ ignore_errors: true
+ register: query_non_existing_sg
+
+- name: Verify query_non_existing_sg
+ assert:
+ that:
+ - query_non_existing_sg.msg == "Service Graph 'non_existent' not found"
+
+- name: Use non_existing schema
+ cisco.mso.mso_schema_template_service_graph:
+ <<: *mso_info
+ schema: non_existing_schema
+ template: Template1
+ service_graph: SG
+ state: query
+ ignore_errors: true
+ register: query_non_existing_schema
+
+- name: Use non_existing template
+ cisco.mso.mso_schema_template_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: non_existing_template
+ service_graph: SG
+ state: query
+ ignore_errors: true
+ register: query_non_existing_template
+
+- name: Verify query_non_existing_schema and query_non_existing_template
+ assert:
+ that:
+ - query_non_existing_schema.msg == "Provided schema 'non_existing_schema' does not exist."
+ - query_non_existing_template.msg == "Provided template 'non_existing_template' does not exist. Existing templates{{':'}} Template1"
+
+- name: Remove service graph (check mode)
+ cisco.mso.mso_schema_template_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG1
+ state: absent
+ register: rm_sg_cm
+ check_mode: true
+
+- name: Verify rm_sg_cm
+ assert:
+ that:
+ - rm_sg_cm is changed
+ - rm_sg_cm.current == {}
+ - rm_sg_cm.previous.name == "SG1"
+
+- name: Remove service graph (normal mode)
+ cisco.mso.mso_schema_template_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG1
+ state: absent
+ register: rm_sg
+
+- name: Verify rm_sg
+ assert:
+ that:
+ - rm_sg is changed
+ - rm_sg.current == {}
+ - rm_sg.previous.name == "SG1"
+
+- name: Remove service graph again
+ cisco.mso.mso_schema_template_service_graph:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ service_graph: SG1
+ state: absent
+ register: rm_sg_again
+
+- name: Verify rm_sg_again
+ assert:
+ that:
+ - rm_sg_again is not changed
+ - rm_sg_again.current == {}
+ - rm_sg_again.previous == {}
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_vrf/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_vrf/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_vrf/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_vrf/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_vrf/tasks/main.yml
new file mode 100644
index 000000000..0f25a76b0
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_vrf/tasks/main.yml
@@ -0,0 +1,542 @@
+# Test code for the MSO modules
+# Copyright: (c) 2023, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com>
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case)
+#
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+# - name: Ensure site exist
+# mso_site: &site_present
+# host: '{{ mso_hostname }}'
+# username: '{{ mso_username }}'
+# password: '{{ mso_password }}'
+# validate_certs: '{{ mso_validate_certs | default(false) }}'
+# use_ssl: '{{ mso_use_ssl | default(true) }}'
+# use_proxy: '{{ mso_use_proxy | default(true) }}'
+# output_level: '{{ mso_output_level | default("info") }}'
+# site: '{{ mso_site | default("ansible_test") }}'
+# apic_username: '{{ apic_username }}'
+# apic_password: '{{ apic_password }}'
+# apic_site_id: '{{ apic_site_id | default(101) }}'
+# urls:
+# - https://{{ apic_hostname }}
+# state: present
+
+- name: Remove schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Ensure tenant ansible_test exist
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ # sites:
+ # - '{{ mso_site | default("ansible_test") }}'
+ state: present
+
+- name: Ensure schema 1 with Template 1 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: Template 1
+ state: present
+
+- name: Ensure schema 1 with Template 2 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: Template 2
+ state: present
+
+- name: Ensure schema 2 with Template 3 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template 3
+ state: present
+
+- name: Ensure Filter 1 exist
+ mso_schema_template_filter_entry:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ filter: Filter1
+ entry: Filter1-Entry
+ state: present
+
+- name: Ensure Contract1 exist
+ mso_schema_template_contract_filter:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract1
+ filter: Filter1
+ filter_schema: '{{ mso_schema | default("ansible_test") }}'
+ filter_template: Template 1
+ state: present
+
+# ADD VRF1
+- name: Add a new VRF1 (check mode)
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF1
+ state: present
+ check_mode: true
+ register: vrf1_cm
+
+- name: Verify vrf1_cm
+ assert:
+ that:
+ - vrf1_cm is changed
+ - vrf1_cm.current.name == 'VRF1'
+ - vrf1_cm.current.displayName == 'VRF1'
+
+- name: Add VRF1 (normal mode)
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF1
+ state: present
+ register: vrf1_nm
+
+- name: Verify vrf1_nm
+ assert:
+ that:
+ - vrf1_nm is changed
+ - vrf1_nm.current.name == 'VRF1'
+ - vrf1_nm.current.displayName == 'VRF1'
+
+- name: Add VRF2 (normal mode)
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ vrf: VRF2
+ layer3_multicast: true
+ vzany: true
+ state: present
+ register: vrf2_nm
+
+- name: Verify vrf2_nm
+ assert:
+ that:
+ - vrf2_nm is changed
+ - vrf2_nm.current.name == 'VRF2'
+ - vrf2_nm.current.displayName == 'VRF2'
+ - vrf2_nm.current.vzAnyEnabled == True
+ - vrf2_nm.current.l3MCast == True
+
+- name: Add VRF3 (normal mode)
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ vrf: VRF3
+ state: present
+ register: vrf3_nm
+
+- name: Verify vrf3_nm
+ assert:
+ that:
+ - vrf3_nm is changed
+ - vrf3_nm.current.name == 'VRF3'
+ - vrf3_nm.current.displayName == 'VRF3'
+
+- name: Add preferred_group to VRF3 (normal mode)
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ vrf: VRF3
+ preferred_group: true
+ state: present
+ register: vrf3_nm_change
+
+- name: Verify vrf3_nm_change
+ assert:
+ that:
+ - vrf3_nm_change is changed
+ - vrf3_nm_change.current.name == 'VRF3'
+ - vrf3_nm_change.current.preferredGroup == True
+
+# ADD EXISTING VRF
+- name: Add VRF2 again (normal mode)
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ vrf: VRF2
+ layer3_multicast: true
+ vzany: true
+ state: present
+ register: vrf2_nm_again
+
+- name: Verify vrf2_nm_again
+ assert:
+ that:
+ - vrf2_nm_again is not changed
+
+# CHANGE VRF SETTINGS
+- name: Change VRF1 settings (check mode)
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF1
+ layer3_multicast: true
+ vzany: true
+ state: present
+ check_mode: true
+ register: vrf1_change_cm
+
+- name: Change VRF1 settings (normal mode)
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF1
+ layer3_multicast: true
+ vzany: true
+ state: present
+ register: vrf1_change_nm
+
+- name: Change VRF2 settings (check mode)
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ vrf: VRF2
+ layer3_multicast: false
+ vzany: false
+ preferred_group: true
+ ip_data_plane_learning: disabled
+ state: present
+ check_mode: true
+ register: vrf2_change_cm
+
+- name: Change VRF2 settings (normal mode)
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ vrf: VRF2
+ layer3_multicast: false
+ vzany: false
+ preferred_group: true
+ ip_data_plane_learning: disabled
+ state: present
+ register: vrf2_change_nm
+
+- name: Verify vrf2_nm
+ assert:
+ that:
+ - vrf1_change_cm is changed
+ - vrf1_change_nm is changed
+ - vrf2_change_cm is changed
+ - vrf2_change_nm is changed
+ - vrf1_change_cm.current.name == vrf1_change_nm.current.name == 'VRF1'
+ - vrf2_change_cm.current.name == vrf2_change_nm.current.name == 'VRF2'
+ - vrf1_change_cm.current.vzAnyEnabled == vrf1_change_nm.current.vzAnyEnabled == True
+ - vrf1_change_cm.current.vzAnyEnabled == vrf1_change_nm.current.l3MCast == True
+ - vrf2_change_cm.current.vzAnyEnabled == vrf2_change_nm.current.vzAnyEnabled == False
+ - vrf2_change_cm.current.vzAnyEnabled == vrf2_change_nm.current.l3MCast == False
+ - vrf2_change_cm.current.preferredGroup == vrf2_change_nm.current.preferredGroup == True
+ - vrf2_change_cm.current.ipDataPlaneLearning == vrf2_change_nm.current.ipDataPlaneLearning == 'disabled'
+
+# ADD VRF4 WITH NO STATE
+- name: Add VRF4 (normal mode)
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ vrf: VRF4
+ ignore_errors: true
+ register: vrf4_nm_stateless
+
+- name: Verify vrf4_nm_stateless
+ assert:
+ that:
+ - vrf4_nm_stateless is changed
+
+# QUERY A VRF
+- name: Query VRF2 (normal mode)
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ vrf: VRF2
+ state: query
+ register: vrf2_query
+
+- name: Verify vrf2_query
+ assert:
+ that:
+ - vrf2_query is not changed
+
+# QUERY ALL VRFs
+- name: Query all (normal mode)
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ state: query
+ register: vrfs_query
+
+- name: Verify vrfs_query
+ assert:
+ that:
+ - vrfs_query is not changed
+
+# REMOVE A VRF
+- name: Remove VRF3 (normal mode)
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ vrf: VRF3
+ state: absent
+ register: vrf3_remove
+
+- name: Verify vrf3_remove
+ assert:
+ that:
+ - vrf3_remove is changed
+ - vrf3_remove.previous.name == 'VRF3'
+ - vrf3_remove.previous.displayName == 'VRF3'
+
+# REMOVE A VRF
+- name: Remove VRF3 again (normal mode)
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ vrf: VRF3
+ state: absent
+ register: vrf3_remove_again
+
+- name: Verify vrf3_remove_again
+ assert:
+ that:
+ - vrf3_remove_again is not changed
+
+# QUERY REMOVED VRF
+- name: Query VRF3 (normal mode)
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ vrf: VRF3
+ state: query
+ ignore_errors: true
+ register: vrf3_query_removed
+
+- name: Verify vrf3_query_removed
+ assert:
+ that:
+ - vrf3_query_removed.msg == "VRF 'VRF3' not found"
+
+# Enable vzAny on VRF
+- name: Ensure VRF exist
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF
+ vzany: true
+ state: present
+
+- name: Add Contract1 to VRF with type consumer (normal_mode)
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF
+ contract:
+ name: Contract1
+ type: consumer
+ state: present
+ register: nm_add_contract1_consumer
+
+- name: Verify nm_add_contract1_consumer
+ assert:
+ that:
+ - nm_add_contract1_consumer is changed
+ - nm_add_contract1_consumer.previous == {}
+ - nm_add_contract1_consumer.current.contractRef.templateName == "Template1"
+ - nm_add_contract1_consumer.current.contractRef.contractName == "Contract1"
+ - nm_add_contract1_consumer.current.relationshipType == "consumer"
+
+- name: Add Contract1 to VRF with type provider (normal_mode)
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF
+ contract:
+ name: Contract1
+ type: provider
+ state: present
+ register: nm_add_contract1_provider
+
+- name: Verify nm_add_contract1_provider
+ assert:
+ that:
+ - nm_add_contract1_provider is changed
+ - nm_add_contract1_provider.previous == {}
+ - nm_add_contract1_provider.current.contractRef.templateName == "Template1"
+ - nm_add_contract1_provider.current.contractRef.contractName == "Contract1"
+ - nm_add_contract1_provider.current.relationshipType == "provider"
+
+# USE A NON-EXISTING SCHEMA
+- name: Non-existing schema for VRF (check_mode)
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: non_existing_schema
+ template: Template 1
+ vrf: VRF5
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_schema
+
+- name: Non-existing schema for VRF (normal_mode)
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: non_existing_schema
+ template: Template 1
+ vrf: VRF5
+ ignore_errors: true
+ register: nm_non_existing_schema
+
+- name: Verify nm_non_existing_schema
+ assert:
+ that:
+ - cm_non_existing_schema is not changed
+ - nm_non_existing_schema is not changed
+ - cm_non_existing_schema == nm_non_existing_schema
+ - cm_non_existing_schema.msg == nm_non_existing_schema.msg == "Provided schema 'non_existing_schema' does not exist."
+
+# USE A NON-EXISTING TEMPLATE
+- name: Non-existing template for vrf (check_mode)
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: non_existing_template
+ vrf: VRF5
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_template
+
+- name: Non-existing template for vrf (normal_mode)
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: non_existing_template
+ vrf: VRF5
+ ignore_errors: true
+ register: nm_non_existing_template
+
+- name: Verify non_existing_template
+ assert:
+ that:
+ - cm_non_existing_template is not changed
+ - nm_non_existing_template is not changed
+ - cm_non_existing_template == nm_non_existing_template
+ - cm_non_existing_template.msg == nm_non_existing_template.msg == "Provided template 'non_existing_template' does not exist. Existing templates{{':'}} Template1, Template2"
+
+# Checking if contract are removed after re-applying an VRF.
+- name: Add VRF again (normal_mode)
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF
+ vzany: true
+ state: present
+ register: nm_add_VRF_again
+
+- name: Verify that VRF didn't changed
+ assert:
+ that:
+ - nm_add_VRF_again is not changed
+
+- name: Verify contract VRF again
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF
+ state: query
+ register: nm_query_vrf_contract_again
+
+- name: Verify 2 contracts are in VRF
+ assert:
+ that:
+ - nm_query_vrf_contract_again is not changed
+ - nm_query_vrf_contract_again.current | length == 2
+
+# Checking if modifying VRF with existing contracts throw an MSO error. (#82)
+- name: Change VRF (normal_mode)
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF
+ vzany: true
+ layer3_multicast: true
+ state: present
+ register: nm_change_vrf
+
+- name: Verify that VRF did change
+ assert:
+ that:
+ - nm_change_vrf is changed
+ - nm_change_vrf.current.name == "VRF"
+ - nm_change_vrf.current.l3MCast == True
+
+- name: Verify contract VRF again
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF
+ state: query
+ register: nm_query_change_vrf_contract_again
+
+- name: Verify 2 contracts are in VRF
+ assert:
+ that:
+ - nm_query_change_vrf_contract_again is not changed
+ - nm_query_change_vrf_contract_again.current | length == 2
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_vrf_contract/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_vrf_contract/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_vrf_contract/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_vrf_contract/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_vrf_contract/tasks/main.yml
new file mode 100644
index 000000000..3a5f816a1
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_template_vrf_contract/tasks/main.yml
@@ -0,0 +1,859 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com>
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case)
+#
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+# - name: Ensure site exist
+# mso_site: &site_present
+# host: '{{ mso_hostname }}'
+# username: '{{ mso_username }}'
+# password: '{{ mso_password }}'
+# validate_certs: '{{ mso_validate_certs | default(false) }}'
+# use_ssl: '{{ mso_use_ssl | default(true) }}'
+# use_proxy: '{{ mso_use_proxy | default(true) }}'
+# output_level: '{{ mso_output_level | default("info") }}'
+# site: '{{ mso_site | default("ansible_test") }}'
+# apic_username: '{{ apic_username }}'
+# apic_password: '{{ apic_password }}'
+# apic_site_id: '{{ apic_site_id | default(101) }}'
+# urls:
+# - https://{{ apic_hostname }}
+# state: present
+
+- name: Remove schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Ensure tenant ansible_test exist
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ # sites:
+ # - '{{ mso_site | default("ansible_test") }}'
+ state: present
+
+- name: Ensure schema 1 with Template 1 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: Template 1
+ state: present
+
+- name: Ensure schema 1 with Template 2 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: Template 2
+ state: present
+
+- name: Ensure schema 2 with Template 3 exist
+ mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template 3
+ state: present
+
+- name: Ensure VRF exist
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF
+ state: present
+
+- name: Ensure VRF2 exist
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF2
+ vzany: true
+ state: present
+
+- name: Ensure VRF3 exist
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ vrf: VRF3
+ vzany: true
+ state: present
+
+- name: Ensure VRF4 exist
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ vrf: VRF4
+ vzany: true
+ state: present
+
+- name: Ensure Filter 1 exist
+ mso_schema_template_filter_entry:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ filter: Filter1
+ entry: Filter1-Entry
+ state: present
+
+- name: Ensure Contract1 exist
+ mso_schema_template_contract_filter:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract1
+ filter: Filter1
+ filter_schema: '{{ mso_schema | default("ansible_test") }}'
+ filter_template: Template 1
+ state: present
+
+- name: Ensure Contract4 exist
+ mso_schema_template_contract_filter:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract4
+ filter: Filter1
+ filter_schema: '{{ mso_schema | default("ansible_test") }}'
+ filter_template: Template 1
+ state: present
+
+- name: Ensure Contract2 exist
+ mso_schema_template_contract_filter:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract2
+ filter: Filter1
+ filter_schema: '{{ mso_schema | default("ansible_test") }}'
+ filter_template: Template 1
+ state: present
+
+- name: Ensure Contract3 exist
+ mso_schema_template_contract_filter:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ contract: Contract3
+ filter: Filter1
+ filter_schema: '{{ mso_schema | default("ansible_test") }}'
+ filter_template: Template 1
+ state: present
+
+# ADD Contract to VRF
+- name: Add Contract1 to VRF with vzany disabled
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF
+ contract:
+ name: Contract1
+ type: consumer
+ state: present
+ ignore_errors: true
+ register: add_contract1_vrf_vzany_disabled
+
+- name: Verify add_contract1_vrf_vzany_disabled
+ assert:
+ that:
+ - add_contract1_vrf_vzany_disabled.msg == "vzAny attribute on vrf 'VRF' is disabled."
+
+# Enable vzAny on VRF
+- name: Ensure VRF exist
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF
+ vzany: true
+ state: present
+
+- name: Add Contract1 to VRF with type consumer (check_mode)
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF
+ contract:
+ name: Contract1
+ type: consumer
+ state: present
+ check_mode: true
+ register: cm_add_contract1_consumer
+
+- name: Verify cm_add_contract1_consumer
+ assert:
+ that:
+ - cm_add_contract1_consumer is changed
+ - cm_add_contract1_consumer.previous == {}
+ - cm_add_contract1_consumer.current.contractRef.templateName == "Template1"
+ - cm_add_contract1_consumer.current.contractRef.contractName == "Contract1"
+ - cm_add_contract1_consumer.current.relationshipType == "consumer"
+
+- name: Add Contract1 to VRF with type consumer (normal_mode)
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF
+ contract:
+ name: Contract1
+ type: consumer
+ state: present
+ register: nm_add_contract1_consumer
+
+- name: Verify nm_add_contract1_consumer
+ assert:
+ that:
+ - nm_add_contract1_consumer is changed
+ - nm_add_contract1_consumer.previous == {}
+ - nm_add_contract1_consumer.current.contractRef.templateName == "Template1"
+ - nm_add_contract1_consumer.current.contractRef.contractName == "Contract1"
+ - nm_add_contract1_consumer.current.relationshipType == "consumer"
+ - cm_add_contract1_consumer.current.contractRef.schemaId == nm_add_contract1_consumer.current.contractRef.schemaId
+
+- name: Add Contract1 to VRF with type provider (normal_mode)
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF
+ contract:
+ name: Contract1
+ type: provider
+ state: present
+ register: nm_add_contract1_provider
+
+- name: Verify nm_add_contract1_provider
+ assert:
+ that:
+ - nm_add_contract1_provider is changed
+ - nm_add_contract1_provider.previous == {}
+ - nm_add_contract1_provider.current.contractRef.templateName == "Template1"
+ - nm_add_contract1_provider.current.contractRef.contractName == "Contract1"
+ - nm_add_contract1_provider.current.relationshipType == "provider"
+
+- name: Add Contract1 to VRF with type consumer again(normal_mode)
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF
+ contract:
+ name: Contract1
+ type: consumer
+ state: present
+ register: nm_add_contract1_consumer_again
+
+- name: Verify nm_add_contract1_consumer_again
+ assert:
+ that:
+ - nm_add_contract1_consumer_again is not changed
+ - nm_add_contract1_consumer_again.current.contractRef.templateName == "Template1" == nm_add_contract1_consumer_again.previous.contractRef.templateName
+ - nm_add_contract1_consumer_again.current.contractRef.contractName == "Contract1" == nm_add_contract1_consumer_again.previous.contractRef.contractName
+ - nm_add_contract1_consumer_again.current.relationshipType == "consumer" == nm_add_contract1_consumer_again.previous.relationshipType
+
+- name: Add Contract1 to VRF with type provider again(normal_mode)
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF
+ contract:
+ name: Contract1
+ type: provider
+ state: present
+ register: nm_add_contract1_provider_again
+
+- name: Verify nm_add_contract1_provider_again
+ assert:
+ that:
+ - nm_add_contract1_provider_again is not changed
+ - nm_add_contract1_provider_again.current.contractRef.templateName == "Template1" == nm_add_contract1_provider_again.previous.contractRef.templateName
+ - nm_add_contract1_provider_again.current.contractRef.contractName == "Contract1" == nm_add_contract1_provider_again.previous.contractRef.contractName
+ - nm_add_contract1_provider_again.current.relationshipType == "provider" == nm_add_contract1_provider_again.previous.relationshipType
+
+- name: Add Contract4 to VRF2 with type consumer (normal_mode)
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF2
+ contract:
+ name: Contract4
+ type: consumer
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ state: present
+ register: nm_add_vrf2_consumer
+
+- name: Add Contract4 to VRF2 with type provider (normal_mode)
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF2
+ contract:
+ name: Contract4
+ type: provider
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ state: present
+ register: nm_add_vrf2_provider
+
+- name: nm_add_vrf2_consumer and nm_add_vrf2_provider
+ assert:
+ that:
+ - nm_add_vrf2_consumer is changed
+ - nm_add_vrf2_provider is changed
+ - nm_add_vrf2_consumer.previous == {} == nm_add_vrf2_provider.previous
+ - nm_add_vrf2_consumer.current.contractRef.templateName == "Template1" == nm_add_vrf2_provider.current.contractRef.templateName
+ - nm_add_vrf2_consumer.current.contractRef.contractName == "Contract4" == nm_add_vrf2_provider.current.contractRef.contractName
+ - nm_add_vrf2_consumer.current.relationshipType == "consumer"
+ - nm_add_vrf2_provider.current.relationshipType == "provider"
+
+- name: Add Contract3 to VRF3 with type consumer (normal_mode)
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ vrf: VRF3
+ contract:
+ name: Contract3
+ type: consumer
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ state: present
+ register: nm_add_vrf3_consumer
+
+- name: Add Contract3 to VRF3 with type provider (normal_mode)
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ vrf: VRF3
+ contract:
+ name: Contract3
+ type: provider
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ state: present
+ register: nm_add_vrf3_provider
+
+- name: nm_add_vrf3_consumer and nm_add_vrf3_provider
+ assert:
+ that:
+ - nm_add_vrf3_consumer is changed
+ - nm_add_vrf3_provider is changed
+ - nm_add_vrf3_consumer.previous == {} == nm_add_vrf3_provider.previous
+ - nm_add_vrf3_consumer.current.contractRef.templateName == "Template1" == nm_add_vrf3_provider.current.contractRef.templateName
+ - nm_add_vrf3_consumer.current.contractRef.contractName == "Contract3" == nm_add_vrf3_provider.current.contractRef.contractName
+ - nm_add_vrf3_consumer.current.relationshipType == "consumer"
+ - nm_add_vrf3_provider.current.relationshipType == "provider"
+
+- name: Add Contract2 to VRF4 with type consumer (normal_mode)
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ vrf: VRF4
+ contract:
+ name: Contract2
+ type: consumer
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ state: present
+ register: nm_add_vrf4_consumer
+
+- name: Add Contract2 to VRF4 with type provider (normal_mode)
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 3
+ vrf: VRF4
+ contract:
+ name: Contract2
+ type: provider
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ state: present
+ register: nm_add_vrf4_provider
+
+- name: nm_add_vrf4_consumer and nm_add_vrf4_provider
+ assert:
+ that:
+ - nm_add_vrf4_consumer is changed
+ - nm_add_vrf4_provider is changed
+ - nm_add_vrf4_consumer.previous == {} == nm_add_vrf4_provider.previous
+ - nm_add_vrf4_consumer.current.contractRef.templateName == "Template1" == nm_add_vrf4_provider.current.contractRef.templateName
+ - nm_add_vrf4_consumer.current.contractRef.contractName == "Contract2" == nm_add_vrf4_provider.current.contractRef.contractName
+ - nm_add_vrf4_consumer.current.relationshipType == "consumer"
+ - nm_add_vrf4_provider.current.relationshipType == "provider"
+
+# REMOVE A Contract to VRF
+- name: Remove contract4 to VRF2 - provider (check_mode)
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF2
+ contract:
+ name: Contract4
+ type: provider
+ state: absent
+ check_mode: true
+ register: cm_remove_contract4_vrf2_provider
+
+- name: Remove contract4 to VRF2 - provider (normal_mode)
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF2
+ contract:
+ name: Contract4
+ type: provider
+ state: absent
+ register: nm_remove_contract4_vrf2_provider
+
+- name: Remove contract4 to VRF2 - consumer (normal_mode)
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF2
+ contract:
+ name: Contract4
+ type: consumer
+ state: absent
+ register: nm_remove_contract4_vrf2_consumer
+
+- name: Verify cm_remove_contract4_vrf2_provider and cm_remove_contract4_vrf2_provider and nm_remove_contract4_vrf2_consumer
+ assert:
+ that:
+ - cm_remove_contract4_vrf2_provider is changed
+ - nm_remove_contract4_vrf2_provider is changed
+ - cm_remove_contract4_vrf2_provider.current == {}
+ - nm_remove_contract4_vrf2_provider.current == {}
+ - nm_remove_contract4_vrf2_consumer.current == {}
+ - cm_remove_contract4_vrf2_provider.previous.contractRef.contractName == nm_remove_contract4_vrf2_provider.previous.contractRef.contractName == nm_remove_contract4_vrf2_consumer.previous.contractRef.contractName == "Contract4"
+ - cm_remove_contract4_vrf2_provider.previous.contractRef.templateName == nm_remove_contract4_vrf2_provider.previous.contractRef.templateName == nm_remove_contract4_vrf2_consumer.previous.contractRef.templateName == "Template1"
+ - cm_remove_contract4_vrf2_provider.previous.contractRef.schemaId == nm_remove_contract4_vrf2_provider.previous.contractRef.schemaId == nm_remove_contract4_vrf2_consumer.previous.contractRef.schemaId
+ - cm_remove_contract4_vrf2_provider.previous.relationshipType == "provider"
+ - nm_remove_contract4_vrf2_provider.previous.relationshipType == "provider"
+ - nm_remove_contract4_vrf2_consumer is changed
+ - nm_remove_contract4_vrf2_consumer.previous.relationshipType == "consumer"
+
+- name: Remove contract4 to VRF2 - provider again (normal_mode)
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF2
+ contract:
+ name: Contract4
+ type: provider
+ state: absent
+ register: nm_remove_contract4_vrf2_provider_again
+
+- name: Verify nm_remove_contract4_vrf2_provider_again
+ assert:
+ that:
+ - nm_remove_contract4_vrf2_provider_again is not changed
+ - nm_remove_contract4_vrf2_provider_again.previous == {}
+ - nm_remove_contract4_vrf2_provider_again.current == {}
+
+# QUERY A Contract to VRF
+- name: Query Contract1 relationship for VRF - consumer (check_mode)
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF
+ contract:
+ name: Contract1
+ type: consumer
+ state: query
+ check_mode: true
+ register: cm_query_VRF_contract1_consumer
+
+- name: Query Contract1 relationship for VRF - consumer (normal_mode)
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF
+ contract:
+ name: Contract1
+ type: consumer
+ state: query
+ check_mode: true
+ register: nm_query_VRF_contract1_consumer
+
+- name: Verify cm_query_VRF_contract1_consumer and nm_query_VRF_contract1_consumer
+ assert:
+ that:
+ - cm_query_VRF_contract1_consumer is not changed
+ - nm_query_VRF_contract1_consumer is not changed
+ - cm_query_VRF_contract1_consumer.current.relationshipType == nm_query_VRF_contract1_consumer.current.relationshipType == "consumer"
+ - cm_query_VRF_contract1_consumer.current.contractRef.contractName == nm_query_VRF_contract1_consumer.current.contractRef.contractName == "Contract1"
+ - cm_query_VRF_contract1_consumer.current.contractRef.schemaId == nm_query_VRF_contract1_consumer.current.contractRef.schemaId
+ - cm_query_VRF_contract1_consumer.current.contractRef.templateName == nm_query_VRF_contract1_consumer.current.contractRef.templateName == "Template1"
+
+- name: Query Contract1 relationship for VRF - provider (check_mode)
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF
+ contract:
+ name: Contract1
+ type: provider
+ state: query
+ check_mode: true
+ register: cm_query_VRF_contract1_provider
+
+- name: Query Contract1 relationship for VRF - provider (normal_mode)
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF
+ contract:
+ name: Contract1
+ type: provider
+ state: query
+ check_mode: true
+ register: nm_query_VRF_contract1_provider
+
+- name: Verify cm_query_VRF_contract1_provider and nm_query_VRF_contract1_provider
+ assert:
+ that:
+ - cm_query_VRF_contract1_provider is not changed
+ - nm_query_VRF_contract1_provider is not changed
+ - cm_query_VRF_contract1_provider.current.relationshipType == nm_query_VRF_contract1_provider.current.relationshipType == "provider"
+ - cm_query_VRF_contract1_provider.current.contractRef.contractName == nm_query_VRF_contract1_provider.current.contractRef.contractName == "Contract1"
+ - cm_query_VRF_contract1_provider.current.contractRef.schemaId == nm_query_VRF_contract1_provider.current.contractRef.schemaId
+ - cm_query_VRF_contract1_provider.current.contractRef.templateName == nm_query_VRF_contract1_provider.current.contractRef.templateName == "Template1"
+
+# QUERY ALL Contract to VRF
+- name: Query all contracts relationship for VRF (check_mode)
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF
+ state: query
+ check_mode: true
+ register: cm_query_all_contract_vrf
+
+- name: Query all contracts relationship for VRF (normal_mode)
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF
+ state: query
+ register: nm_query_all_contract_vrf
+
+- name: Verify cm_query_all_contract_vrf and nm_query_all_contract_vrf
+ assert:
+ that:
+ - nm_query_all_contract_vrf is not changed
+ - cm_query_all_contract_vrf is not changed
+ - cm_query_all_contract_vrf.current | length == nm_query_all_contract_vrf.current | length == 2
+
+# QUERY ALL Contracts to VRF2
+- name: Query all contracts relationship for VRF2 (check_mode)
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF2
+ state: query
+ check_mode: true
+ register: cm_query_all_contract_vrf2
+
+- name: Query all contracts relationship for VRF2 (normal_mode)
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF2
+ state: query
+ register: nm_query_all_contract_vrf2
+
+- name: Verify cm_query_all_contract_vrf2 and nm_query_all_contract_vrf2
+ assert:
+ that:
+ - nm_query_all_contract_vrf2 is not changed
+ - cm_query_all_contract_vrf2 is not changed
+ - cm_query_all_contract_vrf2.current == nm_query_all_contract_vrf2.current == []
+
+# QUERY NON-EXISTING Contract to VRF
+- name: Query non-existing contract (check_mode)
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF
+ contract:
+ name: non_existing_contract
+ type: provider
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_query_non_existing_contract
+
+- name: Query non-existing contract (normal_mode)
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF
+ contract:
+ name: non_existing_contract
+ type: provider
+ state: query
+ ignore_errors: true
+ register: nm_query_non_existing_contract
+
+- name: Verify cm_query_non_existing_contract and nm_query_non_existing_contract
+ assert:
+ that:
+ - cm_query_non_existing_contract is not changed
+ - nm_query_non_existing_contract is not changed
+ - cm_query_non_existing_contract == nm_query_non_existing_contract
+ - cm_query_non_existing_contract.msg == "Contract 'non_existing_contract' not found"
+ - nm_query_non_existing_contract.msg == "Contract 'non_existing_contract' not found"
+
+# QUERY NON-EXISTING VRF
+- name: Query non-existing VRF (check_mode)
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: non_existing_vrf
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_query_non_existing_vrf
+
+- name: Query non-existing VRF (normal_mode)
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: non_existing_vrf
+ state: query
+ ignore_errors: true
+ register: nm_query_non_existing_vrf
+
+- name: Verify cm_query_non_existing_vrf and nm_query_non_existing_vrf
+ assert:
+ that:
+ - cm_query_non_existing_vrf is not changed
+ - nm_query_non_existing_vrf is not changed
+ - cm_query_non_existing_vrf == nm_query_non_existing_vrf
+ - cm_query_non_existing_vrf.msg == "Provided vrf 'non_existing_vrf' does not exist. Existing vrfs{{':'}} VRF, VRF2"
+ - nm_query_non_existing_vrf.msg == "Provided vrf 'non_existing_vrf' does not exist. Existing vrfs{{':'}} VRF, VRF2"
+
+# USE A NON-EXISTING SCHEMA
+- name: Non-existing schema for contract relationship (check_mode)
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: non_existing_schema
+ template: Template 1
+ vrf: VRF
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_query_non_existing_schema
+
+- name: Non-existing schema for contract relationship (normal_mode)
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: non_existing_schema
+ template: Template 1
+ vrf: VRF
+ state: query
+ ignore_errors: true
+ register: nm_query_non_existing_schema
+
+- name: Verify cm_query_non_existing_schema and nm_query_non_existing_schema
+ assert:
+ that:
+ - cm_query_non_existing_schema is not changed
+ - nm_query_non_existing_schema is not changed
+ - cm_query_non_existing_schema.msg == "Provided schema 'non_existing_schema' does not exist."
+ - nm_query_non_existing_schema.msg == "Provided schema 'non_existing_schema' does not exist."
+
+- name: Non-existing contract schema for contract relationship (check_mode)
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF
+ contract:
+ name: Contract1
+ type: provider
+ schema: non_existing_schema
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_query_non_existing_contract_schema
+
+- name: Non-existing schema for contract relationship (normal_mode)
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF
+ contract:
+ name: Contract1
+ type: provider
+ schema: non_existing_schema
+ state: query
+ ignore_errors: true
+ register: nm_query_non_existing_contract_schema
+
+- name: Verify cm_query_non_existing_contract_schema and nm_query_non_existing_contract_schema
+ assert:
+ that:
+ - cm_query_non_existing_contract_schema is not changed
+ - nm_query_non_existing_contract_schema is not changed
+ - cm_query_non_existing_contract_schema.msg == "Provided schema 'non_existing_schema' does not exist."
+ - nm_query_non_existing_contract_schema.msg == "Provided schema 'non_existing_schema' does not exist."
+
+# USE A NON-EXISTING TEMPLATE
+- name: Non-existing templateName for contract relationship (check_mode)
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: non_existing_template
+ vrf: VRF
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_query_non_existing_template
+
+- name: Non-existing templateName for contract relationship (normal_mode)
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: non_existing_template
+ vrf: VRF
+ state: query
+ ignore_errors: true
+ register: nm_query_non_existing_template
+
+- name: Verify cm_query_non_existing_template and nm_query_non_existing_template
+ assert:
+ that:
+ - cm_query_non_existing_template is not changed
+ - nm_query_non_existing_template is not changed
+ - cm_query_non_existing_template.msg == "Provided template 'non_existing_template' does not exist. Existing templates{{':'}} Template1, Template2"
+ - nm_query_non_existing_template.msg == "Provided template 'non_existing_template' does not exist. Existing templates{{':'}} Template1, Template2"
+
+- name: Non-existing contract templateName for contract relationship (check_mode)
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF
+ contract:
+ name: Contract1
+ type: provider
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: non_existing_template
+ state: query
+ check_mode: true
+ ignore_errors: true
+ register: cm_query_non_existing_contract_template
+
+- name: Non-existing contract templateName for contract relationship (normal_mode)
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF
+ contract:
+ name: Contract1
+ type: provider
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: non_existing_template
+ state: query
+ ignore_errors: true
+ register: nm_query_non_existing_contract_template
+
+- name: Verify cm_query_non_existing_contract_template and nm_query_non_existing_contract_template
+ assert:
+ that:
+ - cm_query_non_existing_contract_template is not changed
+ - nm_query_non_existing_contract_template is not changed
+ - cm_query_non_existing_contract_template.msg == "Contract 'Contract1' not found"
+ - nm_query_non_existing_contract_template.msg == "Contract 'Contract1' not found"
+
+# Checking if contract are removed after re-applying an VRF.
+- name: Add VRF again (normal_mode)
+ mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF
+ vzany: true
+ state: present
+ register: nm_add_VRF_again
+
+- name: Verify that VRF didn't changed
+ assert:
+ that:
+ - nm_add_VRF_again is not changed
+
+- name: Verify contract VRF again
+ mso_schema_template_vrf_contract:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF
+ state: query
+ register: nm_query_vrf_contract_again
+
+- name: Verify 2 contracts are in VRF
+ assert:
+ that:
+ - nm_query_vrf_contract_again is not changed
+ - nm_query_vrf_contract_again.current | length == 2 \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_validate/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_validate/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_validate/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_validate/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_validate/tasks/main.yml
new file mode 100644
index 000000000..9de7d1fb1
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_schema_validate/tasks/main.yml
@@ -0,0 +1,251 @@
+# Test code for the MSO modules
+# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+#
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ ansible.builtin.set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ cisco.mso.mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Ensure site exist
+ cisco.mso.mso_site:
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ apic_username }}'
+ apic_password: '{{ apic_password }}'
+ apic_site_id: '{{ apic_site_id | default(101) }}'
+ urls:
+ - https://{{ apic_hostname }}
+ state: present
+
+- name: Undeploy template from Schema 1
+ cisco.mso.mso_schema_template_deploy:
+ <<: *mso_info
+ schema: ansible_test
+ template: Template 1
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: undeploy
+ ignore_errors: true
+
+- name: Undeploy template from Schema 1
+ cisco.mso.ndo_schema_template_deploy:
+ <<: *mso_info
+ schema: ansible_test
+ template: Template 1
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ state: undeploy
+ ignore_errors: true
+
+- name: Remove schemas
+ cisco.mso.mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Ensure tenant ansible_test exists
+ cisco.mso.mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ state: present
+
+# Validate schema when MSO version >= 3.3
+- name: Execute tasks only for MSO version >= 3.3
+ when: version.current.version is version('3.3', '>=')
+ block:
+ - name: Ensure schema 1 with Template 1 exist
+ cisco.mso.mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: Template 1
+ state: present
+
+ - name: Ensure VRF exist
+ cisco.mso.mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ vrf: VRF_1
+ state: present
+
+ - name: Add bd
+ cisco.mso.mso_schema_template_bd:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template1
+ bd: BD_1
+ vrf:
+ name: VRF_1
+ state: present
+
+ - name: Get Validation status
+ cisco.mso.mso_schema_validate:
+ <<: *mso_info
+ schema: ansible_test
+ state: query
+ register: query_validate
+
+ - name: Verify query_validate for NDO 4.1 and higher
+ ansible.builtin.assert:
+ that:
+ - query_validate is not changed
+ - query_validate.current.result == true
+ when: version.current.version is version('4.0', '>=')
+
+ - name: Verify query_validate
+ ansible.builtin.assert:
+ that:
+ - query_validate is not changed
+ - query_validate.current.result == "true"
+ when: version.current.version is version('4.0', '<')
+
+ - name: Add physical site to a schema
+ cisco.mso.mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ state: present
+
+ - name: Get Validation status
+ cisco.mso.mso_schema_validate:
+ <<: *mso_info
+ schema: ansible_test
+ state: query
+ register: query_validate_again
+
+ - name: Verify query_validate_again for NDO 4.1 and higher
+ ansible.builtin.assert:
+ that:
+ - query_validate_again is not changed
+ - query_validate_again.current.result == true
+ when: version.current.version is version('4.0', '>=')
+
+ - name: Verify query_validate_again for NDO 3.7 and lower
+ ansible.builtin.assert:
+ that:
+ - query_validate_again is not changed
+ - query_validate_again.current.result == "true"
+ when: version.current.version is version('4.0', '<')
+
+ - name: Deploy templates for NDO 3.7 and lower (normal_mode)
+ cisco.mso.mso_schema_template_deploy:
+ <<: *mso_info
+ schema: ansible_test
+ template: Template 1
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: deploy
+ register: nm_deploy_template_37
+ when: version.current.version is version('4.0', '<')
+
+ - name: Deploy templates for NDO 4.1 and higher (normal_mode)
+ cisco.mso.ndo_schema_template_deploy:
+ <<: *mso_info
+ schema: ansible_test
+ template: Template 1
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ state: deploy
+ register: nm_deploy_template_40
+ when: version.current.version is version('4.0', '>=')
+
+ - name: Verify nm_deploy_template for NDO 3.7 and lower
+ ansible.builtin.assert:
+ that:
+ - nm_deploy_template_37 is not changed
+ - nm_deploy_template_37.msg == "Successfully deployed"
+ when: version.current.version is version('4.0', '<')
+
+ - name: Verify nm_deploy_template for NDO 4.1 and higher
+ ansible.builtin.assert:
+ that:
+ - '"deploy" in nm_deploy_template_40.current.reqDetails'
+ when: version.current.version is version('4.0', '>=')
+
+ - name: Ensure schema 2 with Template 2 exist
+ cisco.mso.mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ tenant: ansible_test
+ template: Template 2
+ state: present
+
+ - name: Ensure VRF exist
+ cisco.mso.mso_schema_template_vrf:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}_2'
+ template: Template 2
+ vrf: VRF_2
+ layer3_multicast: true
+ vzany: true
+ state: present
+
+ - name: Get Validation status
+ cisco.mso.mso_schema_validate:
+ <<: *mso_info
+ schema: ansible_test_2
+ state: query
+ ignore_errors: true
+ register: query_validate_2
+
+ - name: Verify query_validate_2 for NDO 4.1 and higher
+ ansible.builtin.assert:
+ that:
+ - query_validate_2 is not changed
+ - query_validate_2.msg == "MSO Error 400{{':'}} VRF{{':'}} VRF_2 in Schema{{':'}} ansible_test_2 , Template{{':'}} Template2 has VzAnyEnabled flag enabled but is not consuming or providing contracts"
+ when: version.current.version is version('4.0', '>=')
+
+ - name: Verify query_validate_2 for NDO 3.7 and lower
+ ansible.builtin.assert:
+ that:
+ - query_validate_2 is not changed
+ - query_validate_2.msg == "MSO Error 400{{':'}} Bad Request{{':'}} Patch Failed, Received{{':'}} vzAny contract must be configured if vzAny flag is set. VRF(s) {{':'}} VRF_2 exception while trying to update schema"
+ when: version.current.version is version('4.0', '<')
+
+ always:
+ - name: Undeploy template from Schema 1
+ cisco.mso.mso_schema_template_deploy:
+ <<: *mso_info
+ schema: ansible_test
+ template: Template 1
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: undeploy
+ ignore_errors: true
+
+ - name: Undeploy template from Schema 1
+ cisco.mso.ndo_schema_template_deploy:
+ <<: *mso_info
+ schema: ansible_test
+ template: Template 1
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ state: undeploy
+ ignore_errors: true
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_service_node_type/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_service_node_type/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_service_node_type/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_service_node_type/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_service_node_type/tasks/main.yml
new file mode 100644
index 000000000..d6b25df94
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_service_node_type/tasks/main.yml
@@ -0,0 +1,199 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(false) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Remove existing nodes added during test
+ cisco.mso.mso_service_node_type:
+ <<: *mso_info
+ name: '{{ item }}'
+ state: absent
+ loop:
+ - TEST1
+ - TEST2
+
+- name: Add a new node type (check_mode)
+ cisco.mso.mso_service_node_type:
+ <<: *mso_info
+ name: TEST1
+ display_name: test
+ state: present
+ register: node_cm
+ check_mode: true
+
+- name: Verify node_cm
+ assert:
+ that:
+ - node_cm is changed
+ - node_cm.current.displayName == "test"
+ - node_cm.current.name == "TEST1"
+
+- name: Add a new node type (normal mode)
+ cisco.mso.mso_service_node_type:
+ <<: *mso_info
+ name: TEST1
+ display_name: test
+ state: present
+ register: node1
+
+- name: Verify node1
+ assert:
+ that:
+ - node1 is changed
+ - node1.current.displayName == "test"
+ - node1.current.name == "TEST1"
+
+- name: Add another node type
+ cisco.mso.mso_service_node_type:
+ <<: *mso_info
+ name: TEST2
+ state: present
+ register: node2
+
+- name: Verify node2
+ assert:
+ that:
+ - node2 is changed
+ - node2.current.displayName == "TEST2"
+ - node2.current.name == "TEST2"
+
+- name: Add node type TEST2 again
+ cisco.mso.mso_service_node_type:
+ <<: *mso_info
+ name: TEST2
+ state: present
+ register: node2_again
+
+- name: Verify node2_again
+ assert:
+ that:
+ - node2_again is not changed
+ - node2_again.current.displayName == "TEST2"
+ - node2_again.current.name == "TEST2"
+
+- name: Add TEST1 again with a different display name
+ cisco.mso.mso_service_node_type:
+ <<: *mso_info
+ name: TEST1
+ display_name: change_test
+ state: present
+ register: node2_different_display_name
+ ignore_errors: true
+
+- name: Verify node2_different_display_name
+ assert:
+ that:
+ - node2_different_display_name.msg == "Service Node Type 'TEST1' already exists with display name 'test' which is different from provided display name 'change_test'."
+
+- name: Query a node type
+ cisco.mso.mso_service_node_type:
+ <<: *mso_info
+ name: TEST1
+ state: query
+ register: query_node1
+
+- name: Verify query_node1
+ assert:
+ that:
+ - query_node1 is not changed
+ - query_node1.current.displayName == "test"
+ - query_node1.current.name == "TEST1"
+
+- name: Query all node types
+ cisco.mso.mso_service_node_type:
+ <<: *mso_info
+ state: query
+ register: query_all
+
+- name: Verify query_all
+ assert:
+ that:
+ - query_all is not changed
+ - query_all.current | length >= 4
+
+- name: Remove a node type (check_mode)
+ cisco.mso.mso_service_node_type:
+ <<: *mso_info
+ name: TEST1
+ state: absent
+ check_mode: true
+ register: cm_rm
+
+- name: Verify cm_rm
+ assert:
+ that:
+ - cm_rm is changed
+ - cm_rm.previous.name == "TEST1"
+
+- name: Remove a node type (normal_mode)
+ cisco.mso.mso_service_node_type:
+ <<: *mso_info
+ name: TEST1
+ state: absent
+ register: rm_node1
+
+- name: Verify rm_node1
+ assert:
+ that:
+ - rm_node1 is changed
+ - rm_node1.current == {}
+ - rm_node1.previous.name == "TEST1"
+
+- name: Query absent node type
+ cisco.mso.mso_service_node_type:
+ <<: *mso_info
+ name: TEST1
+ state: query
+ register: query_absent
+ ignore_errors: true
+
+- name: Verify query_absent
+ assert:
+ that:
+ - query_absent.msg == "Service Node Type 'TEST1' not found"
+
+- name: Remove another node type
+ cisco.mso.mso_service_node_type:
+ <<: *mso_info
+ name: TEST2
+ state: absent
+ register: rm_node2
+
+- name: Verify rm_node2
+ assert:
+ that:
+ - rm_node2 is changed
+ - rm_node2.current == {}
+ - rm_node2.previous.name == "TEST2"
+
+- name: Remove node type again
+ cisco.mso.mso_service_node_type:
+ <<: *mso_info
+ name: TEST2
+ state: absent
+ register: rm_node2_again
+
+- name: Verify rm_node2_again
+ assert:
+ that:
+ - rm_node2_again is not changed
+ - rm_node2_again.current == {}
+ - rm_node2_again.previous == {}
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_site/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_site/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_site/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_site/tasks/connectivity.j2 b/ansible_collections/cisco/mso/tests/integration/targets/mso_site/tasks/connectivity.j2
new file mode 100644
index 000000000..bb8804324
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_site/tasks/connectivity.j2
@@ -0,0 +1,489 @@
+{
+ "siteGroup": {
+ "name": "default",
+ "common": {
+ "peeringType": "full-mesh",
+ "ttl": 16,
+ "keepAliveInterval": 60,
+ "holdInterval": 180,
+ "staleInterval": 300,
+ "gracefulRestartEnabled": true,
+ "maxAsLimit": 0,
+ "externalSubnetPools": [
+ "169.254.0.0/16",
+ "10.104.0.0/16",
+ "20.253.0.0/16"
+ ]
+ },
+ "dcnm": {
+ "l2VniRange": "130000-149000",
+ "l3VniRange": "150000-159000",
+ "msiteAnycastTepPool": "10.10.0.0/24",
+ "msiteAnycastMac": "2020.0000.00aa",
+ "routeTargetPrefix": 23456,
+ "peeringType": ""
+ },
+ "apic": {
+ "gracefulRestartEnabled": false,
+ "ipns": [
+ {
+ "name": "test",
+ "ip": "2.2.2.2"
+ }
+ ],
+ "cloudsecUdpPortCtrl": false
+ },
+ "capic": {
+ "ospfAreaID": "0.0.0.0"
+ },
+ "externalDevices": {}
+ },
+ "sites": [
+ {
+ "id": "{{ site_dict.azure_ansible_test.id }}",
+ "siteId": "{{ azure_site_id }}",
+ "siteGroupId": "{{ site_dict.azure_ansible_test.site_group_id }}",
+ "siteType": "CloudApic",
+ "bgpAsn": 64701,
+ "msiteEnabled": true,
+ "health": {},
+ "apic": {
+ "srgbRange": {}
+ },
+ "dcnm": {
+ "fabricType": ""
+ },
+ "capic": {
+ "regions": [
+ {
+ "name": "westus",
+ "cloudRouters": [
+ {
+ "name": "ct_routerp_westus_1",
+ "routerType": "CSR",
+ "msiteControlPlaneTep": "10.253.254.116/28",
+ "bgpPeeringEnabled": true,
+ "routeReflectorEnabled": false
+ },
+ {
+ "name": "ct_routerp_westus_0",
+ "routerType": "CSR",
+ "bgpPeeringEnabled": true,
+ "routeReflectorEnabled": false
+ }
+ ],
+ "cApicDeployed": true
+ },
+ {
+ "name": "westus2",
+ "cloudRouters": [
+ {
+ "name": "ct_routerp_westus2_0",
+ "routerType": "CSR",
+ "bgpPeeringEnabled": true,
+ "routeReflectorEnabled": false
+ },
+ {
+ "name": "ct_routerp_westus2_1",
+ "routerType": "CSR",
+ "bgpPeeringEnabled": true,
+ "routeReflectorEnabled": false
+ }
+ ]
+ }
+ ]
+ },
+ "status": {
+ "state": "success"
+ },
+ "deployed": true
+ },
+ {
+ "id": "{{ site_dict.aws_ansible_test.id }}",
+ "siteId": "{{ aws_site_id }}",
+ "siteGroupId": "{{ site_dict.aws_ansible_test.site_group_id }}",
+ "siteType": "CloudApic",
+ "bgpAsn": 200,
+ "msiteEnabled": true,
+ "health": {},
+ "apic": {
+ "srgbRange": {}
+ },
+ "dcnm": {
+ "fabricType": ""
+ },
+ "capic": {
+ "regions": [
+ {
+ "name": "us-west-1",
+ "cloudRouters": [],
+ "cApicDeployed": true
+ },
+ {
+ "name": "us-east-1",
+ "cloudRouters": []
+ },
+ {
+ "name": "us-east-2",
+ "cloudRouters": []
+ },
+ {
+ "name": "us-west-2",
+ "cloudRouters": []
+ }
+ ]
+ },
+ "status": {
+ "state": "success"
+ },
+ "deployed": true
+ },
+ {
+ "id": "{{ site_dict.ansible_test.id }}",
+ "siteId": "{{ apic_site_id }}",
+ "siteGroupId": "{{ site_dict.ansible_test.site_group_id }}",
+ "siteType": "Apic",
+ "bgpAsn": 100,
+ "msiteEnabled": true,
+ "ospfAreaID": "0.0.0.1",
+ "ospfAreaType": "nssa",
+ "ospfPolicies": [
+ {
+ "name": "default",
+ "networkType": "broadcast",
+ "priority": 1,
+ "interfaceCost": 0,
+ "interfaceControls": [],
+ "helloInterval": 10,
+ "deadInterval": 40,
+ "retransmitInterval": 5,
+ "transmitDelay": 1
+ },
+ {
+ "name": "msc-ospf-policy-default",
+ "networkType": "point-to-point",
+ "priority": 1,
+ "interfaceCost": 0,
+ "interfaceControls": [],
+ "helloInterval": 10,
+ "deadInterval": 40,
+ "retransmitInterval": 5,
+ "transmitDelay": 1
+ },
+ {
+ "name": "common/default",
+ "networkType": "unspecified",
+ "priority": 1,
+ "interfaceCost": 0,
+ "interfaceControls": [],
+ "helloInterval": 10,
+ "deadInterval": 40,
+ "retransmitInterval": 5,
+ "transmitDelay": 1
+ }
+ ],
+ "health": {},
+ "apic": {
+ "fabricID": 1,
+ "dpMcTep": "1.1.1.2",
+ "extRoutedDom": "uni/l3dom-L3out_Dom",
+ "srgbRange": {},
+ "pods": [
+ {
+ "podId": 1,
+ "name": "pod-1",
+ "msiteDataPlaneUnicastTep": "1.1.1.1",
+ "spines": [
+ {
+ "nodeId": 201,
+ "name": "lh-dmz1-spine201",
+ "ports": [
+ {
+ "portId": "1/5",
+ "ipAddress": "10.1.0.202/12",
+ "mtu": "inherit",
+ "routingPolicy": "default",
+ "ospfAuthType": "none",
+ "ospfAuthKeyId": 1,
+ "bgpPeer": {
+ "ttl": 1,
+ "adminStateEnabled": false
+ }
+ }
+ ],
+ "bgpPeeringEnabled": true,
+ "msiteControlPlaneTep": "1.2.2.2",
+ "routeReflectorEnabled": false,
+ "health": {}
+ }
+ ],
+ "msiteDataPlaneRoutableTEPPools": [
+ {
+ "pool": "192.168.1.0/24",
+ "reserveAddressCount": 2
+ }
+ ],
+ "health": {},
+ "podFabricTepPools": [
+ {
+ "pool": "10.0.0.0/16"
+ }
+ ]
+ }
+ ]
+ },
+ "dcnm": {
+ "fabricType": ""
+ },
+ "capic": {},
+ "status": {
+ "state": "success"
+ },
+ "deployed": true
+ }
+ ],
+ "sitesUc": [
+ {
+ "id": "{{ site_dict.azure_ansible_test.id }}",
+ "siteId": "{{ azure_site_id }}",
+ "siteType": "CloudApic",
+ "apic": {},
+ "capic": {
+ "provider": "Azure",
+ "accountID": "85ca999d-c9c7-484b-82b8-6854bc1e2af5",
+ "regions": [
+ {
+ "regionName": "westus",
+ "cloudProviderID": "/subscriptions/85ca999d-c9c7-484b-82b8-6854bc1e2af5/resourceGroups/cAPIC-02/providers/Microsoft.Network/virtualNetworks/overlay-1",
+ "cidrs": [
+ "10.253.253.128/25",
+ "10.253.253.0/25",
+ "10.253.254.0/25"
+ ]
+ },
+ {
+ "regionName": "westus2",
+ "cloudProviderID": "/subscriptions/85ca999d-c9c7-484b-82b8-6854bc1e2af5/resourceGroups/CAPIC_infra_overlay-1_westus2/providers/Microsoft.Network/virtualNetworks/overlay-1",
+ "cidrs": [
+ "10.253.255.0/25",
+ "10.253.255.128/25",
+ "10.253.254.128/25"
+ ]
+ }
+ ]
+ },
+ "dcnm": {},
+ "remoteSites": [
+ {
+ "remoteType": "",
+ "id": "{{ site_dict.aws_ansible_test.id }}",
+ "siteId": "{{ aws_site_id }}",
+ "siteType": "CloudApic",
+ "connections": [
+ {
+ "priority": 0,
+ "connectionType": "Public",
+ "ipsec": true,
+ "ikev": "ikev2",
+ "protocol": "BgpEvpn",
+ "nonEvpnConfig": {},
+ "bfdConfig": {
+ "name": "default"
+ },
+ "tunnels": [
+ {
+ "ikev": "ikev2",
+ "bgpPeer": {
+ "asn": ""
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "remoteType": "",
+ "id": "{{ site_dict.ansible_test.id }}",
+ "siteId": "{{ apic_site_id }}",
+ "siteType": "Apic",
+ "connections": [
+ {
+ "priority": 0,
+ "connectionType": "Public",
+ "ipsec": true,
+ "ikev": "ikev2",
+ "protocol": "BgpEvpn",
+ "nonEvpnConfig": {},
+ "bfdConfig": {
+ "name": "default"
+ },
+ "tunnels": [
+ {
+ "ikev": "ikev2",
+ "bgpPeer": {
+ "asn": ""
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "status": {
+ "state": "success"
+ },
+ "ecStatus": {
+ "state": "success"
+ }
+ },
+ {
+ "id": "{{ site_dict.aws_ansible_test.id }}",
+ "siteId": "{{ aws_site_id }}",
+ "siteType": "CloudApic",
+ "apic": {},
+ "capic": {
+ "provider": "Aws",
+ "accountID": "787820171958",
+ "regions": [
+ {
+ "regionName": "us-west-1",
+ "cloudProviderID": "vpc-01d3ce3ef88c18087",
+ "cloudDirectoryID": "787820171958",
+ "cidrs": [
+ "10.10.0.0/25"
+ ]
+ }
+ ]
+ },
+ "dcnm": {},
+ "remoteSites": [
+ {
+ "remoteType": "",
+ "id": "{{ site_dict.azure_ansible_test.id }}",
+ "siteId": "{{ azure_site_id }}",
+ "siteType": "CloudApic",
+ "connections": [
+ {
+ "priority": 0,
+ "connectionType": "Public",
+ "ipsec": true,
+ "ikev": "ikev2",
+ "protocol": "BgpEvpn",
+ "nonEvpnConfig": {},
+ "bfdConfig": {
+ "name": "default"
+ },
+ "tunnels": [
+ {
+ "ikev": "ikev2",
+ "bgpPeer": {
+ "asn": ""
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "remoteType": "",
+ "id": "{{ site_dict.ansible_test.id }}",
+ "siteId": "{{ apic_site_id }}",
+ "siteType": "Apic",
+ "connections": [
+ {
+ "priority": 0,
+ "connectionType": "Public",
+ "ipsec": true,
+ "ikev": "ikev2",
+ "protocol": "BgpEvpn",
+ "nonEvpnConfig": {},
+ "bfdConfig": {
+ "name": "default"
+ },
+ "tunnels": [
+ {
+ "ikev": "ikev2",
+ "bgpPeer": {
+ "asn": ""
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "status": {
+ "state": "success"
+ },
+ "ecStatus": {
+ "state": "success"
+ }
+ },
+ {
+ "id": "{{ site_dict.ansible_test.id }}",
+ "siteId": "{{ apic_site_id }}",
+ "siteType": "Apic",
+ "apic": {},
+ "capic": {},
+ "dcnm": {},
+ "remoteSites": [
+ {
+ "remoteType": "",
+ "id": "{{ site_dict.azure_ansible_test.id }}",
+ "siteId": "{{ azure_site_id }}",
+ "siteType": "CloudApic",
+ "connections": [
+ {
+ "priority": 0,
+ "connectionType": "Public",
+ "ipsec": true,
+ "ikev": "ikev2",
+ "protocol": "BgpEvpn",
+ "nonEvpnConfig": {},
+ "bfdConfig": {
+ "name": "default"
+ },
+ "tunnels": [
+ {
+ "ikev": "ikev2",
+ "bgpPeer": {
+ "asn": ""
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "remoteType": "",
+ "id": "{{ site_dict.aws_ansible_test.id }}",
+ "siteId": "{{ aws_site_id }}",
+ "siteType": "CloudApic",
+ "connections": [
+ {
+ "priority": 0,
+ "connectionType": "Public",
+ "ipsec": true,
+ "ikev": "ikev2",
+ "protocol": "BgpEvpn",
+ "nonEvpnConfig": {},
+ "bfdConfig": {
+ "name": "default"
+ },
+ "tunnels": [
+ {
+ "ikev": "ikev2",
+ "bgpPeer": {
+ "asn": ""
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "status": {},
+ "ecStatus": {}
+ }
+ ]
+} \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_site/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_site/tasks/main.yml
new file mode 100644
index 000000000..9285613cb
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_site/tasks/main.yml
@@ -0,0 +1,565 @@
+# Test code for the MSO modules
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ site_dict: {}
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Undeploy a schema 1 template 1
+ mso_schema_template_deploy: &schema_undeploy
+ <<: *mso_info
+ schema: ansible_test
+ template: Template 1
+ site: '{{ item }}'
+ state: undeploy
+ ignore_errors: true
+ loop:
+ - '{{ mso_site | default("ansible_test") }}'
+ - '{{ mso_site | default("ansible_test") }}_2'
+
+- name: Undeploy a schema 1 template 2
+ mso_schema_template_deploy:
+ <<: *schema_undeploy
+ template: Template 2
+ site: '{{ item }}'
+ state: undeploy
+ ignore_errors: true
+ loop:
+ - '{{ mso_site | default("ansible_test") }}'
+ - '{{ mso_site | default("ansible_test") }}_2'
+
+- name: Undeploy a schema 2 template 3
+ mso_schema_template_deploy:
+ <<: *schema_undeploy
+ schema: ansible_test_2
+ template: Template 3
+ site: '{{ item }}'
+ state: undeploy
+ ignore_errors: true
+ loop:
+ - '{{ mso_site | default("ansible_test") }}'
+ - '{{ mso_site | default("ansible_test") }}_2'
+
+- name: Remove schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+ - 'Schema1'
+ - 'Schema2'
+
+- name: Remove tenant ansible_test
+ mso_tenant: &tenant_absent
+ <<: *mso_info
+ tenant: ansible_test
+ state: absent
+
+- name: Remove tenant ansible_test2
+ mso_tenant:
+ <<: *tenant_absent
+ tenant: ansible_test2
+ register: cm_remove_tenant
+
+- name: Remove site
+ mso_site: &site_absent
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: absent
+
+- name: Remove site 2
+ mso_site:
+ <<: *site_absent
+ site: '{{ mso_site | default("ansible_test") }}_2'
+ register: cm_remove_site
+
+
+# ADD SITE
+- name: Add site (check_mode)
+ mso_site: &site_present
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ apic_username }}'
+ apic_password: '{{ apic_password }}'
+ apic_site_id: '{{ apic_site_id | default(101) }}'
+ urls:
+ - https://{{ apic_hostname }}
+ location:
+ latitude: 50.887318
+ longitude: 4.447084
+ labels:
+ - Diegem
+ - EMEA
+ - POD51
+ state: present
+ check_mode: true
+ register: cm_add_site
+
+- name: Verify cm_add_site
+ assert:
+ that:
+ - cm_add_site is changed
+ - cm_add_site.previous == {}
+
+- name: Verify cm_add_site (MSO)
+ assert:
+ that:
+ - cm_add_site.current.id is not defined
+ - cm_add_site.current.name == mso_site|default("ansible_test")
+ when: version.current.version is version('3.2', '<')
+
+- name: Verify cm_add_site (ND)
+ assert:
+ that:
+ - cm_add_site.current.id == ""
+ - cm_add_site.current.common.name == mso_site|default("ansible_test")
+ when: version.current.version is version('3.2', '>=')
+
+- name: Add site (normal mode)
+ mso_site: *site_present
+ register: nm_add_site
+
+- name: Verify nm_add_site
+ assert:
+ that:
+ - nm_add_site is changed
+ - nm_add_site.previous == {}
+
+- name: Verify nm_add_site (MSO)
+ assert:
+ that:
+ - nm_add_site.current.id is defined
+ - nm_add_site.current.name == mso_site|default("ansible_test")
+ when: version.current.version is version('3.2', '<')
+
+- name: Verify nm_add_site (ND)
+ assert:
+ that:
+ - nm_add_site.current.common.name == mso_site|default("ansible_test")
+ when: version.current.version is version('3.2', '>=')
+
+- name: Add site again (check_mode)
+ mso_site: *site_present
+ check_mode: true
+ register: cm_add_site_again
+
+- name: Verify cm_add_site_again
+ assert:
+ that:
+ - cm_add_site_again is not changed
+ - cm_add_site_again.current.id == nm_add_site.current.id
+
+- name: Verify cm_add_site_again (MSO)
+ assert:
+ that:
+ - cm_add_site_again.previous.name == mso_site|default("ansible_test")
+ - cm_add_site_again.current.name == mso_site|default("ansible_test")
+ when: version.current.version is version('3.2', '<')
+
+- name: Verify cm_add_site_again (ND)
+ assert:
+ that:
+ - cm_add_site_again.previous.common.name == mso_site|default("ansible_test")
+ - cm_add_site_again.current.common.name == mso_site|default("ansible_test")
+ when: version.current.version is version('3.2', '>=')
+
+- name: Add site again (normal mode)
+ mso_site: *site_present
+ register: nm_add_site_again
+
+- name: Verify nm_add_site_again
+ assert:
+ that:
+ - nm_add_site_again is not changed
+ - nm_add_site_again.current.id == nm_add_site.current.id
+
+- name: Verify nm_add_site_again (MSO)
+ assert:
+ that:
+ - nm_add_site_again.previous.name == mso_site|default("ansible_test")
+ - nm_add_site_again.current.name == mso_site|default("ansible_test")
+ when: version.current.version is version('3.2', '<')
+
+- name: Verify nm_add_site_again (ND)
+ assert:
+ that:
+ - nm_add_site_again.previous.common.name == mso_site|default("ansible_test")
+ - nm_add_site_again.current.common.name == mso_site|default("ansible_test")
+ when: version.current.version is version('3.2', '>=')
+
+
+# CHANGE SITE
+- name: Change site (check_mode)
+ mso_site:
+ <<: *site_present
+ site: '{{ mso_site | default("ansible_test") }}'
+ apic_login_domain: '{{ apic_login_domain | default("test") }}'
+ location:
+ latitude: 51.887318
+ longitude: 5.447084
+ labels:
+ - Charleroi
+ - EMEA
+ check_mode: true
+ register: cm_change_site
+
+- name: Verify cm_change_site
+ assert:
+ that:
+ - cm_change_site.current.id == nm_add_site.current.id
+
+- name: Verify cm_change_site (MSO)
+ assert:
+ that:
+ - cm_change_site is changed
+ - cm_change_site.current.location.lat == 51.887318
+ - cm_change_site.current.location.long == 5.447084
+ - cm_change_site.current.labels[0] != nm_add_site.current.labels[0]
+ - cm_change_site.current.labels[1] == nm_add_site.current.labels[1]
+ - cm_change_site.current.name == mso_site|default("ansible_test")
+ when: version.current.version is version('3.2', '<')
+
+- name: Verify cm_change_site (ND)
+ assert:
+ that:
+ - cm_change_site.current.common.name == mso_site|default("ansible_test")
+ when: version.current.version is version('3.2', '>=')
+
+- name: Change site (normal mode)
+ mso_site:
+ <<: *site_present
+ site: '{{ mso_site | default("ansible_test") }}'
+ apic_login_domain: '{{ apic_login_domain | default("test") }}'
+ location:
+ latitude: 51.887318
+ longitude: 5.447084
+ labels:
+ - Charleroi
+ - EMEA
+ output_level: debug
+ register: nm_change_site
+
+- name: Verify nm_change_site
+ assert:
+ that:
+ - nm_change_site.current.id == nm_add_site.current.id
+
+- name: Verify nm_change_site (MSO)
+ assert:
+ that:
+ - nm_change_site is changed
+ - nm_change_site.current.location.lat == 51.887318
+ - nm_change_site.current.location.long == 5.447084
+ - nm_change_site.current.labels[0] != nm_add_site.current.labels[0]
+ - nm_change_site.current.labels[1] == nm_add_site.current.labels[1]
+ - nm_change_site.current.name == mso_site|default("ansible_test")
+ when: version.current.version is version('3.2', '<')
+
+- name: Verify nm_change_site (ND)
+ assert:
+ that:
+ - nm_change_site.current.common.name == mso_site|default("ansible_test")
+ when: version.current.version is version('3.2', '>=')
+
+- name: Change site again (check_mode)
+ mso_site:
+ <<: *site_present
+ site: '{{ mso_site | default("ansible_test") }}'
+ apic_login_domain: '{{ apic_login_domain | default("test") }}'
+ location:
+ latitude: 51.887318
+ longitude: 5.447084
+ labels:
+ - Charleroi
+ - EMEA
+ check_mode: true
+ register: cm_change_site_again
+
+- name: Verify cm_change_site_again
+ assert:
+ that:
+ - cm_change_site_again is not changed
+ - cm_change_site_again.current.id == nm_add_site.current.id
+
+- name: Verify cm_change_site_again (MSO)
+ assert:
+ that:
+ - cm_change_site_again.current.location.lat == 51.887318
+ - cm_change_site_again.current.location.long == 5.447084
+ - cm_change_site_again.current.labels[0] == nm_change_site.current.labels[0]
+ - cm_change_site_again.current.labels[1] == nm_change_site.current.labels[1]
+ - cm_change_site_again.current.name == mso_site|default("ansible_test")
+ when: version.current.version is version('3.2', '<')
+
+- name: Verify cm_change_site_again (ND)
+ assert:
+ that:
+ - cm_change_site_again.current.common.name == mso_site|default("ansible_test")
+ when: version.current.version is version('3.2', '>=')
+
+- name: Change site again (normal mode)
+ mso_site:
+ <<: *site_present
+ site: '{{ mso_site | default("ansible_test") }}'
+ apic_login_domain: '{{ apic_login_domain | default("test") }}'
+ location:
+ latitude: 51.887318
+ longitude: 5.447084
+ labels:
+ - Charleroi
+ - EMEA
+ output_level: debug
+ register: nm_change_site_again
+
+- name: Verify nm_change_site_again
+ assert:
+ that:
+ - nm_change_site_again is not changed
+ - nm_change_site_again.current.id == nm_add_site.current.id
+
+- name: Verify nm_change_site_again (MSO)
+ assert:
+ that:
+ - nm_change_site_again.current.location.lat == 51.887318
+ - nm_change_site_again.current.location.long == 5.447084
+ - nm_change_site_again.current.labels[0] == nm_change_site.current.labels[0]
+ - nm_change_site_again.current.labels[1] == nm_change_site.current.labels[1]
+ - nm_change_site_again.current.name == mso_site|default("ansible_test")
+ when: version.current.version is version('3.2', '<')
+
+- name: Verify nm_change_site_again (ND)
+ assert:
+ that:
+ - nm_change_site_again.current.common.name == mso_site|default("ansible_test")
+ when: version.current.version is version('3.2', '>=')
+
+# QUERY ALL SITES
+- name: Query all sites (check_mode)
+ mso_site: &site_query
+ <<: *mso_info
+ state: query
+ check_mode: true
+ register: cm_query_all_sites
+
+- name: Query all sites (normal mode)
+ mso_site: *site_query
+ register: nm_query_all_sites
+
+- name: Verify query_all_sites
+ assert:
+ that:
+ - cm_query_all_sites is not changed
+ - nm_query_all_sites is not changed
+ # NOTE: Order of sites is not stable between calls
+ #- cm_query_all_sites == nm_query_all_sites
+
+
+# QUERY A SITE
+- name: Query our site
+ mso_site:
+ <<: *site_query
+ site: '{{ mso_site | default("ansible_test") }}'
+ check_mode: true
+ register: cm_query_site
+
+- name: Query our site
+ mso_site:
+ <<: *site_query
+ site: '{{ mso_site | default("ansible_test") }}'
+ register: nm_query_site
+
+- name: Verify query_site
+ assert:
+ that:
+ - cm_query_site is not changed
+ - cm_query_site.current.id == nm_add_site.current.id
+ - nm_query_site is not changed
+ - nm_query_site.current.id == nm_add_site.current.id
+ - cm_query_site == nm_query_site
+
+- name: Verify query_site (MSO)
+ assert:
+ that:
+ - cm_query_site.current.name == mso_site|default("ansible_test")
+ - nm_query_site.current.name == mso_site|default("ansible_test")
+ when: version.current.version is version('3.2', '<')
+
+- name: Verify query_site (ND)
+ assert:
+ that:
+ - cm_query_site.current.common.name == mso_site|default("ansible_test")
+ - nm_query_site.current.common.name == mso_site|default("ansible_test")
+ when: version.current.version is version('3.2', '>=')
+
+# REMOVE SITE
+- name: Remove site (check_mode)
+ mso_site: *site_absent
+ check_mode: true
+ register: cm_remove_site
+
+- name: Verify cm_remove_site
+ assert:
+ that:
+ - cm_remove_site is changed
+ - cm_remove_site.current == {}
+
+- name: Remove site (normal mode)
+ mso_site: *site_absent
+ register: nm_remove_site
+
+- name: Verify nm_remove_site
+ assert:
+ that:
+ - nm_remove_site is changed
+ - nm_remove_site.current == {}
+
+- name: Remove site again (check_mode)
+ mso_site: *site_absent
+ check_mode: true
+ register: cm_remove_site_again
+
+- name: Verify cm_remove_site_again
+ assert:
+ that:
+ - cm_remove_site_again is not changed
+ - cm_remove_site_again.current == {}
+
+- name: Remove site again (normal mode)
+ mso_site: *site_absent
+ register: nm_remove_site_again
+
+- name: Verify nm_remove_site_again
+ assert:
+ that:
+ - nm_remove_site_again is not changed
+ - nm_remove_site_again.current == {}
+
+
+# QUERY NON-EXISTING SITE
+- name: Query non-existing site (check_mode)
+ mso_site:
+ <<: *site_query
+ site: '{{ mso_site | default("ansible_test") }}'
+ check_mode: true
+ register: cm_query_non_site
+
+- name: Query non-existing site (normal mode)
+ mso_site:
+ <<: *site_query
+ site: '{{ mso_site | default("ansible_test") }}'
+ register: nm_query_non_site
+
+# TODO: Implement more tests
+- name: Verify query_non_site
+ assert:
+ that:
+ - cm_query_non_site is not changed
+ - nm_query_non_site is not changed
+ - cm_query_non_site == nm_query_non_site
+
+# USE A NON-EXISTING STATE
+- name: Non-existing state for site (check_mode)
+ mso_site:
+ <<: *site_query
+ state: non-existing-state
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_state
+
+- name: Non-existing state for bd (normal_mode)
+ mso_site:
+ <<: *site_query
+ state: non-existing-state
+ ignore_errors: true
+ register: nm_non_existing_state
+
+- name: Verify non_existing_state
+ assert:
+ that:
+ - cm_non_existing_state is not changed
+ - nm_non_existing_state is not changed
+ - cm_non_existing_state == nm_non_existing_state
+ - cm_non_existing_state.msg == nm_non_existing_state.msg == "value of state must be one of{{':'}} absent, present, query, got{{':'}} non-existing-state"
+
+# ADD SITE
+- name: Add site (normal_mode)
+ mso_site:
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ apic_username }}'
+ apic_password: '{{ apic_password }}'
+ apic_site_id: '{{ apic_site_id | default(101) }}'
+ apic_login_domain: '{{ apic_login_domain | default("test") }}'
+ urls:
+ - https://{{ apic_hostname }}
+ state: present
+ register: nm_add_site_no_location
+
+- name: Verify nm_add_site_no_location
+ assert:
+ that:
+ - nm_add_site_no_location is changed
+ - nm_add_site_no_location.previous == {}
+ - nm_add_site_no_location.current.id is defined
+
+- name: Verify nm_add_site_no_location (MSO)
+ assert:
+ that:
+ - nm_add_site_no_location.current.name == mso_site|default("ansible_test")
+ when: version.current.version is version('3.2', '<')
+
+- name: Verify nm_add_site_no_location (ND)
+ assert:
+ that:
+ - nm_add_site_no_location.current.common.name == mso_site|default("ansible_test")
+ when: version.current.version is version('3.2', '>=')
+
+- name: Execute tasks only for MSO version >= 4.0 to reset site connectivity
+ when: version.current.version is version('4.0', '>=')
+ block:
+ - name: Query all sites (check_mode)
+ mso_site:
+ <<: *mso_info
+ state: query
+ register: sites
+
+ - name: Add sites to dict
+ set_fact:
+ site_dict: "{{ site_dict | combine( { item.common.name : { 'id' : item.id, 'site_group_id' : item.common.siteGroup } } ) }}"
+ loop: "{{ sites.current }}"
+
+ - name: Render a connectivity jinja2 template
+ set_fact:
+ site_payload: "{{ lookup('template', 'connectivity.j2') }}"
+
+ - name: Configure site connectivity
+ cisco.mso.mso_rest:
+ <<: *mso_info
+ path: /mso/api/v2/sites/fabric-connectivity
+ method: put
+ content: "{{ site_payload }}" \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_tenant/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_tenant/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_tenant/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_tenant/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_tenant/tasks/main.yml
new file mode 100644
index 000000000..e5cc5a0e6
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_tenant/tasks/main.yml
@@ -0,0 +1,758 @@
+# Test code for the MSO modules
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Ensure sites exists
+ mso_site:
+ <<: *mso_info
+ site: '{{ item.site }}'
+ apic_username: '{{ item.username }}'
+ apic_password: '{{ item.password }}'
+ apic_site_id: '{{ item.id }}'
+ urls:
+ - https://{{ item.urls }}
+ state: present
+ loop:
+ - { site: '{{ mso_site | default("ansible_test") }}', username: '{{ apic_username }}', password: '{{ apic_password }}', id: '{{ apic_site_id | default(101) }}', urls: '{{ apic_hostname }}' }
+ - { site: 'aws_{{ mso_site | default("ansible_test") }}', username: '{{ aws_apic_username }}', password: '{{ aws_apic_password }}', id: '{{ aws_site_id | default(102) }}', urls: '{{ aws_apic_hostname }}' }
+
+- name: Undeploy a schema 1 template 1
+ mso_schema_template_deploy: &schema_undeploy
+ <<: *mso_info
+ schema: ansible_test
+ template: Template 1
+ site: '{{ item }}'
+ state: undeploy
+ ignore_errors: true
+ loop:
+ - '{{ mso_site | default("ansible_test") }}'
+ - 'aws_{{ mso_site | default("ansible_test") }}'
+
+- name: Undeploy a schema 1 template 2
+ mso_schema_template_deploy:
+ <<: *schema_undeploy
+ template: Template 2
+ site: '{{ item }}'
+ state: undeploy
+ ignore_errors: true
+ loop:
+ - '{{ mso_site | default("ansible_test") }}'
+ - 'aws_{{ mso_site | default("ansible_test") }}'
+
+- name: Undeploy a schema 2 template 3
+ mso_schema_template_deploy:
+ <<: *schema_undeploy
+ schema: ansible_test_2
+ template: Template 3
+ site: '{{ item }}'
+ state: undeploy
+ ignore_errors: true
+ loop:
+ - '{{ mso_site | default("ansible_test") }}'
+ - 'aws_{{ mso_site | default("ansible_test") }}'
+
+- name: Remove schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Remove tenants
+ mso_tenant: &tenant_absent
+ <<: *mso_info
+ tenant: '{{ item }}'
+ state: absent
+ loop:
+ - ansible_test
+ - ansible_test2
+ - ansible_test3
+ - tenant_with_site
+
+# ADD TENANT
+- name: Add tenant (check_mode)
+ mso_tenant: &tenant_present
+ <<: *mso_info
+ tenant: ansible_test
+ display_name: Ansible test title
+ description: Ansible test tenant
+ state: present
+ check_mode: true
+ register: cm_add_tenant
+
+- name: Verify cm_add_tenant
+ assert:
+ that:
+ - cm_add_tenant is changed
+ - cm_add_tenant.previous == {}
+ - cm_add_tenant.current.id is not defined
+ - cm_add_tenant.current.name == 'ansible_test'
+ - cm_add_tenant.current.description == 'Ansible test tenant'
+ - cm_add_tenant.current.userAssociations | length == 1
+
+- name: Add tenant (normal mode)
+ mso_tenant: *tenant_present
+ register: nm_add_tenant
+
+- name: Verify nm_add_tenant
+ assert:
+ that:
+ - nm_add_tenant is changed
+ - nm_add_tenant.previous == {}
+ - nm_add_tenant.current.id is defined
+ - nm_add_tenant.current.name == 'ansible_test'
+ - nm_add_tenant.current.description == 'Ansible test tenant'
+ - nm_add_tenant.current.userAssociations | length == 1
+
+- name: Add tenant again (check_mode)
+ mso_tenant: *tenant_present
+ check_mode: true
+ register: cm_add_tenant_again
+
+- name: Verify cm_add_tenant_again
+ assert:
+ that:
+ - cm_add_tenant_again is not changed
+ - cm_add_tenant_again.previous.name == 'ansible_test'
+ - cm_add_tenant_again.previous.description == 'Ansible test tenant'
+ - cm_add_tenant_again.current.id == nm_add_tenant.current.id
+ - cm_add_tenant_again.current.name == 'ansible_test'
+ - cm_add_tenant_again.current.description == 'Ansible test tenant'
+ - cm_add_tenant_again.current.userAssociations == cm_add_tenant_again.previous.userAssociations
+
+- name: Add tenant again (normal mode)
+ mso_tenant: *tenant_present
+ register: nm_add_tenant_again
+
+- name: Verify nm_add_tenant_again
+ assert:
+ that:
+ - nm_add_tenant_again is not changed
+ - nm_add_tenant_again.previous.name == 'ansible_test'
+ - nm_add_tenant_again.previous.description == 'Ansible test tenant'
+ - nm_add_tenant_again.current.id == nm_add_tenant.current.id
+ - nm_add_tenant_again.current.name == 'ansible_test'
+ - nm_add_tenant_again.current.description == 'Ansible test tenant'
+ - nm_add_tenant_again.current.userAssociations == nm_add_tenant_again.previous.userAssociations
+
+# ADD TENANT WITH USERS
+- name: Add tenant 2 (normal mode)
+ mso_tenant:
+ <<: *tenant_present
+ tenant: ansible_test2
+ users:
+ - '{{ mso_username }}'
+ display_name: null
+ state: present
+ register: nm_add_tenant2
+
+- name: Verify nm_add_tenant2
+ assert:
+ that:
+ - nm_add_tenant2 is changed
+
+- name: Verify nm_add_tenant2 (when mso_username != admin)
+ assert:
+ that:
+ - nm_add_tenant2.current.userAssociations | length == 2
+ when: mso_username != 'admin'
+
+- name: Verify nm_add_tenant2 (when mso_username == admin)
+ assert:
+ that:
+ - nm_add_tenant2.current.userAssociations | length == 1
+ when: mso_username == 'admin'
+
+- name: Add tenant 2 again (normal mode)
+ mso_tenant:
+ <<: *tenant_present
+ tenant: ansible_test2
+ users:
+ - '{{ mso_username }}'
+ display_name: null
+ state: present
+ register: nm_add_tenant2_again
+
+- name: Verify nm_add_tenant2_again
+ assert:
+ that:
+ - nm_add_tenant2_again is not changed
+
+- name: Verify nm_add_tenant2_again (when mso_username != admin)
+ assert:
+ that:
+ - nm_add_tenant2_again.current.userAssociations | length == 2
+ when: mso_username != 'admin'
+
+- name: Verify nm_add_tenant2_again (when mso_username == admin)
+ assert:
+ that:
+ - nm_add_tenant2_again.current.userAssociations | length == 1
+ when: mso_username == 'admin'
+
+- name: Add tenant 3 with duplicate admin user (normal mode)
+ mso_tenant:
+ <<: *tenant_present
+ tenant: ansible_test3
+ users:
+ - admin
+ - admin
+ display_name: null
+ state: present
+ ignore_errors: true
+ register: nm_add_tenant3_with_duplicate_admin
+
+- name: Verify nm_add_tenant3_with_duplicate_admin
+ assert:
+ that:
+ - nm_add_tenant3_with_duplicate_admin is not changed
+ - nm_add_tenant3_with_duplicate_admin.msg == "User 'admin' is duplicate."
+
+- name: Add tenant 3 with invalid user (normal mode)
+ mso_tenant:
+ <<: *tenant_present
+ tenant: ansible_test3
+ users:
+ - invalid user
+ display_name: null
+ state: present
+ ignore_errors: true
+ register: nm_add_tenant3_with_invalid_user
+
+- name: nm_add_tenant3_with_invalid_user
+ assert:
+ that:
+ - nm_add_tenant3_with_invalid_user is not changed
+ - nm_add_tenant3_with_invalid_user.msg == "User 'invalid user' is not a valid user name."
+
+- name: Add tenant 3 (normal mode)
+ mso_tenant:
+ <<: *tenant_present
+ tenant: ansible_test3
+ users:
+ - '{{ mso_username }}'
+ display_name: null
+ state: present
+ register: nm_add_tenant3
+
+- name: Verify nm_add_tenant3
+ assert:
+ that:
+ - nm_add_tenant3 is changed
+
+- name: Verify nm_add_tenant3 (when mso_username != admin)
+ assert:
+ that:
+ - nm_add_tenant3.current.userAssociations | length == 2
+ when: mso_username != 'admin'
+
+- name: Verify nm_add_tenant3 (when mso_username == admin)
+ assert:
+ that:
+ - nm_add_tenant3.current.userAssociations | length == 1
+ when: mso_username == 'admin'
+
+# CHANGE TENANT
+- name: Change tenant (check_mode)
+ mso_tenant:
+ <<: *tenant_present
+ tenant: ansible_test
+ description: Ansible test tenant 2
+ check_mode: true
+ register: cm_change_tenant
+
+- name: Verify cm_change_tenant
+ assert:
+ that:
+ - cm_change_tenant is changed
+ - cm_change_tenant.current.id == nm_add_tenant.current.id
+ - cm_change_tenant.current.name == 'ansible_test'
+ - cm_change_tenant.current.description == 'Ansible test tenant 2'
+
+- name: Change tenant (normal mode)
+ mso_tenant:
+ <<: *tenant_present
+ tenant: ansible_test
+ description: Ansible test tenant 2
+ output_level: debug
+ register: nm_change_tenant
+
+- name: Verify nm_change_tenant
+ assert:
+ that:
+ - nm_change_tenant is changed
+ - nm_change_tenant.current.id == nm_add_tenant.current.id
+ - nm_change_tenant.current.name == 'ansible_test'
+ - nm_change_tenant.current.description == 'Ansible test tenant 2'
+
+- name: Change tenant again (check_mode)
+ mso_tenant:
+ <<: *tenant_present
+ tenant: ansible_test
+ description: Ansible test tenant 2
+ check_mode: true
+ register: cm_change_tenant_again
+
+- name: Verify cm_change_tenant_again
+ assert:
+ that:
+ - cm_change_tenant_again is not changed
+ - cm_change_tenant_again.current.id == nm_add_tenant.current.id
+ - cm_change_tenant_again.current.name == 'ansible_test'
+ - cm_change_tenant_again.current.description == 'Ansible test tenant 2'
+
+- name: Change tenant again (normal mode)
+ mso_tenant:
+ <<: *tenant_present
+ tenant: ansible_test
+ description: Ansible test tenant 2
+ register: nm_change_tenant_again
+
+- name: Verify nm_change_tenant_again
+ assert:
+ that:
+ - nm_change_tenant_again is not changed
+ - nm_change_tenant_again.current.id == nm_add_tenant.current.id
+ - nm_change_tenant_again.current.name == 'ansible_test'
+ - nm_change_tenant_again.current.description == 'Ansible test tenant 2'
+
+
+# QUERY ALL TENANTS
+- name: Query all tenants (check_mode)
+ mso_tenant: &tenant_query
+ <<: *mso_info
+ state: query
+ check_mode: true
+ register: cm_query_all_tenants
+
+- name: Query all tenants (normal mode)
+ mso_tenant: *tenant_query
+ register: nm_query_all_tenants
+
+- name: Verify query_all_tenants
+ assert:
+ that:
+ - cm_query_all_tenants is not changed
+ - nm_query_all_tenants is not changed
+ # NOTE: Order of tenants is not stable between calls
+ #- cm_query_all_tenants == nm_query_all_tenants
+
+
+# QUERY A TENANT
+- name: Query our tenant
+ mso_tenant:
+ <<: *tenant_query
+ tenant: ansible_test
+ check_mode: true
+ register: cm_query_tenant
+
+- name: Query our tenant
+ mso_tenant:
+ <<: *tenant_query
+ tenant: ansible_test
+ register: nm_query_tenant
+
+- name: Verify query_tenant
+ assert:
+ that:
+ - cm_query_tenant is not changed
+ - cm_query_tenant.current.id == nm_add_tenant.current.id
+ - cm_query_tenant.current.name == 'ansible_test'
+ - cm_query_tenant.current.description == 'Ansible test tenant 2'
+ - nm_query_tenant is not changed
+ - nm_query_tenant.current.id == nm_add_tenant.current.id
+ - nm_query_tenant.current.name == 'ansible_test'
+ - nm_query_tenant.current.description == 'Ansible test tenant 2'
+ - cm_query_tenant.current == nm_query_tenant.current
+
+
+# REMOVE TENANT
+- name: Remove tenant (check_mode)
+ mso_tenant:
+ <<: *tenant_absent
+ tenant: ansible_test
+ check_mode: true
+ register: cm_remove_tenant
+
+- name: Verify cm_remove_tenant
+ assert:
+ that:
+ - cm_remove_tenant is changed
+ - cm_remove_tenant.current == {}
+
+- name: Remove tenant (normal mode)
+ mso_tenant:
+ <<: *tenant_absent
+ tenant: ansible_test
+ register: nm_remove_tenant
+
+- name: Verify nm_remove_tenant
+ assert:
+ that:
+ - nm_remove_tenant is changed
+ - nm_remove_tenant.current == {}
+
+- name: Remove tenant again (check_mode)
+ mso_tenant:
+ <<: *tenant_absent
+ tenant: ansible_test
+ check_mode: true
+ register: cm_remove_tenant_again
+
+- name: Verify cm_remove_tenant_again
+ assert:
+ that:
+ - cm_remove_tenant_again is not changed
+ - cm_remove_tenant_again.current == {}
+
+- name: Remove tenant again (normal mode)
+ mso_tenant:
+ <<: *tenant_absent
+ tenant: ansible_test
+ register: nm_remove_tenant_again
+
+- name: Verify nm_remove_tenant_again
+ assert:
+ that:
+ - nm_remove_tenant_again is not changed
+ - nm_remove_tenant_again.current == {}
+
+
+# QUERY NON-EXISTING TENANT
+- name: Query non-existing tenant (check_mode)
+ mso_tenant:
+ <<: *tenant_query
+ tenant: ansible_test
+ check_mode: true
+ register: cm_query_non_tenant
+
+- name: Query non-existing tenant (normal mode)
+ mso_tenant:
+ <<: *tenant_query
+ tenant: ansible_test
+ register: nm_query_non_tenant
+
+# TODO: Implement more tests
+- name: Verify query_non_tenant
+ assert:
+ that:
+ - cm_query_non_tenant is not changed
+ - nm_query_non_tenant is not changed
+ - cm_query_non_tenant.current == nm_query_non_tenant.current
+
+- name: Add common tenant
+ mso_tenant:
+ <<: *tenant_present
+ tenant: common
+ display_name: common
+ sites: ['{{ mso_site | default("ansible_test") }}', 'aws_{{ mso_site | default("ansible_test") }}']
+ register: nm_add_common_tenant
+
+- name: Verify nm_add_common_tenant
+ assert:
+ that:
+ - nm_add_common_tenant is changed
+ - nm_add_common_tenant.current.name == "common"
+
+- name: Add tenant with site
+ mso_tenant:
+ <<: *tenant_present
+ tenant: tenant_with_site
+ display_name: tenant_with_site
+ sites: '{{ mso_site | default("ansible_test") }}'
+ register: nm_add_tenant_with_site
+
+- name: Verify nm_add_tenant_with_site
+ assert:
+ that:
+ - nm_add_tenant_with_site is changed
+ - nm_add_tenant_with_site.current.name == "tenant_with_site"
+
+- name: Remove common tenant
+ mso_tenant:
+ <<: *tenant_absent
+ tenant: common
+ ignore_errors: true
+ register: rm_common
+
+- name: Verify rm_common
+ assert:
+ that:
+ - rm_common.msg is search("Common [Tt]enant cannot be deleted")
+
+- name: Remove tenant_with_site
+ mso_tenant:
+ <<: *tenant_absent
+ tenant: tenant_with_site
+ register: rm_tenant_with_site
+
+- name: Verify rm_tenant_with_site
+ assert:
+ that:
+ - rm_tenant_with_site is changed
+ - rm_tenant_with_site.current == {}
+
+- name: Remove "anstest_imp_tenant" to the MSO if exists
+ mso_tenant:
+ <<: *mso_info
+ tenant: anstest_imp_tenant
+ display_name: anstest_imp_tenant_display_name
+ description: anstest_imp_tenant_description
+ sites: '{{ mso_site | default("ansible_test") }}'
+ state: absent
+ register: pre_test_anstest_imp_tenant_absent
+
+- name: Import "anstest_imp_tenant" to the MSO with check mode
+ mso_tenant: &cm_import_anstest_imp_tenant_present
+ <<: *mso_info
+ tenant: anstest_imp_tenant
+ display_name: anstest_imp_tenant_display_name
+ description: anstest_imp_tenant_description
+ sites: '{{ mso_site | default("ansible_test") }}'
+ state: present
+ check_mode: true
+ register: cm_import_anstest_imp_tenant_present
+
+- name: Assertions check for import "anstest_imp_tenant" to the MSO with check mode
+ assert:
+ that:
+ - cm_import_anstest_imp_tenant_present is changed
+ - cm_import_anstest_imp_tenant_present.current != {}
+ - cm_import_anstest_imp_tenant_present.previous == {}
+ - cm_import_anstest_imp_tenant_present.current.name == "anstest_imp_tenant"
+ - cm_import_anstest_imp_tenant_present.current.displayName == "anstest_imp_tenant_display_name"
+ - cm_import_anstest_imp_tenant_present.current.description == "anstest_imp_tenant_description"
+
+- name: Import "anstest_imp_tenant" to the MSO with normal mode
+ mso_tenant: &nm_import_anstest_imp_tenant_present
+ <<: *cm_import_anstest_imp_tenant_present
+ register: nm_import_anstest_imp_tenant_present
+
+- name: Assertions check for import "anstest_imp_tenant" to the MSO with normal mode
+ assert:
+ that:
+ - nm_import_anstest_imp_tenant_present is changed
+ - nm_import_anstest_imp_tenant_present.current != {}
+ - nm_import_anstest_imp_tenant_present.previous == {}
+ - nm_import_anstest_imp_tenant_present.current.name == "anstest_imp_tenant"
+ - nm_import_anstest_imp_tenant_present.current.displayName == "anstest_imp_tenant_display_name"
+ - nm_import_anstest_imp_tenant_present.current.description == "anstest_imp_tenant_description"
+
+- name: Import "anstest_imp_tenant" to the MSO with normal mode - idempotency works
+ mso_tenant:
+ <<: *nm_import_anstest_imp_tenant_present
+ register: idempotency_nm_import_anstest_imp_tenant_present
+
+- name: Idempotency assertions check for import "anstest_imp_tenant" to the MSO with normal mode
+ assert:
+ that:
+ - idempotency_nm_import_anstest_imp_tenant_present is not changed
+ - idempotency_nm_import_anstest_imp_tenant_present.current != {}
+ - idempotency_nm_import_anstest_imp_tenant_present.previous != {}
+ - idempotency_nm_import_anstest_imp_tenant_present.current.name == "anstest_imp_tenant"
+ - idempotency_nm_import_anstest_imp_tenant_present.current.displayName == "anstest_imp_tenant_display_name"
+ - idempotency_nm_import_anstest_imp_tenant_present.current.description == "anstest_imp_tenant_description"
+ - idempotency_nm_import_anstest_imp_tenant_present.previous.name == "anstest_imp_tenant"
+ - idempotency_nm_import_anstest_imp_tenant_present.previous.displayName == "anstest_imp_tenant_display_name"
+ - idempotency_nm_import_anstest_imp_tenant_present.previous.description == "anstest_imp_tenant_description"
+
+- name: Query a tenant with name "anstest_imp_tenant" when it is imported to the MSO
+ mso_tenant:
+ <<: *mso_info
+ tenant: anstest_imp_tenant
+ state: query
+ register: query_anstest_imp_tenant
+
+- name: Assertions check for query a tenant with name "anstest_imp_tenant" when it is imported to the MSO
+ assert:
+ that:
+ - query_anstest_imp_tenant is not changed
+ - query_anstest_imp_tenant.current != {}
+ - query_anstest_imp_tenant.current.name == "anstest_imp_tenant"
+ - query_anstest_imp_tenant.current.displayName == "anstest_imp_tenant_display_name"
+ - query_anstest_imp_tenant.current.description == "anstest_imp_tenant_description"
+
+- name: Remove "anstest_imp_tenant" tenant from MSO and APIC using orchestrator_only flag value yes with check mode
+ mso_tenant: &cm_anstest_imp_tenant_absent_orchestrator_only_yes
+ <<: *mso_info
+ tenant: anstest_imp_tenant
+ orchestrator_only: yes
+ state: absent
+ check_mode: true
+ register: cm_anstest_imp_tenant_absent_orchestrator_only_yes
+
+- name: Assertions check for remove "anstest_imp_tenant" tenant from MSO and APIC using orchestrator_only flag value yes with check mode
+ assert:
+ that:
+ - cm_anstest_imp_tenant_absent_orchestrator_only_yes is changed
+ - cm_anstest_imp_tenant_absent_orchestrator_only_yes.current == {}
+ - cm_anstest_imp_tenant_absent_orchestrator_only_yes.previous != {}
+ - cm_anstest_imp_tenant_absent_orchestrator_only_yes.previous.name == "anstest_imp_tenant"
+ - cm_anstest_imp_tenant_absent_orchestrator_only_yes.previous.displayName == "anstest_imp_tenant_display_name"
+ - cm_anstest_imp_tenant_absent_orchestrator_only_yes.previous.description == "anstest_imp_tenant_description"
+
+- name: Remove "anstest_imp_tenant" tenant from MSO and APIC using orchestrator_only flag value yes with normal mode
+ mso_tenant: &nm_anstest_imp_tenant_absent_orchestrator_only_yes
+ <<: *cm_anstest_imp_tenant_absent_orchestrator_only_yes
+ register: nm_anstest_imp_tenant_absent_orchestrator_only_yes
+
+- name: Assertions check for remove "anstest_imp_tenant" tenant from MSO and APIC using orchestrator_only flag value yes with normal mode
+ assert:
+ that:
+ - nm_anstest_imp_tenant_absent_orchestrator_only_yes is changed
+ - nm_anstest_imp_tenant_absent_orchestrator_only_yes.current == {}
+ - nm_anstest_imp_tenant_absent_orchestrator_only_yes.previous != {}
+ - nm_anstest_imp_tenant_absent_orchestrator_only_yes.previous.name == "anstest_imp_tenant"
+ - nm_anstest_imp_tenant_absent_orchestrator_only_yes.previous.displayName == "anstest_imp_tenant_display_name"
+ - nm_anstest_imp_tenant_absent_orchestrator_only_yes.previous.description == "anstest_imp_tenant_description"
+
+- name: Remove "anstest_imp_tenant" tenant from MSO and APIC using orchestrator_only flag value yes with normal mode - idempotency works
+ mso_tenant:
+ <<: *nm_anstest_imp_tenant_absent_orchestrator_only_yes
+ register: idempotency_nm_anstest_imp_tenant_absent_orchestrator_only_yes
+
+- name: Idempotency assertions check for remove "anstest_imp_tenant" tenant from MSO and APIC using orchestrator_only flag value yes with normal mode
+ assert:
+ that:
+ - idempotency_nm_anstest_imp_tenant_absent_orchestrator_only_yes is not changed
+ - idempotency_nm_anstest_imp_tenant_absent_orchestrator_only_yes.current == {}
+ - idempotency_nm_anstest_imp_tenant_absent_orchestrator_only_yes.previous == {}
+
+- name: Import "anstest_imp_tenant" to the MSO with normal mode once again
+ mso_tenant:
+ <<: *nm_import_anstest_imp_tenant_present
+ register: nm_import_tenant_once_again
+
+- name: Assertions check for import "anstest_imp_tenant" to the MSO with normal mode once again
+ assert:
+ that:
+ - nm_import_tenant_once_again is changed
+ - nm_import_tenant_once_again.current != {}
+ - nm_import_tenant_once_again.previous == {}
+ - nm_import_tenant_once_again.current.name == "anstest_imp_tenant"
+ - nm_import_tenant_once_again.current.displayName == "anstest_imp_tenant_display_name"
+ - nm_import_tenant_once_again.current.description == "anstest_imp_tenant_description"
+
+- name: Update "anstest_imp_tenant" tenant description with check mode
+ mso_tenant: &cm_update_anstest_imp_tenant
+ <<: *nm_import_anstest_imp_tenant_present
+ description: "updated_anstest_imp_tenant_description"
+ check_mode: true
+ register: cm_update_anstest_imp_tenant
+
+- name: Assertions check for update "anstest_imp_tenant" tenant description with check mode
+ assert:
+ that:
+ - cm_update_anstest_imp_tenant is changed
+ - cm_update_anstest_imp_tenant.current != {}
+ - cm_update_anstest_imp_tenant.previous != {}
+ - cm_update_anstest_imp_tenant.current.name == "anstest_imp_tenant"
+ - cm_update_anstest_imp_tenant.current.displayName == "anstest_imp_tenant_display_name"
+ - cm_update_anstest_imp_tenant.current.description == "updated_anstest_imp_tenant_description"
+ - cm_update_anstest_imp_tenant.previous.name == "anstest_imp_tenant"
+ - cm_update_anstest_imp_tenant.previous.displayName == "anstest_imp_tenant_display_name"
+ - cm_update_anstest_imp_tenant.previous.description == "anstest_imp_tenant_description"
+
+- name: Update "anstest_imp_tenant" tenant description with normal mode
+ mso_tenant: &nm_update_anstest_imp_tenant
+ <<: *cm_update_anstest_imp_tenant
+ register: nm_update_anstest_imp_tenant
+
+- name: Assertions check for update "anstest_imp_tenant" tenant description with normal mode
+ assert:
+ that:
+ - nm_update_anstest_imp_tenant is changed
+ - nm_update_anstest_imp_tenant.current != {}
+ - nm_update_anstest_imp_tenant.previous != {}
+ - nm_update_anstest_imp_tenant.current.name == "anstest_imp_tenant"
+ - nm_update_anstest_imp_tenant.current.displayName == "anstest_imp_tenant_display_name"
+ - nm_update_anstest_imp_tenant.current.description == "updated_anstest_imp_tenant_description"
+ - nm_update_anstest_imp_tenant.previous.name == "anstest_imp_tenant"
+ - nm_update_anstest_imp_tenant.previous.displayName == "anstest_imp_tenant_display_name"
+ - nm_update_anstest_imp_tenant.previous.description == "anstest_imp_tenant_description"
+
+- name: Update "anstest_imp_tenant" tenant description with normal mode - idempotency works
+ mso_tenant:
+ <<: *nm_update_anstest_imp_tenant
+ register: nm_idempotency_update_anstest_imp_tenant
+
+- name: Idempotency assertions check for update "anstest_imp_tenant" tenant description with normal mode
+ assert:
+ that:
+ - nm_idempotency_update_anstest_imp_tenant is not changed
+ - nm_idempotency_update_anstest_imp_tenant.current != {}
+ - nm_idempotency_update_anstest_imp_tenant.previous != {}
+ - nm_idempotency_update_anstest_imp_tenant.current.name == "anstest_imp_tenant"
+ - nm_idempotency_update_anstest_imp_tenant.current.displayName == "anstest_imp_tenant_display_name"
+ - nm_idempotency_update_anstest_imp_tenant.current.description == "updated_anstest_imp_tenant_description"
+ - nm_idempotency_update_anstest_imp_tenant.previous.name == "anstest_imp_tenant"
+ - nm_idempotency_update_anstest_imp_tenant.previous.displayName == "anstest_imp_tenant_display_name"
+ - nm_idempotency_update_anstest_imp_tenant.previous.description == "updated_anstest_imp_tenant_description"
+
+# Orchestrator Only no will remove the tenant from MSO and APIC
+- name: Remove "anstest_imp_tenant" tenant from MSO and APIC using orchestrator_only flag value no with check mode
+ mso_tenant: &cm_anstest_imp_tenant_absent_orchestrator_only_no
+ <<: *mso_info
+ tenant: anstest_imp_tenant
+ orchestrator_only: no
+ state: absent
+ check_mode: true
+ register: cm_anstest_imp_tenant_absent_orchestrator_only_no
+
+- name: Assertions check for remove "anstest_imp_tenant" tenant from MSO and APIC using orchestrator_only flag value no with check mode
+ assert:
+ that:
+ - cm_anstest_imp_tenant_absent_orchestrator_only_no is changed
+ - cm_anstest_imp_tenant_absent_orchestrator_only_no.current == {}
+ - cm_anstest_imp_tenant_absent_orchestrator_only_no.previous != {}
+ - cm_anstest_imp_tenant_absent_orchestrator_only_no.previous.name == "anstest_imp_tenant"
+ - cm_anstest_imp_tenant_absent_orchestrator_only_no.previous.displayName == "anstest_imp_tenant_display_name"
+ - cm_anstest_imp_tenant_absent_orchestrator_only_no.previous.description == "updated_anstest_imp_tenant_description"
+
+- name: Remove "anstest_imp_tenant" tenant from MSO and APIC using orchestrator_only flag value no with normal mode
+ mso_tenant: &nm_anstest_imp_tenant_absent_orchestrator_only_no
+ <<: *cm_anstest_imp_tenant_absent_orchestrator_only_no
+ register: nm_anstest_imp_tenant_absent_orchestrator_only_no
+
+- name: Assertions check for remove "anstest_imp_tenant" tenant from MSO and APIC using orchestrator_only flag value no with normal mode
+ assert:
+ that:
+ - nm_anstest_imp_tenant_absent_orchestrator_only_no is changed
+ - nm_anstest_imp_tenant_absent_orchestrator_only_no.current == {}
+ - nm_anstest_imp_tenant_absent_orchestrator_only_no.previous != {}
+ - nm_anstest_imp_tenant_absent_orchestrator_only_no.previous.name == "anstest_imp_tenant"
+ - nm_anstest_imp_tenant_absent_orchestrator_only_no.previous.displayName == "anstest_imp_tenant_display_name"
+ - nm_anstest_imp_tenant_absent_orchestrator_only_no.previous.description == "updated_anstest_imp_tenant_description"
+
+- name: Remove "anstest_imp_tenant" tenant from MSO and APIC using orchestrator_only flag value no with normal mode - idempotency works
+ mso_tenant:
+ <<: *nm_anstest_imp_tenant_absent_orchestrator_only_no
+ register: idempotency_nm_anstest_imp_tenant_absent_orchestrator_only_no
+
+- name: Idempotency assertions check for remove "anstest_imp_tenant" tenant from MSO and APIC using orchestrator_only flag value no with normal mode
+ assert:
+ that:
+ - idempotency_nm_anstest_imp_tenant_absent_orchestrator_only_no is not changed
+ - idempotency_nm_anstest_imp_tenant_absent_orchestrator_only_no.current == {}
+ - idempotency_nm_anstest_imp_tenant_absent_orchestrator_only_no.previous == {}
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_tenant_site/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_tenant_site/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_tenant_site/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_tenant_site/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_tenant_site/tasks/main.yml
new file mode 100644
index 000000000..dbe1bb9e2
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_tenant_site/tasks/main.yml
@@ -0,0 +1,679 @@
+# Test code for the MSO modules
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com> (based on mso_site test case)
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Remove schemas
+ mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Dissociate clouds that are associated with ansible_tenant
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_site | default("ansible_test") }}'
+ - 'aws_{{ mso_site | default("ansible_test") }}'
+ - 'azure_{{ mso_site | default("ansible_test") }}'
+ ignore_errors: true
+
+- name: Remove tenant ansible_test
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ state: absent
+
+- name: Ensure non-cloud site exists
+ mso_site:
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ apic_username }}'
+ apic_password: '{{ apic_password }}'
+ apic_site_id: '{{ apic_site_id }}'
+ urls:
+ - https://{{ apic_hostname }}
+ state: present
+
+- name: Ensure azure site exists
+ mso_site:
+ <<: *mso_info
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ azure_apic_username }}'
+ apic_password: '{{ azure_apic_password }}'
+ apic_site_id: '{{ azure_site_id | default(103) }}'
+ urls:
+ - https://{{ azure_apic_hostname }}
+ state: present
+
+- name: Ensure aws site exists
+ mso_site:
+ <<: *mso_info
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ aws_apic_username }}'
+ apic_password: '{{ aws_apic_password }}'
+ apic_site_id: '{{ aws_site_id | default(102) }}'
+ urls:
+ - https://{{ aws_apic_hostname }}
+ state: present
+
+- name: Ensure tenant ansible_test exists
+ mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ state: present
+
+- name: Associate non-cloud site with ansible_test in check mode
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: present
+ check_mode: true
+ register: ncs_cm
+
+- name: Verify ncs_cm
+ assert:
+ that:
+ - ncs_cm is changed
+
+- name: Associate non-cloud site with ansible_test in normal mode
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: present
+ register: ncs_nm
+
+- name: Verify ncs_nm
+ assert:
+ that:
+ - ncs_nm is changed
+
+- name: Associate non-cloud site with ansible_test again in normal mode
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: present
+ register: ncs_nm_again
+
+- name: Verify ncs_nm_again
+ assert:
+ that:
+ - ncs_nm_again is not changed
+
+- name: Associate aws site with ansible_test in check mode
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ cloud_account: "000000000000"
+ aws_trusted: false
+ aws_access_key: "1"
+ secret_key: "0"
+ state: present
+ check_mode: true
+ register: aaws_cm
+
+- name: Verify aaws_cm
+ assert:
+ that:
+ - aaws_cm is changed
+ - aaws_cm.current.awsAccount != 'null'
+ - aaws_cm.current.awsAccount[0].isAccountInOrg == false
+ - aaws_cm.current.awsAccount[0].isTrusted == false
+
+- name: Associate aws site with ansible_test in normal mode
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ cloud_account: "000000000000"
+ aws_trusted: false
+ aws_access_key: "1"
+ secret_key: "0"
+ state: present
+ register: aaws_nm
+
+- name: Verify aaws_nm
+ assert:
+ that:
+ - aaws_nm is changed
+ - aaws_nm.current.awsAccount != 'null'
+ - aaws_nm.current.awsAccount[0].isAccountInOrg == false
+ - aaws_nm.current.awsAccount[0].isTrusted == false
+
+- name: Associate aws site with ansible_test again in normal mode
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ cloud_account: "000000000000"
+ aws_trusted: false
+ aws_access_key: "1"
+ secret_key: "0"
+ state: present
+ register: aaws_nm_again
+
+- name: Verify aaws_nm_again
+ assert:
+ that:
+ - aaws_nm_again is not changed
+
+- name: Associate aws site with ansible_test in normal mode when aws_trusted is false and aws_access_key is missing
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ cloud_account: "000000000000"
+ aws_trusted: false
+ secret_key: "0"
+ state: present
+ ignore_errors: true
+ register: aaws_nm_ak
+
+- name: Verify aaws_nm_ak
+ assert:
+ that:
+ - aaws_nm_ak.msg is match ("aws_access_key is a required field in untrusted mode.")
+
+- name: Associate aws site with ansible_test in normal mode when aws_trusted is true
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ cloud_account: "000000000000"
+ aws_trusted: true
+ state: present
+ register: aws_nm_trusted
+
+- name: Verify aws_nm_trusted
+ assert:
+ that:
+ - aws_nm_trusted is changed
+
+- name: Associate aws site with ansible_test in normal mode when aws_trusted is false and secret_key is missing
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ cloud_account: "000000000000"
+ aws_trusted: false
+ aws_access_key: "1"
+ state: present
+ ignore_errors: true
+ register: aaws_nm_sk
+
+- name: Verify aaws_nm_sk
+ assert:
+ that:
+ - aaws_nm_sk.msg is match ("secret_key is a required field in untrusted mode.")
+
+- name: Associate aws site with ansible_test, with organization mode true
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ aws_account_org: true
+ cloud_account: "000000000000"
+ secret_key: "0"
+ aws_access_key: "1"
+ state: present
+ ignore_errors: true
+ register: aaws_nm_om
+
+- name: Verify aaws_nm_om
+ assert:
+ that:
+ - aaws_nm_om.current.awsAccount[0].isAccountInOrg == true
+
+- name: Associate azure site with access_type not present, with ansible_test in normal mode
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ cloud_account: uni/tn-ansible_test/act-[100]-vendor-azure
+ state: present
+ register: aazure_shared_nm
+
+- name: Verify aazure_shared_nm
+ assert:
+ that:
+ - aazure_shared_nm is changed
+
+- name: Associate azure site in shared mode with ansible_test in normal mode
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ cloud_account: uni/tn-ansible_test/act-[100]-vendor-azure
+ azure_access_type: shared
+ state: present
+ register: aazure_shared_nm
+
+- name: Verify aazure_shared_nm
+ assert:
+ that:
+ - aazure_shared_nm is not changed
+
+- name: Associate azure site with managed mode, with ansible_test in normal mode having no application_id
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ azure_subscription_id: "9"
+ cloud_account: uni/tn-ansible_test/act-[9]-vendor-azure
+ azure_access_type: managed
+ state: present
+ ignore_errors: true
+ register: aazure_managed_nm_app
+
+- name: Verify aazure_managed_nm_app
+ assert:
+ that:
+ - aazure_managed_nm_app.msg is match ("azure_application_id is required when in managed mode.")
+
+- name: Associate azure site with managed mode, with ansible_test in normal mode having no subscription_id
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ azure_application_id: "100"
+ cloud_account: uni/tn-ansible_test/act-[9]-vendor-azure
+ azure_access_type: managed
+ state: present
+ ignore_errors: true
+ register: aazure_managed_nm_si
+
+- name: Verify aazure_managed_nm_si
+ assert:
+ that:
+ - aazure_managed_nm_si.msg is match ("azure_susbscription_id is required when in managed mode.")
+
+- name: Associate azure site with managed mode, with ansible_test in normal mode
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ azure_subscription_id: "9"
+ azure_application_id: "100"
+ cloud_account: uni/tn-ansible_test/act-[9]-vendor-azure
+ azure_access_type: managed
+ state: present
+ ignore_errors: true
+ register: aazure_managed_nm
+
+- name: Verify aazure_managed_nm
+ assert:
+ that:
+ - aazure_managed_nm is changed
+ - aazure_managed_nm.current.azureAccount != 'null'
+ - aazure_managed_nm.current.azureAccount[0].cloudSubscription.cloudApplicationId == '100'
+ - aazure_managed_nm.current.azureAccount[0].cloudSubscription.cloudSubscriptionId == '9'
+ - aazure_managed_nm.current.azureAccount[0].cloudApplication == []
+ - aazure_managed_nm.current.azureAccount[0].cloudActiveDirectory == []
+
+- name: Associate azure site with credentials mode, with ansible_test in normal mode having no azure_subscription_id
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ azure_application_id: "100"
+ azure_credential_name: cApicApp
+ secret_key: iins
+ azure_active_directory_id: "32"
+ azure_active_directory_name: CiscoINSBUAd
+ cloud_account: uni/tn-ansible_test/act-[9]-vendor-azure
+ azure_access_type: unmanaged
+ state: present
+ ignore_errors: true
+ register: aazure_credentials_nm_si
+
+- name: Verify aazure_credentials_nm_si
+ assert:
+ that:
+ - aazure_credentials_nm_si.msg is match ("azure_subscription_id is required when in unmanaged mode.")
+
+- name: Associate azure site with credentials mode, with ansible_test in normal mode having no azure_application_id
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ azure_subscription_id: "9"
+ azure_credential_name: cApicApp
+ secret_key: iins
+ azure_active_directory_id: "32"
+ azure_active_directory_name: CiscoINSBUAd
+ cloud_account: uni/tn-ansible_test/act-[9]-vendor-azure
+ azure_access_type: unmanaged
+ state: present
+ ignore_errors: true
+ register: aazure_credentials_nm_app
+
+- name: Verify aazure_credentials_nm_app
+ assert:
+ that:
+ - aazure_credentials_nm_app.msg is match ("azure_application_id is required when in unmanaged mode.")
+
+- name: Associate azure site with credentials mode, with ansible_test in normal mode having no secret_key
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ azure_subscription_id: "9"
+ azure_credential_name: cApicApp
+ azure_active_directory_id: "32"
+ azure_active_directory_name: CiscoINSBUAd
+ azure_application_id: "100"
+ cloud_account: uni/tn-ansible_test/act-[9]-vendor-azure
+ azure_access_type: unmanaged
+ state: present
+ ignore_errors: true
+ register: aazure_credentials_nm_secret
+
+- name: Verify aazure_credentials_nm_secret
+ assert:
+ that:
+ - aazure_credentials_nm_secret.msg is match ("secret_key is required when in unmanaged mode.")
+
+- name: Associate azure site with credentials mode, with ansible_test in normal mode having no azure_active_directory_id
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ azure_subscription_id: "9"
+ azure_credential_name: cApicApp
+ azure_active_directory_name: CiscoINSBUAd
+ azure_application_id: "100"
+ secret_key: iins
+ cloud_account: uni/tn-ansible_test/act-[9]-vendor-azure
+ azure_access_type: unmanaged
+ state: present
+ ignore_errors: true
+ register: aazure_credentials_nm_ad
+
+- name: Verify aazure_credentials_nm_ad
+ assert:
+ that:
+ - aazure_credentials_nm_ad.msg is match ("azure_active_directory_id is required when in unmanaged mode.")
+
+- name: Associate azure site with credentials mode, with ansible_test in normal mode having no azure_active_directory_name
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ azure_subscription_id: "9"
+ azure_credential_name: cApicApp
+ secret_key: iins
+ azure_active_directory_id: "32"
+ azure_application_id: "100"
+ cloud_account: uni/tn-ansible_test/act-[9]-vendor-azure
+ azure_access_type: unmanaged
+ state: present
+ ignore_errors: true
+ register: aazure_credentials_nm_adn
+
+- name: Verify aazure_credentials_nm_adn
+ assert:
+ that:
+ - aazure_credentials_nm_adn.msg is match ("azure_active_directory_name is required when in unmanaged mode.")
+
+- name: Associate azure site with credentials mode, with ansible_test in normal mode having no azure_credential_name
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ azure_subscription_id: "9"
+ secret_key: iins
+ azure_active_directory_name: CiscoINSBUAd
+ azure_active_directory_id: "32"
+ azure_application_id: "100"
+ cloud_account: uni/tn-ansible_test/act-[9]-vendor-azure
+ azure_access_type: unmanaged
+ state: present
+ ignore_errors: true
+ register: aazure_credentials_nm_cdn
+
+- name: Verify aazure_credentials_nm_cdn
+ assert:
+ that:
+ - aazure_credentials_nm_cdn.msg is match ("azure_credential_name is required when in unmanaged mode.")
+
+- name: Associate azure site with credentials mode, with ansible_test in normal mode
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ azure_subscription_id: "9"
+ azure_application_id: "100"
+ azure_credential_name: cApicApp
+ secret_key: iins
+ azure_active_directory_id: "32"
+ azure_active_directory_name: CiscoINSBUAd
+ cloud_account: uni/tn-ansible_test/act-[9]-vendor-azure
+ azure_access_type: unmanaged
+ state: present
+ register: aazure_credentials_nm
+
+- name: Verify aazure_credentials_nm
+ assert:
+ that:
+ - aazure_credentials_nm is changed
+ - aazure_credentials_nm.current.azureAccount[0].cloudSubscription.cloudApplicationId == '100'
+ - aazure_credentials_nm.current.azureAccount[0].cloudSubscription.cloudSubscriptionId == '9'
+ - aazure_credentials_nm.current.azureAccount[0].cloudActiveDirectory[0].cloudActiveDirectoryId == '32'
+ - aazure_credentials_nm.current.azureAccount[0].cloudActiveDirectory[0].cloudActiveDirectoryName == 'CiscoINSBUAd'
+ - aazure_credentials_nm.current.azureAccount[0].cloudApplication[0].cloudApplicationId == '100'
+ - aazure_credentials_nm.current.azureAccount[0].cloudApplication[0].cloudActiveDirectoryId == '32'
+ - aazure_credentials_nm.current.azureAccount[0].cloudApplication[0].cloudCredentialName == 'cApicApp'
+
+- name: Associate azure site with credentials mode, with ansible_test again in normal mode
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ azure_subscription_id: "9"
+ azure_application_id: "100"
+ azure_credential_name: cApicApp
+ secret_key: iins
+ azure_active_directory_id: "32"
+ azure_active_directory_name: CiscoINSBUAd
+ cloud_account: uni/tn-ansible_test/act-[9]-vendor-azure
+ azure_access_type: unmanaged
+ state: present
+ register: aazure_credentials_nm_again
+
+- name: Verify aazure_credentials_nm_again
+ assert:
+ that:
+ - aazure_credentials_nm_again is not changed
+
+- name: Query associated non-cloud site of a tenant
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: query
+ register: anc_query
+
+- name: Verify anc_query
+ assert:
+ that:
+ - anc_query is not changed
+
+- name: Query associated azure site of a tenant
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ azure_subscription_id: "9"
+ azure_application_id: "100"
+ azure_credential_name: cApicApp
+ secret_key: iins
+ azure_active_directory_id: "32"
+ azure_active_directory_name: CiscoINSBUAd
+ cloud_account: uni/tn-ansible_test/act-[9]-vendor-azure
+ azure_access_type: unmanaged
+ state: query
+ register: aazure_query
+
+- name: Verify aazure_query
+ assert:
+ that:
+ - aazure_query is not changed
+
+- name: Query associated aws site of a tenant
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ state: query
+ register: aaws_query
+
+- name: Verify aaws_query
+ assert:
+ that:
+ - aaws_query is not changed
+
+- name: Query all associated sites of a tenant
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ state: query
+ register: all_sites_query
+
+- name: Verify all_sites_query
+ assert:
+ that:
+ - all_sites_query is not changed
+
+- name: Dissociate non-cloud site with ansible_test
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: absent
+ register: dnc
+
+- name: Verify dnc
+ assert:
+ that:
+ - dnc is changed
+
+- name: Query dissociated non-cloud site of a tenant
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: '{{ mso_site | default("ansible_test") }}'
+ state: query
+ ignore_errors: true
+ register: dnc_query
+
+- name: Verify dnc_query
+ assert:
+ that:
+ - dnc_query.msg is match ("Site Id [0-9a-zA-Z]* not associated with tenant Id [0-9a-zA-Z]*")
+
+- name: Dissociate azure site with ansible_test
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ state: absent
+ register: dazure
+
+- name: Verify dazure
+ assert:
+ that:
+ - dazure is changed
+
+- name: Query dissociated azure site of a tenant
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ state: query
+ ignore_errors: true
+ register: dazure_query
+
+- name: Verify dnc_query
+ assert:
+ that:
+ - dazure_query.msg is match ("Site Id [0-9a-zA-Z]* not associated with tenant Id [0-9a-zA-Z]*")
+
+- name: Dissociate aws site with ansible_test
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ state: absent
+ register: daaws
+
+- name: Verify daaws
+ assert:
+ that:
+ - daaws is changed
+
+- name: Query dissociated aws site of a tenant
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ state: query
+ ignore_errors: true
+ register: daaws_query
+
+- name: Verify daaws_query
+ assert:
+ that:
+ - daaws_query.msg is match ("No site associated with tenant Id [0-9a-zA-Z]*")
+
+- name: Query all
+ mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ state: query
+ ignore_errors: true
+ register: query_all
+
+- name: Verify query_all
+ assert:
+ that:
+ - query_all.msg is match ("No site associated with tenant Id [0-9a-zA-Z]*") \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_user/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_user/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_user/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_user/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_user/tasks/main.yml
new file mode 100644
index 000000000..2aa2d2bf6
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_user/tasks/main.yml
@@ -0,0 +1,511 @@
+# Test code for the MSO modules
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Execute tasks only for MSO version < 3.2
+ when: version.current.version is version('3.2', '<')
+ block:
+ # CLEAN ENVIRONMENT
+ - name: Remove user ansible_test
+ mso_user: &user_absent
+ <<: *mso_info
+ user: '{{ item }}'
+ state: absent
+ loop:
+ - ansible_test
+ - ansible_test2
+ - ansible_test_read
+ - ansible_test_read_2
+
+ # ADD USER
+ - name: Add user (check_mode)
+ mso_user: &user_present
+ <<: *mso_info
+ user: ansible_test
+ user_password: 'S0m3!1n1t14l!p455w0rd'
+ # NOTE: First name, last name, phone and email are mandatory on creation
+ first_name: Ansible
+ last_name: Test
+ email: mso@cisco.com
+ phone: +32 478 436 299
+ account_status: active
+ roles:
+ - name: powerUser
+ access_type: write
+ domain: Local
+ state: present
+ check_mode: true
+ register: cm_add_user
+
+ - name: Verify cm_add_user
+ assert:
+ that:
+ - cm_add_user is changed
+ - cm_add_user.previous == {}
+ - cm_add_user.current.id is not defined
+ - cm_add_user.current.username == 'ansible_test'
+ - cm_add_user.current.lastName == 'Test'
+ - cm_add_user.current.firstName == 'Ansible'
+ - cm_add_user.current.emailAddress == 'mso@cisco.com'
+ - cm_add_user.current.phoneNumber == '+32 478 436 299'
+ - cm_add_user.current.accountStatus == 'active'
+ - cm_add_user.current.roles[0].accessType == 'readWrite'
+
+ - name: Add user (normal mode)
+ mso_user: *user_present
+ register: nm_add_user
+
+ - name: Verify nm_add_user
+ assert:
+ that:
+ - nm_add_user is changed
+ - nm_add_user.previous == {}
+ - nm_add_user.current.id is defined
+ - nm_add_user.current.username == 'ansible_test'
+ - nm_add_user.current.lastName == 'Test'
+ - nm_add_user.current.firstName == 'Ansible'
+ - nm_add_user.current.emailAddress == 'mso@cisco.com'
+ - nm_add_user.current.phoneNumber == '+32 478 436 299'
+ - nm_add_user.current.accountStatus == 'active'
+ - nm_add_user.current.roles[0].accessType == 'readWrite'
+
+ - name: Add user again (check_mode)
+ mso_user:
+ <<: *user_present
+ # NOTE: We need to modify the password for a new user
+ user_password: 'S0m3!n3w!p455w0rd'
+ check_mode: true
+ register: cm_add_user_again
+
+ - name: Verify cm_add_user_again
+ assert:
+ that:
+ - cm_add_user_again is changed
+ - cm_add_user_again.previous.username == 'ansible_test'
+ - cm_add_user_again.current.id == nm_add_user.current.id
+ - cm_add_user_again.current.username == 'ansible_test'
+
+ - name: Add user again (normal mode)
+ mso_user:
+ <<: *user_present
+ # NOTE: We need to modify the password for a new user
+ user_password: 'S0m3!n3w!p455w0rd'
+ register: nm_add_user_again
+
+ - name: Verify nm_add_user_again
+ assert:
+ that:
+ - nm_add_user_again is changed
+ - nm_add_user_again.previous.username == 'ansible_test'
+ - nm_add_user_again.current.id == nm_add_user.current.id
+ - nm_add_user_again.current.username == 'ansible_test'
+
+ - name: Add user with read only role (check_mode)
+ mso_user: &user_present2
+ <<: *mso_info
+ user: ansible_test_read
+ user_password: '#123455#123455Aa'
+ # NOTE: First name, last name, phone and email are mandatory on creation
+ first_name: Ansible2
+ last_name: Test2
+ email: mso3@cisco.com
+ phone: +32 478 436 299
+ account_status: active
+ roles:
+ - name: powerUser
+ access_type: read
+ domain: Local
+ state: present
+ check_mode: true
+ register: cm_add_user2
+
+ - name: Verify cm_add_user2
+ assert:
+ that:
+ - cm_add_user2 is changed
+ - cm_add_user2.previous == {}
+ - cm_add_user2.current.id is not defined
+ - cm_add_user2.current.username == 'ansible_test_read'
+ - cm_add_user2.current.lastName == 'Test2'
+ - cm_add_user2.current.firstName == 'Ansible2'
+ - cm_add_user2.current.emailAddress == 'mso3@cisco.com'
+ - cm_add_user2.current.phoneNumber == '+32 478 436 299'
+ - cm_add_user2.current.accountStatus == 'active'
+ - cm_add_user2.current.roles[0].accessType == 'readOnly'
+
+ - name: Add user with read only role (normal mode)
+ mso_user: *user_present2
+ register: nm_add_user2
+
+ - name: Verify nm_add_user2
+ assert:
+ that:
+ - nm_add_user2 is changed
+ - nm_add_user2.current.id is defined
+ - nm_add_user2.current.username == 'ansible_test_read'
+ - nm_add_user2.current.lastName == 'Test2'
+ - nm_add_user2.current.firstName == 'Ansible2'
+ - nm_add_user2.current.emailAddress == 'mso3@cisco.com'
+ - nm_add_user2.current.phoneNumber == '+32 478 436 299'
+ - nm_add_user2.current.accountStatus == 'active'
+ - nm_add_user2.current.roles[0].accessType == 'readOnly'
+
+ - name: Add user with read only role again (check mode)
+ mso_user:
+ <<: *user_present2
+ user_password: '#123455#123455Aa'
+ check_mode: true
+ register: cm_add_user2_again
+
+ - name: Add user with read only role again (normal mode)
+ mso_user: *user_present2
+ register: nm_add_user2
+
+ - name: Add user3 with read only role and no password (check_mode)
+ mso_user: &user_present3
+ <<: *mso_info
+ user: ansible_test_read_2
+ # NOTE: First name, last name, phone and email are mandatory on creation
+ first_name: Ansible3
+ #user_password: '#123455#123455Aa'
+ last_name: Test3
+ email: mso4@cisco.com
+ phone: +32 478 436 299
+ account_status: active
+ roles:
+ - name: powerUser
+ access_type: read
+ domain: Local
+ state: present
+ ignore_errors: true
+ register: nm_add_user3
+
+ - name: Verify nm_add_user2
+ assert:
+ that:
+ - nm_add_user3.msg == "The user ansible_test_read_2 does not exist. The 'user_password' attribute is required to create a new user."
+
+ - name: Add user3 with read only role and with password (normal mode)
+ mso_user:
+ <<: *user_present3
+ user_password: '#123455#123455Aa'
+ register: nm_add_user3_again
+
+ - name: Verify nm_add_user3_again
+ assert:
+ that:
+ - nm_add_user3_again is changed
+ - nm_add_user3_again.current.id is defined
+ - nm_add_user3_again.current.username == 'ansible_test_read_2'
+ - nm_add_user3_again.current.lastName == 'Test3'
+ - nm_add_user3_again.current.firstName == 'Ansible3'
+ - nm_add_user3_again.current.emailAddress == 'mso4@cisco.com'
+ - nm_add_user3_again.current.phoneNumber == '+32 478 436 299'
+ - nm_add_user3_again.current.accountStatus == 'active'
+ - nm_add_user3_again.current.roles[0].accessType == 'readOnly'
+
+ # CHANGE USER
+ - name: Change user (check_mode)
+ mso_user: &user_change
+ <<: *mso_info
+ user: ansible_test
+ roles:
+ - name: powerUser
+ access_type: write
+ domain: Local
+ state: present
+ # FIXME: Add support for name change
+ email: mso2@cisco.com
+ phone: +32 478 436 300
+ check_mode: true
+ register: cm_change_user
+
+ - name: Verify cm_change_user
+ assert:
+ that:
+ - cm_change_user is changed
+ - cm_change_user.current.id == nm_add_user.current.id
+ - cm_change_user.current.username == 'ansible_test'
+ - cm_change_user.current.emailAddress == 'mso2@cisco.com'
+ - cm_change_user.current.phoneNumber == '+32 478 436 300'
+
+ - name: Change user (normal mode)
+ mso_user:
+ <<: *user_change
+ output_level: debug
+ register: nm_change_user
+
+ - name: Verify nm_change_user
+ assert:
+ that:
+ - nm_change_user is changed
+ - nm_change_user.current.id == nm_add_user.current.id
+ - nm_change_user.current.username == 'ansible_test'
+ - nm_change_user.current.emailAddress == 'mso2@cisco.com'
+ - nm_change_user.current.phoneNumber == '+32 478 436 300'
+
+ - name: Change user again (check_mode)
+ mso_user:
+ <<: *user_change
+ check_mode: true
+ register: cm_change_user_again
+
+ - name: Verify cm_change_user_again
+ assert:
+ that:
+ - cm_change_user_again is not changed
+ - cm_change_user_again.current.id == nm_add_user.current.id
+ - cm_change_user_again.current.username == 'ansible_test'
+ - cm_change_user_again.current.emailAddress == 'mso2@cisco.com'
+ - cm_change_user_again.current.phoneNumber == '+32 478 436 300'
+
+ - name: Change user again (normal mode)
+ mso_user:
+ <<: *user_change
+ register: nm_change_user_again
+
+ - name: Verify nm_change_user_again
+ assert:
+ that:
+ - nm_change_user_again is not changed
+ - nm_change_user_again.current.id == nm_add_user.current.id
+ - nm_change_user_again.current.username == 'ansible_test'
+ - nm_change_user_again.current.emailAddress == 'mso2@cisco.com'
+ - nm_change_user_again.current.phoneNumber == '+32 478 436 300'
+
+ - name: Add second user
+ mso_user:
+ <<: *user_change
+ user: ansible_test2
+ user_password: 'S0m3!1n1t14l!p455w0rd'
+ first_name: Ansible
+ last_name: Test
+ roles:
+ - powerUser
+ state: present
+ register: nm_add_user_2
+
+ - name: Change user 2 again (normal mode)
+ mso_user:
+ <<: *user_change
+ user: ansible_test2
+ user_password: null
+ first_name: Ansible
+ last_name: Test
+ register: nm_change_user_2_again
+
+ - name: Verify nm_change_user_2_again
+ assert:
+ that:
+ - nm_change_user_2_again is not changed
+ - nm_change_user_2_again.current.id == nm_add_user_2.current.id
+ - nm_change_user_2_again.current.username == 'ansible_test2'
+
+ # TODO: Add query with user ansible_test2 to try if user can login.
+
+# QUERY ALL USERS
+- name: Query all users (check_mode)
+ mso_user: &user_query
+ <<: *mso_info
+ state: query
+ check_mode: true
+ register: cm_query_all_users
+
+- name: Query all users (normal mode)
+ mso_user: *user_query
+ register: nm_query_all_users
+
+- name: Verify query_all_users
+ assert:
+ that:
+ - cm_query_all_users is not changed
+ - nm_query_all_users is not changed
+ # NOTE: Order of users is not stable between calls
+ #- cm_query_all_users == nm_query_all_users
+
+
+# QUERY A USER
+- name: Query our user
+ mso_user:
+ <<: *user_query
+ user: '{{ mso_username }}'
+ check_mode: true
+ register: cm_query_user
+
+- name: Query our user
+ mso_user:
+ <<: *user_query
+ user: '{{ mso_username }}'
+ register: nm_query_user
+
+- name: Verify query_user
+ assert:
+ that:
+ - cm_query_user is not changed
+ - cm_query_user.current.id is defined
+ - cm_query_user.current.username == '{{ mso_username }}'
+ - nm_query_user is not changed
+ - nm_query_user.current.id is defined
+ - nm_query_user.current.username == '{{ mso_username }}'
+ - cm_query_user == nm_query_user
+
+- name: Execute tasks only for MSO version < 3.2
+ when: version.current.version is version('3.2', '<')
+ block:
+ - name: Query our read-only user
+ mso_user:
+ <<: *user_query
+ user: ansible_test_read
+ register: nm_query_user2
+
+ - name: Verify query_user2
+ assert:
+ that:
+ - nm_query_user2 is not changed
+ - nm_query_user2.current.roles[0].accessType == 'readOnly'
+
+ # REMOVE USER
+ - name: Remove user (check_mode)
+ mso_user:
+ <<: *user_absent
+ user: ansible_test
+ state: absent
+ check_mode: true
+ register: cm_remove_user
+
+ - name: Verify cm_remove_user
+ assert:
+ that:
+ - cm_remove_user is changed
+ - cm_remove_user.current == {}
+
+ - name: Remove user (normal mode)
+ mso_user:
+ <<: *user_absent
+ user: ansible_test
+ state: absent
+ register: nm_remove_user
+
+ - name: Verify nm_remove_user
+ assert:
+ that:
+ - nm_remove_user is changed
+ - nm_remove_user.current == {}
+
+ - name: Remove user again (check_mode)
+ mso_user:
+ <<: *user_absent
+ user: ansible_test
+ state: absent
+ check_mode: true
+ register: cm_remove_user_again
+
+ - name: Verify cm_remove_user_again
+ assert:
+ that:
+ - cm_remove_user_again is not changed
+ - cm_remove_user_again.current == {}
+
+ - name: Remove user again (normal mode)
+ mso_user:
+ <<: *user_absent
+ user: ansible_test
+ state: absent
+ register: nm_remove_user_again
+
+ - name: Verify nm_remove_user_again
+ assert:
+ that:
+ - nm_remove_user_again is not changed
+ - nm_remove_user_again.current == {}
+
+# QUERY NON-EXISTING USER
+- name: Query non-existing user (check_mode)
+ mso_user:
+ <<: *user_query
+ user: ansible_test
+ check_mode: true
+ register: cm_query_non_user
+
+- name: Query non-existing user (normal mode)
+ mso_user:
+ <<: *user_query
+ user: ansible_test
+ register: nm_query_non_user
+
+# TODO: Implement more tests
+- name: Verify query_non_user
+ assert:
+ that:
+ - cm_query_non_user is not changed
+ - nm_query_non_user is not changed
+ - cm_query_non_user == nm_query_non_user
+
+- name: Execute tasks only for MSO version < 3.2
+ when: version.current.version is version('3.2', '<')
+ block:
+ - name: inactive user (check_mode)
+ mso_user:
+ <<: *user_present
+ account_status: inactive
+ check_mode: true
+ register: cm_inactive_user
+
+ - name: inactive user (normal_mode)
+ mso_user:
+ <<: *user_present
+ account_status: inactive
+ register: nm_inactive_user
+
+ - name: Verify cm_inactive_user and nm_inactive_user
+ assert:
+ that:
+ - cm_inactive_user is changed
+ - nm_inactive_user is changed
+ - cm_inactive_user.current.accountStatus == "inactive"
+ - nm_inactive_user.current.accountStatus == "inactive"
+
+
+ - name: active user (check_mode)
+ mso_user:
+ <<: *user_present
+ account_status: active
+ check_mode: true
+ register: cm_active_user
+
+ - name: active user (normal_mode)
+ mso_user:
+ <<: *user_present
+ account_status: active
+ register: nm_active_user
+
+ - name: Verify cm_active_user and nm_active_user
+ assert:
+ that:
+ - cm_active_user is changed
+ - nm_active_user is changed
+ - cm_active_user.previous.accountStatus == nm_active_user.previous.accountStatus == "inactive"
+ - cm_active_user.current.accountStatus == nm_active_user.current.accountStatus == "active" \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_version/aliases b/ansible_collections/cisco/mso/tests/integration/targets/mso_version/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_version/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/mso_version/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/mso_version/tasks/main.yml
new file mode 100644
index 000000000..4ee73eb90
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/mso_version/tasks/main.yml
@@ -0,0 +1,96 @@
+# Test code for the MSO modules
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+# QUERY VERSION
+- name: Query MSO version
+ mso_version: &mso_query
+ <<: *mso_info
+ state: query
+ check_mode: true
+ register: cm_query_version
+
+- name: Verify cm_query_version
+ assert:
+ that:
+ - cm_query_version is not changed
+ - cm_query_version.current.id is defined
+ - cm_query_version.current.version is defined
+ - cm_query_version.current.timestamp is defined
+
+- name: Query MSO version (normal mode)
+ mso_version:
+ <<: *mso_query
+ register: nm_query_version
+
+- name: Verify nm_query_version
+ assert:
+ that:
+ - nm_query_version is not changed
+ - nm_query_version.current.id is defined
+ - nm_query_version.current.version is defined
+ - nm_query_version.current.timestamp is defined
+ - nm_query_version.current.id == cm_query_version.current.id
+ - nm_query_version.current.version == cm_query_version.current.version
+ - nm_query_version.current.timestamp == cm_query_version.current.timestamp
+
+# USE A NON-EXISTING STATE
+- name: Non-existing state for version (check_mode)
+ mso_version:
+ <<: *mso_query
+ state: non-existing-state
+ check_mode: true
+ ignore_errors: true
+ register: cm_non_existing_state
+
+- name: Non-existing state for version (normal_mode)
+ mso_version:
+ <<: *mso_query
+ state: non-existing-state
+ ignore_errors: true
+ register: nm_non_existing_state
+
+- name: Verify non_existing_state
+ assert:
+ that:
+ - cm_non_existing_state is not changed
+ - nm_non_existing_state is not changed
+ - cm_non_existing_state == nm_non_existing_state
+ - cm_non_existing_state.msg == nm_non_existing_state.msg == "value of state must be one of{{':'}} query, got{{':'}} non-existing-state"
+
+# query without setting username&password in task
+- name: Query MSO version
+ cisco.mso.mso_version:
+ state: query
+ register: query_version_global_params
+ when: ansible_connection != 'local'
+
+- name: Verify query_version_global_params
+ assert:
+ that:
+ - query_version_global_params is not changed
+ - query_version_global_params.current.id is defined
+ - query_version_global_params.current.version is defined
+ - query_version_global_params.current.timestamp is defined
+ - query_version_global_params.current.id == cm_query_version.current.id
+ - query_version_global_params.current.version == cm_query_version.current.version
+ - query_version_global_params.current.timestamp == cm_query_version.current.timestamp
+ when: ansible_connection != 'local' \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/ndo_schema_template_deploy/aliases b/ansible_collections/cisco/mso/tests/integration/targets/ndo_schema_template_deploy/aliases
new file mode 100644
index 000000000..5042c9c09
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/ndo_schema_template_deploy/aliases
@@ -0,0 +1,2 @@
+# No ACI MultiSite infrastructure, so not enabled
+# unsupported
diff --git a/ansible_collections/cisco/mso/tests/integration/targets/ndo_schema_template_deploy/tasks/main.yml b/ansible_collections/cisco/mso/tests/integration/targets/ndo_schema_template_deploy/tasks/main.yml
new file mode 100644
index 000000000..3af935cfc
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/integration/targets/ndo_schema_template_deploy/tasks/main.yml
@@ -0,0 +1,407 @@
+# Test code for the MSO modules
+# Copyright: (c) 2022, Akini Ross (@akinross) <akinross@cisco.com>
+
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: Test that we have an ACI MultiSite host, username and password
+ ansible.builtin.fail:
+ msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.'
+ when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined
+
+
+# CLEAN ENVIRONMENT
+- name: Set vars
+ ansible.builtin.set_fact:
+ mso_info: &mso_info
+ host: '{{ mso_hostname }}'
+ username: '{{ mso_username }}'
+ password: '{{ mso_password }}'
+ validate_certs: '{{ mso_validate_certs | default(false) }}'
+ use_ssl: '{{ mso_use_ssl | default(true) }}'
+ use_proxy: '{{ mso_use_proxy | default(true) }}'
+ output_level: '{{ mso_output_level | default("info") }}'
+
+- name: Query MSO version
+ cisco.mso.mso_version:
+ <<: *mso_info
+ state: query
+ register: version
+
+- name: Ensure site exist
+ cisco.mso.mso_site:
+ <<: *mso_info
+ site: '{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ apic_username }}'
+ apic_password: '{{ apic_password }}'
+ apic_site_id: '{{ apic_site_id | default(101) }}'
+ urls:
+ - https://{{ apic_hostname }}
+ state: present
+
+- name: Ensure aws site exists
+ cisco.mso.mso_site:
+ <<: *mso_info
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ aws_apic_username }}'
+ apic_password: '{{ aws_apic_password }}'
+ apic_site_id: '{{ aws_site_id | default(102) }}'
+ urls:
+ - https://{{ aws_apic_hostname }}
+ state: present
+
+- name: Ensure azure site exists
+ cisco.mso.mso_site:
+ <<: *mso_info
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ apic_username: '{{ azure_apic_username }}'
+ apic_password: '{{ azure_apic_password }}'
+ apic_site_id: '{{ azure_site_id | default(103) }}'
+ urls:
+ - https://{{ azure_apic_hostname }}
+ state: present
+
+- name: Undeploy template
+ cisco.mso.ndo_schema_template_deploy:
+ <<: *mso_info
+ schema: ansible_test
+ template: "{{ item }}"
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ state: undeploy
+ ignore_errors: true
+ loop:
+ - Template 1
+ - Template 2
+
+- name: Remove schemas
+ cisco.mso.mso_schema:
+ <<: *mso_info
+ schema: '{{ item }}'
+ state: absent
+ loop:
+ - '{{ mso_schema | default("ansible_test") }}_2'
+ - '{{ mso_schema | default("ansible_test") }}'
+
+- name: Ensure tenant ansible_test exists
+ cisco.mso.mso_tenant:
+ <<: *mso_info
+ tenant: ansible_test
+ users:
+ - '{{ mso_username }}'
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ state: present
+
+- name: Ensure schema 1 with Template 1, and Template 2 exist
+ cisco.mso.mso_schema_template:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ tenant: ansible_test
+ template: '{{ item }}'
+ state: present
+ loop:
+ - Template 1
+ - Template 2
+
+- name: Add physical site to a schema
+ cisco.mso.mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: '{{ mso_site | default("ansible_test") }}'
+ template: '{{ item }}'
+ state: present
+ loop:
+ - Template 1
+ - Template 2
+
+- name: Deploy templates (check_mode)
+ cisco.mso.ndo_schema_template_deploy:
+ <<: *mso_info
+ schema: ansible_test
+ template: "{{ item }}"
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ state: deploy
+ check_mode: true
+ register: cm_deploy_template
+ loop:
+ - Template 1
+ - Template 2
+
+- name: Verify cm_deploy_template
+ ansible.builtin.assert:
+ that:
+ - item is not changed
+ loop: "{{ cm_deploy_template.results }}"
+
+- name: Deploy templates (normal_mode)
+ cisco.mso.ndo_schema_template_deploy:
+ <<: *mso_info
+ schema: ansible_test
+ template: "{{ item }}"
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ state: deploy
+ register: nm_deploy_template
+ loop:
+ - Template 1
+ - Template 2
+
+- name: Verify nm_deploy_template
+ ansible.builtin.assert:
+ that:
+ - item is not changed
+ - '"isRedeploy" not in item.current.reqDetails'
+ loop: "{{ nm_deploy_template.results }}"
+
+- name: Verify nm_deploy_template 4.0 specific
+ ansible.builtin.assert:
+ that:
+ - '"deploy" in item.current.reqDetails'
+ loop: "{{ nm_deploy_template.results }}"
+ when: version.current.version is version('4.0', '>=')
+
+- name: Query deployment
+ cisco.mso.ndo_schema_template_deploy:
+ <<: *mso_info
+ schema: ansible_test
+ template: "{{ item }}"
+ state: query
+ register: query_deploy_status
+ loop:
+ - Template 1
+ - Template 2
+
+- name: Verify query_deploy_status
+ ansible.builtin.assert:
+ that:
+ - item is not changed
+ - item.current.status.0.status.siteStatus == "Succeeded"
+ loop: "{{ query_deploy_status.results }}"
+
+- name: Redeploy templates
+ cisco.mso.ndo_schema_template_deploy:
+ <<: *mso_info
+ schema: ansible_test
+ template: '{{ item }}'
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ state: redeploy
+ register: redeploy_template
+ loop:
+ - Template 1
+ - Template 2
+
+- name: Verify redeploy_template
+ ansible.builtin.assert:
+ that:
+ - item is not changed
+ - item.current.reqDetails.isRedeploy == true
+ loop: "{{ redeploy_template.results }}"
+
+- name: Verify redeploy_template 4.0 specific
+ ansible.builtin.assert:
+ that:
+ - '"deploy" in item.current.reqDetails'
+ loop: "{{ redeploy_template.results }}"
+ when: version.current.version is version('4.0', '>=')
+
+- name: Undeploy templates
+ cisco.mso.ndo_schema_template_deploy:
+ <<: *mso_info
+ schema: ansible_test
+ template: '{{ item }}'
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ state: undeploy
+ register: undeploy_template
+ loop:
+ - Template 1
+ - Template 2
+
+- name: Verify undeploy_template
+ assert:
+ that:
+ - item is not changed
+ - '"undeploy" in item.current.reqDetails'
+ loop: "{{ undeploy_template.results }}"
+
+- name: Add VRF1 with validation error
+ cisco.mso.mso_schema_template_vrf: &fail_validation
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 2
+ vrf: VRF1
+ layer3_multicast: true
+ vzany: true
+ state: present
+
+- name: Deploy template with validation error
+ cisco.mso.ndo_schema_template_deploy:
+ <<: *mso_info
+ schema: ansible_test
+ template: Template 2
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ state: deploy
+ register: failed_validaton_deploy
+ ignore_errors: true
+
+- name: Redeploy template with validation error
+ cisco.mso.ndo_schema_template_deploy:
+ <<: *mso_info
+ schema: ansible_test
+ template: Template 2
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ state: redeploy
+ register: failed_validaton_redeploy
+ ignore_errors: true
+
+- name: Verify validation errors before deploy
+ ansible.builtin.assert:
+ that:
+ - failed_validaton_deploy.msg == "MSO Error 400{{':'}} Bad Request{{':'}} Patch Failed, Received{{':'}} vzAny contract must be configured if vzAny flag is set. VRF(s) {{':'}} VRF1 exception while trying to update schema"
+ - failed_validaton_redeploy.msg == "MSO Error 400{{':'}} Bad Request{{':'}} Patch Failed, Received{{':'}} vzAny contract must be configured if vzAny flag is set. VRF(s) {{':'}} VRF1 exception while trying to update schema"
+ when: version.current.version is version('4.0', '<')
+
+- name: Verify validation errors before deploy and redploy
+ ansible.builtin.assert:
+ that:
+ - failed_validaton_deploy.msg == "MSO Error 400{{':'}} VRF{{':'}} VRF1 in Schema{{':'}} ansible_test , Template{{':'}} Template2 has VzAnyEnabled flag enabled but is not consuming or providing contracts"
+ - failed_validaton_redeploy.msg == "MSO Error 400{{':'}} VRF{{':'}} VRF1 in Schema{{':'}} ansible_test , Template{{':'}} Template2 has VzAnyEnabled flag enabled but is not consuming or providing contracts"
+ when: version.current.version is version('4.0', '>=')
+
+- name: Remove VRF1 with validation error
+ cisco.mso.mso_schema_template_vrf:
+ <<: *fail_validation
+ state: absent
+
+- name: Ensure AWS site is present under tenant ansible_test
+ cisco.mso.mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ cloud_account: '000000000000'
+ aws_access_key: 1
+ secret_key: 0
+ state: present
+
+- name: Ensure Azure site is present under tenant ansible_test
+ cisco.mso.mso_tenant_site:
+ <<: *mso_info
+ tenant: ansible_test
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ cloud_account: uni/tn-ansible_test/act-[9]-vendor-azure
+ state: present
+
+- name: Add AWS site to a schema
+ cisco.mso.mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'aws_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ state: present
+
+- name: Add Azure site to a schema
+ cisco.mso.mso_schema_site:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ site: 'azure_{{ mso_site | default("ansible_test") }}'
+ template: Template 1
+ state: present
+
+- name: Deploy templates
+ cisco.mso.ndo_schema_template_deploy:
+ <<: *mso_info
+ schema: ansible_test
+ template: Template 1
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ - 'aws_{{ mso_site | default("ansible_test") }}'
+ - 'azure_{{ mso_site | default("ansible_test") }}'
+ state: deploy
+ register: deploy_template_all
+
+- name: Query deployment
+ cisco.mso.ndo_schema_template_deploy:
+ <<: *mso_info
+ schema: ansible_test
+ template: Template 1
+ state: query
+ register: query_deploy_status_all
+
+- name: Check deployment status of Template 1
+ cisco.mso.mso_schema_template_deploy_status:
+ <<: *mso_info
+ schema: '{{ mso_schema | default("ansible_test") }}'
+ template: Template 1
+ state: query
+ register: deployment_status
+
+- name: Redeploy templates
+ cisco.mso.ndo_schema_template_deploy:
+ <<: *mso_info
+ schema: ansible_test
+ template: Template 1
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ - 'aws_{{ mso_site | default("ansible_test") }}'
+ - 'azure_{{ mso_site | default("ansible_test") }}'
+ state: redeploy
+ register: redeploy_template_all
+
+- name: Query redeployment
+ cisco.mso.ndo_schema_template_deploy:
+ <<: *mso_info
+ schema: ansible_test
+ template: Template 1
+ state: query
+ register: query_redeploy_status_all
+
+- name: Undeploy templates
+ cisco.mso.ndo_schema_template_deploy:
+ <<: *mso_info
+ schema: ansible_test
+ template: Template 1
+ sites:
+ - '{{ mso_site | default("ansible_test") }}'
+ - 'aws_{{ mso_site | default("ansible_test") }}'
+ - 'azure_{{ mso_site | default("ansible_test") }}'
+ state: undeploy
+ register: undeploy_template_all
+
+- name: Query undeployment
+ cisco.mso.ndo_schema_template_deploy:
+ <<: *mso_info
+ schema: ansible_test
+ template: Template 1
+ state: query
+ register: query_undeploy_status_all
+
+- name: Verify multiple sites
+ ansible.builtin.assert:
+ that:
+ - deploy_template_all is not changed
+ - redeploy_template_all is not changed
+ - '"isRedeploy" in redeploy_template_all.current.reqDetails'
+ - undeploy_template_all is not changed
+ - '"undeploy" in undeploy_template_all.current.reqDetails'
+ - undeploy_template_all.current.reqDetails.undeploy | length == 3
+ - deployment_status.current | length == 3
+ - query_deploy_status_all.current.status | length == 3
+ - query_deploy_status_all.current.status.0.status.siteStatus == "Succeeded"
+ - query_deploy_status_all.current.status.1.status.siteStatus == "Succeeded"
+ - query_deploy_status_all.current.status.2.status.siteStatus == "Succeeded"
+ - query_redeploy_status_all.current.status | length == 3
+ - query_redeploy_status_all.current.status.0.status.siteStatus == "Succeeded"
+ - query_redeploy_status_all.current.status.1.status.siteStatus == "Succeeded"
+ - query_redeploy_status_all.current.status.2.status.siteStatus == "Succeeded"
+ - query_undeploy_status_all.current.status == []
+
+- name: Verify multiple sites 4.0 specific
+ ansible.builtin.assert:
+ that:
+ - '"deploy" in deploy_template_all.current.reqDetails'
+ - '"deploy" in redeploy_template_all.current.reqDetails'
+ when: version.current.version is version('4.0', '>=') \ No newline at end of file
diff --git a/ansible_collections/cisco/mso/tests/sanity/requirements.txt b/ansible_collections/cisco/mso/tests/sanity/requirements.txt
new file mode 100644
index 000000000..66ac0c81a
--- /dev/null
+++ b/ansible_collections/cisco/mso/tests/sanity/requirements.txt
@@ -0,0 +1,5 @@
+packaging # needed for update-bundled and changelog
+sphinx ; python_version >= '3.5' # docs build requires python 3+
+sphinx-notfound-page ; python_version >= '3.5' # docs build requires python 3+
+straight.plugin ; python_version >= '3.5' # needed for hacking/build-ansible.py which will host changelog generation and requires python 3+
+requests-toolbelt \ No newline at end of file