tests: Add test to cover daemon socket paths
authorKienan Stewart <kstewart@efficios.com>
Fri, 26 Apr 2024 21:04:17 +0000 (17:04 -0400)
committerJérémie Galarneau <jeremie.galarneau@efficios.com>
Thu, 31 Oct 2024 16:06:01 +0000 (12:06 -0400)
Drawbacks
=========

The shared memory test uses `/dev/shm` which is only available on Linux
systems. The test is skipped otherwise.

Change-Id: I2e60c5b3191c638d6e36ceafa88e01473891c250
Signed-off-by: Kienan Stewart <kstewart@efficios.com>
Signed-off-by: Jérémie Galarneau <jeremie.galarneau@efficios.com>
.gitignore
configure.ac
tests/regression/Makefile.am
tests/regression/tools/config-directory/test_config.py.in [new file with mode: 0755]
tests/utils/lttngtest/environment.py
tests/utils/lttngtest/lttng.py

index 204ea1125d5a99e4ecb8ceb9103b14f40a28c6e7..13a1c9911adea96deed88fb67884e7f97826cbe8 100644 (file)
@@ -106,6 +106,7 @@ compile_commands.json
 /tests/utils/testapp/gen-ust-events-constructor/gen-ust-events-c-constructor-so
 /tests/utils/testapp/gen-ust-events-constructor/uses_heap
 /tests/utils/testapp/gen-ust-events-ns/gen-ust-events-ns
+/tests/regression/tools/config-directory/test_config.py
 /tests/regression/tools/health/health_check
 /tests/regression/kernel/select_poll_epoll
 /tests/regression/tools/notification/base_client
index ee4ee5aa7cce915e0dd8510b59773ed46730520a..59c00fe880c97e4cbf74db4649c000a1957d52a5 100644 (file)
@@ -1365,6 +1365,7 @@ AC_CONFIG_FILES([
 ])
 
 # Inject variable into python test script.
+AC_CONFIG_FILES([tests/regression/tools/config-directory/test_config.py],[chmod +x tests/regression/tools/config-directory/test_config.py])
 AC_CONFIG_FILES([tests/regression/ust/python-logging/test_python_logging],[chmod +x tests/regression/ust/python-logging/test_python_logging])
 # Inject LTTNG_TOOLS_BUILD_WITH_LIBPFM variable in test script.
 AC_CONFIG_FILES([tests/perf/test_perf_raw],[chmod +x tests/perf/test_perf_raw])
index 3dda0a2b63aeed8503bb745055fd0736753e4563..90f5c512fef89abb7b74cf5eb2b89c8c67389127 100644 (file)
@@ -101,6 +101,7 @@ TESTS += ust/before-after/test_before_after \
        ust/ust-constructor/test_ust_constructor_c_static.py \
        ust/ust-constructor/test_ust_constructor_cpp_dynamic.py \
        ust/ust-constructor/test_ust_constructor_cpp_static.py \
+       tools/config-directory/test_config.py \
        tools/metadata/test_ust \
        tools/relayd-grouping/test_ust \
        tools/trigger/rate-policy/test_ust_rate_policy
