Adds 'delegate_to' as a task option which can be used to signal load balancers and outage windows.

This commit is contained in:
Michael DeHaan 2012-08-18 08:46:51 -04:00
parent fda4f80828
commit f07e55c568
3 changed files with 56 additions and 27 deletions

View file

@ -39,6 +39,7 @@ Ansible Changes By Release
* ANSIBLE_KEEP_REMOTE_FILES=1 can be used in debugging (envrionment variable)
* add pattern= as a paramter to the service module
* various fixes to mysql & postresql modules
* adds 'delegate_to' for a task, which can be used to signal outage windows and load balancers on behalf of hosts
0.6 "Cabo" -- August 6, 2012

View file

@ -23,13 +23,15 @@ class Task(object):
__slots__ = [
'name', 'action', 'only_if', 'async_seconds', 'async_poll_interval',
'notify', 'module_name', 'module_args', 'module_vars',
'play', 'notified_by', 'tags', 'register', 'with_items', 'first_available_file', 'ignore_errors'
'play', 'notified_by', 'tags', 'register', 'with_items',
'delegate_to', 'first_available_file', 'ignore_errors'
]
# to prevent typos and such
VALID_KEYS = [
'name', 'action', 'only_if', 'async', 'poll', 'notify', 'with_items', 'first_available_file',
'include', 'tags', 'register', 'ignore_errors'
'name', 'action', 'only_if', 'async', 'poll', 'notify', 'with_items',
'first_available_file', 'include', 'tags', 'register', 'ignore_errors',
'delegate_to'
]
def __init__(self, play, ds, module_vars=None):
@ -63,6 +65,8 @@ class Task(object):
self.notify = ds.get('notify', [])
self.first_available_file = ds.get('first_available_file', None)
self.with_items = ds.get('with_items', None)
self.delegate_to = ds.get('delegate_to', None)
self.ignore_errors = ds.get('ignore_errors', False)
# notify can be a string or a list, store as a list
@ -99,6 +103,9 @@ class Task(object):
self.with_items = [ ]
self.module_vars['items'] = self.with_items
# allow runner to see delegate_to option
self.module_vars['delegate_to'] = self.delegate_to
# make ignore_errors accessable to Runner code
self.module_vars['ignore_errors'] = self.ignore_errors

View file

