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.