salt/tests/unit/test_daemons.py
Erik Johnson 2926834423
Improve robustness of daemons unit tests
https://github.com/saltstack/salt/pull/45583 was opened to fix some test
failures in the daemons tests. However, with the way that the tests were
written, `_multiproc_exec_test` was waiting for a response from the
child function, which would never come due to the error raised during
the test. This means that any exception raised during the test would
stop the test suite dead in its tracks, causing it to eventually end in
a timeout.

This commit addresses this design flaw by wrapping the test logic in a
try/except, and ensuring that we always return a response from the child
function. The tests will instead log the exception and gracefully fail
the test, allowing whomever troubleshoots the failures to consult the
salt-runtests.log to see the traceback.
2018-01-20 22:49:02 -06:00

270 lines
7.9 KiB
Python

# -*- coding: utf-8 -*-
'''
:codeauthor: :email:`Bo Maryniuk <bo@suse.de>`
'''
# Import python libs
from __future__ import absolute_import, print_function, unicode_literals
import logging
import multiprocessing
# Import Salt Testing libs
from tests.support.unit import TestCase, skipIf
from tests.support.mock import patch, MagicMock, NO_MOCK, NO_MOCK_REASON
from tests.support.mixins import SaltClientTestCaseMixin
# Import Salt libs
import salt.cli.daemons as daemons
log = logging.getLogger(__name__)
class LoggerMock(object):
'''
Logger data collector
'''
def __init__(self):
'''
init
:return:
'''
self.reset()
def reset(self):
'''
Reset values
:return:
'''
self.messages = []
def info(self, message, *args, **kwargs):
'''
Collects the data from the logger of info type.
:param data:
:return:
'''
self.messages.append({'message': message, 'args': args,
'kwargs': kwargs, 'type': 'info'})
def warning(self, message, *args, **kwargs):
'''
Collects the data from the logger of warning type.
:param data:
:return:
'''
self.messages.append({'message': message, 'args': args,
'kwargs': kwargs, 'type': 'warning'})
def has_message(self, msg, log_type=None):
'''
Check if log has message.
:param data:
:return:
'''
for data in self.messages:
log_str = data['message'] % data['args'] # pylint: disable=incompatible-py3-code
if (data['type'] == log_type or not log_type) and log_str.find(msg) > -1:
return True
return False
def _master_exec_test(child_pipe):
def _create_master():
'''
Create master instance
:return:
'''
obj = daemons.Master()
obj.config = {'user': 'dummy', 'hash_type': alg}
for attr in ['start_log_info', 'prepare', 'shutdown', 'master']:
setattr(obj, attr, MagicMock())
return obj
_logger = LoggerMock()
ret = True
try:
with patch('salt.cli.daemons.check_user', MagicMock(return_value=True)):
with patch('salt.cli.daemons.log', _logger):
for alg in ['md5', 'sha1']:
_create_master().start()
ret = ret and _logger.has_message(
'Do not use {alg}'.format(alg=alg),
log_type='warning')
_logger.reset()
for alg in ['sha224', 'sha256', 'sha384', 'sha512']:
_create_master().start()
ret = ret and _logger.messages \
and not _logger.has_message('Do not use ')
except Exception:
log.exception('Exception raised in master daemon unit test')
ret = False
child_pipe.send(ret)
child_pipe.close()
def _minion_exec_test(child_pipe):
def _create_minion():
'''
Create minion instance
:return:
'''
obj = daemons.Minion()
obj.config = {'user': 'dummy', 'hash_type': alg}
for attr in ['start_log_info', 'prepare', 'shutdown']:
setattr(obj, attr, MagicMock())
setattr(obj, 'minion', MagicMock(restart=False))
return obj
ret = True
try:
_logger = LoggerMock()
with patch('salt.cli.daemons.check_user', MagicMock(return_value=True)):
with patch('salt.cli.daemons.log', _logger):
for alg in ['md5', 'sha1']:
_create_minion().start()
ret = ret and _logger.has_message(
'Do not use {alg}'.format(alg=alg),
log_type='warning')
_logger.reset()
for alg in ['sha224', 'sha256', 'sha384', 'sha512']:
_create_minion().start()
ret = ret and _logger.messages \
and not _logger.has_message('Do not use ')
except Exception:
log.exception('Exception raised in minion daemon unit test')
ret = False
child_pipe.send(ret)
child_pipe.close()
def _proxy_exec_test(child_pipe):
def _create_proxy_minion():
'''
Create proxy minion instance
:return:
'''
obj = daemons.ProxyMinion()
obj.config = {'user': 'dummy', 'hash_type': alg}
for attr in ['minion', 'start_log_info', 'prepare', 'shutdown', 'tune_in']:
setattr(obj, attr, MagicMock())
obj.minion.restart = False
return obj
ret = True
try:
_logger = LoggerMock()
with patch('salt.cli.daemons.check_user', MagicMock(return_value=True)):
with patch('salt.cli.daemons.log', _logger):
for alg in ['md5', 'sha1']:
_create_proxy_minion().start()
ret = ret and _logger.has_message(
'Do not use {alg}'.format(alg=alg),
log_type='warning')
_logger.reset()
for alg in ['sha224', 'sha256', 'sha384', 'sha512']:
_create_proxy_minion().start()
ret = ret and _logger.messages \
and not _logger.has_message('Do not use ')
except Exception:
log.exception('Exception raised in proxy daemon unit test')
ret = False
child_pipe.send(ret)
child_pipe.close()
def _syndic_exec_test(child_pipe):
def _create_syndic():
'''
Create syndic instance
:return:
'''
obj = daemons.Syndic()
obj.config = {'user': 'dummy', 'hash_type': alg}
for attr in ['syndic', 'start_log_info', 'prepare', 'shutdown']:
setattr(obj, attr, MagicMock())
return obj
ret = True
try:
_logger = LoggerMock()
with patch('salt.cli.daemons.check_user', MagicMock(return_value=True)):
with patch('salt.cli.daemons.log', _logger):
for alg in ['md5', 'sha1']:
_create_syndic().start()
ret = ret and _logger.has_message(
'Do not use {alg}'.format(alg=alg),
log_type='warning')
_logger.reset()
for alg in ['sha224', 'sha256', 'sha384', 'sha512']:
_create_syndic().start()
ret = ret and _logger.messages \
and not _logger.has_message('Do not use ')
except Exception:
log.exception('Exception raised in syndic daemon unit test')
ret = False
child_pipe.send(ret)
child_pipe.close()
@skipIf(NO_MOCK, NO_MOCK_REASON)
class DaemonsStarterTestCase(TestCase, SaltClientTestCaseMixin):
'''
Unit test for the daemons starter classes.
'''
def _multiproc_exec_test(self, exec_test):
m_parent, m_child = multiprocessing.Pipe()
p_ = multiprocessing.Process(target=exec_test, args=(m_child,))
p_.start()
self.assertTrue(m_parent.recv())
p_.join()
def test_master_daemon_hash_type_verified(self):
'''
Verify if Master is verifying hash_type config option.
:return:
'''
self._multiproc_exec_test(_master_exec_test)
def test_minion_daemon_hash_type_verified(self):
'''
Verify if Minion is verifying hash_type config option.
:return:
'''
self._multiproc_exec_test(_minion_exec_test)
def test_proxy_minion_daemon_hash_type_verified(self):
'''
Verify if ProxyMinion is verifying hash_type config option.
:return:
'''
self._multiproc_exec_test(_proxy_exec_test)
def test_syndic_daemon_hash_type_verified(self):
'''
Verify if Syndic is verifying hash_type config option.
:return:
'''
self._multiproc_exec_test(_syndic_exec_test)