diff --git a/tests/regression/tools/config-directory/test_config.py.in b/tests/regression/tools/config-directory/test_config.py.in
new file mode 100755 (executable)
index 0000000..0166347
--- /dev/null
@@ -0,0 +1,278 @@
+#!/usr/bin/env python3
+#
+# SPDX-FileCopyrightText: 2024 Kienan Stewart <kstewart@efficios.com>
+# SPDX-License-Identifier: GPL-2.0-only
+#
+
+"""
+Tests the behaviour of the configuration and socket paths for lttng-sessiond, lttng-relayd, the consumer daemons, and liblttng-ctl.
+"""
+
+import copy
+import os
+import os.path
+import pathlib
+import platform
+import pwd
+import re
+import socket
+import stat
+import subprocess
+import sys
+import tempfile
+import time
+
+# Import in-tree test utils
+test_utils_import_path = pathlib.Path(__file__).absolute().parents[3] / "utils"
+sys.path.append(str(test_utils_import_path))
+
+import lttngtest
+import bt2
+
+user_info = pwd.getpwuid(os.getuid())
+user_is_root = user_info.pw_uid == 0
+
+DEFAULT_SUBSTITUTIONS = {
+    "USER_HOME": user_info.pw_dir,
+    "USER_ID": user_info.pw_uid,
+    "SYSTEM_HOME": "@LTTNG_SYSTEM_RUNDIR@",
+    "LTTNG_HOME": "@LTTNG_SYSTEM_RUNDIR@" if user_is_root else "{USER_HOME}",
+    "RUNDIR": "@LTTNG_SYSTEM_RUNDIR@" if user_is_root else "{LTTNG_HOME}/.lttng",
+    # If the values from the lttng-ust headers change, these will need to be updated.
+    "LTTNG_UST_SOCKET_NAME": "lttng-ust-sock-8",
+    "LTTNG_UST_WAIT_FILENAME": "lttng-ust-wait-8",
+}
+
+DEFAULT_EXPECTED_PATHS = {
+    "rundir": {
+        "type": "directory",
+        "path": "{RUNDIR}",
+    },
+    "apps_unix_sock_path": {
+        "type": "socket",
+        "path": "{RUNDIR}/{LTTNG_UST_SOCKET_NAME}",
+    },
+    "wait_shm_path": {
+        "type": "shm",
+        "path": (
+            "/{LTTNG_UST_WAIT_FILENAME}"
+            if user_is_root
+            else "/{LTTNG_UST_WAIT_FILENAME}-{USER_ID}"
+        ),
+    },
+    "client_unix_sock_path": {
+        "type": "socket",
+        "path": "{RUNDIR}/client-lttng-sessiond",
+    },
+    "sessiond_health_unix_sock_path": {
+        "type": "socket",
+        "path": "{RUNDIR}/sessiond-health",
+    },
+    "sessiond_notification_unix_sock_path": {
+        "type": "socket",
+        "path": "{RUNDIR}/sessiond-notification",
+    },
+    "relayd_health_unix_sock_path": {
+        "type": "socket",
+        "path": "{RUNDIR}/relayd/health-{RELAYD_PID}",
+    },
+}
+
+if platform.architecture()[0] == "64bit":
+    DEFAULT_EXPECTED_PATHS["ustconsumerd64_health_unix_sock_path"] = {
+        "type": "socket",
+        "path": "{RUNDIR}/ustconsumerd64/health",
+    }
+else:
+    DEFAULT_EXPECTED_PATHS["ustconsumerd32_healh_unix_sock_path"] = {
+        "type": "socket",
+        "path": "{RUNDIR}/ustconsumerd32/health",
+    }
+
+if lttngtest._Environment.run_kernel_tests():
+    DEFAULT_EXPECTED_PATHS["kconsumerd_health_unix_sock_path"] = {
+        "type": "socket",
+        "path": "{RUNDIR}/kconsumerd/health",
+    }
+
+# These are used for compatibility with python < 3.9. If the python
+# version is 3.9+, `dict_a | dict_b` can be used instead.
+_ust_ctl_path_expected_paths = copy.deepcopy(DEFAULT_EXPECTED_PATHS)
+_ust_ctl_path_expected_paths.update(
+    {
+        "apps_unix_sock_path": {
+            "type": "socket",
+            "path": "{LTTNG_UST_CTL_PATH}/{LTTNG_UST_SOCKET_NAME}",
+        },
+    }
+)
+
+tests = {
+    "default": {
+        "description": "LTTng with no specific environment settings",
+        "environment_variables": {},
+        "substitutions": DEFAULT_SUBSTITUTIONS,
+        "expected_paths": DEFAULT_EXPECTED_PATHS,
+    },
+    "lttng_home": {
+        "description": "LTTng with LTTNG_HOME set",
+        "environment_variables": {
+            # This value will come from the lttngtest environment
+            "LTTNG_HOME": True,
+        },
+        "substitutions": DEFAULT_SUBSTITUTIONS,
+        "expected_paths": DEFAULT_EXPECTED_PATHS,
+    },
+    "ust_ctl_path": {
+        "description": "LTTng with LTTNG_UST_CTL_PATH set",
+        "environment_variables": {
+            # This value will be generated in the lttngtest environment
+            "LTTNG_UST_CTL_PATH": True,
+        },
+        "substitutions": DEFAULT_SUBSTITUTIONS,
+        "expected_paths": _ust_ctl_path_expected_paths,
+    },
+    "lttng_home_and_ust_ctl_path": {
+        "description": "LTTng with LTTNG_HOME and LTTNG_UST_CTL_PATH set",
+        "environment_variables": {
+            "LTTNG_HOME": True,
+            "LTTNG_UST_CTL_PATH": True,
+        },
+        "substitutions": DEFAULT_SUBSTITUTIONS,
+        "expected_paths": _ust_ctl_path_expected_paths,
+    },
+}
+
+
+class MissingDict(dict):
+    def __missing__(self, key):
+        return key
+
+
+def test_config_and_socket_paths(
+    test_env,
+    tap,
+    test_identifier,
+    description,
+    environment_variables,
+    substitutions,
+    expected_paths,
+):
+    tap.diagnostic(
+        "[{}] Config and socket paths - {}".format(test_identifier, description)
+    )
+
+    if (
+        "LTTNG_HOME" in environment_variables
+        and test_env.lttng_home_location is not None
+    ):
+        environment_variables["LTTNG_HOME"] = str(test_env.lttng_home_location)
+        substitutions.update(environment_variables)
+
+    substitutions["RELAYD_PID"] = test_env._relayd.pid
+
+    client = lttngtest.LTTngClient(test_env, log=tap.diagnostic)
+    session_output_location = lttngtest.LocalSessionOutputLocation(
+        test_env.create_temporary_directory("trace")
+    )
+    session = client.create_session(output=session_output_location)
+    channel = session.add_channel(lttngtest.lttngctl.TracingDomain.User)
+    channel.add_recording_rule(lttngtest.lttngctl.UserTracepointEventRule("tp:tptest"))
+
+    kernel_channel = None
+    if test_env.run_kernel_tests():
+        kernel_channel = session.add_channel(lttngtest.lttngctl.TracingDomain.Kernel)
+        kernel_channel.add_recording_rule(
+            lttngtest.lttngctl.KernelTracepointEventRule("*")
+        )
+
+    session.start()
+    test_app = test_env.launch_wait_trace_test_application(100)
+    test_app.trace()
+    test_app.wait_for_tracing_done()
+    test_app.wait_for_exit()
+
+    errors = []
+    for path_id, path_conf in expected_paths.items():
+        path = path_conf["path"]
+        while re.search("\{\w+\}", path) is not None:
+            path = path.format_map(MissingDict(substitutions))
+        tap.diagnostic(
+            "Checking for file type `{}` at `{}`".format(path_conf["type"], path)
+        )
+        if path_conf["type"] == "file":
+            if not os.path.isfile(path):
+                tap.diagnostic("`{}` is not a file".format(path))
+                errors.append(path_id)
+        elif path_conf["type"] == "socket":
+            try:
+                if not stat.S_ISSOCK(os.stat(path).st_mode):
+                    tap.diagnostic("`{}` is not a socket".format(path))
+                    errors.append(path_id)
+            except Exception as e:
+                tap.diagnostic(
+                    "Exception while checking socket `{}`: {}".format(path, str(e))
+                )
+                errors.append(path_id)
+        elif path_conf["type"] == "directory":
+            if not os.path.isdir(path):
+                tap.diagnostic("`{}` is not a directory".format(path))
+                errors.append(path_id)
+        elif path_conf["type"] == "shm":
+            # @FIXME: Portability on Windows and MacOSX
+            if platform.system() != "Linux":
+                tap.diagnostic("Skipping check of shm at `{}`".format(path))
+                continue
+            path = "/dev/shm/{}".format(path)
+            if not os.path.isfile(path):
+                tap.diagnostic("Shared memory at `{}` is not a file".format(path))
+                errors.append(path_id)
+        else:
+            tap.diagnostic(
+                "Unknown type `{}` for path `{}`".format(path_conf["type"], path)
+            )
+            errors.append(path_id)
+    tap.test(
+        len(errors) == 0,
+        "{} expected configuration paths exist. {} errors".format(
+            len(expected_paths), len(errors)
+        ),
+    )
+
+    session.stop()
+    session.destroy()
+
+
+if __name__ == "__main__":
+    tap = lttngtest.TapGenerator(len(tests))
+
+    for test_identifier, test_configuration in tests.items():
+        temp_conf = copy.deepcopy(test_configuration)
+        skip_lttng_home = (
+            "LTTNG_HOME" not in test_configuration["environment_variables"]
+        )
+        if skip_lttng_home and os.getenv("LTTNG_HOME", None) is not None:
+            tap.skip(
+                "LTTNG_HOME is already set, skipping test that should exercise the default value"
+            )
+            continue
+
+        # This will hold keep the temporary object file alive until the next
+        # iteration. When the object is destroy, the tempfile will be cleaned
+        # up.
+        tempfiles = []
+        if "LTTNG_UST_CTL_PATH" in test_configuration["environment_variables"]:
+            tempfiles.append(tempfile.TemporaryDirectory())
+            temp_conf["environment_variables"]["LTTNG_UST_CTL_PATH"] = tempfiles[
+                -1
+            ].name
+            temp_conf["substitutions"]["LTTNG_UST_CTL_PATH"] = tempfiles[-1].name
+        with lttngtest.test_environment(
+            with_sessiond=True,
+            log=tap.diagnostic,
+            with_relayd=True,
+            extra_env_vars=temp_conf["environment_variables"],
+            skip_temporary_lttng_home=skip_lttng_home,
+        ) as test_env:
+            test_config_and_socket_paths(test_env, tap, test_identifier, **temp_conf)
+    sys.exit(0 if tap.is_successful else 1)
index f97546367aaa3e5516b988e359add8564aad311a..03c1b5c8168b2e8d97e2b0886da82bbf52fc8ab4 100644 (file)
@@ -236,7 +236,11 @@ class _WaitTraceTestApplication:
         self._environment = environment  # type: Environment
         self._iteration_count = event_count
         # File that the application will wait to see before tracing its events.
