From 47e16bef08ae6ff40d3400540bddec508dab9684 Mon Sep 17 00:00:00 2001 From: James Cammarata Date: Wed, 28 Dec 2016 08:16:02 -0600 Subject: [PATCH] Fix role completion detection problem When the same role is listed consecutively in a play, the previous role completion detection failed to mark it as complete as it only checked to see if the role changed. This patch addresses that by also keeping track of which task in the role we are on, so that even if the same role is encountered during later passes the task number will be less than or equal to the last noted task position. Related to #15409 (cherry picked from commit fed079e4cb45af397c8f0b244490b2af0e0d0f38) --- lib/ansible/compat/six/_six.py | 2 ++ lib/ansible/executor/play_iterator.py | 51 +++++++++++++++++++++++++-- lib/ansible/module_utils/six.py | 2 ++ 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/lib/ansible/compat/six/_six.py b/lib/ansible/compat/six/_six.py index 190c0239cd7..8caf2ad85cc 100644 --- a/lib/ansible/compat/six/_six.py +++ b/lib/ansible/compat/six/_six.py @@ -43,6 +43,7 @@ if PY3: class_types = type, text_type = str binary_type = bytes + cmp = lambda a, b: (a > b) - (a < b) MAXSIZE = sys.maxsize else: @@ -51,6 +52,7 @@ else: class_types = (type, types.ClassType) text_type = unicode binary_type = str + cmp = cmp if sys.platform.startswith("java"): # Jython always uses 32 bits. diff --git a/lib/ansible/executor/play_iterator.py b/lib/ansible/executor/play_iterator.py index a20351b16f7..5903e6407b0 100644 --- a/lib/ansible/executor/play_iterator.py +++ b/lib/ansible/executor/play_iterator.py @@ -26,6 +26,7 @@ from ansible.compat.six import iteritems from ansible import constants as C from ansible.errors import AnsibleError +from ansible.module_utils.six import cmp from ansible.playbook.block import Block from ansible.playbook.task import Task from ansible.playbook.role_include import IncludeRole @@ -50,6 +51,7 @@ class HostState: self.cur_rescue_task = 0 self.cur_always_task = 0 self.cur_role = None + self.cur_role_task = None self.cur_dep_chain = None self.run_state = PlayIterator.ITERATING_SETUP self.fail_state = PlayIterator.FAILED_NONE @@ -122,6 +124,8 @@ class HostState: new_state.cur_rescue_task = self.cur_rescue_task new_state.cur_always_task = self.cur_always_task new_state.cur_role = self.cur_role + if self.cur_role_task: + new_state.cur_role_task = self.cur_role_task[:] new_state.run_state = self.run_state new_state.fail_state = self.fail_state new_state.pending_setup = self.pending_setup @@ -277,12 +281,53 @@ class PlayIterator: parent = parent._parent return False + def _get_cur_task(s, depth=0): + res = [s.run_state, depth, s.cur_block, -1] + if s.run_state == self.ITERATING_TASKS: + if s.tasks_child_state: + res = _get_cur_task(s.tasks_child_state, depth=depth+1) + else: + res[-1] = s.cur_regular_task + elif s.run_state == self.ITERATING_RESCUE: + if s.rescue_child_state: + res = _get_cur_task(s.rescue_child_state, depth=depth+1) + else: + res[-1] = s.cur_rescue_task + elif s.run_state == self.ITERATING_ALWAYS: + if s.always_child_state: + res = _get_cur_task(s.always_child_state, depth=depth+1) + else: + res[-1] = s.cur_always_task + return res + + def _role_task_cmp(s): + ''' + tt is a tuple made of the regular/rescue/always task number + from the current state of the host. + ''' + if not s.cur_role_task: + return 1 + cur_task = _get_cur_task(s) + res = cmp(cur_task[0], s.cur_role_task[0]) + if res == 0: + res = cmp(cur_task[1], s.cur_role_task[1]) + if res == 0: + res = cmp(cur_task[2], s.cur_role_task[2]) + if res == 0: + res = cmp(cur_task[3], s.cur_role_task[3]) + return res + if task and task._role: # if we had a current role, mark that role as completed - if s.cur_role and _roles_are_different(task._role, s.cur_role) and host.name in s.cur_role._had_task_run and \ - not _role_is_child(s.cur_role) and not peek: - s.cur_role._completed[host.name] = True + if s.cur_role: + role_diff = _roles_are_different(task._role, s.cur_role) + role_child = _role_is_child(s.cur_role) + tasks_cmp = _role_task_cmp(s) + host_done = host.name in s.cur_role._had_task_run + if (role_diff or (not role_diff and tasks_cmp <= 0)) and host_done and not role_child and not peek: + s.cur_role._completed[host.name] = True s.cur_role = task._role + s.cur_role_task = _get_cur_task(s) s.cur_dep_chain = task.get_dep_chain() if not peek: diff --git a/lib/ansible/module_utils/six.py b/lib/ansible/module_utils/six.py index 85898ec7127..b93d4bf8bb2 100644 --- a/lib/ansible/module_utils/six.py +++ b/lib/ansible/module_utils/six.py @@ -38,6 +38,7 @@ if PY3: class_types = type, text_type = str binary_type = bytes + cmp = lambda a, b: (a > b) - (a < b) MAXSIZE = sys.maxsize else: @@ -46,6 +47,7 @@ else: class_types = (type, types.ClassType) text_type = unicode binary_type = str + cmp = cmp if sys.platform.startswith("java"): # Jython always uses 32 bits.