mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Merge pull request #54252 from s0undt3ch/hotfix/git-pillar-2019.2.1
[2019.2.1] More control on spun test deamons on git pillar tests
This commit is contained in:
commit
c181f5a633
7 changed files with 477 additions and 283 deletions
|
@ -1,3 +1,4 @@
|
|||
daemon off;
|
||||
worker_processes 1;
|
||||
error_log {{ pillar['git_pillar']['config_dir'] }}/error.log;
|
||||
pid {{ pillar['git_pillar']['config_dir'] }}/nginx.pid;
|
||||
|
|
|
@ -2,7 +2,6 @@ uwsgi:
|
|||
socket: 127.0.0.1:{{ pillar['git_pillar']['uwsgi_port'] }}
|
||||
cgi: {{ pillar['git_pillar']['git-http-backend'] }}
|
||||
chdir: %d
|
||||
daemonize: {{ pillar['git_pillar']['config_dir'] }}/uwsgi.log
|
||||
pidfile: {{ pillar['git_pillar']['config_dir'] }}/uwsgi.pid
|
||||
# This is required to work around a bug in git-http-backend, introduced in
|
||||
# git 2.4.4 and worked around with cgi-close-stdin-on-eof in uwsgi >= 2.0.13.
|
||||
|
|
|
@ -43,22 +43,10 @@
|
|||
|
||||
uwsgi:
|
||||
pip.installed:
|
||||
- name: 'uwsgi >= 2.0.13'
|
||||
- name: 'uwsgi == 2.0.18'
|
||||
- bin_env: {{ venv_dir }}
|
||||
{#- The env var bellow is EXTREMELY important #}
|
||||
- env_vars:
|
||||
UWSGI_PROFILE: cgi
|
||||
- require:
|
||||
- virtualenv: {{ venv_dir }}
|
||||
|
||||
start_uwsgi:
|
||||
cmd.run:
|
||||
- name: '{{ venv_dir }}/bin/uwsgi --yaml {{ config_dir }}/uwsgi.yml'
|
||||
- require:
|
||||
- pip: uwsgi
|
||||
- file: {{ config_dir }}/uwsgi.yml
|
||||
|
||||
start_nginx:
|
||||
cmd.run:
|
||||
- name: 'nginx -c {{ config_dir }}/nginx.conf'
|
||||
- require:
|
||||
- file: {{ config_dir }}/nginx.conf
|
||||
|
|
|
@ -31,14 +31,3 @@
|
|||
- group: root
|
||||
- mode: 755
|
||||
{%- endif %}
|
||||
|
||||
start_sshd:
|
||||
cmd.run:
|
||||
- name: '{{ pillar['git_pillar']['sshd_bin'] }} -f {{ sshd_config_dir }}/sshd_config'
|
||||
- require:
|
||||
- file: {{ sshd_config_dir }}/sshd_config
|
||||
- file: {{ sshd_config_dir }}/ssh_host_rsa_key
|
||||
- file: {{ sshd_config_dir }}/ssh_host_rsa_key.pub
|
||||
{%- if grains['os_family'] == 'Debian' %}
|
||||
- file: /var/run/sshd
|
||||
{%- endif %}
|
||||
|
|
|
@ -5,34 +5,36 @@ Base classes for gitfs/git_pillar integration tests
|
|||
|
||||
# Import python libs
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
import sys
|
||||
import copy
|
||||
import errno
|
||||
import logging
|
||||
import os
|
||||
import psutil
|
||||
import pprint
|
||||
import shutil
|
||||
import signal
|
||||
import tempfile
|
||||
import textwrap
|
||||
import time
|
||||
|
||||
# Import 3rd-party libs
|
||||
import psutil
|
||||
|
||||
# Import Salt libs
|
||||
import salt.utils.files
|
||||
import salt.utils.path
|
||||
import salt.utils.yaml
|
||||
import salt.ext.six as six
|
||||
from salt.fileserver import gitfs
|
||||
from salt.pillar import git_pillar
|
||||
from salt.ext.six.moves import range # pylint: disable=redefined-builtin
|
||||
|
||||
# Import Salt Testing libs
|
||||
from tests.support.case import ModuleCase
|
||||
from tests.support.mixins import LoaderModuleMockMixin, SaltReturnAssertsMixin
|
||||
from tests.support.paths import TMP
|
||||
from tests.support.helpers import (
|
||||
get_unused_localhost_port,
|
||||
requires_system_grains,
|
||||
)
|
||||
from tests.support.unit import SkipTest
|
||||
from tests.support.mixins import AdaptedConfigurationTestCaseMixin, LoaderModuleMockMixin, SaltReturnAssertsMixin
|
||||
from tests.support.helpers import get_unused_localhost_port, requires_system_grains
|
||||
from tests.support.runtests import RUNTIME_VARS
|
||||
from tests.support.mock import patch
|
||||
from pytestsalt.utils import SaltDaemonScriptBase, terminate_process
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -72,122 +74,374 @@ _OPTS = {
|
|||
PROC_TIMEOUT = 10
|
||||
|
||||
|
||||
class ProcessManager(object):
|
||||
def start_daemon(daemon_cli_script_name,
|
||||
daemon_config_dir,
|
||||
daemon_check_port,
|
||||
daemon_class,
|
||||
fail_hard=False,
|
||||
start_timeout=10,
|
||||
slow_stop=True,
|
||||
environ=None,
|
||||
cwd=None,
|
||||
max_attempts=3,
|
||||
**kwargs):
|
||||
'''
|
||||
Functions used both to set up self-contained SSH/HTTP servers for testing
|
||||
Returns a running process daemon
|
||||
'''
|
||||
wait = 10
|
||||
|
||||
def find_proc(self, name=None, search=None):
|
||||
def _search(proc):
|
||||
return any([search in x for x in proc.cmdline()])
|
||||
if name is None and search is None:
|
||||
raise ValueError('one of name or search is required')
|
||||
for proc in psutil.process_iter():
|
||||
if name is not None:
|
||||
try:
|
||||
if search is None:
|
||||
if name in proc.name():
|
||||
return proc
|
||||
elif name in proc.name() and _search(proc):
|
||||
return proc
|
||||
except psutil.NoSuchProcess:
|
||||
# Whichever process we are interrogating is no longer alive.
|
||||
# Skip it and keep searching.
|
||||
continue
|
||||
else:
|
||||
if _search(proc):
|
||||
return proc
|
||||
return None
|
||||
|
||||
def wait_proc(self, name=None, search=None, timeout=PROC_TIMEOUT):
|
||||
for idx in range(1, self.wait + 1):
|
||||
proc = self.find_proc(name=name, search=search)
|
||||
if proc is not None:
|
||||
return proc
|
||||
else:
|
||||
if idx != self.wait:
|
||||
log.debug(
|
||||
'Waiting for %s process (%d of %d)',
|
||||
name, idx, self.wait
|
||||
)
|
||||
time.sleep(1)
|
||||
else:
|
||||
log.debug(
|
||||
'Failed fo find %s process after %d seconds',
|
||||
name, self.wait
|
||||
)
|
||||
raise Exception(
|
||||
'Unable to find {0} process running from temp config file {1} '
|
||||
'using psutil'.format(name, search)
|
||||
log.info('[%s] Starting %s', daemon_class.log_prefix, daemon_class.__name__)
|
||||
attempts = 0
|
||||
process = None
|
||||
while attempts <= max_attempts: # pylint: disable=too-many-nested-blocks
|
||||
attempts += 1
|
||||
process = daemon_class(str(daemon_config_dir),
|
||||
daemon_check_port,
|
||||
cli_script_name=daemon_cli_script_name,
|
||||
slow_stop=slow_stop,
|
||||
environ=environ,
|
||||
cwd=cwd,
|
||||
**kwargs)
|
||||
process.start()
|
||||
if process.is_alive():
|
||||
try:
|
||||
connectable = process.wait_until_running(timeout=start_timeout)
|
||||
if connectable is False:
|
||||
connectable = process.wait_until_running(timeout=start_timeout/2)
|
||||
if connectable is False:
|
||||
process.terminate()
|
||||
if attempts >= max_attempts:
|
||||
raise AssertionError(
|
||||
'The {} has failed to confirm running status '
|
||||
'after {} attempts'.format(daemon_class.__name__, attempts))
|
||||
continue
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
log.exception('[%s] %s', daemon_class.log_prefix, exc, exc_info=True)
|
||||
terminate_process(process.pid, kill_children=True, slow_stop=slow_stop)
|
||||
if attempts >= max_attempts:
|
||||
raise AssertionError(str(exc))
|
||||
continue
|
||||
# A little breathing before returning the process
|
||||
time.sleep(0.5)
|
||||
log.info(
|
||||
'[%s] The %s is running after %d attempts',
|
||||
daemon_class.log_prefix,
|
||||
daemon_class.__name__,
|
||||
attempts
|
||||
)
|
||||
return process
|
||||
else:
|
||||
terminate_process(process.pid, kill_children=True, slow_stop=slow_stop)
|
||||
time.sleep(1)
|
||||
continue
|
||||
else: # pylint: disable=useless-else-on-loop
|
||||
# Wrong, we have a return, its not useless
|
||||
if process is not None:
|
||||
terminate_process(process.pid, kill_children=True, slow_stop=slow_stop)
|
||||
raise AssertionError(
|
||||
'The {} has failed to start after {} attempts'.format(
|
||||
daemon_class.__name__,
|
||||
attempts-1
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class SSHDMixin(ModuleCase, ProcessManager, SaltReturnAssertsMixin):
|
||||
class UwsgiDaemon(SaltDaemonScriptBase):
|
||||
|
||||
log_prefix = 'uWSGI'
|
||||
|
||||
def __init__(self,
|
||||
config_dir,
|
||||
uwsgi_port,
|
||||
cli_script_name='uwsgi',
|
||||
**kwargs):
|
||||
super(UwsgiDaemon, self).__init__(None, # request
|
||||
{'check_port': uwsgi_port}, # config
|
||||
config_dir, # config_dir
|
||||
None, # bin_dir_path
|
||||
self.__class__.log_prefix, # log_prefix
|
||||
cli_script_name=cli_script_name,
|
||||
**kwargs)
|
||||
|
||||
def get_script_path(self, script_name):
|
||||
'''
|
||||
Returns the path to the script to run
|
||||
'''
|
||||
return script_name
|
||||
|
||||
def get_base_script_args(self):
|
||||
'''
|
||||
Returns any additional arguments to pass to the CLI script
|
||||
'''
|
||||
return ['--yaml', os.path.join(self.config_dir, 'uwsgi.yml')]
|
||||
|
||||
def get_check_ports(self):
|
||||
'''
|
||||
Return a list of ports to check against to ensure the daemon is running
|
||||
'''
|
||||
return [self.config['check_port']]
|
||||
|
||||
def get_salt_run_event_listener(self):
|
||||
# Remove this method once pytest-salt get's past 2019.7.20
|
||||
# Just return a class with a terminate method
|
||||
class EV(object):
|
||||
def terminate(self):
|
||||
pass
|
||||
|
||||
return EV()
|
||||
|
||||
|
||||
class NginxDaemon(SaltDaemonScriptBase):
|
||||
|
||||
log_prefix = 'Nginx'
|
||||
|
||||
def __init__(self,
|
||||
config_dir,
|
||||
nginx_port,
|
||||
cli_script_name='nginx',
|
||||
**kwargs):
|
||||
super(NginxDaemon, self).__init__(None, # request
|
||||
{'check_port': nginx_port}, # config
|
||||
config_dir, # config_dir
|
||||
None, # bin_dir_path
|
||||
self.__class__.log_prefix, # log_prefix
|
||||
cli_script_name=cli_script_name,
|
||||
**kwargs)
|
||||
|
||||
def get_script_path(self, script_name):
|
||||
'''
|
||||
Returns the path to the script to run
|
||||
'''
|
||||
return script_name
|
||||
|
||||
def get_base_script_args(self):
|
||||
'''
|
||||
Returns any additional arguments to pass to the CLI script
|
||||
'''
|
||||
return ['-c', os.path.join(self.config_dir, 'nginx.conf')]
|
||||
|
||||
def get_check_ports(self):
|
||||
'''
|
||||
Return a list of ports to check against to ensure the daemon is running
|
||||
'''
|
||||
return [self.config['check_port']]
|
||||
|
||||
def get_salt_run_event_listener(self):
|
||||
# Remove this method once pytest-salt get's past 2019.7.20
|
||||
# Just return a class with a terminate method
|
||||
class EV(object):
|
||||
def terminate(self):
|
||||
pass
|
||||
|
||||
return EV()
|
||||
|
||||
|
||||
class SshdDaemon(SaltDaemonScriptBase):
|
||||
|
||||
log_prefix = 'SSHD'
|
||||
|
||||
def __init__(self,
|
||||
config_dir,
|
||||
sshd_port,
|
||||
cli_script_name='sshd',
|
||||
**kwargs):
|
||||
super(SshdDaemon, self).__init__(None, # request
|
||||
{'check_port': sshd_port}, # config
|
||||
config_dir, # config_dir
|
||||
None, # bin_dir_path
|
||||
self.__class__.log_prefix, # log_prefix
|
||||
cli_script_name=cli_script_name,
|
||||
**kwargs)
|
||||
|
||||
def get_script_path(self, script_name):
|
||||
'''
|
||||
Returns the path to the script to run
|
||||
'''
|
||||
return script_name
|
||||
|
||||
def get_base_script_args(self):
|
||||
'''
|
||||
Returns any additional arguments to pass to the CLI script
|
||||
'''
|
||||
return ['-D', '-e', '-f', os.path.join(self.config_dir, 'sshd_config')]
|
||||
|
||||
def get_check_ports(self):
|
||||
'''
|
||||
Return a list of ports to check against to ensure the daemon is running
|
||||
'''
|
||||
return [self.config['check_port']]
|
||||
|
||||
def get_salt_run_event_listener(self):
|
||||
# Remove this method once pytest-salt get's past 2019.7.20
|
||||
# Just return a class with a terminate method
|
||||
class EV(object):
|
||||
def terminate(self):
|
||||
pass
|
||||
|
||||
return EV()
|
||||
|
||||
|
||||
class SaltClientMixin(ModuleCase):
|
||||
|
||||
client = None
|
||||
|
||||
@classmethod
|
||||
@requires_system_grains
|
||||
def setUpClass(cls, grains=None):
|
||||
# Cent OS 6 has too old a version of git to handle the make_repo code, as
|
||||
# it lacks the -c option for git itself.
|
||||
make_repo = getattr(cls, 'make_repo', None)
|
||||
if callable(make_repo) and grains['os_family'] == 'RedHat' and grains['osmajorrelease'] < 7:
|
||||
raise SkipTest('RHEL < 7 has too old a version of git to run these tests')
|
||||
# Late import
|
||||
import salt.client
|
||||
mopts = AdaptedConfigurationTestCaseMixin.get_config('master', from_scratch=True)
|
||||
cls.user = mopts['user']
|
||||
cls.client = salt.client.get_local_client(mopts=mopts)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.client = None
|
||||
|
||||
@classmethod
|
||||
def cls_run_function(cls, function, *args, **kwargs):
|
||||
orig = cls.client.cmd('minion',
|
||||
function,
|
||||
arg=args,
|
||||
timeout=300,
|
||||
kwarg=kwargs)
|
||||
return orig['minion']
|
||||
|
||||
|
||||
class SSHDMixin(SaltClientMixin, SaltReturnAssertsMixin):
|
||||
'''
|
||||
Functions to stand up an SSHD server to serve up git repos for tests.
|
||||
'''
|
||||
sshd_proc = None
|
||||
prep_states_ran = False
|
||||
known_hosts_setup = False
|
||||
|
||||
@classmethod
|
||||
def prep_server(cls):
|
||||
cls.sshd_config_dir = tempfile.mkdtemp(dir=TMP)
|
||||
cls.sshd_config = os.path.join(cls.sshd_config_dir, 'sshd_config')
|
||||
cls.sshd_port = get_unused_localhost_port()
|
||||
cls.url = 'ssh://{username}@127.0.0.1:{port}/~/repo.git'.format(
|
||||
username=cls.username,
|
||||
port=cls.sshd_port)
|
||||
cls.url_extra_repo = 'ssh://{username}@127.0.0.1:{port}/~/extra_repo.git'.format(
|
||||
username=cls.username,
|
||||
port=cls.sshd_port)
|
||||
home = '/root/.ssh'
|
||||
cls.ext_opts = {
|
||||
'url': cls.url,
|
||||
'url_extra_repo': cls.url_extra_repo,
|
||||
'privkey_nopass': os.path.join(home, cls.id_rsa_nopass),
|
||||
'pubkey_nopass': os.path.join(home, cls.id_rsa_nopass + '.pub'),
|
||||
'privkey_withpass': os.path.join(home, cls.id_rsa_withpass),
|
||||
'pubkey_withpass': os.path.join(home, cls.id_rsa_withpass + '.pub'),
|
||||
'passphrase': cls.passphrase}
|
||||
|
||||
def spawn_server(self):
|
||||
ret = self.run_function(
|
||||
'state.apply',
|
||||
mods='git_pillar.ssh',
|
||||
pillar={'git_pillar': {'git_ssh': self.git_ssh,
|
||||
'id_rsa_nopass': self.id_rsa_nopass,
|
||||
'id_rsa_withpass': self.id_rsa_withpass,
|
||||
'sshd_bin': self.sshd_bin,
|
||||
'sshd_port': self.sshd_port,
|
||||
'sshd_config_dir': self.sshd_config_dir,
|
||||
'master_user': self.master_opts['user'],
|
||||
'user': self.username}}
|
||||
)
|
||||
|
||||
def setUpClass(cls): # pylint: disable=arguments-differ
|
||||
super(SSHDMixin, cls).setUpClass()
|
||||
try:
|
||||
self.sshd_proc = self.wait_proc(name='sshd',
|
||||
search=self.sshd_config)
|
||||
finally:
|
||||
# Do the assert after we check for the PID so that we can track
|
||||
# it regardless of whether or not something else in the SLS
|
||||
# failed (but the SSH server still started).
|
||||
self.assertSaltTrueReturn(ret)
|
||||
log.info('%s: prep_server()', cls.__name__)
|
||||
cls.sshd_bin = salt.utils.path.which('sshd')
|
||||
cls.sshd_config_dir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
|
||||
cls.sshd_config = os.path.join(cls.sshd_config_dir, 'sshd_config')
|
||||
cls.sshd_port = get_unused_localhost_port()
|
||||
cls.url = 'ssh://{username}@127.0.0.1:{port}/~/repo.git'.format(
|
||||
username=cls.username,
|
||||
port=cls.sshd_port)
|
||||
cls.url_extra_repo = 'ssh://{username}@127.0.0.1:{port}/~/extra_repo.git'.format(
|
||||
username=cls.username,
|
||||
port=cls.sshd_port)
|
||||
home = '/root/.ssh'
|
||||
cls.ext_opts = {
|
||||
'url': cls.url,
|
||||
'url_extra_repo': cls.url_extra_repo,
|
||||
'privkey_nopass': os.path.join(home, cls.id_rsa_nopass),
|
||||
'pubkey_nopass': os.path.join(home, cls.id_rsa_nopass + '.pub'),
|
||||
'privkey_withpass': os.path.join(home, cls.id_rsa_withpass),
|
||||
'pubkey_withpass': os.path.join(home, cls.id_rsa_withpass + '.pub'),
|
||||
'passphrase': cls.passphrase}
|
||||
|
||||
if cls.prep_states_ran is False:
|
||||
ret = cls.cls_run_function(
|
||||
'state.apply',
|
||||
mods='git_pillar.ssh',
|
||||
pillar={'git_pillar': {'git_ssh': cls.git_ssh,
|
||||
'id_rsa_nopass': cls.id_rsa_nopass,
|
||||
'id_rsa_withpass': cls.id_rsa_withpass,
|
||||
'sshd_bin': cls.sshd_bin,
|
||||
'sshd_port': cls.sshd_port,
|
||||
'sshd_config_dir': cls.sshd_config_dir,
|
||||
'master_user': cls.user,
|
||||
'user': cls.username}}
|
||||
)
|
||||
assert next(six.itervalues(ret))['result'] is True
|
||||
cls.prep_states_ran = True
|
||||
log.info('%s: States applied', cls.__name__)
|
||||
if cls.sshd_proc is not None:
|
||||
if not psutil.pid_exists(cls.sshd_proc.pid):
|
||||
log.info('%s: sshd started but appears to be dead now. Will try to restart it.',
|
||||
cls.__name__)
|
||||
cls.sshd_proc = None
|
||||
if cls.sshd_proc is None:
|
||||
cls.sshd_proc = start_daemon(cls.sshd_bin, cls.sshd_config_dir, cls.sshd_port, SshdDaemon)
|
||||
log.info('\n\n%s: sshd started\n\n\n\n', cls.__name__)
|
||||
except AssertionError:
|
||||
cls.tearDownClass()
|
||||
six.reraise(*sys.exc_info())
|
||||
|
||||
if cls.known_hosts_setup is False:
|
||||
known_hosts_ret = cls.cls_run_function(
|
||||
'ssh.set_known_host',
|
||||
user=cls.user,
|
||||
hostname='127.0.0.1',
|
||||
port=cls.sshd_port,
|
||||
enc='ssh-rsa',
|
||||
fingerprint='fd:6f:7f:5d:06:6b:f2:06:0d:26:93:9e:5a:b5:19:46',
|
||||
hash_known_hosts=False,
|
||||
fingerprint_hash_type='md5',
|
||||
)
|
||||
if 'error' in known_hosts_ret:
|
||||
cls.tearDownClass()
|
||||
raise AssertionError(
|
||||
'Failed to add key to {0} user\'s known_hosts '
|
||||
'file: {1}'.format(
|
||||
cls.master_opts['user'],
|
||||
known_hosts_ret['error']
|
||||
)
|
||||
)
|
||||
cls.known_hosts_setup = True
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
if cls.sshd_proc is not None:
|
||||
log.info('[%s] Stopping %s', cls.sshd_proc.log_prefix, cls.sshd_proc.__class__.__name__)
|
||||
terminate_process(cls.sshd_proc.pid, kill_children=True, slow_stop=True)
|
||||
log.info('[%s] %s stopped', cls.sshd_proc.log_prefix, cls.sshd_proc.__class__.__name__)
|
||||
cls.sshd_proc = None
|
||||
if cls.prep_states_ran:
|
||||
ret = cls.cls_run_function('state.single', 'user.absent', name=cls.username, purge=True)
|
||||
try:
|
||||
if ret and 'minion' in ret:
|
||||
ret_data = next(six.itervalues(ret['minion']))
|
||||
if not ret_data['result']:
|
||||
log.warning('Failed to delete test account %s', cls.username)
|
||||
except KeyError:
|
||||
log.warning('Failed to delete test account. Salt return:\n%s',
|
||||
pprint.pformat(ret))
|
||||
shutil.rmtree(cls.sshd_config_dir, ignore_errors=True)
|
||||
ssh_dir = os.path.expanduser('~/.ssh')
|
||||
for filename in (cls.id_rsa_nopass,
|
||||
cls.id_rsa_nopass + '.pub',
|
||||
cls.id_rsa_withpass,
|
||||
cls.id_rsa_withpass + '.pub',
|
||||
cls.git_ssh):
|
||||
try:
|
||||
os.remove(os.path.join(ssh_dir, filename))
|
||||
except OSError as exc:
|
||||
if exc.errno != errno.ENOENT:
|
||||
raise
|
||||
super(SSHDMixin, cls).tearDownClass()
|
||||
|
||||
|
||||
class WebserverMixin(ModuleCase, ProcessManager, SaltReturnAssertsMixin):
|
||||
class WebserverMixin(SaltClientMixin, SaltReturnAssertsMixin):
|
||||
'''
|
||||
Functions to stand up an nginx + uWSGI + git-http-backend webserver to
|
||||
serve up git repos for tests.
|
||||
'''
|
||||
nginx_proc = uwsgi_proc = None
|
||||
prep_states_ran = False
|
||||
|
||||
@classmethod
|
||||
def prep_server(cls):
|
||||
def setUpClass(cls): # pylint: disable=arguments-differ
|
||||
'''
|
||||
Set up all the webserver paths. Designed to be run once in a
|
||||
setUpClass function.
|
||||
'''
|
||||
cls.root_dir = tempfile.mkdtemp(dir=TMP)
|
||||
super(WebserverMixin, cls).setUpClass()
|
||||
cls.root_dir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
|
||||
cls.config_dir = os.path.join(cls.root_dir, 'config')
|
||||
cls.nginx_conf = os.path.join(cls.config_dir, 'nginx.conf')
|
||||
cls.uwsgi_conf = os.path.join(cls.config_dir, 'uwsgi.yml')
|
||||
|
@ -208,50 +462,68 @@ class WebserverMixin(ModuleCase, ProcessManager, SaltReturnAssertsMixin):
|
|||
for credential_param in ('user', 'password'):
|
||||
if hasattr(cls, credential_param):
|
||||
cls.ext_opts[credential_param] = getattr(cls, credential_param)
|
||||
|
||||
@requires_system_grains
|
||||
def spawn_server(self, grains):
|
||||
auth_enabled = hasattr(self, 'username') and hasattr(self, 'password')
|
||||
pillar = {'git_pillar': {'config_dir': self.config_dir,
|
||||
'git_dir': self.git_dir,
|
||||
'venv_dir': self.venv_dir,
|
||||
'root_dir': self.root_dir,
|
||||
'nginx_port': self.nginx_port,
|
||||
'uwsgi_port': self.uwsgi_port,
|
||||
auth_enabled = hasattr(cls, 'username') and hasattr(cls, 'password')
|
||||
pillar = {'git_pillar': {'config_dir': cls.config_dir,
|
||||
'git_dir': cls.git_dir,
|
||||
'venv_dir': cls.venv_dir,
|
||||
'root_dir': cls.root_dir,
|
||||
'nginx_port': cls.nginx_port,
|
||||
'uwsgi_port': cls.uwsgi_port,
|
||||
'auth_enabled': auth_enabled}}
|
||||
|
||||
# Different libexec dir for git backend on Debian-based systems
|
||||
git_core = '/usr/libexec/git-core' \
|
||||
if grains['os_family'] in ('RedHat') \
|
||||
else '/usr/lib/git-core'
|
||||
git_core = '/usr/libexec/git-core'
|
||||
if not os.path.exists(git_core):
|
||||
git_core = '/usr/lib/git-core'
|
||||
|
||||
pillar['git_pillar']['git-http-backend'] = os.path.join(
|
||||
git_core,
|
||||
'git-http-backend')
|
||||
|
||||
ret = self.run_function(
|
||||
'state.apply',
|
||||
mods='git_pillar.http',
|
||||
pillar=pillar)
|
||||
|
||||
if not os.path.exists(pillar['git_pillar']['git-http-backend']):
|
||||
self.fail(
|
||||
'{0} not found. Either git is not installed, or the test '
|
||||
'class needs to be updated.'.format(
|
||||
pillar['git_pillar']['git-http-backend']
|
||||
)
|
||||
if not os.path.exists(git_core):
|
||||
cls.tearDownClass()
|
||||
raise AssertionError(
|
||||
'{} not found. Either git is not installed, or the test '
|
||||
'class needs to be updated.'.format(git_core)
|
||||
)
|
||||
|
||||
pillar['git_pillar']['git-http-backend'] = os.path.join(git_core, 'git-http-backend')
|
||||
try:
|
||||
self.nginx_proc = self.wait_proc(name='nginx',
|
||||
search=self.nginx_conf)
|
||||
self.uwsgi_proc = self.wait_proc(name='uwsgi',
|
||||
search=self.uwsgi_conf)
|
||||
finally:
|
||||
# Do the assert after we check for the PID so that we can track
|
||||
# it regardless of whether or not something else in the SLS
|
||||
# failed (but the webserver still started).
|
||||
self.assertSaltTrueReturn(ret)
|
||||
if cls.prep_states_ran is False:
|
||||
ret = cls.cls_run_function('state.apply', mods='git_pillar.http', pillar=pillar)
|
||||
assert next(six.itervalues(ret))['result'] is True
|
||||
cls.prep_states_ran = True
|
||||
log.info('%s: States applied', cls.__name__)
|
||||
if cls.uwsgi_proc is not None:
|
||||
if not psutil.pid_exists(cls.uwsgi_proc.pid):
|
||||
log.warning('%s: uWsgi started but appears to be dead now. Will try to restart it.',
|
||||
cls.__name__)
|
||||
cls.uwsgi_proc = None
|
||||
if cls.uwsgi_proc is None:
|
||||
cls.uwsgi_proc = start_daemon(cls.uwsgi_bin, cls.config_dir, cls.uwsgi_port, UwsgiDaemon)
|
||||
log.info('\n\n\n%s: %s started\n\n\n', cls.__name__, cls.uwsgi_bin)
|
||||
if cls.nginx_proc is not None:
|
||||
if not psutil.pid_exists(cls.nginx_proc.pid):
|
||||
log.warning('%s: nginx started but appears to be dead now. Will try to restart it.',
|
||||
cls.__name__)
|
||||
cls.nginx_proc = None
|
||||
if cls.nginx_proc is None:
|
||||
cls.nginx_proc = start_daemon('nginx', cls.config_dir, cls.nginx_port, NginxDaemon)
|
||||
log.info('\n\n\n%s: nginx started\n\n\n', cls.__name__)
|
||||
except AssertionError:
|
||||
cls.tearDownClass()
|
||||
six.reraise(*sys.exc_info())
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
if cls.nginx_proc is not None:
|
||||
log.info('[%s] Stopping %s', cls.nginx_proc.log_prefix, cls.nginx_proc.__class__.__name__)
|
||||
terminate_process(cls.nginx_proc.pid, kill_children=True, slow_stop=True)
|
||||
log.info('[%s] %s stopped', cls.nginx_proc.log_prefix, cls.nginx_proc.__class__.__name__)
|
||||
cls.nginx_proc = None
|
||||
if cls.uwsgi_proc is not None:
|
||||
log.info('[%s] Stopping %s', cls.uwsgi_proc.log_prefix, cls.uwsgi_proc.__class__.__name__)
|
||||
terminate_process(cls.uwsgi_proc.pid, kill_children=True, slow_stop=True)
|
||||
log.info('[%s] %s stopped', cls.uwsgi_proc.log_prefix, cls.uwsgi_proc.__class__.__name__)
|
||||
cls.uwsgi_proc = None
|
||||
shutil.rmtree(cls.root_dir, ignore_errors=True)
|
||||
super(WebserverMixin, cls).tearDownClass()
|
||||
|
||||
|
||||
class GitTestBase(ModuleCase):
|
||||
|
@ -259,47 +531,10 @@ class GitTestBase(ModuleCase):
|
|||
Base class for all gitfs/git_pillar tests. Must be subclassed and paired
|
||||
with either SSHDMixin or WebserverMixin to provide the server.
|
||||
'''
|
||||
case = port = bare_repo = base_extra_repo = admin_repo = admin_extra_repo = None
|
||||
maxDiff = None
|
||||
git_opts = '-c user.name="Foo Bar" -c user.email=foo@bar.com'
|
||||
ext_opts = {}
|
||||
|
||||
# We need to temporarily skip pygit2 tests on EL7 until the EPEL packager
|
||||
# updates pygit2 to bring it up-to-date with libgit2.
|
||||
@requires_system_grains
|
||||
def is_el7(self, grains):
|
||||
return grains['os_family'] == 'RedHat' and grains['osmajorrelease'] == 7
|
||||
|
||||
# Cent OS 6 has too old a version of git to handle the make_repo code, as
|
||||
# it lacks the -c option for git itself.
|
||||
@requires_system_grains
|
||||
def is_pre_el7(self, grains):
|
||||
return grains['os_family'] == 'RedHat' and grains['osmajorrelease'] < 7
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.prep_server()
|
||||
|
||||
def setUp(self):
|
||||
# Make the test class available to the tearDownClass so we can clean up
|
||||
# after ourselves. This (and the gated block below) prevent us from
|
||||
# needing to spend the extra time creating an ssh server and user and
|
||||
# then tear them down separately for each test.
|
||||
self.update_class(self)
|
||||
if self.is_pre_el7(): # pylint: disable=E1120
|
||||
self.skipTest(
|
||||
'RHEL < 7 has too old a version of git to run these tests')
|
||||
|
||||
@classmethod
|
||||
def update_class(cls, case):
|
||||
'''
|
||||
Make the test class available to the tearDownClass. Note that this
|
||||
cannot be defined in a parent class and inherited, as this will cause
|
||||
the parent class to be modified.
|
||||
'''
|
||||
if getattr(cls, 'case') is None:
|
||||
setattr(cls, 'case', case)
|
||||
|
||||
def make_repo(self, root_dir, user='root'):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
@ -325,6 +560,9 @@ class GitPillarTestBase(GitTestBase, LoaderModuleMockMixin):
|
|||
'''
|
||||
Base class for all git_pillar tests
|
||||
'''
|
||||
bare_repo = bare_repo_backup = bare_extra_repo = bare_extra_repo_backup = None
|
||||
admin_repo = admin_repo_backup = admin_extra_repo = admin_extra_repo_backup = None
|
||||
|
||||
@requires_system_grains
|
||||
def setup_loader_modules(self, grains): # pylint: disable=W0221
|
||||
return {
|
||||
|
@ -338,7 +576,7 @@ class GitPillarTestBase(GitTestBase, LoaderModuleMockMixin):
|
|||
'''
|
||||
Run git_pillar with the specified configuration
|
||||
'''
|
||||
cachedir = tempfile.mkdtemp(dir=TMP)
|
||||
cachedir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
|
||||
self.addCleanup(shutil.rmtree, cachedir, ignore_errors=True)
|
||||
ext_pillar_opts = {'optimization_order': [0, 1, 2]}
|
||||
ext_pillar_opts.update(
|
||||
|
@ -358,12 +596,20 @@ class GitPillarTestBase(GitTestBase, LoaderModuleMockMixin):
|
|||
)
|
||||
|
||||
def make_repo(self, root_dir, user='root'):
|
||||
log.info('Creating test Git repo....')
|
||||
self.bare_repo = os.path.join(root_dir, 'repo.git')
|
||||
self.bare_repo_backup = '{}.backup'.format(self.bare_repo)
|
||||
self.admin_repo = os.path.join(root_dir, 'admin')
|
||||
self.admin_repo_backup = '{}.backup'.format(self.admin_repo)
|
||||
|
||||
for dirname in (self.bare_repo, self.admin_repo):
|
||||
shutil.rmtree(dirname, ignore_errors=True)
|
||||
|
||||
if os.path.exists(self.bare_repo_backup) and os.path.exists(self.admin_repo_backup):
|
||||
shutil.copytree(self.bare_repo_backup, self.bare_repo)
|
||||
shutil.copytree(self.admin_repo_backup, self.admin_repo)
|
||||
return
|
||||
|
||||
# Create bare repo
|
||||
self.run_function(
|
||||
'git.init',
|
||||
|
@ -498,14 +744,25 @@ class GitPillarTestBase(GitTestBase, LoaderModuleMockMixin):
|
|||
- mounted.bar
|
||||
'''))
|
||||
_push('top_mounted', 'add top_mounted branch')
|
||||
shutil.copytree(self.bare_repo, self.bare_repo_backup)
|
||||
shutil.copytree(self.admin_repo, self.admin_repo_backup)
|
||||
log.info('Test Git repo created.')
|
||||
|
||||
def make_extra_repo(self, root_dir, user='root'):
|
||||
log.info('Creating extra test Git repo....')
|
||||
self.bare_extra_repo = os.path.join(root_dir, 'extra_repo.git')
|
||||
self.bare_extra_repo_backup = '{}.backup'.format(self.bare_extra_repo)
|
||||
self.admin_extra_repo = os.path.join(root_dir, 'admin_extra')
|
||||
self.admin_extra_repo_backup = '{}.backup'.format(self.admin_extra_repo)
|
||||
|
||||
for dirname in (self.bare_extra_repo, self.admin_extra_repo):
|
||||
shutil.rmtree(dirname, ignore_errors=True)
|
||||
|
||||
if os.path.exists(self.bare_extra_repo_backup) and os.path.exists(self.admin_extra_repo_backup):
|
||||
shutil.copytree(self.bare_extra_repo_backup, self.bare_extra_repo)
|
||||
shutil.copytree(self.admin_extra_repo_backup, self.admin_extra_repo)
|
||||
return
|
||||
|
||||
# Create bare extra repo
|
||||
self.run_function(
|
||||
'git.init',
|
||||
|
@ -553,6 +810,23 @@ class GitPillarTestBase(GitTestBase, LoaderModuleMockMixin):
|
|||
motd: The force will be with you. Always.
|
||||
'''))
|
||||
_push('master', 'initial commit')
|
||||
shutil.copytree(self.bare_extra_repo, self.bare_extra_repo_backup)
|
||||
shutil.copytree(self.admin_extra_repo, self.admin_extra_repo_backup)
|
||||
log.info('Extra test Git repo created.')
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
super(GitPillarTestBase, cls).tearDownClass()
|
||||
for dirname in (cls.admin_repo,
|
||||
cls.admin_repo_backup,
|
||||
cls.admin_extra_repo,
|
||||
cls.admin_extra_repo_backup,
|
||||
cls.bare_repo,
|
||||
cls.bare_repo_backup,
|
||||
cls.bare_extra_repo,
|
||||
cls.bare_extra_repo_backup):
|
||||
if dirname is not None:
|
||||
shutil.rmtree(dirname, ignore_errors=True)
|
||||
|
||||
|
||||
class GitPillarSSHTestBase(GitPillarTestBase, SSHDMixin):
|
||||
|
@ -562,69 +836,22 @@ class GitPillarSSHTestBase(GitPillarTestBase, SSHDMixin):
|
|||
id_rsa_nopass = id_rsa_withpass = None
|
||||
git_ssh = '/tmp/git_ssh'
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
if cls.case is None:
|
||||
return
|
||||
if cls.case.sshd_proc is not None:
|
||||
cls.case.sshd_proc.send_signal(signal.SIGTERM)
|
||||
cls.case.run_state('user.absent', name=cls.username, purge=True)
|
||||
for dirname in (cls.sshd_config_dir, cls.case.admin_repo,
|
||||
cls.case.bare_repo):
|
||||
if dirname is not None:
|
||||
shutil.rmtree(dirname, ignore_errors=True)
|
||||
ssh_dir = os.path.expanduser('~/.ssh')
|
||||
for filename in (cls.id_rsa_nopass,
|
||||
cls.id_rsa_nopass + '.pub',
|
||||
cls.id_rsa_withpass,
|
||||
cls.id_rsa_withpass + '.pub',
|
||||
cls.git_ssh):
|
||||
try:
|
||||
os.remove(os.path.join(ssh_dir, filename))
|
||||
except OSError as exc:
|
||||
if exc.errno != errno.ENOENT:
|
||||
raise
|
||||
|
||||
def setUp(self):
|
||||
'''
|
||||
Create the SSH server and user, and create the git repo
|
||||
'''
|
||||
log.info('%s.setUp() started...', self.__class__.__name__)
|
||||
super(GitPillarSSHTestBase, self).setUp()
|
||||
self.sshd_proc = self.find_proc(name='sshd',
|
||||
search=self.sshd_config)
|
||||
self.sshd_bin = salt.utils.path.which('sshd')
|
||||
|
||||
if self.sshd_proc is None:
|
||||
self.spawn_server()
|
||||
|
||||
known_hosts_ret = self.run_function(
|
||||
'ssh.set_known_host',
|
||||
user=self.master_opts['user'],
|
||||
hostname='127.0.0.1',
|
||||
port=self.sshd_port,
|
||||
enc='ssh-rsa',
|
||||
fingerprint='fd:6f:7f:5d:06:6b:f2:06:0d:26:93:9e:5a:b5:19:46',
|
||||
hash_known_hosts=False,
|
||||
fingerprint_hash_type='md5',
|
||||
)
|
||||
if 'error' in known_hosts_ret:
|
||||
raise Exception(
|
||||
'Failed to add key to {0} user\'s known_hosts '
|
||||
'file: {1}'.format(
|
||||
self.master_opts['user'],
|
||||
known_hosts_ret['error']
|
||||
)
|
||||
)
|
||||
|
||||
root_dir = os.path.expanduser('~{0}'.format(self.username))
|
||||
if root_dir.startswith('~'):
|
||||
self.fail(
|
||||
raise AssertionError(
|
||||
'Unable to resolve homedir for user \'{0}\''.format(
|
||||
self.username
|
||||
)
|
||||
)
|
||||
self.make_repo(root_dir, user=self.username)
|
||||
self.make_extra_repo(root_dir, user=self.username)
|
||||
log.info('%s.setUp() complete.', self.__class__.__name__)
|
||||
|
||||
def get_pillar(self, ext_pillar_conf):
|
||||
'''
|
||||
|
@ -648,31 +875,13 @@ class GitPillarHTTPTestBase(GitPillarTestBase, WebserverMixin):
|
|||
'''
|
||||
Base class for GitPython and Pygit2 HTTP tests
|
||||
'''
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
for proc in (cls.case.nginx_proc, cls.case.uwsgi_proc):
|
||||
if proc is not None:
|
||||
try:
|
||||
proc.send_signal(signal.SIGTERM)
|
||||
time.sleep(1)
|
||||
if proc.is_running():
|
||||
proc.send_signal(signal.SIGKILL)
|
||||
except psutil.NoSuchProcess:
|
||||
pass
|
||||
shutil.rmtree(cls.root_dir, ignore_errors=True)
|
||||
|
||||
def setUp(self):
|
||||
'''
|
||||
Create and start the webserver, and create the git repo
|
||||
'''
|
||||
log.info('%s.setUp() started...', self.__class__.__name__)
|
||||
super(GitPillarHTTPTestBase, self).setUp()
|
||||
self.nginx_proc = self.find_proc(name='nginx',
|
||||
search=self.nginx_conf)
|
||||
self.uwsgi_proc = self.find_proc(name='uwsgi',
|
||||
search=self.uwsgi_conf)
|
||||
|
||||
if self.nginx_proc is None and self.uwsgi_proc is None:
|
||||
self.spawn_server() # pylint: disable=E1120
|
||||
|
||||
self.make_repo(self.repo_dir)
|
||||
self.make_extra_repo(self.repo_dir)
|
||||
log.info('%s.setUp() complete', self.__class__.__name__)
|
||||
|
|
|
@ -1064,17 +1064,25 @@ def requires_system_grains(func):
|
|||
case.
|
||||
'''
|
||||
@functools.wraps(func)
|
||||
def decorator(cls):
|
||||
def decorator(*args, **kwargs):
|
||||
if not hasattr(requires_system_grains, '__grains__'):
|
||||
if not hasattr(cls, 'run_function'):
|
||||
raise RuntimeError(
|
||||
'{0} does not have the \'run_function\' method which is '
|
||||
'necessary to collect the system grains'.format(
|
||||
cls.__class__.__name__
|
||||
)
|
||||
)
|
||||
requires_system_grains.__grains__ = func(cls, grains=cls.run_function('grains.items'))
|
||||
return requires_system_grains.__grains__
|
||||
import salt.config
|
||||
root_dir = tempfile.mkdtemp(dir=TMP)
|
||||
defaults = salt.config.DEFAULT_MINION_OPTS.copy()
|
||||
defaults.pop('conf_file')
|
||||
defaults.update({
|
||||
'root_dir': root_dir,
|
||||
'cachedir': 'cachedir',
|
||||
'sock_dir': 'sock',
|
||||
'pki_dir': 'pki',
|
||||
'log_file': 'logs/minion',
|
||||
'pidfile': 'pids/minion.pid'
|
||||
})
|
||||
opts = salt.config.minion_config(None, defaults=defaults)
|
||||
requires_system_grains.__grains__ = salt.loader.grains(opts)
|
||||
shutil.rmtree(root_dir, ignore_errors=True)
|
||||
kwargs['grains'] = requires_system_grains.__grains__
|
||||
return func(*args, **kwargs)
|
||||
return decorator
|
||||
|
||||
|
||||
|
|
|
@ -66,7 +66,7 @@ if sys.version_info < (2, 7):
|
|||
TestResult as _TestResult,
|
||||
TextTestResult as __TextTestResult
|
||||
)
|
||||
from unittest2.case import _id
|
||||
from unittest2.case import _id, SkipTest
|
||||
# pylint: enable=import-error
|
||||
|
||||
class NewStyleClassMixin(object):
|
||||
|
@ -110,7 +110,7 @@ else:
|
|||
TestResult,
|
||||
TextTestResult as _TextTestResult
|
||||
)
|
||||
from unittest.case import _id
|
||||
from unittest.case import _id, SkipTest
|
||||
|
||||
|
||||
class TestSuite(_TestSuite):
|
||||
|
|
Loading…
Add table
Reference in a new issue