mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Multimaster tests environment and test module test.
This commit is contained in:
parent
2c178b0f8d
commit
65dc47c300
14 changed files with 1040 additions and 22 deletions
|
@ -96,7 +96,7 @@ def get_unused_localhost_port():
|
|||
usock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
usock.bind(('127.0.0.1', 0))
|
||||
port = usock.getsockname()[1]
|
||||
if port in (54505, 54506, 64505, 64506, 64510, 64511, 64520, 64521):
|
||||
if port in (54505, 54506, 64505, 64506, 64507, 64508, 64510, 64511, 64520, 64521):
|
||||
# These ports are hardcoded in the test configuration
|
||||
port = get_unused_localhost_port()
|
||||
usock.close()
|
||||
|
|
0
tests/integration/files/conf/mm_master
Normal file
0
tests/integration/files/conf/mm_master
Normal file
3
tests/integration/files/conf/mm_minion
Normal file
3
tests/integration/files/conf/mm_minion
Normal file
|
@ -0,0 +1,3 @@
|
|||
master:
|
||||
- localhost:64506
|
||||
- localhost:64508
|
10
tests/integration/files/conf/mm_sub_master
Normal file
10
tests/integration/files/conf/mm_sub_master
Normal file
|
@ -0,0 +1,10 @@
|
|||
id: sub_master
|
||||
publish_port: 64507
|
||||
ret_port: 64508
|
||||
|
||||
# These settings needed for tests on Windows which defaults
|
||||
# to ipc_mode: tcp
|
||||
tcp_master_pub_port: 64512
|
||||
tcp_master_pull_port: 64513
|
||||
tcp_master_publish_pull: 64514
|
||||
tcp_master_workers: 64515
|
3
tests/integration/files/conf/mm_sub_minion
Normal file
3
tests/integration/files/conf/mm_sub_minion
Normal file
|
@ -0,0 +1,3 @@
|
|||
master:
|
||||
- localhost:64506
|
||||
- localhost:64508
|
637
tests/multimaster/__init__.py
Normal file
637
tests/multimaster/__init__.py
Normal file
|
@ -0,0 +1,637 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
'''
|
||||
Set up the Salt multimaster test suite
|
||||
'''
|
||||
|
||||
# Import Python libs
|
||||
import copy
|
||||
import os
|
||||
import stat
|
||||
import shutil
|
||||
import sys
|
||||
import time
|
||||
import threading
|
||||
|
||||
# Import salt tests support dirs
|
||||
from tests.support.paths import (
|
||||
ENGINES_DIR,
|
||||
FILES,
|
||||
INTEGRATION_TEST_DIR,
|
||||
LOG_HANDLERS_DIR,
|
||||
SCRIPT_DIR,
|
||||
TMP,
|
||||
)
|
||||
from tests.support.runtests import RUNTIME_VARS
|
||||
from tests.support.parser import PNUM, print_header
|
||||
from tests.support.processes import start_daemon
|
||||
|
||||
# Import Salt libs
|
||||
from tests.integration import (
|
||||
SALT_LOG_PORT,
|
||||
SocketServerRequestHandler,
|
||||
TestDaemon,
|
||||
ThreadedSocketServer,
|
||||
get_unused_localhost_port,
|
||||
)
|
||||
import salt.config
|
||||
import salt.log.setup as salt_log_setup
|
||||
from salt.utils.immutabletypes import freeze
|
||||
from salt.utils.verify import verify_env
|
||||
|
||||
# Import salt tests support libs
|
||||
from tests.support.processes import SaltMaster, SaltMinion
|
||||
|
||||
|
||||
class MultimasterTestDaemon(TestDaemon):
|
||||
'''
|
||||
Set up the master and minion daemons, and run related cases
|
||||
'''
|
||||
|
||||
def __enter__(self):
|
||||
'''
|
||||
Start a master and minion
|
||||
'''
|
||||
# Setup the multiprocessing logging queue listener
|
||||
salt_log_setup.setup_multiprocessing_logging_listener(
|
||||
self.mm_master_opts
|
||||
)
|
||||
|
||||
# Set up PATH to mockbin
|
||||
self._enter_mockbin()
|
||||
|
||||
self.master_targets = [self.mm_master_opts, self.mm_sub_master_opts]
|
||||
self.minion_targets = set(['minion', 'sub_minion'])
|
||||
|
||||
if self.parser.options.transport == 'zeromq':
|
||||
self.start_zeromq_daemons()
|
||||
elif self.parser.options.transport == 'raet':
|
||||
self.start_raet_daemons()
|
||||
elif self.parser.options.transport == 'tcp':
|
||||
self.start_tcp_daemons()
|
||||
|
||||
self.pre_setup_minions()
|
||||
self.setup_minions()
|
||||
|
||||
#if getattr(self.parser.options, 'ssh', False):
|
||||
#self.prep_ssh()
|
||||
|
||||
self.wait_for_minions(time.time(), self.MINIONS_CONNECT_TIMEOUT)
|
||||
|
||||
if self.parser.options.sysinfo:
|
||||
try:
|
||||
print_header(
|
||||
'~~~~~~~ Versions Report ', inline=True,
|
||||
width=getattr(self.parser.options, 'output_columns', PNUM)
|
||||
)
|
||||
except TypeError:
|
||||
print_header('~~~~~~~ Versions Report ', inline=True)
|
||||
|
||||
print('\n'.join(salt.version.versions_report()))
|
||||
|
||||
try:
|
||||
print_header(
|
||||
'~~~~~~~ Minion Grains Information ', inline=True,
|
||||
width=getattr(self.parser.options, 'output_columns', PNUM)
|
||||
)
|
||||
except TypeError:
|
||||
print_header('~~~~~~~ Minion Grains Information ', inline=True)
|
||||
|
||||
grains = self.client.cmd('minion', 'grains.items')
|
||||
|
||||
minion_opts = self.mm_minion_opts.copy()
|
||||
minion_opts['color'] = self.parser.options.no_colors is False
|
||||
salt.output.display_output(grains, 'grains', minion_opts)
|
||||
|
||||
try:
|
||||
print_header(
|
||||
'=', sep='=', inline=True,
|
||||
width=getattr(self.parser.options, 'output_columns', PNUM)
|
||||
)
|
||||
except TypeError:
|
||||
print_header('', sep='=', inline=True)
|
||||
|
||||
try:
|
||||
return self
|
||||
finally:
|
||||
self.post_setup_minions()
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
'''
|
||||
Kill the minion and master processes
|
||||
'''
|
||||
self.sub_minion_process.terminate()
|
||||
self.minion_process.terminate()
|
||||
self.sub_master_process.terminate()
|
||||
self.master_process.terminate()
|
||||
self.log_server.server_close()
|
||||
self.log_server.shutdown()
|
||||
self._exit_mockbin()
|
||||
self._exit_ssh()
|
||||
self.log_server_process.join()
|
||||
# Shutdown the multiprocessing logging queue listener
|
||||
salt_log_setup.shutdown_multiprocessing_logging()
|
||||
salt_log_setup.shutdown_multiprocessing_logging_listener(daemonizing=True)
|
||||
|
||||
def start_zeromq_daemons(self):
|
||||
'''
|
||||
Fire up the daemons used for zeromq tests
|
||||
'''
|
||||
self.log_server = ThreadedSocketServer(('localhost', SALT_LOG_PORT), SocketServerRequestHandler)
|
||||
self.log_server_process = threading.Thread(target=self.log_server.serve_forever)
|
||||
self.log_server_process.daemon = True
|
||||
self.log_server_process.start()
|
||||
try:
|
||||
sys.stdout.write(
|
||||
' * {LIGHT_YELLOW}Starting salt-master ... {ENDC}'.format(**self.colors)
|
||||
)
|
||||
sys.stdout.flush()
|
||||
self.master_process = start_daemon(
|
||||
daemon_name='salt-master',
|
||||
daemon_id=self.mm_master_opts['id'],
|
||||
daemon_log_prefix='salt-master/{}'.format(self.mm_master_opts['id']),
|
||||
daemon_cli_script_name='master',
|
||||
daemon_config=self.mm_master_opts,
|
||||
daemon_config_dir=RUNTIME_VARS.TMP_MM_CONF_DIR,
|
||||
daemon_class=SaltMaster,
|
||||
bin_dir_path=SCRIPT_DIR,
|
||||
fail_hard=True,
|
||||
start_timeout=60)
|
||||
sys.stdout.write(
|
||||
'\r{0}\r'.format(
|
||||
' ' * getattr(self.parser.options, 'output_columns', PNUM)
|
||||
)
|
||||
)
|
||||
sys.stdout.write(
|
||||
' * {LIGHT_GREEN}Starting salt-master ... STARTED!\n{ENDC}'.format(**self.colors)
|
||||
)
|
||||
sys.stdout.flush()
|
||||
except (RuntimeWarning, RuntimeError):
|
||||
sys.stdout.write(
|
||||
'\r{0}\r'.format(
|
||||
' ' * getattr(self.parser.options, 'output_columns', PNUM)
|
||||
)
|
||||
)
|
||||
sys.stdout.write(
|
||||
' * {LIGHT_RED}Starting salt-master ... FAILED!\n{ENDC}'.format(**self.colors)
|
||||
)
|
||||
sys.stdout.flush()
|
||||
|
||||
# Clone the master key to sub-master's pki dir
|
||||
for keyfile in ('master.pem', 'master.pub'):
|
||||
shutil.copyfile(
|
||||
os.path.join(self.mm_master_opts['pki_dir'], keyfile),
|
||||
os.path.join(self.mm_sub_master_opts['pki_dir'], keyfile)
|
||||
)
|
||||
|
||||
try:
|
||||
sys.stdout.write(
|
||||
' * {LIGHT_YELLOW}Starting second salt-master ... {ENDC}'.format(**self.colors)
|
||||
)
|
||||
sys.stdout.flush()
|
||||
self.sub_master_process = start_daemon(
|
||||
daemon_name='sub salt-master',
|
||||
daemon_id=self.mm_master_opts['id'],
|
||||
daemon_log_prefix='sub-salt-master/{}'.format(self.mm_sub_master_opts['id']),
|
||||
daemon_cli_script_name='master',
|
||||
daemon_config=self.mm_sub_master_opts,
|
||||
daemon_config_dir=RUNTIME_VARS.TMP_MM_SUB_CONF_DIR,
|
||||
daemon_class=SaltMaster,
|
||||
bin_dir_path=SCRIPT_DIR,
|
||||
fail_hard=True,
|
||||
start_timeout=60)
|
||||
sys.stdout.write(
|
||||
'\r{0}\r'.format(
|
||||
' ' * getattr(self.parser.options, 'output_columns', PNUM)
|
||||
)
|
||||
)
|
||||
sys.stdout.write(
|
||||
' * {LIGHT_GREEN}Starting second salt-master ... STARTED!\n{ENDC}'.format(**self.colors)
|
||||
)
|
||||
sys.stdout.flush()
|
||||
except (RuntimeWarning, RuntimeError):
|
||||
sys.stdout.write(
|
||||
'\r{0}\r'.format(
|
||||
' ' * getattr(self.parser.options, 'output_columns', PNUM)
|
||||
)
|
||||
)
|
||||
sys.stdout.write(
|
||||
' * {LIGHT_RED}Starting second salt-master ... FAILED!\n{ENDC}'.format(**self.colors)
|
||||
)
|
||||
sys.stdout.flush()
|
||||
|
||||
try:
|
||||
sys.stdout.write(
|
||||
' * {LIGHT_YELLOW}Starting salt-minion ... {ENDC}'.format(**self.colors)
|
||||
)
|
||||
sys.stdout.flush()
|
||||
self.minion_process = start_daemon(
|
||||
daemon_name='salt-minion',
|
||||
daemon_id=self.mm_master_opts['id'],
|
||||
daemon_log_prefix='salt-minion/{}'.format(self.mm_minion_opts['id']),
|
||||
daemon_cli_script_name='minion',
|
||||
daemon_config=self.mm_minion_opts,
|
||||
daemon_config_dir=RUNTIME_VARS.TMP_MM_CONF_DIR,
|
||||
daemon_class=SaltMinion,
|
||||
bin_dir_path=SCRIPT_DIR,
|
||||
fail_hard=True,
|
||||
start_timeout=60)
|
||||
sys.stdout.write(
|
||||
'\r{0}\r'.format(
|
||||
' ' * getattr(self.parser.options, 'output_columns', PNUM)
|
||||
)
|
||||
)
|
||||
sys.stdout.write(
|
||||
' * {LIGHT_GREEN}Starting salt-minion ... STARTED!\n{ENDC}'.format(**self.colors)
|
||||
)
|
||||
sys.stdout.flush()
|
||||
except (RuntimeWarning, RuntimeError):
|
||||
sys.stdout.write(
|
||||
'\r{0}\r'.format(
|
||||
' ' * getattr(self.parser.options, 'output_columns', PNUM)
|
||||
)
|
||||
)
|
||||
sys.stdout.write(
|
||||
' * {LIGHT_RED}Starting salt-minion ... FAILED!\n{ENDC}'.format(**self.colors)
|
||||
)
|
||||
sys.stdout.flush()
|
||||
|
||||
try:
|
||||
sys.stdout.write(
|
||||
' * {LIGHT_YELLOW}Starting sub salt-minion ... {ENDC}'.format(**self.colors)
|
||||
)
|
||||
sys.stdout.flush()
|
||||
self.sub_minion_process = start_daemon(
|
||||
daemon_name='sub salt-minion',
|
||||
daemon_id=self.mm_master_opts['id'],
|
||||
daemon_log_prefix='sub-salt-minion/{}'.format(self.mm_sub_minion_opts['id']),
|
||||
daemon_cli_script_name='minion',
|
||||
daemon_config=self.mm_sub_minion_opts,
|
||||
daemon_config_dir=RUNTIME_VARS.TMP_MM_SUB_CONF_DIR,
|
||||
daemon_class=SaltMinion,
|
||||
bin_dir_path=SCRIPT_DIR,
|
||||
fail_hard=True,
|
||||
start_timeout=60)
|
||||
sys.stdout.write(
|
||||
'\r{0}\r'.format(
|
||||
' ' * getattr(self.parser.options, 'output_columns', PNUM)
|
||||
)
|
||||
)
|
||||
sys.stdout.write(
|
||||
' * {LIGHT_GREEN}Starting sub salt-minion ... STARTED!\n{ENDC}'.format(**self.colors)
|
||||
)
|
||||
sys.stdout.flush()
|
||||
except (RuntimeWarning, RuntimeError):
|
||||
sys.stdout.write(
|
||||
'\r{0}\r'.format(
|
||||
' ' * getattr(self.parser.options, 'output_columns', PNUM)
|
||||
)
|
||||
)
|
||||
sys.stdout.write(
|
||||
' * {LIGHT_RED}Starting sub salt-minion ... FAILED!\n{ENDC}'.format(**self.colors)
|
||||
)
|
||||
sys.stdout.flush()
|
||||
|
||||
def wait_for_minions(self, start, timeout, sleep=5):
|
||||
'''
|
||||
Ensure all minions and masters (including sub-masters) are connected.
|
||||
'''
|
||||
success = [False] * len(self.master_targets)
|
||||
while True:
|
||||
for num, client in enumerate(self.clients):
|
||||
if success[num]:
|
||||
continue
|
||||
try:
|
||||
ret = self.client.run_job('*', 'test.ping')
|
||||
except salt.exceptions.SaltClientError:
|
||||
ret = None
|
||||
if ret and 'minions' not in ret:
|
||||
continue
|
||||
if ret and sorted(ret['minions']) == sorted(self.minion_targets):
|
||||
success[num] = True
|
||||
continue
|
||||
if all(success):
|
||||
break
|
||||
if time.time() - start >= timeout:
|
||||
raise RuntimeError("Ping Minions Failed")
|
||||
time.sleep(sleep)
|
||||
|
||||
@property
|
||||
def clients(self):
|
||||
'''
|
||||
Return a local client which will be used for example to ping and sync
|
||||
the test minions.
|
||||
|
||||
This client is defined as a class attribute because its creation needs
|
||||
to be deferred to a latter stage. If created it on `__enter__` like it
|
||||
previously was, it would not receive the master events.
|
||||
'''
|
||||
if 'runtime_clients' not in RUNTIME_VARS.RUNTIME_CONFIGS:
|
||||
clients = []
|
||||
for master_opts in self.master_targets:
|
||||
clients.append(salt.client.get_local_client(mopts=master_opts))
|
||||
RUNTIME_VARS.RUNTIME_CONFIGS['runtime_clients'] = clients
|
||||
return RUNTIME_VARS.RUNTIME_CONFIGS['runtime_clients']
|
||||
|
||||
@property
|
||||
def client(self):
|
||||
return self.clients[0]
|
||||
|
||||
@classmethod
|
||||
def transplant_configs(cls, transport='zeromq'):
|
||||
os.makedirs(RUNTIME_VARS.TMP_MM_CONF_DIR)
|
||||
os.makedirs(RUNTIME_VARS.TMP_MM_SUB_CONF_DIR)
|
||||
print(' * Transplanting multimaster configuration files to \'{0}\''.format(
|
||||
RUNTIME_VARS.TMP_CONF_DIR))
|
||||
tests_known_hosts_file = os.path.join(RUNTIME_VARS.TMP_CONF_DIR, 'salt_ssh_known_hosts')
|
||||
|
||||
# Primary master in multimaster environment
|
||||
master_opts = salt.config._read_conf_file(os.path.join(RUNTIME_VARS.CONF_DIR, 'master'))
|
||||
master_opts.update(salt.config._read_conf_file(os.path.join(RUNTIME_VARS.CONF_DIR,
|
||||
'mm_master')))
|
||||
master_opts['known_hosts_file'] = tests_known_hosts_file
|
||||
master_opts['cachedir'] = os.path.join(TMP, 'rootdir_multimaster', 'cache')
|
||||
master_opts['user'] = RUNTIME_VARS.RUNNING_TESTS_USER
|
||||
master_opts['config_dir'] = RUNTIME_VARS.TMP_MM_CONF_DIR
|
||||
master_opts['root_dir'] = os.path.join(TMP, 'rootdir-multimaster')
|
||||
master_opts['pki_dir'] = os.path.join(TMP, 'rootdir-multimaster', 'pki', 'master')
|
||||
file_tree = {
|
||||
'root_dir': os.path.join(FILES, 'pillar', 'base', 'file_tree'),
|
||||
'follow_dir_links': False,
|
||||
'keep_newline': True,
|
||||
}
|
||||
master_opts['ext_pillar'].append({'file_tree': file_tree})
|
||||
|
||||
# Secondary master in multimaster environment
|
||||
sub_master_opts = salt.config._read_conf_file(os.path.join(RUNTIME_VARS.CONF_DIR, 'master'))
|
||||
sub_master_opts.update(salt.config._read_conf_file(os.path.join(RUNTIME_VARS.CONF_DIR,
|
||||
'mm_sub_master')))
|
||||
sub_master_opts['known_hosts_file'] = tests_known_hosts_file
|
||||
sub_master_opts['cachedir'] = os.path.join(TMP, 'rootdir-sub-multimaster', 'cache')
|
||||
sub_master_opts['user'] = RUNTIME_VARS.RUNNING_TESTS_USER
|
||||
sub_master_opts['config_dir'] = RUNTIME_VARS.TMP_MM_SUB_CONF_DIR
|
||||
sub_master_opts['root_dir'] = os.path.join(TMP, 'rootdir-sub-multimaster')
|
||||
sub_master_opts['pki_dir'] = os.path.join(TMP, 'rootdir-sub-multimaster', 'pki', 'master')
|
||||
sub_master_opts['ext_pillar'].append({'file_tree': copy.deepcopy(file_tree)})
|
||||
|
||||
# Under windows we can't seem to properly create a virtualenv off of another
|
||||
# virtualenv, we can on linux but we will still point to the virtualenv binary
|
||||
# outside the virtualenv running the test suite, if that's the case.
|
||||
try:
|
||||
real_prefix = sys.real_prefix
|
||||
# The above attribute exists, this is a virtualenv
|
||||
if salt.utils.is_windows():
|
||||
virtualenv_binary = os.path.join(real_prefix, 'Scripts', 'virtualenv.exe')
|
||||
else:
|
||||
# We need to remove the virtualenv from PATH or we'll get the virtualenv binary
|
||||
# from within the virtualenv, we don't want that
|
||||
path = os.environ.get('PATH')
|
||||
if path is not None:
|
||||
path_items = path.split(os.pathsep)
|
||||
for item in path_items[:]:
|
||||
if item.startswith(sys.base_prefix):
|
||||
path_items.remove(item)
|
||||
os.environ['PATH'] = os.pathsep.join(path_items)
|
||||
virtualenv_binary = salt.utils.which('virtualenv')
|
||||
if path is not None:
|
||||
# Restore previous environ PATH
|
||||
os.environ['PATH'] = path
|
||||
if not virtualenv_binary.startswith(real_prefix):
|
||||
virtualenv_binary = None
|
||||
if virtualenv_binary and not os.path.exists(virtualenv_binary):
|
||||
# It doesn't exist?!
|
||||
virtualenv_binary = None
|
||||
except AttributeError:
|
||||
# We're not running inside a virtualenv
|
||||
virtualenv_binary = None
|
||||
|
||||
# This minion connects to both masters
|
||||
minion_opts = salt.config._read_conf_file(os.path.join(RUNTIME_VARS.CONF_DIR, 'minion'))
|
||||
minion_opts.update(salt.config._read_conf_file(os.path.join(RUNTIME_VARS.CONF_DIR,
|
||||
'mm_minion')))
|
||||
minion_opts['cachedir'] = os.path.join(TMP, 'rootdir-multimaster', 'cache')
|
||||
minion_opts['user'] = RUNTIME_VARS.RUNNING_TESTS_USER
|
||||
minion_opts['config_dir'] = RUNTIME_VARS.TMP_MM_CONF_DIR
|
||||
minion_opts['root_dir'] = os.path.join(TMP, 'rootdir-multimaster')
|
||||
minion_opts['pki_dir'] = os.path.join(TMP, 'rootdir-multimaster', 'pki', 'minion')
|
||||
minion_opts['hosts.file'] = os.path.join(TMP, 'rootdir', 'hosts')
|
||||
minion_opts['aliases.file'] = os.path.join(TMP, 'rootdir', 'aliases')
|
||||
if virtualenv_binary:
|
||||
minion_opts['venv_bin'] = virtualenv_binary
|
||||
|
||||
# This sub_minion also connects to both masters
|
||||
sub_minion_opts = salt.config._read_conf_file(os.path.join(RUNTIME_VARS.CONF_DIR, 'sub_minion'))
|
||||
sub_minion_opts.update(salt.config._read_conf_file(os.path.join(RUNTIME_VARS.CONF_DIR,
|
||||
'mm_sub_minion')))
|
||||
sub_minion_opts['cachedir'] = os.path.join(TMP, 'rootdir-sub-multimaster', 'cache')
|
||||
sub_minion_opts['user'] = RUNTIME_VARS.RUNNING_TESTS_USER
|
||||
sub_minion_opts['config_dir'] = RUNTIME_VARS.TMP_MM_SUB_CONF_DIR
|
||||
sub_minion_opts['root_dir'] = os.path.join(TMP, 'rootdir-sub-multimaster')
|
||||
sub_minion_opts['pki_dir'] = os.path.join(TMP, 'rootdir-sub-multimaster', 'pki', 'minion')
|
||||
sub_minion_opts['hosts.file'] = os.path.join(TMP, 'rootdir', 'hosts')
|
||||
sub_minion_opts['aliases.file'] = os.path.join(TMP, 'rootdir', 'aliases')
|
||||
if virtualenv_binary:
|
||||
sub_minion_opts['venv_bin'] = virtualenv_binary
|
||||
|
||||
if transport == 'raet':
|
||||
master_opts['transport'] = 'raet'
|
||||
master_opts['raet_port'] = 64506
|
||||
sub_master_opts['transport'] = 'raet'
|
||||
sub_master_opts['raet_port'] = 64556
|
||||
minion_opts['transport'] = 'raet'
|
||||
minion_opts['raet_port'] = 64510
|
||||
sub_minion_opts['transport'] = 'raet'
|
||||
sub_minion_opts['raet_port'] = 64520
|
||||
# syndic_master_opts['transport'] = 'raet'
|
||||
|
||||
if transport == 'tcp':
|
||||
master_opts['transport'] = 'tcp'
|
||||
sub_master_opts['transport'] = 'tcp'
|
||||
minion_opts['transport'] = 'tcp'
|
||||
sub_minion_opts['transport'] = 'tcp'
|
||||
|
||||
# Set up config options that require internal data
|
||||
master_opts['pillar_roots'] = sub_master_opts['pillar_roots'] = {
|
||||
'base': [
|
||||
RUNTIME_VARS.TMP_PILLAR_TREE,
|
||||
os.path.join(FILES, 'pillar', 'base'),
|
||||
]
|
||||
}
|
||||
minion_opts['pillar_roots'] = {
|
||||
'base': [
|
||||
RUNTIME_VARS.TMP_PILLAR_TREE,
|
||||
os.path.join(FILES, 'pillar', 'base'),
|
||||
]
|
||||
}
|
||||
master_opts['file_roots'] = sub_master_opts['file_roots'] = {
|
||||
'base': [
|
||||
os.path.join(FILES, 'file', 'base'),
|
||||
# Let's support runtime created files that can be used like:
|
||||
# salt://my-temp-file.txt
|
||||
RUNTIME_VARS.TMP_STATE_TREE
|
||||
],
|
||||
# Alternate root to test __env__ choices
|
||||
'prod': [
|
||||
os.path.join(FILES, 'file', 'prod'),
|
||||
RUNTIME_VARS.TMP_PRODENV_STATE_TREE
|
||||
]
|
||||
}
|
||||
minion_opts['file_roots'] = {
|
||||
'base': [
|
||||
os.path.join(FILES, 'file', 'base'),
|
||||
# Let's support runtime created files that can be used like:
|
||||
# salt://my-temp-file.txt
|
||||
RUNTIME_VARS.TMP_STATE_TREE
|
||||
],
|
||||
# Alternate root to test __env__ choices
|
||||
'prod': [
|
||||
os.path.join(FILES, 'file', 'prod'),
|
||||
RUNTIME_VARS.TMP_PRODENV_STATE_TREE
|
||||
]
|
||||
}
|
||||
master_opts.setdefault('reactor', []).append(
|
||||
{
|
||||
'salt/minion/*/start': [
|
||||
os.path.join(FILES, 'reactor-sync-minion.sls')
|
||||
],
|
||||
}
|
||||
)
|
||||
for opts_dict in (master_opts, sub_master_opts):
|
||||
if 'ext_pillar' not in opts_dict:
|
||||
opts_dict['ext_pillar'] = []
|
||||
if salt.utils.platform.is_windows():
|
||||
opts_dict['ext_pillar'].append(
|
||||
{'cmd_yaml': 'type {0}'.format(os.path.join(FILES, 'ext.yaml'))})
|
||||
else:
|
||||
opts_dict['ext_pillar'].append(
|
||||
{'cmd_yaml': 'cat {0}'.format(os.path.join(FILES, 'ext.yaml'))})
|
||||
|
||||
# all read, only owner write
|
||||
autosign_file_permissions = stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH | stat.S_IWUSR
|
||||
for opts_dict in (master_opts, sub_master_opts):
|
||||
# We need to copy the extension modules into the new master root_dir or
|
||||
# it will be prefixed by it
|
||||
new_extension_modules_path = os.path.join(opts_dict['root_dir'], 'extension_modules')
|
||||
if not os.path.exists(new_extension_modules_path):
|
||||
shutil.copytree(
|
||||
os.path.join(
|
||||
INTEGRATION_TEST_DIR, 'files', 'extension_modules'
|
||||
),
|
||||
new_extension_modules_path
|
||||
)
|
||||
opts_dict['extension_modules'] = os.path.join(opts_dict['root_dir'], 'extension_modules')
|
||||
|
||||
# Copy the autosign_file to the new master root_dir
|
||||
new_autosign_file_path = os.path.join(opts_dict['root_dir'], 'autosign_file')
|
||||
shutil.copyfile(
|
||||
os.path.join(INTEGRATION_TEST_DIR, 'files', 'autosign_file'),
|
||||
new_autosign_file_path
|
||||
)
|
||||
os.chmod(new_autosign_file_path, autosign_file_permissions)
|
||||
|
||||
# Point the config values to the correct temporary paths
|
||||
for name in ('hosts', 'aliases'):
|
||||
optname = '{0}.file'.format(name)
|
||||
optname_path = os.path.join(TMP, name)
|
||||
master_opts[optname] = optname_path
|
||||
sub_master_opts[optname] = optname_path
|
||||
minion_opts[optname] = optname_path
|
||||
sub_minion_opts[optname] = optname_path
|
||||
|
||||
master_opts['runtests_conn_check_port'] = get_unused_localhost_port()
|
||||
sub_master_opts['runtests_conn_check_port'] = get_unused_localhost_port()
|
||||
minion_opts['runtests_conn_check_port'] = get_unused_localhost_port()
|
||||
sub_minion_opts['runtests_conn_check_port'] = get_unused_localhost_port()
|
||||
|
||||
for conf in (master_opts, sub_master_opts, minion_opts, sub_minion_opts):
|
||||
if 'engines' not in conf:
|
||||
conf['engines'] = []
|
||||
conf['engines'].append({'salt_runtests': {}})
|
||||
|
||||
if 'engines_dirs' not in conf:
|
||||
conf['engines_dirs'] = []
|
||||
|
||||
conf['engines_dirs'].insert(0, ENGINES_DIR)
|
||||
|
||||
if 'log_handlers_dirs' not in conf:
|
||||
conf['log_handlers_dirs'] = []
|
||||
conf['log_handlers_dirs'].insert(0, LOG_HANDLERS_DIR)
|
||||
conf['runtests_log_port'] = SALT_LOG_PORT
|
||||
conf['runtests_log_level'] = os.environ.get('TESTS_MIN_LOG_LEVEL_NAME') or 'debug'
|
||||
|
||||
# ----- Transcribe Configuration ---------------------------------------------------------------------------->
|
||||
computed_config = copy.deepcopy(master_opts)
|
||||
with salt.utils.files.fopen(os.path.join(RUNTIME_VARS.TMP_MM_CONF_DIR, 'master'), 'w') as wfh:
|
||||
salt.utils.yaml.safe_dump(copy.deepcopy(master_opts), wfh, default_flow_style=False)
|
||||
with salt.utils.files.fopen(os.path.join(RUNTIME_VARS.TMP_MM_SUB_CONF_DIR, 'master'), 'w') as wfh:
|
||||
salt.utils.yaml.safe_dump(copy.deepcopy(sub_master_opts), wfh, default_flow_style=False)
|
||||
with salt.utils.files.fopen(os.path.join(RUNTIME_VARS.TMP_MM_CONF_DIR, 'minion'), 'w') as wfh:
|
||||
salt.utils.yaml.safe_dump(copy.deepcopy(minion_opts), wfh, default_flow_style=False)
|
||||
with salt.utils.files.fopen(os.path.join(RUNTIME_VARS.TMP_MM_SUB_CONF_DIR, 'minion'), 'w') as wfh:
|
||||
salt.utils.yaml.safe_dump(copy.deepcopy(sub_minion_opts), wfh, default_flow_style=False)
|
||||
# <---- Transcribe Configuration -----------------------------------------------------------------------------
|
||||
|
||||
# ----- Verify Environment ---------------------------------------------------------------------------------->
|
||||
master_opts = salt.config.master_config(os.path.join(RUNTIME_VARS.TMP_MM_CONF_DIR, 'master'))
|
||||
sub_master_opts = salt.config.master_config(os.path.join(RUNTIME_VARS.TMP_MM_SUB_CONF_DIR, 'master'))
|
||||
minion_opts = salt.config.minion_config(os.path.join(RUNTIME_VARS.TMP_MM_CONF_DIR, 'minion'))
|
||||
sub_minion_opts = salt.config.minion_config(os.path.join(RUNTIME_VARS.TMP_MM_SUB_CONF_DIR, 'minion'))
|
||||
|
||||
RUNTIME_VARS.RUNTIME_CONFIGS['mm_master'] = freeze(master_opts)
|
||||
RUNTIME_VARS.RUNTIME_CONFIGS['mm_sub_master'] = freeze(sub_master_opts)
|
||||
RUNTIME_VARS.RUNTIME_CONFIGS['mm_minion'] = freeze(minion_opts)
|
||||
RUNTIME_VARS.RUNTIME_CONFIGS['mm_sub_minion'] = freeze(sub_minion_opts)
|
||||
|
||||
verify_env([os.path.join(master_opts['pki_dir'], 'minions'),
|
||||
os.path.join(master_opts['pki_dir'], 'minions_pre'),
|
||||
os.path.join(master_opts['pki_dir'], 'minions_rejected'),
|
||||
os.path.join(master_opts['pki_dir'], 'minions_denied'),
|
||||
os.path.join(master_opts['cachedir'], 'jobs'),
|
||||
os.path.join(master_opts['cachedir'], 'raet'),
|
||||
os.path.join(master_opts['root_dir'], 'cache', 'tokens'),
|
||||
os.path.join(master_opts['pki_dir'], 'accepted'),
|
||||
os.path.join(master_opts['pki_dir'], 'rejected'),
|
||||
os.path.join(master_opts['pki_dir'], 'pending'),
|
||||
os.path.join(master_opts['cachedir'], 'raet'),
|
||||
os.path.join(sub_master_opts['pki_dir'], 'minions'),
|
||||
os.path.join(sub_master_opts['pki_dir'], 'minions_pre'),
|
||||
os.path.join(sub_master_opts['pki_dir'], 'minions_rejected'),
|
||||
os.path.join(sub_master_opts['pki_dir'], 'minions_denied'),
|
||||
os.path.join(sub_master_opts['cachedir'], 'jobs'),
|
||||
os.path.join(sub_master_opts['cachedir'], 'raet'),
|
||||
os.path.join(sub_master_opts['root_dir'], 'cache', 'tokens'),
|
||||
os.path.join(sub_master_opts['pki_dir'], 'accepted'),
|
||||
os.path.join(sub_master_opts['pki_dir'], 'rejected'),
|
||||
os.path.join(sub_master_opts['pki_dir'], 'pending'),
|
||||
os.path.join(sub_master_opts['cachedir'], 'raet'),
|
||||
|
||||
os.path.join(minion_opts['pki_dir'], 'accepted'),
|
||||
os.path.join(minion_opts['pki_dir'], 'rejected'),
|
||||
os.path.join(minion_opts['pki_dir'], 'pending'),
|
||||
os.path.join(minion_opts['cachedir'], 'raet'),
|
||||
os.path.join(sub_minion_opts['pki_dir'], 'accepted'),
|
||||
os.path.join(sub_minion_opts['pki_dir'], 'rejected'),
|
||||
os.path.join(sub_minion_opts['pki_dir'], 'pending'),
|
||||
os.path.join(sub_minion_opts['cachedir'], 'raet'),
|
||||
os.path.dirname(master_opts['log_file']),
|
||||
minion_opts['extension_modules'],
|
||||
sub_minion_opts['extension_modules'],
|
||||
sub_minion_opts['pki_dir'],
|
||||
master_opts['sock_dir'],
|
||||
sub_master_opts['sock_dir'],
|
||||
sub_minion_opts['sock_dir'],
|
||||
minion_opts['sock_dir'],
|
||||
],
|
||||
RUNTIME_VARS.RUNNING_TESTS_USER,
|
||||
root_dir=master_opts['root_dir'],
|
||||
)
|
||||
|
||||
cls.mm_master_opts = master_opts
|
||||
cls.mm_sub_master_opts = sub_master_opts
|
||||
cls.mm_minion_opts = minion_opts
|
||||
cls.mm_sub_minion_opts = sub_minion_opts
|
||||
# <---- Verify Environment -----------------------------------------------------------------------------------
|
||||
|
||||
@classmethod
|
||||
def config_location(cls):
|
||||
return (RUNTIME_VARS.TMP_MM_CONF_DIR, RUNTIME_VARS.TMP_MM_SUB_CONF_DIR)
|
1
tests/multimaster/modules/__init__.py
Normal file
1
tests/multimaster/modules/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
# -*- coding: utf-8 -*-
|
92
tests/multimaster/modules/test_test.py
Normal file
92
tests/multimaster/modules/test_test.py
Normal file
|
@ -0,0 +1,92 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Import Python libs
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
# Import Salt Testing libs
|
||||
from tests.support.case import MultimasterModuleCase
|
||||
from tests.support.mixins import AdaptedConfigurationTestCaseMixin
|
||||
|
||||
# Import salt libs
|
||||
import salt.version
|
||||
import salt.config
|
||||
|
||||
|
||||
class TestModuleTest(MultimasterModuleCase, AdaptedConfigurationTestCaseMixin):
|
||||
'''
|
||||
Validate the test module
|
||||
'''
|
||||
def test_ping(self):
|
||||
'''
|
||||
test.ping
|
||||
'''
|
||||
self.assertEqual(self.run_function_all_masters('test.ping'), [True] * 2)
|
||||
|
||||
def test_echo(self):
|
||||
'''
|
||||
test.echo
|
||||
'''
|
||||
self.assertEqual(self.run_function_all_masters('test.echo', ['text']), ['text'] * 2)
|
||||
|
||||
def test_version(self):
|
||||
'''
|
||||
test.version
|
||||
'''
|
||||
self.assertEqual(self.run_function_all_masters('test.version'),
|
||||
[salt.version.__saltstack_version__.string] * 2)
|
||||
|
||||
def test_conf_test(self):
|
||||
'''
|
||||
test.conf_test
|
||||
'''
|
||||
self.assertEqual(self.run_function_all_masters('test.conf_test'), ['baz'] * 2)
|
||||
|
||||
def test_get_opts(self):
|
||||
'''
|
||||
test.get_opts
|
||||
'''
|
||||
opts = salt.config.minion_config(
|
||||
self.get_config_file_path('mm_minion')
|
||||
)
|
||||
ret = self.run_function_all_masters('test.get_opts')
|
||||
self.assertEqual(
|
||||
ret[0]['cachedir'],
|
||||
opts['cachedir']
|
||||
)
|
||||
self.assertEqual(
|
||||
ret[1]['cachedir'],
|
||||
opts['cachedir']
|
||||
)
|
||||
|
||||
def test_cross_test(self):
|
||||
'''
|
||||
test.cross_test
|
||||
'''
|
||||
self.assertTrue(
|
||||
self.run_function_all_masters(
|
||||
'test.cross_test',
|
||||
['test.ping']
|
||||
)
|
||||
)
|
||||
|
||||
def test_fib(self):
|
||||
'''
|
||||
test.fib
|
||||
'''
|
||||
ret = self.run_function_all_masters('test.fib', ['20'])
|
||||
self.assertEqual(ret[0][0], 6765)
|
||||
self.assertEqual(ret[1][0], 6765)
|
||||
|
||||
def test_collatz(self):
|
||||
'''
|
||||
test.collatz
|
||||
'''
|
||||
ret = self.run_function_all_masters('test.collatz', ['40'])
|
||||
self.assertEqual(ret[0][0][-1], 2)
|
||||
self.assertEqual(ret[1][0][-1], 2)
|
||||
|
||||
def test_outputter(self):
|
||||
'''
|
||||
test.outputter
|
||||
'''
|
||||
self.assertEqual(self.run_function_all_masters('test.outputter', ['text']), ['text'] * 2)
|
|
@ -59,6 +59,7 @@ except ImportError as exc:
|
|||
raise exc
|
||||
|
||||
from tests.integration import TestDaemon # pylint: disable=W0403
|
||||
from tests.multimaster import MultimasterTestDaemon
|
||||
import salt.utils.platform
|
||||
|
||||
if not salt.utils.platform.is_windows():
|
||||
|
@ -105,6 +106,9 @@ TEST_SUITES_UNORDERED = {
|
|||
'kitchen':
|
||||
{'display_name': 'Kitchen',
|
||||
'path': 'kitchen'},
|
||||
'multimaster':
|
||||
{'display_name': 'Multimaster',
|
||||
'path': 'multimaster'},
|
||||
'module':
|
||||
{'display_name': 'Module',
|
||||
'path': 'integration/modules'},
|
||||
|
@ -189,7 +193,7 @@ TEST_SUITES_UNORDERED = {
|
|||
}
|
||||
|
||||
TEST_SUITES = collections.OrderedDict(sorted(TEST_SUITES_UNORDERED.items(),
|
||||
key=lambda x: x[0]))
|
||||
key=lambda x: x[0]))
|
||||
|
||||
|
||||
class SaltTestsuiteParser(SaltCoverageTestingParser):
|
||||
|
@ -198,7 +202,7 @@ class SaltTestsuiteParser(SaltCoverageTestingParser):
|
|||
source_code_basedir = SALT_ROOT
|
||||
|
||||
def _get_suites(self, include_unit=False, include_cloud_provider=False,
|
||||
include_proxy=False, include_kitchen=False):
|
||||
include_proxy=False, include_kitchen=False, include_multimaster=False):
|
||||
'''
|
||||
Return a set of all test suites except unit and cloud provider tests
|
||||
unless requested
|
||||
|
@ -212,20 +216,24 @@ class SaltTestsuiteParser(SaltCoverageTestingParser):
|
|||
suites -= set(['proxy'])
|
||||
if not include_kitchen:
|
||||
suites -= set(['kitchen'])
|
||||
if not include_multimaster:
|
||||
suites -= set(['multimaster'])
|
||||
|
||||
return suites
|
||||
|
||||
def _check_enabled_suites(self, include_unit=False,
|
||||
include_cloud_provider=False,
|
||||
include_proxy=False,
|
||||
include_kitchen=False):
|
||||
include_kitchen=False,
|
||||
include_multimaster=False):
|
||||
'''
|
||||
Query whether test suites have been enabled
|
||||
'''
|
||||
suites = self._get_suites(include_unit=include_unit,
|
||||
include_cloud_provider=include_cloud_provider,
|
||||
include_proxy=include_proxy,
|
||||
include_kitchen=include_kitchen)
|
||||
include_kitchen=include_kitchen,
|
||||
include_multimaster=include_multimaster)
|
||||
|
||||
return any([getattr(self.options, suite) for suite in suites])
|
||||
|
||||
|
@ -527,6 +535,13 @@ class SaltTestsuiteParser(SaltCoverageTestingParser):
|
|||
default=False,
|
||||
help='Run logging integration tests'
|
||||
)
|
||||
self.test_selection_group.add_option(
|
||||
'--multimaster',
|
||||
dest='multimaster',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Start multimaster daemons and run multimaster integration tests'
|
||||
)
|
||||
|
||||
def validate_options(self):
|
||||
if self.options.cloud_provider or self.options.external_api:
|
||||
|
@ -570,7 +585,8 @@ class SaltTestsuiteParser(SaltCoverageTestingParser):
|
|||
self._check_enabled_suites(include_unit=True,
|
||||
include_cloud_provider=True,
|
||||
include_proxy=True,
|
||||
include_kitchen=True):
|
||||
include_kitchen=True,
|
||||
include_multimaster=True):
|
||||
self._enable_suites(include_unit=True)
|
||||
|
||||
self.start_coverage(
|
||||
|
@ -583,6 +599,7 @@ class SaltTestsuiteParser(SaltCoverageTestingParser):
|
|||
|
||||
# Transplant configuration
|
||||
TestDaemon.transplant_configs(transport=self.options.transport)
|
||||
MultimasterTestDaemon.transplant_configs(transport=self.options.transport)
|
||||
|
||||
def post_execution_cleanup(self):
|
||||
SaltCoverageTestingParser.post_execution_cleanup(self)
|
||||
|
@ -670,6 +687,61 @@ class SaltTestsuiteParser(SaltCoverageTestingParser):
|
|||
while True:
|
||||
time.sleep(1)
|
||||
|
||||
def start_multimaster_daemons_only(self):
|
||||
if not salt.utils.platform.is_windows():
|
||||
self.set_filehandle_limits('integration')
|
||||
try:
|
||||
print_header(
|
||||
' * Setting up Salt daemons for interactive use',
|
||||
top=False, width=getattr(self.options, 'output_columns', PNUM)
|
||||
)
|
||||
except TypeError:
|
||||
print_header(' * Setting up Salt daemons for interactive use', top=False)
|
||||
|
||||
with MultimasterTestDaemon(self):
|
||||
print_header(' * Salt daemons started')
|
||||
master_conf = MultimasterTestDaemon.config('mm_master')
|
||||
sub_master_conf = MultimasterTestDaemon.config('mm_sub_master')
|
||||
minion_conf = MultimasterTestDaemon.config('mm_minion')
|
||||
sub_minion_conf = MultimasterTestDaemon.config('mm_sub_minion')
|
||||
|
||||
print_header(' * Master configuration values', top=True)
|
||||
print('interface: {0}'.format(master_conf['interface']))
|
||||
print('publish port: {0}'.format(master_conf['publish_port']))
|
||||
print('return port: {0}'.format(master_conf['ret_port']))
|
||||
print('\n')
|
||||
|
||||
print_header(' * Second master configuration values', top=True)
|
||||
print('interface: {0}'.format(sub_master_conf['interface']))
|
||||
print('publish port: {0}'.format(sub_master_conf['publish_port']))
|
||||
print('return port: {0}'.format(sub_master_conf['ret_port']))
|
||||
print('\n')
|
||||
|
||||
print_header(' * Minion configuration values', top=True)
|
||||
print('interface: {0}'.format(minion_conf['interface']))
|
||||
print('masters: {0}'.format(', '.join(minion_conf['master'])))
|
||||
if minion_conf['ipc_mode'] == 'tcp':
|
||||
print('tcp pub port: {0}'.format(minion_conf['tcp_pub_port']))
|
||||
print('tcp pull port: {0}'.format(minion_conf['tcp_pull_port']))
|
||||
print('\n')
|
||||
|
||||
print_header(' * Sub Minion configuration values', top=True)
|
||||
print('interface: {0}'.format(sub_minion_conf['interface']))
|
||||
print('masters: {0}'.format(', '.join(sub_minion_conf['master'])))
|
||||
if sub_minion_conf['ipc_mode'] == 'tcp':
|
||||
print('tcp pub port: {0}'.format(sub_minion_conf['tcp_pub_port']))
|
||||
print('tcp pull port: {0}'.format(sub_minion_conf['tcp_pull_port']))
|
||||
print('\n')
|
||||
|
||||
print_header(' Your client configurations are at {0}'.format(
|
||||
', '.join(MultimasterTestDaemon.config_location())))
|
||||
print('To access minions from different masters use:')
|
||||
for location in MultimasterTestDaemon.config_location():
|
||||
print(' salt -c {0} minion test.ping'.format(location))
|
||||
|
||||
while True:
|
||||
time.sleep(1)
|
||||
|
||||
def set_filehandle_limits(self, limits='integration'):
|
||||
'''
|
||||
Set soft and hard limits on open file handles at required thresholds
|
||||
|
@ -737,12 +809,14 @@ class SaltTestsuiteParser(SaltCoverageTestingParser):
|
|||
|
||||
if self.options.name:
|
||||
for test in self.options.name:
|
||||
if test.startswith(('tests.unit.', 'unit.', 'test.kitchen.', 'kitchen.')):
|
||||
if test.startswith(('tests.unit.', 'unit.',
|
||||
'test.kitchen.', 'kitchen.',
|
||||
'test.multimaster.', 'multimaster.')):
|
||||
named_unit_test.append(test)
|
||||
continue
|
||||
named_tests.append(test)
|
||||
|
||||
if (self.options.unit or self.options.kitchen or named_unit_test) \
|
||||
if (self.options.unit or self.options.kitchen or self.options.multimaster or named_unit_test) \
|
||||
and not named_tests \
|
||||
and (self.options.from_filenames or
|
||||
not self._check_enabled_suites(include_cloud_provider=True)):
|
||||
|
@ -795,10 +869,75 @@ class SaltTestsuiteParser(SaltCoverageTestingParser):
|
|||
status.append(results)
|
||||
return status
|
||||
for suite in TEST_SUITES:
|
||||
if suite != 'unit' and getattr(self.options, suite):
|
||||
if suite != 'unit' and suite != 'multimaster' and getattr(self.options, suite):
|
||||
status.append(self.run_integration_suite(**TEST_SUITES[suite]))
|
||||
return status
|
||||
|
||||
def run_multimaster_tests(self):
|
||||
'''
|
||||
Execute the multimaster tests suite
|
||||
'''
|
||||
named_tests = []
|
||||
named_unit_test = []
|
||||
|
||||
if self.options.name:
|
||||
for test in self.options.name:
|
||||
if test.startswith(('tests.unit.', 'unit.',
|
||||
'test.kitchen.', 'kitchen.',
|
||||
'test.integration.', 'integration.')):
|
||||
named_unit_test.append(test)
|
||||
continue
|
||||
named_tests.append(test)
|
||||
|
||||
# TODO: check 'from_filenames'
|
||||
if not self.options.multimaster and not named_tests:
|
||||
# We're either not running any integration test suites, or we're
|
||||
# only running unit tests by passing --unit or by passing only
|
||||
# `unit.<whatever>` to --name. We don't need the tests daemon
|
||||
# running
|
||||
return [True]
|
||||
|
||||
if not salt.utils.platform.is_windows():
|
||||
self.set_filehandle_limits('integration')
|
||||
|
||||
try:
|
||||
print_header(
|
||||
' * Setting up Salt daemons to execute tests',
|
||||
top=False, width=getattr(self.options, 'output_columns', PNUM)
|
||||
)
|
||||
except TypeError:
|
||||
print_header(' * Setting up Salt daemons to execute tests', top=False)
|
||||
|
||||
status = []
|
||||
# Return an empty status if no tests have been enabled
|
||||
if not self._check_enabled_suites(include_multimaster=True) and not self.options.name:
|
||||
return status
|
||||
|
||||
with MultimasterTestDaemon(self):
|
||||
if self.options.name:
|
||||
for name in self.options.name:
|
||||
name = name.strip()
|
||||
if not name:
|
||||
continue
|
||||
if os.path.isfile(name):
|
||||
if not name.endswith('.py'):
|
||||
continue
|
||||
if name.startswith(os.path.join('tests', 'unit')):
|
||||
continue
|
||||
results = self.run_suite(os.path.dirname(name),
|
||||
name,
|
||||
suffix=os.path.basename(name),
|
||||
load_from_name=False)
|
||||
status.append(results)
|
||||
continue
|
||||
if name.startswith(('tests.unit.', 'unit.')):
|
||||
continue
|
||||
results = self.run_suite('', name, suffix='test_*.py', load_from_name=True)
|
||||
status.append(results)
|
||||
return status
|
||||
status.append(self.run_integration_suite(**TEST_SUITES['multimaster']))
|
||||
return status
|
||||
|
||||
def run_unit_tests(self):
|
||||
'''
|
||||
Execute the unit tests
|
||||
|
@ -893,9 +1032,14 @@ def main(**kwargs):
|
|||
|
||||
overall_status = []
|
||||
if parser.options.interactive:
|
||||
parser.start_daemons_only()
|
||||
if parser.options.multimaster:
|
||||
parser.start_multimaster_daemons_only()
|
||||
else:
|
||||
parser.start_daemons_only()
|
||||
status = parser.run_integration_tests()
|
||||
overall_status.extend(status)
|
||||
status = parser.run_multimaster_tests()
|
||||
overall_status.extend(status)
|
||||
status = parser.run_unit_tests()
|
||||
overall_status.extend(status)
|
||||
status = parser.run_kitchen_tests()
|
||||
|
|
|
@ -33,7 +33,11 @@ from tests.support.helpers import (
|
|||
RedirectStdStreams, requires_sshd_server, win32_kill_process_tree
|
||||
)
|
||||
from tests.support.runtests import RUNTIME_VARS
|
||||
from tests.support.mixins import AdaptedConfigurationTestCaseMixin, SaltClientTestCaseMixin
|
||||
from tests.support.mixins import (
|
||||
AdaptedConfigurationTestCaseMixin,
|
||||
SaltClientTestCaseMixin,
|
||||
SaltMultimasterClientTestCaseMixin,
|
||||
)
|
||||
from tests.support.paths import ScriptPathMixin, INTEGRATION_TEST_DIR, CODE_DIR, PYEXEC, SCRIPT_DIR
|
||||
|
||||
# Import 3rd-party libs
|
||||
|
@ -808,7 +812,7 @@ class ModuleCase(TestCase, SaltClientTestCaseMixin):
|
|||
'''
|
||||
return self.run_function(_function, args, **kw)
|
||||
|
||||
def run_function(self, function, arg=(), minion_tgt='minion', timeout=300, **kwargs):
|
||||
def run_function(self, function, arg=(), minion_tgt='minion', timeout=300, master_tgt=None, **kwargs):
|
||||
'''
|
||||
Run a single salt function and condition the return down to match the
|
||||
behavior of the raw function call
|
||||
|
@ -827,11 +831,12 @@ class ModuleCase(TestCase, SaltClientTestCaseMixin):
|
|||
kwargs['arg'] = kwargs.pop('f_arg')
|
||||
if 'f_timeout' in kwargs:
|
||||
kwargs['timeout'] = kwargs.pop('f_timeout')
|
||||
orig = self.client.cmd(minion_tgt,
|
||||
function,
|
||||
arg,
|
||||
timeout=timeout,
|
||||
kwarg=kwargs)
|
||||
client = self.client if master_tgt is None else self.clients[master_tgt]
|
||||
orig = client.cmd(minion_tgt,
|
||||
function,
|
||||
arg,
|
||||
timeout=timeout,
|
||||
kwarg=kwargs)
|
||||
|
||||
if minion_tgt not in orig:
|
||||
self.skipTest(
|
||||
|
@ -853,6 +858,16 @@ class ModuleCase(TestCase, SaltClientTestCaseMixin):
|
|||
|
||||
return orig[minion_tgt]
|
||||
|
||||
def run_function_all_masters(self, function, arg=(), minion_tgt='minion', timeout=300, **kwargs):
|
||||
'''
|
||||
Run a single salt function from all the masters in multimaster environment
|
||||
and condition the return down to match the behavior of the raw function call
|
||||
'''
|
||||
ret = []
|
||||
for master in range(len(self.clients)):
|
||||
ret.append(self.run_function(function, arg, minion_tgt, timeout, master_tgt=master, **kwargs))
|
||||
return ret
|
||||
|
||||
def run_state(self, function, **kwargs):
|
||||
'''
|
||||
Run the state.single command and return the state return structure
|
||||
|
@ -895,6 +910,67 @@ class ModuleCase(TestCase, SaltClientTestCaseMixin):
|
|||
return ret
|
||||
|
||||
|
||||
class MultimasterModuleCase(ModuleCase, SaltMultimasterClientTestCaseMixin):
|
||||
'''
|
||||
Execute a module function
|
||||
'''
|
||||
|
||||
def run_function(self, function, arg=(), minion_tgt='minion', timeout=300, master_tgt=0, **kwargs):
|
||||
'''
|
||||
Run a single salt function and condition the return down to match the
|
||||
behavior of the raw function call
|
||||
'''
|
||||
known_to_return_none = (
|
||||
'data.get',
|
||||
'file.chown',
|
||||
'file.chgrp',
|
||||
'pkg.refresh_db',
|
||||
'ssh.recv_known_host_entries',
|
||||
'time.sleep'
|
||||
)
|
||||
if minion_tgt == 'sub_minion':
|
||||
known_to_return_none += ('mine.update',)
|
||||
if 'f_arg' in kwargs:
|
||||
kwargs['arg'] = kwargs.pop('f_arg')
|
||||
if 'f_timeout' in kwargs:
|
||||
kwargs['timeout'] = kwargs.pop('f_timeout')
|
||||
orig = self.clients[master_tgt].cmd(minion_tgt,
|
||||
function,
|
||||
arg,
|
||||
timeout=timeout,
|
||||
kwarg=kwargs)
|
||||
|
||||
if minion_tgt not in orig:
|
||||
self.skipTest(
|
||||
'WARNING(SHOULD NOT HAPPEN #1935): Failed to get a reply '
|
||||
'from the minion \'{0}\'. Command output: {1}'.format(
|
||||
minion_tgt, orig
|
||||
)
|
||||
)
|
||||
elif orig[minion_tgt] is None and function not in known_to_return_none:
|
||||
self.skipTest(
|
||||
'WARNING(SHOULD NOT HAPPEN #1935): Failed to get \'{0}\' from '
|
||||
'the minion \'{1}\'. Command output: {2}'.format(
|
||||
function, minion_tgt, orig
|
||||
)
|
||||
)
|
||||
|
||||
# Try to match stalled state functions
|
||||
orig[minion_tgt] = self._check_state_return(orig[minion_tgt])
|
||||
|
||||
return orig[minion_tgt]
|
||||
|
||||
def run_function_all_masters(self, function, arg=(), minion_tgt='minion', timeout=300, **kwargs):
|
||||
'''
|
||||
Run a single salt function from all the masters in multimaster environment
|
||||
and condition the return down to match the behavior of the raw function call
|
||||
'''
|
||||
ret = []
|
||||
for master in range(len(self.clients)):
|
||||
ret.append(self.run_function(function, arg, minion_tgt, timeout, master_tgt=master, **kwargs))
|
||||
return ret
|
||||
|
||||
|
||||
class SyndicCase(TestCase, SaltClientTestCaseMixin):
|
||||
'''
|
||||
Execute a syndic based execution test
|
||||
|
|
|
@ -126,7 +126,7 @@ class AdaptedConfigurationTestCaseMixin(object):
|
|||
@staticmethod
|
||||
def get_config(config_for, from_scratch=False):
|
||||
if from_scratch:
|
||||
if config_for in ('master', 'syndic_master'):
|
||||
if config_for in ('master', 'syndic_master', 'mm_master', 'mm_sub_master'):
|
||||
return salt.config.master_config(
|
||||
AdaptedConfigurationTestCaseMixin.get_config_file_path(config_for)
|
||||
)
|
||||
|
@ -145,7 +145,7 @@ class AdaptedConfigurationTestCaseMixin(object):
|
|||
)
|
||||
|
||||
if config_for not in RUNTIME_VARS.RUNTIME_CONFIGS:
|
||||
if config_for in ('master', 'syndic_master'):
|
||||
if config_for in ('master', 'syndic_master', 'mm_master', 'mm_sub_master'):
|
||||
RUNTIME_VARS.RUNTIME_CONFIGS[config_for] = freeze(
|
||||
salt.config.master_config(
|
||||
AdaptedConfigurationTestCaseMixin.get_config_file_path(config_for)
|
||||
|
@ -188,6 +188,14 @@ class AdaptedConfigurationTestCaseMixin(object):
|
|||
return os.path.join(RUNTIME_VARS.TMP_SYNDIC_MINION_CONF_DIR, 'minion')
|
||||
if filename == 'sub_minion':
|
||||
return os.path.join(RUNTIME_VARS.TMP_SUB_MINION_CONF_DIR, 'minion')
|
||||
if filename == 'mm_master':
|
||||
return os.path.join(RUNTIME_VARS.TMP_MM_CONF_DIR, 'master')
|
||||
if filename == 'mm_sub_master':
|
||||
return os.path.join(RUNTIME_VARS.TMP_MM_SUB_CONF_DIR, 'master')
|
||||
if filename == 'mm_minion':
|
||||
return os.path.join(RUNTIME_VARS.TMP_MM_CONF_DIR, 'minion')
|
||||
if filename == 'mm_sub_minion':
|
||||
return os.path.join(RUNTIME_VARS.TMP_MM_SUB_CONF_DIR, 'minion')
|
||||
return os.path.join(RUNTIME_VARS.TMP_CONF_DIR, filename)
|
||||
|
||||
@property
|
||||
|
@ -249,6 +257,44 @@ class SaltClientTestCaseMixin(AdaptedConfigurationTestCaseMixin):
|
|||
return RUNTIME_VARS.RUNTIME_CONFIGS['runtime_client']
|
||||
|
||||
|
||||
class SaltMultimasterClientTestCaseMixin(AdaptedConfigurationTestCaseMixin):
|
||||
'''
|
||||
Mix-in class that provides a ``clients`` attribute which returns a list of Salt
|
||||
:class:`LocalClient<salt:salt.client.LocalClient>`.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class LocalClientTestCase(TestCase, SaltMultimasterClientTestCaseMixin):
|
||||
|
||||
def test_check_pub_data(self):
|
||||
just_minions = {'minions': ['m1', 'm2']}
|
||||
jid_no_minions = {'jid': '1234', 'minions': []}
|
||||
valid_pub_data = {'minions': ['m1', 'm2'], 'jid': '1234'}
|
||||
|
||||
for client in self.clients:
|
||||
self.assertRaises(EauthAuthenticationError,
|
||||
client._check_pub_data, None)
|
||||
self.assertDictEqual({},
|
||||
client._check_pub_data(just_minions),
|
||||
'Did not handle lack of jid correctly')
|
||||
|
||||
self.assertDictEqual(
|
||||
{},
|
||||
client._check_pub_data({'jid': '0'}),
|
||||
'Passing JID of zero is not handled gracefully')
|
||||
'''
|
||||
_salt_client_config_file_name_ = 'master'
|
||||
|
||||
@property
|
||||
def clients(self):
|
||||
# Late import
|
||||
import salt.client
|
||||
if 'runtime_clients' not in RUNTIME_VARS.RUNTIME_CONFIGS:
|
||||
mopts = self.get_config(self._salt_client_config_file_name_, from_scratch=True)
|
||||
RUNTIME_VARS.RUNTIME_CONFIGS['runtime_clients'] = salt.client.get_local_client(mopts=mopts)
|
||||
return RUNTIME_VARS.RUNTIME_CONFIGS['runtime_clients']
|
||||
|
||||
|
||||
class ShellCaseCommonTestsMixin(CheckShellBinaryNameAndVersionMixin):
|
||||
|
||||
_call_binary_expected_version_ = salt.version.__version__
|
||||
|
|
|
@ -408,13 +408,14 @@ class SaltTestingParser(optparse.OptionParser):
|
|||
ret.update(
|
||||
x for x in
|
||||
['.'.join(('unit', mod_relname)),
|
||||
'.'.join(('integration', mod_relname))]
|
||||
'.'.join(('integration', mod_relname)),
|
||||
'.'.join(('multimaster', mod_relname))]
|
||||
if x in self._test_mods
|
||||
)
|
||||
|
||||
# First, try a path match
|
||||
for path in files:
|
||||
match = re.match(r'^(salt/|tests/(integration|unit)/)(.+\.py)$', path)
|
||||
match = re.match(r'^(salt/|tests/(unit|integration|multimaster)/)(.+\.py)$', path)
|
||||
if match:
|
||||
comps = match.group(3).split('/')
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ if sys.platform.startswith('win'):
|
|||
CODE_DIR = CODE_DIR.replace('\\', '\\\\')
|
||||
UNIT_TEST_DIR = os.path.join(TESTS_DIR, 'unit')
|
||||
INTEGRATION_TEST_DIR = os.path.join(TESTS_DIR, 'integration')
|
||||
MULTIMASTER_TEST_DIR = os.path.join(TESTS_DIR, 'multimaster')
|
||||
|
||||
# Let's inject CODE_DIR so salt is importable if not there already
|
||||
if TESTS_DIR in sys.path:
|
||||
|
@ -67,6 +68,8 @@ TMP_CONF_DIR = os.path.join(TMP, 'config')
|
|||
TMP_SUB_MINION_CONF_DIR = os.path.join(TMP_CONF_DIR, 'sub-minion')
|
||||
TMP_SYNDIC_MINION_CONF_DIR = os.path.join(TMP_CONF_DIR, 'syndic-minion')
|
||||
TMP_SYNDIC_MASTER_CONF_DIR = os.path.join(TMP_CONF_DIR, 'syndic-master')
|
||||
TMP_MM_CONF_DIR = os.path.join(TMP_CONF_DIR, 'multimaster')
|
||||
TMP_MM_SUB_CONF_DIR = os.path.join(TMP_CONF_DIR, 'sub-multimaster')
|
||||
TMP_PROXY_CONF_DIR = os.path.join(TMP_CONF_DIR, 'proxy')
|
||||
CONF_DIR = os.path.join(INTEGRATION_TEST_DIR, 'files', 'conf')
|
||||
PILLAR_DIR = os.path.join(FILES, 'pillar')
|
||||
|
@ -127,7 +130,7 @@ def test_mods():
|
|||
A generator which returns all of the test files
|
||||
'''
|
||||
test_re = re.compile(r'^test_.+\.py$')
|
||||
for dirname in (UNIT_TEST_DIR, INTEGRATION_TEST_DIR):
|
||||
for dirname in (UNIT_TEST_DIR, INTEGRATION_TEST_DIR, MULTIMASTER_TEST_DIR):
|
||||
test_type = os.path.basename(dirname)
|
||||
for root, _, files in salt.utils.path.os_walk(dirname):
|
||||
parent_mod = root[len(dirname):].lstrip(os.sep).replace(os.sep, '.')
|
||||
|
|
|
@ -214,6 +214,8 @@ RUNTIME_VARS = RuntimeVars(
|
|||
TMP_SUB_MINION_CONF_DIR=paths.TMP_SUB_MINION_CONF_DIR,
|
||||
TMP_SYNDIC_MASTER_CONF_DIR=paths.TMP_SYNDIC_MASTER_CONF_DIR,
|
||||
TMP_SYNDIC_MINION_CONF_DIR=paths.TMP_SYNDIC_MINION_CONF_DIR,
|
||||
TMP_MM_CONF_DIR=paths.TMP_MM_CONF_DIR,
|
||||
TMP_MM_SUB_CONF_DIR = paths.TMP_MM_SUB_CONF_DIR,
|
||||
TMP_SCRIPT_DIR=paths.TMP_SCRIPT_DIR,
|
||||
TMP_STATE_TREE=paths.TMP_STATE_TREE,
|
||||
TMP_PILLAR_TREE=paths.TMP_PILLAR_TREE,
|
||||
|
|
Loading…
Add table
Reference in a new issue