Test: client: start, stop, destroy: add tests for --glob/--all
authorJérémie Galarneau <jeremie.galarneau@efficios.com>
Fri, 21 Apr 2023 19:48:56 +0000 (15:48 -0400)
committerJérémie Galarneau <jeremie.galarneau@efficios.com>
Wed, 26 Apr 2023 17:55:24 +0000 (13:55 -0400)
Test the CLI client's start, stop, and destroy commands along with their
--all and --glob options.

The tests validate that only the targeted sessions are affected by the
various commands and that the commands don't error-out when multiple
sessions are targetted.

Signed-off-by: Jérémie Galarneau <jeremie.galarneau@efficios.com>
Change-Id: Ie39e999608a063cc4573d790120fbe0896917d6f

configure.ac
tests/regression/Makefile.am
tests/regression/tools/Makefile.am
tests/regression/tools/client/Makefile.am [new file with mode: 0644]
tests/regression/tools/client/test_session_commands.py [new file with mode: 0755]
tests/utils/lttngtest/lttng.py
tests/utils/lttngtest/lttngctl.py

index 4988aa9e62f88bfc7544ad04c75e0a0a2bdb0d89..87c4e6fcd0e388a0d5b2432b847cacac39e753c2 100644 (file)
@@ -1241,6 +1241,7 @@ AC_CONFIG_FILES([
        tests/regression/tools/trigger/name/Makefile
        tests/regression/tools/trigger/hidden/Makefile
        tests/regression/tools/context/Makefile
+       tests/regression/tools/client/Makefile
        tests/regression/ust/Makefile
        tests/regression/ust/nprocesses/Makefile
        tests/regression/ust/high-throughput/Makefile
index 3aa3b25fed797076fd928125b3c12a2bfc96871e..c1fc9134268f8aaee3a1e8b76f260b934af7924e 100644 (file)
@@ -64,7 +64,8 @@ TESTS = tools/base-path/test_ust \
        tools/trigger/test_remove_trigger_cli \
        tools/trigger/name/test_trigger_name_backwards_compat \
        tools/trigger/hidden/test_hidden_trigger \
-       tools/context/test_ust.py
+       tools/context/test_ust.py \
+       tools/client/test_session_commands.py
 
 # Only build kernel tests on Linux.
 if IS_LINUX
index e77c7b6b929b3d4daee8faeddedb4c8e90fcb314..ecd4eabd62d5d9c1faab9f5065977f69ca2a5eb4 100644 (file)
@@ -3,6 +3,7 @@
 SUBDIRS = base-path \
        channel \
        clear \
+       client \
        context \
        crash \
        exclusion \
diff --git a/tests/regression/tools/client/Makefile.am b/tests/regression/tools/client/Makefile.am
new file mode 100644 (file)
index 0000000..e96a24b
--- /dev/null
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+noinst_SCRIPTS = test_session_commands.py
+EXTRA_DIST = test_session_commands.py
+
+all-local:
+       @if [ x"$(srcdir)" != x"$(builddir)" ]; then \
+               for script in $(EXTRA_DIST); do \
+                       cp -f $(srcdir)/$$script $(builddir); \
+               done; \
+       fi
+
+clean-local:
+       @if [ x"$(srcdir)" != x"$(builddir)" ]; then \
+               for script in $(EXTRA_DIST); do \
+                       rm -f $(builddir)/$$script; \
+               done; \
+       fi
diff --git a/tests/regression/tools/client/test_session_commands.py b/tests/regression/tools/client/test_session_commands.py
new file mode 100755 (executable)
index 0000000..358482e
--- /dev/null
@@ -0,0 +1,485 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 Jérémie Galarneau <jeremie.galarneau@efficios.com>
+#
+# SPDX-License-Identifier: GPL-2.0-only
+
+from cgi import test
+import pathlib
+import sys
+import os
+from typing import Any, Callable, Type, Dict, Iterator
+import random
+import string
+from collections.abc import Mapping
+
+"""
+Test the session commands of the `lttng` CLI client.
+"""
+
+# 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
+
+
+class SessionSet(Mapping):
+    def __init__(self, client, name_prefixes):
+        self._sessions = {}  # type dict[str, lttngtest.Session]
+        for prefix in name_prefixes:
+            new_session = client.create_session(
+                name=self._generate_session_name_from_prefix(prefix),
+                output=lttngtest.LocalSessionOutputLocation(
+                    test_env.create_temporary_directory("trace")
+                ),
+            )
+            # Add a channel to all sessions to ensure the sessions can be started.
+            new_session.add_channel(lttngtest.TracingDomain.User)
+            self._sessions[prefix] = new_session
+
+    @staticmethod
+    def _generate_session_name_from_prefix(prefix):
+        # type: (str) -> str
+        return (
+            prefix
+            + "_"
+            + "".join(
+                random.choice(string.ascii_lowercase + string.digits) for _ in range(8)
+            )
+        )
+
+    def __getitem__(self, __key):
+        # type: (str) -> lttngtest.Session
+        return self._sessions[__key]
+
+    def __len__(self):
+        # type: () -> int
+        return len(self._sessions)
+
+    def __iter__(self):
+        # type: () -> Iterator[str]
+        return iter(self._sessions)
+
+
+def test_start_globbing(tap, test_env):
+    # type: (lttngtest.TapGenerator, lttngtest._Environment) -> None
+    tap.diagnostic("Test --glob match of start command")
+    name_prefixes = ["abba", "alakazou", "alakazam"]
+
+    client = lttngtest.LTTngClient(test_env, log=tap.diagnostic)
+
+    tap.diagnostic("Create a set of sessions to test globbing")
+    sessions = None
+    with tap.case(
+        "Create sessions with prefixes [{}]".format(", ".join(name_prefixes))
+    ) as test_case:
+        sessions = SessionSet(client, name_prefixes)
+
+    tap.test(
+        all(not session.is_active for prefix, session in sessions.items()),
+        "All sessions created are in the inactive state",
+    )
+
+    start_pattern = "alak*"
+    with tap.case("Start sessions with --glob={}".format(start_pattern)) as test_case:
+        client.start_session_by_glob_pattern(start_pattern)
+
+    tap.test(
+        sessions["alakazou"].is_active
+        and sessions["alakazam"].is_active
+        and not sessions["abba"].is_active,
+        "Only sessions 'alakazou' and 'alakazam' are active",
+    )
+
+    with tap.case(
+        "Starting already started sessions with --glob={} doesn't produce an error".format(
+            start_pattern
+        )
+    ) as test_case:
+        client.start_session_by_glob_pattern(start_pattern)
+
+    start_pattern = "tintina*"
+    with tap.case(
+        "Starting with --glob={} that doesn't match any session doesn't produce an error".format(
+            start_pattern
+        )
+    ) as test_case:
+        client.start_session_by_glob_pattern(start_pattern)
+
+    for name, session in sessions.items():
+        session.destroy()
+
+    with tap.case(
+        "Starting with --glob={} when no sessions exist doesn't produce an error".format(
+            start_pattern
+        )
+    ) as test_case:
+        client.start_session_by_glob_pattern(start_pattern)
+
+
+def test_start_single(tap, test_env):
+    # type: (lttngtest.TapGenerator, lttngtest._Environment) -> None
+    tap.diagnostic("Test match of start command targeting a single session")
+    name_prefixes = ["un", "deux", "patate", "pouel"]
+
+    client = lttngtest.LTTngClient(test_env, log=tap.diagnostic)
+
+    tap.diagnostic("Create a set of sessions to test single session start")
+    sessions = None
+    with tap.case(
+        "Create sessions with prefixes [{}]".format(", ".join(name_prefixes))
+    ) as test_case:
+        sessions = SessionSet(client, name_prefixes)
+
+    tap.test(
+        all(not session.is_active for prefix, session in sessions.items()),
+        "All sessions created are in the inactive state",
+    )
+
+    session_to_start_prefix = "patate"
+    full_session_name = sessions[session_to_start_prefix].name
+    with tap.case("Start session '{}'".format(session_to_start_prefix)) as test_case:
+        client.start_session_by_name(full_session_name)
+
+    tap.test(
+        any(
+            session.is_active and prefix != session_to_start_prefix
+            for prefix, session in sessions.items()
+        )
+        is False,
+        "Only session '{}' is active".format(session_to_start_prefix),
+    )
+
+    with tap.case(
+        "Starting already started session '{}' doesn't produce an error".format(
+            session_to_start_prefix
+        )
+    ) as test_case:
+        client.start_session_by_name(full_session_name)
+
+    for name, session in sessions.items():
+        session.destroy()
+
+
+def test_start_all(tap, test_env):
+    # type: (lttngtest.TapGenerator, lttngtest._Environment) -> None
+    tap.diagnostic("Test start command with the --all option")
+    name_prefixes = ["a", "b", "c", "d"]
+
+    client = lttngtest.LTTngClient(test_env, log=tap.diagnostic)
+
+    tap.diagnostic("Create a set of sessions to test starting all sessions")
+    sessions = None
+    with tap.case(
+        "Create sessions with prefixes [{}]".format(", ".join(name_prefixes))
+    ) as test_case:
+        sessions = SessionSet(client, name_prefixes)
+
+    tap.test(
+        all(not session.is_active for prefix, session in sessions.items()),
+        "All sessions created are in the inactive state",
+    )
+
+    with tap.case("Start all sessions") as test_case:
+        client.start_sessions_all()
+
+    tap.test(
+        all(session.is_active for prefix, session in sessions.items()),
+        "All sessions are active",
+    )
+
+    with tap.case("Starting already started sessions") as test_case:
+        client.start_sessions_all()
+
+    for name, session in sessions.items():
+        session.destroy()
+
+    with tap.case(
+        "Starting all sessions when none exist doesn't produce an error"
+    ) as test_case:
+        client.start_sessions_all()
+
+
+def test_stop_globbing(tap, test_env):
+    # type: (lttngtest.TapGenerator, lttngtest._Environment) -> None
+    tap.diagnostic("Test --glob match of stop command")
+    name_prefixes = ["East Farnham", "Amqui", "Amos"]
+
+    client = lttngtest.LTTngClient(test_env, log=tap.diagnostic)
+
+    tap.diagnostic("Create a set of sessions to test globbing")
+    sessions = None
+    with tap.case(
+        "Create sessions with prefixes [{}]".format(", ".join(name_prefixes))
+    ) as test_case:
+        sessions = SessionSet(client, name_prefixes)
+
+    client.start_sessions_all()
+    tap.test(
+        all(session.is_active for prefix, session in sessions.items()),
+        "All sessions are in the active state",
+    )
+
+    stop_pattern = "Am??i*"
+    with tap.case("Stop sessions with --glob={}".format(stop_pattern)) as test_case:
+        client.stop_session_by_glob_pattern(stop_pattern)
+
+    tap.test(
+        (
+            sessions["East Farnham"].is_active
+            and sessions["Amos"].is_active
+            and (not sessions["Amqui"].is_active)
+        ),
+        "Only session 'Amqui' is inactive",
+    )
+
+    stop_pattern = "Am*"
+    with tap.case(
+        "Stopping more sessions, including a stopped session, with --glob={} doesn't produce an error".format(
+            stop_pattern
+        )
+    ) as test_case:
+        client.stop_session_by_glob_pattern(stop_pattern)
+
+    tap.test(
+        sessions["East Farnham"].is_active
+        and (not sessions["Amqui"].is_active)
+        and (not sessions["Amos"].is_active),
+        "Only session 'East Farnham' is active",
+    )
+
+    stop_pattern = "Notre-Dame*"
+    with tap.case(
+        "Stopping with --glob={} that doesn't match any session doesn't produce an error".format(
+            stop_pattern
+        )
+    ) as test_case:
+        client.stop_session_by_glob_pattern(stop_pattern)
+
+    for name, session in sessions.items():
+        session.destroy()
+
+    with tap.case(
+        "Stopping with --glob={} when no sessions exist doesn't produce an error".format(
+            stop_pattern
+        )
+    ) as test_case:
+        client.stop_session_by_glob_pattern(stop_pattern)
+
+
+def test_stop_single(tap, test_env):
+    # type: (lttngtest.TapGenerator, lttngtest._Environment) -> None
+    tap.diagnostic("Test match of stop command targeting a single session")
+    name_prefixes = ["Grosses-Roches", "Kazabazua", "Laval", "Magog"]
+
+    client = lttngtest.LTTngClient(test_env, log=tap.diagnostic)
+
+    tap.diagnostic("Create a set of sessions to test single session stop")
+    sessions = None
+    with tap.case(
+        "Create sessions with prefixes [{}]".format(", ".join(name_prefixes))
+    ) as test_case:
+        sessions = SessionSet(client, name_prefixes)
+
+    client.start_sessions_all()
+    tap.test(
+        all(session.is_active for prefix, session in sessions.items()),
+        "All sessions are in the active state",
+    )
+
+    session_to_stop_prefix = "Kazabazua"
+    full_session_name = sessions[session_to_stop_prefix].name
+    with tap.case("Stop session '{}'".format(session_to_stop_prefix)) as test_case:
+        client.stop_session_by_name(full_session_name)
+
+    inactive_session_prefixes = [
+        prefix for prefix, session in sessions.items() if not session.is_active
+    ]
+    tap.test(
+        len(inactive_session_prefixes) == 1
+        and inactive_session_prefixes[0] == session_to_stop_prefix,
+        "Only session '{}' is inactive".format(session_to_stop_prefix),
+    )
+
+    with tap.case(
+        "Stopping already stopped session '{}' doesn't produce an error".format(
+            session_to_stop_prefix
+        )
+    ) as test_case:
+        client.stop_session_by_name(full_session_name)
+
+    for name, session in sessions.items():
+        session.destroy()
+
+
+def test_stop_all(tap, test_env):
+    # type: (lttngtest.TapGenerator, lttngtest._Environment) -> None
+    tap.diagnostic("Test stop command with the --all option")
+    name_prefixes = ["a", "b", "c", "d"]
+
+    client = lttngtest.LTTngClient(test_env, log=tap.diagnostic)
+
+    tap.diagnostic("Create a set of sessions to test stopping all sessions")
+    sessions = None
+    with tap.case(
+        "Create sessions with prefixes [{}]".format(", ".join(name_prefixes))
+    ) as test_case:
+        sessions = SessionSet(client, name_prefixes)
+
+    client.start_sessions_all()
+    tap.test(
+        all(session.is_active for prefix, session in sessions.items()),
+        "All sessions are in the active state",
+    )
+
+    with tap.case("Stop all sessions") as test_case:
+        client.stop_sessions_all()
+
+    tap.test(
+        all(not session.is_active for prefix, session in sessions.items()),
+        "All sessions are inactive",
+    )
+
+    with tap.case("Stopping already stopped sessions") as test_case:
+        client.stop_sessions_all()
+
+    for name, session in sessions.items():
+        session.destroy()
+
+    with tap.case(
+        "Stopping all sessions when none exist doesn't produce an error"
+    ) as test_case:
+        client.stop_sessions_all()
+
+
+def test_destroy_globbing(tap, test_env):
+    # type: (lttngtest.TapGenerator, lttngtest._Environment) -> None
+    tap.diagnostic("Test --glob match of destroy command")
+    name_prefixes = ["Mont-Laurier", "Montreal", "Montmagny", "Neuville"]
+
+    client = lttngtest.LTTngClient(test_env, log=tap.diagnostic)
+
+    tap.diagnostic("Create a set of sessions to test globbing")
+    sessions = None
+    with tap.case(
+        "Create sessions with prefixes [{}]".format(", ".join(name_prefixes))
+    ) as test_case:
+        sessions = SessionSet(client, name_prefixes)
+
+    destroy_pattern = "Mont*"
+    with tap.case(
+        "Destroy sessions with --glob={}".format(destroy_pattern)
+    ) as test_case:
+        client.destroy_session_by_glob_pattern(destroy_pattern)
+
+    listed_sessions = client.list_sessions()
+    tap.test(
+        len(listed_sessions) == 1
+        and listed_sessions[0].name == sessions["Neuville"].name,
+        "Neuville is the only remaining session",
+    )
+
+    for session in listed_sessions:
+        session.destroy()
+
+    with tap.case(
+        "Destroying with --glob={} when no sessions exist doesn't produce an error".format(
+            destroy_pattern
+        )
+    ) as test_case:
+        client.destroy_session_by_glob_pattern(destroy_pattern)
+
+
+def test_destroy_single(tap, test_env):
+    # type: (lttngtest.TapGenerator, lttngtest._Environment) -> None
+    tap.diagnostic("Test match of destroy command targeting a single session")
+    name_prefixes = ["Natashquan", "Normetal", "Notre-Dame-des-Sept-Douleurs"]
+
+    client = lttngtest.LTTngClient(test_env, log=tap.diagnostic)
+
+    tap.diagnostic("Create a set of sessions to test single session destruction")
+    sessions = None
+    with tap.case(
+        "Create sessions with prefixes [{}]".format(", ".join(name_prefixes))
+    ) as test_case:
+        sessions = SessionSet(client, name_prefixes)
+
+    session_to_destroy_prefix = "Normetal"
+    full_session_name = sessions[session_to_destroy_prefix].name
+    with tap.case(
+        "Destroy session '{}'".format(session_to_destroy_prefix)
+    ) as test_case:
+        client.destroy_session_by_name(full_session_name)
+
+    listed_sessions = client.list_sessions()
+    tap.test(
+        len(listed_sessions) == 2
+        and full_session_name not in [session.name for session in listed_sessions],
+        "Session '{}' no longer exists".format(session_to_destroy_prefix),
+    )
+
+    for session in listed_sessions:
+        session.destroy()
+
+
+def test_destroy_all(tap, test_env):
+    # type: (lttngtest.TapGenerator, lttngtest._Environment) -> None
+    tap.diagnostic("Test destroy command with the --all option")
+    name_prefixes = ["a", "b", "c", "d"]
+
+    client = lttngtest.LTTngClient(test_env, log=tap.diagnostic)
+
+    tap.diagnostic("Create a set of sessions to test destroying all sessions")
+    sessions = None
+    with tap.case(
+        "Create sessions with prefixes [{}]".format(", ".join(name_prefixes))
+    ) as test_case:
+        sessions = SessionSet(client, name_prefixes)
+
+    with tap.case("Destroy all sessions") as test_case:
+        client.destroy_sessions_all()
+
+    tap.test(
+        len(client.list_sessions()) == 0,
+        "No sessions exist after destroying all sessions",
+    )
+
+    with tap.case(
+        "Destroy all sessions when none exist doesn't produce an error"
+    ) as test_case:
+        client.destroy_sessions_all()
+
+
+tap = lttngtest.TapGenerator(48)
+tap.diagnostic("Test client session command --glob and --all options")
+
+with lttngtest.test_environment(with_sessiond=True, log=tap.diagnostic) as test_env:
+    test_start_globbing(tap, test_env)
+
+with lttngtest.test_environment(with_sessiond=True, log=tap.diagnostic) as test_env:
+    test_start_single(tap, test_env)
+
+with lttngtest.test_environment(with_sessiond=True, log=tap.diagnostic) as test_env:
+    test_start_all(tap, test_env)
+
+with lttngtest.test_environment(with_sessiond=True, log=tap.diagnostic) as test_env:
+    test_stop_globbing(tap, test_env)
+
+with lttngtest.test_environment(with_sessiond=True, log=tap.diagnostic) as test_env:
+    test_stop_single(tap, test_env)
+
+with lttngtest.test_environment(with_sessiond=True, log=tap.diagnostic) as test_env:
+    test_stop_all(tap, test_env)
+
+with lttngtest.test_environment(with_sessiond=True, log=tap.diagnostic) as test_env:
+    test_destroy_globbing(tap, test_env)
+
+with lttngtest.test_environment(with_sessiond=True, log=tap.diagnostic) as test_env:
+    test_destroy_single(tap, test_env)
+
+with lttngtest.test_environment(with_sessiond=True, log=tap.diagnostic) as test_env:
+    test_destroy_all(tap, test_env)
+
+sys.exit(0 if tap.is_successful else 1)
index 9ccea6b63f2b9325ef6ecd51362358f80a80ec60..ea92616c6d0a0d3001be234c374e6d5c740bf3f2 100644 (file)
@@ -4,7 +4,7 @@
 #
 # SPDX-License-Identifier: GPL-2.0-only
 
-from concurrent.futures import process
+
 from . import lttngctl, logger, environment
 import pathlib
 import os
@@ -12,6 +12,7 @@ from typing import Callable, Optional, Type, Union
 import shlex
 import subprocess
 import enum
+import xml.etree.ElementTree
 
 """
 Implementation of the lttngctl interface based on the `lttng` command line client.
@@ -24,6 +25,12 @@ class Unsupported(lttngctl.ControlException):
         super().__init__(msg)
 
 
+class InvalidMI(lttngctl.ControlException):
+    def __init__(self, msg):
+        # type: (str) -> None
+        super().__init__(msg)
+
+
 def _get_domain_option_name(domain):
     # type: (lttngctl.TracingDomain) -> str
     if domain == lttngctl.TracingDomain.User:
@@ -78,8 +85,9 @@ class _Channel(lttngctl.Channel):
         domain_option_name = _get_domain_option_name(self.domain)
         context_type_name = _get_context_type_name(context_type)
         self._client._run_cmd(
-            "add-context --{domain_option_name} --type {context_type_name}".format(
+            "add-context --{domain_option_name} --channel '{channel_name}' --type {context_type_name}".format(
                 domain_option_name=domain_option_name,
+                channel_name=self.name,
                 context_type_name=context_type_name,
             )
         )
@@ -208,7 +216,7 @@ class _ProcessAttributeTracker(lttngctl.ProcessAttributeTracker):
         )
         domain_name = _get_domain_option_name(self._domain)
         self._client._run_cmd(
-            "{cmd_name} --session {session_name} --{domain_name} --{tracked_attribute_name} {value}".format(
+            "{cmd_name} --session '{session_name}' --{domain_name} --{tracked_attribute_name} {value}".format(
                 cmd_name=cmd_name,
                 session_name=self._session.name,
                 domain_name=domain_name,
@@ -247,8 +255,10 @@ class _Session(lttngctl.Session):
         channel_name = lttngctl.Channel._generate_name()
         domain_option_name = _get_domain_option_name(domain)
         self._client._run_cmd(
-            "enable-channel --{domain_name} {channel_name}".format(
-                domain_name=domain_option_name, channel_name=channel_name
+            "enable-channel --session '{session_name}' --{domain_name} '{channel_name}'".format(
+                session_name=self.name,
+                domain_name=domain_option_name,
+                channel_name=channel_name,
             )
         )
         return _Channel(self._client, channel_name, domain, self)
@@ -264,15 +274,38 @@ class _Session(lttngctl.Session):
 
     def start(self):
         # type: () -> None
-        self._client._run_cmd("start {session_name}".format(session_name=self.name))
+        self._client._run_cmd("start '{session_name}'".format(session_name=self.name))
 
     def stop(self):
         # type: () -> None
-        self._client._run_cmd("stop {session_name}".format(session_name=self.name))
+        self._client._run_cmd("stop '{session_name}'".format(session_name=self.name))
 
     def destroy(self):
         # type: () -> None
-        self._client._run_cmd("destroy {session_name}".format(session_name=self.name))
+        self._client._run_cmd("destroy '{session_name}'".format(session_name=self.name))
+
+    @property
+    def is_active(self):
+        # type: () -> bool
+        list_session_xml = self._client._run_cmd(
+            "list '{session_name}'".format(session_name=self.name),
+            LTTngClient.CommandOutputFormat.MI_XML,
+        )
+
+        root = xml.etree.ElementTree.fromstring(list_session_xml)
+        command_output = LTTngClient._mi_find_in_element(root, "output")
+        sessions = LTTngClient._mi_find_in_element(command_output, "sessions")
+        session_mi = LTTngClient._mi_find_in_element(sessions, "session")
+
+        enabled_text = LTTngClient._mi_find_in_element(session_mi, "enabled").text
+        if enabled_text not in ["true", "false"]:
+            raise InvalidMI(
+                "Expected boolean value in element '{}': value='{}'".format(
+                    session_mi.tag, enabled_text
+                )
+            )
+
+        return enabled_text == "true"
 
     @property
     def kernel_pid_process_attribute_tracker(self):
@@ -335,6 +368,12 @@ class LTTngClient(logger._Logger, lttngctl.Controller):
     Implementation of a LTTngCtl Controller that uses the `lttng` client as a back-end.
     """
 
+    class CommandOutputFormat(enum.Enum):
+        MI_XML = 0
+        HUMAN = 1
+
+    _MI_NS = "{https://lttng.org/xml/ns/lttng-mi}"
+
     def __init__(
         self,
         test_environment,  # type: environment._Environment
@@ -343,13 +382,21 @@ class LTTngClient(logger._Logger, lttngctl.Controller):
         logger._Logger.__init__(self, log)
         self._environment = test_environment  # type: environment._Environment
 
-    def _run_cmd(self, command_args):
-        # type: (str) -> None
+    @staticmethod
+    def _namespaced_mi_element(property):
+        # type: (str) -> str
+        return LTTngClient._MI_NS + property
+
+    def _run_cmd(self, command_args, output_format=CommandOutputFormat.MI_XML):
+        # type: (str, CommandOutputFormat) -> str
         """
         Invoke the `lttng` client with a set of arguments. The command is
         executed in the context of the client's test environment.
         """
         args = [str(self._environment.lttng_client_path)]  # type: list[str]
+        if output_format == LTTngClient.CommandOutputFormat.MI_XML:
+            args.extend(["--mi", "xml"])
+
         args.extend(shlex.split(command_args))
 
         self._log("lttng {command_args}".format(command_args=command_args))
@@ -368,6 +415,8 @@ class LTTngClient(logger._Logger, lttngctl.Controller):
             for error_line in decoded_output.splitlines():
                 self._log(error_line)
             raise LTTngClientError(command_args, decoded_output)
+        else:
+            return out.decode("utf-8")
 
     def create_session(self, name=None, output=None):
         # type: (Optional[str], Optional[lttngctl.SessionOutputLocation]) -> lttngctl.Session
@@ -381,8 +430,92 @@ class LTTngClient(logger._Logger, lttngctl.Controller):
             raise TypeError("LTTngClient only supports local or no output")
 
         self._run_cmd(
-            "create {session_name} {output_option}".format(
+            "create '{session_name}' {output_option}".format(
                 session_name=name, output_option=output_option
             )
         )
         return _Session(self, name, output)
+
+    def start_session_by_name(self, name):
+        # type: (str) -> None
+        self._run_cmd("start '{session_name}'".format(session_name=name))
+
+    def start_session_by_glob_pattern(self, pattern):
+        # type: (str) -> None
+        self._run_cmd("start --glob '{pattern}'".format(pattern=pattern))
+
+    def start_sessions_all(self):
+        # type: () -> None
+        self._run_cmd("start --all")
+
+    def stop_session_by_name(self, name):
+        # type: (str) -> None
+        self._run_cmd("stop '{session_name}'".format(session_name=name))
+
+    def stop_session_by_glob_pattern(self, pattern):
+        # type: (str) -> None
+        self._run_cmd("stop --glob '{pattern}'".format(pattern=pattern))
+
+    def stop_sessions_all(self):
+        # type: () -> None
+        self._run_cmd("stop --all")
+
+    def destroy_session_by_name(self, name):
+        # type: (str) -> None
+        self._run_cmd("destroy '{session_name}'".format(session_name=name))
+
+    def destroy_session_by_glob_pattern(self, pattern):
+        # type: (str) -> None
+        self._run_cmd("destroy --glob '{pattern}'".format(pattern=pattern))
+
+    def destroy_sessions_all(self):
+        # type: () -> None
+        self._run_cmd("destroy --all")
+
+    @staticmethod
+    def _mi_find_in_element(element, sub_element_name):
+        # type: (xml.etree.ElementTree.Element, str) -> xml.etree.ElementTree.Element
+        result = element.find(LTTngClient._namespaced_mi_element(sub_element_name))
+        if result is None:
+            raise InvalidMI(
+                "Failed to find element '{}' within command MI element '{}'".format(
+                    element.tag, sub_element_name
+                )
+            )
+
+        return result
+
+    def list_sessions(self):
+        # type () -> List[Session]
+        list_sessions_xml = self._run_cmd(
+            "list", LTTngClient.CommandOutputFormat.MI_XML
+        )
+
+        root = xml.etree.ElementTree.fromstring(list_sessions_xml)
+        command_output = self._mi_find_in_element(root, "output")
+        sessions = self._mi_find_in_element(command_output, "sessions")
+
+        ctl_sessions = []  # type: list[lttngctl.Session]
+
+        for session_mi in sessions:
+            name = self._mi_find_in_element(session_mi, "name").text
+            path = self._mi_find_in_element(session_mi, "path").text
+
+            if name is None:
+                raise InvalidMI(
+                    "Invalid empty 'name' element in '{}'".format(session_mi.tag)
+                )
+            if path is None:
+                raise InvalidMI(
+                    "Invalid empty 'path' element in '{}'".format(session_mi.tag)
+                )
+            if not path.startswith("/"):
+                raise Unsupported(
+                    "{} does not support non-local session outputs".format(type(self))
+                )
+
+            ctl_sessions.append(
+                _Session(self, name, lttngctl.LocalSessionOutputLocation(path))
+            )
+
+        return ctl_sessions
index b034308f3685b3de30b135c8168addd414594aa7..9b9658ec626086ef280d8acc9ff19c26894f60d0 100644 (file)
@@ -371,6 +371,11 @@ class Session(abc.ABC):
         # type: () -> None
         pass
 
+    @abc.abstractmethod
+    def is_active(self):
+        # type: () -> bool
+        pass
+
     @abc.abstractproperty
     def kernel_pid_process_attribute_tracker(self):
         # type: () -> Type[ProcessIDProcessAttributeTracker]
@@ -442,3 +447,83 @@ class Controller(abc.ABC):
         to create a session without an output.
         """
         pass
+
+    @abc.abstractmethod
+    def start_session_by_name(self, name):
+        # type: (str) -> None
+        """
+        Start a session by name.
+        """
+        pass
+
+    @abc.abstractmethod
+    def start_session_by_glob_pattern(self, pattern):
+        # type: (str) -> None
+        """
+        Start sessions whose name matches `pattern`, see GLOB(7).
+        """
+        pass
+
+    @abc.abstractmethod
+    def start_sessions_all(self):
+        """
+        Start all sessions visible to the current user.
+        """
+        # type: () -> None
+        pass
+
+    @abc.abstractmethod
+    def stop_session_by_name(self, name):
+        # type: (str) -> None
+        """
+        Stop a session by name.
+        """
+        pass
+
+    @abc.abstractmethod
+    def stop_session_by_glob_pattern(self, pattern):
+        # type: (str) -> None
+        """
+        Stop sessions whose name matches `pattern`, see GLOB(7).
+        """
+        pass
+
+    @abc.abstractmethod
+    def stop_sessions_all(self):
+        """
+        Stop all sessions visible to the current user.
+        """
+        # type: () -> None
+        pass
+
+    @abc.abstractmethod
+    def destroy_session_by_name(self, name):
+        # type: (str) -> None
+        """
+        Destroy a session by name.
+        """
+        pass
+
+    @abc.abstractmethod
+    def destroy_session_by_glob_pattern(self, pattern):
+        # type: (str) -> None
+        """
+        Destroy sessions whose name matches `pattern`, see GLOB(7).
+        """
+        pass
+
+    @abc.abstractmethod
+    def destroy_sessions_all(self):
+        # type: () -> None
+        """
+        Destroy all sessions visible to the current user.
+        """
+        pass
+
+    @abc.abstractmethod
+    def list_sessions(self):
+        # type: () -> List[Session]
+        """
+        List all sessions visible to the current user.
+        """
+        pass
This page took 0.0346070000000001 seconds and 4 git commands to generate.