2014-11-13 09:23:56 -06:00
|
|
|
# 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
|
|
|
|
|
|
|
|
from ipapython import ipautil
|
2018-08-02 06:45:19 -05:00
|
|
|
from ipatests.pytest_ipa.integration.config import Config, Domain
|
2015-11-13 12:04:41 -06:00
|
|
|
from ipalib.constants import MAX_DOMAIN_LEVEL
|
2014-11-13 09:23:56 -06:00
|
|
|
|
|
|
|
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),
|
2015-11-13 12:04:41 -06:00
|
|
|
_SettingInfo('domain_level', 'DOMAINLVL', MAX_DOMAIN_LEVEL),
|
2017-05-22 11:33:49 -05:00
|
|
|
|
|
|
|
_SettingInfo('log_journal_since', 'LOG_JOURNAL_SINCE', '-1h'),
|
2019-11-13 09:29:51 -06:00
|
|
|
# userspace FIPS mode
|
|
|
|
_SettingInfo('fips_mode', 'IPA_FIPS_MODE', False),
|
2014-11-13 09:23:56 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
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:
|
2015-08-20 08:45:55 -05:00
|
|
|
try:
|
|
|
|
import yaml
|
|
|
|
except ImportError as e:
|
2016-11-23 04:01:56 -06:00
|
|
|
raise ImportError(
|
|
|
|
"%s, please install PyYAML package to fix it" % e)
|
2014-11-13 09:23:56 -06:00
|
|
|
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
|
2018-09-26 05:24:33 -05:00
|
|
|
if isinstance(kwargs['ipv6'], str):
|
2014-11-13 09:23:56 -06:00
|
|
|
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
|
2019-03-27 11:24:26 -05:00
|
|
|
env.get('AD_env%s' % domain_index) or
|
|
|
|
env.get('AD_SUBDOMAIN_env%s' % domain_index) or
|
|
|
|
env.get('AD_TREEDOMAIN_env%s' % domain_index)):
|
2014-11-13 09:23:56 -06:00
|
|
|
|
|
|
|
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:
|
2019-03-27 11:24:26 -05:00
|
|
|
for domain_type in ('AD', 'AD_SUBDOMAIN', 'AD_TREEDOMAIN'):
|
|
|
|
if env.get('%s_env%s' % (domain_type, domain_index)):
|
|
|
|
config.domains.append(
|
|
|
|
domain_from_env(env, config, domain_index,
|
|
|
|
domain_type=domain_type))
|
|
|
|
break
|
2014-11-13 09:23:56 -06:00
|
|
|
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
|
2018-01-04 06:51:39 -06:00
|
|
|
env[name] = ''
|
|
|
|
|
2014-11-13 09:23:56 -06:00
|
|
|
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:
|
2019-03-27 11:24:26 -05:00
|
|
|
master_role = domain_type
|
2014-11-13 09:23:56 -06:00
|
|
|
|
|
|
|
env_suffix = '_env%s' % index
|
|
|
|
|
|
|
|
master_env = '%s%s' % (master_role, env_suffix)
|
2016-09-26 11:22:22 -05:00
|
|
|
hostname, _dot, domain_name = env[master_env].partition('.')
|
2014-11-13 09:23:56 -06:00
|
|
|
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({})
|
|
|
|
|
2018-01-03 12:09:32 -06:00
|
|
|
return cls(domain, hostname, role, ip=ip,
|
|
|
|
external_hostname=external_hostname)
|
2014-11-13 09:23:56 -06:00
|
|
|
|
|
|
|
|
|
|
|
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()])
|