public inbox for passt-dev@passt.top
 help / color / mirror / code / Atom feed
From: Stefano Brivio <sbrivio@redhat.com>
To: David Gibson <david@gibson.dropbear.id.au>
Cc: passt-dev@passt.top
Subject: Re: [PATCH 3/3] test: Re-implement pasta NDP tests using tunbridge & exeter
Date: Tue, 7 Oct 2025 22:01:10 +0200	[thread overview]
Message-ID: <20251007220110.3c8bf21c@elisabeth> (raw)
In-Reply-To: <20251002075708.461931-4-david@gibson.dropbear.id.au>

On Thu,  2 Oct 2025 17:57:08 +1000
David Gibson <david@gibson.dropbear.id.au> wrote:

> Convert the pasta NDP tests from shell and our own DSL to Python using
> the exeter test protocol and tunbridge network simulation library.
> 
> Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
> ---
>  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
> 
> diff --git a/test/Makefile b/test/Makefile
> index f66c7e7e..95e3d75e 100644
> --- a/test/Makefile
> +++ b/test/Makefile
> @@ -67,7 +67,7 @@ ASSETS = $(DOWNLOAD_ASSETS) $(LOCAL_ASSETS)
>  
>  EXETER_SH = smoke/smoke.sh build/static_checkers.sh
>  EXETER_PYPATH = exeter/py3:tunbridge/:.
> -EXETER_PYTHON = smoke/smoke.py build/build.py
> +EXETER_PYTHON = smoke/smoke.py build/build.py pasta/ndp.py
>  EXETER_BATS = $(EXETER_SH:%=%.bats) $(EXETER_PYTHON:%=%.bats)
>  BATS_FILES = $(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 == "ether").ifname'
>  check	[ -n "__IFNAME__" ]
>  
> +# 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 == "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 == "__IFNAME__").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 <david@gibson.dropbear.id.au>
> +
> +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

Until this point, it looks like stuff I can happily copy and paste,
and grasp, even. But then:

> +    @exeter.scenariotest
> +    def test_ifname(self) -> None:
> +        ifs = tunbridge.ip.ifs(self.guest)
> +        exeter.assert_eq(set(ifs), {'lo', self.ifname})

...why does a "Scenario" have a .ifname?

> +
> +    @tunbridge.ndp.NdpScenario.subscenario
> +    def test_ndp(self) -> tunbridge.ndp.NdpScenario:
> +        tunbridge.ip.ifup(self.guest, self.ifname)

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.

I admit I haven't had time to browse tunbridge recently, I'm just
looking at this series right now.

> +        return tunbridge.ndp.NdpScenario(client=self.guest,
> +                                         ifname=self.ifname,
> +                                         network=self.addr6.network,
> +                                         gateway=self.gw6)

This makes sense to me.

> +
> +
> +@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=simh.site,
> +                                       guest=guest,
> +                                       ifname=simh.ifname,
> +                                       addr6=simh.ip6,
> +                                       gw6=simh.gw6_ll)

...and this too.

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.

I mean, I would have expected a syntax, in pseudocode, expressing:

1. x := node (properties such as a list of interfaces a, b, c)

2. pasta implements/connects a

...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.

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:

	A veth B
	x=$(addr A veth)
	B ping -c1 $x
	A $x vxlan B $(addr B veth)
	...

(where 'veth', 'vxlan' were both reserved keywords). Maybe once
non-trivial links are implemented in tunbridge it will all become more
obvious.

> +
> +
> +if __name__ == '__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=${KERNEL:-"/boot/vmlinuz-$(uname -r)"}
>  
>  COMMIT="$(git log --oneline --no-decorate -1)"
>  
> -# Let exeter tests written in Python find their modules
> +# Let exeter tests written in Python find their modules and binaries to run
>  export PYTHONPATH=${BASEPATH}/exeter/py3:${BASEPATH}/tunbridge:${BASEPATH}
> +export PASTA=${PASTA:-${BASEPATH}/../pasta}
> +
>  
>  . lib/util
>  . lib/context
> @@ -75,8 +77,8 @@ run() {
>  	exeter build/build.py
>  	exeter build/static_checkers.sh
>  
> +	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 <david@gibson.dropbear.id.au>
> +
> +from . import pasta
> +
> +__all__ = ['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 <david@gibson.dropbear.id.au>
> +
> +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 site")
> +
> +    # This implies guest is a namespace site
> +    assert isinstance(guest, tunbridge.unshare.NsenterSite)
> +
> +    exe = os.environ['PASTA']
> +
> +    with host.tempdir() as piddir:
> +        pidfile = os.path.join(piddir, 'pasta.pid')
> +        cmd = [exe, '-f', '-P', pidfile] + list(opts) + [f'{guest.pid}']
> +        with host.bg(*cmd, stop=True) as pasta:
> +            # Wait for the PID file to be written
> +            pidstr = None
> +            while not pidstr:
> +                pidstr = host.readfile(pidfile, check=False)
> +            pid = int(pidstr)
> +            print(f'pasta started, host: {host}, guest: {guest}, pid: {pid}')
> +            yield pasta

...perhaps we could also print version and path. This part also looks
quite readable and intuitive to me without having looked into tunbridge
recently.

-- 
Stefano


  reply	other threads:[~2025-10-07 20:01 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-10-02  7:57 [PATCH 0/3] RFC: Preview of tunbridge based tests David Gibson
2025-10-02  7:57 ` [PATCH 1/3] test: Prepare for " David Gibson
2025-10-07 20:00   ` Stefano Brivio
2025-10-02  7:57 ` [PATCH 2/3] test: Add some missing quoting in exeter runner David Gibson
2025-10-02  7:57 ` [PATCH 3/3] test: Re-implement pasta NDP tests using tunbridge & exeter David Gibson
2025-10-07 20:01   ` Stefano Brivio [this message]
2025-10-02  7:57 ` [PATCH 0/3] RFC: Preview of tunbridge based tests David Gibson

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=20251007220110.3c8bf21c@elisabeth \
    --to=sbrivio@redhat.com \
    --cc=david@gibson.dropbear.id.au \
    --cc=passt-dev@passt.top \
    /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).