1626 lines
57 KiB
Python
1626 lines
57 KiB
Python
#!/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)
|
|
"""Control Proxmox KVM machines."""
|
|
|
|
from __future__ import (absolute_import, division, print_function)
|
|
|
|
__metaclass__ = type
|
|
|
|
DOCUMENTATION = r"""
|
|
---
|
|
module: proxmox_kvm
|
|
short_description: Management of Qemu(KVM) Virtual Machines in Proxmox VE cluster.
|
|
description:
|
|
- Allows you to create/delete/stop Qemu(KVM) Virtual Machines in Proxmox VE cluster.
|
|
author: "Abdoul Bah (@helldorado) <bahabdoul at gmail.com>"
|
|
version_added: 1.1.0
|
|
options:
|
|
vmid:
|
|
description:
|
|
- Specifies the instance ID.
|
|
- If not set the next available ID will be fetched from ProxmoxAPI.
|
|
type: int
|
|
node:
|
|
description:
|
|
- Proxmox VE node on which to operate.
|
|
- Only required for I(state=present).
|
|
- For every other states it will be autodiscovered.
|
|
type: str
|
|
pool:
|
|
description:
|
|
- Add the new VM to the specified pool.
|
|
type: str
|
|
api_host:
|
|
description:
|
|
- Specify the target host of the Proxmox VE cluster.
|
|
- You can use C(PROXMOX_SERVER) environment variable.
|
|
type: str
|
|
required: true
|
|
api_user:
|
|
description:
|
|
- Specify the user to authenticate with.
|
|
- You can use C(PROXMOX_USER) environment variable.
|
|
type: str
|
|
required: true
|
|
api_password:
|
|
description:
|
|
- Specify the password to authenticate with.
|
|
- You can use C(PROXMOX_PASSWORD) environment variable.
|
|
type: str
|
|
api_token_id:
|
|
description:
|
|
- Specify the token ID.
|
|
- You can use C(PROXMOX_TOKEN_ID) environment variable.
|
|
type: str
|
|
version_added: 1.3.0
|
|
api_token_secret:
|
|
description:
|
|
- Specify the token secret.
|
|
- You can use C(PROXMOX_TOKEN_SECRET) environment variable.
|
|
type: str
|
|
version_added: 1.3.0
|
|
verify_ssl:
|
|
description:
|
|
- If C(false), SSL certificates will not be validated.
|
|
- This should only be used on personally controlled sites using self-signed certificates.
|
|
type: bool
|
|
default: True
|
|
acpi:
|
|
description:
|
|
- Specify if ACPI should be enabled/disabled.
|
|
- If I(proxmox_default_behavior) is set to C(compatiblity) (the default value), this
|
|
option has a default of C(yes).
|
|
type: bool
|
|
agent:
|
|
description:
|
|
- Specify if the QEMU Guest Agent should be enabled/disabled.
|
|
type: bool
|
|
args:
|
|
description:
|
|
- Pass arbitrary arguments to kvm.
|
|
- This option is for experts only!
|
|
- If I(proxmox_default_behavior) is set to C(compatiblity) (the default value), this
|
|
option has a default of C(-serial unix:/var/run/qemu-server/<vmid>.serial,server,nowait).
|
|
type: str
|
|
autostart:
|
|
description:
|
|
- Specify if the VM should be automatically restarted after crash (currently ignored in PVE API).
|
|
- If I(proxmox_default_behavior) is set to C(compatiblity) (the default value), this
|
|
option has a default of C(no).
|
|
type: bool
|
|
balloon:
|
|
description:
|
|
- Specify the amount of RAM for the VM in MB.
|
|
- Using zero disables the balloon driver.
|
|
- If I(proxmox_default_behavior) is set to C(compatiblity) (the default value), this
|
|
option has a default of C(0).
|
|
type: int
|
|
bios:
|
|
description:
|
|
- Specify the BIOS implementation.
|
|
type: str
|
|
choices: ["seabios", "ovmf"]
|
|
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.
|
|
- If I(proxmox_default_behavior) is set to C(compatiblity) (the default value), this
|
|
option has a default of C(cnd).
|
|
type: str
|
|
bootdisk:
|
|
description:
|
|
- Enable booting from specified disk. C((ide|sata|scsi|virtio)\d+)
|
|
type: str
|
|
cicustom:
|
|
description:
|
|
- "cloud-init: Specify custom files to replace the automatically generated ones at start."
|
|
type: str
|
|
cipassword:
|
|
description:
|
|
- "cloud-init: password of default user to create."
|
|
type: str
|
|
citype:
|
|
description:
|
|
- "cloud-init: Specifies the cloud-init configuration format."
|
|
- The default depends on the configured operating system type (C(ostype)).
|
|
- We use the C(nocloud) format for Linux, and C(configdrive2) for Windows.
|
|
type: str
|
|
choices: ["nocloud", "configdrive2"]
|
|
ciuser:
|
|
description:
|
|
- "cloud-init: username of default user to create."
|
|
type: str
|
|
clone:
|
|
description:
|
|
- Name of VM to be cloned. If C(vmid) is setted, C(clone) can take arbitrary value but required for initiating the clone.
|
|
type: str
|
|
cores:
|
|
description:
|
|
- Specify number of cores per socket.
|
|
- If I(proxmox_default_behavior) is set to C(compatiblity) (the default value), this
|
|
option has a default of C(1).
|
|
type: int
|
|
cpu:
|
|
description:
|
|
- Specify emulated CPU type.
|
|
- If I(proxmox_default_behavior) is set to C(compatiblity) (the default value), this
|
|
option has a default of C(kvm64).
|
|
type: str
|
|
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
|
|
type: int
|
|
cpuunits:
|
|
description:
|
|
- Specify CPU weight for a VM.
|
|
- You can disable fair-scheduler configuration by setting this to 0
|
|
- If I(proxmox_default_behavior) is set to C(compatiblity) (the default value), this
|
|
option has a default of C(1000).
|
|
type: int
|
|
delete:
|
|
description:
|
|
- Specify a list of settings you want to delete.
|
|
type: str
|
|
description:
|
|
description:
|
|
- Specify the description for the VM. Only used on the configuration web interface.
|
|
- This is saved as comment inside the configuration file.
|
|
type: str
|
|
digest:
|
|
description:
|
|
- Specify if to prevent changes if current configuration file has different SHA1 digest.
|
|
- This can be used to prevent concurrent modifications.
|
|
type: str
|
|
force:
|
|
description:
|
|
- Allow to force stop VM.
|
|
- Can be used with states C(stopped), C(restarted) and C(absent).
|
|
- If I(proxmox_default_behavior) is set to C(compatiblity) (the default value), this
|
|
option has a default of C(no).
|
|
type: bool
|
|
format:
|
|
description:
|
|
- Target drive's backing file's data format.
|
|
- Used only with clone
|
|
- Use I(format=unspecified) and I(full=false) for a linked clone.
|
|
- If I(proxmox_default_behavior) is set to C(compatiblity) (the default value), this
|
|
option has a default of C(qcow2). If I(proxmox_default_behavior) is set to C(no_defaults),
|
|
not specifying this option is equivalent to setting it to C(unspecified).
|
|
type: str
|
|
choices:
|
|
["cloop", "cow", "qcow", "qcow2", "qed", "raw", "vmdk", "unspecified"]
|
|
freeze:
|
|
description:
|
|
- Specify if PVE should freeze CPU at startup (use 'c' monitor command to start execution).
|
|
type: bool
|
|
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
|
|
type: bool
|
|
default: "yes"
|
|
hostpci:
|
|
description:
|
|
- Specify a hash/dictionary of map host pci devices into guest. C(hostpci='{"key":"value", "key":"value"}').
|
|
- Keys allowed are - C(hostpci[n]) where 0 ≤ n ≤ N.
|
|
- Values allowed are - C("host="HOSTPCIID[;HOSTPCIID2...]",pcie="1|0",rombar="1|0",x-vga="1|0"").
|
|
- The C(host) parameter is Host PCI device pass through. HOSTPCIID syntax is C(bus:dev.func) (hexadecimal numbers).
|
|
- C(pcie=boolean) I(default=0) Choose the PCI-express bus (needs the q35 machine model).
|
|
- C(rombar=boolean) I(default=1) Specify whether or not the device's ROM will be visible in the guest's memory map.
|
|
- C(x-vga=boolean) I(default=0) Enable vfio-vga device support.
|
|
- /!\ This option allows direct access to host hardware. So it is no longer possible to migrate such machines - use with special care.
|
|
type: dict
|
|
hotplug:
|
|
description:
|
|
- Selectively enable hotplug features.
|
|
- This is a comma separated list of hotplug features C('network', 'disk', 'cpu', 'memory' and 'usb').
|
|
- Value 0 disables hotplug completely and value 1 is an alias for the default C('network,disk,usb').
|
|
type: str
|
|
hugepages:
|
|
description:
|
|
- Enable/disable hugepages memory.
|
|
type: str
|
|
choices: ["any", "2", "1024"]
|
|
ide:
|
|
description:
|
|
- A hash/dictionary of volume used as IDE hard disk or CD-ROM. C(ide='{"key":"value", "key":"value"}').
|
|
- Keys allowed are - C(ide[n]) where 0 ≤ n ≤ 3.
|
|
- Values allowed are - C("storage:size,format=value").
|
|
- C(storage) is the storage identifier where to create the disk.
|
|
- C(size) is the size of the disk in GB.
|
|
- C(format) is the drive's backing file's data format. C(qcow2|raw|subvol).
|
|
type: dict
|
|
ipconfig:
|
|
description:
|
|
- "cloud-init: Set the IP configuration."
|
|
- A hash/dictionary of network ip configurations. C(ipconfig='{"key":"value", "key":"value"}').
|
|
- Keys allowed are - C(ipconfig[n]) where 0 ≤ n ≤ network interfaces.
|
|
- Values allowed are - C("[gw=<GatewayIPv4>] [,gw6=<GatewayIPv6>] [,ip=<IPv4Format/CIDR>] [,ip6=<IPv6Format/CIDR>]").
|
|
- "cloud-init: Specify IP addresses and gateways for the corresponding interface."
|
|
- IP addresses use CIDR notation, gateways are optional but they should be in the same subnet of specified IP address.
|
|
- The special string 'dhcp' can be used for IP addresses to use DHCP, in which case no explicit gateway should be provided.
|
|
- For IPv6 the special string 'auto' can be used to use stateless autoconfiguration.
|
|
- If cloud-init is enabled and neither an IPv4 nor an IPv6 address is specified, it defaults to using dhcp on IPv4.
|
|
type: dict
|
|
keyboard:
|
|
description:
|
|
- Sets the keyboard layout for VNC server.
|
|
type: str
|
|
kvm:
|
|
description:
|
|
- Enable/disable KVM hardware virtualization.
|
|
- If I(proxmox_default_behavior) is set to C(compatiblity) (the default value), this
|
|
option has a default of C(yes).
|
|
type: bool
|
|
localtime:
|
|
description:
|
|
- Sets the real time clock to local time.
|
|
- This is enabled by default if ostype indicates a Microsoft OS.
|
|
type: bool
|
|
lock:
|
|
description:
|
|
- Lock/unlock the VM.
|
|
type: str
|
|
choices: ["migrate", "backup", "snapshot", "rollback"]
|
|
machine:
|
|
description:
|
|
- Specifies the Qemu machine type.
|
|
- type => C((pc|pc(-i440fx)?-\d+\.\d+(\.pxe)?|q35|pc-q35-\d+\.\d+(\.pxe)?))
|
|
type: str
|
|
memory:
|
|
description:
|
|
- Memory size in MB for instance.
|
|
- If I(proxmox_default_behavior) is set to C(compatiblity) (the default value), this
|
|
option has a default of C(512).
|
|
type: int
|
|
migrate_downtime:
|
|
description:
|
|
- Sets maximum tolerated downtime (in seconds) for migrations.
|
|
type: int
|
|
migrate_speed:
|
|
description:
|
|
- Sets maximum speed (in MB/s) for migrations.
|
|
- A value of 0 is no limit.
|
|
type: int
|
|
name:
|
|
description:
|
|
- Specifies the VM name. Only used on the configuration web interface.
|
|
- Required only for C(state=present).
|
|
type: str
|
|
nameservers:
|
|
description:
|
|
- "cloud-init: DNS server IP address(es)."
|
|
- If unset, PVE host settings are used.
|
|
type: list
|
|
elements: str
|
|
net:
|
|
description:
|
|
- A hash/dictionary of network interfaces for the VM. C(net='{"key":"value", "key":"value"}').
|
|
- Keys allowed are - C(net[n]) where 0 ≤ n ≤ N.
|
|
- Values allowed are - C("model="XX:XX:XX:XX:XX:XX",bridge="value",rate="value",tag="value",firewall="1|0",trunks="vlanid"").
|
|
- Model is one of C(e1000 e1000-82540em e1000-82544gc e1000-82545em i82551 i82557b i82559er ne2k_isa ne2k_pci pcnet rtl8139 virtio vmxnet3).
|
|
- C(XX:XX:XX:XX:XX:XX) should be an unique MAC address. This is automatically generated if not specified.
|
|
- The C(bridge) parameter can be used to automatically add the interface to a bridge device. The Proxmox VE standard bridge is called 'vmbr0'.
|
|
- Option C(rate) is used to limit traffic bandwidth from and to this interface. It is specified as floating point number, unit is 'Megabytes per second'.
|
|
- If you specify no bridge, we create a kvm 'user' (NATed) network device, which provides DHCP and DNS services.
|
|
type: dict
|
|
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.
|
|
type: int
|
|
numa:
|
|
description:
|
|
- A hash/dictionaries of NUMA topology. C(numa='{"key":"value", "key":"value"}').
|
|
- Keys allowed are - C(numa[n]) where 0 ≤ n ≤ N.
|
|
- Values allowed are - C("cpu="<id[-id];...>",hostnodes="<id[-id];...>",memory="number",policy="(bind|interleave|preferred)"").
|
|
- C(cpus) CPUs accessing this NUMA node.
|
|
- C(hostnodes) Host NUMA nodes to use.
|
|
- C(memory) Amount of memory this NUMA node provides.
|
|
- C(policy) NUMA allocation policy.
|
|
type: dict
|
|
numa_enabled:
|
|
description:
|
|
- Enables NUMA.
|
|
type: bool
|
|
onboot:
|
|
description:
|
|
- Specifies whether a VM will be started during system bootup.
|
|
- If I(proxmox_default_behavior) is set to C(compatiblity) (the default value), this
|
|
option has a default of C(yes).
|
|
type: bool
|
|
ostype:
|
|
description:
|
|
- Specifies guest operating system. This is used to enable special optimization/features for specific operating systems.
|
|
- The l26 is Linux 2.6/3.X Kernel.
|
|
- If I(proxmox_default_behavior) is set to C(compatiblity) (the default value), this
|
|
option has a default of C(l26).
|
|
type: str
|
|
choices:
|
|
[
|
|
"other",
|
|
"wxp",
|
|
"w2k",
|
|
"w2k3",
|
|
"w2k8",
|
|
"wvista",
|
|
"win7",
|
|
"win8",
|
|
"win10",
|
|
"l24",
|
|
"l26",
|
|
"solaris",
|
|
]
|
|
parallel:
|
|
description:
|
|
- A hash/dictionary of map host parallel devices. C(parallel='{"key":"value", "key":"value"}').
|
|
- Keys allowed are - (parallel[n]) where 0 ≤ n ≤ 2.
|
|
- Values allowed are - C("/dev/parport\d+|/dev/usb/lp\d+").
|
|
type: dict
|
|
protection:
|
|
description:
|
|
- Enable/disable the protection flag of the VM. This will enable/disable the remove VM and remove disk operations.
|
|
type: bool
|
|
reboot:
|
|
description:
|
|
- Allow reboot. If set to C(yes), the VM exit on reboot.
|
|
type: bool
|
|
revert:
|
|
description:
|
|
- Revert a pending change.
|
|
type: str
|
|
sata:
|
|
description:
|
|
- A hash/dictionary of volume used as sata hard disk or CD-ROM. C(sata='{"key":"value", "key":"value"}').
|
|
- Keys allowed are - C(sata[n]) where 0 ≤ n ≤ 5.
|
|
- Values allowed are - C("storage:size,format=value").
|
|
- C(storage) is the storage identifier where to create the disk.
|
|
- C(size) is the size of the disk in GB.
|
|
- C(format) is the drive's backing file's data format. C(qcow2|raw|subvol).
|
|
type: dict
|
|
scsi:
|
|
description:
|
|
- A hash/dictionary of volume used as SCSI hard disk or CD-ROM. C(scsi='{"key":"value", "key":"value"}').
|
|
- Keys allowed are - C(sata[n]) where 0 ≤ n ≤ 13.
|
|
- Values allowed are - C("storage:size,format=value").
|
|
- C(storage) is the storage identifier where to create the disk.
|
|
- C(size) is the size of the disk in GB.
|
|
- C(format) is the drive's backing file's data format. C(qcow2|raw|subvol).
|
|
type: dict
|
|
scsihw:
|
|
description:
|
|
- Specifies the SCSI controller model.
|
|
type: str
|
|
choices:
|
|
[
|
|
"lsi",
|
|
"lsi53c810",
|
|
"virtio-scsi-pci",
|
|
"virtio-scsi-single",
|
|
"megasas",
|
|
"pvscsi",
|
|
]
|
|
searchdomains:
|
|
description:
|
|
- "cloud-init: Sets DNS search domain(s)."
|
|
- If unset, PVE host settings are used.
|
|
type: list
|
|
elements: str
|
|
serial:
|
|
description:
|
|
- A hash/dictionary of serial device to create inside the VM. C('{"key":"value", "key":"value"}').
|
|
- Keys allowed are - serial[n](str; required) where 0 ≤ n ≤ 3.
|
|
- Values allowed are - C((/dev/.+|socket)).
|
|
- /!\ If you pass through a host serial device, it is no longer possible to migrate such machines - use with special care.
|
|
type: dict
|
|
shares:
|
|
description:
|
|
- Rets amount of memory shares for auto-ballooning. (0 - 50000).
|
|
- The larger the number is, the more memory this VM gets.
|
|
- The number is relative to weights of all other running VMs.
|
|
- Using 0 disables auto-ballooning, this means no limit.
|
|
type: int
|
|
skiplock:
|
|
description:
|
|
- Ignore locks
|
|
- Only root is allowed to use this option.
|
|
type: bool
|
|
smbios:
|
|
description:
|
|
- Specifies SMBIOS type 1 fields.
|
|
type: str
|
|
snapname:
|
|
description:
|
|
- The name of the snapshot. Used only with clone.
|
|
type: str
|
|
sockets:
|
|
description:
|
|
- Sets the number of CPU sockets. (1 - N).
|
|
- If I(proxmox_default_behavior) is set to C(compatiblity) (the default value), this
|
|
option has a default of C(1).
|
|
type: int
|
|
sshkeys:
|
|
description:
|
|
- "cloud-init: SSH key to assign to the default user. NOT TESTED with multiple keys but a multi-line value should work."
|
|
type: str
|
|
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').
|
|
type: str
|
|
startup:
|
|
description:
|
|
- Startup and shutdown behavior. C([[order=]\d+] [,up=\d+] [,down=\d+]).
|
|
- Order is a non-negative number defining the general startup order.
|
|
- Shutdown in done with reverse ordering.
|
|
type: str
|
|
state:
|
|
description:
|
|
- Indicates desired state of the instance.
|
|
- If C(current), the current state of the VM will be fetched. You can access it with C(results.status)
|
|
type: str
|
|
choices: ["present", "started", "absent", "stopped", "restarted", "current"]
|
|
default: present
|
|
storage:
|
|
description:
|
|
- Target storage for full clone.
|
|
type: str
|
|
tablet:
|
|
description:
|
|
- Enables/disables the USB tablet device.
|
|
- If I(proxmox_default_behavior) is set to C(compatiblity) (the default value), this
|
|
option has a default of C(no).
|
|
type: bool
|
|
tags:
|
|
description:
|
|
- List of tags to apply to the VM instance.
|
|
- Tags must start with C([a-z0-9_]) followed by zero or more of the following characters C([a-z0-9_-+.]).
|
|
- Tags are only available in Proxmox 6+.
|
|
type: list
|
|
elements: str
|
|
target:
|
|
description:
|
|
- Target node. Only allowed if the original VM is on shared storage.
|
|
- Used only with clone
|
|
type: str
|
|
tdf:
|
|
description:
|
|
- Enables/disables time drift fix.
|
|
type: bool
|
|
template:
|
|
description:
|
|
- Enables/disables the template.
|
|
- If I(proxmox_default_behavior) is set to C(compatiblity) (the default value), this
|
|
option has a default of C(no).
|
|
type: bool
|
|
timeout:
|
|
description:
|
|
- Timeout for operations.
|
|
type: int
|
|
default: 30
|
|
update:
|
|
description:
|
|
- If C(yes), the VM will be updated with new value.
|
|
- Update of C(pool) is disabled. It needs an additional API endpoint not covered by this module.
|
|
type: bool
|
|
default: "no"
|
|
vcpus:
|
|
description:
|
|
- Sets number of hotplugged vcpus.
|
|
type: int
|
|
vga:
|
|
description:
|
|
- Select VGA type. If you want to use high resolution modes (>= 1280x1024x16) then you should use option 'std' or 'vmware'.
|
|
- If I(proxmox_default_behavior) is set to C(compatiblity) (the default value), this
|
|
option has a default of C(std).
|
|
type: str
|
|
choices:
|
|
[
|
|
"std",
|
|
"cirrus",
|
|
"vmware",
|
|
"qxl",
|
|
"serial0",
|
|
"serial1",
|
|
"serial2",
|
|
"serial3",
|
|
"qxl2",
|
|
"qxl3",
|
|
"qxl4",
|
|
]
|
|
virtio:
|
|
description:
|
|
- A hash/dictionary of volume used as VIRTIO hard disk. C(virtio='{"key":"value", "key":"value"}').
|
|
- Keys allowed are - C(virto[n]) where 0 ≤ n ≤ 15.
|
|
- Values allowed are - C("storage:size,format=value").
|
|
- C(storage) is the storage identifier where to create the disk.
|
|
- C(size) is the size of the disk in GB.
|
|
- C(format) is the drive's backing file's data format. C(qcow2|raw|subvol).
|
|
type: dict
|
|
watchdog:
|
|
description:
|
|
- Creates a virtual hardware watchdog device.
|
|
type: str
|
|
proxmox_default_behavior:
|
|
description:
|
|
- Various module options used to have default values. This cause problems when
|
|
user expects different behavior from proxmox by default or fill options which cause
|
|
problems when they have been set.
|
|
- The default value is C(compatibility), which will ensure that the default values
|
|
are used when the values are not explicitly specified by the user.
|
|
type: str
|
|
choices:
|
|
- compatibility
|
|
- no_defaults
|
|
""" # noqa
|
|
|
|
EXAMPLES = """
|
|
- name: Create new VM with minimal options
|
|
xoxys.general.proxmox_kvm:
|
|
api_user: root@pam
|
|
api_password: secret
|
|
api_host: helldorado
|
|
name: spynal
|
|
node: sabrewulf
|
|
|
|
- name: Create new VM with minimal options and given vmid
|
|
xoxys.general.proxmox_kvm:
|
|
api_user: root@pam
|
|
api_password: secret
|
|
api_host: helldorado
|
|
name: spynal
|
|
node: sabrewulf
|
|
vmid: 100
|
|
|
|
- name: Create new VM with two network interface options
|
|
xoxys.general.proxmox_kvm:
|
|
api_user: root@pam
|
|
api_password: secret
|
|
api_host: helldorado
|
|
name: spynal
|
|
node: sabrewulf
|
|
net:
|
|
net0: "virtio,bridge=vmbr1,rate=200"
|
|
net1: "e1000,bridge=vmbr2"
|
|
|
|
- name: Create new VM with one network interface, three virto hard disk, 4 cores, and 2 vcpus
|
|
xoxys.general.proxmox_kvm:
|
|
api_user: root@pam
|
|
api_password: secret
|
|
api_host: helldorado
|
|
name: spynal
|
|
node: sabrewulf
|
|
net:
|
|
net0: "virtio,bridge=vmbr1,rate=200"
|
|
virtio:
|
|
virtio0: "VMs_LVM:10"
|
|
virtio1: "VMs:2,format=qcow2"
|
|
virtio2: "VMs:5,format=raw"
|
|
cores: 4
|
|
vcpus: 2
|
|
|
|
- name: >
|
|
Clone VM with only source VM name.
|
|
The VM source is spynal.
|
|
The target VM name is zavala
|
|
xoxys.general.proxmox_kvm:
|
|
api_user: root@pam
|
|
api_password: secret
|
|
api_host: helldorado
|
|
clone: spynal
|
|
name: zavala
|
|
node: sabrewulf
|
|
storage: VMs
|
|
format: qcow2
|
|
timeout: 500
|
|
|
|
- name: >
|
|
Create linked clone VM with only source VM name.
|
|
The VM source is spynal.
|
|
The target VM name is zavala
|
|
xoxys.general.proxmox_kvm:
|
|
api_user: root@pam
|
|
api_password: secret
|
|
api_host: helldorado
|
|
clone: spynal
|
|
name: zavala
|
|
node: sabrewulf
|
|
storage: VMs
|
|
full: no
|
|
format: unspecified
|
|
timeout: 500
|
|
|
|
- name: Clone VM with source vmid and target newid and raw format
|
|
xoxys.general.proxmox_kvm:
|
|
api_user: root@pam
|
|
api_password: secret
|
|
api_host: helldorado
|
|
clone: arbitrary_name
|
|
vmid: 108
|
|
newid: 152
|
|
name: zavala
|
|
node: sabrewulf
|
|
storage: LVM_STO
|
|
format: raw
|
|
timeout: 300
|
|
|
|
- name: Create new VM and lock it for snapshot
|
|
xoxys.general.proxmox_kvm:
|
|
api_user: root@pam
|
|
api_password: secret
|
|
api_host: helldorado
|
|
name: spynal
|
|
node: sabrewulf
|
|
lock: snapshot
|
|
|
|
- name: Create new VM and set protection to disable the remove VM and remove disk operations
|
|
xoxys.general.proxmox_kvm:
|
|
api_user: root@pam
|
|
api_password: secret
|
|
api_host: helldorado
|
|
name: spynal
|
|
node: sabrewulf
|
|
protection: yes
|
|
|
|
- name: Create new VM using cloud-init with a username and password
|
|
xoxys.general.proxmox_kvm:
|
|
node: sabrewulf
|
|
api_user: root@pam
|
|
api_password: secret
|
|
api_host: helldorado
|
|
name: spynal
|
|
ide:
|
|
ide2: "local:cloudinit,format=qcow2"
|
|
ciuser: mylinuxuser
|
|
cipassword: supersecret
|
|
searchdomains: "mydomain.internal"
|
|
nameservers: 1.1.1.1
|
|
net:
|
|
net0: "virtio,bridge=vmbr1,tag=77"
|
|
ipconfig:
|
|
ipconfig0: "ip=192.168.1.1/24,gw=192.168.1.1"
|
|
|
|
- name: Create new VM using Cloud-Init with an ssh key
|
|
xoxys.general.proxmox_kvm:
|
|
node: sabrewulf
|
|
api_user: root@pam
|
|
api_password: secret
|
|
api_host: helldorado
|
|
name: spynal
|
|
ide:
|
|
ide2: "local:cloudinit,format=qcow2"
|
|
sshkeys: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILJkVm98B71lD5XHfihwcYHE9TVpsJmK1vR1JcaU82L+"
|
|
searchdomains: "mydomain.internal"
|
|
nameservers:
|
|
- "1.1.1.1"
|
|
- "8.8.8.8"
|
|
net:
|
|
net0: "virtio,bridge=vmbr1,tag=77"
|
|
ipconfig:
|
|
ipconfig0: "ip=192.168.1.1/24"
|
|
|
|
- name: Start VM
|
|
xoxys.general.proxmox_kvm:
|
|
api_user: root@pam
|
|
api_password: secret
|
|
api_host: helldorado
|
|
name: spynal
|
|
node: sabrewulf
|
|
state: started
|
|
|
|
- name: Stop VM
|
|
xoxys.general.proxmox_kvm:
|
|
api_user: root@pam
|
|
api_password: secret
|
|
api_host: helldorado
|
|
name: spynal
|
|
node: sabrewulf
|
|
state: stopped
|
|
|
|
- name: Stop VM with force
|
|
xoxys.general.proxmox_kvm:
|
|
api_user: root@pam
|
|
api_password: secret
|
|
api_host: helldorado
|
|
name: spynal
|
|
node: sabrewulf
|
|
state: stopped
|
|
force: yes
|
|
|
|
- name: Restart VM
|
|
xoxys.general.proxmox_kvm:
|
|
api_user: root@pam
|
|
api_password: secret
|
|
api_host: helldorado
|
|
name: spynal
|
|
node: sabrewulf
|
|
state: restarted
|
|
|
|
- name: Remove VM
|
|
xoxys.general.proxmox_kvm:
|
|
api_user: root@pam
|
|
api_password: secret
|
|
api_host: helldorado
|
|
name: spynal
|
|
node: sabrewulf
|
|
state: absent
|
|
|
|
- name: Get VM current state
|
|
xoxys.general.proxmox_kvm:
|
|
api_user: root@pam
|
|
api_password: secret
|
|
api_host: helldorado
|
|
name: spynal
|
|
node: sabrewulf
|
|
state: current
|
|
|
|
- name: Update VM configuration
|
|
xoxys.general.proxmox_kvm:
|
|
api_user: root@pam
|
|
api_password: secret
|
|
api_host: helldorado
|
|
name: spynal
|
|
node: sabrewulf
|
|
cores: 8
|
|
memory: 16384
|
|
update: yes
|
|
|
|
- name: Delete QEMU parameters
|
|
xoxys.general.proxmox_kvm:
|
|
api_user: root@pam
|
|
api_password: secret
|
|
api_host: helldorado
|
|
name: spynal
|
|
node: sabrewulf
|
|
delete: "args,template,cpulimit"
|
|
|
|
- name: Revert a pending change
|
|
xoxys.general.proxmox_kvm:
|
|
api_user: root@pam
|
|
api_password: secret
|
|
api_host: helldorado
|
|
name: spynal
|
|
node: sabrewulf
|
|
revert: "template,cpulimit"
|
|
|
|
"""
|
|
|
|
RETURN = """
|
|
vmid:
|
|
description: The VM vmid.
|
|
returned: success
|
|
type: int
|
|
sample: 115
|
|
status:
|
|
description: The current virtual machine status.
|
|
returned: success, not clone, not absent, not update
|
|
type: str
|
|
sample: running
|
|
msg:
|
|
description: A short message
|
|
returned: always
|
|
type: str
|
|
sample: "VM kropta with vmid = 110 is running"
|
|
"""
|
|
|
|
import re
|
|
import string
|
|
import time
|
|
import traceback
|
|
from collections import defaultdict
|
|
|
|
from ansible.module_utils.six.moves.urllib.parse import quote
|
|
from ansible_collections.xoxys.general.plugins.module_utils.version import LooseVersion
|
|
|
|
try:
|
|
from proxmoxer import ProxmoxAPI
|
|
HAS_PROXMOXER = True
|
|
except ImportError:
|
|
HAS_PROXMOXER = False
|
|
|
|
try:
|
|
from requests.packages import urllib3
|
|
HAS_URLLIB3 = True
|
|
except ImportError:
|
|
try:
|
|
import urllib3
|
|
HAS_URLLIB3 = True
|
|
except ImportError:
|
|
HAS_URLLIB3 = False
|
|
|
|
from ansible.module_utils._text import to_native
|
|
from ansible.module_utils.basic import AnsibleModule, env_fallback
|
|
|
|
|
|
def get_nextvmid(module, proxmox):
|
|
try:
|
|
vmid = proxmox.cluster.nextid.get()
|
|
return vmid
|
|
except Exception as e: # noqa
|
|
module.fail_json(
|
|
msg=f"Unable to get next vmid. Failed with exception: {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 = {}
|
|
try:
|
|
vm = proxmox.nodes(node).qemu(vmid).config.get()
|
|
except Exception as e: # noqa
|
|
module.fail_json(
|
|
msg=f"Getting information for VM with vmid = {vmid} failed with exception: {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 k in list(kwargs.keys()):
|
|
if isinstance(kwargs[k], dict):
|
|
kwargs.update(kwargs[k])
|
|
del kwargs[k]
|
|
|
|
results["nets"] = _extract_nets(vm)
|
|
results["disks"] = _extract_disks(vm)
|
|
results["vmid"] = int(vmid)
|
|
results["_raw"] = vm
|
|
|
|
|
|
def settings(module, proxmox, vmid, node, name, **kwargs): # noqa
|
|
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)
|
|
|
|
return (proxmox_node.qemu(vmid).config.set(**kwargs) is None)
|
|
|
|
|
|
def wait_for_task(module, proxmox, node, taskid):
|
|
timeout = module.params["timeout"]
|
|
|
|
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
|
|
time.sleep(1)
|
|
return True
|
|
timeout = timeout - 1
|
|
if timeout == 0:
|
|
break
|
|
time.sleep(1)
|
|
return False
|
|
|
|
|
|
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 = f"-serial unix:/var/run/qemu-server/{vmid}.serial,server,nowait"
|
|
|
|
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]
|
|
|
|
# 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)
|
|
|
|
disks = {}
|
|
for item in [kwargs[i] for i in ["scsi", "virtio", "ide", "sata"] if i in kwargs]:
|
|
disks = _extract_disks(item)
|
|
|
|
nets = {}
|
|
for item in [kwargs[i] for i in ["net"] if i in kwargs]:
|
|
nets = _extract_nets(item)
|
|
|
|
# If update, ensure existing disks are not recreated.
|
|
if update:
|
|
for k, v in disks.items():
|
|
if results["disks"].get(k):
|
|
storage_id = results["disks"][k]["storage_id"]
|
|
storage_opts = results["disks"][k]["storage_opts"]
|
|
opts = ",".join(v["opts"])
|
|
kwargs[k.rstrip(string.digits)][k] = f"{storage_id}:{storage_opts},{opts}"
|
|
|
|
for k, v in nets.items():
|
|
if results["nets"].get(k):
|
|
net_id = results["nets"][k]["net_id"]
|
|
net_opts = results["nets"][k]["net_opts"]
|
|
opts = ",".join(v["opts"])
|
|
kwargs[k.rstrip(string.digits)][k] = f"{net_id}={net_opts},{opts}"
|
|
|
|
# Convert all dict in kwargs to elements.
|
|
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"]
|
|
|
|
# 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)
|
|
|
|
# VM tags are expected to be valid and presented as a comma/semi-colon delimited string
|
|
if "tags" in kwargs:
|
|
for tag in kwargs["tags"]:
|
|
if not re.match(r"^[a-z0-9_][a-z0-9_\-\+\.]*$", tag):
|
|
module.fail_json(msg=f"{tag} is not a valid 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 and 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. ")
|
|
|
|
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:
|
|
return (
|
|
proxmox_node.qemu(vmid).config.
|
|
set(name=name, memory=memory, cpu=cpu, cores=cores, sockets=sockets, **kwargs) is None
|
|
)
|
|
|
|
if 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
|
|
)
|
|
|
|
if not wait_for_task(module, proxmox, node, taskid):
|
|
module.fail_json(
|
|
msg="Reached timeout while waiting for creating VM."
|
|
f"Last line in task before timeout: {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."
|
|
f"Last line in task before timeout: {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."
|
|
f"Last line in task before timeout: {proxmox_node.tasks(taskid).log.get()[:1]}"
|
|
)
|
|
return False
|
|
return True
|
|
|
|
|
|
def proxmox_version(proxmox):
|
|
apireturn = proxmox.version.get()
|
|
return LooseVersion(apireturn["version"])
|
|
|
|
|
|
def main():
|
|
module = AnsibleModule(
|
|
argument_spec=dict(
|
|
acpi=dict(type="bool"),
|
|
agent=dict(type="bool"),
|
|
args=dict(type="str"),
|
|
api_host=dict(required=True, type="str", fallback=(env_fallback, ["PROXMOX_SERVER"])),
|
|
api_user=dict(required=True, type="str", fallback=(env_fallback, ["PROXMOX_USER"])),
|
|
api_password=dict(
|
|
no_log=True, type="str", fallback=(env_fallback, ["PROXMOX_PASSWORD"])
|
|
),
|
|
api_token_id=dict(
|
|
no_log=True, type="str", fallback=(env_fallback, ["PROXMOX_TOKEN_ID"])
|
|
),
|
|
api_token_secret=dict(
|
|
no_log=True, type="str", fallback=(env_fallback, ["PROXMOX_TOKEN_SECRET"])
|
|
),
|
|
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(type="str"),
|
|
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(type="str"),
|
|
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),
|
|
verify_ssl=dict(type="bool", default=True),
|
|
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(type="str"),
|
|
proxmox_default_behavior=dict(type="str", choices=["compatibility", "no_defaults"]),
|
|
),
|
|
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"]
|
|
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"]
|
|
update = bool(module.params["update"])
|
|
vmid = module.params["vmid"]
|
|
verify_ssl = module.params["verify_ssl"]
|
|
|
|
if module.params["proxmox_default_behavior"] is None:
|
|
module.params["proxmox_default_behavior"] = "compatibility"
|
|
if module.params["proxmox_default_behavior"] == "compatibility":
|
|
old_default_values = dict(
|
|
acpi=True,
|
|
autostart=False,
|
|
balloon=0,
|
|
boot="cnd",
|
|
cores=1,
|
|
cpu="kvm64",
|
|
cpuunits=1000,
|
|
format="qcow2",
|
|
kvm=True,
|
|
memory=512,
|
|
ostype="l26",
|
|
sockets=1,
|
|
tablet=False,
|
|
template=False,
|
|
vga="std",
|
|
)
|
|
for param, value in old_default_values.items():
|
|
if module.params[param] is None:
|
|
module.params[param] = value
|
|
|
|
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
|
|
|
|
if not verify_ssl and HAS_URLLIB3:
|
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
|
|
try:
|
|
proxmox = ProxmoxAPI(api_host, verify_ssl=verify_ssl, **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: # noqa
|
|
module.fail_json(msg=f"authorization on proxmox cluster failed with exception: {e}")
|
|
|
|
# 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)
|
|
except Exception: # noqa
|
|
module.fail_json(
|
|
msg=f"Can't get the next vmid for VM {name} automatically."
|
|
"Ensure your cluster state is good"
|
|
)
|
|
else:
|
|
clone_target = clone or name
|
|
try:
|
|
vmid = get_vmid(proxmox, clone_target)[0]
|
|
except Exception: # noqa
|
|
vmid = -1
|
|
|
|
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)
|
|
except Exception: # noqa
|
|
module.fail_json(
|
|
msg=f"Can't get the next vmid for VM {name} automatically."
|
|
"Ensure your cluster state is good"
|
|
)
|
|
|
|
# Ensure source VM name exists when cloning
|
|
if -1 == vmid:
|
|
module.fail_json(msg=f"VM with name = {clone} does not exist in cluster")
|
|
|
|
# Ensure source VM id exists when cloning
|
|
if not get_vm(proxmox, vmid):
|
|
module.fail_json(vmid=vmid, msg=f"VM with vmid = {vmid} does not exist in cluster")
|
|
|
|
# Ensure the choosen VM name doesn't already exist when cloning
|
|
if get_vmid(proxmox, name):
|
|
module.exit_json(changed=False, vmid=vmid, msg=f"VM with name <{name}> already exists")
|
|
|
|
# Ensure the choosen VM id doesn't already exist when cloning
|
|
if get_vm(proxmox, newid):
|
|
module.exit_json(
|
|
changed=False, vmid=vmid, msg=f"vmid {newid} with VM name {name} already exists"
|
|
)
|
|
|
|
if delete is not None:
|
|
try:
|
|
settings(module, proxmox, vmid, node, name, delete=delete)
|
|
module.exit_json(
|
|
changed=True, vmid=vmid, msg=f"Settings has deleted on VM {name} with vmid {vmid}"
|
|
)
|
|
except Exception as e: # noqa
|
|
module.fail_json(
|
|
vmid=vmid,
|
|
msg=f"Unable to delete settings on VM {name} with vmid {vmid}: {str(e)}"
|
|
)
|
|
|
|
if revert is not None:
|
|
try:
|
|
settings(module, proxmox, vmid, node, name, revert=revert)
|
|
module.exit_json(
|
|
changed=True,
|
|
vmid=vmid,
|
|
msg=f"Settings has reverted on VM {name} with vmid {vmid}"
|
|
)
|
|
except Exception as e: # noqa
|
|
module.fail_json(
|
|
vmid=vmid,
|
|
msg=f"Unable to revert settings on VM {name} with vmid {vmid}:"
|
|
f"Maybe is not a pending task...{str(e)}"
|
|
)
|
|
|
|
if state == "present":
|
|
try:
|
|
if get_vm(proxmox, vmid) and not (update or clone):
|
|
module.exit_json(
|
|
changed=False, vmid=vmid, msg=f"VM with vmid <{vmid}> already exists"
|
|
)
|
|
elif get_vmid(proxmox, name) and not (update or clone):
|
|
module.exit_json(
|
|
changed=False, vmid=vmid, msg=f"VM with name <{name}> already exists"
|
|
)
|
|
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=f"node '{node}' does not exist in cluster")
|
|
|
|
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"]
|
|
)
|
|
|
|
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"]
|
|
)
|
|
|
|
if update:
|
|
module.exit_json(
|
|
changed=True, vmid=vmid, msg=f"VM {name} with vmid {vmid} updated"
|
|
)
|
|
elif clone is not None:
|
|
module.exit_json(
|
|
changed=True,
|
|
vmid=vmid,
|
|
msg=f"VM {name} with newid {newid} cloned from vm with vmid {vmid}"
|
|
)
|
|
else:
|
|
module.exit_json(
|
|
changed=True, msg=f"VM {name} with vmid {vmid} deployed", **results
|
|
)
|
|
except Exception as e: # noqa
|
|
if update:
|
|
module.fail_json(
|
|
vmid=vmid, msg=f"Unable to update vm {name} with vmid {vmid}=" + str(e)
|
|
)
|
|
elif clone is not None:
|
|
module.fail_json(
|
|
vmid=vmid, msg=f"Unable to clone vm {name} from vmid {vmid}=" + str(e)
|
|
)
|
|
else:
|
|
module.fail_json(
|
|
vmid=vmid,
|
|
msg=f"creation of qemu VM {name} with vmid {vmid} failed with exception={e}"
|
|
)
|
|
|
|
elif state == "started":
|
|
status = {}
|
|
try:
|
|
if -1 == vmid:
|
|
module.fail_json(msg=f"VM with name = {name} does not exist in cluster")
|
|
vm = get_vm(proxmox, vmid)
|
|
if not vm:
|
|
module.fail_json(vmid=vmid, msg=f"VM with vmid <{vmid}> does not exist in cluster")
|
|
status["status"] = vm[0]["status"]
|
|
if vm[0]["status"] == "running":
|
|
module.exit_json(
|
|
changed=False, vmid=vmid, msg=f"VM {vmid} is already running", **status
|
|
)
|
|
|
|
if start_vm(module, proxmox, vm):
|
|
module.exit_json(changed=True, vmid=vmid, msg=f"VM {vmid} started", **status)
|
|
except Exception as e: # noqa
|
|
module.fail_json(
|
|
vmid=vmid, msg=f"starting of VM {vmid} failed with exception: {e}", **status
|
|
)
|
|
|
|
elif state == "stopped":
|
|
status = {}
|
|
try:
|
|
if -1 == vmid:
|
|
module.fail_json(msg=f"VM with name = {name} does not exist in cluster")
|
|
|
|
vm = get_vm(proxmox, vmid)
|
|
if not vm:
|
|
module.fail_json(vmid=vmid, msg=f"VM with vmid = {vmid} does not exist in cluster")
|
|
|
|
status["status"] = vm[0]["status"]
|
|
if vm[0]["status"] == "stopped":
|
|
module.exit_json(
|
|
changed=False, vmid=vmid, msg=f"VM {vmid} is already stopped", **status
|
|
)
|
|
|
|
if stop_vm(module, proxmox, vm, force=module.params["force"]):
|
|
module.exit_json(
|
|
changed=True, vmid=vmid, msg=f"VM {vmid} is shutting down", **status
|
|
)
|
|
except Exception as e: # noqa
|
|
module.fail_json(
|
|
vmid=vmid, msg=f"stopping of VM {vmid} failed with exception: {e}", **status
|
|
)
|
|
|
|
elif state == "restarted":
|
|
status = {}
|
|
try:
|
|
if -1 == vmid:
|
|
module.fail_json(msg=f"VM with name = {name} does not exist in cluster")
|
|
|
|
vm = get_vm(proxmox, vmid)
|
|
if not vm:
|
|
module.fail_json(vmid=vmid, msg=f"VM with vmid = {vmid} does not exist in cluster")
|
|
status["status"] = vm[0]["status"]
|
|
if vm[0]["status"] == "stopped":
|
|
module.exit_json(
|
|
changed=False, vmid=vmid, msg=f"VM {vmid} is not running", **status
|
|
)
|
|
|
|
if stop_vm(module, proxmox, vm,
|
|
force=module.params["force"]) and start_vm(module, proxmox, vm):
|
|
module.exit_json(changed=True, vmid=vmid, msg=f"VM {vmid} is restarted", **status)
|
|
except Exception as e: # noqa
|
|
module.fail_json(
|
|
vmid=vmid, msg=f"restarting of VM {vmid} failed with exception: {e}", **status
|
|
)
|
|
|
|
elif state == "absent":
|
|
status = {}
|
|
try:
|
|
vm = get_vm(proxmox, vmid)
|
|
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":
|
|
if module.params["force"]:
|
|
stop_vm(module, proxmox, vm, True)
|
|
else:
|
|
module.exit_json(
|
|
changed=False,
|
|
vmid=vmid,
|
|
msg=f"VM {vmid} is running. Stop it before deletion or use force=yes."
|
|
)
|
|
taskid = proxmox_node.qemu.delete(vmid)
|
|
if not wait_for_task(module, proxmox, vm[0]["node"], taskid):
|
|
module.fail_json(
|
|
msg="Reached timeout while waiting for removing VM."
|
|
f"Last line in task before timeout: {proxmox_node.tasks(taskid).log.get()[:1]}"
|
|
)
|
|
else:
|
|
module.exit_json(changed=True, vmid=vmid, msg=f"VM {vmid} removed")
|
|
except Exception as e: # noqa
|
|
module.fail_json(msg=f"deletion of VM {vmid} failed with exception: {e}")
|
|
|
|
elif state == "current":
|
|
status = {}
|
|
if -1 == vmid:
|
|
module.fail_json(msg=f"VM with name = {name} does not exist in cluster")
|
|
vm = get_vm(proxmox, vmid)
|
|
if not vm:
|
|
module.fail_json(msg=f"VM with vmid = {vmid} does not exist in cluster")
|
|
if not name:
|
|
name = vm[0]["name"]
|
|
current = proxmox.nodes(vm[0]["node"]).qemu(vmid).status.current.get()["status"]
|
|
status["status"] = current
|
|
if status:
|
|
module.exit_json(
|
|
changed=False,
|
|
vmid=vmid,
|
|
msg=f"VM {name} with vmid = {vmid} is {current}",
|
|
**status
|
|
)
|
|
|
|
|
|
def _extract_disks(item):
|
|
disks = defaultdict(dict)
|
|
for k, v in item.items():
|
|
if re.match(r"(scsi|virtio|ide|sata)[0-9]", k):
|
|
disks[k]["opts"] = []
|
|
for val in v.split(","):
|
|
if len(val.split(":")) == 2:
|
|
storage = val.split(":")
|
|
disks[k]["storage_id"] = storage[0]
|
|
disks[k]["storage_opts"] = storage[1]
|
|
else:
|
|
disks[k]["opts"].append(val)
|
|
|
|
return disks
|
|
|
|
|
|
def _extract_nets(item):
|
|
nets = defaultdict(dict)
|
|
for k, v in item.items():
|
|
if re.match(r"net[0-9]", k):
|
|
nets[k]["opts"] = []
|
|
for val in v.split(","):
|
|
if (
|
|
any(val.startswith(s) for s in ["e1000", "rtl8139", "virtio", "vmxnet3"])
|
|
and len(val.split("=")) == 2
|
|
):
|
|
net = val.split("=")
|
|
nets[k]["net_id"] = net[0]
|
|
nets[k]["net_opts"] = net[1]
|
|
else:
|
|
nets[k]["opts"].append(val)
|
|
|
|
return nets
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|