From 9f7b6fb3ff374357a43de5a1c1ea8d77d52b131c Mon Sep 17 00:00:00 2001 From: Mike Graves Date: Tue, 16 Feb 2021 12:19:22 -0500 Subject: [PATCH] Honor wait_timeout in k8s_info for missing resource (#360) * Honor wait in k8s_info for missing resource The wait logic in the k8s_info module immediately returns when no resources are found, regardless whether a wait_timeout has been specified. This expands the logic to wait when a name has been provided. The case this is specifically meant to address is when querying for a resource that is indirectly created by another resource, for example, a pod created by a deployment or an operator. The existing logic in every other case should remain as it was before. This means if a query might return more than one resource--all pods with some label, for example--the module will return immediately if no pods are found, even if a wait_timeout has been provided. * Add changelog fragment --- .../fragments/360-k8s_info-wait-timeout.yaml | 2 + molecule/default/tasks/info.yml | 18 +++++ plugins/module_utils/common.py | 69 +++++++++++++------ 3 files changed, 68 insertions(+), 21 deletions(-) create mode 100644 changelogs/fragments/360-k8s_info-wait-timeout.yaml diff --git a/changelogs/fragments/360-k8s_info-wait-timeout.yaml b/changelogs/fragments/360-k8s_info-wait-timeout.yaml new file mode 100644 index 00000000..a26a78ac --- /dev/null +++ b/changelogs/fragments/360-k8s_info-wait-timeout.yaml @@ -0,0 +1,2 @@ +bugfixes: + - respect the ``wait_timeout`` parameter in the ``k8s`` and ``k8s_info`` modules when a resource does not exist (https://github.com/ansible-collections/community.kubernetes/issues/344). diff --git a/molecule/default/tasks/info.yml b/molecule/default/tasks/info.yml index 891f3dcc..f1061a86 100644 --- a/molecule/default/tasks/info.yml +++ b/molecule/default/tasks/info.yml @@ -182,6 +182,24 @@ - not dne_api.resources - not dne_api.api_found + - name: Start timer + set_fact: + start: "{{ lookup('pipe', 'date +%s') }}" + + - name: Wait for non-existent pod to be created + k8s_info: + kind: Pod + name: does-not-exist + namespace: "{{ wait_namespace }}" + wait: yes + wait_timeout: 45 + register: result + + - name: Check that module waited + assert: + that: + - "{{ lookup('pipe', 'date +%s') }} - {{ start }} > 30" + always: - name: Remove namespace k8s: diff --git a/plugins/module_utils/common.py b/plugins/module_utils/common.py index 2e2bc6ed..3c44f5c9 100644 --- a/plugins/module_utils/common.py +++ b/plugins/module_utils/common.py @@ -319,31 +319,58 @@ class K8sAnsibleMixin(object): if not field_selectors: field_selectors = [] + result = None try: - result = resource.get(name=name, - namespace=namespace, + result = resource.get(name=name, namespace=namespace, label_selector=','.join(label_selectors), field_selector=','.join(field_selectors)) - if wait: - satisfied_by = [] - if isinstance(result, ResourceInstance): - # We have a list of ResourceInstance - resource_list = result.get('items', []) - if not resource_list: - resource_list = [result] - - for resource_instance in resource_list: - success, res, duration = self.wait(resource, resource_instance, - sleep=wait_sleep, timeout=wait_timeout, - state=state, condition=condition) - if not success: - self.fail(msg="Failed to gather information about %s(s) even" - " after waiting for %s seconds" % (res.get('kind'), duration)) - satisfied_by.append(res) - return dict(resources=satisfied_by, api_found=True) - result = result.to_dict() - except (openshift.dynamic.exceptions.BadRequestError, openshift.dynamic.exceptions.NotFoundError): + except openshift.dynamic.exceptions.BadRequestError: return dict(resources=[], api_found=True) + except openshift.dynamic.exceptions.NotFoundError: + if not wait or name is None: + return dict(resources=[], api_found=True) + + if not wait: + result = result.to_dict() + if 'items' in result: + return dict(resources=result['items'], api_found=True) + return dict(resources=[result], api_found=True) + + start = datetime.now() + + def _elapsed(): + return (datetime.now() - start).seconds + + if result is None: + while _elapsed() < wait_timeout: + try: + result = resource.get(name=name, namespace=namespace, + label_selector=','.join(label_selectors), + field_selector=','.join(field_selectors)) + break + except NotFoundError: + pass + time.sleep(wait_sleep) + if result is None: + return dict(resources=[], api_found=True) + + if isinstance(result, ResourceInstance): + satisfied_by = [] + # We have a list of ResourceInstance + resource_list = result.get('items', []) + if not resource_list: + resource_list = [result] + + for resource_instance in resource_list: + success, res, duration = self.wait(resource, resource_instance, + sleep=wait_sleep, timeout=wait_timeout, + state=state, condition=condition) + if not success: + self.fail(msg="Failed to gather information about %s(s) even" + " after waiting for %s seconds" % (res.get('kind'), duration)) + satisfied_by.append(res) + return dict(resources=satisfied_by, api_found=True) + result = result.to_dict() if 'items' in result: return dict(resources=result['items'], api_found=True)