Convert the old-style tests for pasta (DHCP, NDP, TCP and UDP transfers) to using avocado. There are a few differences in what we test, but this should generally improve coverage: * We run in a constructed network environment, so we no longer depend on the real host's networking configuration * We do independent setup for each individual test * We add explicit tests for --config-net, which we use to accelerate that setup for the TCP and UDP tests * The TCP and UDP tests now test transfers between the guest and a (simulated) remote site that's on a different network from the simulated pasta host. Thus testing the no NAT case that passt/pasta emphasizes. (We need to add tests for the NAT cases back in). Signed-off-by: David Gibson <david(a)gibson.dropbear.id.au> --- test/Makefile | 4 +- test/pasta/.gitignore | 1 + test/pasta/pasta.py | 130 ++++++++++++++++++++++++++++++++++++++++++ test/tasst/pasta.py | 48 ++++++++++++++++ 4 files changed, 181 insertions(+), 2 deletions(-) create mode 100644 test/pasta/.gitignore create mode 100644 test/pasta/pasta.py create mode 100644 test/tasst/pasta.py diff --git a/test/Makefile b/test/Makefile index 3ac67b66..23dcd368 100644 --- a/test/Makefile +++ b/test/Makefile @@ -64,11 +64,11 @@ LOCAL_ASSETS = mbuto.img mbuto.mem.img podman/bin/podman QEMU_EFI.fd \ $(TESTDATA_ASSETS) ASSETS = $(DOWNLOAD_ASSETS) $(LOCAL_ASSETS) -AVOCADO_ASSETS = +AVOCADO_ASSETS = nstool small.bin medium.bin big.bin META_ASSETS = nstool small.bin medium.bin big.bin EXETER_SH = build/static_checkers.sh -EXETER_PY = build/build.py +EXETER_PY = build/build.py pasta/pasta.py EXETER_JOBS = $(EXETER_SH:%.sh=%.json) $(EXETER_PY:%.py=%.json) AVOCADO_JOBS = $(EXETER_JOBS) avocado/static_checkers.json diff --git a/test/pasta/.gitignore b/test/pasta/.gitignore new file mode 100644 index 00000000..a6c57f5f --- /dev/null +++ b/test/pasta/.gitignore @@ -0,0 +1 @@ +*.json diff --git a/test/pasta/pasta.py b/test/pasta/pasta.py new file mode 100644 index 00000000..b7d5ee2e --- /dev/null +++ b/test/pasta/pasta.py @@ -0,0 +1,130 @@ +#! /usr/bin/env avocado-runner-avocado-classless + +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright Red Hat +# Author: David Gibson <david(a)gibson.dropbear.id.au> + +""" +avocado/pasta.py - Basic tests for pasta mode +""" + +import contextlib +import ipaddress +from typing import Any, Iterator + +import exeter + +import tasst +from tasst import cmdsite, dhcp, ndp, pasta, unshare +from tasst.scenario.simple import simple_net + +IN_FWD_PORT = 10002 +SPLICE_FWD_PORT = 10003 +FWD_OPTS = ['-t', f'{IN_FWD_PORT}', '-u', f'{IN_FWD_PORT}', + '-T', f'{SPLICE_FWD_PORT}', '-U', f'{SPLICE_FWD_PORT}'] + + +(a)contextlib.contextmanager +def pasta_unconfigured(*opts: str) -> Iterator[tuple[Any, unshare.Unshare]]: + with simple_net() as simnet: + with unshare.unshare('pastans', '-Ucnpf', '--mount-proc', + parent=simnet.simhost, privilege=True) \ + as guestns: + with pasta.pasta(simnet.simhost, guestns, *opts) as p: + yield simnet, p.ns + + +(a)exeter.test +def test_ifname() -> None: + with pasta_unconfigured() as (simnet, ns): + expected = set(['lo', simnet.IFNAME]) + exeter.assert_eq(set(tasst.ip.ifs(ns)), expected) + + +(a)ndp.NdpScenario.test +def pasta_ndp_setup() -> Iterator[ndp.NdpScenario]: + with pasta_unconfigured() as (simnet, guestns): + tasst.ip.ifup(guestns, simnet.IFNAME) + yield ndp.NdpScenario(client=guestns, + ifname=simnet.IFNAME, + network=simnet.IP6.network, + gateway=simnet.gw_ip6_ll.ip) + + +(a)dhcp.Dhcp4Scenario.test +def pasta_dhcp() -> Iterator[dhcp.Dhcp4Scenario]: + with pasta_unconfigured() as (simnet, guestns): + yield dhcp.Dhcp4Scenario(client=guestns, + ifname=simnet.IFNAME, + addr=simnet.IP4.ip, + gateway=simnet.GW_IP4.ip, + mtu=65520) + + +(a)dhcp.Dhcp6Scenario.test +def pasta_dhcpv6() -> Iterator[dhcp.Dhcp6Scenario]: + with pasta_unconfigured() as (simnet, guestns): + yield dhcp.Dhcp6Scenario(client=guestns, + ifname=simnet.IFNAME, + addr=simnet.IP6.ip) + + +(a)contextlib.contextmanager +def pasta_configured() -> Iterator[tuple[Any, unshare.Unshare]]: + with pasta_unconfigured('--config-net', *FWD_OPTS) as (simnet, ns): + # Wait for DAD to complete on the --config-net address + tasst.ip.addr_wait(ns, simnet.IFNAME, family='inet6', scope='global') + yield simnet, ns + + +(a)exeter.test +def test_config_net_addr() -> None: + with pasta_configured() as (simnet, ns): + addrs = tasst.ip.addrs(ns, simnet.IFNAME, scope='global') + exeter.assert_eq(set(addrs), set([simnet.IP4, simnet.IP6])) + + +(a)exeter.test +def test_config_net_route4() -> None: + with pasta_configured() as (simnet, ns): + (defroute,) = tasst.ip.routes4(ns, dst='default') + gateway = ipaddress.ip_address(defroute['gateway']) + exeter.assert_eq(gateway, simnet.GW_IP4.ip) + + +(a)exeter.test +def test_config_net_route6() -> None: + with pasta_configured() as (simnet, ns): + (defroute,) = tasst.ip.routes6(ns, dst='default') + gateway = ipaddress.ip_address(defroute['gateway']) + exeter.assert_eq(gateway, simnet.gw_ip6_ll.ip) + + +(a)exeter.test +def test_config_net_mtu() -> None: + with pasta_configured() as (simnet, ns): + mtu = tasst.ip.mtu(ns, simnet.IFNAME) + exeter.assert_eq(mtu, 65520) + + +(a)contextlib.contextmanager +def outward_transfer() -> Iterator[tuple[Any, cmdsite.CmdSite]]: + with pasta_configured() as (simnet, ns): + yield ns, simnet.gw + + +(a)contextlib.contextmanager +def inward_transfer() -> Iterator[tuple[Any, cmdsite.CmdSite]]: + with pasta_configured() as (simnet, ns): + yield simnet.gw, ns + + +(a)contextlib.contextmanager +def spliced_transfer() -> Iterator[tuple[cmdsite.CmdSite, cmdsite.CmdSite]]: + with pasta_configured() as (simnet, ns): + yield ns, simnet.simhost + + +if __name__ == '__main__': + exeter.main() diff --git a/test/tasst/pasta.py b/test/tasst/pasta.py new file mode 100644 index 00000000..e224b81b --- /dev/null +++ b/test/tasst/pasta.py @@ -0,0 +1,48 @@ +#! /usr/bin/python3 + +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Copyright Red Hat +# Author: David Gibson <david(a)gibson.dropbear.id.au> + +""" +Test A Simple Socket Transport + +pasta.py - Helpers for starting pasta +""" + +import contextlib +import os.path +import tempfile +from typing import Iterator + +from . import cmdsite, unshare + + +PASTA_BIN = '../pasta' + + +class _Pasta: + """A running pasta instance""" + + ns: unshare.Unshare + + def __init__(self, ns: unshare.Unshare): + self.ns = ns + + +(a)contextlib.contextmanager +def pasta(host: cmdsite.CmdSite, ns: unshare.Unshare, *opts: str) \ + -> Iterator[_Pasta]: + with tempfile.TemporaryDirectory() as piddir: + pidfile = os.path.join(piddir, 'pasta.pid') + relpid = ns.relative_pid(host) + cmd = [PASTA_BIN, '-f', '-P', pidfile] + list(opts) + [f'{relpid}'] + with host.bg(*cmd): + # Wait for the PID file to be written + pidstr = None + while not pidstr: + pidstr = host.output('cat', pidfile, check=False) + pid = int(pidstr) + yield _Pasta(ns) + host.fg('kill', '-TERM', f'{pid}') -- 2.46.0