2014-11-14 22:14:08 +00:00
# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# Make coding more python3-ish
from __future__ import ( absolute_import , division , print_function )
__metaclass__ = type
2016-08-26 16:48:27 +00:00
from ansible . compat . six import iteritems
2015-09-03 06:23:27 +00:00
2015-01-15 07:13:45 +00:00
from ansible . errors import AnsibleError
2015-02-26 15:51:12 +00:00
from ansible . executor . play_iterator import PlayIterator
2015-03-25 18:51:40 +00:00
from ansible . playbook . block import Block
2015-05-29 04:58:38 +00:00
from ansible . playbook . included_file import IncludedFile
2015-02-26 15:51:12 +00:00
from ansible . playbook . task import Task
2015-03-02 18:38:45 +00:00
from ansible . plugins import action_loader
2015-09-15 15:57:54 +00:00
from ansible . plugins . strategy import StrategyBase
2015-06-18 18:27:20 +00:00
from ansible . template import Templar
2016-03-16 21:56:47 +00:00
from ansible . utils . unicode import to_unicode
2014-11-14 22:14:08 +00:00
2015-08-25 21:51:51 +00:00
try :
from __main__ import display
except ImportError :
from ansible . utils . display import Display
display = Display ( )
2014-11-14 22:14:08 +00:00
class StrategyModule ( StrategyBase ) :
2015-02-26 15:51:12 +00:00
def _get_next_task_lockstep ( self , hosts , iterator ) :
'''
Returns a list of ( host , task ) tuples , where the task may
be a noop task to keep the iterator in lock step across
all hosts .
'''
noop_task = Task ( )
noop_task . action = ' meta '
noop_task . args [ ' _raw_params ' ] = ' noop '
noop_task . set_loader ( iterator . _play . _loader )
host_tasks = { }
2015-08-25 21:51:51 +00:00
display . debug ( " building list of next tasks for hosts " )
2015-02-26 15:51:12 +00:00
for host in hosts :
2016-02-03 23:42:27 +00:00
host_tasks [ host . name ] = iterator . get_next_task_for_host ( host , peek = True )
2015-08-25 21:51:51 +00:00
display . debug ( " done building task lists " )
2015-02-26 15:51:12 +00:00
num_setups = 0
num_tasks = 0
num_rescue = 0
num_always = 0
2015-08-25 21:51:51 +00:00
display . debug ( " counting tasks in each state of execution " )
2016-01-05 19:19:47 +00:00
host_tasks_to_run = [ ( host , state_task )
for host , state_task in iteritems ( host_tasks )
if state_task and state_task [ 1 ] ]
if host_tasks_to_run :
lowest_cur_block = min (
( s . cur_block for h , ( s , t ) in host_tasks_to_run
if s . run_state != PlayIterator . ITERATING_COMPLETE ) )
else :
# empty host_tasks_to_run will just run till the end of the function
# without ever touching lowest_cur_block
lowest_cur_block = None
for ( k , v ) in host_tasks_to_run :
2015-02-26 15:51:12 +00:00
( s , t ) = v
2015-08-14 22:07:14 +00:00
2016-01-05 19:19:47 +00:00
if s . cur_block > lowest_cur_block :
# Not the current block, ignore it
continue
2015-02-26 15:51:12 +00:00
if s . run_state == PlayIterator . ITERATING_SETUP :
num_setups + = 1
elif s . run_state == PlayIterator . ITERATING_TASKS :
num_tasks + = 1
elif s . run_state == PlayIterator . ITERATING_RESCUE :
num_rescue + = 1
elif s . run_state == PlayIterator . ITERATING_ALWAYS :
num_always + = 1
2016-04-25 15:05:33 +00:00
display . debug ( " done counting tasks in each state of execution: \n \t num_setups: %s \n \t num_tasks: %s \n \t num_rescue: %s \n \t num_always: %s " % ( num_setups , num_tasks , num_rescue , num_always ) )
2015-02-26 15:51:12 +00:00
def _advance_selected_hosts ( hosts , cur_block , cur_state ) :
'''
This helper returns the task for all hosts in the requested
state , otherwise they get a noop dummy task . This also advances
the state of the host , since the given states are determined
while using peek = True .
'''
# we return the values in the order they were originally
# specified in the given hosts array
rvals = [ ]
2015-08-25 21:51:51 +00:00
display . debug ( " starting to advance hosts " )
2015-02-26 15:51:12 +00:00
for host in hosts :
2016-01-02 05:31:09 +00:00
host_state_task = host_tasks . get ( host . name )
2015-07-22 19:55:11 +00:00
if host_state_task is None :
continue
( s , t ) = host_state_task
2015-08-17 21:58:35 +00:00
if t is None :
continue
2015-02-26 15:51:12 +00:00
if s . run_state == cur_state and s . cur_block == cur_block :
new_t = iterator . get_next_task_for_host ( host )
rvals . append ( ( host , t ) )
else :
rvals . append ( ( host , noop_task ) )
2015-08-25 21:51:51 +00:00
display . debug ( " done advancing hosts to next task " )
2015-02-26 15:51:12 +00:00
return rvals
# if any hosts are in ITERATING_SETUP, return the setup task
# while all other hosts get a noop
if num_setups :
2015-08-25 21:51:51 +00:00
display . debug ( " advancing hosts in ITERATING_SETUP " )
2015-02-26 15:51:12 +00:00
return _advance_selected_hosts ( hosts , lowest_cur_block , PlayIterator . ITERATING_SETUP )
# if any hosts are in ITERATING_TASKS, return the next normal
# task for these hosts, while all other hosts get a noop
if num_tasks :
2015-08-25 21:51:51 +00:00
display . debug ( " advancing hosts in ITERATING_TASKS " )
2015-02-26 15:51:12 +00:00
return _advance_selected_hosts ( hosts , lowest_cur_block , PlayIterator . ITERATING_TASKS )
# if any hosts are in ITERATING_RESCUE, return the next rescue
# task for these hosts, while all other hosts get a noop
if num_rescue :
2015-08-25 21:51:51 +00:00
display . debug ( " advancing hosts in ITERATING_RESCUE " )
2015-02-26 15:51:12 +00:00
return _advance_selected_hosts ( hosts , lowest_cur_block , PlayIterator . ITERATING_RESCUE )
# if any hosts are in ITERATING_ALWAYS, return the next always
# task for these hosts, while all other hosts get a noop
if num_always :
2015-08-25 21:51:51 +00:00
display . debug ( " advancing hosts in ITERATING_ALWAYS " )
2015-02-26 15:51:12 +00:00
return _advance_selected_hosts ( hosts , lowest_cur_block , PlayIterator . ITERATING_ALWAYS )
# at this point, everything must be ITERATING_COMPLETE, so we
# return None for all hosts in the list
2015-08-25 21:51:51 +00:00
display . debug ( " all hosts are done, so returning None ' s for all hosts " )
2015-02-26 15:51:12 +00:00
return [ ( host , None ) for host in hosts ]
2015-07-21 16:12:22 +00:00
def run ( self , iterator , play_context ) :
2014-11-14 22:14:08 +00:00
'''
The linear strategy is simple - get the next task and queue
it for all hosts , then wait for the queue to drain before
moving on to the next task
'''
# iteratate over each task, while there is one left to run
2016-08-05 18:44:57 +00:00
result = self . _tqm . RUN_OK
2014-11-14 22:14:08 +00:00
work_to_do = True
2015-01-12 22:04:56 +00:00
while work_to_do and not self . _tqm . _terminated :
2014-11-14 22:14:08 +00:00
try :
2015-11-11 18:19:58 +00:00
display . debug ( " getting the remaining hosts for this loop " )
2016-05-17 00:29:42 +00:00
hosts_left = [ host for host in self . _inventory . get_hosts ( iterator . _play . hosts ) if host . name not in self . _tqm . _unreachable_hosts ]
2015-11-11 18:19:58 +00:00
display . debug ( " done getting the remaining hosts for this loop " )
2014-11-14 22:14:08 +00:00
# queue up this task for each host in the inventory
callback_sent = False
work_to_do = False
2015-01-12 22:04:56 +00:00
2015-03-03 20:59:23 +00:00
host_results = [ ]
2015-02-26 15:51:12 +00:00
host_tasks = self . _get_next_task_lockstep ( hosts_left , iterator )
2015-07-07 18:19:49 +00:00
2015-07-24 00:47:24 +00:00
# skip control
skip_rest = False
choose_step = True
2016-01-21 21:41:05 +00:00
# flag set if task is set to any_errors_fatal
any_errors_fatal = False
2015-12-10 23:03:25 +00:00
results = [ ]
2015-02-26 15:51:12 +00:00
for ( host , task ) in host_tasks :
2014-11-14 22:14:08 +00:00
if not task :
continue
2015-11-05 21:21:34 +00:00
if self . _tqm . _terminated :
break
2015-03-02 18:38:45 +00:00
run_once = False
2014-11-14 22:14:08 +00:00
work_to_do = True
2015-02-26 15:51:12 +00:00
2015-03-02 18:38:45 +00:00
# test to see if the task across all hosts points to an action plugin which
# sets BYPASS_HOST_LOOP to true, or if it has run_once enabled. If so, we
# will only send this task to the first host in the list.
2015-03-03 20:59:23 +00:00
try :
action = action_loader . get ( task . action , class_only = True )
except KeyError :
# we don't care here, because the action may simply not have a
# corresponding action plugin
2016-02-05 16:19:50 +00:00
action = None
2015-03-02 18:38:45 +00:00
2015-02-26 15:51:12 +00:00
# check to see if this task should be skipped, due to it being a member of a
# role which has already run (and whether that role allows duplicate execution)
2015-08-11 20:34:58 +00:00
if task . _role and task . _role . has_run ( host ) :
2015-02-26 15:51:12 +00:00
# If there is no metadata, the default behavior is to not allow duplicates,
# if there is metadata, check to see if the allow_duplicates flag was set to true
if task . _role . _metadata is None or task . _role . _metadata and not task . _role . _metadata . allow_duplicates :
2015-11-11 18:19:58 +00:00
display . debug ( " ' %s ' skipped because role has already run " % task )
2015-02-26 15:51:12 +00:00
continue
2015-01-15 07:13:45 +00:00
if task . action == ' meta ' :
2015-07-18 19:24:44 +00:00
self . _execute_meta ( task , play_context , iterator )
2015-01-15 07:13:45 +00:00
else :
2015-07-24 00:47:24 +00:00
# handle step if needed, skip meta actions as they are used internally
if self . _step and choose_step :
if self . _take_step ( task ) :
choose_step = False
else :
skip_rest = True
2015-08-05 15:52:52 +00:00
break
2015-07-24 00:47:24 +00:00
2015-11-11 18:19:58 +00:00
display . debug ( " getting variables " )
2015-06-22 04:53:34 +00:00
task_vars = self . _variable_manager . get_vars ( loader = self . _loader , play = iterator . _play , host = host , task = task )
2015-11-04 16:26:06 +00:00
self . add_tqm_variables ( task_vars , play = iterator . _play )
2015-06-22 04:53:34 +00:00
templar = Templar ( loader = self . _loader , variables = task_vars )
2015-11-11 18:19:58 +00:00
display . debug ( " done getting variables " )
2015-06-22 04:53:34 +00:00
2016-02-05 16:19:50 +00:00
run_once = templar . template ( task . run_once ) or action and getattr ( action , ' BYPASS_HOST_LOOP ' , False )
2016-02-05 15:58:24 +00:00
2016-05-01 16:38:55 +00:00
if ( task . any_errors_fatal or run_once ) and not task . ignore_errors :
2016-02-05 15:58:24 +00:00
any_errors_fatal = True
2016-01-21 01:53:31 +00:00
2015-01-15 07:13:45 +00:00
if not callback_sent :
2015-08-25 21:51:51 +00:00
display . debug ( " sending task start callback, copying the task so we can template it temporarily " )
saved_name = task . name
display . debug ( " done copying, going to template now " )
2015-07-23 19:45:36 +00:00
try :
2016-08-26 16:48:27 +00:00
task . name = to_unicode ( templar . template ( task . name , fail_on_undefined = False ) , nonstring = ' empty ' )
2015-08-25 21:51:51 +00:00
display . debug ( " done templating " )
2015-07-23 19:45:36 +00:00
except :
# just ignore any errors during task name templating,
# we don't care if it just shows the raw name
2015-08-25 21:51:51 +00:00
display . debug ( " templating failed for some reason " )
2015-07-23 19:45:36 +00:00
pass
2015-08-25 21:51:51 +00:00
display . debug ( " here goes the callback... " )
self . _tqm . send_callback ( ' v2_playbook_on_task_start ' , task , is_conditional = False )
task . name = saved_name
2015-01-15 07:13:45 +00:00
callback_sent = True
2015-08-25 21:51:51 +00:00
display . debug ( " sending task start callback " )
2015-01-15 07:13:45 +00:00
self . _blocked_hosts [ host . get_name ( ) ] = True
2015-07-21 16:12:22 +00:00
self . _queue_task ( host , task , task_vars , play_context )
2016-07-31 08:23:28 +00:00
del task_vars
2014-11-14 22:14:08 +00:00
2015-03-02 18:38:45 +00:00
# if we're bypassing the host loop, break out now
2016-02-05 15:58:24 +00:00
if run_once :
2015-03-02 18:38:45 +00:00
break
2015-12-10 23:03:25 +00:00
results + = self . _process_pending_results ( iterator , one_pass = True )
2015-07-24 00:47:24 +00:00
# go to next host/task group
if skip_rest :
continue
2015-11-11 18:19:58 +00:00
display . debug ( " done queuing things up, now waiting for results queue to drain " )
2015-12-10 23:03:25 +00:00
results + = self . _wait_on_pending_results ( iterator )
2015-07-07 20:09:11 +00:00
host_results . extend ( results )
2015-05-29 04:58:38 +00:00
try :
2015-12-17 00:12:05 +00:00
included_files = IncludedFile . process_include_results (
host_results ,
self . _tqm ,
iterator = iterator ,
inventory = self . _inventory ,
loader = self . _loader ,
variable_manager = self . _variable_manager
)
2015-08-27 06:16:11 +00:00
except AnsibleError as e :
2016-08-05 18:44:57 +00:00
# this is a fatal error, so we abort here regardless of block state
2016-06-08 15:11:34 +00:00
return self . _tqm . RUN_ERROR
2015-03-03 20:59:23 +00:00
2016-01-15 18:14:27 +00:00
include_failure = False
2015-03-03 20:59:23 +00:00
if len ( included_files ) > 0 :
2015-11-16 22:13:55 +00:00
display . debug ( " we have included files to process " )
2015-03-03 20:59:23 +00:00
noop_task = Task ( )
noop_task . action = ' meta '
noop_task . args [ ' _raw_params ' ] = ' noop '
noop_task . set_loader ( iterator . _play . _loader )
2015-11-16 22:13:55 +00:00
display . debug ( " generating all_blocks data " )
2015-03-25 18:51:40 +00:00
all_blocks = dict ( ( host , [ ] ) for host in hosts_left )
2015-11-16 22:13:55 +00:00
display . debug ( " done generating all_blocks data " )
2015-03-03 20:59:23 +00:00
for included_file in included_files :
2015-11-16 22:13:55 +00:00
display . debug ( " processing included file: %s " % included_file . _filename )
2015-03-03 20:59:23 +00:00
# included hosts get the task list while those excluded get an equal-length
# list of noop tasks, to make sure that they continue running in lock-step
2015-03-12 17:14:57 +00:00
try :
2015-05-29 04:58:38 +00:00
new_blocks = self . _load_included_file ( included_file , iterator = iterator )
2015-10-28 18:00:03 +00:00
2015-11-16 22:13:55 +00:00
display . debug ( " iterating over new_blocks loaded from include file " )
2015-10-28 18:00:03 +00:00
for new_block in new_blocks :
2015-11-16 22:13:55 +00:00
task_vars = self . _variable_manager . get_vars (
loader = self . _loader ,
play = iterator . _play ,
task = included_file . _task ,
)
display . debug ( " filtering new block on tags " )
final_block = new_block . filter_tagged_tasks ( play_context , task_vars )
display . debug ( " done filtering new block on tags " )
2016-08-01 19:10:02 +00:00
noop_block = Block ( parent_block = task . _parent )
2015-10-28 18:00:03 +00:00
noop_block . block = [ noop_task for t in new_block . block ]
noop_block . always = [ noop_task for t in new_block . always ]
noop_block . rescue = [ noop_task for t in new_block . rescue ]
2015-11-16 22:13:55 +00:00
2015-10-28 18:00:03 +00:00
for host in hosts_left :
if host in included_file . _hosts :
all_blocks [ host ] . append ( final_block )
else :
all_blocks [ host ] . append ( noop_block )
2015-11-16 22:13:55 +00:00
display . debug ( " done iterating over new_blocks loaded from include file " )
2015-10-28 18:00:03 +00:00
2015-08-27 06:16:11 +00:00
except AnsibleError as e :
2015-03-12 17:14:57 +00:00
for host in included_file . _hosts :
2015-10-28 18:00:03 +00:00
self . _tqm . _failed_hosts [ host . name ] = True
2015-03-12 17:14:57 +00:00
iterator . mark_host_failed ( host )
2016-03-16 21:56:47 +00:00
display . error ( to_unicode ( e ) , wrap_text = False )
2016-01-15 18:14:27 +00:00
include_failure = True
2015-05-13 19:52:51 +00:00
continue
2015-03-12 17:14:57 +00:00
2015-10-28 18:00:03 +00:00
# finally go through all of the hosts and append the
# accumulated blocks to their list of tasks
2015-11-16 22:13:55 +00:00
display . debug ( " extending task lists for all hosts with included blocks " )
2015-03-03 20:59:23 +00:00
for host in hosts_left :
2015-03-25 18:51:40 +00:00
iterator . add_tasks ( host , all_blocks [ host ] )
2015-02-26 15:51:12 +00:00
2015-11-16 22:13:55 +00:00
display . debug ( " done extending task lists " )
display . debug ( " done processing included files " )
2015-11-11 18:19:58 +00:00
display . debug ( " results queue empty " )
2016-01-15 18:14:27 +00:00
display . debug ( " checking for any_errors_fatal " )
2016-01-20 17:16:27 +00:00
failed_hosts = [ ]
2016-07-12 08:01:47 +00:00
unreachable_hosts = [ ]
2016-01-15 18:14:27 +00:00
for res in results :
2016-04-12 22:04:44 +00:00
if res . is_failed ( ) :
2016-01-20 17:16:27 +00:00
failed_hosts . append ( res . _host . name )
2016-07-12 08:01:47 +00:00
elif res . is_unreachable ( ) :
unreachable_hosts . append ( res . _host . name )
2016-01-20 17:16:27 +00:00
# if any_errors_fatal and we had an error, mark all hosts as failed
2016-07-12 08:01:47 +00:00
if any_errors_fatal and ( len ( failed_hosts ) > 0 or len ( unreachable_hosts ) > 0 ) :
2016-01-20 17:16:27 +00:00
for host in hosts_left :
2016-08-05 18:44:57 +00:00
( s , _ ) = iterator . get_next_task_for_host ( host , peek = True )
if s . run_state != iterator . ITERATING_RESCUE :
2016-01-20 17:16:27 +00:00
self . _tqm . _failed_hosts [ host . name ] = True
2016-08-05 18:44:57 +00:00
result | = self . _tqm . RUN_FAILED_BREAK_PLAY
2016-01-20 17:16:27 +00:00
display . debug ( " done checking for any_errors_fatal " )
2016-01-15 18:14:27 +00:00
2016-04-13 18:12:54 +00:00
display . debug ( " checking for max_fail_percentage " )
if iterator . _play . max_fail_percentage is not None and len ( results ) > 0 :
percentage = iterator . _play . max_fail_percentage / 100.0
2016-05-18 18:06:13 +00:00
if ( len ( self . _tqm . _failed_hosts ) / len ( results ) ) > percentage :
2016-04-13 18:12:54 +00:00
for host in hosts_left :
# don't double-mark hosts, or the iterator will potentially
# fail them out of the rescue/always states
if host . name not in failed_hosts :
self . _tqm . _failed_hosts [ host . name ] = True
iterator . mark_host_failed ( host )
2016-06-08 15:11:34 +00:00
self . _tqm . send_callback ( ' v2_playbook_on_no_hosts_remaining ' )
2016-08-05 18:44:57 +00:00
result | = self . _tqm . RUN_FAILED_BREAK_PLAY
2016-04-13 18:12:54 +00:00
display . debug ( " done checking for max_fail_percentage " )
2015-08-27 06:16:11 +00:00
except ( IOError , EOFError ) as e :
2015-11-11 18:19:58 +00:00
display . debug ( " got IOError/EOFError in task loop: %s " % e )
2014-11-14 22:14:08 +00:00
# most likely an abort, return failed
2016-06-08 15:11:34 +00:00
return self . _tqm . RUN_UNKNOWN_ERROR
2014-11-14 22:14:08 +00:00
# run the base class run() method, which executes the cleanup function
# and runs any outstanding handlers which have been triggered
2015-07-21 16:12:22 +00:00
return super ( StrategyModule , self ) . run ( iterator , play_context , result )