From 11ababaeaf2a133ea263041cb254e2a272a8f893 Mon Sep 17 00:00:00 2001 From: Kienan Stewart Date: Fri, 26 Apr 2024 17:04:17 -0400 Subject: [PATCH] tests: Add test to cover daemon socket paths MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit 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 Signed-off-by: Jérémie Galarneau --- .gitignore | 1 + configure.ac | 1 + tests/regression/Makefile.am | 1 + .../tools/config-directory/test_config.py.in | 278 ++++++++++++++++++ tests/utils/lttngtest/environment.py | 81 +++-- tests/utils/lttngtest/lttng.py | 18 +- 6 files changed, 352 insertions(+), 28 deletions(-) create mode 100755 tests/regression/tools/config-directory/test_config.py.in diff --git a/.gitignore b/.gitignore index 204ea1125..13a1c9911 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/configure.ac b/configure.ac index ee4ee5aa7..59c00fe88 100644 --- a/configure.ac +++ b/configure.ac @@ -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]) diff --git a/tests/regression/Makefile.am b/tests/regression/Makefile.am index 3dda0a2b6..90f5c512f 100644 --- a/tests/regression/Makefile.am +++ b/tests/regression/Makefile.am @@ -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 index 000000000..016634713 --- /dev/null +++ b/tests/regression/tools/config-directory/test_config.py.in @@ -0,0 +1,278 @@ +#!/usr/bin/env python3 +# +# SPDX-FileCopyrightText: 2024 Kienan Stewart +# 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) diff --git a/tests/utils/lttngtest/environment.py b/tests/utils/lttngtest/environment.py index f97546367..03c1b5c81 100644 --- a/tests/utils/lttngtest/environment.py +++ b/tests/utils/lttngtest/environment.py @@ -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: diff --git a/tests/utils/lttngtest/lttng.py b/tests/utils/lttngtest/lttng.py index 3d5977657..3365cd0d8 100644 --- a/tests/utils/lttngtest/lttng.py +++ b/tests/utils/lttngtest/lttng.py @@ -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 -- 2.34.1