]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
qa/tasks: add a new cephadm task for setting up samba ad dc
authorJohn Mulligan <jmulligan@redhat.com>
Tue, 20 Feb 2024 23:28:58 +0000 (18:28 -0500)
committerJohn Mulligan <jmulligan@redhat.com>
Thu, 21 Mar 2024 22:30:58 +0000 (18:30 -0400)
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 <jmulligan@redhat.com>
qa/tasks/cephadm.py

index 248ce68e12f190e2b27e8112c0c5af3b311efb9c..927cf6c4599fff7e78db80e4bbd6cfe4ad024b90 100644 (file)
@@ -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):
     """