Imported Debian patch 4.0.5-6~numeezy

This commit is contained in:
Alexandre Ellert
2016-02-17 15:07:45 +01:00
committed by Mario Fetka
parent c44de33144
commit 10dfc9587b
1203 changed files with 53869 additions and 241462 deletions

View File

@@ -19,29 +19,72 @@
"""Base class for FreeIPA integration tests"""
import pytest
import nose
from ipapython.ipa_log_manager import log_mgr
from ipatests.test_integration.config import get_global_config
from ipatests.test_integration import tasks
from pytest_sourceorder import ordered
from ipatests.order_plugin import ordered
log = log_mgr.get_logger(__name__)
@ordered
@pytest.mark.usefixtures('mh')
@pytest.mark.usefixtures('integration_logs')
class IntegrationTest(object):
num_replicas = 0
num_clients = 0
num_ad_domains = 0
required_extra_roles = []
topology = None
domain_level = None
@classmethod
def setup_class(cls):
pass
def get_resources(resource_container, resource_str, num_needed):
if len(resource_container) < num_needed:
raise nose.SkipTest(
'Not enough %s available (have %s, need %s)' %
(resource_str, len(resource_container), num_needed))
return resource_container[:num_needed]
config = get_global_config()
if not config.domains:
raise nose.SkipTest('Integration testing not configured')
cls.logs_to_collect = {}
cls.domain = config.domains[0]
# Check that we have enough resources available
cls.master = cls.domain.master
cls.replicas = get_resources(cls.domain.replicas, 'replicas',
cls.num_replicas)
cls.clients = get_resources(cls.domain.clients, 'clients',
cls.num_clients)
cls.ad_domains = get_resources(config.ad_domains, 'AD domains',
cls.num_ad_domains)
# Check that we have all required extra hosts at our disposal
available_extra_roles = [role for domain in cls.get_domains()
for role in domain.extra_roles]
missing_extra_roles = list(set(cls.required_extra_roles) -
set(available_extra_roles))
if missing_extra_roles:
raise nose.SkipTest("Not all required extra hosts available, "
"missing: %s, available: %s"
% (missing_extra_roles,
available_extra_roles))
for host in cls.get_all_hosts():
host.add_log_collector(cls.collect_log)
cls.prepare_host(host)
try:
cls.install()
except:
cls.uninstall()
raise
@classmethod
def host_by_role(cls, role):
@@ -55,35 +98,51 @@ class IntegrationTest(object):
@classmethod
def get_all_hosts(cls):
return ([cls.master] + cls.replicas + cls.clients +
[cls.host_by_role(r) for r in cls.required_extra_roles])
map(cls.host_by_role, cls.required_extra_roles))
@classmethod
def get_domains(cls):
return [cls.domain] + cls.ad_domains
@classmethod
def install(cls, mh):
if cls.domain_level is not None:
domain_level = cls.domain_level
else:
domain_level = cls.master.config.domain_level
def prepare_host(cls, host):
cls.log.info('Preparing host %s', host.hostname)
tasks.prepare_host(host)
@classmethod
def install(cls):
if cls.topology is None:
return
else:
tasks.install_topo(cls.topology,
cls.master, cls.replicas,
cls.clients, domain_level)
@classmethod
def teardown_class(cls):
pass
cls.master, cls.replicas, cls.clients)
@classmethod
def uninstall(cls, mh):
def teardown_class(cls):
for host in cls.get_all_hosts():
host.remove_log_collector(cls.collect_log)
try:
cls.uninstall()
finally:
del cls.master
del cls.replicas
del cls.clients
del cls.ad_domains
del cls.domain
@classmethod
def uninstall(cls):
tasks.uninstall_master(cls.master)
for replica in cls.replicas:
tasks.uninstall_master(replica)
for client in cls.clients:
tasks.uninstall_client(client)
@classmethod
def collect_log(cls, host, filename):
cls.log.info('Adding %s:%s to list of logs to collect' %
(host.external_hostname, filename))
cls.logs_to_collect.setdefault(host, []).append(filename)
IntegrationTest.log = log_mgr.get_logger(IntegrationTest())

View File

@@ -20,114 +20,412 @@
"""Utilities for configuration of multi-master tests"""
import os
import collections
import random
import json
import pytest_multihost.config
from ipapython import ipautil
from ipapython.dn import DN
from ipapython.ipa_log_manager import log_mgr
from ipalib.constants import MAX_DOMAIN_LEVEL
from ipatests.test_integration.util import check_config_dict_empty
from ipatests.test_integration.util import TESTHOST_PREFIX
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',
}
_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('root_ssh_key_filename', 'IPA_ROOT_SSH_KEY', None),
# SSH password for root (used if root_ssh_key_filename is not set)
_SettingInfo('root_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),
)
class Config(object):
def __init__(self, **kwargs):
kwargs.setdefault('test_dir', '/root/ipatests')
super(Config, self).__init__(**kwargs)
self.log = log_mgr.get_logger(self)
admin_password = kwargs.get('admin_password') or 'Secret123'
# This unfortunately duplicates information in _setting_infos,
# but is left here for the sake of static analysis.
self.test_dir = kwargs.get('test_dir', '/root/ipatests')
self.root_ssh_key_filename = kwargs.get('root_ssh_key_filename')
self.root_password = kwargs.get('root_password')
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.dns_forwarder = kwargs.get('dns_forwarder') or '8.8.8.8'
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
if self.domain_level is None:
self.domain_level = MAX_DOMAIN_LEVEL
self.ipv6 = bool(kwargs.get('ipv6', False))
self.debug = bool(kwargs.get('debug', False))
def get_domain_class(self):
return Domain
if not self.root_password and not self.root_ssh_key_filename:
self.root_ssh_key_filename = '~/.ssh/id_rsa'
def get_logger(self, name):
return log_mgr.get_logger(name)
self.domains = []
@property
def ad_domains(self):
return [d for d in self.domains if d.type == 'AD']
return filter(lambda d: d.type == 'AD', self.domains)
def get_all_hosts(self):
for domain in self.domains:
for host in domain.hosts:
yield host
@classmethod
def from_dict(cls, dct):
kwargs = {s.name: dct.pop(s.name, s.default) for s in _setting_infos}
self = cls(**kwargs)
for domain_dict in dct.pop('domains'):
self.domains.append(Domain.from_dict(domain_dict, self))
check_config_dict_empty(dct, 'config')
return self
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
dct = {'domains': [d.to_dict() for d in self.domains]}
for setting in _setting_infos:
value = getattr(self, setting.name)
if isinstance(value, DN):
value = str(value)
dct[setting.name] = value
return dct
@classmethod
def from_env(cls, env):
from ipatests.test_integration.env_config import config_from_env
return config_from_env(env)
"""Create a test config from environment variables
def to_env(self, **kwargs):
from ipatests.test_integration.env_config import config_to_env
return config_to_env(self, **kwargs)
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 'IPATEST_YAML_CONFIG' in env:
import yaml
with open(env['IPATEST_YAML_CONFIG']) as file:
data = yaml.safe_load(file)
return cls.from_dict(data)
if 'IPATEST_JSON_CONFIG' in env:
with open(env['IPATEST_JSON_CONFIG']) as file:
data = json.load(file)
return cls.from_dict(data)
env_normalize(env)
kwargs = {s.name: env.get(s.var_name, s.default)
for s in _setting_infos}
# $IPv6SETUP needs to be 'TRUE' to enable ipv6
if isinstance(kwargs['ipv6'], basestring):
kwargs['ipv6'] = (kwargs['ipv6'].upper() == 'TRUE')
self = cls(**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
self.domains.append(Domain.from_env(env, self, domain_index,
domain_type='IPA'))
else:
self.domains.append(Domain.from_env(env, self, domain_index,
domain_type='AD'))
domain_index += 1
return self
def to_env(self, 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(self, 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 self.domains:
env_suffix = '_env%s' % (self.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 self.domains:
default_domain = self.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 host_by_name(self, name):
for domain in self.domains:
try:
return domain.host_by_name(name)
except LookupError:
pass
raise LookupError(name)
class Domain(pytest_multihost.config.Domain):
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')
class Domain(object):
"""Configuration for an IPA / AD domain"""
def __init__(self, config, name, domain_type):
self.log = log_mgr.get_logger(self)
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 roles(self):
return sorted(set(host.role for host in self.hosts))
@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':
else:
return ('ad',)
else:
raise LookupError(self.type)
def get_host_class(self, host_dict):
from ipatests.test_integration.host import Host, WinHost
@property
def extra_roles(self):
return [role for role in self.roles if role not in self.static_roles]
if self.type == 'IPA':
return Host
elif self.type == 'AD':
return WinHost
def _roles_from_env(self, env, env_suffix):
for role in self.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
@classmethod
def from_dict(cls, dct, config):
from ipatests.test_integration.host import BaseHost
domain_type = dct.pop('type')
assert domain_type in ('IPA', 'AD')
domain_name = dct.pop('name')
self = cls(config, domain_name, domain_type)
for host_dict in dct.pop('hosts'):
host = BaseHost.from_dict(host_dict, self)
self.hosts.append(host)
check_config_dict_empty(dct, 'domain %s' % domain_name)
return self
def to_dict(self):
return {
'type': self.type,
'name': self.name,
'hosts': [h.to_dict() for h in self.hosts],
}
@classmethod
def from_env(cls, env, config, index, domain_type):
from ipatests.test_integration.host import BaseHost
# 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:
raise LookupError(self.type)
master_role = 'AD'
env_suffix = '_env%s' % index
master_env = '%s%s' % (master_role, env_suffix)
hostname, dot, domain_name = env[master_env].partition('.')
self = cls(config, domain_name, domain_type)
for role in self._roles_from_env(env, env_suffix):
prefix = '' if role in self.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 = BaseHost.from_env(env, self, hostname, role,
host_index, index)
self.hosts.append(host)
if not self.hosts:
raise ValueError('No hosts defined for %s' % env_suffix)
return self
def to_env(self, **kwargs):
"""Return environment variables specific to this domain"""
env = self.config.to_env(**kwargs)
env['DOMAIN'] = self.name
env['RELM'] = self.realm
env['BASEDN'] = str(self.basedn)
return env
def host_by_role(self, role):
if self.hosts_by_role(role):
return self.hosts_by_role(role)[0]
else:
raise LookupError(role)
def hosts_by_role(self, role):
return [h for h in self.hosts if h.role == role]
@property
def master(self):
@@ -153,11 +451,17 @@ class Domain(pytest_multihost.config.Domain):
def other_hosts(self):
return self.hosts_by_role('other')
@classmethod
def from_env(cls, env, config, index, domain_type):
from ipatests.test_integration.env_config import domain_from_env
return domain_from_env(env, config, index, domain_type)
def host_by_name(self, name):
for host in self.hosts:
if name in (host.hostname, host.external_hostname, host.shortname):
return host
raise LookupError(name)
def to_env(self, **kwargs):
from ipatests.test_integration.env_config import domain_to_env
return domain_to_env(self, **kwargs)
def env_to_script(env):
return ''.join(['export %s=%s\n' % (key, ipautil.shell_quote(value))
for key, value in env.items()])
def get_global_config():
return Config.from_env(os.environ)

View File

@@ -1,364 +0,0 @@
# 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.test_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),
)
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.message)
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()])

View File

@@ -19,13 +19,93 @@
"""Host class for integration testing"""
import pytest_multihost.host
import os
import socket
from ipapython.ipaldap import IPAdmin
from ipapython import ipautil
from ipapython.ipa_log_manager import log_mgr
from ipatests.test_integration import transport
from ipatests.test_integration.util import check_config_dict_empty
from ipatests.test_integration.util import TESTHOST_PREFIX
class Host(pytest_multihost.host.Host):
class BaseHost(object):
"""Representation of a remote IPA host"""
transport_class = None
def __init__(self, domain, hostname, role, ip=None,
external_hostname=None):
self.domain = domain
self.role = str(role)
shortname, dot, ext_domain = hostname.partition('.')
self.shortname = shortname
self.hostname = (hostname[:-1]
if hostname.endswith('.')
else shortname + '.' + self.domain.name)
self.external_hostname = str(external_hostname or hostname)
self.netbios = self.domain.name.split('.')[0].upper()
self.logger_name = '%s.%s.%s' % (
self.__module__, type(self).__name__, shortname)
self.log = log_mgr.get_logger(self.logger_name)
if ip:
self.ip = str(ip)
else:
if self.config.ipv6:
# $(dig +short $M $rrtype|tail -1)
stdout, stderr, returncode = ipautil.run(
['dig', '+short', self.external_hostname, 'AAAA'])
self.ip = stdout.splitlines()[-1].strip()
else:
try:
self.ip = socket.gethostbyname(self.external_hostname)
except socket.gaierror:
self.ip = None
if not self.ip:
raise RuntimeError('Could not determine IP address of %s' %
self.external_hostname)
self.root_password = self.config.root_password
self.root_ssh_key_filename = self.config.root_ssh_key_filename
self.host_key = None
self.ssh_port = 22
self.env_sh_path = os.path.join(domain.config.test_dir, 'env.sh')
self.log_collectors = []
def __str__(self):
template = ('<{s.__class__.__name__} {s.hostname} ({s.role})>')
return template.format(s=self)
def __repr__(self):
template = ('<{s.__module__}.{s.__class__.__name__} '
'{s.hostname} ({s.role})>')
return template.format(s=self)
def add_log_collector(self, collector):
"""Register a log collector for this host"""
self.log_collectors.append(collector)
def remove_log_collector(self, collector):
"""Unregister a log collector"""
self.log_collectors.remove(collector)
@classmethod
def from_env(cls, 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)
return cls._make_host(domain, hostname, role, ip, external_hostname)
@staticmethod
def _make_host(domain, hostname, role, ip, external_hostname):
@@ -40,6 +120,84 @@ class Host(pytest_multihost.host.Host):
return cls(domain, hostname, role, ip, external_hostname)
@classmethod
def from_dict(cls, dct, domain):
if isinstance(dct, basestring):
dct = {'name': dct}
try:
role = dct.pop('role').lower()
except KeyError:
role = domain.static_roles[0]
hostname = dct.pop('name')
if '.' not in hostname:
hostname = '.'.join((hostname, domain.name))
ip = dct.pop('ip', None)
external_hostname = dct.pop('external_hostname', None)
check_config_dict_empty(dct, 'host %s' % hostname)
return cls._make_host(domain, hostname, role, ip, external_hostname)
def to_dict(self):
return {
'name': str(self.hostname),
'ip': self.ip,
'role': self.role,
'external_hostname': self.external_hostname,
}
@property
def config(self):
return self.domain.config
def to_env(self, **kwargs):
"""Return environment variables specific to this host"""
env = self.domain.to_env(**kwargs)
index = self.domain.hosts.index(self) + 1
domain_index = self.config.domains.index(self.domain) + 1
role = self.role.upper()
if self.role != 'master':
role += str(index)
env['MYHOSTNAME'] = self.hostname
env['MYBEAKERHOSTNAME'] = self.external_hostname
env['MYIP'] = self.ip
prefix = ('' if self.role in self.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
@property
def transport(self):
try:
return self._transport
except AttributeError:
cls = self.transport_class
if cls:
# transport_class is None in the base class and must be
# set in subclasses.
# Pylint reports that calling None will fail
self._transport = cls(self) # pylint: disable=E1102
else:
raise NotImplementedError('transport class not available')
return self._transport
def get_file_contents(self, filename):
"""Shortcut for transport.get_file_contents"""
return self.transport.get_file_contents(filename)
def put_file_contents(self, filename, contents):
"""Shortcut for transport.put_file_contents"""
self.transport.put_file_contents(filename, contents)
def ldap_connect(self):
"""Return an LDAPClient authenticated to this host as directory manager
"""
@@ -50,20 +208,80 @@ class Host(pytest_multihost.host.Host):
ldap.do_simple_bind(binddn, self.config.dirman_password)
return ldap
@classmethod
def from_env(cls, env, domain, hostname, role, index, domain_index):
from ipatests.test_integration.env_config import host_from_env
return host_from_env(env, domain, hostname, role, index, domain_index)
def collect_log(self, filename):
for collector in self.log_collectors:
collector(self, filename)
def to_env(self, **kwargs):
from ipatests.test_integration.env_config import host_to_env
return host_to_env(self, **kwargs)
def run_command(self, argv, set_env=True, stdin_text=None,
log_stdout=True, raiseonerr=True,
cwd=None):
"""Run the given command on this host
Returns a Shell instance. The command will have already run in the
shell when this method returns, so its stdout_text, stderr_text, and
returncode attributes will be available.
:param argv: Command to run, as either a Popen-style list, or a string
containing a shell script
:param set_env: If true, env.sh exporting configuration variables will
be sourced before running the command.
:param stdin_text: If given, will be written to the command's stdin
:param log_stdout: If false, standard output will not be logged
(but will still be available as cmd.stdout_text)
:param raiseonerr: If true, an exception will be raised if the command
does not exit with return code 0
:param cwd: The working directory for the command
"""
raise NotImplementedError()
class WinHost(pytest_multihost.host.WinHost):
class Host(BaseHost):
"""A Unix host"""
transport_class = transport.SSHTransport
def run_command(self, argv, set_env=True, stdin_text=None,
log_stdout=True, raiseonerr=True,
cwd=None):
# This will give us a Bash shell
command = self.transport.start_shell(argv, log_stdout=log_stdout)
# Set working directory
if cwd is None:
cwd = self.config.test_dir
command.stdin.write('cd %s\n' % ipautil.shell_quote(cwd))
# Set the environment
if set_env:
command.stdin.write('. %s\n' %
ipautil.shell_quote(self.env_sh_path))
command.stdin.write('set -e\n')
if isinstance(argv, basestring):
# Run a shell command given as a string
command.stdin.write('(')
command.stdin.write(argv)
command.stdin.write(')')
else:
# Run a command given as a popen-style list (no shell expansion)
for arg in argv:
command.stdin.write(ipautil.shell_quote(arg))
command.stdin.write(' ')
command.stdin.write(';exit\n')
if stdin_text:
command.stdin.write(stdin_text)
command.stdin.flush()
command.wait(raiseonerr=raiseonerr)
return command
class WinHost(BaseHost):
"""
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.
"""
pass

View File

@@ -24,55 +24,22 @@ import textwrap
import re
import collections
import itertools
import tempfile
import time
import StringIO
import dns
from ldif import LDIFWriter
from SSSDConfig import SSSDConfig
from six import StringIO
from ipapython import ipautil
from ipaplatform.paths import paths
from ipapython.dn import DN
from ipapython.ipa_log_manager import log_mgr
from ipatests.test_integration import util
from ipatests.test_integration.env_config import env_to_script
from ipatests.test_integration.config import env_to_script
from ipatests.test_integration.host import Host
from ipalib import errors
from ipalib.util import get_reverse_zone_default, verify_host_resolvable
from ipalib.constants import DOMAIN_SUFFIX_NAME
from ipalib.constants import DOMAIN_LEVEL_0
log = log_mgr.get_logger(__name__)
def check_arguments_are(slice, instanceof):
"""
:param: slice - tuple of integers denoting the beginning and the end
of argument list to be checked
:param: instanceof - name of the class the checked arguments should be
instances of
Example: @check_arguments_are((1, 3), int) will check that the second
and third arguments are integers
"""
def wrapper(func):
def wrapped(*args):
for i in args[slice[0]:slice[1]]:
assert isinstance(i, instanceof), "Wrong type: %s: %s" % (i, type(i))
return func(*args)
return wrapped
return wrapper
def prepare_reverse_zone(host, ip):
zone = get_reverse_zone_default(ip)
result = host.run_command(["ipa",
"dnszone-add",
zone], raiseonerr=False)
if result.returncode > 0:
log.warning(result.stderr_text)
return zone, result.returncode
def prepare_host(host):
if isinstance(host, Host):
env_filename = os.path.join(host.config.test_dir, 'env.sh')
@@ -81,24 +48,14 @@ def prepare_host(host):
host.run_command(['true'], set_env=False)
host.collect_log(env_filename)
try:
host.transport.mkdir_recursive(host.config.test_dir)
except IOError:
# The folder already exists
pass
host.transport.mkdir_recursive(host.config.test_dir)
host.put_file_contents(env_filename, env_to_script(host.to_env()))
def allow_sync_ptr(host):
kinit_admin(host)
host.run_command(["ipa", "dnsconfig-mod", "--allow-sync-ptr=true"],
raiseonerr=False)
def apply_common_fixes(host):
fix_etc_hosts(host)
fix_hostname(host)
prepare_host(host)
fix_resolv_conf(host)
def backup_file(host, filename):
@@ -122,14 +79,12 @@ def fix_etc_hosts(host):
backup_file(host, paths.HOSTS)
contents = host.get_file_contents(paths.HOSTS)
# Remove existing mentions of the host's FQDN, short name, and IP
# Removing of IP must be done as first, otherwise hosts file may be
# corrupted
contents = re.sub('^%s.*' % re.escape(host.ip), '', contents,
flags=re.MULTILINE)
contents = re.sub('\s%s(\s|$)' % re.escape(host.hostname), ' ', contents,
flags=re.MULTILINE)
contents = re.sub('\s%s(\s|$)' % re.escape(host.shortname), ' ', contents,
flags=re.MULTILINE)
contents = re.sub('^%s.*' % re.escape(host.ip), '', contents,
flags=re.MULTILINE)
# Add the host's info again
contents += '\n%s %s %s\n' % (host.ip, host.hostname, host.shortname)
log.debug('Writing the following to /etc/hosts:\n%s', contents)
@@ -145,14 +100,16 @@ def fix_hostname(host):
host.run_command('hostname > %s' % ipautil.shell_quote(backupname))
def host_service_active(host, service):
res = host.run_command(['systemctl', 'is-active', '--quiet', service],
raiseonerr=False)
if res.returncode == 0:
return True
else:
return False
def fix_resolv_conf(host):
backup_file(host, paths.RESOLV_CONF)
lines = host.get_file_contents(paths.RESOLV_CONF).splitlines()
lines = ['#' + l if l.startswith('nameserver') else l for l in lines]
for other_host in host.domain.hosts:
if other_host.role in ('master', 'replica'):
lines.append('nameserver %s' % other_host.ip)
contents = '\n'.join(lines)
log.debug('Writing the following to /etc/resolv.conf:\n%s', contents)
host.put_file_contents(paths.RESOLV_CONF, contents)
def fix_apache_semaphores(master):
@@ -161,8 +118,7 @@ def fix_apache_semaphores(master):
if systemd_available:
master.run_command(['systemctl', 'stop', 'httpd'], raiseonerr=False)
else:
master.run_command([paths.SBIN_SERVICE, 'httpd', 'stop'],
raiseonerr=False)
master.run_command([paths.SBIN_SERVICE, 'httpd', 'stop'], raiseonerr=False)
master.run_command('for line in `ipcs -s | grep apache | cut -d " " -f 2`; '
'do ipcrm -s $line; done', raiseonerr=False)
@@ -228,10 +184,7 @@ def enable_replication_debugging(host):
stdin_text=logging_ldif)
def install_master(host, setup_dns=True, setup_kra=False, extra_args=(),
domain_level=None):
if domain_level is None:
domain_level = host.config.domain_level
def install_master(host):
host.collect_log(paths.IPASERVER_INSTALL_LOG)
host.collect_log(paths.IPACLIENT_INSTALL_LOG)
inst = host.domain.realm.replace('.', '-')
@@ -241,129 +194,48 @@ def install_master(host, setup_dns=True, setup_kra=False, extra_args=(),
apply_common_fixes(host)
fix_apache_semaphores(host)
args = [
'ipa-server-install', '-U',
'-n', host.domain.name,
'-r', host.domain.realm,
'-p', host.config.dirman_password,
'-a', host.config.admin_password,
"--domain-level=%i" % domain_level,
]
host.run_command(['ipa-server-install', '-U',
'-r', host.domain.name,
'-p', host.config.dirman_password,
'-a', host.config.admin_password,
'--setup-dns',
'--forwarder', host.config.dns_forwarder])
if setup_dns:
args.extend([
'--setup-dns',
'--forwarder', host.config.dns_forwarder,
'--auto-reverse'
])
args.extend(extra_args)
host.run_command(args)
enable_replication_debugging(host)
setup_sssd_debugging(host)
if setup_kra:
args = [
"ipa-kra-install",
"-p", host.config.dirman_password,
"-U",
]
host.run_command(args)
kinit_admin(host)
def get_replica_filename(replica):
return os.path.join(replica.config.test_dir, 'replica-info.gpg')
def domainlevel(host):
# Dynamically determines the domainlevel on master. Needed for scenarios
# when domainlevel is changed during the test execution.
result = host.run_command(['ipa', 'domainlevel-get'], raiseonerr=False)
level = 0
domlevel_re = re.compile('.*(\d)')
if result.returncode == 0:
# "domainlevel-get" command doesn't exist on ipa versions prior to 4.3
level = int(domlevel_re.findall(result.stdout_text)[0])
return level
def master_authoritative_for_client_domain(master, client):
zone = ".".join(client.hostname.split('.')[1:])
result = master.run_command(["ipa", "dnszone-show", zone],
raiseonerr=False)
if result.returncode == 0:
return True
else:
return False
def replica_prepare(master, replica):
fix_apache_semaphores(replica)
prepare_reverse_zone(master, replica.ip)
args = ['ipa-replica-prepare',
'-p', replica.config.dirman_password,
replica.hostname]
if master_authoritative_for_client_domain(master, replica):
args.extend(['--ip-address', replica.ip])
master.run_command(args)
replica_bundle = master.get_file_contents(
paths.REPLICA_INFO_GPG_TEMPLATE % replica.hostname)
replica_filename = get_replica_filename(replica)
replica.put_file_contents(replica_filename, replica_bundle)
def install_replica(master, replica, setup_ca=True, setup_dns=False,
setup_kra=False, extra_args=(), domain_level=None):
if domain_level is None:
domain_level = domainlevel(master)
apply_common_fixes(replica)
def install_replica(master, replica, setup_ca=True):
replica.collect_log(paths.IPAREPLICA_INSTALL_LOG)
replica.collect_log(paths.IPAREPLICA_CONNCHECK_LOG)
allow_sync_ptr(master)
# Otherwise ipa-client-install would not create a PTR
# and replica installation would fail
apply_common_fixes(replica)
fix_apache_semaphores(replica)
master.run_command(['ipa-replica-prepare',
'-p', replica.config.dirman_password,
'--ip-address', replica.ip,
replica.hostname])
replica_bundle = master.get_file_contents(
paths.REPLICA_INFO_GPG_TEMPLATE % replica.hostname)
replica_filename = os.path.join(replica.config.test_dir,
'replica-info.gpg')
replica.put_file_contents(replica_filename, replica_bundle)
args = ['ipa-replica-install', '-U',
'--setup-ca',
'-p', replica.config.dirman_password,
'-w', replica.config.admin_password]
'-w', replica.config.admin_password,
'--ip-address', replica.ip,
replica_filename]
if setup_ca:
args.append('--setup-ca')
if setup_dns:
args.extend([
'--setup-dns',
'--forwarder', replica.config.dns_forwarder
])
if master_authoritative_for_client_domain(master, replica):
args.extend(['--ip-address', replica.ip])
args.extend(extra_args)
if domain_level == DOMAIN_LEVEL_0:
# prepare the replica file on master and put it to replica, AKA "old way"
replica_prepare(master, replica)
replica_filename = get_replica_filename(replica)
args.append(replica_filename)
else:
# install client on a replica machine and then promote it to replica
install_client(master, replica)
fix_apache_semaphores(replica)
args.extend(['-r', replica.domain.realm])
replica.run_command(args)
enable_replication_debugging(replica)
setup_sssd_debugging(replica)
if setup_kra:
assert setup_ca, "CA must be installed on replica with KRA"
args = [
"ipa-kra-install",
"-p", replica.config.dirman_password,
"-U",
]
if domainlevel(master) == DOMAIN_LEVEL_0:
args.append(replica_filename)
replica.run_command(args)
kinit_admin(replica)
@@ -371,14 +243,6 @@ def install_client(master, client, extra_args=()):
client.collect_log(paths.IPACLIENT_INSTALL_LOG)
apply_common_fixes(client)
allow_sync_ptr(master)
# Now, for the situations where a client resides in a different subnet from
# master, we need to explicitly tell master to create a reverse zone for
# the client and enable dynamic updates for this zone.
zone, error = prepare_reverse_zone(master, client.ip)
if not error:
master.run_command(["ipa", "dnszone-mod", zone,
"--dynamic-update=TRUE"])
client.run_command(['ipa-client-install', '-U',
'--domain', client.domain.name,
@@ -386,7 +250,7 @@ def install_client(master, client, extra_args=()):
'-p', client.config.admin_name,
'-w', client.config.admin_password,
'--server', master.hostname]
+ list(extra_args))
+ list(extra_args))
setup_sssd_debugging(client)
kinit_admin(client)
@@ -414,18 +278,11 @@ def install_adtrust(host):
# Restart named because it lost connection to dirsrv
# (Directory server restarts during the ipa-adtrust-install)
# we use two services named and named-pkcs11,
# if named is masked restart named-pkcs11
result = host.run_command(['systemctl', 'is-enabled', 'named'],
raiseonerr=False)
if result.stdout_text.startswith("masked"):
host.run_command(['systemctl', 'restart', 'named-pkcs11'])
else:
host.run_command(['systemctl', 'restart', 'named'])
host.run_command(['systemctl', 'restart', 'named'])
# Check that named is running and has loaded the information from LDAP
dig_command = ['dig', 'SRV', '+short', '@localhost',
'_ldap._tcp.%s' % host.domain.name]
'_ldap._tcp.%s' % host.domain.name]
dig_output = '0 100 389 %s.' % host.hostname
dig_test = lambda x: re.search(re.escape(dig_output), x)
@@ -497,9 +354,9 @@ def establish_trust_with_ad(master, ad, extra_args=()):
master.run_command(['smbcontrol', 'all', 'debug', '100'])
util.run_repeatedly(master,
['ipa', 'trust-add',
'--type', 'ad', ad.domain.name,
'--admin', 'Administrator',
'--password'] + list(extra_args),
'--type', 'ad', ad.domain.name,
'--admin', 'Administrator',
'--password'] + list(extra_args),
stdin_text=master.config.ad_admin_password)
master.run_command(['smbcontrol', 'all', 'debug', '1'])
clear_sssd_cache(master)
@@ -552,14 +409,15 @@ def setup_sssd_debugging(host):
# First, remove any previous occurences
host.run_command(['sed', '-i',
'/debug_level = 7/d',
paths.SSSD_CONF],
raiseonerr=False)
paths.SSSD_CONF
], raiseonerr=False)
# Add the debug directive to each section
host.run_command(['sed', '-i',
'/\[*\]/ a\debug_level = 7',
paths.SSSD_CONF],
raiseonerr=False)
paths.SSSD_CONF
], raiseonerr=False)
host.collect_log('/var/log/sssd/*')
@@ -567,47 +425,6 @@ def setup_sssd_debugging(host):
clear_sssd_cache(host)
def modify_sssd_conf(host, domain, mod_dict, provider='ipa',
provider_subtype=None):
"""
modify options in a single domain section of host's sssd.conf
:param host: multihost.Host object
:param domain: domain section name to modify
:param mod_dict: dictionary of options which will be passed to
SSSDDomain.set_option(). To remove an option specify its value as
None
:param provider: provider backend to set. Defaults to ipa
:param provider_subtype: backend subtype (e.g. id or sudo), will be added
to the domain config if not present
"""
try:
temp_config_file = tempfile.mkstemp()[1]
current_config = host.transport.get_file_contents(paths.SSSD_CONF)
with open(temp_config_file, 'wb') as f:
f.write(current_config)
sssd_config = SSSDConfig()
sssd_config.import_config(temp_config_file)
sssd_domain = sssd_config.get_domain(domain)
if provider_subtype is not None:
sssd_domain.add_provider(provider, provider_subtype)
for m in mod_dict:
sssd_domain.set_option(m, mod_dict[m])
sssd_config.save_domain(sssd_domain)
new_config = sssd_config.dump(sssd_config.opts).encode('utf-8')
host.transport.put_file_contents(paths.SSSD_CONF, new_config)
finally:
try:
os.remove(temp_config_file)
except OSError:
pass
def clear_sssd_cache(host):
"""
Clears SSSD cache by removing the cache files. Restarts SSSD.
@@ -644,48 +461,26 @@ def sync_time(host, server):
host.run_command(['ntpdate', server.hostname])
def connect_replica(master, replica, domain_level=None):
if domain_level is None:
domain_level = master.config.domain_level
if domain_level == DOMAIN_LEVEL_0:
replica.run_command(['ipa-replica-manage', 'connect', master.hostname])
else:
kinit_admin(master)
master.run_command(["ipa", "topologysegment-add", DOMAIN_SUFFIX_NAME,
"%s-to-%s" % (master.hostname, replica.hostname),
"--leftnode=%s" % master.hostname,
"--rightnode=%s" % replica.hostname
])
def connect_replica(master, replica):
kinit_admin(replica)
replica.run_command(['ipa-replica-manage', 'connect', master.hostname])
def disconnect_replica(master, replica, domain_level=None):
if domain_level is None:
domain_level = master.config.domain_level
if domain_level == DOMAIN_LEVEL_0:
replica.run_command(['ipa-replica-manage', 'disconnect', master.hostname])
else:
kinit_admin(master)
master.run_command(["ipa", "topologysegment-del", DOMAIN_SUFFIX_NAME,
"%s-to-%s" % (master.hostname, replica.hostname),
"--continue"
])
def disconnect_replica(master, replica):
kinit_admin(replica)
replica.run_command(['ipa-replica-manage', 'disconnect', master.hostname])
def kinit_admin(host):
host.run_command(['kinit', 'admin'],
stdin_text=host.config.admin_password)
stdin_text=host.config.admin_password)
def uninstall_master(host, ignore_topology_disconnect=True):
def uninstall_master(host):
host.collect_log(paths.IPASERVER_UNINSTALL_LOG)
uninstall_cmd = ['ipa-server-install', '--uninstall', '-U']
host_domain_level = domainlevel(host)
if ignore_topology_disconnect and host_domain_level != DOMAIN_LEVEL_0:
uninstall_cmd.append('--ignore-topology-disconnect')
host.run_command(uninstall_cmd, raiseonerr=False)
host.run_command(['ipa-server-install', '--uninstall', '-U'],
raiseonerr=False)
host.run_command(['pkidestroy', '-s', 'CA', '-i', 'pki-tomcat'],
raiseonerr=False)
host.run_command(['rm', '-rf',
@@ -693,9 +488,8 @@ def uninstall_master(host, ignore_topology_disconnect=True):
paths.SYSCONFIG_PKI_TOMCAT,
paths.SYSCONFIG_PKI_TOMCAT_PKI_TOMCAT_DIR,
paths.VAR_LIB_PKI_TOMCAT_DIR,
paths.PKI_TOMCAT,
paths.REPLICA_INFO_GPG_TEMPLATE % host.hostname],
raiseonerr=False)
paths.PKI_TOMCAT],
raiseonerr=False)
unapply_fixes(host)
@@ -707,56 +501,6 @@ def uninstall_client(host):
unapply_fixes(host)
@check_arguments_are((0, 2), Host)
def clean_replication_agreement(master, replica):
"""
Performs `ipa-replica-manage del replica_hostname --force`.
"""
master.run_command(['ipa-replica-manage',
'del',
replica.hostname,
'--force'])
@check_arguments_are((0, 3), Host)
def create_segment(master, leftnode, rightnode):
"""
creates a topology segment. The first argument is a node to run the command
:returns: a hash object containing segment's name, leftnode, rightnode
information and an error string.
"""
kinit_admin(master)
lefthost = leftnode.hostname
righthost = rightnode.hostname
segment_name = "%s-to-%s" % (lefthost, righthost)
result = master.run_command(["ipa", "topologysegment-add", DOMAIN_SUFFIX_NAME,
segment_name,
"--leftnode=%s" % lefthost,
"--rightnode=%s" % righthost], raiseonerr=False)
if result.returncode == 0:
return {'leftnode': lefthost,
'rightnode': righthost,
'name': segment_name}, ""
else:
return {}, result.stderr_text
def destroy_segment(master, segment_name):
"""
Destroys topology segment.
:param master: reference to master object of class Host
:param segment_name: name of the segment to be created
"""
assert isinstance(master, Host), "master should be an instance of Host"
kinit_admin(master)
command = ["ipa",
"topologysegment-del",
DOMAIN_SUFFIX_NAME,
segment_name]
result = master.run_command(command, raiseonerr=False)
return result.returncode, result.stderr_text
def get_topo(name_or_func):
"""Get a topology function by name
@@ -875,123 +619,13 @@ def tree2_topo(master, replicas):
master = replica
@_topo('2-connected')
def two_connected_topo(master, replicas):
r"""No replica has more than 4 agreements and at least two
replicas must fail to disconnect the topology.
. . . .
. . . .
. . . .
... R --- R R --- R ...
\ / \ / \ /
\ / \ / \ /
... R R R ...
\ / \ /
\ / \ /
M0 -- R2
| |
| |
R1 -- R3
. \ / .
. \ / .
. R .
. .
. .
. .
"""
grow = []
pool = [master] + replicas
try:
v0 = pool.pop(0)
v1 = pool.pop(0)
yield v0, v1
v2 = pool.pop(0)
yield v0, v2
grow.append((v0, v2))
v3 = pool.pop(0)
yield v2, v3
yield v1, v3
grow.append((v1, v3))
for (r, s) in grow:
t = pool.pop(0)
for (u, v) in [(r, t), (s, t)]:
yield u, v
w = pool.pop(0)
yield u, w
x = pool.pop(0)
yield v, x
yield w, x
grow.append((w, x))
except IndexError:
return
@_topo('double-circle')
def double_circle_topo(master, replicas, site_size=6):
"""
R--R
|\/|
|/\|
R--R
/ \
M -- R
/| |\
/ | | \
R - R - R--|----|--R - R - R
| X | | | | | | X |
R - R - R -|----|--R - R - R
\ | | /
\| |/
R -- R
\ /
R--R
|\/|
|/\|
R--R
"""
# to provide redundancy there must be at least two replicas per site
assert site_size >= 2
# do not handle master other than the rest of the servers
servers = [master] + replicas
# split servers into sites
it = [iter(servers)] * site_size
sites = [(x[0], x[1], x[2:]) for x in zip(*it)]
num_sites = len(sites)
for i in range(num_sites):
(a, b, _ignore) = sites[i]
# create agreement inside the site
yield a, b
# create agreement to one server in two next sites
for (c, d, _ignore) in [sites[(i+n) % num_sites] for n in [1, 2]]:
yield b, c
if site_size > 2:
# deploy servers inside the site
for site in sites:
site_servers = list(site[2])
yield site[0], site_servers[0]
for edge in complete_topo(site_servers[0], site_servers[1:]):
yield edge
yield site[1], site_servers[-1]
def install_topo(topo, master, replicas, clients, domain_level=None,
def install_topo(topo, master, replicas, clients,
skip_master=False, setup_replica_cas=True):
"""Install IPA servers and clients in the given topology"""
replicas = list(replicas)
installed = {master}
if not skip_master:
install_master(master, domain_level=domain_level)
install_master(master)
add_a_records_for_hosts_in_master_domain(master)
@@ -1016,7 +650,7 @@ def install_clients(servers, clients):
def _entries_to_ldif(entries):
"""Format LDAP entries as LDIF"""
lines = []
io = StringIO()
io = StringIO.StringIO()
writer = LDIFWriter(io)
for entry in entries:
writer.unparse(str(entry.dn), dict(entry))
@@ -1061,21 +695,16 @@ def add_a_records_for_hosts_in_master_domain(master):
for host in master.domain.hosts:
# We don't need to take care of the zone creation since it is master
# domain
try:
verify_host_resolvable(host.hostname, log)
log.debug("The host (%s) is resolvable." % host.domain.name)
except errors.DNSNotARecordError:
log.debug("Hostname (%s) does not have A/AAAA record. Adding new one.",
master.hostname)
add_a_record(master, host)
add_a_record(master, host)
def add_a_record(master, host):
# Find out if the record is already there
cmd = master.run_command(['ipa',
'dnsrecord-show',
'dnsrecord-find',
master.domain.name,
host.hostname + "."],
host.hostname,
'--a-rec', host.ip],
raiseonerr=False)
# If not, add it
@@ -1083,99 +712,5 @@ def add_a_record(master, host):
master.run_command(['ipa',
'dnsrecord-add',
master.domain.name,
host.hostname + ".",
host.hostname,
'--a-rec', host.ip])
def resolve_record(nameserver, query, rtype="SOA", retry=True, timeout=100):
"""Resolve DNS record
:retry: if resolution failed try again until timeout is reached
:timeout: max period of time while method will try to resolve query
(requires retry=True)
"""
res = dns.resolver.Resolver()
res.nameservers = [nameserver]
res.lifetime = 10 # wait max 10 seconds for reply
wait_until = time.time() + timeout
while time.time() < wait_until:
try:
ans = res.query(query, rtype)
return ans
except dns.exception.DNSException:
if not retry:
raise
time.sleep(1)
def ipa_backup(master):
result = master.run_command(["ipa-backup"])
path_re = re.compile("^Backed up to (?P<backup>.*)$", re.MULTILINE)
matched = path_re.search(result.stdout_text + result.stderr_text)
return matched.group("backup")
def ipa_restore(master, backup_path):
master.run_command(["ipa-restore", "-U",
"-p", master.config.dirman_password,
backup_path])
def install_kra(host, domain_level=None, first_instance=False, raiseonerr=True):
if domain_level is None:
domain_level = domainlevel(host)
command = ["ipa-kra-install", "-U", "-p", host.config.dirman_password]
if domain_level == DOMAIN_LEVEL_0 and not first_instance:
replica_file = get_replica_filename(host)
command.append(replica_file)
return host.run_command(command, raiseonerr=raiseonerr)
def install_ca(host, domain_level=None, first_instance=False, raiseonerr=True):
if domain_level is None:
domain_level = domainlevel(host)
command = ["ipa-ca-install", "-U", "-p", host.config.dirman_password,
"-P", 'admin', "-w", host.config.admin_password]
if domain_level == DOMAIN_LEVEL_0 and not first_instance:
replica_file = get_replica_filename(host)
command.append(replica_file)
return host.run_command(command, raiseonerr=raiseonerr)
def install_dns(host, raiseonerr=True):
args = [
"ipa-dns-install",
"--forwarder", host.config.dns_forwarder,
"-U",
]
return host.run_command(args, raiseonerr=raiseonerr)
def uninstall_replica(master, replica):
master.run_command(["ipa-replica-manage", "del", "--force",
"-p", master.config.dirman_password,
replica.hostname], raiseonerr=False)
uninstall_master(replica)
def replicas_cleanup(func):
"""
replicas_cleanup decorator, applied to any test method in integration tests
uninstalls all replicas in the topology leaving only master
configured
"""
def wrapped(*args):
func(*args)
for host in args[0].replicas:
uninstall_replica(args[0].master, host)
uninstall_client(host)
result = args[0].master.run_command(
["ipa", "host-del", "--updatedns", host.hostname],
raiseonerr=False)
# Workaround for 5627
if "host not found" in result.stderr_text:
args[0].master.run_command(["ipa",
"host-del",
host.hostname], raiseonerr=False)
return wrapped

View File

@@ -1,135 +0,0 @@
# Authors:
# Gabe Alford <redhatrises@gmail.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/>.
# FIXME: Pylint errors
# pylint: disable=no-member
import re
from ipatests.test_integration import tasks
from ipatests.test_integration.base import IntegrationTest
def run_advice(master, advice_id, advice_regex, raiseerr):
# Obtain the advice from the server
tasks.kinit_admin(master)
result = master.run_command(['ipa-advise', advice_id],
raiseonerr=raiseerr)
if not result.stdout_text:
advice = result.stderr_text
else:
advice = result.stdout_text
assert re.search(advice_regex, advice, re.S)
class TestAdvice(IntegrationTest):
"""
Tests ipa-advise output.
"""
advice_id = None
raiseerr = None
advice_regex = ''
topology = 'line'
def test_invalid_advice(self):
advice_id = 'invalid-advise-param'
advice_regex = "invalid[\s]+\'advice\'.*"
raiseerr = False
run_advice(self.master, advice_id, advice_regex, raiseerr)
def test_advice_FedoraAuthconfig(self):
advice_id = 'config-fedora-authconfig'
advice_regex = "\#\!\/bin\/sh.*" \
"authconfig[\s]+\-\-enableldap[\s]+" \
"\-\-ldapserver\=.*[\s]+\-\-enablerfc2307bis[\s]+" \
"\-\-enablekrb5"
raiseerr = True
run_advice(self.master, advice_id, advice_regex, raiseerr)
def test_advice_FreeBSDNSSPAM(self):
advice_id = 'config-freebsd-nss-pam-ldapd'
advice_regex = "\#\!\/bin\/sh.*" \
"pkg_add[\s]+\-r[\s]+nss\-pam\-ldapd[\s]+curl.*" \
"\/usr\/local\/etc\/rc\.d\/nslcd[\s]+restart"
raiseerr = True
run_advice(self.master, advice_id, advice_regex, raiseerr)
def test_advice_GenericNSSPAM(self):
advice_id = 'config-generic-linux-nss-pam-ldapd'
advice_regex = "\#\!\/bin\/sh.*" \
"apt\-get[\s]+\-y[\s]+install[\s]+curl[\s]+openssl[\s]+" \
"libnss\-ldapd[\s]+libpam\-ldapd[\s]+nslcd.*" \
"service[\s]+nscd[\s]+stop[\s]+\&\&[\s]+service[\s]+" \
"nslcd[\s]+restart"
raiseerr = True
run_advice(self.master, advice_id, advice_regex, raiseerr)
def test_advice_GenericSSSDBefore19(self):
advice_id = 'config-generic-linux-sssd-before-1-9'
advice_regex = "\#\!\/bin\/sh.*" \
"apt\-get[\s]+\-y[\s]+install sssd curl openssl.*" \
"service[\s]+sssd[\s]+start"
raiseerr = True
run_advice(self.master, advice_id, advice_regex, raiseerr)
def test_advice_RedHatNSS(self):
advice_id = 'config-redhat-nss-ldap'
advice_regex = "\#\!\/bin\/sh.*" \
"yum[\s]+install[\s]+\-y[\s]+curl[\s]+openssl[\s]+nss_ldap" \
"[\s]+authconfig.*authconfig[\s]+\-\-updateall" \
"[\s]+\-\-enableldap[\s]+\-\-enableldaptls"\
"[\s]+\-\-enableldapauth[\s]+" \
"\-\-ldapserver=.*[\s]+\-\-ldapbasedn=.*"
raiseerr = True
run_advice(self.master, advice_id, advice_regex, raiseerr)
def test_advice_RedHatNSSPAM(self):
advice_id = 'config-redhat-nss-pam-ldapd'
advice_regex = "\#\!\/bin\/sh.*" \
"yum[\s]+install[\s]+\-y[\s]+curl[\s]+openssl[\s]+" \
"nss\-pam\-ldapd[\s]+pam_ldap[\s]+authconfig.*" \
"authconfig[\s]+\-\-updateall[\s]+\-\-enableldap"\
"[\s]+\-\-enableldaptls[\s]+\-\-enableldapauth[\s]+" \
"\-\-ldapserver=.*[\s]+\-\-ldapbasedn=.*"
raiseerr = True
run_advice(self.master, advice_id, advice_regex, raiseerr)
def test_advice_RedHatSSSDBefore19(self):
advice_id = 'config-redhat-sssd-before-1-9'
advice_regex = "\#\!\/bin\/sh.*" \
"yum[\s]+install[\s]+\-y[\s]+sssd[\s]+authconfig[\s]+" \
"curl[\s]+openssl.*service[\s]+sssd[\s]+start"
raiseerr = True
run_advice(self.master, advice_id, advice_regex, raiseerr)

View File

@@ -1,414 +0,0 @@
# Authors:
# Petr Viktorin <pviktori@redhat.com>
#
# Copyright (C) 2014 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/>.
from __future__ import print_function
import os
import re
import contextlib
from ipaplatform.constants import constants
from ipapython.ipa_log_manager import log_mgr
from ipapython.dn import DN
from ipatests.test_integration.base import IntegrationTest
from ipatests.test_integration import tasks
from ipatests.test_integration.test_dnssec import wait_until_record_is_signed
from ipatests.util import assert_deepequal
log = log_mgr.get_logger(__name__)
def assert_entries_equal(a, b):
assert_deepequal(a.dn, b.dn)
assert_deepequal(dict(a), dict(b))
def assert_results_equal(a, b):
def to_dict(r):
return {
'stdout': r.stdout_text,
'stderr': r.stderr_text,
'returncode': r.returncode,
}
assert_deepequal(to_dict(a), to_dict(b))
def check_admin_in_ldap(host):
ldap = host.ldap_connect()
basedn = host.domain.basedn
user_dn = DN(('uid', 'admin'), ('cn', 'users'), ('cn', 'accounts'), basedn)
entry = ldap.get_entry(user_dn)
print(entry)
assert entry.dn == user_dn
assert entry['uid'] == ['admin']
del entry['krbLastSuccessfulAuth']
return entry
def check_admin_in_cli(host):
result = host.run_command(['ipa', 'user-show', 'admin'])
assert 'User login: admin' in result.stdout_text, result.stdout_text
return result
def check_admin_in_id(host):
result = host.run_command(['id', 'admin'])
assert 'admin' in result.stdout_text, result.stdout_text
return result
def check_certs(host):
result = host.run_command(['ipa', 'cert-find'])
assert re.search('^Number of entries returned [1-9]\d*$',
result.stdout_text, re.MULTILINE), result.stdout_text
return result
def check_dns(host):
result = host.run_command(['host', host.hostname, 'localhost'])
return result
def check_kinit(host):
result = host.run_command(['kinit', 'admin'],
stdin_text=host.config.admin_password)
return result
CHECKS = [
(check_admin_in_ldap, assert_entries_equal),
(check_admin_in_cli, assert_results_equal),
(check_admin_in_id, assert_results_equal),
(check_certs, assert_results_equal),
(check_dns, assert_results_equal),
(check_kinit, assert_results_equal),
]
@contextlib.contextmanager
def restore_checker(host):
"""Check that the IPA at host works the same at context enter and exit"""
tasks.kinit_admin(host)
results = []
for check, assert_func in CHECKS:
log.info('Storing result for %s', check)
results.append(check(host))
yield
for (check, assert_func), expected in zip(CHECKS, results):
log.info('Checking result for %s', check)
got = check(host)
assert_func(expected, got)
def backup(host):
"""Run backup on host, return the path to the backup directory"""
result = host.run_command(['ipa-backup', '-v'])
# Get the backup location from the command's output
for line in result.stderr_text.splitlines():
prefix = ('ipa.ipaserver.install.ipa_backup.Backup: '
'INFO: Backed up to ')
if line.startswith(prefix):
backup_path = line[len(prefix):].strip()
log.info('Backup path for %s is %s', host, backup_path)
return backup_path
else:
raise AssertionError('Backup directory not found in output')
class TestBackupAndRestore(IntegrationTest):
topology = 'star'
def test_full_backup_and_restore(self):
"""backup, uninstall, restore"""
with restore_checker(self.master):
backup_path = backup(self.master)
self.master.run_command(['ipa-server-install',
'--uninstall',
'-U'])
dirman_password = self.master.config.dirman_password
self.master.run_command(['ipa-restore', backup_path],
stdin_text=dirman_password + '\nyes')
def test_full_backup_and_restore_with_removed_users(self):
"""regression test for https://fedorahosted.org/freeipa/ticket/3866"""
with restore_checker(self.master):
backup_path = backup(self.master)
self.log.info('Backup path for %s is %s', self.master, backup_path)
self.master.run_command(['ipa-server-install',
'--uninstall',
'-U'])
self.master.run_command(['userdel', constants.DS_USER])
self.master.run_command(['userdel', constants.PKI_USER])
homedir = os.path.join(self.master.config.test_dir,
'testuser_homedir')
self.master.run_command(['useradd', 'ipatest_user1',
'--system',
'-d', homedir])
try:
dirman_password = self.master.config.dirman_password
self.master.run_command(['ipa-restore', backup_path],
stdin_text=dirman_password + '\nyes')
finally:
self.master.run_command(['userdel', 'ipatest_user1'])
def test_full_backup_and_restore_with_selinux_booleans_off(self):
"""regression test for https://fedorahosted.org/freeipa/ticket/4157"""
with restore_checker(self.master):
backup_path = backup(self.master)
self.log.info('Backup path for %s is %s', self.master, backup_path)
self.master.run_command(['ipa-server-install',
'--uninstall',
'-U'])
self.master.run_command([
'setsebool', '-P',
'httpd_can_network_connect=off',
'httpd_manage_ipa=off',
])
dirman_password = self.master.config.dirman_password
self.master.run_command(['ipa-restore', backup_path],
stdin_text=dirman_password + '\nyes')
result = self.master.run_command([
'getsebool',
'httpd_can_network_connect',
'httpd_manage_ipa',
])
assert 'httpd_can_network_connect --> on' in result.stdout_text
assert 'httpd_manage_ipa --> on' in result.stdout_text
class BaseBackupAndRestoreWithDNS(IntegrationTest):
"""
Abstract class for DNS restore tests
"""
topology = 'star'
example_test_zone = "example.test."
example2_test_zone = "example2.test."
@classmethod
def install(cls, mh):
tasks.install_master(cls.master, setup_dns=True)
def _full_backup_restore_with_DNS_zone(self, reinstall=False):
"""backup, uninstall, restore"""
with restore_checker(self.master):
self.master.run_command([
'ipa', 'dnszone-add',
self.example_test_zone,
])
tasks.resolve_record(self.master.ip, self.example_test_zone)
backup_path = backup(self.master)
self.master.run_command(['ipa-server-install',
'--uninstall',
'-U'])
if reinstall:
tasks.install_master(self.master, setup_dns=True)
dirman_password = self.master.config.dirman_password
self.master.run_command(['ipa-restore', backup_path],
stdin_text=dirman_password + '\nyes')
tasks.resolve_record(self.master.ip, self.example_test_zone)
tasks.kinit_admin(self.master)
self.master.run_command([
'ipa', 'dnszone-add',
self.example2_test_zone,
])
tasks.resolve_record(self.master.ip, self.example2_test_zone)
class TestBackupAndRestoreWithDNS(BaseBackupAndRestoreWithDNS):
def test_full_backup_and_restore_with_DNS_zone(self):
"""backup, uninstall, restore"""
self._full_backup_restore_with_DNS_zone(reinstall=False)
class TestBackupReinstallRestoreWithDNS(BaseBackupAndRestoreWithDNS):
def test_full_backup_reinstall_restore_with_DNS_zone(self):
"""backup, uninstall, reinstall, restore"""
self._full_backup_restore_with_DNS_zone(reinstall=True)
class BaseBackupAndRestoreWithDNSSEC(IntegrationTest):
"""
Abstract class for DNSSEC restore tests
"""
topology = 'star'
example_test_zone = "example.test."
example2_test_zone = "example2.test."
@classmethod
def install(cls, mh):
tasks.install_master(cls.master, setup_dns=True)
args = [
"ipa-dns-install",
"--dnssec-master",
"--forwarder", cls.master.config.dns_forwarder,
"-U",
]
cls.master.run_command(args)
def _full_backup_and_restore_with_DNSSEC_zone(self, reinstall=False):
with restore_checker(self.master):
self.master.run_command([
'ipa', 'dnszone-add',
self.example_test_zone,
'--dnssec', 'true',
])
assert wait_until_record_is_signed(self.master.ip,
self.example_test_zone, self.log), "Zone is not signed"
backup_path = backup(self.master)
self.master.run_command(['ipa-server-install',
'--uninstall',
'-U'])
if reinstall:
tasks.install_master(self.master, setup_dns=True)
dirman_password = self.master.config.dirman_password
self.master.run_command(['ipa-restore', backup_path],
stdin_text=dirman_password + '\nyes')
assert wait_until_record_is_signed(self.master.ip,
self.example_test_zone, self.log), ("Zone is not signed after "
"restore")
tasks.kinit_admin(self.master)
self.master.run_command([
'ipa', 'dnszone-add',
self.example2_test_zone,
'--dnssec', 'true',
])
assert wait_until_record_is_signed(self.master.ip,
self.example2_test_zone, self.log), "A new zone is not signed"
class TestBackupAndRestoreWithDNSSEC(BaseBackupAndRestoreWithDNSSEC):
def test_full_backup_and_restore_with_DNSSEC_zone(self):
"""backup, uninstall, restore"""
self._full_backup_and_restore_with_DNSSEC_zone(reinstall=False)
class TestBackupReinstallRestoreWithDNSSEC(BaseBackupAndRestoreWithDNSSEC):
def test_full_backup_reinstall_restore_with_DNSSEC_zone(self):
"""backup, uninstall, install, restore"""
self._full_backup_and_restore_with_DNSSEC_zone(reinstall=True)
class BaseBackupAndRestoreWithKRA(IntegrationTest):
"""
Abstract class for KRA restore tests
"""
topology = 'star'
vault_name = "ci_test_vault"
vault_password = "password"
vault_data = "SSBsb3ZlIENJIHRlc3RzCg=="
@classmethod
def install(cls, mh):
tasks.install_master(cls.master, setup_dns=True, setup_kra=True)
def _full_backup_restore_with_vault(self, reinstall=False):
with restore_checker(self.master):
# create vault
self.master.run_command([
"ipa", "vault-add",
self.vault_name,
"--password", self.vault_password,
"--type", "symmetric",
])
# archive secret
self.master.run_command([
"ipa", "vault-archive",
self.vault_name,
"--password", self.vault_password,
"--data", self.vault_data,
])
# retrieve secret
self.master.run_command([
"ipa", "vault-retrieve",
self.vault_name,
"--password", self.vault_password,
])
backup_path = backup(self.master)
self.master.run_command(['ipa-server-install',
'--uninstall',
'-U'])
if reinstall:
tasks.install_master(self.master, setup_dns=True)
dirman_password = self.master.config.dirman_password
self.master.run_command(['ipa-restore', backup_path],
stdin_text=dirman_password + '\nyes')
tasks.kinit_admin(self.master)
# retrieve secret after restore
self.master.run_command([
"ipa", "vault-retrieve",
self.vault_name,
"--password", self.vault_password,
])
class TestBackupAndRestoreWithKRA(BaseBackupAndRestoreWithKRA):
def test_full_backup_restore_with_vault(self):
"""backup, uninstall, restore"""
self._full_backup_restore_with_vault(reinstall=False)
class TestBackupReinstallRestoreWithKRA(BaseBackupAndRestoreWithKRA):
def test_full_backup_reinstall_restore_with_vault(self):
"""backup, uninstall, reinstall, restore"""
self._full_backup_restore_with_vault(reinstall=True)

View File

@@ -24,7 +24,6 @@ import base64
import glob
import contextlib
import nose
import pytest
from ipalib import x509
from ipapython import ipautil
@@ -32,6 +31,7 @@ from ipaplatform.paths import paths
from ipapython.dn import DN
from ipatests.test_integration.base import IntegrationTest
from ipatests.test_integration import tasks
from ipatests.order_plugin import ordered
_DEFAULT = object()
@@ -67,8 +67,8 @@ def assert_error(result, stderr_text, returncode=None):
class CALessBase(IntegrationTest):
@classmethod
def install(cls, mh):
super(CALessBase, cls).install(mh)
def install(cls):
super(CALessBase, cls).install()
cls.cert_dir = tempfile.mkdtemp(prefix="ipatest-")
cls.pem_filename = os.path.join(cls.cert_dir, 'root.pem')
scriptfile = os.path.join(os.path.dirname(__file__),
@@ -108,11 +108,17 @@ class CALessBase(IntegrationTest):
host.transport.put_file(source, dest)
@classmethod
def uninstall(cls, mh):
def uninstall(cls):
# Remove the NSS database
shutil.rmtree(cls.cert_dir)
super(CALessBase, cls).uninstall(mh)
# Remove CA cert in /etc/pki/nssdb, in case of failed (un)install
for host in cls.get_all_hosts():
cls.master.run_command(['certutil', '-d', paths.NSS_DB_DIR, '-D',
'-n', 'External CA cert'],
raiseonerr=False)
super(CALessBase, cls).uninstall()
@classmethod
def install_server(cls, host=None,
@@ -140,17 +146,17 @@ class CALessBase(IntegrationTest):
for filename in set(files_to_copy):
cls.copy_cert(host, filename)
host.collect_log(paths.IPASERVER_INSTALL_LOG)
host.collect_log(paths.IPACLIENT_INSTALL_LOG)
cls.collect_log(host, paths.IPASERVER_INSTALL_LOG)
cls.collect_log(host, paths.IPACLIENT_INSTALL_LOG)
inst = host.domain.realm.replace('.', '-')
host.collect_log(paths.SLAPD_INSTANCE_ERROR_LOG_TEMPLATE % inst)
host.collect_log(paths.SLAPD_INSTANCE_ACCESS_LOG_TEMPLATE % inst)
cls.collect_log(host, paths.SLAPD_INSTANCE_ERROR_LOG_TEMPLATE % inst)
cls.collect_log(host, paths.SLAPD_INSTANCE_ACCESS_LOG_TEMPLATE % inst)
args = [
'ipa-server-install',
'--http-cert-file', http_pkcs12,
'--dirsrv-cert-file', dirsrv_pkcs12,
'--ca-cert-file', root_ca_file,
'--http_pkcs12', http_pkcs12,
'--dirsrv_pkcs12', dirsrv_pkcs12,
'--root-ca-file', root_ca_file,
'--ip-address', host.ip,
'-r', host.domain.name,
'-p', host.config.dirman_password,
@@ -160,9 +166,9 @@ class CALessBase(IntegrationTest):
]
if http_pin is not None:
args.extend(['--http-pin', http_pin])
args.extend(['--http_pin', http_pin])
if dirsrv_pin is not None:
args.extend(['--dirsrv-pin', dirsrv_pin])
args.extend(['--dirsrv_pin', dirsrv_pin])
if unattended:
args.extend(['-U'])
@@ -211,11 +217,11 @@ class CALessBase(IntegrationTest):
os.path.join(self.cert_dir, filename),
os.path.join(master.config.test_dir, filename))
replica.collect_log(paths.IPAREPLICA_INSTALL_LOG)
replica.collect_log(paths.IPACLIENT_INSTALL_LOG)
self.collect_log(replica, paths.IPAREPLICA_INSTALL_LOG)
self.collect_log(replica, paths.IPACLIENT_INSTALL_LOG)
inst = replica.domain.realm.replace('.', '-')
replica.collect_log(paths.SLAPD_INSTANCE_ERROR_LOG_TEMPLATE % inst)
replica.collect_log(paths.SLAPD_INSTANCE_ACCESS_LOG_TEMPLATE % inst)
self.collect_log(replica, paths.SLAPD_INSTANCE_ERROR_LOG_TEMPLATE % inst)
self.collect_log(replica, paths.SLAPD_INSTANCE_ACCESS_LOG_TEMPLATE % inst)
args = [
'ipa-replica-prepare',
@@ -224,13 +230,13 @@ class CALessBase(IntegrationTest):
]
if http_pkcs12:
args.extend(['--http-cert-file', http_pkcs12])
args.extend(['--http_pkcs12', http_pkcs12])
if dirsrv_pkcs12:
args.extend(['--dirsrv-cert-file', dirsrv_pkcs12])
args.extend(['--dirsrv_pkcs12', dirsrv_pkcs12])
if http_pin is not None:
args.extend(['--http-pin', http_pin])
args.extend(['--http_pin', http_pin])
if dirsrv_pin is not None:
args.extend(['--dirsrv-pin', dirsrv_pin])
args.extend(['--dirsrv_pin', dirsrv_pin])
args.extend([replica.hostname])
@@ -337,6 +343,12 @@ class TestServerInstall(CALessBase):
def tearDown(self):
self.uninstall_server()
# Remove CA cert in /etc/pki/nssdb, in case of failed (un)install
for host in self.get_all_hosts():
self.master.run_command(['certutil', '-d', paths.NSS_DB_DIR, '-D',
'-n', 'External CA cert'],
raiseonerr=False)
def test_nonexistent_ca_pem_file(self):
"IPA server install with non-existent CA PEM file "
@@ -416,8 +428,8 @@ class TestServerInstall(CALessBase):
result = self.install_server(http_pin=None)
assert_error(result,
'ipa-server-install: error: You must specify --http-pin '
'with --http-cert-file')
'ipa-server-install: error: You must specify --http_pin '
'with --http_pkcs12')
def test_missing_ds_password(self):
"IPA server install with missing DS PKCS#12 password (unattended)"
@@ -429,7 +441,7 @@ class TestServerInstall(CALessBase):
result = self.install_server(dirsrv_pin=None)
assert_error(result,
'ipa-server-install: error: You must specify '
'--dirsrv-pin with --dirsrv-cert-file')
'--dirsrv_pin with --dirsrv_pkcs12')
def test_incorect_http_pin(self):
"IPA server install with incorrect HTTP PKCS#12 password"
@@ -757,7 +769,12 @@ class TestReplicaInstall(CALessBase):
self.master.run_command(['ipa', 'host-del', replica.hostname],
raiseonerr=False)
replica.run_command(['certutil', '-d', paths.NSS_DB_DIR, '-D',
'-n', 'External CA cert'], raiseonerr=False)
self.uninstall_server()
self.master.run_command(['certutil', '-d', paths.NSS_DB_DIR, '-D',
'-n', 'External CA cert'], raiseonerr=False)
def test_no_certs(self):
"IPA replica install without certificates"
@@ -767,9 +784,8 @@ class TestReplicaInstall(CALessBase):
raiseonerr=False)
assert result.returncode > 0
assert ('Cannot issue certificates: a CA is not installed. Use the '
'--http-cert-file, --dirsrv-cert-file options to provide '
'custom certificates.' in result.stderr_text), \
result.stderr_text
'--http_pkcs12, --dirsrv_pkcs12 options to provide custom '
'certificates.' in result.stderr_text), result.stderr_text
def test_nonexistent_http_pkcs12_file(self):
"IPA replica install with non-existent HTTP PKCS#12 file"
@@ -1126,10 +1142,11 @@ class TestClientInstall(CALessBase):
self.verify_installation()
@ordered
class TestIPACommands(CALessBase):
@classmethod
def install(cls, mh):
super(TestIPACommands, cls).install(mh)
def install(cls):
super(TestIPACommands, cls).install()
cls.export_pkcs12('ca1/server')
with open(cls.pem_filename, 'w') as f:
@@ -1151,16 +1168,18 @@ class TestIPACommands(CALessBase):
result = self.master.run_command(['ipa', command], raiseonerr=False)
assert_error(result, "ipa: ERROR: unknown command '%s'" % command)
@pytest.mark.parametrize('command', (
'cert-status',
'cert-show',
'cert-find',
'cert-revoke',
'cert-remove-hold',
'cert-status'))
def test_cert_commands_unavailable(self, command):
result = self.master.run_command(['ipa', command], raiseonerr=False)
assert_error(result, "ipa: ERROR: unknown command '%s'" % command)
def test_cert_commands_unavailable(self):
for cmd in (
'cert-status',
'cert-show',
'cert-find',
'cert-revoke',
'cert-remove-hold',
'cert-status'):
func = lambda: self.check_ipa_command_not_available(cmd)
func.description = 'Verify that %s command is not available' % cmd
func.test_argument = cmd
yield (func, )
def test_cert_help_unavailable(self):
"Verify that cert plugin help is not available"
@@ -1228,7 +1247,7 @@ class TestIPACommands(CALessBase):
class TestCertinstall(CALessBase):
@classmethod
def install(cls, mh):
def install(cls):
super(TestCertinstall, cls).install()
cls.export_pkcs12('ca1/server')
@@ -1460,7 +1479,7 @@ class TestCertinstall(CALessBase):
args = ['ipa-server-certinstall',
'-w', 'server.p12',
'--http-pin', self.cert_password]
'--http_pin', self.cert_password]
result = self.certinstall('w', 'ca1/server', args=args)
assert result.returncode == 0
@@ -1471,7 +1490,7 @@ class TestCertinstall(CALessBase):
args = ['ipa-server-certinstall',
'-d', 'server.p12',
'--dirsrv-pin', self.cert_password]
'--dirsrv_pin', self.cert_password]
stdin_text = self.master.config.dirman_password + '\n'
result = self.certinstall('d', 'ca1/server',

View File

@@ -1,83 +0,0 @@
from ipatests.test_integration.base import IntegrationTest
from ipatests.test_integration import tasks
DIRSRV_CONFIG_MODS = """
# https://fedorahosted.org/freeipa/ticket/4949
dn: cn=config,cn=ldbm database,cn=plugins,cn=config
changetype: modify
replace: nsslapd-db-locks
nsslapd-db-locks: 100000
# https://fedorahosted.org/freeipa/ticket/1930
dn: cn=config
changetype: modify
replace: nsslapd-allow-unauthenticated-binds
nsslapd-allow-unauthenticated-binds: off
-
replace: nsslapd-require-secure-binds
nsslapd-require-secure-binds: off
-
replace: nsslapd-allow-anonymous-access
nsslapd-allow-anonymous-access: off
-
replace: nsslapd-minssf
nsslapd-minssf: 0
# https://fedorahosted.org/freeipa/ticket/4048
dn: cn=config
changetype: modify
replace: nssslapd-maxbersize
nssslapd-maxbersize: 209715201
dn: cn=userRoot,cn=ldbm database,cn=plugins,cn=config
changetype: modify
replace: nsslapd-cachememsize
nsslapd-cachememsize: 10485761
dn: cn=config,cn=ldbm database,cn=plugins,cn=config
changetype: modify
replace: nsslapd-import_cachesize
nsslapd-import_cachesize: 20000001
-
replace: nsslapd-dbcachesize
nsslapd-dbcachesize: 10000001
"""
CONFIG_LDIF_PATH = "/root/dirsrv-config-mod.ldif"
class TestCustomInstallMaster(IntegrationTest):
"""
Install master with customized DS config
"""
topology = 'star'
@classmethod
def install(cls, mh):
# just prepare LDIF file on both master and replica
cls.master.put_file_contents(CONFIG_LDIF_PATH, DIRSRV_CONFIG_MODS)
def test_customized_ds_install_master(self):
tasks.install_master(self.master, setup_dns=False, extra_args=[
'--dirsrv-config-file', CONFIG_LDIF_PATH
])
class TestCustomInstallReplica(IntegrationTest):
"""
Install replica with customized DS config
"""
topology = 'star'
num_replicas = 1
@classmethod
def install(cls, mh):
# just prepare LDIF file on both master and replica
cls.replicas[0].put_file_contents(CONFIG_LDIF_PATH, DIRSRV_CONFIG_MODS)
tasks.install_master(cls.master)
def test_customized_ds_install_replica(self):
tasks.install_replica(
self.master, self.replicas[0], setup_ca=False,
extra_args=['--dirsrv-config-file', CONFIG_LDIF_PATH])

View File

@@ -1,563 +0,0 @@
#
# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
#
import dns.dnssec
import dns.resolver
import dns.name
import time
from ipatests.test_integration.base import IntegrationTest
from ipatests.test_integration import tasks
from ipaplatform.paths import paths
test_zone = "dnssec.test."
test_zone_repl = "dnssec-replica.test."
root_zone = "."
example_test_zone = "example.test."
example2_test_zone = "example2.test."
example3_test_zone = "example3.test."
def resolve_with_dnssec(nameserver, query, log, rtype="SOA"):
res = dns.resolver.Resolver()
res.nameservers = [nameserver]
res.lifetime = 10 # wait max 10 seconds for reply
# enable Authenticated Data + Checking Disabled flags
res.set_flags(dns.flags.AD | dns.flags.CD)
# enable EDNS v0 + enable DNSSEC-Ok flag
res.use_edns(0, dns.flags.DO, 0)
ans = res.query(query, rtype)
return ans
def get_RRSIG_record(nameserver, query, log, rtype="SOA"):
ans = resolve_with_dnssec(nameserver, query, log, rtype=rtype)
return ans.response.find_rrset(
ans.response.answer, dns.name.from_text(query),
dns.rdataclass.IN, dns.rdatatype.RRSIG,
dns.rdatatype.from_text(rtype))
def is_record_signed(nameserver, query, log, rtype="SOA"):
try:
get_RRSIG_record(nameserver, query, log, rtype=rtype)
except KeyError:
return False
except dns.exception.DNSException:
return False
return True
def wait_until_record_is_signed(nameserver, record, log, rtype="SOA",
timeout=100):
"""
Returns True if record is signed, or False on timeout
:param nameserver: nameserver to query
:param record: query
:param log: logger
:param rtype: record type
:param timeout:
:return: True if records is signed, False if timeout
"""
log.info("Waiting for signed %s record of %s from server %s (timeout %s "
"sec)", rtype, record, nameserver, timeout)
wait_until = time.time() + timeout
while time.time() < wait_until:
if is_record_signed(nameserver, record, log, rtype=rtype):
return True
time.sleep(1)
return False
class TestInstallDNSSECLast(IntegrationTest):
"""Simple DNSSEC test
Install a server and a replica with DNS, then reinstall server
as DNSSEC master
"""
num_replicas = 1
topology = 'star'
@classmethod
def install(cls, mh):
tasks.install_master(cls.master, setup_dns=True)
tasks.install_replica(cls.master, cls.replicas[0], setup_dns=True)
def test_install_dnssec_master(self):
"""Both master and replica have DNS installed"""
args = [
"ipa-dns-install",
"--dnssec-master",
"--forwarder", self.master.config.dns_forwarder,
"-U",
]
self.master.run_command(args)
def test_if_zone_is_signed_master(self):
# add zone with enabled DNSSEC signing on master
args = [
"ipa",
"dnszone-add", test_zone,
"--skip-overlap-check",
"--dnssec", "true",
]
self.master.run_command(args)
# test master
assert wait_until_record_is_signed(
self.master.ip, test_zone, self.log, timeout=100
), "Zone %s is not signed (master)" % test_zone
# test replica
assert wait_until_record_is_signed(
self.replicas[0].ip, test_zone, self.log, timeout=200
), "DNS zone %s is not signed (replica)" % test_zone
def test_if_zone_is_signed_replica(self):
# add zone with enabled DNSSEC signing on replica
args = [
"ipa",
"dnszone-add", test_zone_repl,
"--skip-overlap-check",
"--dnssec", "true",
]
self.replicas[0].run_command(args)
# test replica
assert wait_until_record_is_signed(
self.replicas[0].ip, test_zone_repl, self.log, timeout=300
), "Zone %s is not signed (replica)" % test_zone_repl
# we do not need to wait, on master zones should be singed faster
# than on replicas
assert wait_until_record_is_signed(
self.master.ip, test_zone_repl, self.log, timeout=5
), "DNS zone %s is not signed (master)" % test_zone
def test_disable_reenable_signing_master(self):
dnskey_old = resolve_with_dnssec(self.master.ip, test_zone,
self.log, rtype="DNSKEY").rrset
# disable DNSSEC signing of zone on master
args = [
"ipa",
"dnszone-mod", test_zone,
"--dnssec", "false",
]
self.master.run_command(args)
time.sleep(20) # sleep a bit until LDAP changes are applied to DNS
# test master
assert not is_record_signed(
self.master.ip, test_zone, self.log
), "Zone %s is still signed (master)" % test_zone
# test replica
assert not is_record_signed(
self.replicas[0].ip, test_zone, self.log
), "DNS zone %s is still signed (replica)" % test_zone
# reenable DNSSEC signing
args = [
"ipa",
"dnszone-mod", test_zone,
"--dnssec", "true",
]
self.master.run_command(args)
time.sleep(20) # sleep a bit until LDAP changes are applied to DNS
# test master
assert wait_until_record_is_signed(
self.master.ip, test_zone, self.log, timeout=100
), "Zone %s is not signed (master)" % test_zone
# test replica
assert wait_until_record_is_signed(
self.replicas[0].ip, test_zone, self.log, timeout=200
), "DNS zone %s is not signed (replica)" % test_zone
dnskey_new = resolve_with_dnssec(self.master.ip, test_zone,
self.log, rtype="DNSKEY").rrset
assert dnskey_old != dnskey_new, "DNSKEY should be different"
def test_disable_reenable_signing_replica(self):
dnskey_old = resolve_with_dnssec(self.replicas[0].ip, test_zone_repl,
self.log, rtype="DNSKEY").rrset
# disable DNSSEC signing of zone on replica
args = [
"ipa",
"dnszone-mod", test_zone_repl,
"--dnssec", "false",
]
self.master.run_command(args)
time.sleep(20) # sleep a bit until LDAP changes are applied to DNS
# test master
assert not is_record_signed(
self.master.ip, test_zone_repl, self.log
), "Zone %s is still signed (master)" % test_zone_repl
# test replica
assert not is_record_signed(
self.replicas[0].ip, test_zone_repl, self.log
), "DNS zone %s is still signed (replica)" % test_zone_repl
# reenable DNSSEC signing
args = [
"ipa",
"dnszone-mod", test_zone_repl,
"--dnssec", "true",
]
self.master.run_command(args)
time.sleep(20) # sleep a bit until LDAP changes are applied to DNS
# test master
assert wait_until_record_is_signed(
self.master.ip, test_zone_repl, self.log, timeout=100
), "Zone %s is not signed (master)" % test_zone_repl
# test replica
assert wait_until_record_is_signed(
self.replicas[0].ip, test_zone_repl, self.log, timeout=200
), "DNS zone %s is not signed (replica)" % test_zone_repl
dnskey_new = resolve_with_dnssec(self.replicas[0].ip, test_zone_repl,
self.log, rtype="DNSKEY").rrset
assert dnskey_old != dnskey_new, "DNSKEY should be different"
class TestInstallDNSSECFirst(IntegrationTest):
"""Simple DNSSEC test
Install the server with DNSSEC and then install the replica with DNS
"""
num_replicas = 1
topology = 'star'
@classmethod
def install(cls, mh):
tasks.install_master(cls.master, setup_dns=False)
args = [
"ipa-dns-install",
"--dnssec-master",
"--forwarder", cls.master.config.dns_forwarder,
"-U",
]
cls.master.run_command(args)
tasks.install_replica(cls.master, cls.replicas[0], setup_dns=True)
# backup trusted key
tasks.backup_file(cls.master, paths.DNSSEC_TRUSTED_KEY)
tasks.backup_file(cls.replicas[0], paths.DNSSEC_TRUSTED_KEY)
@classmethod
def uninstall(cls, mh):
# restore trusted key
tasks.restore_files(cls.master)
tasks.restore_files(cls.replicas[0])
super(TestInstallDNSSECFirst, cls).uninstall(mh)
def test_sign_root_zone(self):
args = [
"ipa", "dnszone-add", root_zone, "--dnssec", "true",
"--skip-overlap-check",
]
self.master.run_command(args)
# make BIND happy: add the glue record and delegate zone
args = [
"ipa", "dnsrecord-add", root_zone, self.master.hostname,
"--a-rec=" + self.master.ip
]
self.master.run_command(args)
time.sleep(10) # sleep a bit until data are provided by bind-dyndb-ldap
args = [
"ipa", "dnsrecord-add", root_zone, self.master.domain.name,
"--ns-rec=" + self.master.hostname
]
self.master.run_command(args)
# test master
assert wait_until_record_is_signed(
self.master.ip, root_zone, self.log, timeout=100
), "Zone %s is not signed (master)" % root_zone
# test replica
assert wait_until_record_is_signed(
self.replicas[0].ip, root_zone, self.log, timeout=300
), "Zone %s is not signed (replica)" % root_zone
def test_chain_of_trust(self):
"""
Validate signed DNS records, using our own signed root zone
:return:
"""
# add test zone
args = [
"ipa", "dnszone-add", example_test_zone, "--dnssec", "true",
"--skip-overlap-check",
]
self.master.run_command(args)
# delegation
args = [
"ipa", "dnsrecord-add", root_zone, example_test_zone,
"--ns-rec=" + self.master.hostname
]
self.master.run_command(args)
# wait until zone is signed
assert wait_until_record_is_signed(
self.master.ip, example_test_zone, self.log, timeout=100
), "Zone %s is not signed (master)" % example_test_zone
# wait until zone is signed
assert wait_until_record_is_signed(
self.replicas[0].ip, example_test_zone, self.log, timeout=200
), "Zone %s is not signed (replica)" % example_test_zone
# GET DNSKEY records from zone
ans = resolve_with_dnssec(self.master.ip, example_test_zone, self.log,
rtype="DNSKEY")
dnskey_rrset = ans.response.get_rrset(
ans.response.answer,
dns.name.from_text(example_test_zone),
dns.rdataclass.IN,
dns.rdatatype.DNSKEY)
assert dnskey_rrset, "No DNSKEY records received"
self.log.debug("DNSKEY records returned: %s", dnskey_rrset.to_text())
# generate DS records
ds_records = []
for key_rdata in dnskey_rrset:
if key_rdata.flags != 257:
continue # it is not KSK
ds_records.append(dns.dnssec.make_ds(example_test_zone, key_rdata,
'sha256'))
assert ds_records, ("No KSK returned from the %s zone" %
example_test_zone)
self.log.debug("DS records for %s created: %r", example_test_zone,
ds_records)
# add DS records to root zone
args = [
"ipa", "dnsrecord-add", root_zone, example_test_zone,
# DS record requires to coexists with NS
"--ns-rec", self.master.hostname,
]
for ds in ds_records:
args.append("--ds-rec")
args.append(ds.to_text())
self.master.run_command(args)
# wait until DS records it replicated
assert wait_until_record_is_signed(
self.replicas[0].ip, example_test_zone, self.log, timeout=100,
rtype="DS"
), "No DS record of '%s' returned from replica" % example_test_zone
# extract DSKEY from root zone
ans = resolve_with_dnssec(self.master.ip, root_zone, self.log,
rtype="DNSKEY")
dnskey_rrset = ans.response.get_rrset(ans.response.answer,
dns.name.from_text(root_zone),
dns.rdataclass.IN,
dns.rdatatype.DNSKEY)
assert dnskey_rrset, "No DNSKEY records received"
self.log.debug("DNSKEY records returned: %s", dnskey_rrset.to_text())
# export trust keys for root zone
root_key_rdatas = []
for key_rdata in dnskey_rrset:
if key_rdata.flags != 257:
continue # it is not KSK
root_key_rdatas.append(key_rdata)
assert root_key_rdatas, "No KSK returned from the root zone"
root_keys_rrset = dns.rrset.from_rdata_list(dnskey_rrset.name,
dnskey_rrset.ttl,
root_key_rdatas)
self.log.debug("Root zone trusted key: %s", root_keys_rrset.to_text())
# set trusted key for our root zone
self.master.put_file_contents(paths.DNSSEC_TRUSTED_KEY,
root_keys_rrset.to_text() + '\n')
self.replicas[0].put_file_contents(paths.DNSSEC_TRUSTED_KEY,
root_keys_rrset.to_text() + '\n')
# verify signatures
time.sleep(1)
args = [
"drill", "@localhost", "-k",
paths.DNSSEC_TRUSTED_KEY, "-S",
example_test_zone, "SOA"
]
# test if signature chains are valid
self.master.run_command(args)
self.replicas[0].run_command(args)
class TestMigrateDNSSECMaster(IntegrationTest):
"""test DNSSEC master migration
Install a server and a replica with DNS, then reinstall server
as DNSSEC master
Test:
* migrate dnssec master to replica
* create new zone
* verify if zone is signed on all replicas
* add new replica
* add new zone
* test if new zone is signed on all replicas
"""
num_replicas = 2
topology = 'star'
@classmethod
def install(cls, mh):
tasks.install_master(cls.master, setup_dns=True)
args = [
"ipa-dns-install",
"--dnssec-master",
"--forwarder", cls.master.config.dns_forwarder,
"-U",
]
cls.master.run_command(args)
tasks.install_replica(cls.master, cls.replicas[0], setup_dns=True)
def test_migrate_dnssec_master(self):
"""Both master and replica have DNS installed"""
backup_filename = "/var/lib/ipa/ipa-kasp.db.backup"
replica_backup_filename = "/tmp/ipa-kasp.db.backup"
# add test zone
args = [
"ipa", "dnszone-add", example_test_zone, "--dnssec", "true",
"--skip-overlap-check",
]
self.master.run_command(args)
# wait until zone is signed
assert wait_until_record_is_signed(
self.master.ip, example_test_zone, self.log, timeout=100
), "Zone %s is not signed (master)" % example_test_zone
# wait until zone is signed
assert wait_until_record_is_signed(
self.replicas[0].ip, example_test_zone, self.log, timeout=200
), "Zone %s is not signed (replica)" % example_test_zone
dnskey_old = resolve_with_dnssec(self.master.ip, example_test_zone,
self.log, rtype="DNSKEY").rrset
# migrate dnssec master to replica
args = [
"ipa-dns-install",
"--disable-dnssec-master",
"--forwarder", self.master.config.dns_forwarder,
"--force",
"-U",
]
self.master.run_command(args)
# move content of "ipa-kasp.db.backup" to replica
kasp_db_backup = self.master.get_file_contents(backup_filename)
self.replicas[0].put_file_contents(replica_backup_filename,
kasp_db_backup)
args = [
"ipa-dns-install",
"--dnssec-master",
"--kasp-db", replica_backup_filename,
"--forwarder", self.master.config.dns_forwarder,
"-U",
]
self.replicas[0].run_command(args)
# wait until zone is signed
assert wait_until_record_is_signed(
self.master.ip, example_test_zone, self.log, timeout=100
), "Zone %s is not signed after migration (master)" % example_test_zone
# wait until zone is signed
assert wait_until_record_is_signed(
self.replicas[0].ip, example_test_zone, self.log, timeout=200
), "Zone %s is not signed after migration (replica)" % example_test_zone
# test if dnskey are the same
dnskey_new = resolve_with_dnssec(self.master.ip, example_test_zone,
self.log, rtype="DNSKEY").rrset
assert dnskey_old == dnskey_new, "DNSKEY should be the same"
# add test zone
args = [
"ipa", "dnszone-add", example2_test_zone, "--dnssec", "true",
"--skip-overlap-check",
]
self.replicas[0].run_command(args)
# wait until zone is signed
assert wait_until_record_is_signed(
self.replicas[0].ip, example2_test_zone, self.log, timeout=100
), ("Zone %s is not signed after migration (replica - dnssec master)"
% example2_test_zone)
# wait until zone is signed
assert wait_until_record_is_signed(
self.master.ip, example2_test_zone, self.log, timeout=200
), ("Zone %s is not signed after migration (master)"
% example2_test_zone)
# add new replica
tasks.install_replica(self.master, self.replicas[1], setup_dns=True)
# test if originial zones are signed on new replica
# wait until zone is signed
assert wait_until_record_is_signed(
self.replicas[1].ip, example_test_zone, self.log, timeout=200
), ("Zone %s is not signed (new replica)"
% example_test_zone)
# wait until zone is signed
assert wait_until_record_is_signed(
self.replicas[1].ip, example2_test_zone, self.log, timeout=200
), ("Zone %s is not signed (new replica)"
% example2_test_zone)
# add new zone to new replica
args = [
"ipa", "dnszone-add", example3_test_zone, "--dnssec", "true",
"--skip-overlap-check",
]
self.replicas[1].run_command(args)
# wait until zone is signed
assert wait_until_record_is_signed(
self.replicas[1].ip, example3_test_zone, self.log, timeout=200
), ("Zone %s is not signed (new replica)"
% example3_test_zone)
assert wait_until_record_is_signed(
self.replicas[0].ip, example3_test_zone, self.log, timeout=200
), ("Zone %s is not signed (replica)"
% example3_test_zone)
# wait until zone is signed
assert wait_until_record_is_signed(
self.master.ip, example3_test_zone, self.log, timeout=200
), ("Zone %s is not signed (master)"
% example3_test_zone)

View File

@@ -33,9 +33,7 @@ class TestExternalCA(IntegrationTest):
'-a', self.master.config.admin_password,
'-p', self.master.config.dirman_password,
'--setup-dns', '--no-forwarders',
'-n', self.master.domain.name,
'-r', self.master.domain.realm,
'--domain-level=%i' % self.master.config.domain_level,
'-r', self.master.domain.name,
'--external-ca'
])
@@ -99,8 +97,8 @@ class TestExternalCA(IntegrationTest):
'ipa-server-install',
'-a', self.master.config.admin_password,
'-p', self.master.config.dirman_password,
'--external-cert-file', external_cert_file,
'--external-cert-file', external_ca_file
'--external_cert_file', external_cert_file,
'--external_ca_file', external_ca_file
])
# Make sure IPA server is working properly

View File

@@ -19,7 +19,6 @@
import os
import subprocess
from ipaplatform.paths import paths
import pytest
from ipatests.test_integration.base import IntegrationTest
from ipatests.test_integration import tasks
@@ -36,8 +35,8 @@ class TestForcedClientReenrollment(IntegrationTest):
num_clients = 1
@classmethod
def install(cls, mh):
super(TestForcedClientReenrollment, cls).install(mh)
def install(cls):
super(TestForcedClientReenrollment, cls).install()
tasks.install_master(cls.master)
tasks.install_replica(cls.master, cls.replicas[0], setup_ca=False)
cls.BACKUP_KEYTAB = os.path.join(
@@ -45,7 +44,15 @@ class TestForcedClientReenrollment(IntegrationTest):
'krb5.keytab'
)
def test_reenroll_with_force_join(self, client):
def setUp(self):
tasks.prepare_host(self.clients[0])
tasks.install_client(self.master, self.clients[0])
def tearDown(self):
tasks.uninstall_client(self.clients[0])
self.delete_client_host_entry()
def test_reenroll_with_force_join(self):
"""
Client re-enrollment using admin credentials (--force-join)
"""
@@ -56,7 +63,7 @@ class TestForcedClientReenrollment(IntegrationTest):
sshfp_record_post = self.get_sshfp_record()
assert sshfp_record_pre == sshfp_record_post
def test_reenroll_with_keytab(self, client):
def test_reenroll_with_keytab(self):
"""
Client re-enrollment using keytab
"""
@@ -69,7 +76,7 @@ class TestForcedClientReenrollment(IntegrationTest):
sshfp_record_post = self.get_sshfp_record()
assert sshfp_record_pre == sshfp_record_post
def test_reenroll_with_both_force_join_and_keytab(self, client):
def test_reenroll_with_both_force_join_and_keytab(self):
"""
Client re-enrollment using both --force-join and --keytab options
"""
@@ -82,7 +89,7 @@ class TestForcedClientReenrollment(IntegrationTest):
sshfp_record_post = self.get_sshfp_record()
assert sshfp_record_pre == sshfp_record_post
def test_reenroll_to_replica(self, client):
def test_reenroll_to_replica(self):
"""
Client re-enrollment using keytab, to a replica
"""
@@ -95,7 +102,7 @@ class TestForcedClientReenrollment(IntegrationTest):
sshfp_record_post = self.get_sshfp_record()
assert sshfp_record_pre == sshfp_record_post
def test_try_to_reenroll_with_disabled_host(self, client):
def test_try_to_reenroll_with_disabled_host(self):
"""
Client re-enrollment using keytab, with disabled host
"""
@@ -106,7 +113,7 @@ class TestForcedClientReenrollment(IntegrationTest):
self.restore_keytab()
self.reenroll_client(keytab=self.BACKUP_KEYTAB, expect_fail=True)
def test_try_to_reenroll_with_uninstalled_host(self, client):
def test_try_to_reenroll_with_uninstalled_host(self):
"""
Client re-enrollment using keytab, with uninstalled host
"""
@@ -117,7 +124,7 @@ class TestForcedClientReenrollment(IntegrationTest):
self.restore_keytab()
self.reenroll_client(keytab=self.BACKUP_KEYTAB, expect_fail=True)
def test_try_to_reenroll_with_deleted_host(self, client):
def test_try_to_reenroll_with_deleted_host(self):
"""
Client re-enrollment using keytab, with deleted host
"""
@@ -128,7 +135,7 @@ class TestForcedClientReenrollment(IntegrationTest):
self.restore_keytab()
self.reenroll_client(keytab=self.BACKUP_KEYTAB, expect_fail=True)
def test_try_to_reenroll_with_incorrect_keytab(self, client):
def test_try_to_reenroll_with_incorrect_keytab(self):
"""
Client re-enrollment using keytab, with incorrect keytab file
"""
@@ -198,7 +205,7 @@ class TestForcedClientReenrollment(IntegrationTest):
assert 'IPA Server: %s' % server.hostname in result.stderr_text
if expect_fail:
err_msg = "Kerberos authentication failed: "
err_msg = 'Kerberos authentication failed using keytab'
assert result.returncode == 1
assert err_msg in result.stderr_text
elif force_join and keytab:
@@ -227,11 +234,10 @@ class TestForcedClientReenrollment(IntegrationTest):
['ipa', 'host-disable', self.clients[0].hostname]
)
@classmethod
def delete_client_host_entry(cls):
def delete_client_host_entry(self):
try:
cls.master.run_command(
['ipa', 'host-del', cls.clients[0].hostname]
self.master.run_command(
['ipa', 'host-del', self.clients[0].hostname]
)
except subprocess.CalledProcessError as e:
if e.returncode != 2:
@@ -275,13 +281,3 @@ class TestForcedClientReenrollment(IntegrationTest):
if not contents.startswith(nameserver):
contents = nameserver + contents.replace(nameserver, '')
client.put_file_contents(paths.RESOLV_CONF, contents)
@pytest.fixture()
def client(request):
tasks.install_client(request.cls.master, request.cls.clients[0])
def teardown_client():
tasks.uninstall_client(request.cls.clients[0])
request.cls.delete_client_host_entry()
request.addfinalizer(teardown_client)

View File

@@ -1,200 +0,0 @@
#
# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
#
"""
Module provides tests which testing ability of various subsystems to be
installed.
"""
import time
from ipatests.test_integration.base import IntegrationTest
from ipatests.test_integration import tasks
class InstallTestBase1(IntegrationTest):
num_replicas = 3
topology = 'star'
@classmethod
def install(cls, mh):
tasks.install_master(cls.master, setup_dns=False)
def test_replica0_ca_less_install(self):
tasks.install_replica(self.master, self.replicas[0], setup_ca=False)
def test_replica0_ipa_ca_install(self):
tasks.install_ca(self.replicas[0])
def test_replica0_ipa_kra_install(self):
tasks.install_kra(self.replicas[0], first_instance=True)
def test_replica0_ipa_dns_install(self):
tasks.install_dns(self.replicas[0])
def test_replica1_with_ca_install(self):
tasks.install_replica(self.master, self.replicas[1], setup_ca=True)
def test_replica1_ipa_kra_install(self):
tasks.install_kra(self.replicas[1])
def test_replica1_ipa_dns_install(self):
tasks.install_dns(self.replicas[1])
def test_replica2_with_ca_kra_install(self):
tasks.install_replica(self.master, self.replicas[2], setup_ca=True,
setup_kra=True)
def test_replica2_ipa_dns_install(self):
tasks.install_dns(self.replicas[2])
class InstallTestBase2(IntegrationTest):
num_replicas = 3
topology = 'star'
@classmethod
def install(cls, mh):
tasks.install_master(cls.master, setup_dns=False)
def test_replica0_with_ca_kra_dns_install(self):
tasks.install_replica(self.master, self.replicas[0], setup_ca=True,
setup_kra=True, setup_dns=True)
def test_replica1_with_ca_dns_install(self):
tasks.install_replica(self.master, self.replicas[1], setup_ca=True,
setup_dns=True)
def test_replica1_ipa_kra_install(self):
tasks.install_kra(self.replicas[1])
def test_replica2_with_dns_install(self):
tasks.install_replica(self.master, self.replicas[2], setup_ca=False,
setup_dns=True)
def test_replica2_ipa_ca_install(self):
tasks.install_ca(self.replicas[2])
def test_replica2_ipa_kra_install(self):
tasks.install_kra(self.replicas[2])
##
# Master X Replicas installation tests
##
class TestInstallWithCA1(InstallTestBase1):
@classmethod
def install(cls, mh):
tasks.install_master(cls.master, setup_dns=False)
class TestInstallWithCA2(InstallTestBase2):
@classmethod
def install(cls, mh):
tasks.install_master(cls.master, setup_dns=False)
class TestInstallWithCA_KRA1(InstallTestBase1):
@classmethod
def install(cls, mh):
tasks.install_master(cls.master, setup_dns=False, setup_kra=True)
def test_replica0_ipa_kra_install(self):
tasks.install_kra(self.replicas[0], first_instance=False)
class TestInstallWithCA_KRA2(InstallTestBase2):
@classmethod
def install(cls, mh):
tasks.install_master(cls.master, setup_dns=False, setup_kra=True)
class TestInstallWithCA_DNS1(InstallTestBase1):
@classmethod
def install(cls, mh):
tasks.install_master(cls.master, setup_dns=True)
class TestInstallWithCA_DNS2(InstallTestBase2):
@classmethod
def install(cls, mh):
tasks.install_master(cls.master, setup_dns=True)
class TestInstallWithCA_KRA_DNS1(InstallTestBase1):
@classmethod
def install(cls, mh):
tasks.install_master(cls.master, setup_dns=True, setup_kra=True)
def test_replica0_ipa_kra_install(self):
tasks.install_kra(self.replicas[0], first_instance=False)
class TestInstallWithCA_KRA_DNS2(InstallTestBase2):
@classmethod
def install(cls, mh):
tasks.install_master(cls.master, setup_dns=True, setup_kra=True)
##
# Rest of master installation tests
##
class TestInstallMaster(IntegrationTest):
num_replicas = 0
@classmethod
def install(cls, mh):
pass
def test_install_master(self):
tasks.install_master(self.master, setup_dns=False)
def test_install_kra(self):
tasks.install_kra(self.master, first_instance=True)
def test_install_dns(self):
tasks.install_dns(self.master)
class TestInstallMasterKRA(IntegrationTest):
num_replicas = 0
@classmethod
def install(cls, mh):
pass
def test_install_master(self):
tasks.install_master(self.master, setup_dns=False, setup_kra=True)
def test_install_dns(self):
tasks.install_dns(self.master)
class TestInstallMasterDNS(IntegrationTest):
num_replicas = 0
@classmethod
def install(cls, mh):
pass
def test_install_master(self):
tasks.install_master(self.master, setup_dns=True)
def test_install_kra(self):
tasks.install_kra(self.master, first_instance=True)

View File

@@ -17,9 +17,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# FIXME: Pylint errors
# pylint: disable=no-member
import os
import re
@@ -46,10 +43,6 @@ class BaseTestLegacyClient(object):
'/etc/nsswitch.conf',
paths.SSSD_CONF]
homedir_template = "/home/{domain}/{username}"
required_extra_roles = ()
optional_extra_roles = ()
# Actual test classes need to override these attributes to set the expected
# values on the UID and GID results, since this varies with the usage of the
# POSIX and non-POSIX ID ranges
@@ -62,6 +55,26 @@ class BaseTestLegacyClient(object):
# To allow custom validation dependent on the trust type
posix_trust = False
@classmethod
def setup_class(cls):
super(BaseTestLegacyClient, cls).setup_class()
cls.ad = cls.ad_domains[0].ads[0]
cls.legacy_client = cls.host_by_role(cls.required_extra_roles[0])
# Determine whether the subdomain AD is available
try:
child_ad = cls.host_by_role(cls.optional_extra_roles[0])
cls.ad_subdomain = '.'.join(
child_ad.hostname.split('.')[1:])
except LookupError:
cls.ad_subdomain = None
tasks.apply_common_fixes(cls.legacy_client)
for f in cls.backup_files:
tasks.backup_file(cls.legacy_client, f)
def test_apply_advice(self):
# Obtain the advice from the server
tasks.kinit_admin(self.master)
@@ -327,8 +340,8 @@ class BaseTestLegacyClient(object):
assert result.returncode != 0
@classmethod
def install(cls, mh):
super(BaseTestLegacyClient, cls).install(mh)
def install(cls):
super(BaseTestLegacyClient, cls).install()
tasks.kinit_admin(cls.master)
@@ -346,25 +359,8 @@ class BaseTestLegacyClient(object):
cls.master.run_command(['ipa', 'user-disable', 'disabledipauser'])
cls.ad = cls.ad_domains[0].ads[0]
cls.legacy_client = cls.host_by_role(cls.required_extra_roles[0])
# Determine whether the subdomain AD is available
try:
child_ad = cls.host_by_role(cls.optional_extra_roles[0])
cls.ad_subdomain = '.'.join(
child_ad.hostname.split('.')[1:])
except LookupError:
cls.ad_subdomain = None
tasks.apply_common_fixes(cls.legacy_client)
for f in cls.backup_files:
tasks.backup_file(cls.legacy_client, f)
@classmethod
def uninstall(cls, mh):
def uninstall(cls):
cls.master.run_command(['ipa', 'user-del', 'disabledipauser'],
raiseonerr=False)
@@ -372,7 +368,7 @@ class BaseTestLegacyClient(object):
if hasattr(cls, 'legacy_client'):
tasks.unapply_fixes(cls.legacy_client)
super(BaseTestLegacyClient, cls).uninstall(mh)
super(BaseTestLegacyClient, cls).uninstall()
# Base classes with attributes that are specific for each legacy client test
@@ -414,6 +410,7 @@ class BaseTestLegacyClientPosix(BaseTestLegacyClient,
testuser_gid_regex = '10047'
subdomain_testuser_uid_regex = '10142'
subdomain_testuser_gid_regex = '10147'
homedir_template = "/home/{domain}/{username}"
posix_trust = True
def test_remove_trust_with_posix_attributes(self):
@@ -427,6 +424,7 @@ class BaseTestLegacyClientNonPosix(BaseTestLegacyClient,
testuser_gid_regex = '(?!10047)(\d+)'
subdomain_testuser_uid_regex = '(?!10142)(\d+)'
subdomain_testuser_gid_regex = '(?!10147)(\d+)'
homedir_template = '/home/{domain}/{username}'
def test_remove_nonposix_trust(self):
pass

View File

@@ -1,54 +0,0 @@
# Authors:
# Petr Viktorin <pviktori@redhat.com>
#
# Copyright (C) 2014 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/>.
"""Test the ordering of tests
IPA integration tests, marked with `@ordered`, require tests to be run
in a specific order:
- Base classes first
- Within a class, test methods are ordered according to source line
"""
from pytest_sourceorder import ordered
@ordered
class TestBase(object):
@classmethod
def setup_class(cls):
cls.value = 'unchanged'
def test_d_first(self):
type(self).value = 'changed once'
class TestChild(TestBase):
def test_b_third(self):
assert type(self).value == 'changed twice'
type(self).value = 'changed thrice'
def test_a_fourth(self):
assert type(self).value == 'changed thrice'
def test_c_second(self):
assert type(self).value == 'changed once'
type(self).value = 'changed twice'
TestBase.test_c_second = test_c_second
del test_c_second

View File

@@ -1,223 +0,0 @@
#
# Copyright (C) 2016 FreeIPA Contributors see COPYING for license
#
from ipatests.test_integration.base import IntegrationTest
from ipatests.test_integration import tasks
from ipatests.test_integration.test_caless import assert_error
from ipalib.constants import DOMAIN_LEVEL_0
from ipalib.constants import DOMAIN_LEVEL_1
from ipalib.constants import DOMAIN_SUFFIX_NAME
from ipatests.test_integration.tasks import replicas_cleanup
class ReplicaPromotionBase(IntegrationTest):
@classmethod
def install(cls, mh):
tasks.install_master(cls.master, domain_level=cls.domain_level)
def test_kra_install_master(self):
result1 = tasks.install_kra(self.master,
first_instance=True,
raiseonerr=False)
assert result1.returncode == 0, result1.stderr_text
tasks.kinit_admin(self.master)
result2 = self.master.run_command(["ipa", "vault-find"],
raiseonerr=False)
found = result2.stdout_text.find("0 vaults matched")
assert(found > 0), result2.stdout_text
class TestReplicaPromotionLevel0(ReplicaPromotionBase):
topology = 'star'
num_replicas = 1
domain_level = DOMAIN_LEVEL_0
@replicas_cleanup
def test_promotion_disabled(self):
"""
Testcase:
http://www.freeipa.org/page/V4/Replica_Promotion/Test_plan#Test_case:
_Make_sure_the_feature_is_unavailable_under_domain_level_0
"""
client = self.replicas[0]
tasks.install_client(self.master, client)
args = ['ipa-replica-install', '-U',
'-p', self.master.config.dirman_password,
'-w', self.master.config.admin_password,
'--ip-address', client.ip]
result = client.run_command(args, raiseonerr=False)
assert_error(result,
'You must provide a file generated by ipa-replica-prepare'
' to create a replica when the domain is at level 0', 1)
@replicas_cleanup
def test_backup_restore(self):
"""
TestCase:
http://www.freeipa.org/page/V4/Replica_Promotion/Test_plan#Test_case:
_ipa-restore_after_domainlevel_raise_restores_original_domain_level
"""
command = ["ipa", "topologysegment-find", DOMAIN_SUFFIX_NAME]
tasks.install_replica(self.master, self.replicas[0])
backup_file = tasks.ipa_backup(self.master)
self.master.run_command(["ipa", "domainlevel-set", "1"])
result = self.master.run_command(command)
found1 = result.stdout_text.rfind("1 segment matched")
assert(found1 > 0), result.stdout_text
tasks.ipa_restore(self.master, backup_file)
result2 = self.master.run_command(command, raiseonerr=False)
found2 = result2.stdout_text.rfind("0 segments matched")
assert(found2 > 0), result2.stdout_text
class TestKRAInstall(IntegrationTest):
"""
TestCase: http://www.freeipa.org/page/V4/Replica_Promotion/Test_plan
#Test_case:_ipa-kra-install_with_replica_file_works_only_on_domain_level_0
"""
topology = 'star'
domain_level = DOMAIN_LEVEL_0
num_replicas = 2
@classmethod
def install(cls, mh):
tasks.install_master(cls.master, domain_level=cls.domain_level)
def test_kra_install_without_replica_file(self):
master = self.master
replica1 = self.replicas[0]
replica2 = self.replicas[1]
tasks.install_kra(master, first_instance=True)
tasks.install_replica(master, replica1)
result1 = tasks.install_kra(replica1,
domain_level=DOMAIN_LEVEL_1,
raiseonerr=False)
assert_error(result1, "A replica file is required", 1)
tasks.install_kra(replica1,
domain_level=DOMAIN_LEVEL_0,
raiseonerr=True)
# Now prepare the replica file, copy it to the client and raise
# domain level on master to test the reverse situation
tasks.replica_prepare(master, replica2)
master.run_command(["ipa", "domainlevel-set", str(DOMAIN_LEVEL_1)])
tasks.install_replica(master, replica2)
result2 = tasks.install_kra(replica2,
domain_level=DOMAIN_LEVEL_0,
raiseonerr=False)
assert_error(result2, "No replica file is required", 1)
tasks.install_kra(replica2)
class TestCAInstall(IntegrationTest):
topology = 'star'
domain_level = DOMAIN_LEVEL_0
num_replicas = 2
@classmethod
def install(cls, mh):
tasks.install_master(cls.master, domain_level=cls.domain_level)
def test_ca_install_without_replica_file(self):
"""
TestCase:
http://www.freeipa.org/page/V4/Replica_Promotion/Test_plan#Test_case:
_ipa-ca-install_with_replica_file_works_only_on_domain_level_0
"""
master = self.master
replica1 = self.replicas[0]
replica2 = self.replicas[1]
for replica in self.replicas:
tasks.install_replica(master, replica, setup_ca=False,
setup_dns=True)
result1 = tasks.install_ca(replica1,
domain_level=DOMAIN_LEVEL_1,
raiseonerr=False)
assert_error(result1, "If you wish to replicate CA to this host,"
" please re-run 'ipa-ca-install'\nwith a"
" replica file generated on an existing CA"
" master as argument.", 1)
tasks.install_ca(replica1, domain_level=DOMAIN_LEVEL_0)
# Now prepare the replica file, copy it to the client and raise
# domain level on master to test the reverse situation
master.run_command(["ipa", "domainlevel-set", str(DOMAIN_LEVEL_1)])
result2 = tasks.install_ca(replica2,
domain_level=DOMAIN_LEVEL_0,
raiseonerr=False)
assert_error(result2, 'Too many parameters provided.'
' No replica file is required', 1)
tasks.install_ca(replica2, domain_level=DOMAIN_LEVEL_1)
class TestReplicaPromotionLevel1(ReplicaPromotionBase):
"""
TestCase: http://www.freeipa.org/page/V4/Replica_Promotion/Test_plan#
Test_case:_Make_sure_the_old_workflow_is_disabled_at_domain_level_1
"""
topology = 'star'
num_replicas = 1
domain_level = DOMAIN_LEVEL_1
@replicas_cleanup
def test_replica_prepare_disabled(self):
replica = self.replicas[0]
args = ['ipa-replica-prepare',
'-p', self.master.config.dirman_password,
'--ip-address', replica.ip,
replica.hostname]
result = self.master.run_command(args, raiseonerr=False)
assert_error(result, "Replica creation using 'ipa-replica-prepare'"
" to generate replica file\n"
"is supported only in 0-level IPA domain", 1)
class TestReplicaManageCommands(IntegrationTest):
topology = "star"
num_replicas = 2
domain_level = DOMAIN_LEVEL_0
def test_replica_manage_commands(self):
"""
TestCase: http://www.freeipa.org/page/V4/Replica_Promotion/Test_plan
#Test_case:_ipa-replica-manage_connect_is_deprecated_in_domain_level_1
"""
master = self.master
replica1 = self.replicas[0]
replica2 = self.replicas[1]
result1 = master.run_command(["ipa-replica-manage",
"connect",
replica1.hostname,
replica2.hostname],
raiseonerr=False)
assert result1.returncode == 0, result1.stderr_text
result2 = master.run_command(["ipa-replica-manage",
"disconnect",
replica1.hostname,
replica2.hostname],
raiseonerr=False)
assert result2.returncode == 0, result2.stderr_text
master.run_command(["ipa", "domainlevel-set", str(DOMAIN_LEVEL_1)])
result3 = master.run_command(["ipa-replica-manage",
"connect",
replica1.hostname,
replica2.hostname],
raiseonerr=False)
assert_error(result3, 'Creation of IPA replication agreement is'
' deprecated with managed IPA replication'
' topology. Please use `ipa topologysegment-*`'
' commands to manage the topology', 1)
tasks.create_segment(master, replica1, replica2)
result4 = master.run_command(["ipa-replica-manage",
"disconnect",
replica1.hostname,
replica2.hostname],
raiseonerr=False)
assert_error(result4, 'Removal of IPA replication agreement is'
' deprecated with managed IPA replication'
' topology. Please use `ipa topologysegment-*`'
' commands to manage the topology', 1)

View File

@@ -1,123 +0,0 @@
#
# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
#
import time
from ipatests.test_integration.base import IntegrationTest
from ipatests.test_integration import tasks
class LayoutsBaseTest(IntegrationTest):
@classmethod
def install(cls, mh):
# tests use custom installation
pass
def replication_is_working(self):
test_user = 'replication-testuser'
self.master.run_command(
['ipa', 'user-add', test_user, '--first', 'test', '--last', 'user']
)
time.sleep(60) # make sure the replication of user is done
for r in self.replicas:
r.run_command(['ipa', 'user-show', test_user])
class TestLineTopologyWithoutCA(LayoutsBaseTest):
num_replicas = 3
def test_line_topology_without_ca(self):
tasks.install_topo('line', self.master, self.replicas, [],
setup_replica_cas=False)
self.replication_is_working()
class TestLineTopologyWithCA(LayoutsBaseTest):
num_replicas = 3
def test_line_topology_with_ca(self):
tasks.install_topo('line', self.master, self.replicas, [],
setup_replica_cas=True)
self.replication_is_working()
class TestStarTopologyWithoutCA(LayoutsBaseTest):
num_replicas = 3
def test_star_topology_without_ca(self):
tasks.install_topo('star', self.master, self.replicas, [],
setup_replica_cas=False)
self.replication_is_working()
class TestStarTopologyWithCA(LayoutsBaseTest):
num_replicas = 3
def test_star_topology_with_ca(self):
tasks.install_topo('star', self.master, self.replicas, [],
setup_replica_cas=True)
self.replication_is_working()
class TestCompleteTopologyWithoutCA(LayoutsBaseTest):
num_replicas = 3
def test_complete_topology_without_ca(self):
tasks.install_topo('complete', self.master, self.replicas, [],
setup_replica_cas=False)
self.replication_is_working()
class TestCompleteTopologyWithCA(LayoutsBaseTest):
num_replicas = 3
def test_complete_topology_with_ca(self):
tasks.install_topo('complete', self.master, self.replicas, [],
setup_replica_cas=True)
self.replication_is_working()
class Test2ConnectedTopologyWithoutCA(LayoutsBaseTest):
num_replicas = 33
def test_2_connected_topology_without_ca(self):
tasks.install_topo('2-connected', self.master, self.replicas, [],
setup_replica_cas=False)
self.replication_is_working()
class Test2ConnectedTopologyWithCA(LayoutsBaseTest):
num_replicas = 33
def test_2_connected_topology_with_ca(self):
tasks.install_topo('2-connected', self.master, self.replicas, [],
setup_replica_cas=True)
self.replication_is_working()
class TestDoubleCircleTopologyWithoutCA(LayoutsBaseTest):
num_replicas = 29
def test_2_connected_topology_with_ca(self):
tasks.install_topo('double-circle', self.master, self.replicas, [],
setup_replica_cas=False)
self.replication_is_working()
class TestDoubleCircleTopologyWithCA(LayoutsBaseTest):
num_replicas = 29
def test_2_connected_topology_with_ca(self):
tasks.install_topo('double-circle', self.master, self.replicas, [],
setup_replica_cas=True)
self.replication_is_working()

View File

@@ -1,82 +0,0 @@
# Authors:
# Petr Viktorin <pviktori@redhat.com>
#
# Copyright (C) 2014 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/>.
import os
from ipatests.test_integration.base import IntegrationTest
from ipatests.test_integration import tasks
class TestServicePermissions(IntegrationTest):
topology = 'star'
def test_service_as_user_admin(self):
"""Test that a service in User Administrator role can manage users"""
service_name = 'testservice/%s@%s' % (self.master.hostname,
self.master.domain.realm)
keytab_file = os.path.join(self.master.config.test_dir,
'testservice_keytab')
# Prepare a service
self.master.run_command(['ipa', 'service-add', service_name])
self.master.run_command(['ipa-getkeytab',
'-p', service_name,
'-k', keytab_file,
'-s', self.master.hostname])
# Check that the service cannot add a user
self.master.run_command(['kdestroy'])
self.master.run_command(['kinit', '-k', service_name,
'-t', keytab_file])
result = self.master.run_command(['ipa', 'role-add-member',
'User Administrator',
'--service', service_name],
raiseonerr=False)
assert result.returncode > 0
# Add service to User Administrator role
self.master.run_command(['kdestroy'])
tasks.kinit_admin(self.master)
self.master.run_command(['ipa', 'role-add-member',
'User Administrator',
'--service', service_name])
# Check that the service now can add a user
self.master.run_command(['kdestroy'])
self.master.run_command(['kinit', '-k', service_name,
'-t', keytab_file])
self.master.run_command(['ipa', 'user-add', 'tuser',
'--first', 'a', '--last', 'b', '--random'])
# Clean up
self.master.run_command(['kdestroy'])
tasks.kinit_admin(self.master)
self.master.run_command(['ipa', 'service-del', service_name])
self.master.run_command(['ipa', 'user-del', 'tuser'])

View File

@@ -17,8 +17,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import print_function
from ipapython.dn import DN
from ipatests.test_integration.base import IntegrationTest
from ipatests.test_integration import tasks
@@ -49,7 +47,7 @@ class TestSimpleReplication(IntegrationTest):
user_dn = DN(('uid', login), ('cn', 'users'), ('cn', 'accounts'),
basedn)
entry = ldap.get_entry(user_dn)
print(entry)
print entry
assert entry.dn == user_dn
assert entry['uid'] == [login]

View File

@@ -17,11 +17,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import pytest
from ipatests.test_integration.base import IntegrationTest
from ipatests.test_integration.tasks import clear_sssd_cache, modify_sssd_conf
from ipatests.test_integration import util
from ipatests.test_integration.tasks import clear_sssd_cache
class TestSudo(IntegrationTest):
@@ -33,8 +30,8 @@ class TestSudo(IntegrationTest):
topology = 'line'
@classmethod
def install(cls, mh):
super(TestSudo, cls).install(mh)
def setup_class(cls):
super(TestSudo, cls).setup_class()
cls.client = cls.clients[0]
@@ -73,10 +70,10 @@ class TestSudo(IntegrationTest):
'localuser'])
@classmethod
def uninstall(cls, mh):
def teardown_class(cls):
cls.client.run_command(['groupdel', 'localgroup'], raiseonerr=False)
cls.client.run_command(['userdel', 'localuser'], raiseonerr=False)
super(TestSudo, cls).uninstall(mh)
super(TestSudo, cls).teardown_class()
def list_sudo_commands(self, user, raiseonerr=False, verbose=False):
clear_sssd_cache(self.client)
@@ -272,38 +269,13 @@ class TestSudo(IntegrationTest):
'--hostgroups', 'testhostgroup'])
def test_sudo_rule_restricted_to_one_hostmask_setup(self):
# We need to detect the hostmask first
full_ip = util.get_host_ip_with_hostmask(self.client)
# Make a note for the next test, which needs to be skipped
# if hostmask detection failed
self.__class__.skip_hostmask_based = False
if not full_ip:
self.__class__.skip_hostmask_based = True
raise pytest.skip("Hostmask could not be detected")
# Add the client's /24 hostmask to the rule
ip = self.client.ip
self.master.run_command(['ipa', '-n', 'sudorule-add-host',
'testrule',
'--hostmask', full_ip])
# SSSD >= 1.13.3-3 uses native IPA schema instead of compat entries to
# pull in sudoers. Since native schema does not (yet) support
# hostmasks, we need to point ldap_sudo_search_base to the old schema
domain = self.client.domain
modify_sssd_conf(
self.client,
domain.name,
{
'ldap_sudo_search_base': 'ou=sudoers,{}'.format(domain.basedn)
},
provider_subtype='sudo'
)
'--hostmask', '%s/24' % ip])
def test_sudo_rule_restricted_to_one_hostmask(self):
if self.__class__.skip_hostmask_based:
raise pytest.skip("Hostmask could not be detected")
result1 = self.list_sudo_commands("testuser1")
assert "(ALL : ALL) NOPASSWD: ALL" in result1.stdout_text
@@ -312,16 +284,11 @@ class TestSudo(IntegrationTest):
assert result.returncode != 0
def test_sudo_rule_restricted_to_one_hostmask_teardown(self):
if self.__class__.skip_hostmask_based:
raise pytest.skip("Hostmask could not be detected")
# Detect the hostmask first to delete the hostmask based rule
full_ip = util.get_host_ip_with_hostmask(self.client)
# Remove the client's hostmask from the rule
# Remove the client's /24 hostmask from the rule
ip = self.client.ip
self.master.run_command(['ipa', '-n', 'sudorule-remove-host',
'testrule',
'--hostmask', full_ip])
'--hostmask', '%s/24' % ip])
def test_sudo_rule_restricted_to_one_hostmask_negative_setup(self):
# Add the master's hostmask to the rule
@@ -341,18 +308,6 @@ class TestSudo(IntegrationTest):
'testrule',
'--hostmask', '%s/32' % ip])
# reset ldap_sudo_search_base back to the default value, the old
# schema is not needed for the upcoming tests
domain = self.client.domain
modify_sssd_conf(
self.client,
domain.name,
{
'ldap_sudo_search_base': None
},
provider_subtype='sudo'
)
def test_sudo_rule_restricted_to_one_command_setup(self):
# Reset testrule configuration
self.reset_rule_categories()

