Starting work on getting integration tests working on v2

This is incomplete work, and requires some minor tweeks to the integration
tests which are not included in this commit.
This commit is contained in:
James Cammarata 2015-01-12 16:04:56 -06:00
parent d7f67ea62b
commit 2aeb79f45f
24 changed files with 189 additions and 155 deletions

View file

@ -51,7 +51,7 @@ class ConnectionInformation:
self.sudo_user = '' self.sudo_user = ''
self.sudo_pass = '' self.sudo_pass = ''
self.verbosity = 0 self.verbosity = 0
self.only_tags = set(['all']) self.only_tags = set()
self.skip_tags = set() self.skip_tags = set()
if play: if play:
@ -101,6 +101,9 @@ class ConnectionInformation:
elif isinstance(options.tags, basestring): elif isinstance(options.tags, basestring):
self.only_tags.update(options.tags.split(',')) self.only_tags.update(options.tags.split(','))
if len(self.only_tags) == 0:
self.only_tags = set(['all'])
if hasattr(options, 'skip_tags'): if hasattr(options, 'skip_tags'):
if isinstance(options.skip_tags, list): if isinstance(options.skip_tags, list):
self.skip_tags.update(options.skip_tags) self.skip_tags.update(options.skip_tags)

View file

@ -53,6 +53,7 @@ class PlaybookExecutor:
signal.signal(signal.SIGINT, self._cleanup) signal.signal(signal.SIGINT, self._cleanup)
result = 0
try: try:
for playbook_path in self._playbooks: for playbook_path in self._playbooks:
pb = Playbook.load(playbook_path, variable_manager=self._variable_manager, loader=self._loader) pb = Playbook.load(playbook_path, variable_manager=self._variable_manager, loader=self._loader)
@ -67,7 +68,6 @@ class PlaybookExecutor:
new_play = play.copy() new_play = play.copy()
new_play.post_validate(all_vars, fail_on_undefined=False) new_play.post_validate(all_vars, fail_on_undefined=False)
result = True
for batch in self._get_serialized_batches(new_play): for batch in self._get_serialized_batches(new_play):
if len(batch) == 0: if len(batch) == 0:
raise AnsibleError("No hosts matched the list specified in the play", obj=play._ds) raise AnsibleError("No hosts matched the list specified in the play", obj=play._ds)
@ -75,22 +75,22 @@ class PlaybookExecutor:
self._inventory.restrict_to_hosts(batch) self._inventory.restrict_to_hosts(batch)
# and run it... # and run it...
result = self._tqm.run(play=play) result = self._tqm.run(play=play)
if not result: if result != 0:
break break
if not result: if result != 0:
# FIXME: do something here, to signify the playbook execution failed # FIXME: do something here, to signify the playbook execution failed
self._cleanup() self._cleanup()
return 1 return result
except: except:
self._cleanup() self._cleanup()
raise raise
self._cleanup() self._cleanup()
return 0 return result
def _cleanup(self, signum=None, framenum=None): def _cleanup(self, signum=None, framenum=None):
self._tqm.cleanup() return self._tqm.cleanup()
def _get_serialized_batches(self, play): def _get_serialized_batches(self, play):
''' '''

View file