-        dir = self._compat_pathlike(environment.lttng_home_location)
+        dir = (
+            self._compat_pathlike(environment.lttng_home_location)
+            if environment.lttng_home_location
+            else None
+        )
         if run_as is not None:
             dir = os.path.join(dir, run_as)
         self._app_start_tracing_file_path = pathlib.Path(
@@ -269,7 +273,8 @@ class _WaitTraceTestApplication:
         self._tracing_started = False
 
         test_app_env = os.environ.copy()
-        test_app_env["LTTNG_HOME"] = str(environment.lttng_home_location)
+        if environment.lttng_home_location is not None:
+            test_app_env["LTTNG_HOME"] = str(environment.lttng_home_location)
         # Make sure the app is blocked until it is properly registered to
         # the session daemon.
         test_app_env["LTTNG_UST_REGISTER_TIMEOUT"] = "-1"
@@ -627,6 +632,8 @@ class _Environment(logger._Logger):
         with_sessiond,  # type: bool
         log=None,  # type: Optional[Callable[[str], None]]
         with_relayd=False,  # type: bool
+        extra_env_vars=dict(),  # type: dict
+        skip_temporary_lttng_home=False,  # type: bool
     ):
         super().__init__(log)
         signal.signal(signal.SIGTERM, self._handle_termination_signal)
