Imported Upstream version 4.6.2
This commit is contained in:
284
ipatests/pytest_plugins/integration/__init__.py
Normal file
284
ipatests/pytest_plugins/integration/__init__.py
Normal file
@@ -0,0 +1,284 @@
|
||||
# Authors:
|
||||
# Petr Viktorin <pviktori@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2011 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Pytest plugin for IPA Integration tests"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
import shutil
|
||||
import re
|
||||
|
||||
import pytest
|
||||
from pytest_multihost import make_multihost_fixture
|
||||
|
||||
from ipapython import ipautil
|
||||
from ipatests.test_util import yield_fixture
|
||||
from .config import Config
|
||||
from .env_config import get_global_config
|
||||
from . import tasks
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("IPA integration tests")
|
||||
|
||||
group.addoption(
|
||||
'--logfile-dir', dest="logfile_dir", default=None,
|
||||
help="Directory to store integration test logs in.")
|
||||
|
||||
|
||||
def _get_logname_from_node(node):
|
||||
name = node.nodeid
|
||||
name = re.sub('\(\)/', '', name) # remove ()/
|
||||
name = re.sub('[()]', '', name) # and standalone brackets
|
||||
name = re.sub('(/|::)', '-', name)
|
||||
return name
|
||||
|
||||
|
||||
def collect_test_logs(node, logs_dict, test_config):
|
||||
"""Collect logs from a test
|
||||
|
||||
Calls collect_logs
|
||||
|
||||
:param node: The pytest collection node (request.node)
|
||||
:param logs_dict: Mapping of host to list of log filnames to collect
|
||||
:param test_config: Pytest configuration
|
||||
"""
|
||||
collect_logs(
|
||||
name=_get_logname_from_node(node),
|
||||
logs_dict=logs_dict,
|
||||
logfile_dir=test_config.getoption('logfile_dir'),
|
||||
beakerlib_plugin=test_config.pluginmanager.getplugin('BeakerLibPlugin'),
|
||||
)
|
||||
|
||||
|
||||
def collect_systemd_journal(node, hosts, test_config):
|
||||
"""Collect systemd journal from remote hosts
|
||||
|
||||
:param node: The pytest collection node (request.node)
|
||||
:param hosts: List of hosts from which to collect journal
|
||||
:param test_config: Pytest configuration
|
||||
"""
|
||||
name = _get_logname_from_node(node)
|
||||
logfile_dir = test_config.getoption('logfile_dir')
|
||||
|
||||
if logfile_dir is None:
|
||||
return
|
||||
|
||||
for host in hosts:
|
||||
logger.info("Collecting journal from: %s", host.hostname)
|
||||
|
||||
topdirname = os.path.join(logfile_dir, name, host.hostname)
|
||||
if not os.path.exists(topdirname):
|
||||
os.makedirs(topdirname)
|
||||
|
||||
# Get journal content
|
||||
cmd = host.run_command(
|
||||
['journalctl', '--since', host.config.log_journal_since],
|
||||
log_stdout=False, raiseonerr=False)
|
||||
if cmd.returncode:
|
||||
logger.error('An error occurred while collecting journal')
|
||||
continue
|
||||
|
||||
# Write journal to file
|
||||
with open(os.path.join(topdirname, "journal"), 'w') as f:
|
||||
f.write(cmd.stdout_text)
|
||||
|
||||
|
||||
def collect_logs(name, logs_dict, logfile_dir=None, beakerlib_plugin=None):
|
||||
"""Collect logs from remote hosts
|
||||
|
||||
Calls collect_logs
|
||||
|
||||
:param name: Name under which logs arecollected, e.g. name of the test
|
||||
:param logs_dict: Mapping of host to list of log filnames to collect
|
||||
:param logfile_dir: Directory to log to
|
||||
:param beakerlib_plugin:
|
||||
BeakerLibProcess or BeakerLibPlugin used to collect tests for BeakerLib
|
||||
|
||||
If neither logfile_dir nor beakerlib_plugin is given, no tests are
|
||||
collected.
|
||||
"""
|
||||
if logs_dict and (logfile_dir or beakerlib_plugin):
|
||||
|
||||
if logfile_dir:
|
||||
remove_dir = False
|
||||
else:
|
||||
logfile_dir = tempfile.mkdtemp()
|
||||
remove_dir = True
|
||||
|
||||
topdirname = os.path.join(logfile_dir, name)
|
||||
|
||||
for host, logs in logs_dict.items():
|
||||
logger.info('Collecting logs from: %s', host.hostname)
|
||||
dirname = os.path.join(topdirname, host.hostname)
|
||||
if not os.path.isdir(dirname):
|
||||
os.makedirs(dirname)
|
||||
tarname = os.path.join(dirname, 'logs.tar.xz')
|
||||
# get temporary file name
|
||||
cmd = host.run_command(['mktemp'])
|
||||
tmpname = cmd.stdout_text.strip()
|
||||
# Tar up the logs on the remote server
|
||||
cmd = host.run_command(
|
||||
['tar', 'cJvf', tmpname, '--ignore-failed-read'] + logs,
|
||||
log_stdout=False, raiseonerr=False)
|
||||
if cmd.returncode:
|
||||
logger.warning('Could not collect all requested logs')
|
||||
# fetch tar file
|
||||
with open(tarname, 'wb') as f:
|
||||
f.write(host.get_file_contents(tmpname))
|
||||
# delete from remote
|
||||
host.run_command(['rm', '-f', tmpname])
|
||||
# Unpack on the local side
|
||||
ipautil.run(['tar', 'xJvf', 'logs.tar.xz'], cwd=dirname,
|
||||
raiseonerr=False)
|
||||
os.unlink(tarname)
|
||||
|
||||
if beakerlib_plugin:
|
||||
# Use BeakerLib's rlFileSubmit on the indifidual files
|
||||
# The resulting submitted filename will be
|
||||
# $HOSTNAME-$FILENAME (with '/' replaced by '-')
|
||||
beakerlib_plugin.run_beakerlib_command(['pushd', topdirname])
|
||||
try:
|
||||
for dirpath, _dirnames, filenames in os.walk(topdirname):
|
||||
for filename in filenames:
|
||||
fullname = os.path.relpath(
|
||||
os.path.join(dirpath, filename), topdirname)
|
||||
logger.debug('Submitting file: %s', fullname)
|
||||
beakerlib_plugin.run_beakerlib_command(
|
||||
['rlFileSubmit', fullname])
|
||||
finally:
|
||||
beakerlib_plugin.run_beakerlib_command(['popd'])
|
||||
|
||||
if remove_dir:
|
||||
if beakerlib_plugin:
|
||||
# The BeakerLib process runs asynchronously, let it clean up
|
||||
# after it's done with the directory
|
||||
beakerlib_plugin.run_beakerlib_command(
|
||||
['rm', '-rvf', topdirname])
|
||||
else:
|
||||
shutil.rmtree(topdirname)
|
||||
|
||||
logs_dict.clear()
|
||||
|
||||
|
||||
@pytest.fixture(scope='class')
|
||||
def class_integration_logs():
|
||||
"""Internal fixture providing class-level logs_dict"""
|
||||
return {}
|
||||
|
||||
|
||||
@yield_fixture
|
||||
def integration_logs(class_integration_logs, request):
|
||||
"""Provides access to test integration logs, and collects after each test
|
||||
"""
|
||||
yield class_integration_logs
|
||||
hosts = class_integration_logs.keys()
|
||||
collect_test_logs(request.node, class_integration_logs, request.config)
|
||||
collect_systemd_journal(request.node, hosts, request.config)
|
||||
|
||||
|
||||
@yield_fixture(scope='class')
|
||||
def mh(request, class_integration_logs):
|
||||
"""IPA's multihost fixture object
|
||||
"""
|
||||
cls = request.cls
|
||||
|
||||
domain_description = {
|
||||
'type': 'IPA',
|
||||
'hosts': {
|
||||
'master': 1,
|
||||
'replica': cls.num_replicas,
|
||||
'client': cls.num_clients,
|
||||
},
|
||||
}
|
||||
domain_description['hosts'].update(
|
||||
{role: 1 for role in cls.required_extra_roles})
|
||||
|
||||
domain_descriptions = [domain_description]
|
||||
for _i in range(cls.num_ad_domains):
|
||||
domain_descriptions.append({
|
||||
'type': 'AD',
|
||||
'hosts': {'ad': 1, 'ad_subdomain': 1, 'ad_treedomain': 1},
|
||||
})
|
||||
|
||||
mh = make_multihost_fixture(
|
||||
request,
|
||||
domain_descriptions,
|
||||
config_class=Config,
|
||||
_config=get_global_config(),
|
||||
)
|
||||
|
||||
mh.domain = mh.config.domains[0]
|
||||
[mh.master] = mh.domain.hosts_by_role('master')
|
||||
mh.replicas = mh.domain.hosts_by_role('replica')
|
||||
mh.clients = mh.domain.hosts_by_role('client')
|
||||
|
||||
cls.logs_to_collect = class_integration_logs
|
||||
|
||||
def collect_log(host, filename):
|
||||
logger.info('Adding %s:%s to list of logs to collect',
|
||||
host.external_hostname, filename)
|
||||
class_integration_logs.setdefault(host, []).append(filename)
|
||||
|
||||
print(mh.config)
|
||||
for host in mh.config.get_all_hosts():
|
||||
host.add_log_collector(collect_log)
|
||||
logger.info('Preparing host %s', host.hostname)
|
||||
tasks.prepare_host(host)
|
||||
|
||||
setup_class(cls, mh)
|
||||
mh._pytestmh_request.addfinalizer(lambda: teardown_class(cls))
|
||||
|
||||
yield mh.install()
|
||||
|
||||
for host in cls.get_all_hosts():
|
||||
host.remove_log_collector(collect_log)
|
||||
|
||||
collect_test_logs(request.node, class_integration_logs, request.config)
|
||||
|
||||
|
||||
def setup_class(cls, mh):
|
||||
"""Add convenience attributes to the test class
|
||||
|
||||
This is deprecated in favor of the mh fixture.
|
||||
To be removed when no more tests using this.
|
||||
"""
|
||||
cls.domain = mh.domain
|
||||
cls.master = mh.master
|
||||
cls.replicas = mh.replicas
|
||||
cls.clients = mh.clients
|
||||
cls.ad_domains = mh.config.ad_domains
|
||||
|
||||
|
||||
def teardown_class(cls):
|
||||
"""Remove convenience attributes from the test class
|
||||
|
||||
This is deprecated in favor of the mh fixture.
|
||||
To be removed when no more tests using this.
|
||||
"""
|
||||
del cls.master
|
||||
del cls.replicas
|
||||
del cls.clients
|
||||
del cls.ad_domains
|
||||
del cls.domain
|
||||
167
ipatests/pytest_plugins/integration/config.py
Normal file
167
ipatests/pytest_plugins/integration/config.py
Normal file
@@ -0,0 +1,167 @@
|
||||
# Authors:
|
||||
# Petr Viktorin <pviktori@redhat.com>
|
||||
# Tomas Babej <tbabej@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2013 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Utilities for configuration of multi-master tests"""
|
||||
|
||||
import logging
|
||||
import random
|
||||
|
||||
import pytest_multihost.config
|
||||
|
||||
from ipapython.dn import DN
|
||||
from ipalib.constants import MAX_DOMAIN_LEVEL
|
||||
|
||||
|
||||
class Config(pytest_multihost.config.Config):
|
||||
extra_init_args = {
|
||||
'admin_name',
|
||||
'admin_password',
|
||||
'dirman_dn',
|
||||
'dirman_password',
|
||||
'nis_domain',
|
||||
'ntp_server',
|
||||
'ad_admin_name',
|
||||
'ad_admin_password',
|
||||
'dns_forwarder',
|
||||
'domain_level',
|
||||
'log_journal_since',
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
kwargs.setdefault('test_dir', '/root/ipatests')
|
||||
super(Config, self).__init__(**kwargs)
|
||||
|
||||
admin_password = kwargs.get('admin_password') or 'Secret123'
|
||||
|
||||
self.admin_name = kwargs.get('admin_name') or 'admin'
|
||||
self.admin_password = admin_password
|
||||
self.dirman_dn = DN(kwargs.get('dirman_dn') or 'cn=Directory Manager')
|
||||
self.dirman_password = kwargs.get('dirman_password') or admin_password
|
||||
self.nis_domain = kwargs.get('nis_domain') or 'ipatest'
|
||||
self.ntp_server = str(kwargs.get('ntp_server') or (
|
||||
'%s.pool.ntp.org' % random.randint(0, 3)))
|
||||
self.ad_admin_name = kwargs.get('ad_admin_name') or 'Administrator'
|
||||
self.ad_admin_password = kwargs.get('ad_admin_password') or 'Secret123'
|
||||
self.domain_level = kwargs.get('domain_level', MAX_DOMAIN_LEVEL)
|
||||
# 8.8.8.8 is probably the best-known public DNS
|
||||
self.dns_forwarder = kwargs.get('dns_forwarder') or '8.8.8.8'
|
||||
self.debug = False
|
||||
self.log_journal_since = kwargs.get('log_journal_since') or '-1h'
|
||||
if self.domain_level is None:
|
||||
self.domain_level = MAX_DOMAIN_LEVEL
|
||||
|
||||
def get_domain_class(self):
|
||||
return Domain
|
||||
|
||||
def get_logger(self, name):
|
||||
logger = logging.getLogger(name)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
return logger
|
||||
|
||||
@property
|
||||
def ad_domains(self):
|
||||
return [d for d in self.domains if d.type == 'AD']
|
||||
|
||||
def get_all_hosts(self):
|
||||
for domain in self.domains:
|
||||
for host in domain.hosts:
|
||||
yield host
|
||||
|
||||
def to_dict(self):
|
||||
extra_args = self.extra_init_args - {'dirman_dn'}
|
||||
result = super(Config, self).to_dict(extra_args)
|
||||
result['dirman_dn'] = str(self.dirman_dn)
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def from_env(cls, env):
|
||||
from ipatests.pytest_plugins.integration.env_config import config_from_env
|
||||
return config_from_env(env)
|
||||
|
||||
def to_env(self, **kwargs):
|
||||
from ipatests.pytest_plugins.integration.env_config import config_to_env
|
||||
return config_to_env(self, **kwargs)
|
||||
|
||||
|
||||
class Domain(pytest_multihost.config.Domain):
|
||||
"""Configuration for an IPA / AD domain"""
|
||||
def __init__(self, config, name, domain_type):
|
||||
self.type = str(domain_type)
|
||||
|
||||
self.config = config
|
||||
self.name = str(name)
|
||||
self.hosts = []
|
||||
|
||||
assert domain_type in ('IPA', 'AD')
|
||||
self.realm = self.name.upper()
|
||||
self.basedn = DN(*(('dc', p) for p in name.split('.')))
|
||||
|
||||
@property
|
||||
def static_roles(self):
|
||||
# Specific roles for each domain type are hardcoded
|
||||
if self.type == 'IPA':
|
||||
return ('master', 'replica', 'client', 'other')
|
||||
elif self.type == 'AD':
|
||||
return ('ad',)
|
||||
else:
|
||||
raise LookupError(self.type)
|
||||
|
||||
def get_host_class(self, host_dict):
|
||||
from ipatests.pytest_plugins.integration.host import Host, WinHost
|
||||
|
||||
if self.type == 'IPA':
|
||||
return Host
|
||||
elif self.type == 'AD':
|
||||
return WinHost
|
||||
else:
|
||||
raise LookupError(self.type)
|
||||
|
||||
@property
|
||||
def master(self):
|
||||
return self.host_by_role('master')
|
||||
|
||||
@property
|
||||
def masters(self):
|
||||
return self.hosts_by_role('master')
|
||||
|
||||
@property
|
||||
def replicas(self):
|
||||
return self.hosts_by_role('replica')
|
||||
|
||||
@property
|
||||
def clients(self):
|
||||
return self.hosts_by_role('client')
|
||||
|
||||
@property
|
||||
def ads(self):
|
||||
return self.hosts_by_role('ad')
|
||||
|
||||
@property
|
||||
def other_hosts(self):
|
||||
return self.hosts_by_role('other')
|
||||
|
||||
@classmethod
|
||||
def from_env(cls, env, config, index, domain_type):
|
||||
from ipatests.pytest_plugins.integration.env_config import domain_from_env
|
||||
return domain_from_env(env, config, index, domain_type)
|
||||
|
||||
def to_env(self, **kwargs):
|
||||
from ipatests.pytest_plugins.integration.env_config import domain_to_env
|
||||
return domain_to_env(self, **kwargs)
|
||||
366
ipatests/pytest_plugins/integration/env_config.py
Normal file
366
ipatests/pytest_plugins/integration/env_config.py
Normal file
@@ -0,0 +1,366 @@
|
||||
# Authors:
|
||||
# Petr Viktorin <pviktori@redhat.com>
|
||||
# Tomas Babej <tbabej@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2013 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Support for configuring multihost testing via environment variables
|
||||
|
||||
This is here to support tests configured for Beaker,
|
||||
such as the ones at https://github.com/freeipa/tests/
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import collections
|
||||
|
||||
import six
|
||||
|
||||
from ipapython import ipautil
|
||||
from ipatests.pytest_plugins.integration.config import Config, Domain
|
||||
from ipalib.constants import MAX_DOMAIN_LEVEL
|
||||
|
||||
TESTHOST_PREFIX = 'TESTHOST_'
|
||||
|
||||
|
||||
_SettingInfo = collections.namedtuple('Setting', 'name var_name default')
|
||||
_setting_infos = (
|
||||
# Directory on which test-specific files will be stored,
|
||||
_SettingInfo('test_dir', 'IPATEST_DIR', '/root/ipatests'),
|
||||
|
||||
# File with root's private RSA key for SSH (default: ~/.ssh/id_rsa)
|
||||
_SettingInfo('ssh_key_filename', 'IPA_ROOT_SSH_KEY', None),
|
||||
|
||||
# SSH password for root (used if root_ssh_key_filename is not set)
|
||||
_SettingInfo('ssh_password', 'IPA_ROOT_SSH_PASSWORD', None),
|
||||
|
||||
_SettingInfo('admin_name', 'ADMINID', 'admin'),
|
||||
_SettingInfo('admin_password', 'ADMINPW', 'Secret123'),
|
||||
_SettingInfo('dirman_dn', 'ROOTDN', 'cn=Directory Manager'),
|
||||
_SettingInfo('dirman_password', 'ROOTDNPWD', None),
|
||||
|
||||
# 8.8.8.8 is probably the best-known public DNS
|
||||
_SettingInfo('dns_forwarder', 'DNSFORWARD', '8.8.8.8'),
|
||||
_SettingInfo('nis_domain', 'NISDOMAIN', 'ipatest'),
|
||||
_SettingInfo('ntp_server', 'NTPSERVER', None),
|
||||
_SettingInfo('ad_admin_name', 'ADADMINID', 'Administrator'),
|
||||
_SettingInfo('ad_admin_password', 'ADADMINPW', 'Secret123'),
|
||||
|
||||
_SettingInfo('ipv6', 'IPv6SETUP', False),
|
||||
_SettingInfo('debug', 'IPADEBUG', False),
|
||||
_SettingInfo('domain_level', 'DOMAINLVL', MAX_DOMAIN_LEVEL),
|
||||
|
||||
_SettingInfo('log_journal_since', 'LOG_JOURNAL_SINCE', '-1h'),
|
||||
)
|
||||
|
||||
|
||||
def get_global_config(env=None):
|
||||
"""Create a test config from environment variables
|
||||
|
||||
If env is None, uses os.environ; otherwise env is an environment dict.
|
||||
|
||||
If IPATEST_YAML_CONFIG or IPATEST_JSON_CONFIG is set,
|
||||
configuration is read from the named file.
|
||||
For YAML, the PyYAML (python-yaml) library needs to be installed.
|
||||
|
||||
Otherwise, configuration is read from various curiously
|
||||
named environment variables:
|
||||
|
||||
See _setting_infos for test-wide settings
|
||||
|
||||
MASTER_env1: FQDN of the master
|
||||
REPLICA_env1: space-separated FQDNs of the replicas
|
||||
CLIENT_env1: space-separated FQDNs of the clients
|
||||
AD_env1: space-separated FQDNs of the Active Directories
|
||||
OTHER_env1: space-separated FQDNs of other hosts
|
||||
(same for _env2, _env3, etc)
|
||||
BEAKERREPLICA1_IP_env1: IP address of replica 1 in env 1
|
||||
(same for MASTER, CLIENT, or any extra defined ROLE)
|
||||
|
||||
For each machine that should be accessible to tests via extra roles,
|
||||
the following environment variable is necessary:
|
||||
|
||||
TESTHOST_<role>_env1: FQDN of the machine with the extra role <role>
|
||||
|
||||
You can also optionally specify the IP address of the host:
|
||||
BEAKER<role>_IP_env1: IP address of the machine of the extra role
|
||||
|
||||
The framework will try to resolve the hostname to its IP address
|
||||
if not passed via this environment variable.
|
||||
|
||||
Also see env_normalize() for alternate variable names
|
||||
"""
|
||||
if env is None:
|
||||
env = os.environ
|
||||
env = dict(env)
|
||||
|
||||
return config_from_env(env)
|
||||
|
||||
|
||||
def config_from_env(env):
|
||||
if 'IPATEST_YAML_CONFIG' in env:
|
||||
try:
|
||||
import yaml
|
||||
except ImportError as e:
|
||||
raise ImportError(
|
||||
"%s, please install PyYAML package to fix it" % e)
|
||||
with open(env['IPATEST_YAML_CONFIG']) as file:
|
||||
confdict = yaml.safe_load(file)
|
||||
return Config.from_dict(confdict)
|
||||
|
||||
if 'IPATEST_JSON_CONFIG' in env:
|
||||
with open(env['IPATEST_JSON_CONFIG']) as file:
|
||||
confdict = json.load(file)
|
||||
return Config.from_dict(confdict)
|
||||
|
||||
env_normalize(env)
|
||||
|
||||
kwargs = {s.name: env.get(s.var_name, s.default)
|
||||
for s in _setting_infos}
|
||||
kwargs['domains'] = []
|
||||
|
||||
# $IPv6SETUP needs to be 'TRUE' to enable ipv6
|
||||
if isinstance(kwargs['ipv6'], six.string_types):
|
||||
kwargs['ipv6'] = (kwargs['ipv6'].upper() == 'TRUE')
|
||||
|
||||
config = Config(**kwargs)
|
||||
|
||||
# Either IPA master or AD can define a domain
|
||||
|
||||
domain_index = 1
|
||||
while (env.get('MASTER_env%s' % domain_index) or
|
||||
env.get('AD_env%s' % domain_index)):
|
||||
|
||||
if env.get('MASTER_env%s' % domain_index):
|
||||
# IPA domain takes precedence to AD domain in case of conflict
|
||||
config.domains.append(domain_from_env(env, config, domain_index,
|
||||
domain_type='IPA'))
|
||||
else:
|
||||
config.domains.append(domain_from_env(env, config, domain_index,
|
||||
domain_type='AD'))
|
||||
domain_index += 1
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def config_to_env(config, simple=True):
|
||||
"""Convert this test config into environment variables"""
|
||||
try:
|
||||
env = collections.OrderedDict()
|
||||
except AttributeError:
|
||||
# Older Python versions
|
||||
env = {}
|
||||
|
||||
for setting in _setting_infos:
|
||||
value = getattr(config, setting.name)
|
||||
if value in (None, False):
|
||||
env[setting.var_name] = ''
|
||||
elif value is True:
|
||||
env[setting.var_name] = 'TRUE'
|
||||
else:
|
||||
env[setting.var_name] = str(value)
|
||||
|
||||
for domain in config.domains:
|
||||
env_suffix = '_env%s' % (config.domains.index(domain) + 1)
|
||||
env['DOMAIN%s' % env_suffix] = domain.name
|
||||
env['RELM%s' % env_suffix] = domain.realm
|
||||
env['BASEDN%s' % env_suffix] = str(domain.basedn)
|
||||
|
||||
for role in domain.roles:
|
||||
hosts = domain.hosts_by_role(role)
|
||||
|
||||
prefix = ('' if role in domain.static_roles
|
||||
else TESTHOST_PREFIX)
|
||||
|
||||
hostnames = ' '.join(h.hostname for h in hosts)
|
||||
env['%s%s%s' % (prefix, role.upper(), env_suffix)] = hostnames
|
||||
|
||||
ext_hostnames = ' '.join(h.external_hostname for h in hosts)
|
||||
env['BEAKER%s%s' % (role.upper(), env_suffix)] = ext_hostnames
|
||||
|
||||
ips = ' '.join(h.ip for h in hosts)
|
||||
env['BEAKER%s_IP%s' % (role.upper(), env_suffix)] = ips
|
||||
|
||||
for i, host in enumerate(hosts, start=1):
|
||||
suffix = '%s%s' % (role.upper(), i)
|
||||
prefix = ('' if role in domain.static_roles
|
||||
else TESTHOST_PREFIX)
|
||||
|
||||
ext_hostname = host.external_hostname
|
||||
env['%s%s%s' % (prefix, suffix,
|
||||
env_suffix)] = host.hostname
|
||||
env['BEAKER%s%s' % (suffix, env_suffix)] = ext_hostname
|
||||
env['BEAKER%s_IP%s' % (suffix, env_suffix)] = host.ip
|
||||
|
||||
if simple:
|
||||
# Simple Vars for simplicity and backwards compatibility with older
|
||||
# tests. This means no _env<NUM> suffix.
|
||||
if config.domains:
|
||||
default_domain = config.domains[0]
|
||||
if default_domain.master:
|
||||
env['MASTER'] = default_domain.master.hostname
|
||||
env['BEAKERMASTER'] = default_domain.master.external_hostname
|
||||
env['MASTERIP'] = default_domain.master.ip
|
||||
if default_domain.replicas:
|
||||
env['SLAVE'] = env['REPLICA'] = env['REPLICA_env1']
|
||||
env['BEAKERSLAVE'] = env['BEAKERREPLICA_env1']
|
||||
env['SLAVEIP'] = env['BEAKERREPLICA_IP_env1']
|
||||
if default_domain.clients:
|
||||
client = default_domain.clients[0]
|
||||
env['CLIENT'] = client.hostname
|
||||
env['BEAKERCLIENT'] = client.external_hostname
|
||||
if len(default_domain.clients) >= 2:
|
||||
client = default_domain.clients[1]
|
||||
env['CLIENT2'] = client.hostname
|
||||
env['BEAKERCLIENT2'] = client.external_hostname
|
||||
|
||||
return env
|
||||
|
||||
|
||||
def env_normalize(env):
|
||||
"""Fill env variables from alternate variable names
|
||||
|
||||
MASTER_env1 <- MASTER
|
||||
REPLICA_env1 <- REPLICA, SLAVE
|
||||
CLIENT_env1 <- CLIENT
|
||||
similarly for BEAKER* variants: BEAKERMASTER1_env1 <- BEAKERMASTER, etc.
|
||||
|
||||
CLIENT_env1 gets extended with CLIENT2 or CLIENT2_env1
|
||||
"""
|
||||
def coalesce(name, *other_names):
|
||||
"""If name is not set, set it to first existing env[other_name]"""
|
||||
if name not in env:
|
||||
for other_name in other_names:
|
||||
try:
|
||||
env[name] = env[other_name]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
return
|
||||
else:
|
||||
env[name] = ''
|
||||
coalesce('MASTER_env1', 'MASTER')
|
||||
coalesce('REPLICA_env1', 'REPLICA', 'SLAVE')
|
||||
coalesce('CLIENT_env1', 'CLIENT')
|
||||
|
||||
coalesce('BEAKERMASTER1_env1', 'BEAKERMASTER')
|
||||
coalesce('BEAKERREPLICA1_env1', 'BEAKERREPLICA', 'BEAKERSLAVE')
|
||||
coalesce('BEAKERCLIENT1_env1', 'BEAKERCLIENT')
|
||||
|
||||
def extend(name, name2):
|
||||
value = env.get(name2)
|
||||
if value and value not in env[name].split(' '):
|
||||
env[name] += ' ' + value
|
||||
extend('CLIENT_env1', 'CLIENT2')
|
||||
extend('CLIENT_env1', 'CLIENT2_env1')
|
||||
|
||||
|
||||
def domain_from_env(env, config, index, domain_type):
|
||||
# Roles available in the domain depend on the type of the domain
|
||||
# Unix machines are added only to the IPA domains, Windows machines
|
||||
# only to the AD domains
|
||||
if domain_type == 'IPA':
|
||||
master_role = 'MASTER'
|
||||
else:
|
||||
master_role = 'AD'
|
||||
|
||||
env_suffix = '_env%s' % index
|
||||
|
||||
master_env = '%s%s' % (master_role, env_suffix)
|
||||
hostname, _dot, domain_name = env[master_env].partition('.')
|
||||
domain = Domain(config, domain_name, domain_type)
|
||||
|
||||
for role in _roles_from_env(domain, env, env_suffix):
|
||||
prefix = '' if role in domain.static_roles else TESTHOST_PREFIX
|
||||
value = env.get('%s%s%s' % (prefix, role.upper(), env_suffix), '')
|
||||
|
||||
for host_index, hostname in enumerate(value.split(), start=1):
|
||||
host = host_from_env(env, domain, hostname, role,
|
||||
host_index, index)
|
||||
domain.hosts.append(host)
|
||||
|
||||
if not domain.hosts:
|
||||
raise ValueError('No hosts defined for %s' % env_suffix)
|
||||
|
||||
return domain
|
||||
|
||||
|
||||
def _roles_from_env(domain, env, env_suffix):
|
||||
for role in domain.static_roles:
|
||||
yield role
|
||||
|
||||
# Extra roles are defined via env variables of form TESTHOST_key_envX
|
||||
roles = set()
|
||||
for var in sorted(env):
|
||||
if var.startswith(TESTHOST_PREFIX) and var.endswith(env_suffix):
|
||||
variable_split = var.split('_')
|
||||
role_name = '_'.join(variable_split[1:-1])
|
||||
if (role_name and not role_name[-1].isdigit()):
|
||||
roles.add(role_name.lower())
|
||||
for role in sorted(roles):
|
||||
yield role
|
||||
|
||||
|
||||
def domain_to_env(domain, **kwargs):
|
||||
"""Return environment variables specific to this domain"""
|
||||
env = domain.config.to_env(**kwargs)
|
||||
|
||||
env['DOMAIN'] = domain.name
|
||||
env['RELM'] = domain.realm
|
||||
env['BASEDN'] = str(domain.basedn)
|
||||
|
||||
return env
|
||||
|
||||
|
||||
def host_from_env(env, domain, hostname, role, index, domain_index):
|
||||
ip = env.get('BEAKER%s%s_IP_env%s' %
|
||||
(role.upper(), index, domain_index), None)
|
||||
external_hostname = env.get(
|
||||
'BEAKER%s%s_env%s' % (role.upper(), index, domain_index), None)
|
||||
|
||||
cls = domain.get_host_class({})
|
||||
|
||||
return cls(domain, hostname, role, ip, external_hostname)
|
||||
|
||||
|
||||
def host_to_env(host, **kwargs):
|
||||
"""Return environment variables specific to this host"""
|
||||
env = host.domain.to_env(**kwargs)
|
||||
|
||||
index = host.domain.hosts.index(host) + 1
|
||||
domain_index = host.config.domains.index(host.domain) + 1
|
||||
|
||||
role = host.role.upper()
|
||||
if host.role != 'master':
|
||||
role += str(index)
|
||||
|
||||
env['MYHOSTNAME'] = host.hostname
|
||||
env['MYBEAKERHOSTNAME'] = host.external_hostname
|
||||
env['MYIP'] = host.ip
|
||||
|
||||
prefix = ('' if host.role in host.domain.static_roles
|
||||
else TESTHOST_PREFIX)
|
||||
env_suffix = '_env%s' % domain_index
|
||||
env['MYROLE'] = '%s%s%s' % (prefix, role, env_suffix)
|
||||
env['MYENV'] = str(domain_index)
|
||||
|
||||
return env
|
||||
|
||||
|
||||
def env_to_script(env):
|
||||
return ''.join(['export %s=%s\n' % (key, ipautil.shell_quote(value))
|
||||
for key, value in env.items()])
|
||||
70
ipatests/pytest_plugins/integration/host.py
Normal file
70
ipatests/pytest_plugins/integration/host.py
Normal file
@@ -0,0 +1,70 @@
|
||||
# Authors:
|
||||
# Petr Viktorin <pviktori@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2013 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Host class for integration testing"""
|
||||
|
||||
import pytest_multihost.host
|
||||
|
||||
from ipapython import ipaldap
|
||||
|
||||
|
||||
class Host(pytest_multihost.host.Host):
|
||||
"""Representation of a remote IPA host"""
|
||||
|
||||
@staticmethod
|
||||
def _make_host(domain, hostname, role, ip, external_hostname):
|
||||
# We need to determine the type of the host, this depends on the domain
|
||||
# type, as we assume all Unix machines are in the Unix domain and
|
||||
# all Windows machine in a AD domain
|
||||
|
||||
if domain.type == 'AD':
|
||||
cls = WinHost
|
||||
else:
|
||||
cls = Host
|
||||
|
||||
return cls(domain, hostname, role, ip, external_hostname)
|
||||
|
||||
def ldap_connect(self):
|
||||
"""Return an LDAPClient authenticated to this host as directory manager
|
||||
"""
|
||||
self.log.info('Connecting to LDAP at %s', self.external_hostname)
|
||||
ldap_uri = ipaldap.get_ldap_uri(self.external_hostname)
|
||||
ldap = ipaldap.LDAPClient(ldap_uri)
|
||||
binddn = self.config.dirman_dn
|
||||
self.log.info('LDAP bind as %s' % binddn)
|
||||
ldap.simple_bind(binddn, self.config.dirman_password)
|
||||
return ldap
|
||||
|
||||
@classmethod
|
||||
def from_env(cls, env, domain, hostname, role, index, domain_index):
|
||||
from ipatests.pytest_plugins.integration.env_config import host_from_env
|
||||
return host_from_env(env, domain, hostname, role, index, domain_index)
|
||||
|
||||
def to_env(self, **kwargs):
|
||||
from ipatests.pytest_plugins.integration.env_config import host_to_env
|
||||
return host_to_env(self, **kwargs)
|
||||
|
||||
|
||||
class WinHost(pytest_multihost.host.WinHost):
|
||||
"""
|
||||
Representation of a remote Windows host.
|
||||
|
||||
This serves as a sketch class once we move from manual preparation of
|
||||
Active Directory to the automated setup.
|
||||
"""
|
||||
1379
ipatests/pytest_plugins/integration/tasks.py
Normal file
1379
ipatests/pytest_plugins/integration/tasks.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user