mirror of
https://github.com/valitydev/salt.git
synced 2024-11-07 08:58:59 +00:00
Make returncode friendly to onfail requisites
Ensure that correctly handled states execution (via onfail) will return a 0 code if onfail requisites suceed.
This commit is contained in:
parent
3c108bd95b
commit
fa2f9cf7bd
@ -83,7 +83,7 @@ def _filter_running(runnings):
|
||||
return ret
|
||||
|
||||
|
||||
def _set_retcode(ret):
|
||||
def _set_retcode(ret, highstate=None):
|
||||
'''
|
||||
Set the return code based on the data back from the state system
|
||||
'''
|
||||
@ -94,7 +94,8 @@ def _set_retcode(ret):
|
||||
if isinstance(ret, list):
|
||||
__context__['retcode'] = 1
|
||||
return
|
||||
if not salt.utils.check_state_result(ret):
|
||||
if not salt.utils.check_state_result(ret, highstate=highstate):
|
||||
|
||||
__context__['retcode'] = 2
|
||||
|
||||
|
||||
@ -345,7 +346,7 @@ def high(data, test=None, queue=False, **kwargs):
|
||||
st_ = salt.state.State(opts, pillar, pillar_enc=pillar_enc)
|
||||
|
||||
ret = st_.call_high(data)
|
||||
_set_retcode(ret)
|
||||
_set_retcode(ret, highstate=data)
|
||||
return ret
|
||||
|
||||
|
||||
@ -393,7 +394,7 @@ def template(tem, queue=False, **kwargs):
|
||||
__context__['retcode'] = 1
|
||||
return errors
|
||||
ret = st_.state.call_high(high_state)
|
||||
_set_retcode(ret)
|
||||
_set_retcode(ret, highstate=high_state)
|
||||
return ret
|
||||
|
||||
|
||||
@ -859,7 +860,7 @@ def highstate(test=None,
|
||||
|
||||
serial = salt.payload.Serial(__opts__)
|
||||
cache_file = os.path.join(__opts__['cachedir'], 'highstate.p')
|
||||
_set_retcode(ret)
|
||||
_set_retcode(ret, highstate=st_.building_highstate)
|
||||
# Work around Windows multiprocessing bug, set __opts__['test'] back to
|
||||
# value from before this function was run.
|
||||
_snapper_post(opts, kwargs.get('__pub_jid', 'called localy'), snapper_pre)
|
||||
@ -1091,7 +1092,7 @@ def sls(mods,
|
||||
except (IOError, OSError):
|
||||
msg = 'Unable to write to SLS cache file {0}. Check permission.'
|
||||
log.error(msg.format(cache_file))
|
||||
_set_retcode(ret)
|
||||
_set_retcode(ret, high_)
|
||||
# Work around Windows multiprocessing bug, set __opts__['test'] back to
|
||||
# value from before this function was run.
|
||||
__opts__['test'] = orig_test
|
||||
@ -1195,7 +1196,7 @@ def top(topfn,
|
||||
finally:
|
||||
st_.pop_active()
|
||||
|
||||
_set_retcode(ret)
|
||||
_set_retcode(ret, highstate=st_.building_highstate)
|
||||
# Work around Windows multiprocessing bug, set __opts__['test'] back to
|
||||
# value from before this function was run.
|
||||
_snapper_post(opts, kwargs.get('__pub_jid', 'called localy'), snapper_pre)
|
||||
@ -1396,7 +1397,7 @@ def sls_id(
|
||||
if chunk.get('__id__', '') == id_:
|
||||
ret.update(st_.state.call_chunk(chunk, {}, chunks))
|
||||
|
||||
_set_retcode(ret)
|
||||
_set_retcode(ret, highstate=highstate)
|
||||
# Work around Windows multiprocessing bug, set __opts__['test'] back to
|
||||
# value from before this function was run.
|
||||
__opts__['test'] = orig_test
|
||||
|
@ -1868,7 +1868,95 @@ def gen_state_tag(low):
|
||||
return '{0[state]}_|-{0[__id__]}_|-{0[name]}_|-{0[fun]}'.format(low)
|
||||
|
||||
|
||||
def check_state_result(running, recurse=False):
|
||||
def search_onfail_requisites(sid, highstate):
|
||||
"""
|
||||
For a particular low chunk, search relevant onfail related
|
||||
states
|
||||
"""
|
||||
onfails = []
|
||||
if '_|-' in sid:
|
||||
st = salt.state.split_low_tag(sid)
|
||||
else:
|
||||
st = {'__id__': sid}
|
||||
for fstate, fchunks in six.iteritems(highstate):
|
||||
if fstate == st['__id__']:
|
||||
continue
|
||||
else:
|
||||
for mod_, fchunk in six.iteritems(fchunks):
|
||||
if (
|
||||
not isinstance(mod_, six.string_types) or
|
||||
mod_.startswith('__')
|
||||
):
|
||||
continue
|
||||
else:
|
||||
if not isinstance(fchunk, list):
|
||||
continue
|
||||
else:
|
||||
for fdata in fchunk:
|
||||
if not isinstance(fdata, dict):
|
||||
continue
|
||||
for knob, fvalue in six.iteritems(fdata):
|
||||
if knob != 'onfail':
|
||||
continue
|
||||
for freqs in fvalue:
|
||||
for fmod, fid in six.iteritems(freqs):
|
||||
if not (
|
||||
fid == st['__id__'] and
|
||||
fmod == st.get('state', fmod)
|
||||
):
|
||||
continue
|
||||
onfails.append((fstate, mod_, fchunk))
|
||||
return onfails
|
||||
|
||||
|
||||
def check_onfail_requisites(state_id, state_result, running, highstate):
|
||||
'''
|
||||
When a state fail and is part of a highstate, check
|
||||
if there is onfail requisites.
|
||||
When we find onfail requisites, we will consider the state failed
|
||||
only if at least one of those onfail requisites also failed
|
||||
|
||||
Returns:
|
||||
|
||||
True: if onfail handlers suceeded
|
||||
False: if one on those handler failed
|
||||
None: if the state does not have onfail requisites
|
||||
|
||||
'''
|
||||
nret = None
|
||||
if (
|
||||
state_id and state_result and
|
||||
highstate and isinstance(highstate, dict)
|
||||
):
|
||||
onfails = search_onfail_requisites(state_id, highstate)
|
||||
if onfails:
|
||||
for handler in onfails:
|
||||
fstate, mod_, fchunk = handler
|
||||
ofresult = True
|
||||
for rstateid, rstate in six.iteritems(running):
|
||||
if '_|-' in rstateid:
|
||||
st = salt.state.split_low_tag(rstateid)
|
||||
# in case of simple state, try to guess
|
||||
else:
|
||||
id_ = rstate.get('__id__', rstateid)
|
||||
if not id_:
|
||||
raise ValueError('no state id')
|
||||
st = {'__id__': id_, 'state': mod_}
|
||||
if mod_ == st['state'] and fstate == st['__id__']:
|
||||
ofresult = rstate.get('result', _empty)
|
||||
if ofresult in [False, True]:
|
||||
nret = ofresult
|
||||
if ofresult is False:
|
||||
# as soon as we find an errored onfail, we stop
|
||||
break
|
||||
# consider that if we parsed onfailes without changing
|
||||
# the ret, that we have failed
|
||||
if nret is None:
|
||||
nret = False
|
||||
return nret
|
||||
|
||||
|
||||
def check_state_result(running, recurse=False, highstate=None):
|
||||
'''
|
||||
Check the total return value of the run and determine if the running
|
||||
dict has any issues
|
||||
@ -1880,7 +1968,7 @@ def check_state_result(running, recurse=False):
|
||||
return False
|
||||
|
||||
ret = True
|
||||
for state_result in six.itervalues(running):
|
||||
for state_id, state_result in six.iteritems(running):
|
||||
if not recurse and not isinstance(state_result, dict):
|
||||
ret = False
|
||||
if ret and isinstance(state_result, dict):
|
||||
@ -1889,7 +1977,13 @@ def check_state_result(running, recurse=False):
|
||||
ret = False
|
||||
# only override return value if we are not already failed
|
||||
elif result is _empty and isinstance(state_result, dict) and ret:
|
||||
ret = check_state_result(state_result, recurse=True)
|
||||
ret = check_state_result(
|
||||
state_result, recurse=True, highstate=highstate)
|
||||
# if we detect a fail, check for onfail requisites
|
||||
if not ret:
|
||||
# ret can be None in case of no onfail reqs, recast it to bool
|
||||
ret = bool(check_onfail_requisites(state_id, state_result,
|
||||
running, highstate))
|
||||
# return as soon as we got a failure
|
||||
if not ret:
|
||||
break
|
||||
|
@ -136,6 +136,7 @@ class MockState(object):
|
||||
'pillar': {}}
|
||||
|
||||
def __init__(self, opts, pillar=None, *args, **kwargs):
|
||||
self.building_highstate = {}
|
||||
self.state = MockState.State(opts,
|
||||
pillar=pillar)
|
||||
|
||||
|
@ -452,6 +452,243 @@ class UtilsTestCase(TestCase):
|
||||
self.assertTrue(
|
||||
utils.check_state_result(data),
|
||||
msg='{0} failed'.format(test))
|
||||
test_invalid_true_ht_states = {
|
||||
'test_onfail_integ2': (
|
||||
OrderedDict([
|
||||
('host1',
|
||||
OrderedDict([
|
||||
('t_|-test_ivstate0_|-echo_|-run', {
|
||||
'result': False}),
|
||||
('cmd_|-test_ivstate0_|-echo_|-run', {
|
||||
'result': False}),
|
||||
('cmd_|-test_ivstate1_|-echo_|-run', {
|
||||
'result': False}),
|
||||
])),
|
||||
]),
|
||||
{
|
||||
'test_ivstate0': {
|
||||
'__env__': 'base',
|
||||
'__sls__': u'a',
|
||||
'cmd': [OrderedDict([('name', '/bin/true')]),
|
||||
'run',
|
||||
{'order': 10002}],
|
||||
't': [OrderedDict([('name', '/bin/true')]),
|
||||
'run',
|
||||
{'order': 10002}]},
|
||||
'test_ivstate1': {
|
||||
'__env__': 'base',
|
||||
'__sls__': u'a',
|
||||
'cmd': [OrderedDict([('name', '/bin/true')]),
|
||||
OrderedDict([
|
||||
('onfail',
|
||||
[OrderedDict([('cmd', 'test_ivstate0')])])
|
||||
]),
|
||||
'run',
|
||||
{'order': 10004}]},
|
||||
}
|
||||
),
|
||||
'test_onfail_integ3': (
|
||||
OrderedDict([
|
||||
('host1',
|
||||
OrderedDict([
|
||||
('t_|-test_ivstate0_|-echo_|-run', {
|
||||
'result': True}),
|
||||
('cmd_|-test_ivstate0_|-echo_|-run', {
|
||||
'result': False}),
|
||||
('cmd_|-test_ivstate1_|-echo_|-run', {
|
||||
'result': False}),
|
||||
])),
|
||||
]),
|
||||
{
|
||||
'test_ivstate0': {
|
||||
'__env__': 'base',
|
||||
'__sls__': u'a',
|
||||
'cmd': [OrderedDict([('name', '/bin/true')]),
|
||||
'run',
|
||||
{'order': 10002}],
|
||||
't': [OrderedDict([('name', '/bin/true')]),
|
||||
'run',
|
||||
{'order': 10002}]},
|
||||
'test_ivstate1': {
|
||||
'__env__': 'base',
|
||||
'__sls__': u'a',
|
||||
'cmd': [OrderedDict([('name', '/bin/true')]),
|
||||
OrderedDict([
|
||||
('onfail',
|
||||
[OrderedDict([('cmd', 'test_ivstate0')])])
|
||||
]),
|
||||
'run',
|
||||
{'order': 10004}]},
|
||||
}
|
||||
),
|
||||
'test_onfail_integ4': (
|
||||
OrderedDict([
|
||||
('host1',
|
||||
OrderedDict([
|
||||
('t_|-test_ivstate0_|-echo_|-run', {
|
||||
'result': False}),
|
||||
('cmd_|-test_ivstate0_|-echo_|-run', {
|
||||
'result': False}),
|
||||
('cmd_|-test_ivstate1_|-echo_|-run', {
|
||||
'result': True}),
|
||||
])),
|
||||
]),
|
||||
{
|
||||
'test_ivstate0': {
|
||||
'__env__': 'base',
|
||||
'__sls__': u'a',
|
||||
'cmd': [OrderedDict([('name', '/bin/true')]),
|
||||
'run',
|
||||
{'order': 10002}],
|
||||
't': [OrderedDict([('name', '/bin/true')]),
|
||||
'run',
|
||||
{'order': 10002}]},
|
||||
'test_ivstate1': {
|
||||
'__env__': 'base',
|
||||
'__sls__': u'a',
|
||||
'cmd': [OrderedDict([('name', '/bin/true')]),
|
||||
OrderedDict([
|
||||
('onfail',
|
||||
[OrderedDict([('cmd', 'test_ivstate0')])])
|
||||
]),
|
||||
'run',
|
||||
{'order': 10004}]},
|
||||
}
|
||||
),
|
||||
'test_onfail': (
|
||||
OrderedDict([
|
||||
('host1',
|
||||
OrderedDict([
|
||||
('test_state0', {'result': False}),
|
||||
('test_state', {'result': True}),
|
||||
])),
|
||||
]),
|
||||
None
|
||||
),
|
||||
'test_onfail_d': (
|
||||
OrderedDict([
|
||||
('host1',
|
||||
OrderedDict([
|
||||
('test_state0', {'result': False}),
|
||||
('test_state', {'result': True}),
|
||||
])),
|
||||
]),
|
||||
{}
|
||||
)
|
||||
}
|
||||
for test, testdata in six.iteritems(test_invalid_true_ht_states):
|
||||
data, ht = testdata
|
||||
for t_ in [a for a in data['host1']]:
|
||||
tdata = data['host1'][t_]
|
||||
if '_|-' in t_:
|
||||
t_ = t_.split('_|-')[1]
|
||||
tdata['__id__'] = t_
|
||||
self.assertFalse(
|
||||
utils.check_state_result(data, highstate=ht),
|
||||
msg='{0} failed'.format(test))
|
||||
|
||||
test_valid_true_ht_states = {
|
||||
'test_onfail_integ': (
|
||||
OrderedDict([
|
||||
('host1',
|
||||
OrderedDict([
|
||||
('cmd_|-test_ivstate0_|-echo_|-run', {
|
||||
'result': False}),
|
||||
('cmd_|-test_ivstate1_|-echo_|-run', {
|
||||
'result': True}),
|
||||
])),
|
||||
]),
|
||||
{
|
||||
'test_ivstate0': {
|
||||
'__env__': 'base',
|
||||
'__sls__': u'a',
|
||||
'cmd': [OrderedDict([('name', '/bin/true')]),
|
||||
'run',
|
||||
{'order': 10002}]},
|
||||
'test_ivstate1': {
|
||||
'__env__': 'base',
|
||||
'__sls__': u'a',
|
||||
'cmd': [OrderedDict([('name', '/bin/true')]),
|
||||
OrderedDict([
|
||||
('onfail',
|
||||
[OrderedDict([('cmd', 'test_ivstate0')])])
|
||||
]),
|
||||
'run',
|
||||
{'order': 10004}]},
|
||||
}
|
||||
),
|
||||
'test_onfail_intega3': (
|
||||
OrderedDict([
|
||||
('host1',
|
||||
OrderedDict([
|
||||
('t_|-test_ivstate0_|-echo_|-run', {
|
||||
'result': True}),
|
||||
('cmd_|-test_ivstate0_|-echo_|-run', {
|
||||
'result': False}),
|
||||
('cmd_|-test_ivstate1_|-echo_|-run', {
|
||||
'result': True}),
|
||||
])),
|
||||
]),
|
||||
{
|
||||
'test_ivstate0': {
|
||||
'__env__': 'base',
|
||||
'__sls__': u'a',
|
||||
'cmd': [OrderedDict([('name', '/bin/true')]),
|
||||
'run',
|
||||
{'order': 10002}],
|
||||
't': [OrderedDict([('name', '/bin/true')]),
|
||||
'run',
|
||||
{'order': 10002}]},
|
||||
'test_ivstate1': {
|
||||
'__env__': 'base',
|
||||
'__sls__': u'a',
|
||||
'cmd': [OrderedDict([('name', '/bin/true')]),
|
||||
OrderedDict([
|
||||
('onfail',
|
||||
[OrderedDict([('cmd', 'test_ivstate0')])])
|
||||
]),
|
||||
'run',
|
||||
{'order': 10004}]},
|
||||
}
|
||||
),
|
||||
'test_onfail_simple': (
|
||||
OrderedDict([
|
||||
('host1',
|
||||
OrderedDict([
|
||||
('test_vstate0', {'result': False}),
|
||||
('test_vstate1', {'result': True}),
|
||||
])),
|
||||
]),
|
||||
{
|
||||
'test_vstate0': {
|
||||
'__env__': 'base',
|
||||
'__sls__': u'a',
|
||||
'cmd': [OrderedDict([('name', '/bin/true')]),
|
||||
'run',
|
||||
{'order': 10002}]},
|
||||
'test_vstate1': {
|
||||
'__env__': 'base',
|
||||
'__sls__': u'a',
|
||||
'cmd': [OrderedDict([('name', '/bin/true')]),
|
||||
OrderedDict([
|
||||
('onfail',
|
||||
[OrderedDict([('cmd', 'test_vstate0')])])
|
||||
]),
|
||||
'run',
|
||||
{'order': 10004}]},
|
||||
}
|
||||
)
|
||||
}
|
||||
for test, testdata in six.iteritems(test_valid_true_ht_states):
|
||||
data, ht = testdata
|
||||
for t_ in [a for a in data['host1']]:
|
||||
tdata = data['host1'][t_]
|
||||
if '_|-' in t_:
|
||||
t_ = t_.split('_|-')[1]
|
||||
tdata['__id__'] = t_
|
||||
self.assertTrue(
|
||||
utils.check_state_result(data, highstate=ht),
|
||||
msg='{0} failed'.format(test))
|
||||
test_valid_false_state = {'host1': {'test_state': {'result': False}}}
|
||||
self.assertFalse(utils.check_state_result(test_valid_false_state))
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user