salt.states.service: detect that service failed to start/stop

This commit is contained in:
Denys Havrysh 2016-09-27 17:41:53 +03:00
parent 57ec792f6b
commit c4f899b3b3
2 changed files with 133 additions and 47 deletions

View File

@ -62,6 +62,9 @@ service, then set the reload value to True:
from __future__ import absolute_import from __future__ import absolute_import
import time import time
# Import Salt libs
from salt.exceptions import CommandExecutionError
__virtualname__ = 'service' __virtualname__ = 'service'
@ -98,9 +101,17 @@ def _enable(name, started, result=True, **kwargs):
ret = {} ret = {}
# is service available? # is service available?
if not _available(name, ret): try:
if not _available(name, ret):
return ret
except CommandExecutionError as exc:
ret['result'] = False
ret['comment'] = exc.strerror
return ret return ret
# Set default expected result
ret['result'] = result
# Check to see if this minion supports enable # Check to see if this minion supports enable
if 'service.enable' not in __salt__ or 'service.enabled' not in __salt__: if 'service.enable' not in __salt__ or 'service.enabled' not in __salt__:
if started is True: if started is True:
@ -139,10 +150,9 @@ def _enable(name, started, result=True, **kwargs):
return ret return ret
if __salt__['service.enable'](name, **kwargs): if __salt__['service.enable'](name, **kwargs):
after_toggle_enable_status = __salt__['service.enabled'](name,
**kwargs)
# Service has been enabled # Service has been enabled
ret['changes'] = {} ret['changes'] = {}
after_toggle_enable_status = __salt__['service.enabled'](name, **kwargs)
# on upstart, certain services like apparmor will always return # on upstart, certain services like apparmor will always return
# False, even if correctly activated # False, even if correctly activated
# do not trigger a change # do not trigger a change
@ -160,16 +170,14 @@ def _enable(name, started, result=True, **kwargs):
return ret return ret
# Service failed to be enabled # Service failed to be enabled
ret['result'] = False
if started is True: if started is True:
ret['result'] = False
ret['comment'] = ('Failed when setting service {0} to start at boot,' ret['comment'] = ('Failed when setting service {0} to start at boot,'
' but the service is running').format(name) ' but the service is running').format(name)
elif started is None: elif started is None:
ret['result'] = False
ret['comment'] = ('Failed when setting service {0} to start at boot,' ret['comment'] = ('Failed when setting service {0} to start at boot,'
' but the service was already running').format(name) ' but the service was already running').format(name)
else: else:
ret['result'] = False
ret['comment'] = ('Failed when setting service {0} to start at boot,' ret['comment'] = ('Failed when setting service {0} to start at boot,'
' and the service is dead').format(name) ' and the service is dead').format(name)
return ret return ret
@ -182,10 +190,18 @@ def _disable(name, started, result=True, **kwargs):
ret = {} ret = {}
# is service available? # is service available?
if not _available(name, ret): try:
ret['result'] = True if not _available(name, ret):
ret['result'] = True
return ret
except CommandExecutionError as exc:
ret['result'] = False
ret['comment'] = exc.strerror
return ret return ret
# Set default expected result
ret['result'] = result
# is enable/disable available? # is enable/disable available?
if 'service.disable' not in __salt__ or 'service.disabled' not in __salt__: if 'service.disable' not in __salt__ or 'service.disabled' not in __salt__:
if started is True: if started is True:
@ -200,8 +216,8 @@ def _disable(name, started, result=True, **kwargs):
' service {0} is dead').format(name) ' service {0} is dead').format(name)
return ret return ret
before_toggle_disable_status = __salt__['service.disabled'](name)
# Service can be disabled # Service can be disabled
before_toggle_disable_status = __salt__['service.disabled'](name)
if before_toggle_disable_status: if before_toggle_disable_status:
# Service is disabled # Service is disabled
if started is True: if started is True:
@ -227,8 +243,6 @@ def _disable(name, started, result=True, **kwargs):
# Service has been disabled # Service has been disabled
ret['changes'] = {} ret['changes'] = {}
after_toggle_disable_status = __salt__['service.disabled'](name) after_toggle_disable_status = __salt__['service.disabled'](name)
# Service has been disabled
ret['changes'] = {}
# on upstart, certain services like apparmor will always return # on upstart, certain services like apparmor will always return
# False, even if correctly activated # False, even if correctly activated
# do not trigger a change # do not trigger a change
@ -254,7 +268,6 @@ def _disable(name, started, result=True, **kwargs):
ret['comment'] = ('Failed when setting service {0} to not start' ret['comment'] = ('Failed when setting service {0} to not start'
' at boot, but the service was already running' ' at boot, but the service was already running'
).format(name) ).format(name)
return ret
else: else:
ret['comment'] = ('Failed when setting service {0} to not start' ret['comment'] = ('Failed when setting service {0} to not start'
' at boot, and the service is dead').format(name) ' at boot, and the service is dead').format(name)
@ -315,13 +328,21 @@ def running(name, enable=None, sig=None, init_delay=None, **kwargs):
return _enabled_used_error(ret) return _enabled_used_error(ret)
# Check if the service is available # Check if the service is available
if not _available(name, ret): try:
if not _available(name, ret):
return ret
except CommandExecutionError as exc:
ret['result'] = False
ret['comment'] = exc.strerror
return ret return ret
# lot of custom init script won't or mis implement the status # lot of custom init script won't or mis implement the status
# command, so it is just an indicator but can not be fully trusted # command, so it is just an indicator but can not be fully trusted
before_toggle_status = __salt__['service.status'](name, sig) before_toggle_status = __salt__['service.status'](name, sig)
before_toggle_enable_status = __salt__['service.enabled'](name) if 'service.enabled' in __salt__:
before_toggle_enable_status = __salt__['service.enabled'](name)
else:
before_toggle_enable_status = True
# See if the service is already running # See if the service is already running
if before_toggle_status: if before_toggle_status:
@ -347,28 +368,39 @@ def running(name, enable=None, sig=None, init_delay=None, **kwargs):
ret.update(_enable(name, False, result=False, **kwargs)) ret.update(_enable(name, False, result=False, **kwargs))
elif enable is False: elif enable is False:
ret.update(_disable(name, False, result=False, **kwargs)) ret.update(_disable(name, False, result=False, **kwargs))
else: return ret
ret['comment'] = 'Started Service {0}'.format(name)
if enable is True:
ret.update(_enable(name, True, **kwargs))
elif enable is False:
ret.update(_disable(name, True, **kwargs))
# only force a change state if we have explicitly detected them
after_toggle_status = __salt__['service.status'](name)
after_toggle_enable_status = __salt__['service.enabled'](name)
if (
(before_toggle_enable_status != after_toggle_enable_status) or
(before_toggle_status != after_toggle_status)
) and not ret.get('changes', {}):
ret['changes'][name] = func_ret
if init_delay: if init_delay:
time.sleep(init_delay) time.sleep(init_delay)
# only force a change state if we have explicitly detected them
after_toggle_status = __salt__['service.status'](name)
if 'service.enabled' in __salt__:
after_toggle_enable_status = __salt__['service.enabled'](name)
else:
after_toggle_enable_status = True
if (
(before_toggle_enable_status != after_toggle_enable_status) or
(before_toggle_status != after_toggle_status)
) and not ret.get('changes', {}):
ret['changes'][name] = after_toggle_status
if after_toggle_status:
ret['comment'] = 'Started Service {0}'.format(name)
else:
ret['comment'] = 'Service {0} failed to start'.format(name)
if enable is True:
ret.update(_enable(name, after_toggle_status, result=after_toggle_status, **kwargs))
elif enable is False:
ret.update(_disable(name, after_toggle_status, result=after_toggle_status, **kwargs))
if init_delay:
ret['comment'] = ( ret['comment'] = (
'{0}\nDelayed return for {1} seconds' '{0}\nDelayed return for {1} seconds'
.format(ret['comment'], init_delay) .format(ret['comment'], init_delay)
) )
return ret return ret
@ -397,14 +429,23 @@ def dead(name, enable=None, sig=None, **kwargs):
return _enabled_used_error(ret) return _enabled_used_error(ret)
# Check if the service is available # Check if the service is available
if not _available(name, ret): try:
ret['result'] = True if not _available(name, ret):
return ret
except CommandExecutionError as exc:
ret['result'] = False
ret['comment'] = exc.strerror
return ret return ret
# lot of custom init script won't or mis implement the status # lot of custom init script won't or mis implement the status
# command, so it is just an indicator but can not be fully trusted # command, so it is just an indicator but can not be fully trusted
before_toggle_status = __salt__['service.status'](name, sig) before_toggle_status = __salt__['service.status'](name, sig)
before_toggle_enable_status = __salt__['service.enabled'](name) if 'service.enabled' in __salt__:
before_toggle_enable_status = __salt__['service.enabled'](name)
else:
before_toggle_enable_status = True
# See if the service is already dead
if not before_toggle_status: if not before_toggle_status:
ret['comment'] = 'The service {0} is already dead'.format(name) ret['comment'] = 'The service {0} is already dead'.format(name)
if enable is True and not before_toggle_enable_status: if enable is True and not before_toggle_enable_status:
@ -413,12 +454,12 @@ def dead(name, enable=None, sig=None, **kwargs):
ret.update(_disable(name, None, **kwargs)) ret.update(_disable(name, None, **kwargs))
return ret return ret
# Run the tests
if __opts__['test']: if __opts__['test']:
ret['result'] = None ret['result'] = None
ret['comment'] = 'Service {0} is set to be killed'.format(name) ret['comment'] = 'Service {0} is set to be killed'.format(name)
return ret return ret
# be sure to stop, in case we mis detected in the check
func_ret = __salt__['service.stop'](name) func_ret = __salt__['service.stop'](name)
if not func_ret: if not func_ret:
ret['result'] = False ret['result'] = False
@ -427,20 +468,32 @@ def dead(name, enable=None, sig=None, **kwargs):
ret.update(_enable(name, True, result=False, **kwargs)) ret.update(_enable(name, True, result=False, **kwargs))
elif enable is False: elif enable is False:
ret.update(_disable(name, True, result=False, **kwargs)) ret.update(_disable(name, True, result=False, **kwargs))
else: return ret
ret['comment'] = 'Service {0} was killed'.format(name)
if enable is True:
ret.update(_enable(name, False, **kwargs))
elif enable is False:
ret.update(_disable(name, False, **kwargs))
# only force a change state if we have explicitly detected them # only force a change state if we have explicitly detected them
after_toggle_status = __salt__['service.status'](name) after_toggle_status = __salt__['service.status'](name)
after_toggle_enable_status = __salt__['service.enabled'](name) if 'service.enabled' in __salt__:
after_toggle_enable_status = __salt__['service.enabled'](name)
else:
after_toggle_enable_status = True
if ( if (
(before_toggle_enable_status != after_toggle_enable_status) or (before_toggle_enable_status != after_toggle_enable_status) or
(before_toggle_status != after_toggle_status) (before_toggle_status != after_toggle_status)
) and not ret.get('changes', {}): ) and not ret.get('changes', {}):
ret['changes'][name] = func_ret ret['changes'][name] = after_toggle_status
# be sure to stop, in case we mis detected in the check
if after_toggle_status:
ret['result'] = False
ret['comment'] = 'Service {0} failed to die'.format(name)
else:
ret['comment'] = 'Service {0} was killed'.format(name)
if enable is True:
ret.update(_enable(name, after_toggle_status, result=not after_toggle_status, **kwargs))
elif enable is False:
ret.update(_disable(name, after_toggle_status, result=not after_toggle_status, **kwargs))
return ret return ret
@ -502,6 +555,19 @@ def mod_watch(name,
sig sig
The string to search for when looking for the service process with ps The string to search for when looking for the service process with ps
reload
Use reload instead of the default restart (exclusive option with full_restart,
defaults to reload if both are used)
full_restart
Use service.full_restart instead of restart (exclusive option with reload)
force
Use service.force_reload instead of reload (needs reload to be set to True)
init_delay
Add a sleep command (in seconds) before the service is restarted/reloaded
''' '''
ret = {'name': name, ret = {'name': name,
'changes': {}, 'changes': {},

View File

@ -58,7 +58,10 @@ class ServiceTestCase(TestCase):
'result': True}, 'result': True},
{'changes': {}, {'changes': {},
'comment': 'The service salt is already running', 'comment': 'The service salt is already running',
'name': 'salt', 'result': True}] 'name': 'salt', 'result': True},
{'changes': 'saltstack',
'comment': 'Service salt failed to start', 'name': 'salt',
'result': True}]
tmock = MagicMock(return_value=True) tmock = MagicMock(return_value=True)
fmock = MagicMock(return_value=False) fmock = MagicMock(return_value=False)
@ -134,6 +137,22 @@ class ServiceTestCase(TestCase):
): ):
self.assertDictEqual(service.running("salt", True), ret[4]) self.assertDictEqual(service.running("salt", True), ret[4])
with contextlib.nested(
patch.dict(service.__opts__, {'test': False}),
patch.dict(
service.__salt__, {
'service.status':
MagicMock(side_effect=[False, False]),
'service.enabled':
MagicMock(side_effect=[True, True]),
'service.start':
MagicMock(return_value="stack")}),
patch.object(
service, '_enable',
MagicMock(return_value={'changes': 'saltstack'}))
):
self.assertDictEqual(service.running("salt", True), ret[6])
def test_dead(self): def test_dead(self):
''' '''
Test to ensure that the named service is dead Test to ensure that the named service is dead
@ -149,8 +168,8 @@ class ServiceTestCase(TestCase):
'comment': 'Service salt was killed', 'name': 'salt', 'comment': 'Service salt was killed', 'name': 'salt',
'result': True}, 'result': True},
{'changes': {}, {'changes': {},
'comment': 'Service salt was killed', 'name': 'salt', 'comment': 'Service salt failed to die', 'name': 'salt',
'result': True}, 'result': False},
{'changes': 'saltstack', {'changes': 'saltstack',
'comment': 'The service salt is already dead', 'name': 'salt', 'comment': 'The service salt is already dead', 'name': 'salt',
'result': True}] 'result': True}]
@ -176,6 +195,7 @@ class ServiceTestCase(TestCase):
patch.object(service, '_enable', mock) patch.object(service, '_enable', mock)
): ):
self.assertDictEqual(service.dead("salt", True), ret[5]) self.assertDictEqual(service.dead("salt", True), ret[5])
with contextlib.nested( with contextlib.nested(
patch.dict(service.__opts__, {'test': False}), patch.dict(service.__opts__, {'test': False}),
patch.dict( patch.dict(
@ -203,7 +223,7 @@ class ServiceTestCase(TestCase):
{'service.enabled': {'service.enabled':
MagicMock(side_effect=[True, True, False]), MagicMock(side_effect=[True, True, False]),
'service.status': 'service.status':
MagicMock(side_effect=[True, True, False]), MagicMock(side_effect=[True, False, False]),
'service.stop': MagicMock(return_value="stack")}), 'service.stop': MagicMock(return_value="stack")}),
patch.object( patch.object(
service, '_enable', service, '_enable',