Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[proxmox_vm_info] Add ability to retrieve config #7485

Merged
merged 13 commits into from
Dec 4, 2023
2 changes: 2 additions & 0 deletions changelogs/fragments/7485-proxmox_vm_info-config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
minor_changes:
- proxmox_vm_info - add ability to retrieve configuration info (https://github.com/ansible-collections/community.general/pull/7485).
106 changes: 63 additions & 43 deletions plugins/modules/proxmox_vm_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,19 @@
- 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.
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:
- community.general.proxmox.documentation
- community.general.attributes
Expand Down Expand Up @@ -73,14 +86,15 @@
type: qemu
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:
api_host: proxmoxhost
api_user: root@pam
api_password: '{{ password | default(omit) }}'
node: node01
type: lxc
name: lxc05.home.arpa
config: current
"""

RETURN = """
Expand Down Expand Up @@ -154,42 +168,46 @@ def get_vms_from_cluster_resources(self):
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):
vms = []
for vm in vms_unfiltered:
if (
type != vm["type"]
or (node and vm["node"] != node)
or (vmid and int(vm["vmid"]) != vmid)
or (name is not None and vm["name"] != name)
):
continue
vms.append(vm)
nodes = frozenset([vm["node"] for vm in vms])
for node in nodes:
if type == "qemu":
vms_from_nodes = self.proxmox_api.nodes(node).qemu().get()
else:
vms_from_nodes = self.proxmox_api.nodes(node).lxc().get()
for vmn in vms_from_nodes:
for vm in vms:
if int(vm["vmid"]) == int(vmn["vmid"]):
vm.update(vmn)
vm["vmid"] = int(vm["vmid"])
vm["template"] = proxmox_to_ansible_bool(vm["template"])
break

return vms

def get_qemu_vms(self, vms_unfiltered, vmid=None, name=None, node=None):
def get_vms_from_nodes(self, cluster_machines, type, vmid=None, name=None, node=None, config=None):
# Leave in dict only machines that user wants to know about
filtered_vms = {
vm: info for vm, info in cluster_machines.items() if not (
type != info["type"]
or (node and info["node"] != node)
or (vmid and int(info["vmid"]) != vmid)
or (name is not None and info["name"] != name)
)
}
# Get list of unique node names and loop through it to get info about machines.
nodes = frozenset([info["node"] for vm, info in filtered_vms.items()])
for this_node in nodes:
# "type" is mandatory and can have only values of "qemu" or "lxc". Seems that use of reflection is safe.
call_vm_getter = getattr(self.proxmox_api.nodes(this_node), type)
vms_from_this_node = call_vm_getter.get()
castorsky marked this conversation as resolved.
Show resolved Hide resolved
for detected_vm in vms_from_this_node:
desired_vm = filtered_vms.get(int(detected_vm["vmid"]), None)
castorsky marked this conversation as resolved.
Show resolved Hide resolved
if desired_vm:
desired_vm.update(detected_vm)
this_vm_id = int(desired_vm["vmid"])
desired_vm["vmid"] = this_vm_id
castorsky marked this conversation as resolved.
Show resolved Hide resolved
desired_vm["template"] = proxmox_to_ansible_bool(desired_vm["template"])
# 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)
castorsky marked this conversation as resolved.
Show resolved Hide resolved
return filtered_vms

def get_qemu_vms(self, cluster_machines, vmid=None, name=None, node=None, config=None):
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:
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:
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:
self.module.fail_json(msg="Failed to retrieve LXC VMs information: %s" % e)

Expand All @@ -203,6 +221,10 @@ def main():
),
vmid=dict(type="int", 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)

Expand All @@ -218,28 +240,26 @@ def main():
type = module.params["type"]
vmid = module.params["vmid"]
name = module.params["name"]
config = module.params["config"]

result = dict(changed=False)

if node and proxmox.get_node(node) is None:
module.fail_json(msg="Node %s doesn't exist in PVE cluster" % node)

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":
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":
vms = proxmox.get_qemu_vms(vms_cluster_resources, vmid, name, node)
vms = proxmox.get_qemu_vms(cluster_machines, vmid, name, node, config)
else:
vms = proxmox.get_qemu_vms(
vms_cluster_resources,
vmid,
name,
node,
) + proxmox.get_lxc_vms(vms_cluster_resources, vmid, name, node)

result["proxmox_vms"] = vms
vms = proxmox.get_qemu_vms(cluster_machines, vmid, name, node, config)
vms.update(proxmox.get_lxc_vms(cluster_machines, vmid, name, node, config))

result["proxmox_vms"] = list(vms.values())
castorsky marked this conversation as resolved.
Show resolved Hide resolved
module.exit_json(**result)


Expand Down
15 changes: 7 additions & 8 deletions tests/unit/plugins/modules/test_proxmox_vm_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -434,10 +434,10 @@ def setUp(self):
self.connect_mock = patch(
"ansible_collections.community.general.plugins.module_utils.proxmox.ProxmoxAnsible._connect",
).start()
self.connect_mock.return_value.nodes.return_value.lxc.return_value.get.return_value = (
self.connect_mock.return_value.nodes.return_value.lxc.get.return_value = (
RAW_LXC_OUTPUT
)
self.connect_mock.return_value.nodes.return_value.qemu.return_value.get.return_value = (
self.connect_mock.return_value.nodes.return_value.qemu.get.return_value = (
RAW_QEMU_OUTPUT
)
self.connect_mock.return_value.cluster.return_value.resources.return_value.get.return_value = (
Expand Down Expand Up @@ -566,7 +566,7 @@ def test_get_multiple_vms_with_the_same_name(self):
assert result["proxmox_vms"] == expected_output
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 = ""
self.connect_mock.return_value.cluster.resources.get.return_value = [
{"name": name, "vmid": "105"},
Expand Down Expand Up @@ -629,7 +629,7 @@ def test_module_returns_empty_list_when_vm_does_not_exist(self):
assert result["proxmox_vms"] == []

def test_module_fail_when_qemu_request_fails(self):
self.connect_mock.return_value.nodes.return_value.qemu.return_value.get.side_effect = IOError(
self.connect_mock.return_value.nodes.return_value.qemu.get.side_effect = IOError(
"Some mocked connection error."
)
with pytest.raises(AnsibleFailJson) as exc_info:
Expand All @@ -640,7 +640,7 @@ def test_module_fail_when_qemu_request_fails(self):
assert "Failed to retrieve QEMU VMs information:" in result["msg"]

def test_module_fail_when_lxc_request_fails(self):
self.connect_mock.return_value.nodes.return_value.lxc.return_value.get.side_effect = IOError(
self.connect_mock.return_value.nodes.return_value.lxc.get.side_effect = IOError(
"Some mocked connection error."
)
with pytest.raises(AnsibleFailJson) as exc_info:
Expand All @@ -665,13 +665,12 @@ def test_module_fail_when_cluster_resources_request_fails(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:
set_module_args(get_module_args(type="all", node=NODE1))
set_module_args(get_module_args(type="all", node="NODE3"))
self.module.main()

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):
with patch(
Expand Down
Loading