selftests: drv-net: define endpoint structures

Define the remote endpoint "model". To execute most meaningful device
driver tests we need to be able to communicate with a remote system,
and have it send traffic to the device under test.

Various test environments will have different requirements.

0) "Local" netdevsim-based testing can simply use net namespaces.
netdevsim supports connecting two devices now, to form a veth-like
construct.

1) Similarly on hosts with multiple NICs, the NICs may be connected
together with a loopback cable or internal device loopback.
One interface may be placed into separate netns, and tests
would proceed much like in the netdevsim case. Note that
the loopback config or the moving of one interface
into a netns is not expected to be part of selftest code.

2) Some systems may need to communicate with the remote endpoint
via SSH.

3) Last but not least environment may have its own custom communication
method.

Fundamentally we only need two operations:
 - run a command remotely
 - deploy a binary (if some tool we need is built as part of kselftests)

Wrap these two in a class. Use dynamic loading to load the Remote
class. This will allow very easy definition of other communication
methods without bothering upstream code base.

Stick to the "simple" / "no unnecessary abstractions" model for
referring to the remote endpoints. The host / remote object are
passed as an argument to the usual cmd() or ip() invocation.
For example:

 ip("link show", json=True, host=remote)

Reviewed-by: Willem de Bruijn <willemb@google.com>
Link: https://lore.kernel.org/r/20240420025237.3309296-2-kuba@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2024-04-19 19:52:31 -07:00
parent b2c8599f64
commit 1a20a9a0dd
5 changed files with 85 additions and 8 deletions

View File

@ -15,3 +15,4 @@ except ModuleNotFoundError as e:
sys.exit(4)
from .env import *
from .remote import Remote

View File

@ -0,0 +1,15 @@
# SPDX-License-Identifier: GPL-2.0
import os
import importlib
_modules = {}
def Remote(kind, args, src_path):
global _modules
if kind not in _modules:
_modules[kind] = importlib.import_module("..remote_" + kind, __name__)
dir_path = os.path.abspath(src_path + "/../")
return getattr(_modules[kind], "Remote")(args, dir_path)

View File

@ -0,0 +1,21 @@
# SPDX-License-Identifier: GPL-2.0
import os
import subprocess
from lib.py import cmd
class Remote:
def __init__(self, name, dir_path):
self.name = name
self.dir_path = dir_path
def cmd(self, comm):
return subprocess.Popen(["ip", "netns", "exec", self.name, "bash", "-c", comm],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
def deploy(self, what):
if os.path.isabs(what):
return what
return os.path.abspath(self.dir_path + "/" + what)

View File

@ -0,0 +1,39 @@
# SPDX-License-Identifier: GPL-2.0
import os
import string
import subprocess
import random
from lib.py import cmd
class Remote:
def __init__(self, name, dir_path):
self.name = name
self.dir_path = dir_path
self._tmpdir = None
def __del__(self):
if self._tmpdir:
cmd("rm -rf " + self._tmpdir, host=self)
self._tmpdir = None
def cmd(self, comm):
return subprocess.Popen(["ssh", "-q", self.name, comm],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
def _mktmp(self):
return ''.join(random.choice(string.ascii_lowercase) for _ in range(8))
def deploy(self, what):
if not self._tmpdir:
self._tmpdir = "/tmp/" + self._mktmp()
cmd("mkdir " + self._tmpdir, host=self)
file_name = self._tmpdir + "/" + self._mktmp() + os.path.basename(what)
if not os.path.isabs(what):
what = os.path.abspath(self.dir_path + "/" + what)
cmd(f"scp {what} {self.name}:{file_name}")
return file_name

View File

@ -4,10 +4,8 @@ import json as _json
import subprocess
class cmd:
def __init__(self, comm, shell=True, fail=True, ns=None, background=False):
def __init__(self, comm, shell=True, fail=True, ns=None, background=False, host=None):
if ns:
if isinstance(ns, NetNS):
ns = ns.name
comm = f'ip netns exec {ns} ' + comm
self.stdout = None
@ -15,15 +13,18 @@ class cmd:
self.ret = None
self.comm = comm
self.proc = subprocess.Popen(comm, shell=shell, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
if host:
self.proc = host.cmd(comm)
else:
self.proc = subprocess.Popen(comm, shell=shell, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
if not background:
self.process(terminate=False, fail=fail)
def process(self, terminate=True, fail=None):
if terminate:
self.proc.terminate()
stdout, stderr = self.proc.communicate()
stdout, stderr = self.proc.communicate(timeout=5)
self.stdout = stdout.decode("utf-8")
self.stderr = stderr.decode("utf-8")
self.proc.stdout.close()
@ -37,12 +38,12 @@ class cmd:
(self.proc.args, stdout, stderr))
def ip(args, json=None, ns=None):
def ip(args, json=None, ns=None, host=None):
cmd_str = "ip "
if json:
cmd_str += '-j '
cmd_str += args
cmd_obj = cmd(cmd_str, ns=ns)
cmd_obj = cmd(cmd_str, ns=ns, host=host)
if json:
return _json.loads(cmd_obj.stdout)
return cmd_obj