@ -109,15 +109,14 @@ class TaskExecutor:
try: try:
tmp_task = self._task.copy() tmp_task = self._task.copy()
tmp_task.post_validate(task_vars)
except AnsibleParserError, e: except AnsibleParserError, e:
results.append(dict(failed=True, msg=str(e))) results.append(dict(failed=True, msg=str(e)))
continue continue
# now we swap the internal task with the re-validate copy, execute, # now we swap the internal task with the copy, execute,
# and swap them back so we can do the next iteration cleanly # and swap them back so we can do the next iteration cleanly
(self._task, tmp_task) = (tmp_task, self._task) (self._task, tmp_task) = (tmp_task, self._task)
res = self._execute() res = self._execute(variables=task_vars)
(self._task, tmp_task) = (tmp_task, self._task) (self._task, tmp_task) = (tmp_task, self._task)
# FIXME: we should be sending back a callback result for each item in the loop here # FIXME: we should be sending back a callback result for each item in the loop here
@ -129,32 +128,24 @@ class TaskExecutor:
return results return results
def _execute(self): def _execute(self, variables=None):
''' '''
The primary workhorse of the executor system, this runs the task The primary workhorse of the executor system, this runs the task
on the specified host (which may be the delegated_to host) and handles on the specified host (which may be the delegated_to host) and handles
the retry/until and block rescue/always execution the retry/until and block rescue/always execution
''' '''
if variables is None:
variables = self._job_vars
self._connection = self._get_connection() self._connection = self._get_connection()
self._handler = self._get_action_handler(connection=self._connection) self._handler = self._get_action_handler(connection=self._connection)
# check to see if this task should be skipped, due to it being a member of a if not self._task.evaluate_conditional(variables):
# role which has already run (and whether that role allows duplicate execution)
if self._task._role and self._task._role.has_run():
# 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 self._task._role._metadata is None or self._task._role._metadata and not self._task._role._metadata.allow_duplicates:
debug("task belongs to a role which has already run, but does not allow duplicate execution")
return dict(skipped=True, skip_reason='This role has already been run, but does not allow duplicates')
if not self._task.evaluate_conditional(self._job_vars):
debug("when evaulation failed, skipping this task") debug("when evaulation failed, skipping this task")
return dict(skipped=True, skip_reason='Conditional check failed') return dict(skipped=True, skip_reason='Conditional check failed')
if not self._task.evaluate_tags(self._connection_info.only_tags, self._connection_info.skip_tags): self._task.post_validate(variables)
debug("Tags don't match, skipping this task")
return dict(skipped=True, skip_reason='Skipped due to specified tags')
retries = self._task.retries retries = self._task.retries
if retries <= 0: if retries <= 0:
@ -173,7 +164,7 @@ class TaskExecutor:
result['attempts'] = attempt + 1 result['attempts'] = attempt + 1
debug("running the handler") debug("running the handler")
result = self._handler.run(task_vars=self._job_vars) result = self._handler.run(task_vars=variables)
debug("handler run complete") debug("handler run complete")
if self._task.async > 0: if self._task.async > 0:
@ -189,7 +180,7 @@ class TaskExecutor:
if self._task.until: if self._task.until:
# make a copy of the job vars here, in case we need to update them # make a copy of the job vars here, in case we need to update them
vars_copy = self._job_vars.copy() vars_copy = variables.copy()
# now update them with the registered value, if it is set # now update them with the registered value, if it is set
if self._task.register: if self._task.register:
vars_copy[self._task.register] = result vars_copy[self._task.register] = result

View file

@ -179,7 +179,7 @@ class DataLoader():
basedir = os.path.dirname(role_path) basedir = os.path.dirname(role_path)
if os.path.islink(basedir): if os.path.islink(basedir):
# FIXME: # FIXME: implement unfrackpath
#basedir = unfrackpath(basedir) #basedir = unfrackpath(basedir)
template2 = os.path.join(basedir, dirname, source) template2 = os.path.join(basedir, dirname, source)
else: else:

View file

@ -122,7 +122,10 @@ class Base:
return self return self
def get_ds(self): def get_ds(self):
return self._ds try:
return getattr(self, '_ds')
except AttributeError:
return None
def get_loader(self): def get_loader(self):
return self._loader return self._loader
@ -214,11 +217,10 @@ class Base:
setattr(self, name, value) setattr(self, name, value)
except (TypeError, ValueError), e: except (TypeError, ValueError), e:
#raise AnsibleParserError("the field '%s' has an invalid value, and could not be converted to an %s" % (name, attribute.isa), obj=self.get_ds()) raise AnsibleParserError("the field '%s' has an invalid value (%s), and could not be converted to an %s. Error was: %s" % (name, value, attribute.isa, e), obj=self.get_ds())
raise AnsibleParserError("the field '%s' has an invalid value (%s), and could not be converted to an %s. Error was: %s" % (name, value, attribute.isa, e)) except UndefinedError, e:
except UndefinedError:
if fail_on_undefined: if fail_on_undefined:
raise AnsibleParserError("the field '%s' has an invalid value, which appears to include a variable that is undefined" % (name,)) raise AnsibleParserError("the field '%s' has an invalid value, which appears to include a variable that is undefined. The error was: %s" % (name,e), obj=self.get_ds())
def serialize(self): def serialize(self):
''' '''

View file

@ -153,14 +153,13 @@ class Block(Base, Conditional, Taggable):
return False return False
return super(Block, self).evaluate_conditional(all_vars) return super(Block, self).evaluate_conditional(all_vars)
def evaluate_tags(self, only_tags, skip_tags): def evaluate_tags(self, only_tags, skip_tags, all_vars):
result = False
if self._parent_block is not None: if self._parent_block is not None:
if not self._parent_block.evaluate_tags(only_tags=only_tags, skip_tags=skip_tags): result |= self._parent_block.evaluate_tags(only_tags=only_tags, skip_tags=skip_tags, all_vars=all_vars)
return False
elif self._role is not None: elif self._role is not None:
if not self._role.evaluate_tags(only_tags=only_tags, skip_tags=skip_tags): result |= self._role.evaluate_tags(only_tags=only_tags, skip_tags=skip_tags, all_vars=all_vars)
return False return result | super(Block, self).evaluate_tags(only_tags=only_tags, skip_tags=skip_tags, all_vars=all_vars)
return super(Block, self).evaluate_tags(only_tags=only_tags, skip_tags=skip_tags)
def set_loader(self, loader): def set_loader(self, loader):
self._loader = loader self._loader = loader

