From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: passt.top; dmarc=none (p=none dis=none) header.from=gibson.dropbear.id.au Authentication-Results: passt.top; dkim=pass (2048-bit key; secure) header.d=gibson.dropbear.id.au header.i=@gibson.dropbear.id.au header.a=rsa-sha256 header.s=202510 header.b=XafLXWEX; dkim-atps=neutral Received: from mail.ozlabs.org (mail.ozlabs.org [IPv6:2404:9400:2221:ea00::3]) by passt.top (Postfix) with ESMTPS id 4940F5A026F for ; Wed, 08 Oct 2025 04:32:38 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gibson.dropbear.id.au; s=202510; t=1759890754; bh=2e93eM9e450GBuLdaTAUj3U4Eq+8WKNkazh0N3l6Pq4=; h=Date:From:To:Cc:Subject:References:In-Reply-To:From; b=XafLXWEXhL0MqKkX6pA96AhA8XeoJH6Kd2of7ot+W+AOMAMNR5bSFSQiaZv38+sig R71GRP8LSWYBRkUapoUgwOdSm3yXXn/BKg7DktZQdtu/cYuWJYZzAbeQnlJimIz9zg N3BIj9HiIP8uN5vuI7E7XFw0sRwaa25pkOgR672Ibl4sNHYk9AOl6rWWfQoJ5O8bgN FJBKzpWS5NlZ5uRlc9WfVraexQ1OdlIanP5RhjHGyCJus18UV47E5guPM594LPPVgL 9zUDk+cP/GHgpoCOc6k3Fp02HZ8MyBa1OaqUkpd6McjBJDmkRbztj9bS0iCV+XModM xU9Np1fumym2w== Received: by gandalf.ozlabs.org (Postfix, from userid 1007) id 4chH8y2gzLz4wCk; Wed, 8 Oct 2025 13:32:34 +1100 (AEDT) Date: Wed, 8 Oct 2025 13:32:27 +1100 From: David Gibson To: Stefano Brivio Subject: Re: [PATCH 3/3] test: Re-implement pasta NDP tests using tunbridge & exeter Message-ID: References: <20251002075708.461931-1-david@gibson.dropbear.id.au> <20251002075708.461931-4-david@gibson.dropbear.id.au> <20251007220110.3c8bf21c@elisabeth> MIME-Version: 1.0 Content-Type: multipart/signed; micalg=pgp-sha512; protocol="application/pgp-signature"; boundary="unkAFnyHpYO3Y8yJ" Content-Disposition: inline In-Reply-To: <20251007220110.3c8bf21c@elisabeth> Message-ID-Hash: JD2BP5DKKKDWONQCYY44RAASPELKK3MH X-Message-ID-Hash: JD2BP5DKKKDWONQCYY44RAASPELKK3MH 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: passt-dev@passt.top 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: --unkAFnyHpYO3Y8yJ Content-Type: text/plain; charset=us-ascii Content-Disposition: inline Content-Transfer-Encoding: quoted-printable On Tue, Oct 07, 2025 at 10:01:10PM +0200, Stefano Brivio wrote: > On Thu, 2 Oct 2025 17:57:08 +1000 > David Gibson wrote: >=20 > > Convert the pasta NDP tests from shell and our own DSL to Python using > > the exeter test protocol and tunbridge network simulation library. > >=20 > > Signed-off-by: David Gibson > > --- > > test/Makefile | 2 +- > > test/pasta/dhcp | 5 ++++ > > test/pasta/ndp.py | 59 ++++++++++++++++++++++++++++++++++++++++++ > > test/run | 6 +++-- > > test/tasst/__init__.py | 4 +++ > > test/tasst/pasta.py | 40 ++++++++++++++++++++++++++++ > > 6 files changed, 113 insertions(+), 3 deletions(-) > > create mode 100755 test/pasta/ndp.py > > create mode 100644 test/tasst/pasta.py > >=20 > > diff --git a/test/Makefile b/test/Makefile > > index f66c7e7e..95e3d75e 100644 > > --- a/test/Makefile > > +++ b/test/Makefile > > @@ -67,7 +67,7 @@ ASSETS =3D $(DOWNLOAD_ASSETS) $(LOCAL_ASSETS) > > =20 > > EXETER_SH =3D smoke/smoke.sh build/static_checkers.sh > > EXETER_PYPATH =3D exeter/py3:tunbridge/:. > > -EXETER_PYTHON =3D smoke/smoke.py build/build.py > > +EXETER_PYTHON =3D smoke/smoke.py build/build.py pasta/ndp.py > > EXETER_BATS =3D $(EXETER_SH:%=3D%.bats) $(EXETER_PYTHON:%=3D%.bats) > > BATS_FILES =3D $(EXETER_BATS) \ > > podman/test/system/505-networking-pasta.bats > > diff --git a/test/pasta/dhcp b/test/pasta/dhcp > > index e1c66be6..61279fbf 100644 > > --- a/test/pasta/dhcp > > +++ b/test/pasta/dhcp > > @@ -18,6 +18,11 @@ test Interface name > > nsout IFNAME ip -j link show | jq -rM '.[] | select(.link_type =3D=3D = "ether").ifname' > > check [ -n "__IFNAME__" ] > > =20 > > +# Bring up the interface > > +ns ip link set dev __IFNAME__ up > > +# Wait for SLAAC & DAD to complete > > +ns while ! ip -j -6 addr show dev __IFNAME__ | jq -e '.[].addr_info.[]= | select(.protocol =3D=3D "kernel_ra")'; do sleep 0.1; done > > + > > test DHCP: address > > ns /sbin/dhclient -4 --no-pid __IFNAME__ > > nsout ADDR ip -j -4 addr show|jq -rM '.[] | select(.ifname =3D=3D "__I= FNAME__").addr_info[0].local' > > diff --git a/test/pasta/ndp.py b/test/pasta/ndp.py > > new file mode 100755 > > index 00000000..8c7ce31e > > --- /dev/null > > +++ b/test/pasta/ndp.py > > @@ -0,0 +1,59 @@ > > +#! /usr/bin/env python3 > > +# > > +# SPDX-License-Identifier: GPL-2.0-or-later > > +# > > +# test/pasta/ndp.py - pasta NDP functionality > > +# > > +# Copyright Red Hat > > +# Author: David Gibson > > + > > +import contextlib > > +import dataclasses > > +from typing import Iterator > > + > > +import exeter > > +import tunbridge > > +import tasst > > + > > + > > +@dataclasses.dataclass > > +class UnconfiguredScenario(exeter.Scenario): > > + """Tests for a pasta instance without --config-net""" > > + > > + host: tunbridge.Site > > + guest: tunbridge.Site > > + ifname: str > > + addr6: tunbridge.ip.AddrMask6 > > + gw6: tunbridge.ip.Addr6 >=20 > Until this point, it looks like stuff I can happily copy and paste, > and grasp, even. But then: >=20 > > + @exeter.scenariotest > > + def test_ifname(self) -> None: > > + ifs =3D tunbridge.ip.ifs(self.guest) > > + exeter.assert_eq(set(ifs), {'lo', self.ifname}) >=20 > ...why does a "Scenario" have a .ifname? Yeah, the readability of the Scenario mechanism was something I was particularly concerned about. I think the concept is valuable, but I'm very open to different ways of naming or organising it, if we can up with something better. A "Scenario" (specifically a subclass of exeter.Scenario) is a group of tests with a common set of parameters. In this case UnconfiguredScenario is a bunch of tests about the behaviour of pasta without --config-net. Each of those tests has access to the host and guest sites, the expected interface name, address and gateway in the guest - that is, the contents of an UncofiguredScenario instance. That instance describes a real (simulated) environment in which we can run those tests. You use this by supplying a function which sets things up, then yields an UnconfiguredScenario instance describing what it set up. exeter will run all of the UnconfiguredScenario tests on the environment the setup function created, each one as a separate test case. Usually, there are multiple ways to set up a suitable enviroment: running pasta with an existing guest ns vs. pasta creating the ns is a simple example. You can create different setup functions for each of those, and re-use all the tests in the Scenario against each of those setups. > > + > > + @tunbridge.ndp.NdpScenario.subscenario > > + def test_ndp(self) -> tunbridge.ndp.NdpScenario: > > + tunbridge.ip.ifup(self.guest, self.ifname) >=20 > This raises the question of how much of tunbridge one needs to know to > be able to write a basic test. Why is ifup() in 'ip'? I thought it > would be more of a "link" thing. Finding misleading names is a big reason for seeking early feedback. There's kind of a reason for ifup to be in ip: it optionally takes IP addresses to configure on the interface. But... there's no inherent reason it couldn't take other sorts of network address too, so I'll look into moving that into a "link" module or something like it. > I admit I haven't had time to browse tunbridge recently, I'm just > looking at this series right now. That's fine. At some point it would be good to have you look at tunbridge too, but reading this series _without_ reading tunbridge is a very useful perspective at this stage. >=20 > > + return tunbridge.ndp.NdpScenario(client=3Dself.guest, > > + ifname=3Dself.ifname, > > + network=3Dself.addr6.network, > > + gateway=3Dself.gw6) >=20 > This makes sense to me. Ok, good. The Scenario stuff might not be as impenetrable as I feared. > > + > > + > > +@UnconfiguredScenario.test > > +@contextlib.contextmanager > > +def simh_pasta_setup() -> Iterator[UnconfiguredScenario]: > > + with (tunbridge.sample.simple_host('host') as simh, > > + tunbridge.sample.isolated('guest', simh.site) as guest): > > + assert simh.ip6 is not None > > + assert simh.gw6_ll is not None > > + with tasst.pasta.pasta(simh.site, guest): > > + yield UnconfiguredScenario(host=3Dsimh.site, > > + guest=3Dguest, > > + ifname=3Dsimh.ifname, > > + addr6=3Dsimh.ip6, > > + gw6=3Dsimh.gw6_ll) >=20 > ...and this too. >=20 > But there's one thing I'm missing: if it's a network simulator, why do > you need to call a simple_host() method to *describe* the fact that you > have a host / site? That looks rather unexpected. >=20 > I mean, I would have expected a syntax, in pseudocode, expressing: >=20 > 1. x :=3D node (properties such as a list of interfaces a, b, c) >=20 > 2. pasta implements/connects a >=20 > ...I think this is mostly embedded in the sample.simple_host() thing, > but I'm not sure how. Maybe it will become clearer once I actually look > into tunbridge, though. Right. "simple_host" isn't just an arbitrary node, but a (small) predefined network topology: a node configured with a single default gateway (also simulated, albeit minimally) - that is, the "classic" pasta host. The idea is that the tunbridge.sample module will have a bunch of such example networks - so far there's: - isolated() (node with loopback only) - back_to_back() (two nodes connected by a veth) - simple_host() Suggestions for better names welcome, as always. > Of course, I'm trying to push away my bias coming from the fact I was, > several years ago, for kselftests, aiming at something like this > instead: >=20 > A veth B > x=3D$(addr A veth) > B ping -c1 $x > A $x vxlan B $(addr B veth) > ... >=20 > (where 'veth', 'vxlan' were both reserved keywords). Maybe once > non-trivial links are implemented in tunbridge it will all become more > obvious. I think tunbridge is not dissimilar to this, though with functions rather than reserved words. It's a bit hidden here, because we're using these pre-built chunks - I expect that would be the case for your system as well, once you get to complex enough setups that you want to re-use non-trivial pieces. For example the guts of back_to_back() is: with isolated(f'{name}0', sb) as s0, \ isolated(f'{name}1', sb) as s1: if0, if1 =3D f'veth{name}0', f'veth{name}1' with veth.veth(s0, if0, s1, if1): ... There's more, but that's mostly about IP allocation (it optionally does that). > > + > > + > > +if __name__ =3D=3D '__main__': > > + exeter.main() > > diff --git a/test/run b/test/run > > index 3872a56e..4f09d767 100755 > > --- a/test/run > > +++ b/test/run > > @@ -43,8 +43,10 @@ KERNEL=3D${KERNEL:-"/boot/vmlinuz-$(uname -r)"} > > =20 > > COMMIT=3D"$(git log --oneline --no-decorate -1)" > > =20 > > -# Let exeter tests written in Python find their modules > > +# Let exeter tests written in Python find their modules and binaries t= o run > > export PYTHONPATH=3D${BASEPATH}/exeter/py3:${BASEPATH}/tunbridge:${BAS= EPATH} > > +export PASTA=3D${PASTA:-${BASEPATH}/../pasta} > > + > > =20 > > . lib/util > > . lib/context > > @@ -75,8 +77,8 @@ run() { > > exeter build/build.py > > exeter build/static_checkers.sh > > =20 > > + exeter pasta/ndp.py > > setup pasta > > - test pasta/ndp > > test pasta/dhcp > > test pasta/tcp > > test pasta/udp > > diff --git a/test/tasst/__init__.py b/test/tasst/__init__.py > > index fd4fe9a8..f5386b3a 100644 > > --- a/test/tasst/__init__.py > > +++ b/test/tasst/__init__.py > > @@ -8,3 +8,7 @@ > > # > > # Copyright Red Hat > > # Author: David Gibson > > + > > +from . import pasta > > + > > +__all__ =3D ['pasta'] > > diff --git a/test/tasst/pasta.py b/test/tasst/pasta.py > > new file mode 100644 > > index 00000000..91f59036 > > --- /dev/null > > +++ b/test/tasst/pasta.py > > @@ -0,0 +1,40 @@ > > +#! /usr/bin/env python3 > > +# > > +# SPDX-License-Identifier: GPL-2.0-or-later > > +# > > +# TASST - Test A Simple Socket Transport > > +# > > +# test/tasst/pasta.py - Helpers for seeting up pasta instances > > +# > > +# Copyright Red Hat > > +# Author: David Gibson > > + > > +import contextlib > > +import os > > +from typing import Iterator > > + > > +import tunbridge > > + > > + > > +@contextlib.contextmanager > > +def pasta(host: tunbridge.Site, guest: tunbridge.Site, *opts: str) \ > > + -> Iterator[tunbridge.site.SiteProcess]: > > + if tunbridge.unshare.parent(guest) is not host: > > + raise ValueError("pasta guest must be a namespace under host s= ite") > > + > > + # This implies guest is a namespace site > > + assert isinstance(guest, tunbridge.unshare.NsenterSite) > > + > > + exe =3D os.environ['PASTA'] > > + > > + with host.tempdir() as piddir: > > + pidfile =3D os.path.join(piddir, 'pasta.pid') > > + cmd =3D [exe, '-f', '-P', pidfile] + list(opts) + [f'{guest.pi= d}'] > > + with host.bg(*cmd, stop=3DTrue) as pasta: > > + # Wait for the PID file to be written > > + pidstr =3D None > > + while not pidstr: > > + pidstr =3D host.readfile(pidfile, check=3DFalse) > > + pid =3D int(pidstr) > > + print(f'pasta started, host: {host}, guest: {guest}, pid: = {pid}') > > + yield pasta >=20 > ...perhaps we could also print version and path. Path I can easily add. Version would require an extra invocation of pasta, which I don't really want to do. > This part also looks > quite readable and intuitive to me without having looked into tunbridge > recently. Ok, that's promising. --=20 David Gibson (he or they) | I'll have my music baroque, and my code david AT gibson.dropbear.id.au | minimalist, thank you, not the other way | around. http://www.ozlabs.org/~dgibson --unkAFnyHpYO3Y8yJ Content-Type: application/pgp-signature; name=signature.asc -----BEGIN PGP SIGNATURE----- iQIzBAEBCgAdFiEEO+dNsU4E3yXUXRK2zQJF27ox2GcFAmjlzToACgkQzQJF27ox 2GePIg/7B4cQDqz5ItKS8HNVftuiaBFaOUvYQAcMA4aJ3VzyDQfDz6nNKP1pVSnZ t7neznvfZMXOK7oPpWBDBVGk+D82NEnxwfBO7A0MuAqQZdL4gLcb+bJRufBZoJs8 8E6eVqa19moGnUnsdpGve+Z2L9ZJxnDaYie8JHIJElnfgVb1V5gT9u/tvHise7ss D/NrmcturRTZ+Hb/dukDQZ/Uegb6Sj7j6TH9iBCywOcHL4iaFgY+fCe4RBOvtaw8 BvvUlAPO/VVvDNZvwY36QjEHzXfFKwEKC09tBMtjowy+l/d2SuvKY1Mlw+BI4LKS j6T4mQee372PB6QFLMT9bEFEdFYqVYJXvjNNvhw6boBj6IxsBXq5Vgu2wiYFtGTb VuLJsGfsRmZ2/HAY7Hmz/wDS4bVS8AwOfRel4bHWokPvrrfIVPszFdb4gyW8QyAF nSqQIbu1vI9l2p3hJGjfx7f6JrPhX23njHj7HjjXo1TGtvlksp+42z02nur6CDYl CNb/AXN92At/dCRHnS2VjwsWzIqt8HGrDt4FpVqm1Pm1mVouaOi7a0/YBSSLVWQJ EiAxOKR5M8SRG9ZX5X/oQIAKjBHCxiFUbtpC/3Mf+riiUkb4imGywjxEu5ciAUAS qYQJ4D43CqikRphF8bpOjeZSEpeDldJoUCby7wZ+5iSHhm+MIN0= =gVEi -----END PGP SIGNATURE----- --unkAFnyHpYO3Y8yJ--