@ -70,15 +70,25 @@ class ReturnData(object):
__slots__ = [ 'result', 'comm_ok', 'host' ]
def __init__(self, host=None, result=None, comm_ok=True):
def __init__(self, conn=None, host=None, result=None, comm_ok=True):
# which host is this ReturnData about?
if conn is not None:
delegate_for = getattr(conn, '_delegate_for', None)
if delegate_for:
self.host = delegate_for
else:
self.host = conn.host
else:
self.host = host
self.result = result
self.comm_ok = comm_ok
if type(self.result) in [ str, unicode ]:
self.result = utils.parse_json(self.result)
if host is None:
if self.host is None:
raise Exception("host not set")
if type(self.result) != dict:
raise Exception("dictionary result expected")
@ -236,13 +246,13 @@ class Runner(object):
cmd = " ".join([str(x) for x in [remote_module_path, async_jid, async_limit, async_module]])
res = self._low_level_exec_command(conn, cmd, tmp, sudoable=True)
return ReturnData(host=conn.host, result=res)
return ReturnData(conn=conn, result=res)
# *****************************************************
def _execute_raw(self, conn, tmp, inject=None):
''' execute a non-module command for bootstrapping, or if there's no python on a device '''
return ReturnData(host=conn.host, result=dict(
return ReturnData(conn=conn, result=dict(
stdout=self._low_level_exec_command(conn, self.module_args, tmp, sudoable = True)
))
@ -292,7 +302,7 @@ class Runner(object):
dest = options.get('dest', None)
if (source is None and not 'first_available_file' in inject) or dest is None:
result=dict(failed=True, msg="src and dest are required")
return ReturnData(host=conn.host, result=result)
return ReturnData(conn=conn, result=result)
# if we have first_available_file in our vars
# look up the files and use the first one we find as src
@ -306,7 +316,7 @@ class Runner(object):
break
if not found:
results=dict(failed=True, msg="could not find src in first_available_file list")
return ReturnData(host=conn.host, results=results)
return ReturnData(conn=conn, results=results)
source = utils.template(source, inject)
source = utils.path_dwim(self.basedir, source)
@ -314,7 +324,7 @@ class Runner(object):
local_md5 = utils.md5(source)
if local_md5 is None:
result=dict(failed=True, msg="could not find src=%s" % source)
return ReturnData(host=conn.host, result=result)
return ReturnData(conn=conn, result=result)
remote_md5 = self._remote_md5(conn, tmp, dest)
@ -334,7 +344,7 @@ class Runner(object):
else:
# no need to transfer the file, already correct md5
result = dict(changed=False, md5sum=remote_md5, transferred=False)
return ReturnData(host=conn.host, result=result).daisychain('file')
return ReturnData(conn=conn, result=result).daisychain('file')
# *****************************************************
@ -347,7 +357,7 @@ class Runner(object):
dest = options.get('dest', None)
if source is None or dest is None:
results = dict(failed=True, msg="src and dest are required")
return ReturnData(host=conn.host, result=results)
return ReturnData(conn=conn, result=results)
# apply templating to source argument
source = utils.template(source, inject)
@ -365,13 +375,13 @@ class Runner(object):
# but keep going to fetch other log files
if remote_md5 == '0':
result = dict(msg="unable to calculate the md5 sum of the remote file", file=source, changed=False)
return ReturnData(host=conn.host, result=result)
return ReturnData(conn=conn, result=result)
if remote_md5 == '1':
result = dict(msg="the remote file does not exist, not transferring, ignored", file=source, changed=False)
return ReturnData(host=conn.host, result=result)
return ReturnData(conn=conn, result=result)
if remote_md5 == '2':
result = dict(msg="no read permission on remote file, not transferring, ignored", file=source, changed=False)
return ReturnData(host=conn.host, result=result)
return ReturnData(conn=conn, result=result)
# calculate md5 sum for the local file
local_md5 = utils.md5(dest)
@ -386,12 +396,12 @@ class Runner(object):
new_md5 = utils.md5(dest)
if new_md5 != remote_md5:
result = dict(failed=True, md5sum=new_md5, msg="md5 mismatch", file=source)
return ReturnData(host=conn.host, result=result)
return ReturnData(conn=conn, result=result)
result = dict(changed=True, md5sum=new_md5)
return ReturnData(host=conn.host, result=result)
return ReturnData(conn=conn, result=result)
else:
result = dict(changed=False, md5sum=local_md5, file=source)
return ReturnData(host=conn.host, result=result)
return ReturnData(conn=conn, result=result)
# *****************************************************
@ -407,7 +417,7 @@ class Runner(object):
dest = options.get('dest', None)
if (source is None and 'first_available_file' not in inject) or dest is None:
result = dict(failed=True, msg="src and dest are required")
return ReturnData(host=conn.host, comm_ok=False, result=result)
return ReturnData(conn=conn, comm_ok=False, result=result)
# if we have first_available_file in our vars
# look up the files and use the first one we find as src
@ -421,7 +431,7 @@ class Runner(object):
break
if not found:
result = dict(failed=True, msg="could not find src in first_available_file list")
return ReturnData(host=conn.host, comm_ok=False, result=result)
return ReturnData(conn=conn, comm_ok=False, result=result)
source = utils.template(source, inject)
@ -430,7 +440,8 @@ class Runner(object):
resultant = utils.template_from_file(self.basedir, source, inject)
except Exception, e:
result = dict(failed=True, msg=str(e))
return ReturnData(host=conn.host, comm_ok=False, result=result)
return ReturnData(conn=conn, comm_ok=False, result=result)
xfered = self._transfer_str(conn, tmp, 'source', resultant)
# run the copy module, queue the file module
@ -483,8 +494,10 @@ class Runner(object):
inject.update(self.module_vars)
inject['hostvars'] = self.setup_cache
items = self.module_vars.get('items', [])
# allow with_items to work in playbooks...
# apt and yum are converted into a single call, others run in a loop
items = self.module_vars.get('items', [])
if isinstance(items, basestring) and items.startswith("$"):
items = items.replace("$","")
if items in inject:
@ -499,6 +512,8 @@ class Runner(object):
inject['item'] = ",".join(items)
items = []
# logic to decide how to run things depends on whether with_items is used
if len(items) == 0:
return self._executor_internal_inner(host, inject, port)
else:
@ -570,8 +585,14 @@ class Runner(object):
return ReturnData(host=host, result=result)
conn = None
actual_host = host
try:
conn = self.connector.connect(host, port)
delegate_to = inject.get('delegate_to', None)
if delegate_to is not None:
actual_host = delegate_to
conn = self.connector.connect(actual_host, port)
if delegate_to is not None:
conn._delegate_for = host
except errors.AnsibleConnectionFailed, e:
result = dict(failed=True, msg="FAILED: %s" % str(e))
return ReturnData(host=host, comm_ok=False, result=result)
@ -627,17 +648,17 @@ class Runner(object):
# no callbacks
return result
if 'skipped' in data:
self.callbacks.on_skipped(result.host)
self.callbacks.on_skipped(host)
elif not result.is_successful():
ignore_errors = self.module_vars.get('ignore_errors', False)
self.callbacks.on_failed(result.host, data, ignore_errors)
self.callbacks.on_failed(host, data, ignore_errors)
if ignore_errors:
if 'failed' in result.result:
result.result['failed'] = False
if 'rc' in result.result:
result.result['rc'] = 0
else:
self.callbacks.on_ok(result.host, data)
self.callbacks.on_ok(host, data)
return result
# *****************************************************