View file

@ -35,6 +35,7 @@ from ansible.playbook.helpers import load_list_of_blocks, compile_block_list
from ansible.playbook.role.include import RoleInclude from ansible.playbook.role.include import RoleInclude
from ansible.playbook.role.metadata import RoleMetadata from ansible.playbook.role.metadata import RoleMetadata
from ansible.playbook.taggable import Taggable from ansible.playbook.taggable import Taggable
from ansible.plugins import module_loader
from ansible.utils.vars import combine_vars from ansible.utils.vars import combine_vars
@ -127,6 +128,10 @@ class Role(Base, Conditional, Taggable):
#self._loader.set_basedir(self._role_path) #self._loader.set_basedir(self._role_path)
# load the role's files, if they exist # load the role's files, if they exist
library = os.path.join(self._role_path, 'library')
if os.path.isdir(library):
module_loader.add_directory(library)
metadata = self._load_role_yaml('meta') metadata = self._load_role_yaml('meta')
if metadata: if metadata:
self._metadata = RoleMetadata.load(metadata, owner=self, loader=self._loader) self._metadata = RoleMetadata.load(metadata, owner=self, loader=self._loader)

View file

@ -19,7 +19,9 @@
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
from ansible.errors import AnsibleError
from ansible.playbook.attribute import FieldAttribute from ansible.playbook.attribute import FieldAttribute
from ansible.template import Templar
class Taggable: class Taggable:
_tags = FieldAttribute(isa='list', default=[]) _tags = FieldAttribute(isa='list', default=[])
@ -27,20 +29,29 @@ class Taggable:
def __init__(self): def __init__(self):
super(Taggable, self).__init__() super(Taggable, self).__init__()
def get_tags(self): def _load_tags(self, attr, ds):
return self._tags[:] if isinstance(ds, list):
return ds
def evaluate_tags(self, only_tags, skip_tags): elif isinstance(ds, basestring):
if self.tags: return [ ds ]
my_tags = set(self.tags)
else: else:
my_tags = set() raise AnsibleError('tags must be specified as a list', obj=ds)
def evaluate_tags(self, only_tags, skip_tags, all_vars):
templar = Templar(loader=self._loader, variables=all_vars)
tags = templar.template(self.tags)
if not isinstance(tags, list):
tags = set([tags])
else:
tags = set(tags)
#print("%s tags are: %s, only_tags=%s, skip_tags=%s" % (self, my_tags, only_tags, skip_tags))
if skip_tags: if skip_tags:
skipped_tags = my_tags.intersection(skip_tags) skipped_tags = tags.intersection(skip_tags)
if len(skipped_tags) > 0: if len(skipped_tags) > 0:
return False return False
matched_tags = my_tags.intersection(only_tags) matched_tags = tags.intersection(only_tags)
#print("matched tags are: %s" % matched_tags)
if len(matched_tags) > 0 or 'all' in only_tags: if len(matched_tags) > 0 or 'all' in only_tags:
return True return True
else: else:

View file

@ -203,7 +203,12 @@ class Task(Base, Conditional, Taggable):
return listify_lookup_plugin_terms(value, all_vars, loader=self._loader) return listify_lookup_plugin_terms(value, all_vars, loader=self._loader)
def get_vars(self): def get_vars(self):
return self.serialize() all_vars = self.serialize()
if 'tags' in all_vars:
del all_vars['tags']
if 'when' in all_vars:
del all_vars['when']
return all_vars
def compile(self): def compile(self):
''' '''
@ -273,16 +278,14 @@ class Task(Base, Conditional, Taggable):
return False return False
return super(Task, self).evaluate_conditional(all_vars) return super(Task, self).evaluate_conditional(all_vars)
def evaluate_tags(self, only_tags, skip_tags): def evaluate_tags(self, only_tags, skip_tags, all_vars):
result = False
if len(self._dep_chain): if len(self._dep_chain):
for dep in self._dep_chain: for dep in self._dep_chain:
if not dep.evaluate_tags(only_tags=only_tags, skip_tags=skip_tags): result |= dep.evaluate_tags(only_tags=only_tags, skip_tags=skip_tags, all_vars=all_vars)
return False
if self._block is not None: if self._block is not None:
if not self._block.evaluate_tags(only_tags=only_tags, skip_tags=skip_tags): result |= self._block.evaluate_tags(only_tags=only_tags, skip_tags=skip_tags, all_vars=all_vars)
return False return result | super(Task, self).evaluate_tags(only_tags=only_tags, skip_tags=skip_tags, all_vars=all_vars)
return super(Task, self).evaluate_tags(only_tags=only_tags, skip_tags=skip_tags)
def set_loader(self, loader): def set_loader(self, loader):
''' '''

