From 1ac40e21b4c78176d56ea85399bda6b6b2682ed6 Mon Sep 17 00:00:00 2001 From: Dag Wieers Date: Fri, 2 Nov 2018 20:07:10 +0100 Subject: [PATCH] msc_site: Manage sites on ACI MultiSite (#47756) This includes the new msc_site module and integration tests. --- lib/ansible/modules/network/aci/msc_site.py | 199 +++++++++++++ test/integration/targets/msc_site/aliases | 2 + .../targets/msc_site/tasks/main.yml | 275 ++++++++++++++++++ 3 files changed, 476 insertions(+) create mode 100644 lib/ansible/modules/network/aci/msc_site.py create mode 100644 test/integration/targets/msc_site/aliases create mode 100644 test/integration/targets/msc_site/tasks/main.yml diff --git a/lib/ansible/modules/network/aci/msc_site.py b/lib/ansible/modules/network/aci/msc_site.py new file mode 100644 index 00000000000..9aa4f614122 --- /dev/null +++ b/lib/ansible/modules/network/aci/msc_site.py @@ -0,0 +1,199 @@ +#!/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_site +short_description: Manage sites +description: +- Manage sites on Cisco ACI Multi-Site. +author: +- Dag Wieers (@dagwieers) +version_added: '2.8' +options: + apic_password: + description: + - The password for the APICs. + apic_site_id: + description: + - The site ID of the APICs. + apic_username: + description: + - The username for the APICs. + default: admin + site_id: + description: + - The ID of the site. + required: yes + site: + description: + - The name of the site. + required: yes + aliases: [ name, site_name ] + labels: + description: + - The labels for this site. + type: list + urls: + description: + - A list of URLs to reference the APICs. + type: list + 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 site + msc_site: + host: msc_host + username: admin + password: SomeSecretPassword + site: north_europe + site_id: 101 + description: North European Datacenter + state: present + delegate_to: localhost + +- name: Remove a site + msc_site: + host: msc_host + username: admin + password: SomeSecretPassword + site: north_europe + state: absent + delegate_to: localhost + +- name: Query a site + msc_site: + host: msc_host + username: admin + password: SomeSecretPassword + site: north_europe + state: query + delegate_to: localhost + register: query_result + +- name: Query all sites + msc_site: + 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( + apic_password=dict(type='str', no_log=True), + apic_site_id=dict(type='str'), + apic_username=dict(type='str', default='admin'), + labels=dict(type='list'), + site=dict(type='str', required=False, aliases=['name', 'site_name']), + site_id=dict(type='str', required=False), + state=dict(type='str', default='present', choices=['absent', 'present', 'query']), + urls=dict(type='list'), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ['state', 'absent', ['site']], + ['state', 'present', ['site']], + ], + ) + + apic_username = module.params['apic_username'] + apic_password = module.params['apic_password'] + apic_site_id = module.params['apic_site_id'] + site = module.params['site'] + site_id = module.params['site_id'] + state = module.params['state'] + urls = module.params['urls'] + + msc = MSCModule(module) + + path = 'sites' + + # Query for msc.existing object(s) + if site_id is None and site is None: + msc.existing = msc.query_objs(path) + elif site_id is None: + msc.existing = msc.get_obj(path, name=site) + if msc.existing: + site_id = msc.existing['id'] + elif site is None: + msc.existing = msc.get_obj(path, id=site_id) + else: + msc.existing = msc.get_obj(path, id=site_id) + existing_by_name = msc.get_obj(path, name=site) + if existing_by_name and site_id != existing_by_name['id']: + msc.fail_json(msg="Provided site '{1}' with id '{2}' does not match existing id '{3}'.".format(site, site_id, existing_by_name['id'])) + + # If we found an existing object, continue with it + if site_id: + path = 'sites/{id}'.format(id=site_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( + apicSiteId=apic_site_id, + id=site_id, + name=site, + urls=urls, + username=apic_username, + password=apic_password, + )) + + if msc.existing: + if not issubset(msc.sent, msc.existing): + if module.check_mode: + msc.existing = msc.proposed + else: + msc.request(path, method='PUT', data=msc.sent) + else: + if module.check_mode: + msc.existing = msc.proposed + else: + msc.request(path, method='POST', data=msc.sent) + + msc.exit_json() + + +if __name__ == "__main__": + main() diff --git a/test/integration/targets/msc_site/aliases b/test/integration/targets/msc_site/aliases new file mode 100644 index 00000000000..cd28d3c3621 --- /dev/null +++ b/test/integration/targets/msc_site/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +unsupported diff --git a/test/integration/targets/msc_site/tasks/main.yml b/test/integration/targets/msc_site/tasks/main.yml new file mode 100644 index 00000000000..fd906e2251a --- /dev/null +++ b/test/integration/targets/msc_site/tasks/main.yml @@ -0,0 +1,275 @@ +# 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 site ansible_test2 + msc_site: &site_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") }}' + site: ansible_test2 + state: absent + +- name: Remove site ansible_test + msc_site: + <<: *site_absent + site: ansible_test + register: cm_remove_site + + +# ADD SITE +- name: Add site (check_mode) + msc_site: &site_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") }}' + site: ansible_test + apic_username: admin + apic_password: '{{ apic_password }}' + urls: + - https://{{ apic_hostname }}/ + state: present + check_mode: yes + register: cm_add_site + +- name: Verify cm_add_site + assert: + that: + - cm_add_site is changed + - cm_add_site.previous == {} + - cm_add_site.current.id is not defined + - cm_add_site.current.name == 'ansible_test' + +- name: Add site (normal mode) + msc_site: *site_present + register: nm_add_site + +- name: nm_Verify add_site + assert: + that: + - nm_add_site is changed + - nm_add_site.previous == {} + - nm_add_site.current.id is defined + - nm_add_site.current.name == 'ansible_test' + +- name: Add site again (check_mode) + msc_site: *site_present + check_mode: yes + register: cm_add_site_again + +- name: Verify cm_add_site_again + assert: + that: + - cm_add_site_again is not changed + - cm_add_site_again.previous.name == 'ansible_test' + - cm_add_site_again.current.id == nm_add_site.current.id + - cm_add_site_again.current.name == 'ansible_test' + +- name: Add site again (normal mode) + msc_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.previous.name == 'ansible_test' + - nm_add_site_again.current.id == nm_add_site.current.id + - nm_add_site_again.current.name == 'ansible_test' + + +# CHANGE SITE +- name: Change site (check_mode) + msc_site: + <<: *site_present + site_id: '{{ nm_add_site.current.id }}' + site: ansible_test2 + check_mode: yes + register: cm_change_site + +- name: Verify cm_change_site + assert: + that: + - cm_change_site is changed + - cm_change_site.current.id == nm_add_site.current.id + - cm_change_site.current.name == 'ansible_test2' + +- name: Change site (normal mode) + msc_site: + <<: *site_present + site_id: '{{ nm_add_site.current.id }}' + site: ansible_test2 + output_level: debug + register: nm_change_site + +- name: Verify nm_change_site + assert: + that: + - nm_change_site is changed + - nm_change_site.current.id == nm_add_site.current.id + - nm_change_site.current.name == 'ansible_test2' + +- name: Change site again (check_mode) + msc_site: + <<: *site_present + site_id: '{{ nm_add_site.current.id }}' + site: ansible_test2 + check_mode: yes + 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 + - cm_change_site_again.current.name == 'ansible_test2' + +- name: Change site again (normal mode) + msc_site: + <<: *site_present + site_id: '{{ nm_add_site.current.id }}' + site: ansible_test2 + 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 + - nm_change_site_again.current.name == 'ansible_test2' + + +# QUERY ALL SITES +- name: Query all sites (check_mode) + msc_site: &site_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_sites + +- name: Query all sites (normal mode) + msc_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 + msc_site: + <<: *site_query + site: ansible_test2 + check_mode: yes + register: cm_query_site + +- name: Query our site + msc_site: + <<: *site_query + site: ansible_test2 + 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 + - cm_query_site.current.name == 'ansible_test2' + - nm_query_site is not changed + - nm_query_site.current.id == nm_add_site.current.id + - nm_query_site.current.name == 'ansible_test2' + - cm_query_site == nm_query_site + + +# REMOVE SITE +- name: Remove site (check_mode) + msc_site: *site_absent + check_mode: yes + 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) + msc_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) + msc_site: *site_absent + check_mode: yes + 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) + msc_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) + msc_site: + <<: *site_query + site: ansible_test + check_mode: yes + register: cm_query_non_site + +- name: Query non-existing site (normal mode) + msc_site: + <<: *site_query + site: 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