From c22fbdc8314239ff9d5aa7f7bf65785c1a672add Mon Sep 17 00:00:00 2001 From: Abdoul Bah Date: Wed, 15 Feb 2017 23:51:46 +0100 Subject: [PATCH] resolve #21056 - Add support for full cloning a Qemu VM. Fix some issues. Update doc (#21225) * resolve #21056 - Add support for full cloning. Fix some issues. Update doc * Fix condition and update doc. Fixes #28585 * Using built-in helper for argspec, revert ansible metadata, add more control and remove type in documentation * PEP8 style compliance --- lib/ansible/modules/cloud/misc/proxmox_kvm.py | 876 +++++++++++------- 1 file changed, 529 insertions(+), 347 deletions(-) diff --git a/lib/ansible/modules/cloud/misc/proxmox_kvm.py b/lib/ansible/modules/cloud/misc/proxmox_kvm.py index 25d80fff69..f37a504c0f 100644 --- a/lib/ansible/modules/cloud/misc/proxmox_kvm.py +++ b/lib/ansible/modules/cloud/misc/proxmox_kvm.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# (c) 2016, Abdoul Bah (@helldorado) +# (c) 2016, Abdoul Bah (@helldorado) """ Ansible module to manage Qemu(KVM) instance in Proxmox VE cluster. @@ -28,7 +28,7 @@ short_description: Management of Qemu(KVM) Virtual Machines in Proxmox VE cluste description: - Allows you to create/delete/stop Qemu(KVM) Virtual Machines in Proxmox VE cluster. version_added: "2.3" -author: "Abdoul Bah (@helldorado) " +author: "Abdoul Bah (@helldorado) " options: acpi: description: @@ -36,21 +36,18 @@ options: required: false default: "yes" choices: [ "yes", "no" ] - type: boolean agent: description: - Specify if the QEMU GuestAgent should be enabled/disabled. required: false default: null choices: [ "yes", "no" ] - type: boolean args: description: - Pass arbitrary arguments to kvm. - This option is for experts only! default: "-serial unix:/var/run/qemu-server/VMID.serial,server,nowait" required: false - type: string api_host: description: - Specify the target host of the Proxmox VE cluster. @@ -71,80 +68,73 @@ options: required: false default: "no" choices: [ "yes", "no" ] - type: boolean balloon: description: - Specify the amount of RAM for the VM in MB. - Using zero disables the balloon driver. required: false default: 0 - type: integer bios: description: - Specify the BIOS implementation. choices: ['seabios', 'ovmf'] required: false default: null - type: string boot: description: - Specify the boot order -> boot on floppy C(a), hard disk C(c), CD-ROM C(d), or network C(n). - You can combine to set order. required: false default: cnd - type: string bootdisk: description: - Enable booting from specified disk. C((ide|sata|scsi|virtio)\d+) required: false default: null - type: string + clone: + description: + - Name of VM to be cloned. If C(vmid) is setted, C(clone) can take arbitrary value but required for intiating the clone. + required: false + default: null cores: description: - Specify number of cores per socket. required: false default: 1 - type: integer cpu: description: - Specify emulated CPU type. required: false default: kvm64 - type: string cpulimit: description: - Specify if CPU usage will be limited. Value 0 indicates no CPU limit. - If the computer has 2 CPUs, it has total of '2' CPU time required: false default: null - type: integer cpuunits: description: - Specify CPU weight for a VM. - You can disable fair-scheduler configuration by setting this to 0 default: 1000 required: false - type: integer delete: description: - Specify a list of settings you want to delete. required: false default: null - type: string description: description: - Specify the description for the VM. Only used on the configuration web interface. - This is saved as comment inside the configuration file. required: false default: null - type: string digest: description: - Specify if to prevent changes if current configuration file has different SHA1 digest. - This can be used to prevent concurrent modifications. required: false default: null - type: string force: description: - Allow to force stop VM. @@ -152,14 +142,27 @@ options: default: null choices: [ "yes", "no" ] required: false - type: boolean + format: + description: + - Target drive’s backing file’s data format. + - Used only with clone + default: qcow2 + choices: [ "cloop", "cow", "qcow", "qcow2", "qed", "raw", "vmdk" ] + required: false freeze: description: - Specify if PVE should freeze CPU at startup (use 'c' monitor command to start execution). required: false default: null choices: [ "yes", "no" ] - type: boolean + full: + description: + - Create a full copy of all disk. This is always done when you clone a normal VM. + - For VM templates, we try to create a linked clone by default. + - Used only with clone + default: yes + choices: [ "yes", "no"] + required: false hostpci: description: - Specify a hash/dictionary of map host pci devices into guest. C(hostpci='{"key":"value", "key":"value"}'). @@ -172,7 +175,6 @@ options: - /!\ This option allows direct access to host hardware. So it is no longer possible to migrate such machines - use with special care. required: false default: null - type: A hash/dictionary defining host pci devices hotplug: description: - Selectively enable hotplug features. @@ -180,14 +182,12 @@ options: - Value 0 disables hotplug completely and value 1 is an alias for the default C('network,disk,usb'). required: false default: null - type: string hugepages: description: - Enable/disable hugepages memory. choices: ['any', '2', '1024'] required: false default: null - type: string ide: description: - A hash/dictionary of volume used as IDE hard disk or CD-ROM. C(ide='{"key":"value", "key":"value"}'). @@ -198,20 +198,17 @@ options: - C(format) is the drive’s backing file’s data format. C(qcow2|raw|subvol). required: false default: null - type: A hash/dictionary defining ide keyboard: description: - Sets the keyboard layout for VNC server. required: false default: null - type: string kvm: description: - Enable/disable KVM hardware virtualization. required: false default: "yes" choices: [ "yes", "no" ] - type: boolean localtime: description: - Sets the real time clock to local time. @@ -219,40 +216,34 @@ options: required: false default: null choices: [ "yes", "no" ] - type: boolean lock: description: - Lock/unlock the VM. choices: ['migrate', 'backup', 'snapshot', 'rollback'] required: false default: null - type: string machine: description: - Specifies the Qemu machine type. - type => C((pc|pc(-i440fx)?-\d+\.\d+(\.pxe)?|q35|pc-q35-\d+\.\d+(\.pxe)?)) required: false default: null - type: string memory: description: - Memory size in MB for instance. required: false default: 512 - type: integer migrate_downtime: description: - Sets maximum tolerated downtime (in seconds) for migrations. required: false default: null - type: integer migrate_speed: description: - Sets maximum speed (in MB/s) for migrations. - A value of 0 is no limit. required: false default: null - type: integer name: description: - Specifies the VM name. Only used on the configuration web interface. @@ -271,7 +262,12 @@ options: - If you specify no bridge, we create a kvm 'user' (NATed) network device, which provides DHCP and DNS services. default: null required: false - type: A hash/dictionary defining interfaces + newid: + description: + - VMID for the clone. Used only with clone. + - If newid is not set, the next available VM ID will be fetched from ProxmoxAPI. + default: null + required: false node: description: - Proxmox VE node, where the new VM will be created. @@ -290,14 +286,12 @@ options: - C(policy) NUMA allocation policy. default: null required: false - type: A hash/dictionary defining NUMA topology onboot: description: - Specifies whether a VM will be started during system bootup. default: "yes" choices: [ "yes", "no" ] required: false - type: boolean ostype: description: - Specifies guest operating system. This is used to enable special optimization/features for specific operating systems. @@ -305,7 +299,6 @@ options: choices: ['other', 'wxp', 'w2k', 'w2k3', 'w2k8', 'wvista', 'win7', 'win8', 'l24', 'l26', 'solaris'] default: l26 required: false - type: string parallel: description: - A hash/dictionary of map host parallel devices. C(parallel='{"key":"value", "key":"value"}'). @@ -313,27 +306,28 @@ options: - Values allowed are - C("/dev/parport\d+|/dev/usb/lp\d+"). default: null required: false - type: A hash/dictionary defining host parallel devices + pool: + description: + - Add the new VM to the specified pool. + default: null + required: false protection: description: - Enable/disable the protection flag of the VM. This will enable/disable the remove VM and remove disk operations. default: null choices: [ "yes", "no" ] required: false - type: boolean reboot: description: - Allow reboot. If set to yes, the VM exit on reboot. default: null choices: [ "yes", "no" ] required: false - type: boolean revert: description: - Revert a pending change. default: null required: false - type: string sata: description: - A hash/dictionary of volume used as sata hard disk or CD-ROM. C(sata='{"key":"value", "key":"value"}'). @@ -344,7 +338,6 @@ options: - C(format) is the drive’s backing file’s data format. C(qcow2|raw|subvol). default: null required: false - type: A hash/dictionary defining sata scsi: description: - A hash/dictionary of volume used as SCSI hard disk or CD-ROM. C(scsi='{"key":"value", "key":"value"}'). @@ -355,14 +348,12 @@ options: - C(format) is the drive’s backing file’s data format. C(qcow2|raw|subvol). default: null required: false - type: A hash/dictionary defining scsi scsihw: description: - Specifies the SCSI controller model. choices: ['lsi', 'lsi53c810', 'virtio-scsi-pci', 'virtio-scsi-single', 'megasas', 'pvscsi'] required: false default: null - type: string serial: description: - A hash/dictionary of serial device to create inside the VM. C('{"key":"value", "key":"value"}'). @@ -371,7 +362,6 @@ options: - /!\ If you pass through a host serial device, it is no longer possible to migrate such machines - use with special care. default: null required: false - type: A hash/dictionary defining serial shares: description: - Rets amount of memory shares for auto-ballooning. (0 - 50000). @@ -380,34 +370,33 @@ options: - Using 0 disables auto-ballooning, this means no limit. required: false default: null - type: integer skiplock: description: - Ignore locks - Only root is allowed to use this option. required: false default: null - choices: [ "yes", "no" ] - type: boolean smbios: description: - Specifies SMBIOS type 1 fields. required: false default: null - type: string + snapname: + description: + - The name of the snapshot. Used only with clone. + default: null + required: false sockets: description: - Sets the number of CPU sockets. (1 - N). required: false default: 1 - type: integer startdate: description: - Sets the initial date of the real time clock. - Valid format for date are C('now') or C('2016-09-25T16:01:21') or C('2016-09-25'). required: false default: null - type: string startup: description: - Startup and shutdown behavior. C([[order=]\d+] [,up=\d+] [,down=\d+]). @@ -415,7 +404,6 @@ options: - Shutdown in done with reverse ordering. required: false default: null - type: string state: description: - Indicates desired state of the instance. @@ -423,46 +411,59 @@ options: choices: ['present', 'started', 'absent', 'stopped', 'restarted','current'] required: false default: present + storage: + description: + - Target storage for full clone. + default: null + required: false tablet: description: - Enables/disables the USB tablet device. required: false choices: [ "yes", "no" ] default: "no" - type: boolean + target: + description: + - Target node. Only allowed if the original VM is on shared storage. + - Used only with clone + default: null + required: false tdf: description: - Enables/disables time drift fix. required: false default: null choices: [ "yes", "no" ] - type: boolean template: description: - Enables/disables the template. required: false default: "no" choices: [ "yes", "no" ] - type: boolean timeout: description: - Timeout for operations. default: 30 required: false - type: integer + update: + description: + - If C(yes), the VM will be update with new value. + - Cause of the operations of the API and security reasons, I have disabled the update of the following parameters + - C(net, virtio, ide, sata, scsi). Per example updating C(net) update the MAC address and C(virtio) create always new disk... + default: "no" + choices: [ "yes", "no" ] + required: false validate_certs: description: - If C(no), SSL certificates will not be validated. This should only be used on personally controlled sites using self-signed certificates. default: "no" choices: [ "yes", "no" ] required: false - type: boolean vcpus: description: - Sets number of hotplugged vcpus. required: false default: null - type: integer vga: description: - Select VGA type. If you want to use high resolution modes (>= 1280x1024x16) then you should use option 'std' or 'vmware'. @@ -479,7 +480,6 @@ options: - C(format) is the drive’s backing file’s data format. C(qcow2|raw|subvol). required: false default: null - type: A hash/dictionary defining virtio vmid: description: - Specifies the VM ID. Instead use I(name) parameter. @@ -491,7 +491,6 @@ options: - Creates a virtual hardware watchdog device. required: false default: null - type: string Notes: - Requires proxmoxer and requests modules on host. This modules can be installed with pip. requirements: [ "proxmoxer", "requests" ] @@ -536,6 +535,32 @@ EXAMPLES = ''' cores : 4 vcpus : 2 +# Clone VM with only source VM name +- proxmox_kvm: + api_user : root@pam + api_password: secret + api_host : helldorado + clone : spynal # The VM source + name : zavala # The target VM name + node : sabrewulf + storage : VMs + format : qcow2 + timeout : 500 # Note: The task can take a while. Adapt + +# Clone VM with source vmid and target newid and raw format +- proxmox_kvm: + api_user : root@pam + api_password: secret + api_host : helldorado + clone : arbitrary_name + vmid : 108 + newid : 152 + name : zavala # The target VM name + node : sabrewulf + storage : LVM_STO + format : raw + timeout : 300 # Note: The task can take a while. Adapt + # Create new VM and lock it for snapashot. - proxmox_kvm: api_user : root@pam @@ -608,6 +633,35 @@ EXAMPLES = ''' name : spynal node : sabrewulf state : current + +# Update VM configuration +- proxmox_kvm: + api_user : root@pam + api_password: secret + api_host : helldorado + name : spynal + node : sabrewulf + cpu : 8 + memory : 16384 + update : yes + +# Delete QEMU parameters +- proxmox_kvm: + api_user : root@pam + api_password: secret + api_host : helldorado + name : spynal + node : sabrewulf + delete : 'args,template,cpulimit' + +# Revert a pending change +- proxmox_kvm: + api_user : root@pam + api_password: secret + api_host : helldorado + name : spynal + node : sabrewulf + revert : 'template,cpulimit' ''' RETURN = ''' @@ -655,266 +709,380 @@ import time try: - from proxmoxer import ProxmoxAPI - HAS_PROXMOXER = True + from proxmoxer import ProxmoxAPI + HAS_PROXMOXER = True except ImportError: - HAS_PROXMOXER = False + HAS_PROXMOXER = False + +VZ_TYPE = 'qemu' -VZ_TYPE='qemu' def get_nextvmid(proxmox): - try: - vmid = proxmox.cluster.nextid.get() - return vmid - except Exception as e: - module.fail_json(msg="Unable to get next vmid. Failed with exception: %s") + try: + vmid = proxmox.cluster.nextid.get() + return vmid + except Exception as e: + module.fail_json(msg="Unable to get next vmid. Failed with exception: %s") + def get_vmid(proxmox, name): - return [ vm['vmid'] for vm in proxmox.cluster.resources.get(type='vm') if vm['name'] == name ] + return [vm['vmid'] for vm in proxmox.cluster.resources.get(type='vm') if vm['name'] == name] + def get_vm(proxmox, vmid): - return [ vm for vm in proxmox.cluster.resources.get(type='vm') if vm['vmid'] == int(vmid) ] + return [vm for vm in proxmox.cluster.resources.get(type='vm') if vm['vmid'] == int(vmid)] + def node_check(proxmox, node): - return [ True for nd in proxmox.nodes.get() if nd['node'] == node ] + return [True for nd in proxmox.nodes.get() if nd['node'] == node] + def get_vminfo(module, proxmox, node, vmid, **kwargs): - global results - results = {} - mac = {} - devices = {} - try: - vm = proxmox.nodes(node).qemu(vmid).config.get() - except Exception as e: - module.fail_json(msg='Getting information for VM with vmid = %s failed with exception: %s' % (vmid, e)) + global results + results = {} + mac = {} + devices = {} + try: + vm = proxmox.nodes(node).qemu(vmid).config.get() + except Exception as e: + module.fail_json(msg='Getting information for VM with vmid = %s failed with exception: %s' % (vmid, e)) - # Sanitize kwargs. Remove not defined args and ensure True and False converted to int. - kwargs = dict((k,v) for k, v in kwargs.items() if v is not None) + # Sanitize kwargs. Remove not defined args and ensure True and False converted to int. + kwargs = dict((k, v) for k, v in kwargs.items() if v is not None) - # Convert all dict in kwargs to elements. For hostpci[n], ide[n], net[n], numa[n], parallel[n], sata[n], scsi[n], serial[n], virtio[n] - for k in kwargs.keys(): - if isinstance(kwargs[k], dict): - kwargs.update(kwargs[k]) - del kwargs[k] + # Convert all dict in kwargs to elements. For hostpci[n], ide[n], net[n], numa[n], parallel[n], sata[n], scsi[n], serial[n], virtio[n] + for k in kwargs.keys(): + if isinstance(kwargs[k], dict): + kwargs.update(kwargs[k]) + del kwargs[k] - # Split information by type - for k, v in kwargs.items(): - if re.match(r'net[0-9]', k) is not None: + # Split information by type + for k, v in kwargs.items(): + if re.match(r'net[0-9]', k) is not None: interface = k k = vm[k] k = re.search('=(.*?),', k).group(1) mac[interface] = k - if re.match(r'virtio[0-9]', k) is not None or re.match(r'ide[0-9]', k) is not None or re.match(r'scsi[0-9]', k) is not None or re.match(r'sata[0-9]', k) is not None: + if re.match(r'virtio[0-9]', k) is not None or re.match(r'ide[0-9]', k) is not None or re.match(r'scsi[0-9]', k) is not None or re.match(r'sata[0-9]', k) is not None: device = k k = vm[k] k = re.search('(.*?),', k).group(1) devices[device] = k - results['mac'] = mac - results['devices'] = devices - results['vmid'] = int(vmid) + results['mac'] = mac + results['devices'] = devices + results['vmid'] = int(vmid) -def create_vm(module, proxmox, vmid, node, name, memory, cpu, cores, sockets, timeout, **kwargs): - # Available only in PVE 4 - only_v4 = ['force','protection','skiplock'] - # Default args for vm. Note: -args option is for experts only. It allows you to pass arbitrary arguments to kvm. - vm_args = "-serial unix:/var/run/qemu-server/{}.serial,server,nowait".format(vmid) - proxmox_node = proxmox.nodes(node) +def settings(module, proxmox, vmid, node, name, timeout, **kwargs): + proxmox_node = proxmox.nodes(node) - # Sanitize kwargs. Remove not defined args and ensure True and False converted to int. - kwargs = dict((k,v) for k, v in kwargs.items() if v is not None) - kwargs.update(dict([k, int(v)] for k, v in kwargs.items() if isinstance(v, bool))) + # Sanitize kwargs. Remove not defined args and ensure True and False converted to int. + kwargs = dict((k, v) for k, v in kwargs.items() if v is not None) - # The features work only on PVE 4 - if PVE_MAJOR_VERSION < 4: - for p in only_v4: - if p in kwargs: - del kwargs[p] + if getattr(proxmox_node, VZ_TYPE)(vmid).config.set(**kwargs) is None: + return True + else: + return False - # Convert all dict in kwargs to elements. For hostpci[n], ide[n], net[n], numa[n], parallel[n], sata[n], scsi[n], serial[n], virtio[n] - for k in kwargs.keys(): - if isinstance(kwargs[k], dict): - kwargs.update(kwargs[k]) - del kwargs[k] - # -args and skiplock require root@pam user - if module.params['api_user'] == "root@pam" and module.params['args'] is None: - kwargs['args'] = vm_args - elif module.params['api_user'] == "root@pam" and module.params['args'] is not None: - kwargs['args'] = module.params['args'] - elif module.params['api_user'] != "root@pam" and module.params['args'] is not None: - module.fail_json(msg='args parameter require root@pam user. ') +def create_vm(module, proxmox, vmid, newid, node, name, memory, cpu, cores, sockets, timeout, update, **kwargs): + # Available only in PVE 4 + only_v4 = ['force', 'protection', 'skiplock'] - if module.params['api_user'] != "root@pam" and module.params['skiplock'] is not None: - module.fail_json(msg='skiplock parameter require root@pam user. ') + # valide clone parameters + valid_clone_params = ['format', 'full', 'pool', 'snapname', 'storage', 'target'] + clone_params = {} + # Default args for vm. Note: -args option is for experts only. It allows you to pass arbitrary arguments to kvm. + vm_args = "-serial unix:/var/run/qemu-server/{}.serial,server,nowait".format(vmid) - taskid = getattr(proxmox_node, VZ_TYPE).create(vmid=vmid, name=name, memory=memory, cpu=cpu, cores=cores, sockets=sockets, **kwargs) + proxmox_node = proxmox.nodes(node) + + # Sanitize kwargs. Remove not defined args and ensure True and False converted to int. + kwargs = dict((k,v) for k, v in kwargs.items() if v is not None) + kwargs.update(dict([k, int(v)] for k, v in kwargs.items() if isinstance(v, bool))) + + # The features work only on PVE 4 + if PVE_MAJOR_VERSION < 4: + for p in only_v4: + if p in kwargs: + del kwargs[p] + + # If update, don't update disk (virtio, ide, sata, scsi) and network interface + if update: + if 'virtio' in kwargs: + del kwargs['virtio'] + if 'sata' in kwargs: + del kwargs['sata'] + if 'scsi' in kwargs: + del kwargs['scsi'] + if 'ide' in kwargs: + del kwargs['ide'] + if 'net' in kwargs: + del kwargs['net'] + + # Convert all dict in kwargs to elements. For hostpci[n], ide[n], net[n], numa[n], parallel[n], sata[n], scsi[n], serial[n], virtio[n] + for k in kwargs.keys(): + if isinstance(kwargs[k], dict): + kwargs.update(kwargs[k]) + del kwargs[k] + + # Rename numa_enabled to numa. According the API documentation + if 'numa_enabled' in kwargs: + kwargs['numa'] = kwargs['numa_enabled'] + del kwargs['numa_enabled'] + + # -args and skiplock require root@pam user + if module.params['api_user'] == "root@pam" and module.params['args'] is None: + if not update: + kwargs['args'] = vm_args + elif module.params['api_user'] == "root@pam" and module.params['args'] is not None: + kwargs['args'] = module.params['args'] + elif module.params['api_user'] != "root@pam" and module.params['args'] is not None: + module.fail_json(msg='args parameter require root@pam user. ') + + if module.params['api_user'] != "root@pam" and module.params['skiplock'] is not None: + module.fail_json(msg='skiplock parameter require root@pam user. ') + + if update: + if getattr(proxmox_node, VZ_TYPE)(vmid).config.set(name=name, memory=memory, cpu=cpu, cores=cores, sockets=sockets, **kwargs) is None: + return True + else: + return False + elif module.params['clone'] is not None: + for param in valid_clone_params: + if module.params[param] is not None: + clone_params[param] = module.params[param] + clone_params.update(dict([k, int(v)] for k, v in clone_params.items() if isinstance(v, bool))) + taskid = proxmox_node.qemu(vmid).clone.post(newid=newid, name=name, **clone_params) + else: + taskid = getattr(proxmox_node, VZ_TYPE).create(vmid=vmid, name=name, memory=memory, cpu=cpu, cores=cores, sockets=sockets, **kwargs) + + while timeout: + if (proxmox_node.tasks(taskid).status.get()['status'] == 'stopped' + and proxmox_node.tasks(taskid).status.get()['exitstatus'] == 'OK'): + return True + timeout = timeout - 1 + if timeout == 0: + module.fail_json(msg='Reached timeout while waiting for creating VM. Last line in task before timeout: %s' + % proxmox_node.tasks(taskid).log.get()[:1]) + time.sleep(1) + return False - while timeout: - if ( proxmox_node.tasks(taskid).status.get()['status'] == 'stopped' - and proxmox_node.tasks(taskid).status.get()['exitstatus'] == 'OK' ): - return True - timeout = timeout - 1 - if timeout == 0: - module.fail_json(msg='Reached timeout while waiting for creating VM. Last line in task before timeout: %s' - % proxmox_node.tasks(taskid).log.get()[:1]) - time.sleep(1) - return False def start_vm(module, proxmox, vm, vmid, timeout): - taskid = getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE)(vmid).status.start.post() - while timeout: - if ( proxmox.nodes(vm[0]['node']).tasks(taskid).status.get()['status'] == 'stopped' - and proxmox.nodes(vm[0]['node']).tasks(taskid).status.get()['exitstatus'] == 'OK' ): - return True - timeout = timeout - 1 - if timeout == 0: - module.fail_json(msg='Reached timeout while waiting for starting VM. Last line in task before timeout: %s' - % proxmox.nodes(vm[0]['node']).tasks(taskid).log.get()[:1]) + taskid = getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE)(vmid).status.start.post() + while timeout: + if (proxmox.nodes(vm[0]['node']).tasks(taskid).status.get()['status'] == 'stopped' + and proxmox.nodes(vm[0]['node']).tasks(taskid).status.get()['exitstatus'] == 'OK' ): + return True + timeout = timeout - 1 + if timeout == 0: + module.fail_json(msg='Reached timeout while waiting for starting VM. Last line in task before timeout: %s' + % proxmox.nodes(vm[0]['node']).tasks(taskid).log.get()[:1]) + + time.sleep(1) + return False - time.sleep(1) - return False def stop_vm(module, proxmox, vm, vmid, timeout, force): - if force: - taskid = getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE)(vmid).status.shutdown.post(forceStop=1) - else: - taskid = getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE)(vmid).status.shutdown.post() - while timeout: - if ( proxmox.nodes(vm[0]['node']).tasks(taskid).status.get()['status'] == 'stopped' - and proxmox.nodes(vm[0]['node']).tasks(taskid).status.get()['exitstatus'] == 'OK' ): - return True - timeout = timeout - 1 - if timeout == 0: - module.fail_json(msg='Reached timeout while waiting for stopping VM. Last line in task before timeout: %s' - % proxmox.nodes(vm[0]['node']).tasks(taskid).log.get()[:1]) + if force: + taskid = getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE)(vmid).status.shutdown.post(forceStop=1) + else: + taskid = getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE)(vmid).status.shutdown.post() + while timeout: + if (proxmox.nodes(vm[0]['node']).tasks(taskid).status.get()['status'] == 'stopped' + and proxmox.nodes(vm[0]['node']).tasks(taskid).status.get()['exitstatus'] == 'OK'): + return True + timeout = timeout - 1 + if timeout == 0: + module.fail_json(msg='Reached timeout while waiting for stopping VM. Last line in task before timeout: %s' + % proxmox.nodes(vm[0]['node']).tasks(taskid).log.get()[:1]) + time.sleep(1) + return False - time.sleep(1) - return False def main(): - module = AnsibleModule( - argument_spec = dict( - acpi = dict(type='bool', default='yes'), - agent = dict(type='bool'), - args = dict(type='str', default=None), - api_host = dict(required=True), - api_user = dict(required=True), - api_password = dict(no_log=True), - autostart = dict(type='bool', default='no'), - balloon = dict(type='int',default=0), - bios = dict(choices=['seabios', 'ovmf']), - boot = dict(type='str', default='cnd'), - bootdisk = dict(type='str'), - cores = dict(type='int', default=1), - cpu = dict(type='str', default='kvm64'), - cpulimit = dict(type='int'), - cpuunits = dict(type='int', default=1000), - delete = dict(type='str'), - description = dict(type='str'), - digest = dict(type='str'), - force = dict(type='bool', default=None), - freeze = dict(type='bool'), - hostpci = dict(type='dict'), - hotplug = dict(type='str'), - hugepages = dict(choices=['any', '2', '1024']), - ide = dict(type='dict', default=None), - keyboard = dict(type='str'), - kvm = dict(type='bool', default='yes'), - localtime = dict(type='bool'), - lock = dict(choices=['migrate', 'backup', 'snapshot', 'rollback']), - machine = dict(type='str'), - memory = dict(type='int', default=512), - migrate_downtime = dict(type='int'), - migrate_speed = dict(type='int'), - name = dict(type='str'), - net = dict(type='dict'), - node = dict(), - numa = dict(type='dict'), - onboot = dict(type='bool', default='yes'), - ostype = dict(default='l26', choices=['other', 'wxp', 'w2k', 'w2k3', 'w2k8', 'wvista', 'win7', 'win8', 'l24', 'l26', 'solaris']), - parallel = dict(type='dict'), - protection = dict(type='bool'), - reboot = dict(type='bool'), - revert = dict(), - sata = dict(type='dict'), - scsi = dict(type='dict'), - scsihw = dict(choices=['lsi', 'lsi53c810', 'virtio-scsi-pci', 'virtio-scsi-single', 'megasas', 'pvscsi']), - serial = dict(type='dict'), - shares = dict(type='int'), - skiplock = dict(type='bool'), - smbios = dict(type='str'), - sockets = dict(type='int', default=1), - startdate = dict(type='str'), - startup = dict(), - state = dict(default='present', choices=['present', 'absent', 'stopped', 'started', 'restarted', 'current']), - tablet = dict(type='bool', default='no'), - tdf = dict(type='bool'), - template = dict(type='bool', default='no'), - timeout = dict(type='int', default=30), - validate_certs = dict(type='bool', default='no'), - vcpus = dict(type='int', default=None), - vga = dict(default='std', choices=['std', 'cirrus', 'vmware', 'qxl', 'serial0', 'serial1', 'serial2', 'serial3', 'qxl2', 'qxl3', 'qxl4']), - virtio = dict(type='dict', default=None), - vmid = dict(type='int', default=None), - watchdog = dict(), - ) - ) + module = AnsibleModule( + argument_spec = dict( + acpi = dict(type='bool', default='yes'), + agent = dict(type='bool'), + args = dict(type='str', default=None), + api_host = dict(required=True), + api_user = dict(required=True), + api_password = dict(no_log=True), + autostart = dict(type='bool', default='no'), + balloon = dict(type='int',default=0), + bios = dict(choices=['seabios', 'ovmf']), + boot = dict(type='str', default='cnd'), + bootdisk = dict(type='str'), + clone = dict(type='str', default=None), + cores = dict(type='int', default=1), + cpu = dict(type='str', default='kvm64'), + cpulimit = dict(type='int'), + cpuunits = dict(type='int', default=1000), + delete = dict(type='str', default=None), + description = dict(type='str'), + digest = dict(type='str'), + force = dict(type='bool', default=None), + format = dict(type='str', default='qcow2', choices=['cloop', 'cow', 'qcow', 'qcow2', 'qed', 'raw', 'vmdk' ]), + freeze = dict(type='bool'), + full = dict(type='bool', default='yes'), + hostpci = dict(type='dict'), + hotplug = dict(type='str'), + hugepages = dict(choices=['any', '2', '1024']), + ide = dict(type='dict', default=None), + keyboard = dict(type='str'), + kvm = dict(type='bool', default='yes'), + localtime = dict(type='bool'), + lock = dict(choices=['migrate', 'backup', 'snapshot', 'rollback']), + machine = dict(type='str'), + memory = dict(type='int', default=512), + migrate_downtime = dict(type='int'), + migrate_speed = dict(type='int'), + name = dict(type='str'), + net = dict(type='dict'), + newid = dict(type='int', default=None), + node = dict(), + numa = dict(type='dict'), + numa_enabled = dict(type='bool'), + onboot = dict(type='bool', default='yes'), + ostype = dict(default='l26', choices=['other', 'wxp', 'w2k', 'w2k3', 'w2k8', 'wvista', 'win7', 'win8', 'l24', 'l26', 'solaris']), + parallel = dict(type='dict'), + pool = dict(type='str'), + protection = dict(type='bool'), + reboot = dict(type='bool'), + revert = dict(type='str', default=None), + sata = dict(type='dict'), + scsi = dict(type='dict'), + scsihw = dict(choices=['lsi', 'lsi53c810', 'virtio-scsi-pci', 'virtio-scsi-single', 'megasas', 'pvscsi']), + serial = dict(type='dict'), + shares = dict(type='int'), + skiplock = dict(type='bool'), + smbios = dict(type='str'), + snapname = dict(type='str'), + sockets = dict(type='int', default=1), + startdate = dict(type='str'), + startup = dict(), + state = dict(default='present', choices=['present', 'absent', 'stopped', 'started', 'restarted', 'current']), + storage = dict(type='str'), + tablet = dict(type='bool', default='no'), + target = dict(type='str'), + tdf = dict(type='bool'), + template = dict(type='bool', default='no'), + timeout = dict(type='int', default=30), + update = dict(type='bool', default='no'), + validate_certs = dict(type='bool', default='no'), + vcpus = dict(type='int', default=None), + vga = dict(default='std', choices=['std', 'cirrus', 'vmware', 'qxl', 'serial0', 'serial1', 'serial2', 'serial3', 'qxl2', 'qxl3', 'qxl4']), + virtio = dict(type='dict', default=None), + vmid = dict(type='int', default=None), + watchdog = dict(), + ), + mutually_exclusive = [('delete', 'revert'), ('delete','update'), ('revert','update'), ('clone', 'update'), ('clone', 'delete'), ('clone','revert')], + required_one_of=[('name','vmid',)], + required_if=[('state', 'present', ['node'])] + ) - if not HAS_PROXMOXER: - module.fail_json(msg='proxmoxer required for this module') + if not HAS_PROXMOXER: + module.fail_json(msg='proxmoxer required for this module') - api_user = module.params['api_user'] - api_host = module.params['api_host'] - api_password = module.params['api_password'] - cpu = module.params['cpu'] - cores = module.params['cores'] - memory = module.params['memory'] - name = module.params['name'] - node = module.params['node'] - sockets = module.params['sockets'], - state = module.params['state'] - timeout = module.params['timeout'] - validate_certs = module.params['validate_certs'] - - # If password not set get it from PROXMOX_PASSWORD env - if not api_password: - try: - api_password = os.environ['PROXMOX_PASSWORD'] - except KeyError as e: - module.fail_json(msg='You should set api_password param or use PROXMOX_PASSWORD environment variable') - - try: - proxmox = ProxmoxAPI(api_host, user=api_user, password=api_password, verify_ssl=validate_certs) - global VZ_TYPE - global PVE_MAJOR_VERSION - PVE_MAJOR_VERSION = 3 if float(proxmox.version.get()['version']) < 4.0 else 4 - except Exception as e: - module.fail_json(msg='authorization on proxmox cluster failed with exception: %s' % e) - - - # If vmid not set get the Next VM id from ProxmoxAPI - # If vm name is set get the VM id from ProxmoxAPI - if module.params['vmid'] is not None: + api_user = module.params['api_user'] + api_host = module.params['api_host'] + api_password = module.params['api_password'] + clone = module.params['clone'] + cpu = module.params['cpu'] + cores = module.params['cores'] + delete = module.params['delete'] + memory = module.params['memory'] + name = module.params['name'] + newid = module.params['newid'] + node = module.params['node'] + revert = module.params['revert'] + sockets = module.params['sockets'] + state = module.params['state'] + timeout = module.params['timeout'] + update = bool(module.params['update']) vmid = module.params['vmid'] - elif state == 'present': - vmid = get_nextvmid(proxmox) - elif module.params['name'] is not None: - vmid = get_vmid(proxmox, name)[0] + validate_certs = module.params['validate_certs'] + + # If password not set get it from PROXMOX_PASSWORD env + if not api_password: + try: + api_password = os.environ['PROXMOX_PASSWORD'] + except KeyError as e: + module.fail_json(msg='You should set api_password param or use PROXMOX_PASSWORD environment variable') - if state == 'present': try: - if get_vm(proxmox, vmid) and not module.params['force']: - module.exit_json(changed=False, msg="VM with vmid <%s> already exists" % vmid) - elif get_vmid(proxmox, name) and not module.params['force']: - module.exit_json(changed=False, msg="VM with name <%s> already exists" % name) - elif not (node, module.params['name']): - module.fail_json(msg='node, name is mandatory for creating vm') - elif not node_check(proxmox, node): - module.fail_json(msg="node '%s' does not exist in cluster" % node) + proxmox = ProxmoxAPI(api_host, user=api_user, password=api_password, verify_ssl=validate_certs) + global VZ_TYPE + global PVE_MAJOR_VERSION + PVE_MAJOR_VERSION = 3 if float(proxmox.version.get()['version']) < 4.0 else 4 + except Exception as e: + module.fail_json(msg='authorization on proxmox cluster failed with exception: %s' % e) - create_vm(module, proxmox, vmid, node, name, memory, cpu, cores, sockets, timeout, + # If vmid not set get the Next VM id from ProxmoxAPI + # If vm name is set get the VM id from ProxmoxAPI + if not vmid: + if state == 'present' and ( not update and not clone) and (not delete and not revert): + try: + vmid = get_nextvmid(proxmox) + except Exception as e: + module.fail_json(msg="Can't get the next vimd for VM {} automatically. Ensure your cluster state is good".format(name)) + else: + try: + if not clone: + vmid = get_vmid(proxmox, name)[0] + else: + vmid = get_vmid(proxmox, clone)[0] + except Exception as e: + if not clone: + module.fail_json(msg="VM {} does not exist in cluster.".format(name)) + else: + module.fail_json(msg="VM {} does not exist in cluster.".format(clone)) + + if clone is not None: + if get_vmid(proxmox, name): + module.exit_json(changed=False, msg="VM with name <%s> already exists" % name) + if vmid is not None: + vm = get_vm(proxmox, vmid) + if not vm: + module.fail_json(msg='VM with vmid = %s does not exist in cluster' % vmid) + if not newid: + try: + newid = get_nextvmid(proxmox) + except Exception as e: + module.fail_json(msg="Can't get the next vimd for VM {} automatically. Ensure your cluster state is good".format(name)) + else: + vm = get_vm(proxmox, newid) + if vm: + module.exit_json(changed=False, msg="vmid %s with VM name %s already exists" % (newid, name)) + + if delete is not None: + try: + settings(module, proxmox, vmid, node, name, timeout, delete=delete) + module.exit_json(changed=True, msg="Settings has deleted on VM {} with vmid {}".format(name, vmid)) + except Exception as e: + module.fail_json(msg='Unable to delete settings on VM {} with vimd {}: '.format(name, vmid) + str(e)) + elif revert is not None: + try: + settings(module, proxmox, vmid, node, name, timeout, revert=revert) + module.exit_json(changed=True, msg="Settings has reverted on VM {} with vmid {}".format(name, vmid)) + except Exception as e: + module.fail_json(msg='Unable to revert settings on VM {} with vimd {}: Maybe is not a pending task... '.format(name, vmid) + str(e)) + + if state == 'present': + try: + if get_vm(proxmox, vmid) and not (update or clone): + module.exit_json(changed=False, msg="VM with vmid <%s> already exists" % vmid) + elif get_vmid(proxmox, name) and not (update or clone): + module.exit_json(changed=False, msg="VM with name <%s> already exists" % name) + elif not (node, name): + module.fail_json(msg='node, name is mandatory for creating/updating vm') + elif not node_check(proxmox, node): + module.fail_json(msg="node '%s' does not exist in cluster" % node) + + create_vm(module, proxmox, vmid, newid, node, name, memory, cpu, cores, sockets, timeout, update, acpi = module.params['acpi'], agent = module.params['agent'], autostart = module.params['autostart'], @@ -924,7 +1092,6 @@ def main(): bootdisk = module.params['bootdisk'], cpulimit = module.params['cpulimit'], cpuunits = module.params['cpuunits'], - delete = module.params['delete'], description = module.params['description'], digest = module.params['digest'], force = module.params['force'], @@ -942,12 +1109,13 @@ def main(): migrate_speed = module.params['migrate_speed'], net = module.params['net'], numa = module.params['numa'], + numa_enabled = module.params['numa_enabled'], onboot = module.params['onboot'], ostype = module.params['ostype'], parallel = module.params['parallel'], + pool = module.params['pool'], protection = module.params['protection'], reboot = module.params['reboot'], - revert = module.params['revert'], sata = module.params['sata'], scsi = module.params['scsi'], scsihw = module.params['scsihw'], @@ -955,9 +1123,11 @@ def main(): shares = module.params['shares'], skiplock = module.params['skiplock'], smbios1 = module.params['smbios'], + snapname = module.params['snapname'], startdate = module.params['startdate'], startup = module.params['startup'], tablet = module.params['tablet'], + target = module.params['target'], tdf = module.params['tdf'], template = module.params['template'], vcpus = module.params['vcpus'], @@ -965,92 +1135,104 @@ def main(): virtio = module.params['virtio'], watchdog = module.params['watchdog']) - get_vminfo(module, proxmox, node, vmid, - ide = module.params['ide'], - net = module.params['net'], - sata = module.params['sata'], - scsi = module.params['scsi'], - virtio = module.params['virtio']) - module.exit_json(changed=True, msg="VM %s with vmid %s deployed" % (name, vmid), **results) - except Exception as e: - module.fail_json(msg="creation of %s VM %s with vmid %s failed with exception: %s" % ( VZ_TYPE, name, vmid, e )) + if not clone: + get_vminfo(module, proxmox, node, vmid, + ide = module.params['ide'], + net = module.params['net'], + sata = module.params['sata'], + scsi = module.params['scsi'], + virtio = module.params['virtio']) + if update: + module.exit_json(changed=True, msg="VM %s with vmid %s updated" % (name, vmid)) + elif clone is not None: + module.exit_json(changed=True, msg="VM %s with newid %s cloned from vm with vmid %s" % (name, newid, vmid)) + else: + module.exit_json(changed=True, msg="VM %s with vmid %s deployed" % (name, vmid), **results) + except Exception as e: + if update: + module.fail_json(msg="Unable to update vm {} with vimd {}=".format(name, vmid) + str(e)) + elif clone is not None: + module.fail_json(msg="Unable to clone vm {} from vimd {}=".format(name, vmid) + str(e)) + else: + module.fail_json(msg="creation of %s VM %s with vmid %s failed with exception=%s" % (VZ_TYPE, name, vmid, e)) - elif state == 'started': - try: - vm = get_vm(proxmox, vmid) - if not vm: - module.fail_json(msg='VM with vmid <%s> does not exist in cluster' % vmid) - if getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE)(vmid).status.current.get()['status'] == 'running': - module.exit_json(changed=False, msg="VM %s is already running" % vmid) + elif state == 'started': + try: + vm = get_vm(proxmox, vmid) + if not vm: + module.fail_json(msg='VM with vmid <%s> does not exist in cluster' % vmid) + if getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE)(vmid).status.current.get()['status'] == 'running': + module.exit_json(changed=False, msg="VM %s is already running" % vmid) - if start_vm(module, proxmox, vm, vmid, timeout): - module.exit_json(changed=True, msg="VM %s started" % vmid) - except Exception as e: - module.fail_json(msg="starting of VM %s failed with exception: %s" % ( vmid, e )) + if start_vm(module, proxmox, vm, vmid, timeout): + module.exit_json(changed=True, msg="VM %s started" % vmid) + except Exception as e: + module.fail_json(msg="starting of VM %s failed with exception: %s" % (vmid, e)) - elif state == 'stopped': - try: - vm = get_vm(proxmox, vmid) - if not vm: - module.fail_json(msg='VM with vmid = %s does not exist in cluster' % vmid) + elif state == 'stopped': + try: + vm = get_vm(proxmox, vmid) + if not vm: + module.fail_json(msg='VM with vmid = %s does not exist in cluster' % vmid) - if getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE)(vmid).status.current.get()['status'] == 'stopped': - module.exit_json(changed=False, msg="VM %s is already stopped" % vmid) + if getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE)(vmid).status.current.get()['status'] == 'stopped': + module.exit_json(changed=False, msg="VM %s is already stopped" % vmid) - if stop_vm(module, proxmox, vm, vmid, timeout, force = module.params['force']): - module.exit_json(changed=True, msg="VM %s is shutting down" % vmid) - except Exception as e: - module.fail_json(msg="stopping of VM %s failed with exception: %s" % ( vmid, e )) + if stop_vm(module, proxmox, vm, vmid, timeout, force = module.params['force']): + module.exit_json(changed=True, msg="VM %s is shutting down" % vmid) + except Exception as e: + module.fail_json(msg="stopping of VM %s failed with exception: %s" % (vmid, e)) - elif state == 'restarted': - try: - vm = get_vm(proxmox, vmid) - if not vm: - module.fail_json(msg='VM with vmid = %s does not exist in cluster' % vmid) - if getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE)(vmid).status.current.get()['status'] == 'stopped': - module.exit_json(changed=False, msg="VM %s is not running" % vmid) + elif state == 'restarted': + try: + vm = get_vm(proxmox, vmid) + if not vm: + module.fail_json(msg='VM with vmid = %s does not exist in cluster' % vmid) + if getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE)(vmid).status.current.get()['status'] == 'stopped': + module.exit_json(changed=False, msg="VM %s is not running" % vmid) - if ( stop_vm(module, proxmox, vm, vmid, timeout, force = module.params['force']) and - start_vm(module, proxmox, vm, vmid, timeout) ): - module.exit_json(changed=True, msg="VM %s is restarted" % vmid) - except Exception as e: - module.fail_json(msg="restarting of VM %s failed with exception: %s" % ( vmid, e )) + if (stop_vm(module, proxmox, vm, vmid, timeout, force = module.params['force']) + and start_vm(module, proxmox, vm, vmid, timeout)): + module.exit_json(changed=True, msg="VM %s is restarted" % vmid) + except Exception as e: + module.fail_json(msg="restarting of VM %s failed with exception: %s" % ( vmid, e )) - elif state == 'absent': - try: - vm = get_vm(proxmox, vmid) - if not vm: - module.exit_json(changed=False, msg="VM %s does not exist" % vmid) + elif state == 'absent': + try: + vm = get_vm(proxmox, vmid) + if not vm: + module.exit_json(changed=False, msg="VM %s does not exist" % vmid) - if getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE)(vmid).status.current.get()['status'] == 'running': - module.exit_json(changed=False, msg="VM %s is running. Stop it before deletion." % vmid) + if getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE)(vmid).status.current.get()['status'] == 'running': + module.exit_json(changed=False, msg="VM %s is running. Stop it before deletion." % vmid) - taskid = getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE).delete(vmid) - while timeout: - if ( proxmox.nodes(vm[0]['node']).tasks(taskid).status.get()['status'] == 'stopped' - and proxmox.nodes(vm[0]['node']).tasks(taskid).status.get()['exitstatus'] == 'OK' ): - module.exit_json(changed=True, msg="VM %s removed" % vmid) - timeout = timeout - 1 - if timeout == 0: - module.fail_json(msg='Reached timeout while waiting for removing VM. Last line in task before timeout: %s' - % proxmox_node.tasks(taskid).log.get()[:1]) + taskid = getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE).delete(vmid) + while timeout: + if (proxmox.nodes(vm[0]['node']).tasks(taskid).status.get()['status'] == 'stopped' + and proxmox.nodes(vm[0]['node']).tasks(taskid).status.get()['exitstatus'] == 'OK' ): + module.exit_json(changed=True, msg="VM %s removed" % vmid) + timeout = timeout - 1 + if timeout == 0: + module.fail_json(msg='Reached timeout while waiting for removing VM. Last line in task before timeout: %s' + % proxmox_node.tasks(taskid).log.get()[:1]) - time.sleep(1) - except Exception as e: - module.fail_json(msg="deletion of VM %s failed with exception: %s" % ( vmid, e )) + time.sleep(1) + except Exception as e: + module.fail_json(msg="deletion of VM %s failed with exception: %s" % (vmid, e)) + + elif state == 'current': + status = {} + try: + vm = get_vm(proxmox, vmid) + if not vm: + module.fail_json(msg='VM with vmid = %s does not exist in cluster' % vmid) + current = getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE)(vmid).status.current.get()['status'] + status['status'] = current + if status: + module.exit_json(changed=False, msg="VM %s with vmid = %s is %s" % (name, vmid, current), **status) + except Exception as e: + module.fail_json(msg="Unable to get vm {} with vmid = {} status: ".format(name, vmid) + str(e)) - elif state == 'current': - status = {} - try: - vm = get_vm(proxmox, vmid) - if not vm: - module.fail_json(msg='VM with vmid = %s does not exist in cluster' % vmid) - current = getattr(proxmox.nodes(vm[0]['node']), VZ_TYPE)(vmid).status.current.get()['status'] - status['status'] = current - if status: - module.exit_json(changed=False, msg="VM %s with vmid = %s is %s" % (name, vmid, current), **status) - except Exception as e: - module.fail_json(msg="Unable to get vm {} with vmid = {} status: ".format(name, vmid) + str(e)) # import module snippets from ansible.module_utils.basic import *