View file

@ -397,7 +397,7 @@ class ActionBase:
debug("done with _execute_module (%s, %s)" % (module_name, module_args)) debug("done with _execute_module (%s, %s)" % (module_name, module_args))
return data return data
def _low_level_execute_command(self, cmd, tmp, executable=None, sudoable=False, in_data=None): def _low_level_execute_command(self, cmd, tmp, executable=None, sudoable=True, in_data=None):
''' '''
This is the function which executes the low level shell command, which This is the function which executes the low level shell command, which
may be commands to create/remove directories for temporary files, or to may be commands to create/remove directories for temporary files, or to
@ -413,8 +413,19 @@ class ActionBase:
if executable is None: if executable is None:
executable = C.DEFAULT_EXECUTABLE executable = C.DEFAULT_EXECUTABLE
prompt = None
success_key = None
if sudoable:
if self._connection_info.su and self._connection_info.su_user:
cmd, prompt, success_key = self._connection_info.make_su_cmd(executable, cmd)
elif self._connection_info.sudo and self._connection_info.sudo_user:
# FIXME: hard-coded sudo_exe here
cmd, prompt, success_key = self._connection_info.make_sudo_cmd('/usr/bin/sudo', executable, cmd)
debug("executing the command through the connection") debug("executing the command through the connection")
rc, stdin, stdout, stderr = self._connection.exec_command(cmd, tmp, executable=executable, in_data=in_data, sudoable=sudoable) #rc, stdin, stdout, stderr = self._connection.exec_command(cmd, tmp, executable=executable, in_data=in_data, sudoable=sudoable)
rc, stdin, stdout, stderr = self._connection.exec_command(cmd, tmp, executable=executable, in_data=in_data)
debug("command execution done") debug("command execution done")
if not isinstance(stdout, basestring): if not isinstance(stdout, basestring):

View file

@ -24,8 +24,8 @@ import tempfile
import base64 import base64
import re import re
from ansible.plugins.action import ActionBase from ansible.plugins.action import ActionBase
from ansible.utils.boolean import boolean
from ansible.utils.hashing import checksum_s from ansible.utils.hashing import checksum_s
class ActionModule(ActionBase): class ActionModule(ActionBase):
@ -78,21 +78,16 @@ class ActionModule(ActionBase):
src = self._task.args.get('src', None) src = self._task.args.get('src', None)
dest = self._task.args.get('dest', None) dest = self._task.args.get('dest', None)
delimiter = self._task.args.get('delimiter', None) delimiter = self._task.args.get('delimiter', None)
# FIXME: boolean needs to be moved out of utils
#remote_src = utils.boolean(options.get('remote_src', 'yes'))
remote_src = self._task.args.get('remote_src', 'yes') remote_src = self._task.args.get('remote_src', 'yes')
regexp = self._task.args.get('regexp', None) regexp = self._task.args.get('regexp', None)
if src is None or dest is None: if src is None or dest is None:
return dict(failed=True, msg="src and dest are required") return dict(failed=True, msg="src and dest are required")
# FIXME: this should be boolean, hard-coded to yes for testing if boolean(remote_src):
if remote_src == 'yes':
return self._execute_module(tmp=tmp) return self._execute_module(tmp=tmp)
# FIXME: we don't do inject anymore, so not sure where the original elif self._task._role is not None:
# file stuff is going to end up at this time src = self._loader.path_dwim_relative(self._task._role._role_path, 'files', src)
#elif '_original_file' in inject:
# src = utils.path_dwim_relative(inject['_original_file'], 'files', src, self.runner.basedir)
else: else:
# the source is local, so expand it here # the source is local, so expand it here
src = os.path.expanduser(src) src = os.path.expanduser(src)

View file

