From: John Mulligan Date: Tue, 20 Feb 2024 23:28:58 +0000 (-0500) Subject: qa/tasks: add a new cephadm task for setting up samba ad dc X-Git-Tag: v19.1.0~82^2~9 X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=eb12219967447c8e9e9291bf1392f16372b1c0da;p=ceph.git qa/tasks: add a new cephadm task for setting up samba ad dc Add a new task function to cephadm.py that sets up a container running the Samba based domain controller on a node using podman or docker. Much of the function actually deals with disabling systemd-resolved because that service conflicts with the DNS server component of the DC. Signed-off-by: John Mulligan (cherry picked from commit a99dc99589a0b81792abab1d14db6aad3ef9f2b8) --- diff --git a/qa/tasks/cephadm.py b/qa/tasks/cephadm.py index 248ce68e12f19..927cf6c4599ff 100644 --- a/qa/tasks/cephadm.py +++ b/qa/tasks/cephadm.py @@ -9,6 +9,7 @@ import json import logging import os import re +import time import uuid import yaml @@ -24,6 +25,7 @@ from teuthology import packaging from teuthology.orchestra import run from teuthology.orchestra.daemon import DaemonGroup from teuthology.config import config as teuth_config +from teuthology.exceptions import ConfigError, CommandFailedError from textwrap import dedent from tasks.cephfs.filesystem import MDSCluster, Filesystem from tasks.util import chacra @@ -1746,6 +1748,254 @@ def initialize_config(ctx, config): yield +def _disable_systemd_resolved(ctx, remote): + r = remote.run(args=['ss', '-lunH'], stdout=StringIO()) + # this heuristic tries to detect if systemd-resolved is running + if '%lo:53' not in r.stdout.getvalue(): + return + log.info('Disabling systemd-resolved on %s', remote.shortname) + # Samba AD DC container DNS support conflicts with resolved stub + # resolver when using host networking. And we want host networking + # because it is the simplest thing to set up. We therefore will turn + # off the stub resolver. + r = remote.run( + args=['sudo', 'cat', '/etc/systemd/resolved.conf'], + stdout=StringIO(), + ) + resolved_conf = r.stdout.getvalue() + setattr(ctx, 'orig_resolved_conf', resolved_conf) + new_resolved_conf = ( + resolved_conf + '\n# EDITED BY TEUTHOLOGY: deploy_samba_ad_dc\n' + ) + if '[Resolve]' not in new_resolved_conf.splitlines(): + new_resolved_conf += '[Resolve]\n' + new_resolved_conf += 'DNSStubListener=no\n' + remote.write_file( + path='/etc/systemd/resolved.conf', + data=new_resolved_conf, + sudo=True, + ) + remote.run(args=['sudo', 'systemctl', 'restart', 'systemd-resolved']) + r = remote.run(args=['ss', '-lunH'], stdout=StringIO()) + assert '%lo:53' not in r.stdout.getvalue() + # because docker is a big fat persistent deamon, we need to bounce it + # after resolved is restarted + remote.run(args=['sudo', 'systemctl', 'restart', 'docker']) + + +def _reset_systemd_resolved(ctx, remote): + orig_resolved_conf = getattr(ctx, 'orig_resolved_conf', None) + if not orig_resolved_conf: + return # no orig_resolved_conf means nothing to reset + log.info('Resetting systemd-resolved state on %s', remote.shortname) + remote.write_file( + path='/etc/systemd/resolved.conf', + data=orig_resolved_conf, + sudo=True, + ) + remote.run(args=['sudo', 'systemctl', 'restart', 'systemd-resolved']) + setattr(ctx, 'orig_resolved_conf', None) + + +def _samba_ad_dc_conf(ctx, remote, cengine): + # this config has not been tested outside of smithi nodes. it's possible + # that this will break when used elsewhere because we have to list + # interfaces explicitly. Later I may add a feature to sambacc to exclude + # known-unwanted interfaces that having to specify known good interfaces. + cf = { + "samba-container-config": "v0", + "configs": { + "demo": { + "instance_features": ["addc"], + "domain_settings": "sink", + "instance_name": "dc1", + } + }, + "domain_settings": { + "sink": { + "realm": "DOMAIN1.SINK.TEST", + "short_domain": "DOMAIN1", + "admin_password": "Passw0rd", + "interfaces": { + "exclude_pattern": "^docker[0-9]+$", + }, + } + }, + "domain_groups": { + "sink": [ + {"name": "supervisors"}, + {"name": "employees"}, + {"name": "characters"}, + {"name": "bulk"}, + ] + }, + "domain_users": { + "sink": [ + { + "name": "bwayne", + "password": "1115Rose.", + "given_name": "Bruce", + "surname": "Wayne", + "member_of": ["supervisors", "characters", "employees"], + }, + { + "name": "ckent", + "password": "1115Rose.", + "given_name": "Clark", + "surname": "Kent", + "member_of": ["characters", "employees"], + }, + { + "name": "user0", + "password": "1115Rose.", + "given_name": "George0", + "surname": "Hue-Sir", + "member_of": ["bulk"], + }, + { + "name": "user1", + "password": "1115Rose.", + "given_name": "George1", + "surname": "Hue-Sir", + "member_of": ["bulk"], + }, + { + "name": "user2", + "password": "1115Rose.", + "given_name": "George2", + "surname": "Hue-Sir", + "member_of": ["bulk"], + }, + { + "name": "user3", + "password": "1115Rose.", + "given_name": "George3", + "surname": "Hue-Sir", + "member_of": ["bulk"], + }, + ] + }, + } + cf_json = json.dumps(cf) + remote.run(args=['sudo', 'mkdir', '-p', '/var/tmp/samba']) + remote.write_file( + path='/var/tmp/samba/container.json', data=cf_json, sudo=True + ) + return [ + '--volume=/var/tmp/samba:/etc/samba-container:ro', + '-eSAMBACC_CONFIG=/etc/samba-container/container.json', + ] + + +@contextlib.contextmanager +def deploy_samba_ad_dc(ctx, config): + role = config.get('role') + ad_dc_image = config.get( + 'ad_dc_image', 'quay.io/samba.org/samba-ad-server:latest' + ) + samba_client_image = config.get( + 'samba_client_image', 'quay.io/samba.org/samba-client:latest' + ) + test_user_pass = config.get('test_user_pass', 'DOMAIN1\\ckent%1115Rose.') + if not role: + raise ConfigError( + "you must specify a role to allocate a host for the AD DC" + ) + (remote,) = ctx.cluster.only(role).remotes.keys() + ip = remote.ssh.get_transport().getpeername()[0] + cengine = 'podman' + try: + log.info("Testing if podman is available") + remote.run(args=['sudo', cengine, '--help']) + except CommandFailedError: + log.info("Failed to find podman. Using docker") + cengine = 'docker' + remote.run(args=['sudo', cengine, 'pull', ad_dc_image]) + remote.run(args=['sudo', cengine, 'pull', samba_client_image]) + _disable_systemd_resolved(ctx, remote) + remote.run( + args=[ + 'sudo', + 'mkdir', + '-p', + '/var/lib/samba/container/logs', + '/var/lib/samba/container/data', + ] + ) + remote.run( + args=[ + 'sudo', + cengine, + 'run', + '-d', + '--name=samba-ad', + '--network=host', + '--privileged', + ] + + _samba_ad_dc_conf(ctx, remote, cengine) + + [ad_dc_image] + ) + + # test that the ad dc is running and basically works + connected = False + samba_client_container_cmd = [ + 'sudo', + cengine, + 'run', + '--rm', + '--net=host', + f'--dns={ip}', + '-eKRB5_CONFIG=/dev/null', + samba_client_image, + ] + for idx in range(10): + time.sleep((2 ** (1 + idx)) / 8) + log.info("Probing SMB status of DC %s, idx=%s", ip, idx) + cmd = samba_client_container_cmd + [ + 'smbclient', + '-U', + test_user_pass, + '//domain1.sink.test/sysvol', + '-c', + 'ls', + ] + try: + remote.run(args=cmd) + connected = True + log.info("SMB status probe succeeded") + break + except CommandFailedError: + pass + if not connected: + raise RuntimeError('failed to connect to AD DC SMB share') + + setattr(ctx, 'samba_ad_dc_ip', ip) + setattr(ctx, 'samba_client_container_cmd', samba_client_container_cmd) + try: + yield + finally: + try: + remote.run(args=['sudo', cengine, 'stop', 'samba-ad']) + except CommandFailedError: + log.error("Failed to stop samba-ad container") + try: + remote.run(args=['sudo', cengine, 'rm', 'samba-ad']) + except CommandFailedError: + log.error("Failed to remove samba-ad container") + remote.run( + args=[ + 'sudo', + 'rm', + '-rf', + '/var/lib/samba/container/logs', + '/var/lib/samba/container/data', + ] + ) + _reset_systemd_resolved(ctx, remote) + setattr(ctx, 'samba_ad_dc_ip', None) + setattr(ctx, 'samba_client_container_cmd', None) + + @contextlib.contextmanager def task(ctx, config): """