From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from gandalf.ozlabs.org (gandalf.ozlabs.org [150.107.74.76]) by passt.top (Postfix) with ESMTPS id E93F95A0276 for ; Wed, 31 May 2023 03:59:01 +0200 (CEST) Received: by gandalf.ozlabs.org (Postfix, from userid 1007) id 4QWC9S39GBz4x4W; Wed, 31 May 2023 11:58:52 +1000 (AEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gibson.dropbear.id.au; s=201602; t=1685498332; bh=Wi+fiu3yWhlbW9JwtNqCl2mbUeKKtCyD4+Fx5xH4zOM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=WOYGJdF7RxEND43zGeVE1/biAQITN989C493HoudNx9f6xPnjDY/YcmeDIlN/dg7b Bi+NGQn2cDi/WNzX7QYwfra8xb6K+FcF1QBlLRp6EQID6e4QPj/6llTSoH+RT+9nnT rUr7CNO2CRB1J8w9nwfk0mtkakjEJsVtaq3sqia0= From: David Gibson To: passt-dev@passt.top, Stefano Brivio Subject: [PATCH v3 09/20] avocado/tasst: Add helpers to run commands with nstool Date: Wed, 31 May 2023 11:58:38 +1000 Message-Id: <20230531015849.3229596-10-david@gibson.dropbear.id.au> X-Mailer: git-send-email 2.40.1 In-Reply-To: <20230531015849.3229596-1-david@gibson.dropbear.id.au> References: <20230531015849.3229596-1-david@gibson.dropbear.id.au> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Message-ID-Hash: TKGTS6PS2JIPBOLR6ARE7SZNSKOWRW3X X-Message-ID-Hash: TKGTS6PS2JIPBOLR6ARE7SZNSKOWRW3X X-MailFrom: dgibson@gandalf.ozlabs.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Cleber Rosa , jarichte@redhat.com, David Gibson X-Mailman-Version: 3.3.8 Precedence: list List-Id: Development discussion and patches for passt Archived-At: Archived-At: List-Archive: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Use our existing nstool C helper, add python wrappers to easily run commands in various namespaces. Signed-off-by: David Gibson --- Makefile | 7 +- avocado/tasst/nstool.py | 182 ++++++++++++++++++++++++++++++++++++++++ avocado/tasst/site.py | 16 +++- 3 files changed, 202 insertions(+), 3 deletions(-) create mode 100644 avocado/tasst/nstool.py diff --git a/Makefile b/Makefile index fc83cd2..9add0a4 100644 --- a/Makefile +++ b/Makefile @@ -298,10 +298,13 @@ cppcheck: $(SRCS) $(HEADERS) AVOCADO = avocado -avocado-%: +avocado-assets: + $(MAKE) -C test nstool + +avocado-%: avocado-assets PYTHONPATH=./avocado $(AVOCADO) run avocado --filter-by-tags=$* -avocado-all: +avocado-all: avocado-assets PYTHONPATH=./avocado $(AVOCADO) run avocado # Default avocado tests to run, everything except the "meta" tests diff --git a/avocado/tasst/nstool.py b/avocado/tasst/nstool.py new file mode 100644 index 0000000..020e8a0 --- /dev/null +++ b/avocado/tasst/nstool.py @@ -0,0 +1,182 @@ +#! /usr/bin/python3 + +# SPDX-License-Identifier: GPL-2.0-or-later +# +# tasst - Test A Simple Socket Transport +# library of test helpers for passt & pasta +# +# tasst/nstool.py - Run commands in namespaces via 'nstool' +# +# Copyright Red Hat +# Author: David Gibson + +import contextlib +import os +import sys +import tempfile + +import avocado +from avocado.utils.process import CmdError + +from tasst import Tasst +from tasst.site import IsolatedSiteTasst, SiteTasst, Site, REAL_HOST +from tasst.typing import typecheck + +# FIXME: Can this be made more portable? +UNIX_PATH_MAX = 108 + + +class NsToolSite(Site): + NST_BIN = './test/nstool' + + def __init__(self, name, sockpath): + if len(sockpath) > UNIX_PATH_MAX: + raise ValueError('Unix domain socket path \"{}\" is too long'.format(sockpath)) + + super().__init__(name) + self.sockpath = sockpath + + def __enter__(self): + self._pid = int(REAL_HOST.output('{} info -wp {}'.format(self.NST_BIN, self.sockpath), timeout=1)) + return self + + def __exit__(self, *exc_details): + pass + + # PID of the nstool hold process as seen by the test host + def pid(self): + return self._pid + + # PID of the nstool hold process as seen by another site + # (important when using PID namespaces) + def relative_pid(self, relative_to): + cmd = '{} info -p {}'.format(self.NST_BIN, self.sockpath) + return int(relative_to.output(cmd)) + + def _nst_cmd(self, cmd, sudo=False): + nst_args = self.sockpath + if sudo: + nst_args = '--keep-caps ' + nst_args + return '{} exec {} -- {}'.format(self.NST_BIN, nst_args, cmd) + + def output(self, cmd, sudo=False, **kwargs): + return REAL_HOST.output(self._nst_cmd(cmd, sudo), **kwargs) + + def fg(self, cmd, sudo=False, **kwargs): + return REAL_HOST.fg(self._nst_cmd(cmd, sudo), **kwargs) + + def bg(self, cmd, sudo=False, **kwargs): + return REAL_HOST.bg(self._nst_cmd(cmd, sudo), **kwargs) + + +# Create path for temporary nstool Unix socket +# +# The obvious choice would be to use Avocado's workdir, but that often +# gives paths that are too long for Unix sockets +def temp_sockpath(name): + tmpd = tempfile.mkdtemp(suffix=name) + return os.path.join(tmpd, 's') + + +class UnshareSite(NsToolSite): + def __init__(self, name, unshare_opts, parent=REAL_HOST, sudo=False): + sockpath = temp_sockpath(name) + parent.require_cmds('unshare', self.NST_BIN) + + super().__init__(name, sockpath) + + self.parent = typecheck(parent, Site) + self.holdcmd = 'unshare {} -- {} hold {}'.format(unshare_opts, + self.NST_BIN, sockpath) + self.sudo = typecheck(sudo, bool) + + def __enter__(self): + self.holder = self.parent.bg(self.holdcmd, sudo=self.sudo) + self.holder.__enter__() + return super().__enter__() + + def __exit__(self, *exc_details): + super().__exit__(*exc_details) + + try: + self.parent.fg('{} stop {}'.format(self.NST_BIN, self.sockpath)) + finally: + self.holder.__exit__(*exc_details) + + try: + os.remove(self.sockpath) + except FileNotFoundError: + pass + + os.rmdir(os.path.dirname(self.sockpath)) + + +class UserNetNsTasst(IsolatedSiteTasst): + """ + Test creating a userns+netns together + + :avocado: tags=meta + """ + + def setup_site(self): + return UnshareSite(type(self).__name__, '-Ucn') + + def test_userns(self): + REAL_HOST.require_cmds('capsh') + with self.setup_site() as ns: + ns.require_cmds('capsh') + capcmd = 'capsh --has-p=CAP_SETUID' + self.assertRaises(CmdError, REAL_HOST.fg, capcmd) + ns.fg(capcmd, sudo=True) + + +class NestedNsTasst(IsolatedSiteTasst): + """ + Test creating userns with a netns nested within + + :avocado: tags=meta + """ + + @contextlib.contextmanager + def setup_site(self): + name = type(self).__name__ + with UnshareSite(name + '.userns', '-Uc') as userns: + with UnshareSite(name + '.netns', '-n', parent=userns, sudo=True) as netns: + yield netns + + +class PidNsTasst(IsolatedSiteTasst): + """ + Test unsing unshare -p to create a pidns + + :avocado: tags=meta + """ + + def setup_site(self): + return UnshareSite(type(self).__name__, '-Upfn') + + def test_relative_pid(self): + with self.setup_site() as site: + # The holder is init (pid 1) within its own pidns + self.assertEquals(site.relative_pid(site), 1) + + +class ConnectNsToolTasst(SiteTasst): + """ + Test connecting to a pre-existing nstool + + :avocado: tags=meta + """ + + @contextlib.contextmanager + def setup_site(self): + sockpath = temp_sockpath(type(self).__name__) + holdcmd = '{} hold {}'.format(NsToolSite.NST_BIN, sockpath) + with REAL_HOST.bg(holdcmd) as holder: + with NsToolSite("fake ns", sockpath) as site: + yield site + + try: + os.remove(sockpath) + finally: + os.rmdir(os.path.dirname(sockpath)) diff --git a/avocado/tasst/site.py b/avocado/tasst/site.py index 6450944..900e945 100644 --- a/avocado/tasst/site.py +++ b/avocado/tasst/site.py @@ -101,6 +101,19 @@ class SiteTasst(Tasst): self.assertIn('lo', site.ifs()) +class IsolatedSiteTasst(SiteTasst): + """ + Test a site with isolated network (loopback only) + + :avocado: disable + :avocado: tags=meta + """ + + def test_isolated_net(self): + with self.setup_site() as site: + self.assertEquals(site.ifs(), ['lo']) + + # Represents the host on which the tests are running, as opposed to # some simulated host created by the tests class RealHost(Site): @@ -129,7 +142,8 @@ class RealHost(Site): proc.start() yield proc finally: - proc.stop() + if proc.poll() is None: + proc.stop() REAL_HOST = RealHost() -- 2.40.1