@ -111,15 +111,9 @@ class ActionModule(ActionBase):
# return ReturnData(conn=conn, result=results) # return ReturnData(conn=conn, result=results)
############################################################################################### ###############################################################################################
else: else:
# FIXME: templating needs to be worked out still if self._task._role is not None:
#source = template.template(self.runner.basedir, source, inject) source = self._loader.path_dwim_relative(self._task._role._role_path, 'files', source)
# FIXME: original_file stuff needs to be reworked - most likely else:
# simply checking to see if the task has a role and using
# using the role path as the dwim target and basedir would work
#if '_original_file' in inject:
# source = utils.path_dwim_relative(inject['_original_file'], 'files', source, self.runner.basedir)
#else:
# source = utils.path_dwim(self.runner.basedir, source)
source = self._loader.path_dwim(source) source = self._loader.path_dwim(source)
# A list of source file tuples (full_path, relative_path) which will try to copy to the destination # A list of source file tuples (full_path, relative_path) which will try to copy to the destination
@ -129,7 +123,7 @@ class ActionModule(ActionBase):
if os.path.isdir(source): if os.path.isdir(source):
# Get the amount of spaces to remove to get the relative path. # Get the amount of spaces to remove to get the relative path.
if source_trailing_slash: if source_trailing_slash:
sz = len(source) + 1 sz = len(source)
else: else:
sz = len(source.rsplit('/', 1)[0]) + 1 sz = len(source.rsplit('/', 1)[0]) + 1

View file

@ -32,7 +32,7 @@ class ActionModule(ActionBase):
source = self._task.args.get('_raw_params') source = self._task.args.get('_raw_params')
if self._task._role: if self._task._role:
source = self._loader.path_dwim_relative(self._task._role.get('_role_path',''), 'vars', source) source = self._loader.path_dwim_relative(self._task._role._role_path, 'vars', source)
else: else:
source = self._loader.path_dwim(source) source = self._loader.path_dwim(source)

View file

@ -45,7 +45,7 @@ class ActionModule(ActionBase):
# look up the files and use the first one we find as src # look up the files and use the first one we find as src
#if 'first_available_file' in task_vars: #if 'first_available_file' in task_vars:
# found = False # found = False
# for fn in self.runner.module_vars.get('first_available_file'): # for fn in task_vars.get('first_available_file'):
# fn_orig = fn # fn_orig = fn
# fnt = template.template(self.runner.basedir, fn, task_vars) # fnt = template.template(self.runner.basedir, fn, task_vars)
# fnd = utils.path_dwim(self.runner.basedir, fnt) # fnd = utils.path_dwim(self.runner.basedir, fnt)
@ -59,15 +59,14 @@ class ActionModule(ActionBase):
# result = dict(failed=True, msg="could not find src in first_available_file list") # result = dict(failed=True, msg="could not find src in first_available_file list")
# return ReturnData(conn=conn, comm_ok=False, result=result) # return ReturnData(conn=conn, comm_ok=False, result=result)
#else: #else:
# source = template.template(self.runner.basedir, source, task_vars) if 1:
# if self._task._role is not None:
# if '_original_file' in task_vars: source = self._loader.path_dwim_relative(self._task._role._role_path, 'templates', source)
# source = utils.path_dwim_relative(task_vars['_original_file'], 'templates', source, self.runner.basedir) else:
# else:
# source = utils.path_dwim(self.runner.basedir, source)
##################################################################################################
source = self._loader.path_dwim(source) source = self._loader.path_dwim(source)
################################################################################################## ##################################################################################################
# END FIXME
##################################################################################################
# Expand any user home dir specification # Expand any user home dir specification
dest = self._remote_expand_user(dest, tmp) dest = self._remote_expand_user(dest, tmp)

View file

@ -39,12 +39,12 @@ class Connection(ConnectionBase):
''' connect to the local host; nothing to do here ''' ''' connect to the local host; nothing to do here '''
return self return self
def exec_command(self, cmd, tmp_path, sudo_user=None, sudoable=False, executable='/bin/sh', in_data=None, su=None, su_user=None): def exec_command(self, cmd, tmp_path, executable='/bin/sh', in_data=None):
''' run a command on the local host ''' ''' run a command on the local host '''
debug("in local.exec_command()") debug("in local.exec_command()")
# su requires to be run from a terminal, and therefore isn't supported here (yet?) # su requires to be run from a terminal, and therefore isn't supported here (yet?)
if su or su_user: if self._connection_info.su:
raise AnsibleError("Internal Error: this module does not support running commands via su") raise AnsibleError("Internal Error: this module does not support running commands via su")
if in_data: if in_data:

View file

