diff --git a/changelogs/fragments/4724-proxmox-qemu-extend.yaml b/changelogs/fragments/4724-proxmox-qemu-extend.yaml new file mode 100644 index 00000000000..4b705bf9a9e --- /dev/null +++ b/changelogs/fragments/4724-proxmox-qemu-extend.yaml @@ -0,0 +1,3 @@ +minor_changes: + - proxmox inventory plugin - added new flag ``qemu_extended_statuses`` and new groups ``prelaunch``, ``paused``. They will be populated only when ``want_facts=true``, ``qemu_extended_statuses=true`` and only for ``QEMU`` machines + (https://github.com/ansible-collections/community.general/pull/4723). diff --git a/plugins/inventory/proxmox.py b/plugins/inventory/proxmox.py index e0734b33e32..1664e6ba03a 100644 --- a/plugins/inventory/proxmox.py +++ b/plugins/inventory/proxmox.py @@ -92,9 +92,21 @@ default: proxmox_ type: str want_facts: - description: Gather LXC/QEMU configuration facts. + description: + - Gather LXC/QEMU configuration facts. + - When I(want_facts) is set to C(true) more details about QEMU VM status are possible, besides the running and stopped states. + Currently if the VM is running and it is suspended, the status will be running and the machine will be in C(running) group, + but its actual state will be paused. See I(qemu_extended_statuses) for how to retrieve the real status. default: no type: bool + qemu_extended_statuses: + description: + - Requires I(want_facts) to be set to C(true) to function. This will allow you to differentiate betweend C(paused) and C(prelaunch) + statuses of the QEMU VMs. + - This introduces multiple groups [prefixed with I(group_prefix)] C(prelaunch) and C(paused). + default: no + type: bool + version_added: 5.1.0 want_proxmox_nodes_ansible_host: version_added: 3.0.0 description: @@ -431,6 +443,7 @@ def _get_vm_config(self, properties, node, vmid, vmtype, name): def _get_vm_status(self, properties, node, vmid, vmtype, name): ret = self._get_json("%s/api2/json/nodes/%s/%s/%s/status/current" % (self.proxmox_url, node, vmtype, vmid)) properties[self._fact('status')] = ret['status'] + properties[self._fact('qmpstatus')] = ret['qmpstatus'] def _get_vm_snapshots(self, properties, node, vmid, vmtype, name): ret = self._get_json("%s/api2/json/nodes/%s/%s/%s/snapshot" % (self.proxmox_url, node, vmtype, vmid)) @@ -489,7 +502,8 @@ def _handle_item(self, node, ittype, item): name, vmid = item['name'], item['vmid'] # get status, config and snapshots if want_facts == True - if self.get_option('want_facts'): + want_facts = self.get_option('want_facts') + if want_facts: self._get_vm_status(properties, node, vmid, ittype, name) self._get_vm_config(properties, node, vmid, ittype, name) self._get_vm_snapshots(properties, node, vmid, ittype, name) @@ -503,10 +517,13 @@ def _handle_item(self, node, ittype, item): node_type_group = self._group('%s_%s' % (node, ittype)) self.inventory.add_child(self._group('all_' + ittype), name) self.inventory.add_child(node_type_group, name) - if item['status'] == 'stopped': - self.inventory.add_child(self._group('all_stopped'), name) - elif item['status'] == 'running': - self.inventory.add_child(self._group('all_running'), name) + + item_status = item['status'] + if item_status == 'running': + if want_facts and ittype == 'qemu' and self.get_option('qemu_extended_statuses'): + # get more details about the status of the qemu VM + item_status = properties.get(self._fact('qmpstatus'), item_status) + self.inventory.add_child(self._group('all_%s' % (item_status, )), name) return name @@ -528,10 +545,14 @@ def _populate_pool_groups(self, added_hosts): def _populate(self): # create common groups - self.inventory.add_group(self._group('all_lxc')) - self.inventory.add_group(self._group('all_qemu')) - self.inventory.add_group(self._group('all_running')) - self.inventory.add_group(self._group('all_stopped')) + default_groups = ['lxc', 'qemu', 'running', 'stopped'] + + if self.get_option('qemu_extended_statuses'): + default_groups.extend(['prelaunch', 'paused']) + + for group in default_groups: + self.inventory.add_group(self._group('all_%s' % (group))) + nodes_group = self._group('nodes') self.inventory.add_group(nodes_group) @@ -621,6 +642,9 @@ def parse(self, inventory, loader, path, cache=True): if proxmox_password is None and (proxmox_token_id is None or proxmox_token_secret is None): raise AnsibleError('You must specify either a password or both token_id and token_secret.') + if self.get_option('qemu_extended_statuses') and not self.get_option('want_facts'): + raise AnsibleError('You must set want_facts to True if you want to use qemu_extended_statuses.') + self.cache_key = self.get_cache_key(path) self.use_cache = cache and self.get_option('cache') self.host_filters = self.get_option('filters') diff --git a/tests/unit/plugins/inventory/test_proxmox.py b/tests/unit/plugins/inventory/test_proxmox.py index ae29ab0a350..91411d1d55a 100644 --- a/tests/unit/plugins/inventory/test_proxmox.py +++ b/tests/unit/plugins/inventory/test_proxmox.py @@ -541,17 +541,11 @@ def get_vm_status(properties, node, vmtype, vmid, name): return True -def get_option(option): - if option == 'group_prefix': - return 'proxmox_' - if option == 'facts_prefix': - return 'proxmox_' - elif option == 'want_facts': - return True - elif option == 'want_proxmox_nodes_ansible_host': - return True - else: - return False +def get_option(opts): + def fn(option): + default = opts.get('default', False) + return opts.get(option, default) + return fn def test_populate(inventory, mocker): @@ -563,12 +557,20 @@ def test_populate(inventory, mocker): inventory.facts_prefix = 'proxmox_' inventory.strict = False + opts = { + 'group_prefix': 'proxmox_', + 'facts_prefix': 'proxmox_', + 'want_facts': True, + 'want_proxmox_nodes_ansible_host': True, + 'qemu_extended_statuses': True + } + # bypass authentication and API fetch calls inventory._get_auth = mocker.MagicMock(side_effect=get_auth) inventory._get_json = mocker.MagicMock(side_effect=get_json) inventory._get_vm_status = mocker.MagicMock(side_effect=get_vm_status) inventory._get_vm_snapshots = mocker.MagicMock(side_effect=get_vm_snapshots) - inventory.get_option = mocker.MagicMock(side_effect=get_option) + inventory.get_option = mocker.MagicMock(side_effect=get_option(opts)) inventory._can_add_host = mocker.MagicMock(return_value=True) inventory._populate() @@ -610,3 +612,38 @@ def test_populate(inventory, mocker): # check that offline node is in inventory assert inventory.inventory.get_host('testnode2') + + # make sure that ['prelaunch', 'paused'] are in the group list + for group in ['paused', 'prelaunch']: + assert ('%sall_%s' % (inventory.group_prefix, group)) in inventory.inventory.groups + + +def test_populate_missing_qemu_extended_groups(inventory, mocker): + # module settings + inventory.proxmox_user = 'root@pam' + inventory.proxmox_password = 'password' + inventory.proxmox_url = 'https://localhost:8006' + inventory.group_prefix = 'proxmox_' + inventory.facts_prefix = 'proxmox_' + inventory.strict = False + + opts = { + 'group_prefix': 'proxmox_', + 'facts_prefix': 'proxmox_', + 'want_facts': True, + 'want_proxmox_nodes_ansible_host': True, + 'qemu_extended_statuses': False + } + + # bypass authentication and API fetch calls + inventory._get_auth = mocker.MagicMock(side_effect=get_auth) + inventory._get_json = mocker.MagicMock(side_effect=get_json) + inventory._get_vm_status = mocker.MagicMock(side_effect=get_vm_status) + inventory._get_vm_snapshots = mocker.MagicMock(side_effect=get_vm_snapshots) + inventory.get_option = mocker.MagicMock(side_effect=get_option(opts)) + inventory._can_add_host = mocker.MagicMock(return_value=True) + inventory._populate() + + # make sure that ['prelaunch', 'paused'] are not in the group list + for group in ['paused', 'prelaunch']: + assert ('%sall_%s' % (inventory.group_prefix, group)) not in inventory.inventory.groups