@@ -643,13 +650,24 @@ class _Environment(logger._Logger):
             pathlib.Path(__file__).absolute().parents[3]
         )  # type: pathlib.Path
 
-        self._lttng_home = TemporaryDirectory(
-            "lttng_test_env_home"
-        )  # type: Optional[str]
-        os.chmod(
-            str(self._lttng_home.path),
-            stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IROTH | stat.S_IXOTH,
-        )
+        self._extra_env_vars = extra_env_vars
+
+        # There are times when we need to exercise default configurations
+        # that don't set LTTNG_HOME. When doing this, it makes it impossible
+        # to safely run parallel tests.
+        self._lttng_home = None
+        if not skip_temporary_lttng_home:
+            self._lttng_home = TemporaryDirectory(
+                "lttng_test_env_home"
+            )  # type: Optional[TemporaryDirectory]
+            os.chmod(
+                str(self._lttng_home.path),
+                stat.S_IRUSR
+                | stat.S_IWUSR
+                | stat.S_IXUSR
+                | stat.S_IROTH
+                | stat.S_IXOTH,
+            )
 
         self._relayd = (
             self._launch_lttng_relayd() if with_relayd else None
@@ -666,9 +684,9 @@ class _Environment(logger._Logger):
     @property
     def lttng_home_location(self):
         # type: () -> pathlib.Path
-        if self._lttng_home is None:
-            raise RuntimeError("Attempt to access LTTng home after clean-up")
-        return self._lttng_home.path
+        if self._lttng_home is not None:
+            return self._lttng_home.path
+        return None
 
     @property
     def lttng_client_path(self):
@@ -742,14 +760,21 @@ class _Environment(logger._Logger):
         # type: (Optional[str]) -> pathlib.Path
         # Simply return a path that is contained within LTTNG_HOME; it will
         # be destroyed when the temporary home goes out of scope.
-        assert self._lttng_home
         return pathlib.Path(
             tempfile.mkdtemp(
                 prefix="tmp" if prefix is None else prefix,
-                dir=str(self._lttng_home.path),
+                dir=str(self.lttng_home_location) if self.lttng_home_location else None,
             )
         )
 
+    @staticmethod
+    def run_kernel_tests():
+        # type: () -> bool
+        return (
+            os.getenv("LTTNG_TOOLS_DISABLE_KERNEL_TESTS", "0") != "1"
+            and os.getuid() == 0
+        )
+
     # Unpack a list of environment variables from a string
     # such as "HELLO=is_it ME='/you/are/looking/for'"
     @staticmethod
@@ -785,16 +810,19 @@ class _Environment(logger._Logger):
 
         relayd_env_vars = os.environ.get("LTTNG_RELAYD_ENV_VARS")
         relayd_env = os.environ.copy()
+        relayd_env.update(self._extra_env_vars)
         if relayd_env_vars:
             self._log("Additional lttng-relayd environment variables:")
             for name, value in self._unpack_env_vars(relayd_env_vars):
                 self._log("{}={}".format(name, value))
                 relayd_env[name] = value
 
-        assert self._lttng_home is not None
-        relayd_env["LTTNG_HOME"] = str(self._lttng_home.path)
+        if self.lttng_home_location is not None:
+            relayd_env["LTTNG_HOME"] = str(self.lttng_home_location)
         self._log(
-            "Launching relayd with LTTNG_HOME='${}'".format(str(self._lttng_home.path))
+            "Launching relayd with LTTNG_HOME='${}'".format(
+                str(self.lttng_home_location)
+            )
         )
         verbose = []
         if os.environ.get("LTTNG_TEST_VERBOSE_RELAYD") is not None:
@@ -877,6 +905,7 @@ class _Environment(logger._Logger):
         # Setup the session daemon's environment
         sessiond_env_vars = os.environ.get("LTTNG_SESSIOND_ENV_VARS")
         sessiond_env = os.environ.copy()
+        sessiond_env.update(self._extra_env_vars)
         if sessiond_env_vars:
             self._log("Additional lttng-sessiond environment variables:")
             additional_vars = self._unpack_env_vars(sessiond_env_vars)
@@ -888,14 +917,14 @@ class _Environment(logger._Logger):
             self._project_root / "src" / "common"
         )
 