@ -87,6 +87,7 @@ class Connection(ConnectionBase):
if self._connection_info.port is not None: if self._connection_info.port is not None:
self._common_args += ["-o", "Port=%d" % (self._connection_info.port)] self._common_args += ["-o", "Port=%d" % (self._connection_info.port)]
# FIXME: need to get this from connection info
#if self.private_key_file is not None: #if self.private_key_file is not None:
# self._common_args += ["-o", "IdentityFile=\"%s\"" % os.path.expanduser(self.private_key_file)] # self._common_args += ["-o", "IdentityFile=\"%s\"" % os.path.expanduser(self.private_key_file)]
#elif self.runner.private_key_file is not None: #elif self.runner.private_key_file is not None:
@ -256,7 +257,7 @@ class Connection(ConnectionBase):
self._display.vvv("EXEC previous known host file not found for %s" % host) self._display.vvv("EXEC previous known host file not found for %s" % host)
return True return True
def exec_command(self, cmd, tmp_path, executable='/bin/sh', in_data=None, sudoable=False): def exec_command(self, cmd, tmp_path, executable='/bin/sh', in_data=None):
''' run a command on the remote host ''' ''' run a command on the remote host '''
ssh_cmd = self._password_cmd() ssh_cmd = self._password_cmd()
@ -266,15 +267,14 @@ class Connection(ConnectionBase):
# inside a tty automatically invokes the python interactive-mode but the modules are not # inside a tty automatically invokes the python interactive-mode but the modules are not
# compatible with the interactive-mode ("unexpected indent" mainly because of empty lines) # compatible with the interactive-mode ("unexpected indent" mainly because of empty lines)
ssh_cmd += ["-tt"] ssh_cmd += ["-tt"]
# FIXME: verbosity needs to move, most likely into connection info or if self._connection_info.verbosity > 3:
# whatever other context we pass around instead of runner objects ssh_cmd += ["-vvv"]
#if utils.VERBOSITY > 3: else:
# ssh_cmd += ["-vvv"]
#else:
# ssh_cmd += ["-q"]
ssh_cmd += ["-q"] ssh_cmd += ["-q"]
ssh_cmd += self._common_args ssh_cmd += self._common_args
# FIXME: ipv6 stuff needs to be figured out. It's in the connection info, however
# not sure if it's all working yet so this remains commented out
#if self._ipv6: #if self._ipv6:
# ssh_cmd += ['-6'] # ssh_cmd += ['-6']
ssh_cmd += [self._host.ipv4_address] ssh_cmd += [self._host.ipv4_address]
@ -436,6 +436,9 @@ class Connection(ConnectionBase):
# FIXME: make a function, used in all 3 methods EXEC/PUT/FETCH # FIXME: make a function, used in all 3 methods EXEC/PUT/FETCH
host = self._host.ipv4_address host = self._host.ipv4_address
# FIXME: ipv6 stuff needs to be figured out. It's in the connection info, however
# not sure if it's all working yet so this remains commented out
#if self._ipv6: #if self._ipv6:
# host = '[%s]' % host # host = '[%s]' % host
@ -463,6 +466,9 @@ class Connection(ConnectionBase):
# FIXME: make a function, used in all 3 methods EXEC/PUT/FETCH # FIXME: make a function, used in all 3 methods EXEC/PUT/FETCH
host = self._host.ipv4_address host = self._host.ipv4_address
# FIXME: ipv6 stuff needs to be figured out. It's in the connection info, however
# not sure if it's all working yet so this remains commented out
#if self._ipv6: #if self._ipv6:
# host = '[%s]' % self._host # host = '[%s]' % self._host

View file

