diff --git a/plugins/module_utils/linode_event_poller.py b/plugins/module_utils/linode_event_poller.py index db7dd8bf..83121a78 100644 --- a/plugins/module_utils/linode_event_poller.py +++ b/plugins/module_utils/linode_event_poller.py @@ -130,3 +130,31 @@ def poll_func(): ) return event + + +def wait_for_resource_free(client: LinodeClient, entity_type: str, entity_id: int, timeout: int): + """ + Waits for all events relevant events to not be scheduled or in-progress. + """ + + timeout_ctx = TimeoutContext(timeout_seconds=timeout) + + api_filter = { + '+order': 'desc', + '+order_by': 'created', + 'entity.id': entity_id, + 'entity.type': entity_type, + } + + def poll_func(): + events = client.get('/account/events', filters=api_filter)['data'] + return all(event['status'] not in ('scheduled', 'started') for event in events) + + if poll_func(): + return + + polling.poll( + poll_func, + step=POLL_INTERVAL_SECONDS, + timeout=timeout_ctx.seconds_remaining, + ) diff --git a/plugins/modules/instance.py b/plugins/modules/instance.py index 6815e207..21aaaa52 100644 --- a/plugins/modules/instance.py +++ b/plugins/modules/instance.py @@ -6,10 +6,9 @@ from __future__ import absolute_import, division, print_function import copy -from typing import Optional, Any, cast, Set, List, Dict, Union +from typing import Optional, Any, cast, List, Dict, Union import linode_api4 -import polling from ansible_specdoc.objects import SpecField, FieldType, SpecDocMeta, SpecReturnValue import ansible_collections.linode.cloud.plugins.module_utils.doc_fragments.instance as docs @@ -17,7 +16,8 @@ LinodeModuleBase from ansible_collections.linode.cloud.plugins.module_utils.linode_docs import global_authors, \ global_requirements -from ansible_collections.linode.cloud.plugins.module_utils.linode_event_poller import EventPoller +from ansible_collections.linode.cloud.plugins.module_utils.linode_event_poller import EventPoller, \ + wait_for_resource_free from ansible_collections.linode.cloud.plugins.module_utils.linode_helper import \ filter_null_values, paginated_list_to_json, drop_empty_strings, mapping_to_dict, \ request_retry, filter_null_values_recursive @@ -557,25 +557,6 @@ def _delete_disk_register(self, disk: Disk) -> None: self.register_action('Deleted disk {0}'.format(disk.label)) disk.delete() - def _wait_for_instance_status(self, instance: Instance, status: Set[str], - not_status: bool = False) -> None: - def poll_func() -> bool: - instance._api_get() - return (instance.status in status) != not_status - - # Initial attempt - if poll_func(): - return - - try: - polling.poll( - poll_func, - step=3, - timeout=self._timeout_ctx.seconds_remaining, - ) - except polling.TimeoutException: - self.fail(msg='failed to wait for instance: timeout period expired') - def _update_interfaces(self) -> None: config = self._get_boot_config() param_interfaces: List[Any] = self.module.params.get('interfaces') @@ -766,10 +747,12 @@ def _handle_instance_boot(self) -> None: should_poll = self.module.params.get('wait') # Wait for instance to not be busy - self._wait_for_instance_status( - self._instance, - {'running', 'offline'}, - self._timeout_ctx.seconds_remaining) + wait_for_resource_free( + self.client, + 'linode', + self._instance.id, + self._timeout_ctx.seconds_remaining + ) self._instance._api_get() @@ -798,19 +781,20 @@ def _handle_instance_boot(self) -> None: self.register_action('Shutdown instance {0}'.format(self.module.params.get('label'))) if should_poll and event_poller is not None: + # Poll for the instance to be booted if necessary event_poller.wait_for_next_event_finished(self._timeout_ctx.seconds_remaining) def _handle_present(self) -> None: """Updates the instance defined in kwargs""" label = self.module.params.get('label') + should_wait = self.module.params.get('wait') self._instance = self._get_instance_by_label(label) - create_poller = EventPoller(self.client, 'linode', 'linode_create') - should_create = self._instance is None + if self._instance is None: + create_poller = EventPoller(self.client, 'linode', 'linode_create') - if should_create: result = self._create_instance() self._instance = cast(Instance, result.get('instance')) @@ -818,8 +802,10 @@ def _handle_present(self) -> None: self.register_action('Created instance {0}'.format(label)) - create_poller.set_entity_id(self._instance.id) + + if should_wait: + create_poller.wait_for_next_event_finished(self._timeout_ctx.seconds_remaining) else: self._update_instance() @@ -829,9 +815,12 @@ def _handle_present(self) -> None: configs = self.module.params.get('configs') or [] if len(configs) > 0 or len(disks) > 0: - # Let's wait for the instance to be created before continuing - if should_create: - create_poller.wait_for_next_event_finished(self._timeout_ctx.seconds_remaining) + wait_for_resource_free( + self.client, + 'linode', + self._instance.id, + self._timeout_ctx.seconds_remaining + ) self._update_disks() self._update_configs()