Refactor all Proxmox modules to use shared module_utils. (#4029) (#4164)

* Refactor Proxmox modules to use `module_utils`.

* Fix tests.

* Rename `node_check`.

* Add `ignore_missing` to `get_vm`.

* Refactor `proxmox` module.

* Add changelog entry.

* Add `choose_first_if_multiple` parameter for deprecation.

(cherry picked from commit a61bdbadd5)

Co-authored-by: Markus Reiter <me@reitermark.us>
This commit is contained in:
patchback[bot] 2022-02-07 17:48:11 +01:00 committed by GitHub
parent fab30c5e55
commit a678029bd2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 849 additions and 1072 deletions

View file

@ -1,6 +1,6 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright: (c) 2016, Abdoul Bah (@helldorado) <bahabdoul at gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
@ -728,72 +728,12 @@ import traceback
from ansible.module_utils.six.moves.urllib.parse import quote
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
from ansible_collections.community.general.plugins.module_utils.proxmox import (proxmox_auth_argument_spec, ProxmoxAnsible)
try:
from proxmoxer import ProxmoxAPI
HAS_PROXMOXER = True
except ImportError:
HAS_PROXMOXER = False
from ansible.module_utils.basic import AnsibleModule, env_fallback
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils.common.text.converters import to_native
def get_nextvmid(module, 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" % to_native(e),
exception=traceback.format_exc())
def get_vmid(proxmox, name):
return [vm['vmid'] for vm in proxmox.cluster.resources.get(type='vm') if vm.get('name') == name]
def get_vm(proxmox, 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]
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))
# 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 list(kwargs.keys()):
if isinstance(kwargs[k], dict):
kwargs.update(kwargs[k])
del kwargs[k]
# Split information by type
re_net = re.compile(r'net[0-9]')
re_dev = re.compile(r'(virtio|ide|scsi|sata)[0-9]')
for k in kwargs.keys():
if re_net.match(k):
mac[k] = parse_mac(vm[k])
elif re_dev.match(k):
devices[k] = parse_dev(vm[k])
results['mac'] = mac
results['devices'] = devices
results['vmid'] = int(vmid)
def parse_mac(netstr):
return re.search('=(.*?),', netstr).group(1)
@ -802,276 +742,294 @@ def parse_dev(devstr):
return re.search('(.*?)(,|$)', devstr).group(1)
def settings(proxmox, vmid, node, **kwargs):
proxmox_node = proxmox.nodes(node)
class ProxmoxKvmAnsible(ProxmoxAnsible):
def get_vminfo(self, node, vmid, **kwargs):
global results
results = {}
mac = {}
devices = {}
try:
vm = self.proxmox_api.nodes(node).qemu(vmid).config.get()
except Exception as e:
self.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)
return proxmox_node.qemu(vmid).config.set(**kwargs) is 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 list(kwargs.keys()):
if isinstance(kwargs[k], dict):
kwargs.update(kwargs[k])
del kwargs[k]
# Split information by type
re_net = re.compile(r'net[0-9]')
re_dev = re.compile(r'(virtio|ide|scsi|sata)[0-9]')
for k in kwargs.keys():
if re_net.match(k):
mac[k] = parse_mac(vm[k])
elif re_dev.match(k):
devices[k] = parse_dev(vm[k])
def wait_for_task(module, proxmox, node, taskid):
timeout = module.params['timeout']
results['mac'] = mac
results['devices'] = devices
results['vmid'] = int(vmid)
while timeout:
task = proxmox.nodes(node).tasks(taskid).status.get()
if task['status'] == 'stopped' and task['exitstatus'] == 'OK':
# Wait an extra second as the API can be a ahead of the hypervisor
def settings(self, vmid, node, **kwargs):
proxmox_node = self.proxmox_api.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)
return proxmox_node.qemu(vmid).config.set(**kwargs) is None
def wait_for_task(self, node, taskid):
timeout = self.module.params['timeout']
while timeout:
task = self.proxmox_api.nodes(node).tasks(taskid).status.get()
if task['status'] == 'stopped' and task['exitstatus'] == 'OK':
# Wait an extra second as the API can be a ahead of the hypervisor
time.sleep(1)
return True
timeout = timeout - 1
if timeout == 0:
break
time.sleep(1)
return True
timeout = timeout - 1
if timeout == 0:
break
time.sleep(1)
return False
return False
def create_vm(self, vmid, newid, node, name, memory, cpu, cores, sockets, update, **kwargs):
# Available only in PVE 4
only_v4 = ['force', 'protection', 'skiplock']
only_v6 = ['ciuser', 'cipassword', 'sshkeys', 'ipconfig', 'tags']
def create_vm(module, proxmox, vmid, newid, node, name, memory, cpu, cores, sockets, update, **kwargs):
# Available only in PVE 4
only_v4 = ['force', 'protection', 'skiplock']
only_v6 = ['ciuser', 'cipassword', 'sshkeys', 'ipconfig', 'tags']
# 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/{0}.serial,server,nowait".format(vmid)
# 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/{0}.serial,server,nowait".format(vmid)
proxmox_node = self.proxmox_api.nodes(node)
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)
kwargs.update(dict([k, int(v)] for k, v in kwargs.items() if isinstance(v, bool)))
version = self.version()
pve_major_version = 3 if version < LooseVersion('4.0') else version.version[0]
# The features work only on PVE 4+
if PVE_MAJOR_VERSION < 4:
for p in only_v4:
if p in kwargs:
del kwargs[p]
# The features work only on PVE 4+
if pve_major_version < 4:
for p in only_v4:
if p in kwargs:
del kwargs[p]
# The features work only on PVE 6
if PVE_MAJOR_VERSION < 6:
for p in only_v6:
if p in kwargs:
del kwargs[p]
# The features work only on PVE 6
if pve_major_version < 6:
for p in only_v6:
if p in kwargs:
del kwargs[p]
# 'sshkeys' param expects an urlencoded string
if 'sshkeys' in kwargs:
urlencoded_ssh_keys = quote(kwargs['sshkeys'], safe='')
kwargs['sshkeys'] = str(urlencoded_ssh_keys)
# 'sshkeys' param expects an urlencoded string
if 'sshkeys' in kwargs:
urlencoded_ssh_keys = quote(kwargs['sshkeys'], safe='')
kwargs['sshkeys'] = str(urlencoded_ssh_keys)
# If update, don't update disk (virtio, ide, sata, scsi) and network interface
# pool parameter not supported by qemu/<vmid>/config endpoint on "update" (PVE 6.2) - only with "create"
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']
if 'force' in kwargs:
del kwargs['force']
if 'pool' in kwargs:
del kwargs['pool']
# If update, don't update disk (virtio, ide, sata, scsi) and network interface
# pool parameter not supported by qemu/<vmid>/config endpoint on "update" (PVE 6.2) - only with "create"
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']
if 'force' in kwargs:
del kwargs['force']
if 'pool' in kwargs:
del kwargs['pool']
# 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], ipconfig[n]
for k in list(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], ipconfig[n]
for k in list(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']
# Rename numa_enabled to numa. According the API documentation
if 'numa_enabled' in kwargs:
kwargs['numa'] = kwargs['numa_enabled']
del kwargs['numa_enabled']
# PVE api expects strings for the following params
if 'nameservers' in module.params:
nameservers = module.params.pop('nameservers')
if nameservers:
kwargs['nameserver'] = ' '.join(nameservers)
if 'searchdomains' in module.params:
searchdomains = module.params.pop('searchdomains')
if searchdomains:
kwargs['searchdomain'] = ' '.join(searchdomains)
# PVE api expects strings for the following params
if 'nameservers' in self.module.params:
nameservers = self.module.params.pop('nameservers')
if nameservers:
kwargs['nameserver'] = ' '.join(nameservers)
if 'searchdomains' in self.module.params:
searchdomains = self.module.params.pop('searchdomains')
if searchdomains:
kwargs['searchdomain'] = ' '.join(searchdomains)
# VM tags are expected to be valid and presented as a comma/semi-colon delimited string
if 'tags' in kwargs:
re_tag = re.compile(r'^[a-z0-9_][a-z0-9_\-\+\.]*$')
for tag in kwargs['tags']:
if not re_tag.match(tag):
module.fail_json(msg='%s is not a valid tag' % tag)
kwargs['tags'] = ",".join(kwargs['tags'])
# VM tags are expected to be valid and presented as a comma/semi-colon delimited string
if 'tags' in kwargs:
re_tag = re.compile(r'^[a-z0-9_][a-z0-9_\-\+\.]*$')
for tag in kwargs['tags']:
if not re_tag.match(tag):
self.module.fail_json(msg='%s is not a valid tag' % tag)
kwargs['tags'] = ",".join(kwargs['tags'])
# -args and skiplock require root@pam user - but can not use api tokens
if module.params['api_user'] == "root@pam" and module.params['args'] is None:
if not update and module.params['proxmox_default_behavior'] == 'compatibility':
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. ')
# -args and skiplock require root@pam user - but can not use api tokens
if self.module.params['api_user'] == "root@pam" and self.module.params['args'] is None:
if not update and self.module.params['proxmox_default_behavior'] == 'compatibility':
kwargs['args'] = vm_args
elif self.module.params['api_user'] == "root@pam" and self.module.params['args'] is not None:
kwargs['args'] = self.module.params['args']
elif self.module.params['api_user'] != "root@pam" and self.module.params['args'] is not None:
self.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 self.module.params['api_user'] != "root@pam" and self.module.params['skiplock'] is not None:
self.module.fail_json(msg='skiplock parameter require root@pam user. ')
if update:
if proxmox_node.qemu(vmid).config.set(name=name, memory=memory, cpu=cpu, cores=cores, sockets=sockets, **kwargs) is None:
return True
if update:
if proxmox_node.qemu(vmid).config.set(name=name, memory=memory, cpu=cpu, cores=cores, sockets=sockets, **kwargs) is None:
return True
else:
return False
elif self.module.params['clone'] is not None:
for param in valid_clone_params:
if self.module.params[param] is not None:
clone_params[param] = self.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 = proxmox_node.qemu.create(vmid=vmid, name=name, memory=memory, cpu=cpu, cores=cores, sockets=sockets, **kwargs)
if not self.wait_for_task(node, taskid):
self.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])
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 = proxmox_node.qemu.create(vmid=vmid, name=name, memory=memory, cpu=cpu, cores=cores, sockets=sockets, **kwargs)
return True
if not wait_for_task(module, proxmox, node, taskid):
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])
return False
return True
def start_vm(self, vm):
vmid = vm['vmid']
proxmox_node = self.proxmox_api.nodes(vm['node'])
taskid = proxmox_node.qemu(vmid).status.start.post()
if not self.wait_for_task(vm['node'], taskid):
self.module.fail_json(msg='Reached timeout while waiting for starting VM. Last line in task before timeout: %s' %
proxmox_node.tasks(taskid).log.get()[:1])
return False
return True
def start_vm(module, proxmox, vm):
vmid = vm[0]['vmid']
proxmox_node = proxmox.nodes(vm[0]['node'])
taskid = proxmox_node.qemu(vmid).status.start.post()
if not wait_for_task(module, proxmox, vm[0]['node'], taskid):
module.fail_json(msg='Reached timeout while waiting for starting VM. Last line in task before timeout: %s' %
proxmox_node.tasks(taskid).log.get()[:1])
return False
return True
def stop_vm(module, proxmox, vm, force):
vmid = vm[0]['vmid']
proxmox_node = proxmox.nodes(vm[0]['node'])
taskid = proxmox_node.qemu(vmid).status.shutdown.post(forceStop=(1 if force else 0))
if not wait_for_task(module, proxmox, vm[0]['node'], taskid):
module.fail_json(msg='Reached timeout while waiting for stopping VM. Last line in task before timeout: %s' %
proxmox_node.tasks(taskid).log.get()[:1])
return False
return True
def proxmox_version(proxmox):
apireturn = proxmox.version.get()
return LooseVersion(apireturn['version'])
def stop_vm(self, vm, force):
vmid = vm['vmid']
proxmox_node = self.proxmox_api.nodes(vm['node'])
taskid = proxmox_node.qemu(vmid).status.shutdown.post(forceStop=(1 if force else 0))
if not self.wait_for_task(vm['node'], taskid):
self.module.fail_json(msg='Reached timeout while waiting for stopping VM. Last line in task before timeout: %s' %
proxmox_node.tasks(taskid).log.get()[:1])
return False
return True
def main():
module_args = proxmox_auth_argument_spec()
kvm_args = dict(
acpi=dict(type='bool'),
agent=dict(type='bool'),
args=dict(type='str'),
autostart=dict(type='bool'),
balloon=dict(type='int'),
bios=dict(choices=['seabios', 'ovmf']),
boot=dict(type='str'),
bootdisk=dict(type='str'),
cicustom=dict(type='str'),
cipassword=dict(type='str', no_log=True),
citype=dict(type='str', choices=['nocloud', 'configdrive2']),
ciuser=dict(type='str'),
clone=dict(type='str'),
cores=dict(type='int'),
cpu=dict(type='str'),
cpulimit=dict(type='int'),
cpuunits=dict(type='int'),
delete=dict(type='str'),
description=dict(type='str'),
digest=dict(type='str'),
force=dict(type='bool'),
format=dict(type='str', choices=['cloop', 'cow', 'qcow', 'qcow2', 'qed', 'raw', 'vmdk', 'unspecified']),
freeze=dict(type='bool'),
full=dict(type='bool', default=True),
hostpci=dict(type='dict'),
hotplug=dict(type='str'),
hugepages=dict(choices=['any', '2', '1024']),
ide=dict(type='dict'),
ipconfig=dict(type='dict'),
keyboard=dict(type='str'),
kvm=dict(type='bool'),
localtime=dict(type='bool'),
lock=dict(choices=['migrate', 'backup', 'snapshot', 'rollback']),
machine=dict(type='str'),
memory=dict(type='int'),
migrate_downtime=dict(type='int'),
migrate_speed=dict(type='int'),
name=dict(type='str'),
nameservers=dict(type='list', elements='str'),
net=dict(type='dict'),
newid=dict(type='int'),
node=dict(),
numa=dict(type='dict'),
numa_enabled=dict(type='bool'),
onboot=dict(type='bool'),
ostype=dict(choices=['other', 'wxp', 'w2k', 'w2k3', 'w2k8', 'wvista', 'win7', 'win8', 'win10', 'l24', 'l26', 'solaris']),
parallel=dict(type='dict'),
pool=dict(type='str'),
protection=dict(type='bool'),
reboot=dict(type='bool'),
revert=dict(type='str'),
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'),
searchdomains=dict(type='list', elements='str'),
shares=dict(type='int'),
skiplock=dict(type='bool'),
smbios=dict(type='str'),
snapname=dict(type='str'),
sockets=dict(type='int'),
sshkeys=dict(type='str', no_log=False),
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'),
tags=dict(type='list', elements='str'),
target=dict(type='str'),
tdf=dict(type='bool'),
template=dict(type='bool'),
timeout=dict(type='int', default=30),
update=dict(type='bool', default=False),
vcpus=dict(type='int'),
vga=dict(choices=['std', 'cirrus', 'vmware', 'qxl', 'serial0', 'serial1', 'serial2', 'serial3', 'qxl2', 'qxl3', 'qxl4']),
virtio=dict(type='dict'),
vmid=dict(type='int'),
watchdog=dict(),
proxmox_default_behavior=dict(type='str', default='no_defaults', choices=['compatibility', 'no_defaults']),
)
module_args.update(kvm_args)
module = AnsibleModule(
argument_spec=dict(
acpi=dict(type='bool'),
agent=dict(type='bool'),
args=dict(type='str'),
api_host=dict(required=True),
api_password=dict(no_log=True, fallback=(env_fallback, ['PROXMOX_PASSWORD'])),
api_token_id=dict(no_log=True),
api_token_secret=dict(no_log=True),
api_user=dict(required=True),
autostart=dict(type='bool'),
balloon=dict(type='int'),
bios=dict(choices=['seabios', 'ovmf']),
boot=dict(type='str'),
bootdisk=dict(type='str'),
cicustom=dict(type='str'),
cipassword=dict(type='str', no_log=True),
citype=dict(type='str', choices=['nocloud', 'configdrive2']),
ciuser=dict(type='str'),
clone=dict(type='str'),
cores=dict(type='int'),
cpu=dict(type='str'),
cpulimit=dict(type='int'),
cpuunits=dict(type='int'),
delete=dict(type='str'),
description=dict(type='str'),
digest=dict(type='str'),
force=dict(type='bool'),
format=dict(type='str', choices=['cloop', 'cow', 'qcow', 'qcow2', 'qed', 'raw', 'vmdk', 'unspecified']),
freeze=dict(type='bool'),
full=dict(type='bool', default=True),
hostpci=dict(type='dict'),
hotplug=dict(type='str'),
hugepages=dict(choices=['any', '2', '1024']),
ide=dict(type='dict'),
ipconfig=dict(type='dict'),
keyboard=dict(type='str'),
kvm=dict(type='bool'),
localtime=dict(type='bool'),
lock=dict(choices=['migrate', 'backup', 'snapshot', 'rollback']),
machine=dict(type='str'),
memory=dict(type='int'),
migrate_downtime=dict(type='int'),
migrate_speed=dict(type='int'),
name=dict(type='str'),
nameservers=dict(type='list', elements='str'),
net=dict(type='dict'),
newid=dict(type='int'),
node=dict(),
numa=dict(type='dict'),
numa_enabled=dict(type='bool'),
onboot=dict(type='bool'),
ostype=dict(choices=['other', 'wxp', 'w2k', 'w2k3', 'w2k8', 'wvista', 'win7', 'win8', 'win10', 'l24', 'l26', 'solaris']),
parallel=dict(type='dict'),
pool=dict(type='str'),
protection=dict(type='bool'),
reboot=dict(type='bool'),
revert=dict(type='str'),
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'),
searchdomains=dict(type='list', elements='str'),
shares=dict(type='int'),
skiplock=dict(type='bool'),
smbios=dict(type='str'),
snapname=dict(type='str'),
sockets=dict(type='int'),
sshkeys=dict(type='str', no_log=False),
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'),
tags=dict(type='list', elements='str'),
target=dict(type='str'),
tdf=dict(type='bool'),
template=dict(type='bool'),
timeout=dict(type='int', default=30),
update=dict(type='bool', default=False),
validate_certs=dict(type='bool', default=False),
vcpus=dict(type='int'),
vga=dict(choices=['std', 'cirrus', 'vmware', 'qxl', 'serial0', 'serial1', 'serial2', 'serial3', 'qxl2', 'qxl3', 'qxl4']),
virtio=dict(type='dict'),
vmid=dict(type='int'),
watchdog=dict(),
proxmox_default_behavior=dict(type='str', default='no_defaults', choices=['compatibility', 'no_defaults']),
),
argument_spec=module_args,
mutually_exclusive=[('delete', 'revert'), ('delete', 'update'), ('revert', 'update'), ('clone', 'update'), ('clone', 'delete'), ('clone', 'revert')],
required_together=[('api_token_id', 'api_token_secret')],
required_one_of=[('name', 'vmid'), ('api_password', 'api_token_id')],
required_if=[('state', 'present', ['node'])],
)
if not HAS_PROXMOXER:
module.fail_json(msg='proxmoxer required for this module')
api_host = module.params['api_host']
api_password = module.params['api_password']
api_token_id = module.params['api_token_id']
api_token_secret = module.params['api_token_secret']
api_user = module.params['api_user']
clone = module.params['clone']
cpu = module.params['cpu']
cores = module.params['cores']
@ -1112,153 +1070,136 @@ def main():
if module.params['format'] == 'unspecified':
module.params['format'] = None
auth_args = {'user': api_user}
if not (api_token_id and api_token_secret):
auth_args['password'] = api_password
else:
auth_args['token_name'] = api_token_id
auth_args['token_value'] = api_token_secret
try:
proxmox = ProxmoxAPI(api_host, verify_ssl=validate_certs, **auth_args)
global PVE_MAJOR_VERSION
version = proxmox_version(proxmox)
PVE_MAJOR_VERSION = 3 if version < LooseVersion('4.0') else version.version[0]
except Exception as e:
module.fail_json(msg='authorization on proxmox cluster failed with exception: %s' % e)
proxmox = ProxmoxKvmAnsible(module)
# If vmid is not defined then retrieve its value from the vm name,
# the cloned vm name or retrieve the next free 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(module, proxmox)
vmid = proxmox.get_nextvmid()
except Exception:
module.fail_json(msg="Can't get the next vmid for VM {0} automatically. Ensure your cluster state is good".format(name))
else:
clone_target = clone or name
try:
vmid = get_vmid(proxmox, clone_target)[0]
except Exception:
vmid = -1
vmid = proxmox.get_vmid(clone_target, ignore_missing=True, choose_first_if_multiple=True)
if clone is not None:
# If newid is not defined then retrieve the next free id from ProxmoxAPI
if not newid:
try:
newid = get_nextvmid(module, proxmox)
newid = proxmox.get_nextvmid()
except Exception:
module.fail_json(msg="Can't get the next vmid for VM {0} automatically. Ensure your cluster state is good".format(name))
# Ensure source VM name exists when cloning
if -1 == vmid:
if not vmid:
module.fail_json(msg='VM with name = %s does not exist in cluster' % clone)
# Ensure source VM id exists when cloning
if not get_vm(proxmox, vmid):
module.fail_json(vmid=vmid, msg='VM with vmid = %s does not exist in cluster' % vmid)
proxmox.get_vm(vmid)
# Ensure the choosen VM name doesn't already exist when cloning
existing_vmid = get_vmid(proxmox, name)
existing_vmid = proxmox.get_vmid(name, choose_first_if_multiple=True)
if existing_vmid:
module.exit_json(changed=False, vmid=existing_vmid[0], msg="VM with name <%s> already exists" % name)
module.exit_json(changed=False, vmid=existing_vmid, msg="VM with name <%s> already exists" % name)
# Ensure the choosen VM id doesn't already exist when cloning
if get_vm(proxmox, newid):
if proxmox.get_vm(newid, ignore_errors=True):
module.exit_json(changed=False, vmid=vmid, msg="vmid %s with VM name %s already exists" % (newid, name))
if delete is not None:
try:
settings(proxmox, vmid, node, delete=delete)
proxmox.settings(vmid, node, delete=delete)
module.exit_json(changed=True, vmid=vmid, msg="Settings has deleted on VM {0} with vmid {1}".format(name, vmid))
except Exception as e:
module.fail_json(vmid=vmid, msg='Unable to delete settings on VM {0} with vmid {1}: '.format(name, vmid) + str(e))
if revert is not None:
try:
settings(proxmox, vmid, node, revert=revert)
proxmox.settings(vmid, node, revert=revert)
module.exit_json(changed=True, vmid=vmid, msg="Settings has reverted on VM {0} with vmid {1}".format(name, vmid))
except Exception as e:
module.fail_json(vmid=vmid, msg='Unable to revert settings on VM {0} with vmid {1}: 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):
if proxmox.get_vm(vmid, ignore_missing=True) and not (update or clone):
module.exit_json(changed=False, vmid=vmid, msg="VM with vmid <%s> already exists" % vmid)
elif get_vmid(proxmox, name) and not (update or clone):
module.exit_json(changed=False, vmid=get_vmid(proxmox, name)[0], msg="VM with name <%s> already exists" % name)
elif proxmox.get_vmid(name, ignore_missing=True, choose_first_if_multiple=True) and not (update or clone):
module.exit_json(changed=False, vmid=proxmox.get_vmid(name, choose_first_if_multiple=True), 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):
elif not proxmox.get_node(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, update,
acpi=module.params['acpi'],
agent=module.params['agent'],
autostart=module.params['autostart'],
balloon=module.params['balloon'],
bios=module.params['bios'],
boot=module.params['boot'],
bootdisk=module.params['bootdisk'],
cicustom=module.params['cicustom'],
cipassword=module.params['cipassword'],
citype=module.params['citype'],
ciuser=module.params['ciuser'],
cpulimit=module.params['cpulimit'],
cpuunits=module.params['cpuunits'],
description=module.params['description'],
digest=module.params['digest'],
force=module.params['force'],
freeze=module.params['freeze'],
hostpci=module.params['hostpci'],
hotplug=module.params['hotplug'],
hugepages=module.params['hugepages'],
ide=module.params['ide'],
ipconfig=module.params['ipconfig'],
keyboard=module.params['keyboard'],
kvm=module.params['kvm'],
localtime=module.params['localtime'],
lock=module.params['lock'],
machine=module.params['machine'],
migrate_downtime=module.params['migrate_downtime'],
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'],
sata=module.params['sata'],
scsi=module.params['scsi'],
scsihw=module.params['scsihw'],
serial=module.params['serial'],
shares=module.params['shares'],
skiplock=module.params['skiplock'],
smbios1=module.params['smbios'],
snapname=module.params['snapname'],
sshkeys=module.params['sshkeys'],
startdate=module.params['startdate'],
startup=module.params['startup'],
tablet=module.params['tablet'],
tags=module.params['tags'],
target=module.params['target'],
tdf=module.params['tdf'],
template=module.params['template'],
vcpus=module.params['vcpus'],
vga=module.params['vga'],
virtio=module.params['virtio'],
watchdog=module.params['watchdog'])
proxmox.create_vm(vmid, newid, node, name, memory, cpu, cores, sockets, update,
acpi=module.params['acpi'],
agent=module.params['agent'],
autostart=module.params['autostart'],
balloon=module.params['balloon'],
bios=module.params['bios'],
boot=module.params['boot'],
bootdisk=module.params['bootdisk'],
cicustom=module.params['cicustom'],
cipassword=module.params['cipassword'],
citype=module.params['citype'],
ciuser=module.params['ciuser'],
cpulimit=module.params['cpulimit'],
cpuunits=module.params['cpuunits'],
description=module.params['description'],
digest=module.params['digest'],
force=module.params['force'],
freeze=module.params['freeze'],
hostpci=module.params['hostpci'],
hotplug=module.params['hotplug'],
hugepages=module.params['hugepages'],
ide=module.params['ide'],
ipconfig=module.params['ipconfig'],
keyboard=module.params['keyboard'],
kvm=module.params['kvm'],
localtime=module.params['localtime'],
lock=module.params['lock'],
machine=module.params['machine'],
migrate_downtime=module.params['migrate_downtime'],
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'],
sata=module.params['sata'],
scsi=module.params['scsi'],
scsihw=module.params['scsihw'],
serial=module.params['serial'],
shares=module.params['shares'],
skiplock=module.params['skiplock'],
smbios1=module.params['smbios'],
snapname=module.params['snapname'],
sshkeys=module.params['sshkeys'],
startdate=module.params['startdate'],
startup=module.params['startup'],
tablet=module.params['tablet'],
tags=module.params['tags'],
target=module.params['target'],
tdf=module.params['tdf'],
template=module.params['template'],
vcpus=module.params['vcpus'],
vga=module.params['vga'],
virtio=module.params['virtio'],
watchdog=module.params['watchdog'])
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'])
proxmox.get_vminfo(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, vmid=vmid, msg="VM %s with vmid %s updated" % (name, vmid))
elif clone is not None:
@ -1276,16 +1217,14 @@ def main():
elif state == 'started':
status = {}
try:
if -1 == vmid:
if not vmid:
module.fail_json(msg='VM with name = %s does not exist in cluster' % name)
vm = get_vm(proxmox, vmid)
if not vm:
module.fail_json(vmid=vmid, msg='VM with vmid <%s> does not exist in cluster' % vmid)
status['status'] = vm[0]['status']
if vm[0]['status'] == 'running':
vm = proxmox.get_vm(vmid)
status['status'] = vm['status']
if vm['status'] == 'running':
module.exit_json(changed=False, vmid=vmid, msg="VM %s is already running" % vmid, **status)
if start_vm(module, proxmox, vm):
if proxmox.start_vm(vm):
module.exit_json(changed=True, vmid=vmid, msg="VM %s started" % vmid, **status)
except Exception as e:
module.fail_json(vmid=vmid, msg="starting of VM %s failed with exception: %s" % (vmid, e), **status)
@ -1293,18 +1232,16 @@ def main():
elif state == 'stopped':
status = {}
try:
if -1 == vmid:
if not vmid:
module.fail_json(msg='VM with name = %s does not exist in cluster' % name)
vm = get_vm(proxmox, vmid)
if not vm:
module.fail_json(vmid=vmid, msg='VM with vmid = %s does not exist in cluster' % vmid)
vm = proxmox.get_vm(vmid)
status['status'] = vm[0]['status']
if vm[0]['status'] == 'stopped':
status['status'] = vm['status']
if vm['status'] == 'stopped':
module.exit_json(changed=False, vmid=vmid, msg="VM %s is already stopped" % vmid, **status)
if stop_vm(module, proxmox, vm, force=module.params['force']):
if proxmox.stop_vm(vm, force=module.params['force']):
module.exit_json(changed=True, vmid=vmid, msg="VM %s is shutting down" % vmid, **status)
except Exception as e:
module.fail_json(vmid=vmid, msg="stopping of VM %s failed with exception: %s" % (vmid, e), **status)
@ -1312,17 +1249,15 @@ def main():
elif state == 'restarted':
status = {}
try:
if -1 == vmid:
if not vmid:
module.fail_json(msg='VM with name = %s does not exist in cluster' % name)
vm = get_vm(proxmox, vmid)
if not vm:
module.fail_json(vmid=vmid, msg='VM with vmid = %s does not exist in cluster' % vmid)
status['status'] = vm[0]['status']
if vm[0]['status'] == 'stopped':
vm = proxmox.get_vm(vmid)
status['status'] = vm['status']
if vm['status'] == 'stopped':
module.exit_json(changed=False, vmid=vmid, msg="VM %s is not running" % vmid, **status)
if stop_vm(module, proxmox, vm, force=module.params['force']) and start_vm(module, proxmox, vm):
if proxmox.stop_vm(vm, force=module.params['force']) and proxmox.start_vm(vm):
module.exit_json(changed=True, vmid=vmid, msg="VM %s is restarted" % vmid, **status)
except Exception as e:
module.fail_json(vmid=vmid, msg="restarting of VM %s failed with exception: %s" % (vmid, e), **status)
@ -1330,19 +1265,19 @@ def main():
elif state == 'absent':
status = {}
try:
vm = get_vm(proxmox, vmid)
vm = proxmox.get_vm(vmid, ignore_missing=True)
if not vm:
module.exit_json(changed=False, vmid=vmid)
proxmox_node = proxmox.nodes(vm[0]['node'])
status['status'] = vm[0]['status']
if vm[0]['status'] == 'running':
proxmox_node = proxmox.proxmox_api.nodes(vm['node'])
status['status'] = vm['status']
if vm['status'] == 'running':
if module.params['force']:
stop_vm(module, proxmox, vm, True)
proxmox.stop_vm(vm, True)
else:
module.exit_json(changed=False, vmid=vmid, msg="VM %s is running. Stop it before deletion or use force=yes." % vmid)
taskid = proxmox_node.qemu.delete(vmid)
if not wait_for_task(module, proxmox, vm[0]['node'], taskid):
if not proxmox.wait_for_task(vm['node'], taskid):
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])
else:
@ -1352,14 +1287,12 @@ def main():
elif state == 'current':
status = {}
if -1 == vmid:
if not vmid:
module.fail_json(msg='VM with name = %s does not exist in cluster' % name)
vm = get_vm(proxmox, vmid)
if not vm:
module.fail_json(msg='VM with vmid = %s does not exist in cluster' % vmid)
vm = proxmox.get_vm(vmid)
if not name:
name = vm[0]['name']
current = proxmox.nodes(vm[0]['node']).qemu(vmid).status.current.get()['status']
name = vm['name']
current = proxmox.proxmox_api.nodes(vm['node']).qemu(vmid).status.current.get()['status']
status['status'] = current
if status:
module.exit_json(changed=False, vmid=vmid, msg="VM %s with vmid = %s is %s" % (name, vmid, current), **status)