@ -60,12 +60,26 @@ class StrategyBase:
# outstanding tasks still in queue # outstanding tasks still in queue
self._blocked_hosts = dict() self._blocked_hosts = dict()
def run(self, iterator, connection_info): def run(self, iterator, connection_info, result=True):
# save the counts on failed/unreachable hosts, as the cleanup/handler
# methods will clear that information during their runs
num_failed = len(self._tqm._failed_hosts)
num_unreachable = len(self._tqm._unreachable_hosts)
debug("running the cleanup portion of the play") debug("running the cleanup portion of the play")
result = self.cleanup(iterator, connection_info) result &= self.cleanup(iterator, connection_info)
debug("running handlers") debug("running handlers")
result &= self.run_handlers(iterator, connection_info) result &= self.run_handlers(iterator, connection_info)
return result
if not result:
if num_unreachable > 0:
return 3
elif num_failed > 0:
return 2
else:
return 1
else:
return 0
def get_hosts_remaining(self, play): def get_hosts_remaining(self, play):
return [host for host in self._inventory.get_hosts(play.hosts) if host.name not in self._tqm._failed_hosts and host.get_name() not in self._tqm._unreachable_hosts] return [host for host in self._inventory.get_hosts(play.hosts) if host.name not in self._tqm._failed_hosts and host.get_name() not in self._tqm._unreachable_hosts]
@ -73,37 +87,10 @@ class StrategyBase:
def get_failed_hosts(self): def get_failed_hosts(self):
return [host for host in self._inventory.get_hosts() if host.name in self._tqm._failed_hosts] return [host for host in self._inventory.get_hosts() if host.name in self._tqm._failed_hosts]
def _queue_task(self, play, host, task, connection_info): def _queue_task(self, host, task, task_vars, connection_info):
''' handles queueing the task up to be sent to a worker ''' ''' handles queueing the task up to be sent to a worker '''
debug("entering _queue_task() for %s/%s/%s" % (play, host, task)) debug("entering _queue_task() for %s/%s" % (host, task))
# copy the task, to make sure we have a clean version, since the
# post-validation step will alter attribute values but this Task object
# is shared across all hosts in the play
debug("copying task")
new_task = task.copy()
debug("done copying task")
# squash variables down to a single dictionary using the variable manager and
# call post_validate() on the task, which will finalize the attribute values
debug("getting variables")
try:
task_vars = self._variable_manager.get_vars(loader=self._loader, play=play, host=host, task=new_task)
except EOFError:
# usually happens if the program is aborted, and the proxied object
# queue is cut off from the call, so we just ignore this and exit
return
debug("done getting variables")
debug("running post_validate() on the task")
if new_task.loop:
# if the task has a lookup loop specified, we do not error out
# on undefined variables yet, as fields may use {{item}} or some
# variant, which won't be defined until execution time
new_task.post_validate(task_vars, fail_on_undefined=False)
else:
new_task.post_validate(task_vars)
debug("done running post_validate() on the task")
# and then queue the new task # and then queue the new task
debug("%s - putting task (%s) in queue" % (host, task)) debug("%s - putting task (%s) in queue" % (host, task))
@ -116,12 +103,12 @@ class StrategyBase:
self._cur_worker = 0 self._cur_worker = 0
self._pending_results += 1 self._pending_results += 1
main_q.put((host, new_task, self._loader.get_basedir(), task_vars, connection_info), block=False) main_q.put((host, task, self._loader.get_basedir(), task_vars, connection_info), block=False)
except (EOFError, IOError, AssertionError), e: except (EOFError, IOError, AssertionError), e:
# most likely an abort # most likely an abort
debug("got an error while queuing: %s" % e) debug("got an error while queuing: %s" % e)
return return
debug("exiting _queue_task() for %s/%s/%s" % (play, host, task)) debug("exiting _queue_task() for %s/%s" % (host, task))
def _process_pending_results(self): def _process_pending_results(self):
''' '''
@ -140,6 +127,7 @@ class StrategyBase:
host = task_result._host host = task_result._host
task = task_result._task task = task_result._task
if result[0] == 'host_task_failed': if result[0] == 'host_task_failed':
if not task.ignore_errors:
self._tqm._failed_hosts[host.get_name()] = True self._tqm._failed_hosts[host.get_name()] = True
self._callback.runner_on_failed(task, task_result) self._callback.runner_on_failed(task, task_result)
elif result[0] == 'host_unreachable': elif result[0] == 'host_unreachable':

View file

@ -43,7 +43,7 @@ class StrategyModule(StrategyBase):
last_host = 0 last_host = 0
work_to_do = True work_to_do = True
while work_to_do: while work_to_do and not self._tqm._terminated:
hosts_left = self.get_hosts_remaining() hosts_left = self.get_hosts_remaining()
if len(hosts_left) == 0: if len(hosts_left) == 0:

View file

