freeipa/ipatests/azure/scripts/setup_containers.py
Stanislav Levin 8882fc49d0 Azure: Allow chronyd to sync time
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>
2020-05-12 09:51:50 +02:00

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)