diff --git a/lib/ansible/modules/network/aci/msc_tenant.py b/lib/ansible/modules/network/aci/msc_tenant.py new file mode 100644 index 00000000000..9a33d069d0a --- /dev/null +++ b/lib/ansible/modules/network/aci/msc_tenant.py @@ -0,0 +1,182 @@ +#!/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: msc_tenant +short_description: Manage tenants +description: +- Manage tenants on Cisco ACI Multi-Site. +author: +- Dag Wieers (@dagwieers) +version_added: '2.8' +options: + tenant_id: + description: + - The ID of the tenant. + required: yes + tenant: + description: + - The name of the tenant. + required: yes + aliases: [ name, tenant_name ] + display_name: + description: + - The name of the tenant to be displayed in the web UI. + description: + description: + - The description for this tenant. + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: msc +''' + +EXAMPLES = r''' +- name: Add a new tenant + msc_tenant: + host: msc_host + username: admin + password: SomeSecretPassword + tenant: north_europe + tenant_id: 101 + description: North European Datacenter + state: present + delegate_to: localhost + +- name: Remove a tenant + msc_tenant: + host: msc_host + username: admin + password: SomeSecretPassword + tenant: north_europe + state: absent + delegate_to: localhost + +- name: Query a tenant + msc_tenant: + host: msc_host + username: admin + password: SomeSecretPassword + tenant: north_europe + state: query + delegate_to: localhost + register: query_result + +- name: Query all tenants + msc_tenant: + host: msc_host + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result +''' + +RETURN = r''' +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.network.aci.msc import MSCModule, msc_argument_spec, issubset + + +def main(): + argument_spec = msc_argument_spec() + argument_spec.update( + description=dict(type='str'), + display_name=dict(type='str'), + tenant=dict(type='str', required=False, aliases=['name', 'tenant_name']), + tenant_id=dict(type='str', required=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', ['tenant']], + ['state', 'present', ['tenant']], + ], + ) + + description = module.params['description'] + display_name = module.params['display_name'] + tenant = module.params['tenant'] + tenant_id = module.params['tenant_id'] + state = module.params['state'] + + msc = MSCModule(module) + + path = 'tenants' + + # Query for existing object(s) + if tenant_id is None and tenant is None: + msc.existing = msc.query_objs(path) + elif tenant_id is None: + msc.existing = msc.get_obj(path, name=tenant) + if msc.existing: + tenant_id = msc.existing['id'] + elif tenant is None: + msc.existing = msc.get_obj(path, id=tenant_id) + else: + msc.existing = msc.get_obj(path, id=tenant_id) + existing_by_name = msc.get_obj(path, name=tenant) + if existing_by_name and tenant_id != existing_by_name['id']: + msc.fail_json(msg="Provided tenant '{1}' with id '{2}' does not match existing id '{3}'.".format(tenant, tenant_id, existing_by_name['id'])) + + # If we found an existing object, continue with it + if tenant_id: + path = 'tenants/{id}'.format(id=tenant_id) + + if state == 'query': + pass + + elif state == 'absent': + msc.previous = msc.existing + if msc.existing: + if module.check_mode: + msc.existing = {} + else: + msc.existing = msc.request(path, method='DELETE') + + elif state == 'present': + msc.previous = msc.existing + + msc.sanitize(dict( + description=description, + id=tenant_id, + name=tenant, + displayName=display_name, + siteAssociations=[], + userAssociations=[dict(userId="0000ffff0000000000000020")], + )) + + if msc.existing: + if not issubset(msc.sent, msc.existing): + if module.check_mode: + msc.existing = msc.proposed + else: + msc.existing = msc.request(path, method='PUT', data=msc.sent) + else: + if module.check_mode: + msc.existing = msc.proposed + else: + msc.existing = msc.request(path, method='POST', data=msc.sent) + + msc.exit_json() + + +if __name__ == "__main__": + main() diff --git a/test/integration/targets/msc_tenant/aliases b/test/integration/targets/msc_tenant/aliases new file mode 100644 index 00000000000..cd28d3c3621 --- /dev/null +++ b/test/integration/targets/msc_tenant/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +unsupported diff --git a/test/integration/targets/msc_tenant/tasks/main.yml b/test/integration/targets/msc_tenant/tasks/main.yml new file mode 100644 index 00000000000..23380b2b6f6 --- /dev/null +++ b/test/integration/targets/msc_tenant/tasks/main.yml @@ -0,0 +1,288 @@ +# Test code for the MSC modules +# Copyright: (c) 2018, Dag Wieers (@dagwieers) + +# GNU General Public License v3.0+ (see COPYING 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: msc_hostname, msc_username and msc_password.' + when: msc_hostname is not defined or msc_username is not defined or msc_password is not defined + + +# CLEAN ENVIRONMENT +- name: Remove tenant ansible_test2 + msc_tenant: &tenant_absent + host: '{{ msc_hostname }}' + username: '{{ msc_username }}' + password: '{{ msc_password }}' + validate_certs: '{{ msc_validate_certs | default(false) }}' + use_ssl: '{{ msc_use_ssl | default(true) }}' + use_proxy: '{{ msc_use_proxy | default(true) }}' + output_level: '{{ msc_output_level | default("info") }}' + tenant: ansible_test2 + state: absent + +- name: Remove tenant ansible_test + msc_tenant: + <<: *tenant_absent + tenant: ansible_test + register: cm_remove_tenant + + +# ADD TENANT +- name: Add tenant (check_mode) + msc_tenant: &tenant_present + host: '{{ msc_hostname }}' + username: '{{ msc_username }}' + password: '{{ msc_password }}' + validate_certs: '{{ msc_validate_certs | default(false) }}' + use_ssl: '{{ msc_use_ssl | default(true) }}' + use_proxy: '{{ msc_use_proxy | default(true) }}' + output_level: '{{ msc_output_level | default("info") }}' + tenant: ansible_test + description: Ansible test tenant + state: present + check_mode: yes + 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.displayName == 'ansible_test' + - cm_add_tenant.current.description == 'Ansible test tenant' + +- name: Add tenant (normal mode) + msc_tenant: *tenant_present + register: nm_add_tenant + +- name: nm_Verify add_tenant + assert: + that: + - nm_add_tenant is changed + - nm_add_tenant.previous == {} + - nm_add_tenant.current.id is defined + - nm_add_tenant.current.displayName == 'ansible_test' + - nm_add_tenant.current.description == 'Ansible test tenant' + +- name: Add tenant again (check_mode) + msc_tenant: *tenant_present + check_mode: yes + 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.displayName == '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.displayName == 'ansible_test' + - cm_add_tenant_again.current.description == 'Ansible test tenant' + +- name: Add tenant again (normal mode) + msc_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.displayName == '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.displayName == 'ansible_test' + - nm_add_tenant_again.current.description == 'Ansible test tenant' + + +# CHANGE TENANT +- name: Change tenant (check_mode) + msc_tenant: + <<: *tenant_present + tenant_id: '{{ nm_add_tenant.current.id }}' + tenant: ansible_test2 + description: Ansible test tenant 2 + check_mode: yes + 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.displayName == 'ansible_test2' + - cm_change_tenant.current.description == 'Ansible test tenant 2' + +- name: Change tenant (normal mode) + msc_tenant: + <<: *tenant_present + tenant_id: '{{ nm_add_tenant.current.id }}' + tenant: ansible_test2 + 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.displayName == 'ansible_test2' + - nm_change_tenant.current.description == 'Ansible test tenant 2' + +- name: Change tenant again (check_mode) + msc_tenant: + <<: *tenant_present + tenant_id: '{{ nm_add_tenant.current.id }}' + tenant: ansible_test2 + description: Ansible test tenant 2 + check_mode: yes + 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.displayName == 'ansible_test2' + - cm_change_tenant_again.current.description == 'Ansible test tenant 2' + +- name: Change tenant again (normal mode) + msc_tenant: + <<: *tenant_present + tenant_id: '{{ nm_add_tenant.current.id }}' + tenant: ansible_test2 + 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.displayName == 'ansible_test2' + - nm_change_tenant_again.current.description == 'Ansible test tenant 2' + + +# QUERY ALL TENANTS +- name: Query all tenants (check_mode) + msc_tenant: &tenant_query + host: '{{ msc_hostname }}' + username: '{{ msc_username }}' + password: '{{ msc_password }}' + validate_certs: '{{ msc_validate_certs | default(false) }}' + use_ssl: '{{ msc_use_ssl | default(true) }}' + use_proxy: '{{ msc_use_proxy | default(true) }}' + output_level: '{{ msc_output_level | default("info") }}' + state: query + check_mode: yes + register: cm_query_all_tenants + +- name: Query all tenants (normal mode) + msc_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 + msc_tenant: + <<: *tenant_query + tenant: ansible_test2 + check_mode: yes + register: cm_query_tenant + +- name: Query our tenant + msc_tenant: + <<: *tenant_query + tenant: ansible_test2 + 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.displayName == 'ansible_test2' + - 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.displayName == 'ansible_test2' + - nm_query_tenant.current.description == 'Ansible test tenant 2' + - cm_query_tenant == nm_query_tenant + + +# REMOVE TENANT +- name: Remove tenant (check_mode) + msc_tenant: *tenant_absent + check_mode: yes + 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) + msc_tenant: *tenant_absent + 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) + msc_tenant: *tenant_absent + check_mode: yes + 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) + msc_tenant: *tenant_absent + 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) + msc_tenant: + <<: *tenant_query + tenant: ansible_test + check_mode: yes + register: cm_query_non_tenant + +- name: Query non-existing tenant (normal mode) + msc_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 == nm_query_non_tenant