-        assert self._lttng_home is not None
-        sessiond_env["LTTNG_HOME"] = str(self._lttng_home.path)
+        if self.lttng_home_location is not None:
+            sessiond_env["LTTNG_HOME"] = str(self.lttng_home_location)
 
         wait_queue = _SignalWaitQueue()
         with wait_queue.intercept_signal(signal.SIGUSR1):
             self._log(
                 "Launching session daemon with LTTNG_HOME=`{home_dir}`".format(
-                    home_dir=str(self._lttng_home.path)
+                    home_dir=str(self.lttng_home_location)
                 )
             )
             verbose = []
@@ -1099,9 +1128,17 @@ class _Environment(logger._Logger):
 
 
 @contextlib.contextmanager
-def test_environment(with_sessiond, log=None, with_relayd=False):
+def test_environment(
+    with_sessiond,
+    log=None,
+    with_relayd=False,
+    extra_env_vars=dict(),
+    skip_temporary_lttng_home=False,
+):
     # type: (bool, Optional[Callable[[str], None]], bool) -> Iterator[_Environment]
-    env = _Environment(with_sessiond, log, with_relayd)
+    env = _Environment(
+        with_sessiond, log, with_relayd, extra_env_vars, skip_temporary_lttng_home
+    )
     try:
         yield env
     finally:
index 3d59776578556bb8cd02414fbb177904a3d7743c..3365cd0d8788e9b4a897f881ee7b24bc397531c3 100644 (file)
@@ -261,7 +261,7 @@ class _Channel(lttngctl.Channel):
             if rule.filter_expression:
                 client_args = client_args + " " + rule.filter_expression
 
-            if rule.log_level_rule:
+            if getattr(rule, "log_level_rule", None):
                 if isinstance(rule.log_level_rule, lttngctl.LogLevelRuleAsSevereAs):
                     client_args = client_args + " --loglevel {log_level}".format(
                         log_level=_get_log_level_argument_name(
@@ -281,7 +281,7 @@ class _Channel(lttngctl.Channel):
                         )
                     )
 
-            if rule.name_pattern_exclusions:
+            if getattr(rule, "name_pattern_exclusions", None):
                 client_args = client_args + " --exclude "
                 for idx, pattern in enumerate(rule.name_pattern_exclusions):
                     if idx != 0:
@@ -516,15 +516,20 @@ class _Session(lttngctl.Session):
         # type: (lttngctl.TracingDomain, Optional[str], lttngctl.BufferSharingPolicy) -> lttngctl.Channel
         channel_name = lttngctl.Channel._generate_name()
         domain_option_name = _get_domain_option_name(domain)
+        buffer_sharing_policy = (
+            "--buffers-uid"
+            if buffer_sharing_policy == lttngctl.BufferSharingPolicy.PerUID
+            else "--buffers-pid"
+        )
         self._client._run_cmd(
             "enable-channel --session '{session_name}' --{domain_name} '{channel_name}' {buffer_sharing_policy}".format(
                 session_name=self.name,
                 domain_name=domain_option_name,
                 channel_name=channel_name,
                 buffer_sharing_policy=(
-                    "--buffers-uid"
-                    if buffer_sharing_policy == lttngctl.BufferSharingPolicy.PerUID
-                    else "--buffers-pid"
+                    buffer_sharing_policy
+                    if domain != lttngctl.TracingDomain.Kernel
+                    else ""
                 ),
             )
         )
@@ -677,7 +682,8 @@ class LTTngClient(logger._Logger, lttngctl.Controller):
         self._log("lttng {command_args}".format(command_args=command_args))
 
         client_env = os.environ.copy()  # type: dict[str, str]
-        client_env["LTTNG_HOME"] = str(self._environment.lttng_home_location)
+        if self._environment.lttng_home_location is not None:
+            client_env["LTTNG_HOME"] = str(self._environment.lttng_home_location)
 
         process = subprocess.Popen(
             args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=client_env
This page took 0.032119 seconds and 4 git commands to generate.