Multimaster tests environment and test module test.

This commit is contained in:
Dmitry Kuzmenko 2019-07-15 04:32:38 +03:00
parent 2c178b0f8d
commit 65dc47c300
No known key found for this signature in database
GPG key ID: 4C7CAD30C95651DA
14 changed files with 1040 additions and 22 deletions

View file

@ -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()

View file

View file

@ -0,0 +1,3 @@
master:
- localhost:64506
- localhost:64508

View 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

View file

@ -0,0 +1,3 @@
master:
- localhost:64506
- localhost:64508

View 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)

View file

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View 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)

View file

@ -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()

View file

@ -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

View file

@ -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__

View file

@ -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('/')

View file

@ -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, '.')

View file

@ -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,