@ -36,7 +36,7 @@ class StrategyModule(StrategyBase):
# iteratate over each task, while there is one left to run # iteratate over each task, while there is one left to run
work_to_do = True work_to_do = True
while work_to_do: while work_to_do and not self._tqm._terminated:
try: try:
debug("getting the remaining hosts for this loop") debug("getting the remaining hosts for this loop")
@ -52,7 +52,30 @@ class StrategyModule(StrategyBase):
callback_sent = False callback_sent = False
work_to_do = False work_to_do = False
for host in hosts_left: for host in hosts_left:
while True:
task = iterator.get_next_task_for_host(host) task = iterator.get_next_task_for_host(host)
if not task:
break
debug("getting variables")
task_vars = self._variable_manager.get_vars(loader=self._loader, play=iterator._play, host=host, task=task)
debug("done getting variables")
# 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)
if task._role and task._role.has_run():
# 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:
debug("'%s' skipped because role has already run" % task)
continue
if not task.evaluate_tags(connection_info.only_tags, connection_info.skip_tags, task_vars):
debug("'%s' failed tag evaluation" % task)
continue
break
if not task: if not task:
continue continue
@ -61,24 +84,21 @@ class StrategyModule(StrategyBase):
self._callback.playbook_on_task_start(task.get_name(), False) self._callback.playbook_on_task_start(task.get_name(), False)
callback_sent = True callback_sent = True
host_name = host.get_name() self._blocked_hosts[host.get_name()] = True
if 1: #host_name not in self._tqm._failed_hosts and host_name not in self._tqm._unreachable_hosts: self._queue_task(host, task, task_vars, connection_info)
self._blocked_hosts[host_name] = True
self._queue_task(iterator._play, host, task, connection_info)
self._process_pending_results() self._process_pending_results()
debug("done queuing things up, now waiting for results queue to drain") debug("done queuing things up, now waiting for results queue to drain")
self._wait_on_pending_results() self._wait_on_pending_results()
debug("results queue empty") debug("results queue empty")
except IOError, e: except (IOError, EOFError), e:
debug("got IOError: %s" % e) debug("got IOError/EOFError in task loop: %s" % e)
# most likely an abort, return failed # most likely an abort, return failed
return 1 return 1
# run the base class run() method, which executes the cleanup function # run the base class run() method, which executes the cleanup function
# and runs any outstanding handlers which have been triggered # and runs any outstanding handlers which have been triggered
result &= super(StrategyModule, self).run(iterator, connection_info) return super(StrategyModule, self).run(iterator, connection_info, result)
return result

View file

@ -25,7 +25,7 @@ from jinja2.utils import concat as j2_concat
from jinja2.runtime import StrictUndefined from jinja2.runtime import StrictUndefined
from ansible import constants as C from ansible import constants as C
from ansible.errors import * from ansible.errors import AnsibleError, AnsibleFilterError, AnsibleUndefinedVariable
from ansible.plugins import filter_loader, lookup_loader from ansible.plugins import filter_loader, lookup_loader
from ansible.template.safe_eval import safe_eval from ansible.template.safe_eval import safe_eval
from ansible.template.template import AnsibleJ2Template from ansible.template.template import AnsibleJ2Template
@ -266,7 +266,7 @@ class Templar:
res += '\n' * (data_newlines - res_newlines) res += '\n' * (data_newlines - res_newlines)
return res return res
except UndefinedError, AnsibleUndefinedVariable: except (UndefinedError, AnsibleUndefinedVariable), e:
if self._fail_on_undefined_errors: if self._fail_on_undefined_errors:
raise raise
else: else:

View file

@ -144,6 +144,7 @@ class Cli(object):
dict(action=dict(module=options.module_name, args=parse_kv(options.module_args))), dict(action=dict(module=options.module_name, args=parse_kv(options.module_args))),
] ]
) )
play = Play().load(play_ds, variable_manager=variable_manager, loader=loader) play = Play().load(play_ds, variable_manager=variable_manager, loader=loader)
# now create a task queue manager to execute the play # now create a task queue manager to execute the play
@ -155,7 +156,7 @@ class Cli(object):
tqm.cleanup() tqm.cleanup()
raise raise
return (result, len(tqm._failed_hosts), len(tqm._unreachable_hosts)) return result
# ---------------------------------------------- # ----------------------------------------------
@ -179,12 +180,7 @@ if __name__ == '__main__':
try: try:
cli = Cli() cli = Cli()
(options, args) = cli.parse() (options, args) = cli.parse()
(result, num_failed, num_unreachable) = cli.run(options, args) result = cli.run(options, args)
if not result:
if num_failed > 0:
sys.exit(2)
elif num_unreachable > 0:
sys.exit(3)
except AnsibleError, e: except AnsibleError, e:
print(e) print(e)
@ -195,3 +191,4 @@ if __name__ == '__main__':
print("ERROR: %s" % str(e)) print("ERROR: %s" % str(e))
sys.exit(1) sys.exit(1)
sys.exit(result)

View file

@ -143,7 +143,7 @@ def main(args):
# create the playbook executor, which manages running the plays # create the playbook executor, which manages running the plays
# via a task queue manager # via a task queue manager
pbex = PlaybookExecutor(playbooks=args, inventory=inventory, variable_manager=variable_manager, loader=loader, options=options) pbex = PlaybookExecutor(playbooks=args, inventory=inventory, variable_manager=variable_manager, loader=loader, options=options)
pbex.run() return pbex.run()
if __name__ == "__main__": if __name__ == "__main__":
#display(" ", log_only=True) #display(" ", log_only=True)

View file

@ -0,0 +1,10 @@
- hosts: localhost
connection: local
gather_facts: no
tasks:
- fail:
ignore_errors: yes
- debug: msg="you should still see this"
- fail:
- debug: msg="you should NOT see this"