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 fed079e4cb)
This commit is contained in:
James Cammarata 2016-12-28 08:16:02 -06:00
parent e05222a4cc
commit 47e16bef08
3 changed files with 52 additions and 3 deletions

View file

@ -43,6 +43,7 @@ if PY3:
class_types = type, class_types = type,
text_type = str text_type = str
binary_type = bytes binary_type = bytes
cmp = lambda a, b: (a > b) - (a < b)
MAXSIZE = sys.maxsize MAXSIZE = sys.maxsize
else: else:
@ -51,6 +52,7 @@ else:
class_types = (type, types.ClassType) class_types = (type, types.ClassType)
text_type = unicode text_type = unicode
binary_type = str binary_type = str
cmp = cmp
if sys.platform.startswith("java"): if sys.platform.startswith("java"):
# Jython always uses 32 bits. # Jython always uses 32 bits.

View file

@ -26,6 +26,7 @@ from ansible.compat.six import iteritems
from ansible import constants as C from ansible import constants as C
from ansible.errors import AnsibleError from ansible.errors import AnsibleError
from ansible.module_utils.six import cmp
from ansible.playbook.block import Block from ansible.playbook.block import Block
from ansible.playbook.task import Task from ansible.playbook.task import Task
from ansible.playbook.role_include import IncludeRole from ansible.playbook.role_include import IncludeRole
@ -50,6 +51,7 @@ class HostState:
self.cur_rescue_task = 0 self.cur_rescue_task = 0
self.cur_always_task = 0 self.cur_always_task = 0
self.cur_role = None self.cur_role = None
self.cur_role_task = None
self.cur_dep_chain = None self.cur_dep_chain = None
self.run_state = PlayIterator.ITERATING_SETUP self.run_state = PlayIterator.ITERATING_SETUP
self.fail_state = PlayIterator.FAILED_NONE self.fail_state = PlayIterator.FAILED_NONE
@ -122,6 +124,8 @@ class HostState:
new_state.cur_rescue_task = self.cur_rescue_task new_state.cur_rescue_task = self.cur_rescue_task
new_state.cur_always_task = self.cur_always_task new_state.cur_always_task = self.cur_always_task
new_state.cur_role = self.cur_role 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.run_state = self.run_state
new_state.fail_state = self.fail_state new_state.fail_state = self.fail_state
new_state.pending_setup = self.pending_setup new_state.pending_setup = self.pending_setup
@ -277,12 +281,53 @@ class PlayIterator:
parent = parent._parent parent = parent._parent
return False 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 task and task._role:
# if we had a current role, mark that role as completed # 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 \ if s.cur_role:
not _role_is_child(s.cur_role) and not peek: role_diff = _roles_are_different(task._role, s.cur_role)
s.cur_role._completed[host.name] = True 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._role
s.cur_role_task = _get_cur_task(s)
s.cur_dep_chain = task.get_dep_chain() s.cur_dep_chain = task.get_dep_chain()
if not peek: if not peek:

View file

@ -38,6 +38,7 @@ if PY3:
class_types = type, class_types = type,
text_type = str text_type = str
binary_type = bytes binary_type = bytes
cmp = lambda a, b: (a > b) - (a < b)
MAXSIZE = sys.maxsize MAXSIZE = sys.maxsize
else: else:
@ -46,6 +47,7 @@ else:
class_types = (type, types.ClassType) class_types = (type, types.ClassType)
text_type = unicode text_type = unicode
binary_type = str binary_type = str
cmp = cmp
if sys.platform.startswith("java"): if sys.platform.startswith("java"):
# Jython always uses 32 bits. # Jython always uses 32 bits.