summaryrefslogtreecommitdiff
path: root/lib/ansible/executor/play_iterator.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ansible/executor/play_iterator.py')
-rw-r--r--lib/ansible/executor/play_iterator.py125
1 files changed, 95 insertions, 30 deletions
diff --git a/lib/ansible/executor/play_iterator.py b/lib/ansible/executor/play_iterator.py
index fd59478ead..09caeec2d9 100644
--- a/lib/ansible/executor/play_iterator.py
+++ b/lib/ansible/executor/play_iterator.py
@@ -49,6 +49,7 @@ class HostState:
self.cur_rescue_task = 0
self.cur_always_task = 0
self.cur_role = None
+ self.cur_dep_chain = None
self.run_state = PlayIterator.ITERATING_SETUP
self.fail_state = PlayIterator.FAILED_NONE
self.pending_setup = False
@@ -57,14 +58,32 @@ class HostState:
self.always_child_state = None
def __repr__(self):
- return "HOST STATE: block=%d, task=%d, rescue=%d, always=%d, role=%s, run_state=%d, fail_state=%d, pending_setup=%s, tasks child state? %s, rescue child state? %s, always child state? %s" % (
+ def _run_state_to_string(n):
+ states = ["ITERATING_SETUP", "ITERATING_TASKS", "ITERATING_RESCUE", "ITERATING_ALWAYS", "ITERATING_COMPLETE"]
+ try:
+ return states[n]
+ except IndexError:
+ return "UNKNOWN STATE"
+
+ def _failed_state_to_string(n):
+ states = {1:"FAILED_SETUP", 2:"FAILED_TASKS", 4:"FAILED_RESCUE", 8:"FAILED_ALWAYS"}
+ if n == 0:
+ return "FAILED_NONE"
+ else:
+ ret = []
+ for i in (1, 2, 4, 8):
+ if n & i:
+ ret.append(states[i])
+ return "|".join(ret)
+
+ return "HOST STATE: block=%d, task=%d, rescue=%d, always=%d, role=%s, run_state=%s, fail_state=%s, pending_setup=%s, tasks child state? %s, rescue child state? %s, always child state? %s" % (
self.cur_block,
self.cur_regular_task,
self.cur_rescue_task,
self.cur_always_task,
self.cur_role,
- self.run_state,
- self.fail_state,
+ _run_state_to_string(self.run_state),
+ _failed_state_to_string(self.fail_state),
self.pending_setup,
self.tasks_child_state,
self.rescue_child_state,
@@ -84,6 +103,8 @@ class HostState:
new_state.run_state = self.run_state
new_state.fail_state = self.fail_state
new_state.pending_setup = self.pending_setup
+ if self.cur_dep_chain is not None:
+ new_state.cur_dep_chain = self.cur_dep_chain[:]
if self.tasks_child_state is not None:
new_state.tasks_child_state = self.tasks_child_state.copy()
if self.rescue_child_state is not None:
@@ -119,30 +140,35 @@ class PlayIterator:
self._blocks.append(new_block)
self._host_states = {}
+ start_at_matched = False
for host in inventory.get_hosts(self._play.hosts):
- self._host_states[host.name] = HostState(blocks=self._blocks)
- # if the host's name is in the variable manager's fact cache, then set
- # its _gathered_facts flag to true for smart gathering tests later
- if host.name in variable_manager._fact_cache:
- host._gathered_facts = True
- # if we're looking to start at a specific task, iterate through
- # the tasks for this host until we find the specified task
- if play_context.start_at_task is not None and not start_at_done:
- while True:
- (s, task) = self.get_next_task_for_host(host, peek=True)
- if s.run_state == self.ITERATING_COMPLETE:
- break
- if task.name == play_context.start_at_task or fnmatch.fnmatch(task.name, play_context.start_at_task) or \
- task.get_name() == play_context.start_at_task or fnmatch.fnmatch(task.get_name(), play_context.start_at_task):
- # we have our match, so clear the start_at_task field on the
- # play context to flag that we've started at a task (and future
- # plays won't try to advance)
- play_context.start_at_task = None
- break
- else:
- self.get_next_task_for_host(host)
- # finally, reset the host's state to ITERATING_SETUP
- self._host_states[host.name].run_state = self.ITERATING_SETUP
+ self._host_states[host.name] = HostState(blocks=self._blocks)
+ # if the host's name is in the variable manager's fact cache, then set
+ # its _gathered_facts flag to true for smart gathering tests later
+ if host.name in variable_manager._fact_cache:
+ host._gathered_facts = True
+ # if we're looking to start at a specific task, iterate through
+ # the tasks for this host until we find the specified task
+ if play_context.start_at_task is not None and not start_at_done:
+ while True:
+ (s, task) = self.get_next_task_for_host(host, peek=True)
+ if s.run_state == self.ITERATING_COMPLETE:
+ break
+ if task.name == play_context.start_at_task or fnmatch.fnmatch(task.name, play_context.start_at_task) or \
+ task.get_name() == play_context.start_at_task or fnmatch.fnmatch(task.get_name(), play_context.start_at_task):
+ start_at_matched = True
+ break
+ else:
+ self.get_next_task_for_host(host)
+
+ # finally, reset the host's state to ITERATING_SETUP
+ self._host_states[host.name].run_state = self.ITERATING_SETUP
+
+ if start_at_matched:
+ # we have our match, so clear the start_at_task field on the
+ # play context to flag that we've started at a task (and future
+ # plays won't try to advance)
+ play_context.start_at_task = None
# Extend the play handlers list to include the handlers defined in roles
self._play.handlers.extend(play.compile_roles_handlers())
@@ -189,13 +215,21 @@ class PlayIterator:
s.pending_setup = False
if not task:
+ old_s = s
(s, task) = self._get_next_task_from_state(s, peek=peek)
+ def _roles_are_different(ra, rb):
+ if ra != rb:
+ return True
+ else:
+ return old_s.cur_dep_chain != task._block._dep_chain
+
if task and task._role:
# if we had a current role, mark that role as completed
- if s.cur_role and task._role != s.cur_role and host.name in s.cur_role._had_task_run and not peek:
+ 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 peek:
s.cur_role._completed[host.name] = True
s.cur_role = task._role
+ s.cur_dep_chain = task._block._dep_chain
if not peek:
self._host_states[host.name] = s
@@ -324,13 +358,21 @@ class PlayIterator:
state.tasks_child_state = self._set_failed_state(state.tasks_child_state)
else:
state.fail_state |= self.FAILED_TASKS
- state.run_state = self.ITERATING_RESCUE
+ if state._blocks[state.cur_block].rescue:
+ state.run_state = self.ITERATING_RESCUE
+ elif state._blocks[state.cur_block].always:
+ state.run_state = self.ITERATING_ALWAYS
+ else:
+ state.run_state = self.ITERATING_COMPLETE
elif state.run_state == self.ITERATING_RESCUE:
if state.rescue_child_state is not None:
state.rescue_child_state = self._set_failed_state(state.rescue_child_state)
else:
state.fail_state |= self.FAILED_RESCUE
- state.run_state = self.ITERATING_ALWAYS
+ if state._blocks[state.cur_block].always:
+ state.run_state = self.ITERATING_ALWAYS
+ else:
+ state.run_state = self.ITERATING_COMPLETE
elif state.run_state == self.ITERATING_ALWAYS:
if state.always_child_state is not None:
state.always_child_state = self._set_failed_state(state.always_child_state)
@@ -347,6 +389,28 @@ class PlayIterator:
def get_failed_hosts(self):
return dict((host, True) for (host, state) in iteritems(self._host_states) if state.run_state == self.ITERATING_COMPLETE and state.fail_state != self.FAILED_NONE)
+ def _check_failed_state(self, state):
+ if state is None:
+ return False
+ elif state.run_state == self.ITERATING_TASKS and self._check_failed_state(state.tasks_child_state):
+ return True
+ elif state.run_state == self.ITERATING_RESCUE and self._check_failed_state(state.rescue_child_state):
+ return True
+ elif state.run_state == self.ITERATING_ALWAYS and self._check_failed_state(state.always_child_state):
+ return True
+ elif state.run_state == self.ITERATING_COMPLETE and state.fail_state != self.FAILED_NONE:
+ if state.run_state == self.ITERATING_RESCUE and state.fail_state&self.FAILED_RESCUE == 0:
+ return False
+ elif state.run_state == self.ITERATING_ALWAYS and state.fail_state&self.FAILED_ALWAYS == 0:
+ return False
+ else:
+ return True
+ return False
+
+ def is_failed(self, host):
+ s = self.get_host_state(host)
+ return self._check_failed_state(s)
+
def get_original_task(self, host, task):
'''
Finds the task in the task list which matches the UUID of the given task.
@@ -396,7 +460,8 @@ class PlayIterator:
return None
def _insert_tasks_into_state(self, state, task_list):
- if state.fail_state != self.FAILED_NONE:
+ # if we've failed at all, or if the task list is empty, just return the current state
+ if state.fail_state != self.FAILED_NONE and state.run_state not in (self.ITERATING_RESCUE, self.ITERATING_ALWAYS) or not task_list:
return state
if state.run_state == self.ITERATING_TASKS: