mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2024-12-23 15:40:01 -06:00
8882fc49d0
Though time namespace support was added in Linux kernel 5.6, it is not landed on Azure VM (Ubuntu) yet. The syncing time stuff is required by IPA NTP tests. it's acceptable for testing 1 IPA environment on 1 Azure VM for such tests. Fixes: https://pagure.io/freeipa/issue/8316 Signed-off-by: Stanislav Levin <slev@altlinux.org> Reviewed-By: Christian Heimes <cheimes@redhat.com> Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
325 lines
10 KiB
Python
325 lines
10 KiB
Python
import logging
|
|
import os
|
|
import subprocess
|
|
|
|
import docker
|
|
from jinja2 import Template
|
|
|
|
logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s')
|
|
|
|
IPA_TESTS_ENV_WORKING_DIR = os.environ.get('IPA_TESTS_ENV_WORKING_DIR')
|
|
IPA_TESTS_ENV_NAME = os.environ.get('IPA_TESTS_ENV_NAME')
|
|
IPA_TESTS_ENV_ID = os.environ.get('IPA_TESTS_ENV_ID', '1')
|
|
IPA_TESTS_CLIENTS = int(os.environ.get('IPA_TESTS_CLIENTS', 0))
|
|
IPA_TESTS_REPLICAS = int(os.environ.get('IPA_TESTS_REPLICAS', 0))
|
|
IPA_TESTS_DOMAIN = os.environ.get('IPA_TESTS_DOMAIN', 'ipa.test')
|
|
IPA_SSH_PRIV_KEY = os.environ.get('IPA_SSH_PRIV_KEY', '/root/.ssh/id_rsa')
|
|
IPA_DNS_FORWARDER = os.environ.get('IPA_DNS_FORWARDER', '8.8.8.8')
|
|
IPA_NETWORK = os.environ.get('IPA_NETWORK', 'ipanet')
|
|
IPA_CONTROLLER_TYPE = os.environ.get('IPA_CONTROLLER_TYPE', 'master')
|
|
IPA_TEST_CONFIG_TEMPLATE = os.environ.get(
|
|
'IPA_TEST_CONFIG_TEMPLATE', './templates/ipa-test-config-template.yaml')
|
|
|
|
IPA_TESTS_ENV_DIR = os.path.join(IPA_TESTS_ENV_WORKING_DIR, IPA_TESTS_ENV_NAME)
|
|
IPA_TEST_CONFIG = "ipa-test-config.yaml"
|
|
|
|
|
|
class Container:
|
|
"""
|
|
Represents group of Docker container
|
|
"""
|
|
def __init__(self, role, dns=IPA_DNS_FORWARDER, num=1,
|
|
prefix=IPA_TESTS_ENV_ID, domain=IPA_TESTS_DOMAIN):
|
|
self.role = role
|
|
self.num = num
|
|
self.prefix = prefix
|
|
self.dns = dns
|
|
self.domain = domain
|
|
self.dclient = docker.from_env()
|
|
|
|
@property
|
|
def hostnames(self):
|
|
"""
|
|
hostnames of containers within group
|
|
"""
|
|
if not hasattr(self, '_hostnames'):
|
|
self._hostnames = ['{}{}.{}'.format(self.role, c, self.domain)
|
|
for c in range(1, self.num + 1)]
|
|
return self._hostnames
|
|
|
|
@property
|
|
def names(self):
|
|
"""
|
|
names of containers within group
|
|
"""
|
|
if not hasattr(self, '_names'):
|
|
self._names = ['{}_{}_{}'.format(self.prefix, self.role, c)
|
|
for c in range(1, self.num + 1)]
|
|
return self._names
|
|
|
|
def ip(self, name):
|
|
"""
|
|
ipv4 address of container
|
|
"""
|
|
ipanet = '{}_{}'.format(IPA_TESTS_ENV_ID, IPA_NETWORK)
|
|
dcont = self.dclient.containers.get(name)
|
|
return dcont.attrs['NetworkSettings']['Networks'][ipanet]['IPAddress']
|
|
|
|
@property
|
|
def ips(self):
|
|
"""
|
|
ipv4 addresses of containers within group
|
|
"""
|
|
if not hasattr(self, '_ips'):
|
|
self._ips = [self.ip(n) for n in self.names]
|
|
return self._ips
|
|
|
|
def umount_docker_resource(self, path):
|
|
"""
|
|
Umount resource by its path
|
|
"""
|
|
cmd = [
|
|
"/bin/umount", path
|
|
]
|
|
self.execute_all(cmd)
|
|
|
|
cmd = [
|
|
"/bin/chmod",
|
|
"a-x",
|
|
path,
|
|
]
|
|
self.execute_all(cmd)
|
|
|
|
def execute(self, name, args):
|
|
"""
|
|
Exec an arbitrary command within container
|
|
"""
|
|
dcont = self.dclient.containers.get(name)
|
|
logging.info("%s: run: %s", dcont.name, args)
|
|
result = dcont.exec_run(args, demux=True)
|
|
if result.output[0] is not None:
|
|
logging.info("%s: %s", dcont.name, result.output[0])
|
|
logging.info("%s: result: %s", dcont.name, result.exit_code)
|
|
if result.exit_code:
|
|
logging.error("stderr: %s", result.output[1].decode())
|
|
raise subprocess.CalledProcessError(
|
|
result.exit_code, args,
|
|
result.output[1]
|
|
)
|
|
return result
|
|
|
|
def execute_all(self, args):
|
|
"""
|
|
Exec an arbitrary command within every container of group
|
|
"""
|
|
results = []
|
|
for n in self.names:
|
|
results.append(self.execute(n, args))
|
|
return results
|
|
|
|
def add_ssh_pubkey(self, key):
|
|
"""
|
|
Add ssh public key into every container of group
|
|
"""
|
|
home_ssh_dir = "/root/.ssh"
|
|
auth_keys = os.path.join(home_ssh_dir, "authorized_keys")
|
|
cmd = [
|
|
"/bin/bash", "-c",
|
|
(f"mkdir {home_ssh_dir} "
|
|
f"; chmod 0700 {home_ssh_dir} "
|
|
f"&& touch {auth_keys} "
|
|
f"&& chmod 0600 {auth_keys} "
|
|
f"&& echo {key} >> {auth_keys}"
|
|
)
|
|
]
|
|
self.execute_all(cmd)
|
|
|
|
def setup_hosts(self):
|
|
"""
|
|
Overwrite hosts within every container of group
|
|
"""
|
|
self.umount_docker_resource("/etc/hosts")
|
|
for n, i, h in zip(self.names, self.ips, self.hostnames):
|
|
hosts = "127.0.0.1 localhost\n::1 localhost\n{ip} {host}".format(
|
|
ip=i, host=h,
|
|
)
|
|
cmd = [
|
|
"/bin/bash", "-c",
|
|
"echo -e '{hosts}' > /etc/hosts".format(hosts=hosts),
|
|
]
|
|
self.execute(name=n, args=cmd)
|
|
|
|
def setup_hostname(self):
|
|
self.umount_docker_resource("/etc/hostname")
|
|
for n, h in zip(self.names, self.hostnames):
|
|
cmd = [
|
|
"/bin/bash", "-c",
|
|
"echo -e '{hostname}' > /etc/hostname".format(hostname=h),
|
|
]
|
|
self.execute(name=n, args=cmd)
|
|
|
|
cmd = [
|
|
"hostnamectl",
|
|
"set-hostname", h,
|
|
]
|
|
self.execute(name=n, args=cmd)
|
|
|
|
def setup_resolvconf(self):
|
|
"""
|
|
Overwrite resolv conf within every container of group
|
|
"""
|
|
self.umount_docker_resource("/etc/resolv.conf")
|
|
ns = "nameserver {dns}".format(dns=self.dns)
|
|
cmd = [
|
|
"/bin/bash", "-c",
|
|
"echo {ns} > /etc/resolv.conf".format(ns=ns),
|
|
]
|
|
self.execute_all(cmd)
|
|
|
|
def ignore_service_in_container(self, service):
|
|
"""
|
|
Amend systemd service configuration to be ignored in a container
|
|
"""
|
|
service_dir = os.path.join(
|
|
"/etc/systemd/system", "{}.service.d".format(service))
|
|
override_file = os.path.join(service_dir, "ipa-override.conf")
|
|
cmds = [
|
|
"/bin/bash", "-c",
|
|
(f"mkdir -p {service_dir};"
|
|
f"echo '[Unit]' > {override_file};"
|
|
f"echo 'ConditionVirtualization=!container' >> {override_file}")
|
|
]
|
|
self.execute_all(args=cmds)
|
|
|
|
def setup_container_overrides(self):
|
|
"""
|
|
Set services known to not work in containers to be ignored
|
|
"""
|
|
for service in ['nis-domainname',]:
|
|
self.ignore_service_in_container(service)
|
|
|
|
self.execute_all(args=["systemctl", "daemon-reload"])
|
|
|
|
|
|
class Controller(Container):
|
|
"""
|
|
Manages groups of containers
|
|
"""
|
|
def __init__(self, contr_type=IPA_CONTROLLER_TYPE):
|
|
self.containers = []
|
|
self.contr_type = contr_type
|
|
if self.contr_type == 'master':
|
|
self.master = None
|
|
|
|
def append(self, container):
|
|
self.containers.append(container)
|
|
|
|
def setup_ssh(self):
|
|
"""
|
|
Generate ssh key pair and copy public part to all containers
|
|
"""
|
|
cmd = ["rm", "-f", IPA_SSH_PRIV_KEY]
|
|
self.execute(args=cmd)
|
|
|
|
cmd = [
|
|
"ssh-keygen", "-q",
|
|
"-f", IPA_SSH_PRIV_KEY,
|
|
"-t", "rsa",
|
|
"-m", "PEM",
|
|
"-N", "",
|
|
]
|
|
self.execute(args=cmd)
|
|
|
|
cmd = ["/bin/bash", "-c", "cat {}.pub".format(IPA_SSH_PRIV_KEY)]
|
|
key = self.execute(cmd).output[0].decode().rstrip()
|
|
for container in self.containers:
|
|
container.add_ssh_pubkey(key)
|
|
|
|
def execute(self, args):
|
|
"""
|
|
Execute a command on controller (either master or local machine)
|
|
"""
|
|
if self.contr_type == 'master':
|
|
if self.master is None:
|
|
for container in self.containers:
|
|
if container.role == "master":
|
|
self.master = container
|
|
break
|
|
return self.master.execute(name=master.names[0], args=args)
|
|
|
|
proc = subprocess.run(args, check=True, capture_output=True)
|
|
return [proc.stdout.decode().rstrip().strip("'")]
|
|
|
|
def setup_hosts(self):
|
|
"""
|
|
Overwrite Docker's hosts
|
|
"""
|
|
hosts = []
|
|
for container in self.containers:
|
|
container.setup_hosts()
|
|
for i, h in zip(container.ips, container.hostnames):
|
|
hosts.append("{} {}".format(i, h))
|
|
|
|
cmd = [
|
|
"/bin/bash", "-c",
|
|
"echo -e '{hosts}' >> /etc/hosts".format(hosts='\n'.join(hosts)),
|
|
]
|
|
self.execute(cmd)
|
|
|
|
def setup_hostname(self):
|
|
"""
|
|
Overwrite Docker's hostname
|
|
"""
|
|
for container in self.containers:
|
|
container.setup_hostname()
|
|
|
|
def setup_resolvconf(self):
|
|
"""
|
|
Overwrite Docker's embedded DNS ns
|
|
"""
|
|
for container in self.containers:
|
|
container.setup_resolvconf()
|
|
|
|
def generate_ipa_test_config(self, config):
|
|
with open(IPA_TEST_CONFIG_TEMPLATE, 'r') as f:
|
|
# assert foobar
|
|
template = Template(f.read(), trim_blocks=True, lstrip_blocks=True)
|
|
|
|
print(template.render(config))
|
|
|
|
with open(os.path.join(IPA_TESTS_ENV_DIR, IPA_TEST_CONFIG), 'w') as f:
|
|
f.write(template.render(config))
|
|
|
|
def setup_container_overrides(self):
|
|
"""
|
|
Override services known to not work in containers
|
|
"""
|
|
for container in self.containers:
|
|
container.setup_container_overrides()
|
|
|
|
|
|
controller = Controller()
|
|
master = Container(role='master')
|
|
clients = Container(role='client', num=IPA_TESTS_CLIENTS, dns=master.ips[0])
|
|
replicas = Container(role='replica', num=IPA_TESTS_REPLICAS, dns=master.ips[0])
|
|
|
|
controller.append(master)
|
|
controller.append(clients)
|
|
controller.append(replicas)
|
|
|
|
controller.setup_ssh()
|
|
controller.setup_hosts()
|
|
controller.setup_hostname()
|
|
controller.setup_resolvconf()
|
|
controller.setup_container_overrides()
|
|
|
|
config = {
|
|
'dns_forwarder': IPA_DNS_FORWARDER,
|
|
'ssh_private_key': IPA_SSH_PRIV_KEY,
|
|
'domain_name': IPA_TESTS_DOMAIN,
|
|
'master': master.ips,
|
|
'replicas': replicas.ips,
|
|
'clients': clients.ips,
|
|
}
|
|
controller.generate_ipa_test_config(config)
|