View File

@@ -23,25 +23,23 @@ import copy
from ipatests.test_integration import config
from ipapython.ipautil import write_tmp_file
from ipatests.util import assert_deepequal
from ipalib.constants import MAX_DOMAIN_LEVEL
DEFAULT_OUTPUT_DICT = {
"nis_domain": "ipatest",
"test_dir": "/root/ipatests",
"debug": False,
"ad_admin_name": "Administrator",
"ipv6": False,
"ssh_key_filename": "~/.ssh/id_rsa",
"ssh_username": "root",
"root_ssh_key_filename": "~/.ssh/id_rsa",
"admin_name": "admin",
"ad_admin_password": "Secret123",
"ssh_password": None,
"root_password": None,
"dns_forwarder": "8.8.8.8",
"domains": [],
"dirman_dn": "cn=Directory Manager",
"dirman_password": "Secret123",
"ntp_server": "ntp.clock.test",
"admin_password": "Secret123",
"domain_level": MAX_DOMAIN_LEVEL
"admin_password": "Secret123"
}
DEFAULT_OUTPUT_ENV = {
@@ -59,7 +57,6 @@ DEFAULT_OUTPUT_ENV = {
"ADADMINPW": "Secret123",
"IPv6SETUP": "",
"IPADEBUG": "",
"DOMAINLVL": str(MAX_DOMAIN_LEVEL),
}
DEFAULT_INPUT_ENV = {
@@ -132,12 +129,6 @@ class CheckConfig(object):
assert_deepequal(self.get_output_dict(), conf.to_dict())
self.check_config(conf)
# Settings to override:
extra_input_dict = {}
extra_input_env = {}
extra_output_dict = {}
extra_output_env = {}
class TestEmptyConfig(CheckConfig):
extra_input_dict = {}

View File

@@ -96,86 +96,3 @@ def test_topology_complete():
(2, 3),
]
assert list(topo('M', [])) == []
def test_topology_two_connected():
topo = tasks.get_topo('2-connected')
assert topo == tasks.two_connected_topo
assert list(topo('M', [1, 2, 3, 4, 5, 6, 7, 8])) == [
('M', 1),
('M', 2),
(2, 3),
(1, 3),
('M', 4),
('M', 5),
(4, 6),
(5, 6),
(2, 4),
(2, 7),
(4, 8),
(7, 8),
]
assert list(topo('M', [])) == []
def test_topology_double_circle_topo():
topo = tasks.get_topo('double-circle')
assert topo == tasks.double_circle_topo
assert list(topo('M', list(range(1, 30)))) == [
('M', 1),
(1, 6),
(1, 12),
(6, 7),
(7, 12),
(7, 18),
(12, 13),
(13, 18),
(13, 24),
(18, 19),
(19, 24),
(19, 'M'),
(24, 25),
(25, 'M'),
(25, 6),
('M', 2),
(2, 3),
(2, 4),
(2, 5),
(3, 4),
(3, 5),
(4, 5),
(1, 5),
(6, 8),
(8, 9),
(8, 10),
(8, 11),
(9, 10),
(9, 11),
(10, 11),
(7, 11),
(12, 14),
(14, 15),
(14, 16),
(14, 17),
(15, 16),
(15, 17),
(16, 17),
(13, 17),
(18, 20),
(20, 21),
(20, 22),
(20, 23),
(21, 22),
(21, 23),
(22, 23),
(19, 23),
(24, 26),
(26, 27),
(26, 28),
(26, 29),
(27, 28),
(27, 29),
(28, 29),
(25, 29),
]
assert list(topo('M', [])) == []

View File

@@ -1,155 +0,0 @@
#
# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
#
import re
import time
import pytest
from ipatests.test_integration.base import IntegrationTest
from ipatests.test_integration import tasks
from ipatests.test_integration.env_config import get_global_config
from ipalib.constants import DOMAIN_SUFFIX_NAME
config = get_global_config()
reasoning = "Topology plugin disabled due to domain level 0"
@pytest.mark.skipif(config.domain_level == 0, reason=reasoning)
class TestTopologyOptions(IntegrationTest):
num_replicas = 2
topology = 'star'
rawsegment_re = ('Segment name: (?P<name>.*?)',
'\s+Left node: (?P<lnode>.*?)',
'\s+Right node: (?P<rnode>.*?)',
'\s+Connectivity: (?P<connectivity>\S+)')
segment_re = re.compile("\n".join(rawsegment_re))
noentries_re = re.compile("Number of entries returned (\d+)")
@classmethod
def install(cls, mh):
tasks.install_topo(cls.topology, cls.master,
cls.replicas[:-1],
cls.clients)
def tokenize_topologies(self, command_output):
"""
takes an output of `ipa topologysegment-find` and returns an array of
segment hashes
"""
segments = command_output.split("-----------------")[2]
raw_segments = segments.split('\n\n')
result = []
for i in raw_segments:
matched = self.segment_re.search(i)
if matched:
result.append({'leftnode': matched.group('lnode'),
'rightnode': matched.group('rnode'),
'name': matched.group('name'),
'connectivity': matched.group('connectivity')
}
)
return result
def test_topology_updated_on_replica_install_remove(self):
"""
Install and remove a replica and make sure topology information is
updated on all other replicas
Testcase: http://www.freeipa.org/page/V4/Manage_replication_topology/
Test_plan#Test_case:
_Replication_topology_should_be_saved_in_the_LDAP_tree
"""
tasks.kinit_admin(self.master)
result1 = self.master.run_command(['ipa', 'topologysegment-find',
DOMAIN_SUFFIX_NAME])
first_segment_name = "%s-to-%s" % (self.master.hostname,
self.replicas[0].hostname)
output1 = result1.stdout_text
firstsegment = self.tokenize_topologies(output1)[0]
assert(firstsegment['name'] == first_segment_name)
assert(self.noentries_re.search(output1).group(1) == "1")
assert(firstsegment['leftnode'] == self.master.hostname)
assert(firstsegment['rightnode'] == self.replicas[0].hostname)
tasks.install_replica(self.master, self.replicas[1], setup_ca=False,
setup_dns=False)
# We need to make sure topology information is consistent across all
# replicas
result2 = self.master.run_command(['ipa', 'topologysegment-find',
DOMAIN_SUFFIX_NAME])
result3 = self.replicas[0].run_command(['ipa', 'topologysegment-find',
DOMAIN_SUFFIX_NAME])
result4 = self.replicas[1].run_command(['ipa', 'topologysegment-find',
DOMAIN_SUFFIX_NAME])
segments = self.tokenize_topologies(result2.stdout_text)
assert(len(segments) == 2)
assert(result2.stdout_text == result3.stdout_text)
assert(result3.stdout_text == result4.stdout_text)
# Now let's check that uninstalling the replica will update the topology
# info on the rest of replicas.
tasks.uninstall_master(self.replicas[1])
tasks.clean_replication_agreement(self.master, self.replicas[1])
result5 = self.master.run_command(['ipa', 'topologysegment-find',
DOMAIN_SUFFIX_NAME])
assert(self.noentries_re.search(result5.stdout_text).group(1) == "1")
def test_add_remove_segment(self):
"""
Make sure a topology segment can be manually created and deleted
with the influence on the real topology
Testcase http://www.freeipa.org/page/V4/Manage_replication_topology/
Test_plan#Test_case:_Basic_CRUD_test
"""
tasks.kinit_admin(self.master)
# Install the second replica
tasks.install_replica(self.master, self.replicas[1], setup_ca=False,
setup_dns=False)
# turn a star into a ring
segment, err = tasks.create_segment(self.master,
self.replicas[0],
self.replicas[1])
assert err == "", err
# Make sure the new segment is shown by `ipa topologysegment-find`
result1 = self.master.run_command(['ipa', 'topologysegment-find',
DOMAIN_SUFFIX_NAME])
assert(result1.stdout_text.find(segment['name']) > 0)
# Remove master <-> replica2 segment and make sure that the changes get
# there through replica1
deleteme = "%s-to-%s" % (self.master.hostname,
self.replicas[1].hostname)
returncode, error = tasks.destroy_segment(self.master, deleteme)
assert returncode == 0, error
# make sure replica1 does not have segment that was deleted on master
result3 = self.replicas[0].run_command(['ipa', 'topologysegment-find',
DOMAIN_SUFFIX_NAME])
assert(result3.stdout_text.find(deleteme) < 0)
# Create test data on master and make sure it gets all the way down to
# replica2 through replica1
self.master.run_command(['ipa', 'user-add', 'someuser',
'--first', 'test',
'--last', 'user'])
time.sleep(60) # replication requires some time
users_on_replica2 = self.replicas[1].run_command(['ipa',
'user-find'])
assert(users_on_replica2.find('someuser') > 0)
# We end up having a line topology: master <-> replica1 <-> replica2
def test_remove_the_only_connection(self):
"""
Testcase: http://www.freeipa.org/page/V4/Manage_replication_topology/
Test_plan#Test_case:
_Removal_of_a_topology_segment_is_allowed_only_if_there_is_at_least_one_more_segment_connecting_the_given_replica
"""
text = "Removal of Segment disconnects topology"
error1 = "The system should not have let you remove the segment"
error2 = "Wrong error message thrown during segment removal: \"%s\""
replicas = (self.replicas[0].hostname, self.replicas[1].hostname)
returncode, error = tasks.destroy_segment(self.master, "%s-to-%s" % replicas)
assert returncode != 0, error1
assert error.count(text) == 1, error2 % error
newseg, err = tasks.create_segment(self.master,
self.master,
self.replicas[1])
assert err == "", err
returncode, error = tasks.destroy_segment(self.master, "%s-to-%s" % replicas)
assert returncode == 0, error

View File

@@ -33,8 +33,8 @@ class ADTrustBase(IntegrationTest):
optional_extra_roles = ['ad_subdomain']
@classmethod
def install(cls, mh):
super(ADTrustBase, cls).install(mh)
def install(cls):
super(ADTrustBase, cls).install()
cls.ad = cls.ad_domains[0].ads[0]
cls.install_adtrust()
cls.check_sid_generation()

View File

@@ -1,200 +0,0 @@
#
# Copyright (C) 2015 FreeIPA Contributors see COPYING for license
#
import time
from ipatests.test_integration.base import IntegrationTest
from ipatests.test_integration import tasks
WAIT_AFTER_ARCHIVE = 30 # give some time to replication
class TestInstallKRA(IntegrationTest):
"""
Test if vault feature behaves as expected, when KRA is installed or not
installed on replica
"""
num_replicas = 1
topology = 'star'
vault_password = "password"
vault_data = "SSBsb3ZlIENJIHRlc3RzCg=="
vault_name_master = "ci_test_vault_master"
vault_name_master2 = "ci_test_vault_master2"
vault_name_master3 = "ci_test_vault_master3"
vault_name_replica_without_KRA = "ci_test_vault_replica_without_kra"
vault_name_replica_with_KRA = "ci_test_vault_replica_with_kra"
vault_name_replica_KRA_uninstalled = "ci_test_vault_replica_KRA_uninstalled"
@classmethod
def install(cls, mh):
tasks.install_master(cls.master, setup_kra=True)
# do not install KRA on replica, it is part of test
tasks.install_replica(cls.master, cls.replicas[0], setup_kra=False)
def _retrieve_secret(self, vault_names=[]):
# try to retrieve secret from vault on both master and replica
for vault_name in vault_names:
self.master.run_command([
"ipa", "vault-retrieve",
vault_name,
"--password", self.vault_password,
])
self.replicas[0].run_command([
"ipa", "vault-retrieve",
vault_name,
"--password", self.vault_password,
])
def test_create_and_retrieve_vault_master(self):
# create vault
self.master.run_command([
"ipa", "vault-add",
self.vault_name_master,
"--password", self.vault_password,
"--type", "symmetric",
])
# archive secret
self.master.run_command([
"ipa", "vault-archive",
self.vault_name_master,
"--password", self.vault_password,
"--data", self.vault_data,
])
time.sleep(WAIT_AFTER_ARCHIVE)
self._retrieve_secret([self.vault_name_master])
def test_create_and_retrieve_vault_replica_without_kra(self):
# create vault
self.replicas[0].run_command([
"ipa", "vault-add",
self.vault_name_replica_without_KRA,
"--password", self.vault_password,
"--type", "symmetric",
])
# archive secret
self.replicas[0].run_command([
"ipa", "vault-archive",
self.vault_name_replica_without_KRA,
"--password", self.vault_password,
"--data", self.vault_data,
])
time.sleep(WAIT_AFTER_ARCHIVE)
self._retrieve_secret([self.vault_name_replica_without_KRA])
def test_create_and_retrieve_vault_replica_with_kra(self):
# install KRA on replica
tasks.install_kra(self.replicas[0], first_instance=False)
# create vault
self.replicas[0].run_command([
"ipa", "vault-add",
self.vault_name_replica_with_KRA,
"--password", self.vault_password,
"--type", "symmetric",
])
# archive secret
self.replicas[0].run_command([
"ipa", "vault-archive",
self.vault_name_replica_with_KRA,
"--password", self.vault_password,
"--data", self.vault_data,
])
time.sleep(WAIT_AFTER_ARCHIVE)
self._retrieve_secret([self.vault_name_replica_with_KRA])
################# master #################
# test master again after KRA was installed on replica
# create vault
self.master.run_command([
"ipa", "vault-add",
self.vault_name_master2,
"--password", self.vault_password,
"--type", "symmetric",
])
# archive secret
self.master.run_command([
"ipa", "vault-archive",
self.vault_name_master2,
"--password", self.vault_password,
"--data", self.vault_data,
])
time.sleep(WAIT_AFTER_ARCHIVE)
self._retrieve_secret([self.vault_name_master2])
################ old vaults ###############
# test if old vaults are still accessible
self._retrieve_secret([
self.vault_name_master,
self.vault_name_replica_without_KRA,
])
def test_create_and_retrieve_vault_after_kra_uninstall_on_replica(self):
# uninstall KRA on replica
self.replicas[0].run_command([
"ipa-kra-install",
"-U",
"--uninstall",
])
# create vault
self.replicas[0].run_command([
"ipa", "vault-add",
self.vault_name_replica_KRA_uninstalled,
"--password", self.vault_password,
"--type", "symmetric",
])
# archive secret
self.replicas[0].run_command([
"ipa", "vault-archive",
self.vault_name_replica_KRA_uninstalled,
"--password", self.vault_password,
"--data", self.vault_data,
])
time.sleep(WAIT_AFTER_ARCHIVE)
self._retrieve_secret([self.vault_name_replica_KRA_uninstalled])
################# master #################
# test master again after KRA was uninstalled on replica
# create vault
self.master.run_command([
"ipa", "vault-add",
self.vault_name_master3,
"--password", self.vault_password,
"--type", "symmetric",
])
# archive secret
self.master.run_command([
"ipa", "vault-archive",
self.vault_name_master3,
"--password", self.vault_password,
"--data", self.vault_data,
])
time.sleep(WAIT_AFTER_ARCHIVE)
self._retrieve_secret([self.vault_name_master3,])
################ old vaults ###############
# test if old vaults are still accessible
self._retrieve_secret([
self.vault_name_master,
self.vault_name_master2,
self.vault_name_replica_without_KRA,
self.vault_name_replica_with_KRA,
])

View File

@@ -0,0 +1,443 @@
# 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/>.
"""Objects for communicating with remote hosts
This class defines "SSHTransport" as ParamikoTransport (by default), or as
OpenSSHTransport (if Paramiko is not importable, or the IPA_TEST_SSH_TRANSPORT
environment variable is set to "openssh").
"""
import os
import socket
import threading
import subprocess
from contextlib import contextmanager
import errno
from ipapython.ipa_log_manager import log_mgr
from ipatests import util
try:
import paramiko
have_paramiko = True
except ImportError:
have_paramiko = False
class Transport(object):
"""Mechanism for communicating with remote hosts
The Transport can manipulate files on a remote host, and open a Command.
The base class defines an interface that specific subclasses implement.
"""
def __init__(self, host):
self.host = host
self.logger_name = '%s.%s' % (host.logger_name, type(self).__name__)
self.log = log_mgr.get_logger(self.logger_name)
self._command_index = 0
def get_file_contents(self, filename):
"""Read the named remote file and return the contents as a string"""
raise NotImplementedError('Transport.get_file_contents')
def put_file_contents(self, filename, contents):
"""Write the given string to the named remote file"""
raise NotImplementedError('Transport.put_file_contents')
def file_exists(self, filename):
"""Return true if the named remote file exists"""
raise NotImplementedError('Transport.file_exists')
def mkdir(self, path):
"""Make the named directory"""
raise NotImplementedError('Transport.mkdir')
def start_shell(self, argv, log_stdout=True):
"""Start a Shell
:param argv: The command this shell is intended to run (used for
logging only)
:param log_stdout: If false, the stdout will not be logged (useful when
binary output is expected)
Given a `shell` from this method, the caller can then use
``shell.stdin.write()`` to input any command(s), call ``shell.wait()``
to let the command run, and then inspect ``returncode``,
``stdout_text`` or ``stderr_text``.
"""
raise NotImplementedError('Transport.start_shell')
def mkdir_recursive(self, path):
"""`mkdir -p` on the remote host"""
if not self.file_exists(path):
parent_path = os.path.dirname(path)
if path != parent_path:
self.mkdir_recursive(parent_path)
self.mkdir(path)
def get_file(self, remotepath, localpath):
"""Copy a file from the remote host to a local file"""
contents = self.get_file_contents(remotepath)
with open(localpath, 'wb') as local_file:
local_file.write(contents)
def put_file(self, localpath, remotepath):
"""Copy a local file to the remote host"""
with open(localpath, 'rb') as local_file:
contents = local_file.read()
self.put_file_contents(remotepath, contents)
def get_next_command_logger_name(self):
self._command_index += 1
return '%s.cmd%s' % (self.host.logger_name, self._command_index)
class Command(object):
"""A Popen-style object representing a remote command
Instances of this class should only be created via method of a concrete
Transport, such as start_shell.
The standard error and output are handled by this class. They're not
available for file-like reading, and are logged by default.
To make sure reading doesn't stall after one buffer fills up, they are read
in parallel using threads.
After calling wait(), ``stdout_text`` and ``stderr_text`` attributes will
be strings containing the output, and ``returncode`` will contain the
exit code.
"""
def __init__(self, argv, logger_name=None, log_stdout=True):
self.returncode = None
self.argv = argv
self._done = False
if logger_name:
self.logger_name = logger_name
else:
self.logger_name = '%s.%s' % (self.__module__, type(self).__name__)
self.log = log_mgr.get_logger(self.logger_name)
def wait(self, raiseonerr=True):
"""Wait for the remote process to exit
Raises an excption if the exit code is not 0, unless raiseonerr is
true.
"""
if self._done:
return self.returncode
self._end_process()
self._done = True
if raiseonerr and self.returncode:
self.log.error('Exit code: %s', self.returncode)
raise subprocess.CalledProcessError(self.returncode, self.argv)
else:
self.log.debug('Exit code: %s', self.returncode)
return self.returncode
def _end_process(self):
"""Wait until the process exits and output is received, close channel
Called from wait()
"""
raise NotImplementedError()
class ParamikoTransport(Transport):
"""Transport that uses the Paramiko SSH2 library"""
def __init__(self, host):
super(ParamikoTransport, self).__init__(host)
sock = socket.create_connection((host.external_hostname,
host.ssh_port))
self._transport = transport = paramiko.Transport(sock)
transport.connect(hostkey=host.host_key)
if host.root_ssh_key_filename:
self.log.debug('Authenticating with private RSA key')
filename = os.path.expanduser(host.root_ssh_key_filename)
key = paramiko.RSAKey.from_private_key_file(filename)
transport.auth_publickey(username='root', key=key)
elif host.root_password:
self.log.debug('Authenticating with password')
transport.auth_password(username='root',
password=host.root_password)
else:
self.log.critical('No SSH credentials configured')
raise RuntimeError('No SSH credentials configured')
@contextmanager
def sftp_open(self, filename, mode='r'):
"""Context manager that provides a file-like object over a SFTP channel
This provides compatibility with older Paramiko versions.
(In Paramiko 1.10+, file objects from `sftp.open` are directly usable
as context managers).
"""
file = self.sftp.open(filename, mode)
try:
yield file
finally:
file.close()
@property
def sftp(self):
"""Paramiko SFTPClient connected to this host"""
try:
return self._sftp
except AttributeError:
transport = self._transport
self._sftp = paramiko.SFTPClient.from_transport(transport)
return self._sftp
def get_file_contents(self, filename):
"""Read the named remote file and return the contents as a string"""
self.log.debug('READ %s', filename)
with self.sftp_open(filename) as f:
return f.read()
def put_file_contents(self, filename, contents):
"""Write the given string to the named remote file"""
self.log.info('WRITE %s', filename)
with self.sftp_open(filename, 'w') as f:
f.write(contents)
def file_exists(self, filename):
"""Return true if the named remote file exists"""
self.log.debug('STAT %s', filename)
try:
self.sftp.stat(filename)
except IOError, e:
if e.errno == errno.ENOENT:
return False
else:
raise
return True
def mkdir(self, path):
self.log.info('MKDIR %s', path)
self.sftp.mkdir(path)
def start_shell(self, argv, log_stdout=True):
logger_name = self.get_next_command_logger_name()
ssh = self._transport.open_channel('session')
self.log.info('RUN %s', argv)
return SSHCommand(ssh, argv, logger_name=logger_name,
log_stdout=log_stdout)
def get_file(self, remotepath, localpath):
self.log.debug('GET %s', remotepath)
self.sftp.get(remotepath, localpath)
def put_file(self, localpath, remotepath):
self.log.info('PUT %s', remotepath)
self.sftp.put(localpath, remotepath)
class OpenSSHTransport(Transport):
"""Transport that uses the `ssh` binary"""
def __init__(self, host):
super(OpenSSHTransport, self).__init__(host)
self.control_dir = util.TempDir()
self.ssh_argv = self._get_ssh_argv()
# Run a "control master" process. This serves two purposes:
# - Establishes a control socket; other SSHs will connect to it
# and reuse the same connection. This way the slow handshake
# only needs to be done once
# - Writes the host to known_hosts so stderr of "real" connections
# doesn't contain the "unknown host" warning
# Popen closes the stdin pipe when it's garbage-collected, so
# this process will exit when it's no longer needed
command = ['-o', 'ControlMaster=yes', '/usr/bin/cat']
self.control_master = self._run(command, collect_output=False)
def _get_ssh_argv(self):
"""Return the path to SSH and options needed for every call"""
control_file = os.path.join(self.control_dir.path, 'control')
known_hosts_file = os.path.join(self.control_dir.path, 'known_hosts')
argv = ['ssh',
'-l', 'root',
'-o', 'ControlPath=%s' % control_file,
'-o', 'StrictHostKeyChecking=no',
'-o', 'UserKnownHostsFile=%s' % known_hosts_file]
if self.host.root_ssh_key_filename:
key_filename = os.path.expanduser(self.host.root_ssh_key_filename)
argv.extend(['-i', key_filename])
elif self.host.root_password:
self.log.critical('Password authentication not supported')
raise RuntimeError('Password authentication not supported')
else:
self.log.critical('No SSH credentials configured')
raise RuntimeError('No SSH credentials configured')
argv.append(self.host.external_hostname)
self.log.debug('SSH invocation: %s', argv)
return argv
def start_shell(self, argv, log_stdout=True):
self.log.info('RUN %s', argv)
command = self._run(['bash'], argv=argv, log_stdout=log_stdout)
return command
def _run(self, command, log_stdout=True, argv=None, collect_output=True):
"""Run the given command on the remote host
:param command: Command to run (appended to the common SSH invocation)
:param log_stdout: If false, stdout will not be logged
:param argv: Command to log (if different from ``command``
:param collect_output: If false, no output will be collected
"""
if argv is None:
argv = command
logger_name = self.get_next_command_logger_name()
ssh = SSHCallWrapper(self.ssh_argv + list(command))
return SSHCommand(ssh, argv, logger_name, log_stdout=log_stdout,
collect_output=collect_output)
def file_exists(self, path):
self.log.info('STAT %s', path)
cmd = self._run(['ls', path], log_stdout=False)
cmd.wait(raiseonerr=False)
return cmd.returncode == 0
def mkdir(self, path):
self.log.info('MKDIR %s', path)
cmd = self._run(['mkdir', path])
cmd.wait()
def put_file_contents(self, filename, contents):
self.log.info('PUT %s', filename)
cmd = self._run(['tee', filename], log_stdout=False)
cmd.stdin.write(contents)
cmd.wait()
assert cmd.stdout_text == contents
def get_file_contents(self, filename):
self.log.info('GET %s', filename)
cmd = self._run(['cat', filename], log_stdout=False)
cmd.wait(raiseonerr=False)
if cmd.returncode == 0:
return cmd.stdout_text
else:
raise IOError('File %r could not be read' % filename)
class SSHCallWrapper(object):
"""Adapts a /usr/bin/ssh call to the paramiko.Channel interface
This only wraps what SSHCommand needs.
"""
def __init__(self, command):
self.command = command
def invoke_shell(self):
self.command = subprocess.Popen(
self.command,
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
def makefile(self, mode):
return {
'wb': self.command.stdin,
'rb': self.command.stdout,
}[mode]
def makefile_stderr(self, mode):
assert mode == 'rb'
return self.command.stderr
def shutdown_write(self):
self.command.stdin.close()
def recv_exit_status(self):
return self.command.wait()
def close(self):
return self.command.wait()
class SSHCommand(Command):
"""Command implementation for ParamikoTransport and OpenSSHTranspport"""
def __init__(self, ssh, argv, logger_name, log_stdout=True,
collect_output=True):
super(SSHCommand, self).__init__(argv, logger_name,
log_stdout=log_stdout)
self._stdout_lines = []
self._stderr_lines = []
self.running_threads = set()
self._ssh = ssh
self.log.debug('RUN %s', argv)
self._ssh.invoke_shell()
stdin = self.stdin = self._ssh.makefile('wb')
stdout = self._ssh.makefile('rb')
stderr = self._ssh.makefile_stderr('rb')
if collect_output:
self._start_pipe_thread(self._stdout_lines, stdout, 'out',
log_stdout)
self._start_pipe_thread(self._stderr_lines, stderr, 'err', True)
def _end_process(self, raiseonerr=True):
self._ssh.shutdown_write()
while self.running_threads:
self.running_threads.pop().join()
self.stdout_text = ''.join(self._stdout_lines)
self.stderr_text = ''.join(self._stderr_lines)
self.returncode = self._ssh.recv_exit_status()
self._ssh.close()
def _start_pipe_thread(self, result_list, stream, name, do_log=True):
"""Start a thread that copies lines from ``stream`` to ``result_list``
If do_log is true, also logs the lines under ``name``
The thread is added to ``self.running_threads``.
"""
log = log_mgr.get_logger('%s.%s' % (self.logger_name, name))
def read_stream():
for line in stream:
if do_log:
log.debug(line.rstrip('\n'))
result_list.append(line)
thread = threading.Thread(target=read_stream)
self.running_threads.add(thread)
thread.start()
return thread
if not have_paramiko or os.environ.get('IPA_TEST_SSH_TRANSPORT') == 'openssh':
SSHTransport = OpenSSHTransport
else:
SSHTransport = ParamikoTransport

View File

@@ -18,7 +18,16 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import time
import re
TESTHOST_PREFIX = 'TESTHOST_'
def check_config_dict_empty(dct, name):
"""Ensure that no keys are left in a configuration dict"""
if dct:
raise ValueError('Extra keys in confuguration for %s: %s' %
(name, ', '.join(dct)))
def run_repeatedly(host, command, assert_zero_rc=True, test=None,
@@ -57,21 +66,5 @@ def run_repeatedly(host, command, assert_zero_rc=True, test=None,
raise AssertionError("Command: {cmd} repeatedly failed {times} times, "
"exceeding the timeout of {timeout} seconds."
.format(cmd=' '.join(command),
times=timeout // time_step,
times=timeout / time_step,
timeout=timeout))
def get_host_ip_with_hostmask(host):
"""
Detects the IP of the host including the hostmask.
Returns None if the IP could not be detected.
"""
ip = host.ip
result = host.run_command(['ip', 'addr'])
full_ip_regex = r'(?P<full_ip>%s/\d{1,2}) ' % re.escape(ip)
match = re.search(full_ip_regex, result.stdout_text)
if match:
return match.group('full_ip')