From: David Gibson <david@gibson.dropbear.id.au>
To: Stefano Brivio <sbrivio@redhat.com>, passt-dev@passt.top
Cc: Cleber Rosa <crosa@redhat.com>,
David Gibson <david@gibson.dropbear.id.au>
Subject: [PATCH v2 11/22] tasst: Add helpers to run commands with nstool
Date: Mon, 5 Aug 2024 22:36:50 +1000 [thread overview]
Message-ID: <20240805123701.1720730-12-david@gibson.dropbear.id.au> (raw)
In-Reply-To: <20240805123701.1720730-1-david@gibson.dropbear.id.au>
Use our existing nstool C helper, add python wrappers to easily run
commands in various namespaces.
Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
test/Makefile | 8 +-
test/tasst/__main__.py | 2 +-
test/tasst/nstool.py | 170 +++++++++++++++++++++++++++++++++++++++++
test/tasst/snh.py | 16 ++++
4 files changed, 192 insertions(+), 4 deletions(-)
create mode 100644 test/tasst/nstool.py
diff --git a/test/Makefile b/test/Makefile
index 8373ae77..83725f59 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -64,13 +64,15 @@ LOCAL_ASSETS = mbuto.img mbuto.mem.img podman/bin/podman QEMU_EFI.fd \
$(TESTDATA_ASSETS)
ASSETS = $(DOWNLOAD_ASSETS) $(LOCAL_ASSETS)
+AVOCADO_ASSETS =
+META_ASSETS = nstool
EXETER_SH = build/static_checkers.sh
EXETER_PY = build/build.py
EXETER_JOBS = $(EXETER_SH:%.sh=%.json) $(EXETER_PY:%.py=%.json)
AVOCADO_JOBS = $(EXETER_JOBS) avocado/static_checkers.json
-TASST_SRCS = __init__.py __main__.py snh.py
+TASST_SRCS = __init__.py __main__.py nstool.py snh.py
EXETER_META = meta/lint.json meta/tasst.json
META_JOBS = $(EXETER_META)
@@ -157,11 +159,11 @@ meta/tasst.json: $(TASST_SRCS:%=tasst/%) $(VENV) pull-exeter
cd ..; PYTHONPATH=$(PYPATH_BASE) $(PYTHON) -m tasst --avocado > test/$@
.PHONY: avocado
-avocado: venv $(AVOCADO_JOBS)
+avocado: venv $(AVOCADO_ASSETS) $(AVOCADO_JOBS)
$(RUN_AVOCADO) all $(AVOCADO_JOBS)
.PHONY: meta
-meta: venv $(META_JOBS)
+meta: venv $(META_ASSETS) $(META_JOBS)
$(RUN_AVOCADO) meta $(META_JOBS)
flake8:
diff --git a/test/tasst/__main__.py b/test/tasst/__main__.py
index 91499128..9fd6174e 100644
--- a/test/tasst/__main__.py
+++ b/test/tasst/__main__.py
@@ -13,7 +13,7 @@ library of test helpers for passt & pasta
import exeter
# We import just to get the exeter tests, which flake8 can't see
-from . import snh # noqa: F401
+from . import nstool, snh # noqa: F401
if __name__ == '__main__':
diff --git a/test/tasst/nstool.py b/test/tasst/nstool.py
new file mode 100644
index 00000000..0b23fbfb
--- /dev/null
+++ b/test/tasst/nstool.py
@@ -0,0 +1,170 @@
+#! /usr/bin/env python3
+
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright Red Hat
+# Author: David Gibson <david@gibson.dropbear.id.au>
+
+"""
+Test A Simple Socket Transport
+
+nstool.py - Run commands in namespaces via 'nstool'
+"""
+
+import contextlib
+import os
+import subprocess
+import tempfile
+
+import exeter
+
+from .snh import RealHost, SimNetHost
+
+# FIXME: Can this be made more portable?
+UNIX_PATH_MAX = 108
+
+NSTOOL_BIN = 'test/nstool'
+
+
+class NsTool(SimNetHost):
+ """A bundle of Linux namespaces managed by nstool"""
+
+ def __init__(self, name, sockpath, parent=RealHost()):
+ if len(sockpath) > UNIX_PATH_MAX:
+ raise ValueError(
+ f'Unix domain socket path "{sockpath}" is too long'
+ )
+
+ super().__init__(name)
+ self.sockpath = sockpath
+ self.parent = parent
+ self._pid = None
+
+ def __enter__(self):
+ cmd = [f'{NSTOOL_BIN}', 'info', '-wp', f'{self.sockpath}']
+ pid = self.parent.output(*cmd, timeout=1)
+ self._pid = int(pid)
+ return self
+
+ def __exit__(self, *exc_details):
+ pass
+
+ # PID of the nstool hold process as seen by the parent snh
+ def pid(self):
+ return self._pid
+
+ # PID of the nstool hold process as seen by another snh which can
+ # see the nstool socket (important when using PID namespaces)
+ def relative_pid(self, relative_to):
+ cmd = [f'{NSTOOL_BIN}', 'info', '-p', f'{self.sockpath}']
+ relpid = relative_to.output(*cmd)
+ return int(relpid)
+
+ def hostify(self, *cmd, capable=False, **kwargs):
+ hostcmd = [f'{NSTOOL_BIN}', 'exec']
+ if capable:
+ hostcmd.append('--keep-caps')
+ hostcmd += [self.sockpath, '--']
+ hostcmd += list(cmd)
+ return hostcmd, kwargs
+
+
+@contextlib.contextmanager
+def unshare_snh(name, *opts, parent=RealHost(), capable=False):
+ # Create path for temporary nstool Unix socket
+ with tempfile.TemporaryDirectory() as tmpd:
+ sockpath = os.path.join(tmpd, name)
+ cmd = ['unshare'] + list(opts)
+ cmd += ['--', f'{NSTOOL_BIN}', 'hold', f'{sockpath}']
+ with parent.bg(*cmd, capable=capable) as holder:
+ try:
+ with NsTool(name, sockpath, parent=parent) as snh:
+ yield snh
+ finally:
+ try:
+ parent.fg(f'{NSTOOL_BIN}', 'stop', f'{sockpath}')
+ finally:
+ try:
+ holder.run(timeout=0.1)
+ holder.kill()
+ finally:
+ try:
+ os.remove(sockpath)
+ except FileNotFoundError:
+ pass
+
+
+TEST_EXC = ValueError
+
+
+def test_sockdir_cleanup(s):
+ def mess(sockpaths):
+ with s as snh:
+ ns = snh
+ while isinstance(ns, NsTool):
+ sockpaths.append(ns.sockpath)
+ ns = ns.parent
+ raise TEST_EXC
+
+ sockpaths = []
+ exeter.assert_raises(TEST_EXC, mess, sockpaths)
+ assert sockpaths
+ for path in sockpaths:
+ assert not os.path.exists(os.path.dirname(path))
+
+
+def userns_snh():
+ return unshare_snh('usernetns', '-Ucn')
+
+
+@exeter.test
+def test_userns():
+ cmd = ['capsh', '--has-p=CAP_SETUID']
+ with RealHost() as realhost:
+ status = realhost.fg(*cmd, check=False)
+ assert status.returncode != 0
+ with userns_snh() as ns:
+ ns.fg(*cmd, capable=True)
+
+
+@contextlib.contextmanager
+def nested_snh():
+ with unshare_snh('userns', '-Uc') as userns:
+ with unshare_snh('netns', '-n', parent=userns, capable=True) as netns:
+ yield netns
+
+
+def pidns_snh():
+ return unshare_snh('pidns', '-Upfn')
+
+
+@exeter.test
+def test_relative_pid():
+ with pidns_snh() as snh:
+ # The holder is init (pid 1) within its own pidns
+ exeter.assert_eq(snh.relative_pid(snh), 1)
+
+
+# General tests for all the nstool examples
+for setup in [userns_snh, nested_snh, pidns_snh]:
+ # Common snh tests
+ SimNetHost.selftest_isolated(setup)
+ exeter.register_pipe(f'{setup.__qualname__}|test_sockdir_cleanup',
+ setup, test_sockdir_cleanup)
+
+
+@contextlib.contextmanager
+def connect_snh():
+ with tempfile.TemporaryDirectory() as tmpd:
+ sockpath = os.path.join(tmpd, 'nons')
+ holdcmd = [f'{NSTOOL_BIN}', 'hold', f'{sockpath}']
+ with subprocess.Popen(holdcmd) as holder:
+ try:
+ with NsTool("fakens", sockpath) as snh:
+ yield snh
+ finally:
+ holder.kill()
+ os.remove(sockpath)
+
+
+SimNetHost.selftest(connect_snh)
diff --git a/test/tasst/snh.py b/test/tasst/snh.py
index 8ee9802a..598ea979 100644
--- a/test/tasst/snh.py
+++ b/test/tasst/snh.py
@@ -178,6 +178,22 @@ class SimNetHost(contextlib.AbstractContextManager):
testid = f'{setup.__qualname__}|{t.__qualname__}'
exeter.register_pipe(testid, setup, t)
+ # Additional tests only valid if the snh is isolated (no outside
+ # network connections)
+ def test_is_isolated(self):
+ with self as snh:
+ exeter.assert_eq(snh.ifs(), ['lo'])
+
+ ISOLATED_SELFTESTS = [test_is_isolated]
+
+ @classmethod
+ def selftest_isolated(cls, setup):
+ "Register self tests for an isolated snh example"
+ cls.selftest(setup)
+ for t in cls.ISOLATED_SELFTESTS:
+ testid = f'{setup.__qualname__}|{t.__qualname__}'
+ exeter.register_pipe(testid, setup, t)
+
class RealHost(SimNetHost):
"""Represents the host on which the tests are running (as opposed
--
@@ -178,6 +178,22 @@ class SimNetHost(contextlib.AbstractContextManager):
testid = f'{setup.__qualname__}|{t.__qualname__}'
exeter.register_pipe(testid, setup, t)
+ # Additional tests only valid if the snh is isolated (no outside
+ # network connections)
+ def test_is_isolated(self):
+ with self as snh:
+ exeter.assert_eq(snh.ifs(), ['lo'])
+
+ ISOLATED_SELFTESTS = [test_is_isolated]
+
+ @classmethod
+ def selftest_isolated(cls, setup):
+ "Register self tests for an isolated snh example"
+ cls.selftest(setup)
+ for t in cls.ISOLATED_SELFTESTS:
+ testid = f'{setup.__qualname__}|{t.__qualname__}'
+ exeter.register_pipe(testid, setup, t)
+
class RealHost(SimNetHost):
"""Represents the host on which the tests are running (as opposed
--
2.45.2
next prev parent reply other threads:[~2024-08-05 12:37 UTC|newest]
Thread overview: 31+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-08-05 12:36 [PATCH v2 00/22] RFC: Proof-of-concept based exeter+Avocado tests David Gibson
2024-08-05 12:36 ` [PATCH v2 01/22] nstool: Fix some trivial typos David Gibson
2024-08-05 12:36 ` [PATCH v2 02/22] nstool: Propagate SIGTERM to processes executed in the namespace David Gibson
2024-08-07 7:23 ` Stefano Brivio
2024-08-05 12:36 ` [PATCH v2 03/22] test: run static checkers with Avocado and JSON definitions David Gibson
2024-08-05 12:36 ` [PATCH v2 04/22] test: Extend make targets to run Avocado tests David Gibson
2024-08-05 12:36 ` [PATCH v2 05/22] test: Exeter based static tests David Gibson
2024-08-05 12:36 ` [PATCH v2 06/22] test: Add exeter+Avocado based build tests David Gibson
2024-08-06 22:11 ` Stefano Brivio
2024-08-07 10:51 ` David Gibson
2024-08-07 13:06 ` Stefano Brivio
2024-08-08 1:28 ` David Gibson
2024-08-08 22:55 ` Stefano Brivio
2024-08-05 12:36 ` [PATCH v2 07/22] test: Add linters for Python code David Gibson
2024-08-05 12:36 ` [PATCH v2 08/22] tasst: Introduce library of common test helpers David Gibson
2024-08-05 12:36 ` [PATCH v2 09/22] tasst: "snh" module for simulated network hosts David Gibson
2024-08-05 12:36 ` [PATCH v2 10/22] tasst: Add helper to get network interface names for a site David Gibson
2024-08-05 12:36 ` David Gibson [this message]
2024-08-05 12:36 ` [PATCH v2 12/22] tasst: Add ifup and network address helpers to SimNetHost David Gibson
2024-08-05 12:36 ` [PATCH v2 13/22] tasst: Helper for creating veth devices between namespaces David Gibson
2024-08-05 12:36 ` [PATCH v2 14/22] tasst: Add helper for getting MTU of a network interface David Gibson
2024-08-05 12:36 ` [PATCH v2 15/22] tasst: Add helper to wait for IP address to appear David Gibson
2024-08-05 12:36 ` [PATCH v2 16/22] tasst: Add helpers for getting a SimNetHost's routes David Gibson
2024-08-05 12:36 ` [PATCH v2 17/22] tasst: Helpers to test transferring data between sites David Gibson
2024-08-05 12:36 ` [PATCH v2 18/22] tasst: IP address allocation helpers David Gibson
2024-08-05 12:36 ` [PATCH v2 19/22] tasst: Helpers for testing NDP behaviour David Gibson
2024-08-05 12:36 ` [PATCH v2 20/22] tasst: Helpers for testing DHCP & DHCPv6 behaviour David Gibson
2024-08-05 12:37 ` [PATCH v2 21/22] tasst: Helpers to construct a simple network environment for tests David Gibson
2024-08-05 12:37 ` [PATCH v2 22/22] avocado: Convert basic pasta tests David Gibson
2024-08-06 12:28 ` [PATCH v2 00/22] RFC: Proof-of-concept based exeter+Avocado tests David Gibson
2024-08-07 8:17 ` Stefano Brivio
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20240805123701.1720730-12-david@gibson.dropbear.id.au \
--to=david@gibson.dropbear.id.au \
--cc=crosa@redhat.com \
--cc=passt-dev@passt.top \
--cc=sbrivio@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://passt.top/passt
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for IMAP folder(s).