mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
[proxmox_vm_info] Add ability to retrieve config (#7485)
* feat: add ability to retrieve config Light refactor of get_vms_from_nodes function. Added ability to retrieve configuration for existing machines (current or pending). * Add changelog fragment * Add changelog fragment (newline missed) * Update changelogs/fragments/7485-proxmox_vm_info-config.yml Co-authored-by: Felix Fontein <felix@fontein.de> * Apply suggestions from code review Co-authored-by: Felix Fontein <felix@fontein.de> * Replaced two bool options with one three-state option * Module args for the three-state option * Remove trailing newline * Make use of dict instead of list. Fix uncalled 'get config for lxc'. * Sanity tests * A couple of unit tests fixed * Unit tests fixed * Unit tests for p2.7 fixed. Test for config parameter added. --------- Co-authored-by: Felix Fontein <felix@fontein.de>
This commit is contained in:
parent
a599afa384
commit
68051774d8
3 changed files with 101 additions and 52 deletions
2
changelogs/fragments/7485-proxmox_vm_info-config.yml
Normal file
2
changelogs/fragments/7485-proxmox_vm_info-config.yml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
minor_changes:
|
||||||
|
- proxmox_vm_info - add ability to retrieve configuration info (https://github.com/ansible-collections/community.general/pull/7485).
|
|
@ -41,6 +41,19 @@ options:
|
||||||
- Restrict results to a specific virtual machine(s) by using their name.
|
- Restrict results to a specific virtual machine(s) by using their name.
|
||||||
- If VM(s) with the specified name do not exist in a cluster then the resulting list will be empty.
|
- If VM(s) with the specified name do not exist in a cluster then the resulting list will be empty.
|
||||||
type: str
|
type: str
|
||||||
|
config:
|
||||||
|
description:
|
||||||
|
- Whether to retrieve the VM configuration along with VM status.
|
||||||
|
- If set to V(none) (default), no configuration will be returned.
|
||||||
|
- If set to V(current), the current running configuration will be returned.
|
||||||
|
- If set to V(pending), the configuration with pending changes applied will be returned.
|
||||||
|
type: str
|
||||||
|
choices:
|
||||||
|
- none
|
||||||
|
- current
|
||||||
|
- pending
|
||||||
|
default: none
|
||||||
|
version_added: 8.1.0
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
- community.general.proxmox.documentation
|
- community.general.proxmox.documentation
|
||||||
- community.general.attributes
|
- community.general.attributes
|
||||||
|
@ -73,7 +86,7 @@ EXAMPLES = """
|
||||||
type: qemu
|
type: qemu
|
||||||
vmid: 101
|
vmid: 101
|
||||||
|
|
||||||
- name: Retrieve information about specific VM by name
|
- name: Retrieve information about specific VM by name and get current configuration
|
||||||
community.general.proxmox_vm_info:
|
community.general.proxmox_vm_info:
|
||||||
api_host: proxmoxhost
|
api_host: proxmoxhost
|
||||||
api_user: root@pam
|
api_user: root@pam
|
||||||
|
@ -81,6 +94,7 @@ EXAMPLES = """
|
||||||
node: node01
|
node: node01
|
||||||
type: lxc
|
type: lxc
|
||||||
name: lxc05.home.arpa
|
name: lxc05.home.arpa
|
||||||
|
config: current
|
||||||
"""
|
"""
|
||||||
|
|
||||||
RETURN = """
|
RETURN = """
|
||||||
|
@ -154,42 +168,46 @@ class ProxmoxVmInfoAnsible(ProxmoxAnsible):
|
||||||
msg="Failed to retrieve VMs information from cluster resources: %s" % e
|
msg="Failed to retrieve VMs information from cluster resources: %s" % e
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_vms_from_nodes(self, vms_unfiltered, type, vmid=None, name=None, node=None):
|
def get_vms_from_nodes(self, cluster_machines, type, vmid=None, name=None, node=None, config=None):
|
||||||
vms = []
|
# Leave in dict only machines that user wants to know about
|
||||||
for vm in vms_unfiltered:
|
filtered_vms = {
|
||||||
if (
|
vm: info for vm, info in cluster_machines.items() if not (
|
||||||
type != vm["type"]
|
type != info["type"]
|
||||||
or (node and vm["node"] != node)
|
or (node and info["node"] != node)
|
||||||
or (vmid and int(vm["vmid"]) != vmid)
|
or (vmid and int(info["vmid"]) != vmid)
|
||||||
or (name is not None and vm["name"] != name)
|
or (name is not None and info["name"] != name)
|
||||||
):
|
)
|
||||||
continue
|
}
|
||||||
vms.append(vm)
|
# Get list of unique node names and loop through it to get info about machines.
|
||||||
nodes = frozenset([vm["node"] for vm in vms])
|
nodes = frozenset([info["node"] for vm, info in filtered_vms.items()])
|
||||||
for node in nodes:
|
for this_node in nodes:
|
||||||
if type == "qemu":
|
# "type" is mandatory and can have only values of "qemu" or "lxc". Seems that use of reflection is safe.
|
||||||
vms_from_nodes = self.proxmox_api.nodes(node).qemu().get()
|
call_vm_getter = getattr(self.proxmox_api.nodes(this_node), type)
|
||||||
else:
|
vms_from_this_node = call_vm_getter().get()
|
||||||
vms_from_nodes = self.proxmox_api.nodes(node).lxc().get()
|
for detected_vm in vms_from_this_node:
|
||||||
for vmn in vms_from_nodes:
|
this_vm_id = int(detected_vm["vmid"])
|
||||||
for vm in vms:
|
desired_vm = filtered_vms.get(this_vm_id, None)
|
||||||
if int(vm["vmid"]) == int(vmn["vmid"]):
|
if desired_vm:
|
||||||
vm.update(vmn)
|
desired_vm.update(detected_vm)
|
||||||
vm["vmid"] = int(vm["vmid"])
|
desired_vm["vmid"] = this_vm_id
|
||||||
vm["template"] = proxmox_to_ansible_bool(vm["template"])
|
desired_vm["template"] = proxmox_to_ansible_bool(desired_vm["template"])
|
||||||
break
|
# When user wants to retrieve the VM configuration
|
||||||
|
if config != "none":
|
||||||
|
# pending = 0, current = 1
|
||||||
|
config_type = 0 if config == "pending" else 1
|
||||||
|
# GET /nodes/{node}/qemu/{vmid}/config current=[0/1]
|
||||||
|
desired_vm["config"] = call_vm_getter(this_vm_id).config().get(current=config_type)
|
||||||
|
return filtered_vms
|
||||||
|
|
||||||
return vms
|
def get_qemu_vms(self, cluster_machines, vmid=None, name=None, node=None, config=None):
|
||||||
|
|
||||||
def get_qemu_vms(self, vms_unfiltered, vmid=None, name=None, node=None):
|
|
||||||
try:
|
try:
|
||||||
return self.get_vms_from_nodes(vms_unfiltered, "qemu", vmid, name, node)
|
return self.get_vms_from_nodes(cluster_machines, "qemu", vmid, name, node, config)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.module.fail_json(msg="Failed to retrieve QEMU VMs information: %s" % e)
|
self.module.fail_json(msg="Failed to retrieve QEMU VMs information: %s" % e)
|
||||||
|
|
||||||
def get_lxc_vms(self, vms_unfiltered, vmid=None, name=None, node=None):
|
def get_lxc_vms(self, cluster_machines, vmid=None, name=None, node=None, config=None):
|
||||||
try:
|
try:
|
||||||
return self.get_vms_from_nodes(vms_unfiltered, "lxc", vmid, name, node)
|
return self.get_vms_from_nodes(cluster_machines, "lxc", vmid, name, node, config)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.module.fail_json(msg="Failed to retrieve LXC VMs information: %s" % e)
|
self.module.fail_json(msg="Failed to retrieve LXC VMs information: %s" % e)
|
||||||
|
|
||||||
|
@ -203,6 +221,10 @@ def main():
|
||||||
),
|
),
|
||||||
vmid=dict(type="int", required=False),
|
vmid=dict(type="int", required=False),
|
||||||
name=dict(type="str", required=False),
|
name=dict(type="str", required=False),
|
||||||
|
config=dict(
|
||||||
|
type="str", choices=["none", "current", "pending"],
|
||||||
|
default="none", required=False
|
||||||
|
),
|
||||||
)
|
)
|
||||||
module_args.update(vm_info_args)
|
module_args.update(vm_info_args)
|
||||||
|
|
||||||
|
@ -218,6 +240,7 @@ def main():
|
||||||
type = module.params["type"]
|
type = module.params["type"]
|
||||||
vmid = module.params["vmid"]
|
vmid = module.params["vmid"]
|
||||||
name = module.params["name"]
|
name = module.params["name"]
|
||||||
|
config = module.params["config"]
|
||||||
|
|
||||||
result = dict(changed=False)
|
result = dict(changed=False)
|
||||||
|
|
||||||
|
@ -225,21 +248,18 @@ def main():
|
||||||
module.fail_json(msg="Node %s doesn't exist in PVE cluster" % node)
|
module.fail_json(msg="Node %s doesn't exist in PVE cluster" % node)
|
||||||
|
|
||||||
vms_cluster_resources = proxmox.get_vms_from_cluster_resources()
|
vms_cluster_resources = proxmox.get_vms_from_cluster_resources()
|
||||||
vms = []
|
cluster_machines = {int(machine["vmid"]): machine for machine in vms_cluster_resources}
|
||||||
|
vms = {}
|
||||||
|
|
||||||
if type == "lxc":
|
if type == "lxc":
|
||||||
vms = proxmox.get_lxc_vms(vms_cluster_resources, vmid, name, node)
|
vms = proxmox.get_lxc_vms(cluster_machines, vmid, name, node, config)
|
||||||
elif type == "qemu":
|
elif type == "qemu":
|
||||||
vms = proxmox.get_qemu_vms(vms_cluster_resources, vmid, name, node)
|
vms = proxmox.get_qemu_vms(cluster_machines, vmid, name, node, config)
|
||||||
else:
|
else:
|
||||||
vms = proxmox.get_qemu_vms(
|
vms = proxmox.get_qemu_vms(cluster_machines, vmid, name, node, config)
|
||||||
vms_cluster_resources,
|
vms.update(proxmox.get_lxc_vms(cluster_machines, vmid, name, node, config))
|
||||||
vmid,
|
|
||||||
name,
|
|
||||||
node,
|
|
||||||
) + proxmox.get_lxc_vms(vms_cluster_resources, vmid, name, node)
|
|
||||||
|
|
||||||
result["proxmox_vms"] = vms
|
result["proxmox_vms"] = [info for vm, info in sorted(vms.items())]
|
||||||
module.exit_json(**result)
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -414,7 +414,7 @@ EXPECTED_VMS_OUTPUT = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def get_module_args(type="all", node=None, vmid=None, name=None):
|
def get_module_args(type="all", node=None, vmid=None, name=None, config="none"):
|
||||||
return {
|
return {
|
||||||
"api_host": "host",
|
"api_host": "host",
|
||||||
"api_user": "user",
|
"api_user": "user",
|
||||||
|
@ -423,6 +423,7 @@ def get_module_args(type="all", node=None, vmid=None, name=None):
|
||||||
"type": type,
|
"type": type,
|
||||||
"vmid": vmid,
|
"vmid": vmid,
|
||||||
"name": name,
|
"name": name,
|
||||||
|
"config": config,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -460,23 +461,21 @@ class TestProxmoxVmInfoModule(ModuleTestCase):
|
||||||
def test_get_lxc_vms_information(self):
|
def test_get_lxc_vms_information(self):
|
||||||
with pytest.raises(AnsibleExitJson) as exc_info:
|
with pytest.raises(AnsibleExitJson) as exc_info:
|
||||||
set_module_args(get_module_args(type="lxc"))
|
set_module_args(get_module_args(type="lxc"))
|
||||||
|
expected_output = [vm for vm in EXPECTED_VMS_OUTPUT if vm["type"] == "lxc"]
|
||||||
self.module.main()
|
self.module.main()
|
||||||
|
|
||||||
result = exc_info.value.args[0]
|
result = exc_info.value.args[0]
|
||||||
assert result["changed"] is False
|
assert result["changed"] is False
|
||||||
assert result["proxmox_vms"] == [
|
assert result["proxmox_vms"] == expected_output
|
||||||
vm for vm in EXPECTED_VMS_OUTPUT if vm["type"] == "lxc"
|
|
||||||
]
|
|
||||||
|
|
||||||
def test_get_qemu_vms_information(self):
|
def test_get_qemu_vms_information(self):
|
||||||
with pytest.raises(AnsibleExitJson) as exc_info:
|
with pytest.raises(AnsibleExitJson) as exc_info:
|
||||||
set_module_args(get_module_args(type="qemu"))
|
set_module_args(get_module_args(type="qemu"))
|
||||||
|
expected_output = [vm for vm in EXPECTED_VMS_OUTPUT if vm["type"] == "qemu"]
|
||||||
self.module.main()
|
self.module.main()
|
||||||
|
|
||||||
result = exc_info.value.args[0]
|
result = exc_info.value.args[0]
|
||||||
assert result["proxmox_vms"] == [
|
assert result["proxmox_vms"] == expected_output
|
||||||
vm for vm in EXPECTED_VMS_OUTPUT if vm["type"] == "qemu"
|
|
||||||
]
|
|
||||||
|
|
||||||
def test_get_all_vms_information(self):
|
def test_get_all_vms_information(self):
|
||||||
with pytest.raises(AnsibleExitJson) as exc_info:
|
with pytest.raises(AnsibleExitJson) as exc_info:
|
||||||
|
@ -566,7 +565,7 @@ class TestProxmoxVmInfoModule(ModuleTestCase):
|
||||||
assert result["proxmox_vms"] == expected_output
|
assert result["proxmox_vms"] == expected_output
|
||||||
assert len(result["proxmox_vms"]) == 2
|
assert len(result["proxmox_vms"]) == 2
|
||||||
|
|
||||||
def test_get_multiple_vms_with_the_same_name(self):
|
def test_get_vm_with_an_empty_name(self):
|
||||||
name = ""
|
name = ""
|
||||||
self.connect_mock.return_value.cluster.resources.get.return_value = [
|
self.connect_mock.return_value.cluster.resources.get.return_value = [
|
||||||
{"name": name, "vmid": "105"},
|
{"name": name, "vmid": "105"},
|
||||||
|
@ -665,13 +664,12 @@ class TestProxmoxVmInfoModule(ModuleTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_module_fail_when_node_does_not_exist(self):
|
def test_module_fail_when_node_does_not_exist(self):
|
||||||
self.connect_mock.return_value.nodes.get.return_value = []
|
|
||||||
with pytest.raises(AnsibleFailJson) as exc_info:
|
with pytest.raises(AnsibleFailJson) as exc_info:
|
||||||
set_module_args(get_module_args(type="all", node=NODE1))
|
set_module_args(get_module_args(type="all", node="NODE3"))
|
||||||
self.module.main()
|
self.module.main()
|
||||||
|
|
||||||
result = exc_info.value.args[0]
|
result = exc_info.value.args[0]
|
||||||
assert result["msg"] == "Node pve doesn't exist in PVE cluster"
|
assert result["msg"] == "Node NODE3 doesn't exist in PVE cluster"
|
||||||
|
|
||||||
def test_call_to_get_vmid_is_not_used_when_vmid_provided(self):
|
def test_call_to_get_vmid_is_not_used_when_vmid_provided(self):
|
||||||
with patch(
|
with patch(
|
||||||
|
@ -685,3 +683,32 @@ class TestProxmoxVmInfoModule(ModuleTestCase):
|
||||||
self.module.main()
|
self.module.main()
|
||||||
|
|
||||||
assert get_vmid_mock.call_count == 0
|
assert get_vmid_mock.call_count == 0
|
||||||
|
|
||||||
|
def test_config_returned_when_specified_qemu_vm_with_config(self):
|
||||||
|
config_vm_value = {
|
||||||
|
'scsi0': 'local-lvm:vm-101-disk-0,iothread=1,size=32G',
|
||||||
|
'net0': 'virtio=4E:79:9F:A8:EE:E4,bridge=vmbr0,firewall=1',
|
||||||
|
'scsihw': 'virtio-scsi-single',
|
||||||
|
'cores': 1,
|
||||||
|
'name': 'test1',
|
||||||
|
'ostype': 'l26',
|
||||||
|
'boot': 'order=scsi0;ide2;net0',
|
||||||
|
'memory': 2048,
|
||||||
|
'sockets': 1,
|
||||||
|
}
|
||||||
|
(self.connect_mock.return_value.nodes.return_value.qemu.return_value.
|
||||||
|
config.return_value.get.return_value) = config_vm_value
|
||||||
|
|
||||||
|
with pytest.raises(AnsibleExitJson) as exc_info:
|
||||||
|
vmid = 101
|
||||||
|
set_module_args(get_module_args(
|
||||||
|
type="qemu",
|
||||||
|
vmid=vmid,
|
||||||
|
config="current",
|
||||||
|
))
|
||||||
|
expected_output = [vm for vm in EXPECTED_VMS_OUTPUT if vm["vmid"] == vmid]
|
||||||
|
expected_output[0]["config"] = config_vm_value
|
||||||
|
self.module.main()
|
||||||
|
|
||||||
|
result = exc_info.value.args[0]
|
||||||
|
assert result["proxmox_vms"] == expected_output
|
||||||
|
|
Loading…
Reference in a new issue