From cbde04606af7f35164b780fc379ee37f9e627b5f Mon Sep 17 00:00:00 2001 From: David Passante Date: Wed, 6 Feb 2019 11:33:27 +0100 Subject: [PATCH] Cloudstack: New module cs_vlan_ip_range (#51597) --- .../cloud/cloudstack/cs_vlan_ip_range.py | 341 ++++++++++++++++ .../targets/cs_vlan_ip_range/aliases | 2 + .../targets/cs_vlan_ip_range/meta/main.yml | 3 + .../targets/cs_vlan_ip_range/tasks/main.yml | 371 ++++++++++++++++++ 4 files changed, 717 insertions(+) create mode 100644 lib/ansible/modules/cloud/cloudstack/cs_vlan_ip_range.py create mode 100644 test/integration/targets/cs_vlan_ip_range/aliases create mode 100644 test/integration/targets/cs_vlan_ip_range/meta/main.yml create mode 100644 test/integration/targets/cs_vlan_ip_range/tasks/main.yml diff --git a/lib/ansible/modules/cloud/cloudstack/cs_vlan_ip_range.py b/lib/ansible/modules/cloud/cloudstack/cs_vlan_ip_range.py new file mode 100644 index 0000000000..c7f0f38b53 --- /dev/null +++ b/lib/ansible/modules/cloud/cloudstack/cs_vlan_ip_range.py @@ -0,0 +1,341 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2018, David Passante <@dpassante> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + + +DOCUMENTATION = ''' +--- +module: cs_vlan_ip_range +short_description: Manages VLAN IP ranges on Apache CloudStack based clouds. +description: + - Create and delete VLAN IP range. +version_added: '2.8' +author: "David Passante (@dpassante)" +options: + network: + description: + - The network name or id. + required: true + start_ip: + description: + - The beginning IPv4 address in the VLAN IP range. + - Only considered on create. + required: true + end_ip: + description: + - The ending IPv4 address in the VLAN IP range. + - If not specified, value of I(start_ip) is used. + - Only considered on create. + gateway: + description: + - The gateway of the VLAN IP range. + - Required if I(state=present). + netmask: + description: + - The netmask of the VLAN IP range. + - Required if I(state=present). + start_ipv6: + description: + - The beginning IPv6 address in the IPv6 network range. + - Only considered on create. + end_ipv6: + description: + - The ending IPv6 address in the IPv6 network range. + - If not specified, value of I(start_ipv6) is used. + - Only considered on create. + gateway_ipv6: + description: + - The gateway of the IPv6 network. + - Only considered on create. + cidr_ipv6: + description: + - The CIDR of IPv6 network, must be at least /64. + vlan: + description: + - The ID or VID of the network. + - If not specified, will be defaulted to the vlan of the network. + state: + description: + - State of the network ip range. + default: present + choices: [ present, absent ] + zone: + description: + - The Zone ID of the VLAN IP range. + - If not set, default zone is used. + domain: + description: + - Domain of the account owning the VLAN. + account: + description: + - Account who owns the VLAN. + - Mutually exclusive with I(project). + project: + description: + - Project who owns the VLAN. + - Mutually exclusive with I(account). + for_virtual_network: + description: + - true if VLAN is of Virtual type, false if Direct. + type: bool + default: false +extends_documentation_fragment: cloudstack +''' + +EXAMPLES = ''' +- name: create a VLAN IP range for network test + local_action: + module: cs_vlan_ip_range + network: test + vlan: 98 + start_ip: 10.2.4.10 + end_ip: 10.2.4.100 + gateway: 10.2.4.1 + netmask: 255.255.255.0 + zone: zone-02 + +- name: remove a VLAN IP range for network test + local_action: + module: cs_vlan_ip_range + state: absent + network: test + start_ip: 10.2.4.10 + end_ip: 10.2.4.100 + zone: zone-02 +''' + +RETURN = ''' +--- +id: + description: UUID of the VLAN IP range. + returned: success + type: str + sample: 04589590-ac63-4ffc-93f5-b698b8ac38b6 +network: + description: The network of vlan range + returned: success + type: str + sample: test +vlan: + description: The ID or VID of the VLAN. + returned: success + type: str + sample: vlan://98 +gateway: + description: IPv4 gateway. + returned: success + type: str + sample: 10.2.4.1 +netmask: + description: IPv4 netmask. + returned: success + type: str + sample: 255.255.255.0 +gateway_ipv6: + description: IPv6 gateway. + returned: success + type: str + sample: 2001:db8::1 +cidr_ipv6: + description: The CIDR of IPv6 network. + returned: success + type: str + sample: 2001:db8::/64 +zone: + description: Name of zone. + returned: success + type: str + sample: zone-02 +domain: + description: Domain name of the VLAN IP range. + returned: success + type: str + sample: ROOT +account: + description: Account who owns the network. + returned: success + type: str + sample: example account +project: + description: Project who owns the network. + returned: success + type: str + sample: example project +for_systemvms: + description: Whether VLAN IP range is dedicated to system vms or not. + returned: success + type: bool + sample: false +for_virtual_network: + description: Whether VLAN IP range is of Virtual type or not. + returned: success + type: bool + sample: false +physical_network: + description: The physical network VLAN IP range belongs to. + returned: success + type: str + sample: 04589590-ac63-4ffc-93f5-b698b8ac38b6 +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together, +) + + +class AnsibleCloudStackVlanIpRange(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackVlanIpRange, self).__init__(module) + self.returns = { + 'startip': 'start_ip', + 'endip': 'end_ip', + 'physicalnetworkid': 'physical_network', + 'vlan': 'vlan', + 'forsystemvms': 'for_systemvms', + 'forvirtualnetwork': 'for_virtual_network', + 'gateway': 'gateway', + 'netmask': 'netmask', + 'ip6gateway': 'gateway_ipv6', + 'ip6cidr': 'cidr_ipv6', + 'startipv6': 'start_ipv6', + 'endipv6': 'end_ipv6', + } + self.ip_range = None + + def get_vlan_ip_range(self): + if not self.ip_range: + args = { + 'zoneid': self.get_zone(key='id'), + 'projectid': self.get_project(key='id'), + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'networkid': self.get_network(key='id'), + } + + res = self.query_api('listVlanIpRanges', **args) + if res: + ip_range_list = res['vlaniprange'] + + params = { + 'startip': self.module.params.get('start_ip'), + 'endip': self.get_or_fallback('end_ip', 'start_ip'), + } + + for ipr in ip_range_list: + if params['startip'] == ipr['startip'] and params['endip'] == ipr['endip']: + self.ip_range = ipr + break + + return self.ip_range + + def present_vlan_ip_range(self): + ip_range = self.get_vlan_ip_range() + + if not ip_range: + ip_range = self.create_vlan_ip_range() + + return ip_range + + def create_vlan_ip_range(self): + self.result['changed'] = True + + vlan = self.module.params.get('vlan') + + args = { + 'zoneid': self.get_zone(key='id'), + 'projectid': self.get_project(key='id'), + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'startip': self.module.params.get('start_ip'), + 'endip': self.get_or_fallback('end_ip', 'start_ip'), + 'netmask': self.module.params.get('netmask'), + 'gateway': self.module.params.get('gateway'), + 'startipv6': self.module.params.get('start_ipv6'), + 'endipv6': self.get_or_fallback('end_ipv6', 'start_ipv6'), + 'ip6gateway': self.module.params.get('gateway_ipv6'), + 'ip6cidr': self.module.params.get('cidr_ipv6'), + 'vlan': self.get_network(key='vlan') if not vlan else vlan, + 'networkid': self.get_network(key='id'), + 'forvirtualnetwork': self.module.params.get('for_virtual_network'), + } + + if not self.module.check_mode: + res = self.query_api('createVlanIpRange', **args) + + self.ip_range = res['vlan'] + + return self.ip_range + + def absent_vlan_ip_range(self): + ip_range = self.get_vlan_ip_range() + + if ip_range: + self.result['changed'] = True + + args = { + 'id': ip_range['id'], + } + + if not self.module.check_mode: + self.query_api('deleteVlanIpRange', **args) + + return ip_range + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + network=dict(type='str', required=True), + zone=dict(type='str'), + start_ip=dict(type='str', required=True), + end_ip=dict(type='str'), + gateway=dict(type='str'), + netmask=dict(type='str'), + start_ipv6=dict(type='str'), + end_ipv6=dict(type='str'), + gateway_ipv6=dict(type='str'), + cidr_ipv6=dict(type='str'), + vlan=dict(type='str'), + state=dict(choices=['present', 'absent'], default='present'), + domain=dict(type='str'), + account=dict(type='str'), + project=dict(type='str'), + for_virtual_network=dict(type='bool', default=False), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + mutually_exclusive=( + ['account', 'project'], + ), + required_if=(("state", "present", ("gateway", "netmask")),), + supports_check_mode=True, + ) + + acs_vlan_ip_range = AnsibleCloudStackVlanIpRange(module) + + state = module.params.get('state') + if state == 'absent': + ipr = acs_vlan_ip_range.absent_vlan_ip_range() + + else: + ipr = acs_vlan_ip_range.present_vlan_ip_range() + + result = acs_vlan_ip_range.get_result(ipr) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/cs_vlan_ip_range/aliases b/test/integration/targets/cs_vlan_ip_range/aliases new file mode 100644 index 0000000000..c89c86d7d2 --- /dev/null +++ b/test/integration/targets/cs_vlan_ip_range/aliases @@ -0,0 +1,2 @@ +cloud/cs +shippable/cs/group1 diff --git a/test/integration/targets/cs_vlan_ip_range/meta/main.yml b/test/integration/targets/cs_vlan_ip_range/meta/main.yml new file mode 100644 index 0000000000..e9a5b9eeae --- /dev/null +++ b/test/integration/targets/cs_vlan_ip_range/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: + - cs_common diff --git a/test/integration/targets/cs_vlan_ip_range/tasks/main.yml b/test/integration/targets/cs_vlan_ip_range/tasks/main.yml new file mode 100644 index 0000000000..b032921cba --- /dev/null +++ b/test/integration/targets/cs_vlan_ip_range/tasks/main.yml @@ -0,0 +1,371 @@ +--- +- name: setup cleanup test network + cs_network: + name: ipr_test_network + state: absent + zone: "{{ cs_common_zone_adv }}" + +- name: setup create test network + cs_network: + name: ipr_test_network + zone: "{{ cs_common_zone_adv }}" + vlan: 98 + start_ip: 10.2.4.2 + end_ip: 10.2.4.9 + gateway: 10.2.4.1 + netmask: 255.255.255.0 + network_offering: DefaultSharedNetworkOffering + register: ipr_net +- name: verify setup create test network + assert: + that: + - ipr_net is successful + - ipr_net is changed + +- name: test create a VLAN IP RANGE with missing required param + cs_vlan_ip_range: + network: ipr_test_network + vlan: 98 + start_ip: 10.2.4.10 + end_ip: 10.2.4.100 + gateway: 10.2.4.1 + zone: "{{ cs_common_zone_adv }}" + ignore_errors: yes + register: ipr +- name: verify test create VLAN IP RANGE with missing required param + assert: + that: + - ipr is not successful + - ipr is not changed + - 'ipr.msg == "state is present but all of the following are missing: netmask"' + +- name: test create a VLAN IP RANGE with conflicting params + cs_vlan_ip_range: + network: ipr_test_network + vlan: 98 + start_ip: 10.2.4.10 + end_ip: 10.2.4.100 + gateway: 10.2.4.1 + netmask: 255.255.255.0 + project: fakeproject + account: fakeaccount + zone: "{{ cs_common_zone_adv }}" + ignore_errors: yes + register: ipr +- name: verify test create VLAN IP RANGE with missing conflicting params + assert: + that: + - ipr is not successful + - ipr is not changed + - 'ipr.msg == "parameters are mutually exclusive: account, project"' + +- name: test create a VLAN IP RANGE in check mode + cs_vlan_ip_range: + network: ipr_test_network + vlan: 98 + start_ip: 10.2.4.10 + end_ip: 10.2.4.100 + gateway: 10.2.4.1 + netmask: 255.255.255.0 + zone: "{{ cs_common_zone_adv }}" + register: ipr + check_mode: true +- name: verify test create VLAN IP RANGE in check mode + assert: + that: + - ipr is successful + - ipr is changed + +- name: test create a VLAN IP RANGE + cs_vlan_ip_range: + network: ipr_test_network + vlan: 98 + start_ip: 10.2.4.10 + end_ip: 10.2.4.100 + gateway: 10.2.4.1 + netmask: 255.255.255.0 + zone: "{{ cs_common_zone_adv }}" + register: ipr +- name: verify test create VLAN IP RANGE + assert: + that: + - ipr is successful + - ipr is changed + - ipr.vlan == "vlan://98" + - ipr.start_ip == "10.2.4.10" + - ipr.end_ip == "10.2.4.100" + - ipr.gateway == "10.2.4.1" + - ipr.netmask == "255.255.255.0" + - ipr.network == "ipr_test_network" + - ipr.for_virtual_network == false + +- name: test create a VLAN IP RANGE idempotence + cs_vlan_ip_range: + network: ipr_test_network + vlan: 98 + start_ip: 10.2.4.10 + end_ip: 10.2.4.100 + gateway: 10.2.4.1 + netmask: 255.255.255.0 + zone: "{{ cs_common_zone_adv }}" + register: ipr +- name: verify test create VLAN IP RANGE idempotence + assert: + that: + - ipr is successful + - ipr is not changed + - ipr.vlan == "vlan://98" + - ipr.start_ip == "10.2.4.10" + - ipr.end_ip == "10.2.4.100" + - ipr.gateway == "10.2.4.1" + - ipr.netmask == "255.255.255.0" + - ipr.network == "ipr_test_network" + - ipr.for_virtual_network == false + +- name: test create a second VLAN IP RANGE in check mode + cs_vlan_ip_range: + network: ipr_test_network + start_ip: 10.2.4.101 + end_ip: 10.2.4.150 + gateway: 10.2.4.1 + netmask: 255.255.255.0 + zone: "{{ cs_common_zone_adv }}" + register: ipr2 + check_mode: true +- name: verify test create a second VLAN IP RANGE in check mode + assert: + that: + - ipr2 is successful + - ipr2 is changed + +- name: test create a second VLAN IP RANGE + cs_vlan_ip_range: + network: ipr_test_network + start_ip: 10.2.4.101 + end_ip: 10.2.4.150 + gateway: 10.2.4.1 + netmask: 255.255.255.0 + zone: "{{ cs_common_zone_adv }}" + register: ipr2 +- name: verify test create a second VLAN IP RANGE + assert: + that: + - ipr2 is successful + - ipr2 is changed + - ipr2.vlan == "vlan://98" + - ipr2.start_ip == "10.2.4.101" + - ipr2.end_ip == "10.2.4.150" + - ipr2.gateway == "10.2.4.1" + - ipr2.netmask == "255.255.255.0" + - ipr2.network == "ipr_test_network" + - ipr2.for_virtual_network == false + - ipr2.id != ipr.id + +- name: test create a second VLAN IP RANGE idempotence + cs_vlan_ip_range: + network: ipr_test_network + start_ip: 10.2.4.101 + end_ip: 10.2.4.150 + gateway: 10.2.4.1 + netmask: 255.255.255.0 + zone: "{{ cs_common_zone_adv }}" + register: ipr2 +- name: verify test create a second VLAN IP RANGE idempotence + assert: + that: + - ipr2 is successful + - ipr2 is not changed + - ipr2.vlan == "vlan://98" + - ipr2.start_ip == "10.2.4.101" + - ipr2.end_ip == "10.2.4.150" + - ipr2.gateway == "10.2.4.1" + - ipr2.netmask == "255.255.255.0" + - ipr2.network == "ipr_test_network" + - ipr2.for_virtual_network == false + +- name: test create a single IP VLAN IP RANGE + cs_vlan_ip_range: + network: ipr_test_network + start_ip: 10.2.4.200 + gateway: 10.2.4.1 + netmask: 255.255.255.0 + zone: "{{ cs_common_zone_adv }}" + register: ipr3 +- name: verify test create single IP VLAN IP RANGE + assert: + that: + - ipr3 is successful + - ipr3 is changed + - ipr3.vlan == "vlan://98" + - ipr3.start_ip == "10.2.4.200" + - ipr3.end_ip == "10.2.4.200" + - ipr3.gateway == "10.2.4.1" + - ipr3.netmask == "255.255.255.0" + - ipr3.network == "ipr_test_network" + - ipr3.for_virtual_network == false + +- name: test create an IPv4 + IPv6 VLAN IP RANGE + cs_vlan_ip_range: + network: ipr_test_network + vlan: 98 + start_ip: 10.2.4.151 + end_ip: 10.2.4.199 + gateway: 10.2.4.1 + netmask: 255.255.255.0 + start_ipv6: 2001:db8::10 + end_ipv6: 2001:db8::50 + gateway_ipv6: 2001:db8::1 + cidr_ipv6: 2001:db8::/64 + zone: "{{ cs_common_zone_adv }}" + register: iprv6 +- name: verify test create an IPv4 + IPv6 VLAN IP RANGE + assert: + that: + - iprv6 is successful + - iprv6 is changed + - iprv6.vlan == "vlan://98" + - iprv6.start_ip == "10.2.4.151" + - iprv6.end_ip == "10.2.4.199" + - iprv6.gateway == "10.2.4.1" + - iprv6.netmask == "255.255.255.0" + - iprv6.start_ipv6 == "2001:db8::10" + - iprv6.end_ipv6 == "2001:db8::50" + - iprv6.gateway_ipv6 == "2001:db8::1" + - iprv6.cidr_ipv6 == "2001:db8::/64" + - iprv6.network == "ipr_test_network" + - iprv6.for_virtual_network == false + +- name: test cleanup VLAN IP RANGE in check mode + cs_vlan_ip_range: + network: ipr_test_network + start_ip: 10.2.4.10 + end_ip: 10.2.4.100 + zone: "{{ cs_common_zone_adv }}" + state: absent + check_mode: true + register: ipr +- name: verify test cleanup VLAN IP RANGE in check mode + assert: + that: + - ipr is successful + - ipr is changed + - ipr.vlan == "vlan://98" + - ipr.start_ip == "10.2.4.10" + - ipr.end_ip == "10.2.4.100" + - ipr.gateway == "10.2.4.1" + - ipr.netmask == "255.255.255.0" + - ipr.network == "ipr_test_network" + - ipr.for_virtual_network == false + +- name: test cleanup VLAN IP RANGE + cs_vlan_ip_range: + network: ipr_test_network + start_ip: 10.2.4.10 + end_ip: 10.2.4.100 + zone: "{{ cs_common_zone_adv }}" + state: absent + register: ipr +- name: verify test cleanup VLAN IP RANGE + assert: + that: + - ipr is successful + - ipr is changed + - ipr.vlan == "vlan://98" + - ipr.start_ip == "10.2.4.10" + - ipr.end_ip == "10.2.4.100" + - ipr.gateway == "10.2.4.1" + - ipr.netmask == "255.255.255.0" + - ipr.network == "ipr_test_network" + - ipr.for_virtual_network == false + +- name: test cleanup VLAN IP RANGE idempotence + cs_vlan_ip_range: + network: ipr_test_network + start_ip: 10.2.4.10 + end_ip: 10.2.4.100 + zone: "{{ cs_common_zone_adv }}" + state: absent + register: ipr +- name: verify test cleanup VLAN IP RANGE idempotence + assert: + that: + - ipr is successful + - ipr is not changed + +- name: test cleanup single IP VLAN IP RANGE + cs_vlan_ip_range: + network: ipr_test_network + start_ip: 10.2.4.200 + zone: "{{ cs_common_zone_adv }}" + state: absent + register: ipr +- name: verify test cleanup single IP VLAN IP RANGE + assert: + that: + - ipr is successful + - ipr is changed + - ipr.vlan == "vlan://98" + - ipr.start_ip == "10.2.4.200" + - ipr.end_ip == "10.2.4.200" + - ipr.gateway == "10.2.4.1" + - ipr.netmask == "255.255.255.0" + - ipr.network == "ipr_test_network" + - ipr.for_virtual_network == false + +- name: cleanup second VLAN IP RANGE + cs_vlan_ip_range: + network: ipr_test_network + start_ip: 10.2.4.101 + end_ip: 10.2.4.150 + zone: "{{ cs_common_zone_adv }}" + state: absent + register: ipr2 +- name: verify cleanup second VLAN IP RANGE + assert: + that: + - ipr2 is successful + - ipr2 is changed + - ipr2.vlan == "vlan://98" + - ipr2.start_ip == "10.2.4.101" + - ipr2.end_ip == "10.2.4.150" + - ipr2.gateway == "10.2.4.1" + - ipr2.netmask == "255.255.255.0" + - ipr2.network == "ipr_test_network" + - ipr2.for_virtual_network == false + +- name: test cleanup IPv4 + IPv6 VLAN IP RANGE + cs_vlan_ip_range: + network: ipr_test_network + start_ip: 10.2.4.151 + end_ip: 10.2.4.199 + state: absent + zone: "{{ cs_common_zone_adv }}" + register: iprv6 +- name: verify test cleanup IPv4 + IPv6 VLAN IP RANGE + assert: + that: + - iprv6 is successful + - iprv6 is changed + - iprv6.vlan == "vlan://98" + - iprv6.start_ip == "10.2.4.151" + - iprv6.end_ip == "10.2.4.199" + - iprv6.gateway == "10.2.4.1" + - iprv6.netmask == "255.255.255.0" + - iprv6.start_ipv6 == "2001:db8::10" + - iprv6.end_ipv6 == "2001:db8::50" + - iprv6.gateway_ipv6 == "2001:db8::1" + - iprv6.cidr_ipv6 == "2001:db8::/64" + - iprv6.network == "ipr_test_network" + - iprv6.for_virtual_network == false + +- name: cleanup test network + cs_network: + name: ipr_test_network + zone: "{{ cs_common_zone_adv }}" + state: absent + register: ipr_net +- name: verify cleanup test network + assert: + that: + - ipr_net is successful + - ipr_net is changed