From 5700b0961063085c9c08f7e9df60eedc4c320cea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Moser?= Date: Fri, 4 Aug 2017 15:01:12 +0200 Subject: [PATCH] cloudstack: new module cs_instance_nic_secondaryip, replaces cs_nic (#26491) --- .../cloudstack/{cs_nic.py => _cs_nic.py} | 3 +- .../cloudstack/cs_instance_nic_secondaryip.py | 273 ++++++++++++++++++ .../cs_instance_nic_secondaryip/aliases | 2 + .../cs_instance_nic_secondaryip/meta/main.yml | 3 + .../tasks/main.yml | 220 ++++++++++++++ test/sanity/pep8/legacy-files.txt | 2 +- 6 files changed, 501 insertions(+), 2 deletions(-) rename lib/ansible/modules/cloud/cloudstack/{cs_nic.py => _cs_nic.py} (98%) create mode 100644 lib/ansible/modules/cloud/cloudstack/cs_instance_nic_secondaryip.py create mode 100644 test/integration/targets/cs_instance_nic_secondaryip/aliases create mode 100644 test/integration/targets/cs_instance_nic_secondaryip/meta/main.yml create mode 100644 test/integration/targets/cs_instance_nic_secondaryip/tasks/main.yml diff --git a/lib/ansible/modules/cloud/cloudstack/cs_nic.py b/lib/ansible/modules/cloud/cloudstack/_cs_nic.py similarity index 98% rename from lib/ansible/modules/cloud/cloudstack/cs_nic.py rename to lib/ansible/modules/cloud/cloudstack/_cs_nic.py index e819c4041f8..b562cfd90c5 100644 --- a/lib/ansible/modules/cloud/cloudstack/cs_nic.py +++ b/lib/ansible/modules/cloud/cloudstack/_cs_nic.py @@ -19,7 +19,7 @@ # along with Ansible. If not, see . ANSIBLE_METADATA = {'metadata_version': '1.0', - 'status': ['stableinterface'], + 'status': ['deprecated'], 'supported_by': 'community'} @@ -31,6 +31,7 @@ description: - Add and remove secondary IPs to and from a NIC. version_added: "2.3" author: "René Moser (@resmo)" +deprecated: Deprecated in 2.4. Use M(cs_instance_nic_secondaryip) instead. options: vm: description: diff --git a/lib/ansible/modules/cloud/cloudstack/cs_instance_nic_secondaryip.py b/lib/ansible/modules/cloud/cloudstack/cs_instance_nic_secondaryip.py new file mode 100644 index 00000000000..40204a501f8 --- /dev/null +++ b/lib/ansible/modules/cloud/cloudstack/cs_instance_nic_secondaryip.py @@ -0,0 +1,273 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# (c) 2017, René Moser +# +# This file is part of Ansible +# +# Ansible 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. +# +# Ansible 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 Ansible. If not, see . + +ANSIBLE_METADATA = {'metadata_version': '1.0', + 'status': ['preview'], + 'supported_by': 'community'} + + +DOCUMENTATION = ''' +--- +module: cs_instance_nic_secondaryip +short_description: Manages secondary IPs of an instance on Apache CloudStack based clouds. +description: + - Add and remove secondary IPs to and from a NIC of an instance. +version_added: "2.4" +author: "René Moser (@resmo)" +options: + vm: + description: + - Name of instance. + required: true + aliases: [ name ] + network: + description: + - Name of the network. + - Required to find the NIC if instance has multiple networks assigned. + vm_guest_ip: + description: + - Secondary IP address to be added to the instance nic. + - If not set, the API always returns a new IP address and idempotency is not given. + aliases: [ secondary_ip ] + vpc: + description: + - Name of the VPC the C(vm) is related to. + domain: + description: + - Domain the instance is related to. + account: + description: + - Account the instance is related to. + project: + description: + - Name of the project the instance is deployed in. + zone: + description: + - Name of the zone in which the instance is deployed in. + - If not set, default zone is used. + state: + description: + - State of the ipaddress. + default: present + choices: [ present, absent ] + poll_async: + description: + - Poll async jobs until job has finished. + default: true +extends_documentation_fragment: cloudstack +''' + +EXAMPLES = ''' +- name: Assign a specific IP to the default NIC of the VM + local_action: + module: cs_instance_nic_secondaryip + vm: customer_xy + vm_guest_ip: 10.10.10.10 + +# Note: If vm_guest_ip is not set, you will get a new IP address on every run. +- name: Assign an IP to the default NIC of the VM + local_action: + module: cs_instance_nic_secondaryip + vm: customer_xy + +- name: Remove a specific IP from the default NIC + local_action: + module: cs_instance_nic_secondaryip + vm: customer_xy + vm_guest_ip: 10.10.10.10 + state: absent +''' + +RETURN = ''' +--- +id: + description: UUID of the NIC. + returned: success + type: string + sample: 87b1e0ce-4e01-11e4-bb66-0050569e64b8 +vm: + description: Name of the VM. + returned: success + type: string + sample: web-01 +ip_address: + description: Primary IP of the NIC. + returned: success + type: string + sample: 10.10.10.10 +netmask: + description: Netmask of the NIC. + returned: success + type: string + sample: 255.255.255.0 +mac_address: + description: MAC address of the NIC. + returned: success + type: string + sample: 02:00:33:31:00:e4 +vm_guest_ip: + description: Secondary IP of the NIC. + returned: success + type: string + sample: 10.10.10.10 +network: + description: Name of the network if not default. + returned: success + type: string + sample: sync network +domain: + description: Domain the VM is related to. + returned: success + type: string + sample: example domain +account: + description: Account the VM is related to. + returned: success + type: string + sample: example account +project: + description: Name of project the VM is related to. + returned: success + type: string + sample: Production +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together +) + + +class AnsibleCloudStackInstanceNicSecondaryIp(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackInstanceNicSecondaryIp, self).__init__(module) + self.vm_guest_ip = self.module.params.get('vm_guest_ip') + self.nic = None + self.returns = { + 'ipaddress': 'ip_address', + 'macaddress': 'mac_address', + 'netmask': 'netmask', + } + + def get_nic(self): + if self.nic: + return self.nic + args = { + 'virtualmachineid': self.get_vm(key='id'), + 'networkid': self.get_network(key='id'), + } + nics = self.query_api('listNics', **args) + if nics: + self.nic = nics['nic'][0] + return self.nic + self.fail_json(msg="NIC for VM %s in network %s not found" % (self.get_vm(key='name'), self.get_network(key='name'))) + + def get_secondary_ip(self): + nic = self.get_nic() + if self.vm_guest_ip: + secondary_ips = nic.get('secondaryip') or [] + for secondary_ip in secondary_ips: + if secondary_ip['ipaddress'] == self.vm_guest_ip: + return secondary_ip + return None + + def present_nic_ip(self): + nic = self.get_nic() + if not self.get_secondary_ip(): + self.result['changed'] = True + args = { + 'nicid': nic['id'], + 'ipaddress': self.vm_guest_ip, + } + + if not self.module.check_mode: + res = self.query_api('addIpToNic', **args) + + poll_async = self.module.params.get('poll_async') + if poll_async: + nic = self.poll_job(res, 'nicsecondaryip') + # Save result for RETURNS + self.vm_guest_ip = nic['ipaddress'] + return nic + + def absent_nic_ip(self): + nic = self.get_nic() + secondary_ip = self.get_secondary_ip() + if secondary_ip: + self.result['changed'] = True + if not self.module.check_mode: + res = self.query_api('removeIpFromNic', id=secondary_ip['id']) + + poll_async = self.module.params.get('poll_async') + if poll_async: + self.poll_job(res, 'nicsecondaryip') + return nic + + def get_result(self, nic): + super(AnsibleCloudStackInstanceNicSecondaryIp, self).get_result(nic) + if nic and not self.module.params.get('network'): + self.module.params['network'] = nic.get('networkid') + self.result['network'] = self.get_network(key='name') + self.result['vm'] = self.get_vm(key='name') + self.result['vm_guest_ip'] = self.vm_guest_ip + return self.result + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + vm=dict(required=True, aliases=['name']), + vm_guest_ip=dict(aliases=['secondary_ip']), + network=dict(), + vpc=dict(), + state=dict(choices=['present', 'absent'], default='present'), + domain=dict(), + account=dict(), + project=dict(), + zone=dict(), + poll_async=dict(type='bool', default=True), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True, + required_if=([ + ('state', 'absent', ['vm_guest_ip']) + ]) + ) + + acs_instance_nic_secondaryip = AnsibleCloudStackInstanceNicSecondaryIp(module) + state = module.params.get('state') + + if state == 'absent': + nic = acs_instance_nic_secondaryip.absent_nic_ip() + else: + nic = acs_instance_nic_secondaryip.present_nic_ip() + + result = acs_instance_nic_secondaryip.get_result(nic) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/cs_instance_nic_secondaryip/aliases b/test/integration/targets/cs_instance_nic_secondaryip/aliases new file mode 100644 index 00000000000..ba249b99d73 --- /dev/null +++ b/test/integration/targets/cs_instance_nic_secondaryip/aliases @@ -0,0 +1,2 @@ +cloud/cs +posix/ci/cloud/cs diff --git a/test/integration/targets/cs_instance_nic_secondaryip/meta/main.yml b/test/integration/targets/cs_instance_nic_secondaryip/meta/main.yml new file mode 100644 index 00000000000..e9a5b9eeaef --- /dev/null +++ b/test/integration/targets/cs_instance_nic_secondaryip/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: + - cs_common diff --git a/test/integration/targets/cs_instance_nic_secondaryip/tasks/main.yml b/test/integration/targets/cs_instance_nic_secondaryip/tasks/main.yml new file mode 100644 index 00000000000..536103f6dee --- /dev/null +++ b/test/integration/targets/cs_instance_nic_secondaryip/tasks/main.yml @@ -0,0 +1,220 @@ +--- +- name: setup network + cs_network: + name: "net_nic" + zone: "{{ cs_common_zone_adv }}" + network_offering: DefaultSharedNetworkOffering + network_domain: example.com + vlan: 1234 + start_ip: 10.100.123.11 + end_ip: 10.100.123.250 + gateway: 10.100.123.1 + netmask: 255.255.255.0 + register: net +- name: verify setup network + assert: + that: + - net|success + - net.name == "net_nic" + +- name: setup instance + cs_instance: + name: "instance-nic-vm" + template: "{{ cs_common_template }}" + service_offering: "{{ cs_common_service_offering }}" + zone: "{{ cs_common_zone_adv }}" + network: "net_nic" + state: stopped + register: instance +- name: verify setup instance + assert: + that: + - instance|success + - instance.name == "instance-nic-vm" + - instance.state == "Stopped" + +- name: setup network 2 + cs_network: + name: "net_nic2" + zone: "{{ cs_common_zone_adv }}" + network_offering: DefaultSharedNetworkOffering + network_domain: example.com + vlan: 1235 + start_ip: 10.100.124.11 + end_ip: 10.100.124.250 + gateway: 10.100.124.1 + netmask: 255.255.255.0 + register: net +- name: verify setup network 2 + assert: + that: + - net|success + - net.name == "net_nic2" + +- name: setup nic + cs_instance_nic: + vm: "instance-nic-vm" + network: "net_nic2" + ip_address: 10.100.124.42 + zone: "{{ cs_common_zone_adv }}" + register: nic +- name: verify test create nic + assert: + that: + - nic|success + - nic.ip_address == "10.100.124.42" + - nic.netmask == "255.255.255.0" + - nic.network == "net_nic2" + - nic.vm == "instance-nic-vm" + - nic.zone == "{{ cs_common_zone_adv }}" + - nic.mac_address is defined + +- name: setup remove secondary ip + cs_instance_nic_secondaryip: + vm: "instance-nic-vm" + network: "net_nic2" + vm_guest_ip: 10.100.124.43 + zone: "{{ cs_common_zone_adv }}" + state: absent + register: sip +- name: verify setup remove secondary ip + assert: + that: + - sip|success + +- name: test add secondary ip in check mode + cs_instance_nic_secondaryip: + vm: "instance-nic-vm" + network: "net_nic2" + vm_guest_ip: 10.100.124.43 + zone: "{{ cs_common_zone_adv }}" + check_mode: true + register: sip +- name: verify test add secondary ip in check mode + assert: + that: + - sip|success + - sip|changed + - sip.network == "net_nic2" + - sip.vm == "instance-nic-vm" + - sip.zone == "{{ cs_common_zone_adv }}" + +- name: test add secondary ip + cs_instance_nic_secondaryip: + vm: "instance-nic-vm" + network: "net_nic2" + vm_guest_ip: 10.100.124.43 + zone: "{{ cs_common_zone_adv }}" + register: sip +- name: verify test add secondary ip + assert: + that: + - sip|success + - sip|changed + - sip.vm_guest_ip == "10.100.124.43" + - sip.network == "net_nic2" + - sip.vm == "instance-nic-vm" + - sip.zone == "{{ cs_common_zone_adv }}" + +- name: test add secondary ip idempotence + cs_instance_nic_secondaryip: + vm: "instance-nic-vm" + network: "net_nic2" + vm_guest_ip: 10.100.124.43 + zone: "{{ cs_common_zone_adv }}" + register: sip +- name: verify test add secondary ip idempotence + assert: + that: + - sip|success + - not sip|changed + - sip.vm_guest_ip == "10.100.124.43" + - sip.network == "net_nic2" + - sip.vm == "instance-nic-vm" + - sip.zone == "{{ cs_common_zone_adv }}" + +- name: test remove secondary ip in check mode + cs_instance_nic_secondaryip: + vm: "instance-nic-vm" + network: "net_nic2" + vm_guest_ip: 10.100.124.43 + zone: "{{ cs_common_zone_adv }}" + state: absent + check_mode: true + register: sip +- name: verify test remove secondary ip in check mode + assert: + that: + - sip|success + - sip|changed + - sip.vm_guest_ip == "10.100.124.43" + - sip.network == "net_nic2" + - sip.vm == "instance-nic-vm" + - sip.zone == "{{ cs_common_zone_adv }}" + +- name: test remove secondary ip + cs_instance_nic_secondaryip: + vm: "instance-nic-vm" + network: "net_nic2" + vm_guest_ip: 10.100.124.43 + zone: "{{ cs_common_zone_adv }}" + state: absent + register: sip +- name: verify test remove secondary ip + assert: + that: + - sip|success + - sip|changed + - sip.vm_guest_ip == "10.100.124.43" + - sip.network == "net_nic2" + - sip.vm == "instance-nic-vm" + - sip.zone == "{{ cs_common_zone_adv }}" + +- name: test remove secondary ip idempotence + cs_instance_nic_secondaryip: + vm: "instance-nic-vm" + network: "net_nic2" + vm_guest_ip: 10.100.124.43 + zone: "{{ cs_common_zone_adv }}" + state: absent + register: sip +- name: verify test remove secondary ip idempotence + assert: + that: + - sip|success + - not sip|changed + - sip.network == "net_nic2" + - sip.vm == "instance-nic-vm" + - sip.zone == "{{ cs_common_zone_adv }}" + +- name: cleanup instance + cs_instance: + name: "instance-nic-vm" + state: expunged + register: instance +- name: verify cleanup instance + assert: + that: + - instance|success + +- name: cleanup network + cs_network: + name: "net_nic" + zone: "{{ cs_common_zone_adv }}" + state: absent + register: net +- name: verify cleanup network + assert: + that: + - net|success + +- name: cleanup network 2 + cs_network: + name: "net_nic2" + zone: "{{ cs_common_zone_adv }}" + state: absent + register: net +- name: verify cleanup network 2 + assert: + that: + - net|success diff --git a/test/sanity/pep8/legacy-files.txt b/test/sanity/pep8/legacy-files.txt index 4ac40e9df46..f17e7f08a97 100644 --- a/test/sanity/pep8/legacy-files.txt +++ b/test/sanity/pep8/legacy-files.txt @@ -80,7 +80,7 @@ lib/ansible/modules/cloud/centurylink/clc_loadbalancer.py lib/ansible/modules/cloud/cloudscale/cloudscale_server.py lib/ansible/modules/cloud/cloudstack/cs_instance.py lib/ansible/modules/cloud/cloudstack/cs_instance_facts.py -lib/ansible/modules/cloud/cloudstack/cs_nic.py +lib/ansible/modules/cloud/cloudstack/_cs_nic.py lib/ansible/modules/cloud/cloudstack/cs_portforward.py lib/ansible/modules/cloud/cloudstack/cs_securitygroup.py lib/ansible/modules/cloud/cloudstack/cs_securitygroup_rule.py