Adds 'delegate_to' as a task option which can be used to signal load balancers and outage windows.
This commit is contained in:
parent
fda4f80828
commit
f07e55c568
3 changed files with 56 additions and 27 deletions
|
@ -39,6 +39,7 @@ Ansible Changes By Release
|
||||||
* ANSIBLE_KEEP_REMOTE_FILES=1 can be used in debugging (envrionment variable)
|
* ANSIBLE_KEEP_REMOTE_FILES=1 can be used in debugging (envrionment variable)
|
||||||
* add pattern= as a paramter to the service module
|
* add pattern= as a paramter to the service module
|
||||||
* various fixes to mysql & postresql modules
|
* 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
|
0.6 "Cabo" -- August 6, 2012
|
||||||
|
|
||||||
|
|
|
@ -23,13 +23,15 @@ class Task(object):
|
||||||
__slots__ = [
|
__slots__ = [
|
||||||
'name', 'action', 'only_if', 'async_seconds', 'async_poll_interval',
|
'name', 'action', 'only_if', 'async_seconds', 'async_poll_interval',
|
||||||
'notify', 'module_name', 'module_args', 'module_vars',
|
'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
|
# to prevent typos and such
|
||||||
VALID_KEYS = [
|
VALID_KEYS = [
|
||||||
'name', 'action', 'only_if', 'async', 'poll', 'notify', 'with_items', 'first_available_file',
|
'name', 'action', 'only_if', 'async', 'poll', 'notify', 'with_items',
|
||||||
'include', 'tags', 'register', 'ignore_errors'
|
'first_available_file', 'include', 'tags', 'register', 'ignore_errors',
|
||||||
|
'delegate_to'
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, play, ds, module_vars=None):
|
def __init__(self, play, ds, module_vars=None):
|
||||||
|
@ -63,6 +65,8 @@ class Task(object):
|
||||||
self.notify = ds.get('notify', [])
|
self.notify = ds.get('notify', [])
|
||||||
self.first_available_file = ds.get('first_available_file', None)
|
self.first_available_file = ds.get('first_available_file', None)
|
||||||
self.with_items = ds.get('with_items', 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)
|
self.ignore_errors = ds.get('ignore_errors', False)
|
||||||
|
|
||||||
# notify can be a string or a list, store as a list
|
# notify can be a string or a list, store as a list
|
||||||
|
@ -99,6 +103,9 @@ class Task(object):
|
||||||
self.with_items = [ ]
|
self.with_items = [ ]
|
||||||
self.module_vars['items'] = 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
|
# make ignore_errors accessable to Runner code
|
||||||
self.module_vars['ignore_errors'] = self.ignore_errors
|
self.module_vars['ignore_errors'] = self.ignore_errors
|
||||||
|
|
||||||
|
|
|
@ -70,15 +70,25 @@ class ReturnData(object):
|
||||||
|
|
||||||
__slots__ = [ 'result', 'comm_ok', 'host' ]
|
__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):
|
||||||
self.host = host
|
|
||||||
|
# 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.result = result
|
||||||
self.comm_ok = comm_ok
|
self.comm_ok = comm_ok
|
||||||
|
|
||||||
if type(self.result) in [ str, unicode ]:
|
if type(self.result) in [ str, unicode ]:
|
||||||
self.result = utils.parse_json(self.result)
|
self.result = utils.parse_json(self.result)
|
||||||
|
|
||||||
if host is None:
|
if self.host is None:
|
||||||
raise Exception("host not set")
|
raise Exception("host not set")
|
||||||
if type(self.result) != dict:
|
if type(self.result) != dict:
|
||||||
raise Exception("dictionary result expected")
|
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]])
|
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)
|
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):
|
def _execute_raw(self, conn, tmp, inject=None):
|
||||||
''' execute a non-module command for bootstrapping, or if there's no python on a device '''
|
''' 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)
|
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)
|
dest = options.get('dest', None)
|
||||||
if (source is None and not 'first_available_file' in inject) or dest is 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")
|
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
|
# if we have first_available_file in our vars
|
||||||
# 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
|
||||||
|
@ -306,7 +316,7 @@ class Runner(object):
|
||||||
break
|
break
|
||||||
if not found:
|
if not found:
|
||||||
results=dict(failed=True, msg="could not find src in first_available_file list")
|
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.template(source, inject)
|
||||||
source = utils.path_dwim(self.basedir, source)
|
source = utils.path_dwim(self.basedir, source)
|
||||||
|
@ -314,7 +324,7 @@ class Runner(object):
|
||||||
local_md5 = utils.md5(source)
|
local_md5 = utils.md5(source)
|
||||||
if local_md5 is None:
|
if local_md5 is None:
|
||||||
result=dict(failed=True, msg="could not find src=%s" % source)
|
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)
|
remote_md5 = self._remote_md5(conn, tmp, dest)
|
||||||
|
|
||||||
|
@ -334,7 +344,7 @@ class Runner(object):
|
||||||
else:
|
else:
|
||||||
# no need to transfer the file, already correct md5
|
# no need to transfer the file, already correct md5
|
||||||
result = dict(changed=False, md5sum=remote_md5, transferred=False)
|
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)
|
dest = options.get('dest', None)
|
||||||
if source is None or dest is None:
|
if source is None or dest is None:
|
||||||
results = dict(failed=True, msg="src and dest are required")
|
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
|
# apply templating to source argument
|
||||||
source = utils.template(source, inject)
|
source = utils.template(source, inject)
|
||||||
|
@ -365,13 +375,13 @@ class Runner(object):
|
||||||
# but keep going to fetch other log files
|
# but keep going to fetch other log files
|
||||||
if remote_md5 == '0':
|
if remote_md5 == '0':
|
||||||
result = dict(msg="unable to calculate the md5 sum of the remote file", file=source, changed=False)
|
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':
|
if remote_md5 == '1':
|
||||||
result = dict(msg="the remote file does not exist, not transferring, ignored", file=source, changed=False)
|
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':
|
if remote_md5 == '2':
|
||||||
result = dict(msg="no read permission on remote file, not transferring, ignored", file=source, changed=False)
|
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
|
# calculate md5 sum for the local file
|
||||||
local_md5 = utils.md5(dest)
|
local_md5 = utils.md5(dest)
|
||||||
|
@ -386,12 +396,12 @@ class Runner(object):
|
||||||
new_md5 = utils.md5(dest)
|
new_md5 = utils.md5(dest)
|
||||||
if new_md5 != remote_md5:
|
if new_md5 != remote_md5:
|
||||||
result = dict(failed=True, md5sum=new_md5, msg="md5 mismatch", file=source)
|
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)
|
result = dict(changed=True, md5sum=new_md5)
|
||||||
return ReturnData(host=conn.host, result=result)
|
return ReturnData(conn=conn, result=result)
|
||||||
else:
|
else:
|
||||||
result = dict(changed=False, md5sum=local_md5, file=source)
|
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)
|
dest = options.get('dest', None)
|
||||||
if (source is None and 'first_available_file' not in inject) or dest is 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")
|
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
|
# if we have first_available_file in our vars
|
||||||
# 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
|
||||||
|
@ -421,7 +431,7 @@ class Runner(object):
|
||||||
break
|
break
|
||||||
if not found:
|
if not found:
|
||||||
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(host=conn.host, comm_ok=False, result=result)
|
return ReturnData(conn=conn, comm_ok=False, result=result)
|
||||||
|
|
||||||
source = utils.template(source, inject)
|
source = utils.template(source, inject)
|
||||||
|
|
||||||
|
@ -430,7 +440,8 @@ class Runner(object):
|
||||||
resultant = utils.template_from_file(self.basedir, source, inject)
|
resultant = utils.template_from_file(self.basedir, source, inject)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
result = dict(failed=True, msg=str(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)
|
xfered = self._transfer_str(conn, tmp, 'source', resultant)
|
||||||
|
|
||||||
# run the copy module, queue the file module
|
# run the copy module, queue the file module
|
||||||
|
@ -483,8 +494,10 @@ class Runner(object):
|
||||||
inject.update(self.module_vars)
|
inject.update(self.module_vars)
|
||||||
inject['hostvars'] = self.setup_cache
|
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("$"):
|
if isinstance(items, basestring) and items.startswith("$"):
|
||||||
items = items.replace("$","")
|
items = items.replace("$","")
|
||||||
if items in inject:
|
if items in inject:
|
||||||
|
@ -499,6 +512,8 @@ class Runner(object):
|
||||||
inject['item'] = ",".join(items)
|
inject['item'] = ",".join(items)
|
||||||
items = []
|
items = []
|
||||||
|
|
||||||
|
# logic to decide how to run things depends on whether with_items is used
|
||||||
|
|
||||||
if len(items) == 0:
|
if len(items) == 0:
|
||||||
return self._executor_internal_inner(host, inject, port)
|
return self._executor_internal_inner(host, inject, port)
|
||||||
else:
|
else:
|
||||||
|
@ -570,8 +585,14 @@ class Runner(object):
|
||||||
return ReturnData(host=host, result=result)
|
return ReturnData(host=host, result=result)
|
||||||
|
|
||||||
conn = None
|
conn = None
|
||||||
|
actual_host = host
|
||||||
try:
|
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:
|
except errors.AnsibleConnectionFailed, e:
|
||||||
result = dict(failed=True, msg="FAILED: %s" % str(e))
|
result = dict(failed=True, msg="FAILED: %s" % str(e))
|
||||||
return ReturnData(host=host, comm_ok=False, result=result)
|
return ReturnData(host=host, comm_ok=False, result=result)
|
||||||
|
@ -627,17 +648,17 @@ class Runner(object):
|
||||||
# no callbacks
|
# no callbacks
|
||||||
return result
|
return result
|
||||||
if 'skipped' in data:
|
if 'skipped' in data:
|
||||||
self.callbacks.on_skipped(result.host)
|
self.callbacks.on_skipped(host)
|
||||||
elif not result.is_successful():
|
elif not result.is_successful():
|
||||||
ignore_errors = self.module_vars.get('ignore_errors', False)
|
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 ignore_errors:
|
||||||
if 'failed' in result.result:
|
if 'failed' in result.result:
|
||||||
result.result['failed'] = False
|
result.result['failed'] = False
|
||||||
if 'rc' in result.result:
|
if 'rc' in result.result:
|
||||||
result.result['rc'] = 0
|
result.result['rc'] = 0
|
||||||
else:
|
else:
|
||||||
self.callbacks.on_ok(result.host, data)
|
self.callbacks.on_ok(host, data)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# *****************************************************
|
# *****************************************************
|
||||||
|
|
Loading…
Reference in a new issue