Merge pull request #13361 from LabNConsulting/chopps/munet-cfgopt-and-native

cfgopt in munet and native config support and example
This commit is contained in:
Donatas Abraitis 2023-04-24 13:50:36 +03:00 committed by GitHub
commit 766fcb6056
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 215 additions and 36 deletions

View File

@ -5,6 +5,7 @@ Topotest conftest.py file.
# pylint: disable=consider-using-f-string
import glob
import logging
import os
import re
import resource
@ -16,7 +17,7 @@ import lib.fixtures
import pytest
from lib.micronet_compat import ConfigOptionsProxy, Mininet
from lib.topogen import diagnose_env, get_topogen
from lib.topolog import logger
from lib.topolog import get_test_logdir, logger
from lib.topotest import json_cmp_result
from munet import cli
from munet.base import Commander, proc_error
@ -25,6 +26,19 @@ from munet.testing.util import pause_test
from lib import topolog, topotest
try:
# Used by munet native tests
from munet.testing.fixtures import event_loop, unet # pylint: disable=all # noqa
@pytest.fixture(scope="module")
def rundir_module(pytestconfig):
d = os.path.join(pytestconfig.option.rundir, get_test_logdir())
logging.debug("rundir_module: test module rundir %s", d)
return d
except (AttributeError, ImportError):
pass
def pytest_addoption(parser):
"""

View File

@ -0,0 +1,17 @@
version: 1
topology:
ipv6-enable: true
networks-autonumber: true
networks:
- name: net1
- name: net2
nodes:
- name: r1
kind: frr
connections: ["net1"]
- name: r2
kind: frr
connections: ["net1", "net2"]
- name: r3
kind: frr
connections: ["net2"]

View File

@ -0,0 +1,6 @@
zebra=1
staticd=1
vtysh_enable=1
watchfrr_enable=1
zebra_options="-d -F traditional --log=file:/var/log/frr/zebra.log"
staticd_options="-d -F traditional --log=file:/var/log/frr/staticd.log"

View File

@ -0,0 +1,7 @@
log file /var/log/frr/frr.log
service integrated-vtysh-config
interface eth0
ip address 10.0.1.1/24
ip route 10.0.0.0/8 blackhole

View File

@ -0,0 +1 @@
service integrated-vtysh-config

View File

@ -0,0 +1,6 @@
zebra=1
staticd=1
vtysh_enable=1
watchfrr_enable=1
zebra_options="-d -F traditional --log=file:/var/log/frr/zebra.log"
staticd_options="-d -F traditional --log=file:/var/log/frr/staticd.log"

View File

@ -0,0 +1,10 @@
log file /var/log/frr/frr.log
service integrated-vtysh-config
interface eth0
ip address 10.0.1.2/24
interface eth1
ip address 10.0.2.2/24
ip route 10.0.0.0/8 blackhole

View File

@ -0,0 +1 @@
service integrated-vtysh-config

View File

@ -0,0 +1,6 @@
zebra=1
staticd=1
vtysh_enable=1
watchfrr_enable=1
zebra_options="-d -F traditional --log=file:/var/log/frr/zebra.log"
staticd_options="-d -F traditional --log=file:/var/log/frr/staticd.log"

View File

@ -0,0 +1,7 @@
log file /var/log/frr/frr.log
service integrated-vtysh-config
interface eth0
ip address 10.0.2.3/24
ip route 10.0.0.0/8 blackhole

View File

@ -0,0 +1 @@
service integrated-vtysh-config

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 eval: (blacken-mode 1) -*-
# SPDX-License-Identifier: GPL-2.0-or-later
#
# April 23 2023, Christian Hopps <chopps@labn.net>
#
# Copyright (c) 2023, LabN Consulting, L.L.C.
#
async def test_native_test(unet):
o = unet.hosts["r1"].cmd_nostatus("ip addr")
print(o)

View File

@ -0,0 +1,30 @@
version: 1
kinds:
- name: frr
cmd: |
chown frr:frr -R /var/run/frr
chown frr:frr -R /var/log/frr
/usr/lib/frr/frrinit.sh start
tail -F /var/log/frr/frr.log
cleanup-cmd: |
/usr/lib/frr/frrinit.sh stop
volumes:
- "./%NAME%:/etc/frr"
- "%RUNDIR%/var.log.frr:/var/log/frr"
- "%RUNDIR%/var.run.frr:/var/run/frr"
cap-add:
- SYS_ADMIN
- AUDIT_WRITE
merge: ["volumes"]
cli:
commands:
- name: ""
exec: "vtysh -c '{}'"
format: "[ROUTER ...] COMMAND"
help: "execute vtysh COMMAND on the router[s]"
kinds: ["frr"]
- name: "vtysh"
exec: "/usr/bin/vtysh"
format: "vtysh ROUTER [ROUTER ...]"
new-window: true
kinds: ["frr"]

View File

@ -59,7 +59,6 @@ class Node(LinuxNamespace):
"""Node (mininet compat)."""
def __init__(self, name, rundir=None, **kwargs):
nkwargs = {}
if "unet" in kwargs:
@ -177,8 +176,6 @@ class Mininet(BaseMunet):
self.host_params = {}
self.prefix_len = 8
self.cfgopt = ConfigOptionsProxy(pytestconfig)
# SNMPd used to require this, which was set int he mininet shell
# that all commands executed from. This is goofy default so let's not
# do it if we don't have to. The snmpd.conf files have been updated

View File

@ -26,6 +26,7 @@ from collections import defaultdict
from pathlib import Path
from typing import Union
from . import config as munet_config
from . import linux
@ -2493,7 +2494,15 @@ class Bridge(SharedNamespace, InterfaceMixin):
class BaseMunet(LinuxNamespace):
"""Munet."""
def __init__(self, name="munet", isolated=True, pid=True, rundir=None, **kwargs):
def __init__(
self,
name="munet",
isolated=True,
pid=True,
rundir=None,
pytestconfig=None,
**kwargs,
):
"""Create a Munet."""
# logging.warning("BaseMunet: %s", name)
@ -2562,6 +2571,8 @@ class BaseMunet(LinuxNamespace):
roothost = self.rootcmd
self.cfgopt = munet_config.ConfigOptionsProxy(pytestconfig)
super().__init__(
name, mount=True, net=isolated, uts=isolated, pid=pid, unet=None, **kwargs
)

View File

@ -11,8 +11,18 @@
class PytestConfig:
"""Pytest config duck-type-compatible object using argprase args."""
class Namespace:
"""A namespace defined by a dictionary of values."""
def __init__(self, args):
self.args = args
def __getattr__(self, attr):
return self.args[attr] if attr in self.args else None
def __init__(self, args):
self.args = vars(args)
self.option = PytestConfig.Namespace(self.args)
def getoption(self, name, default=None, skip=False):
assert not skip

View File

@ -156,3 +156,57 @@ def merge_kind_config(kconf, config):
if k not in new:
new[k] = config[k]
return new
def cli_opt_list(option_list):
if not option_list:
return []
if isinstance(option_list, str):
return [x for x in option_list.split(",") if x]
return [x for x in option_list if x]
def name_in_cli_opt_str(name, option_list):
ol = cli_opt_list(option_list)
return name in ol or "all" in ol
class ConfigOptionsProxy:
"""Proxy options object to fill in for any missing pytest config."""
class DefNoneObject:
"""An object that returns None for any attribute access."""
def __getattr__(self, attr):
return None
def __init__(self, pytestconfig=None):
if isinstance(pytestconfig, ConfigOptionsProxy):
self.config = pytestconfig.config
self.option = self.config.option
else:
self.config = pytestconfig
if self.config:
self.option = self.config.option
else:
self.option = ConfigOptionsProxy.DefNoneObject()
def getoption(self, opt, defval=None):
if not self.config:
return defval
try:
return self.config.getoption(opt, default=defval)
except ValueError:
return defval
def get_option(self, opt, defval=None):
return self.getoption(opt, defval)
def get_option_list(self, opt):
value = self.get_option(opt, "")
return cli_opt_list(value)
def name_in_option_list(self, name, opt):
optlist = self.get_option_list(opt)
return "all" in optlist or name in optlist

View File

@ -423,37 +423,37 @@ class NodeMixin:
stdout: file-like object with a ``name`` attribute, or a path to a file.
stderr: file-like object with a ``name`` attribute, or a path to a file.
"""
if not self.unet or not self.unet.pytest_config:
if not self.unet:
return
outopt = self.unet.pytest_config.getoption("--stdout")
outopt = self.unet.cfgopt.getoption("--stdout")
outopt = outopt if outopt is not None else ""
if outopt == "all" or self.name in outopt.split(","):
outname = stdout.name if hasattr(stdout, "name") else stdout
self.run_in_window(f"tail -F {outname}", title=f"O:{self.name}")
if stderr:
erropt = self.unet.pytest_config.getoption("--stderr")
erropt = self.unet.cfgopt.getoption("--stderr")
erropt = erropt if erropt is not None else ""
if erropt == "all" or self.name in erropt.split(","):
errname = stderr.name if hasattr(stderr, "name") else stderr
self.run_in_window(f"tail -F {errname}", title=f"E:{self.name}")
def pytest_hook_open_shell(self):
if not self.unet or not self.unet.pytest_config:
if not self.unet:
return
gdbcmd = self.config.get("gdb-cmd")
shellopt = self.unet.pytest_config.getoption("--gdb", "")
shellopt = self.unet.cfgopt.getoption("--gdb", "")
should_gdb = gdbcmd and (shellopt == "all" or self.name in shellopt.split(","))
use_emacs = self.unet.pytest_config.getoption("--gdb-use-emacs", False)
use_emacs = self.unet.cfgopt.getoption("--gdb-use-emacs", False)
if should_gdb and not use_emacs:
cmds = self.config.get("gdb-target-cmds", [])
for cmd in cmds:
gdbcmd += f" '-ex={cmd}'"
bps = self.unet.pytest_config.getoption("--gdb-breakpoints", "").split(",")
bps = self.unet.cfgopt.getoption("--gdb-breakpoints", "").split(",")
for bp in bps:
gdbcmd += f" '-ex=b {bp}'"
@ -497,7 +497,7 @@ class NodeMixin:
]
)
bps = self.unet.pytest_config.getoption("--gdb-breakpoints", "").split(",")
bps = self.unet.cfgopt.getoption("--gdb-breakpoints", "").split(",")
for bp in bps:
cmd = f"br {bp}"
self.cmd_raises(
@ -520,8 +520,8 @@ class NodeMixin:
)
gdbcmd += f" '-ex={cmd}'"
shellopt = self.unet.pytest_config.getoption("--shell")
shellopt = shellopt if shellopt is not None else ""
shellopt = self.unet.cfgopt.getoption("--shell")
shellopt = shellopt if shellopt else ""
if shellopt == "all" or self.name in shellopt.split(","):
self.run_in_window("bash")
@ -1968,7 +1968,7 @@ class L3QemuVM(L3NodeMixin, LinuxNamespace):
con.cmd_raises(f"ip -6 route add default via {switch.ip6_address}")
con.cmd_raises("ip link set lo up")
if self.unet.pytest_config and self.unet.pytest_config.getoption("--coverage"):
if self.unet.cfgopt.getoption("--coverage"):
con.cmd_raises("mount -t debugfs none /sys/kernel/debug")
async def gather_coverage_data(self):
@ -2402,7 +2402,6 @@ class Munet(BaseMunet):
self,
rundir=None,
config=None,
pytestconfig=None,
pid=True,
logger=None,
**kwargs,
@ -2433,8 +2432,6 @@ class Munet(BaseMunet):
self.config_pathname = ""
self.config_dirname = ""
self.pytest_config = pytestconfig
# Done in BaseMunet now
# # We need some way to actually get back to the root namespace
# if not self.isolated:
@ -2573,10 +2570,8 @@ ff02::2\tip6-allrouters
# # Let's hide podman details
# self.tmpfs_mount("/var/lib/containers/storage/overlay-containers")
shellopt = (
self.pytest_config.getoption("--shell") if self.pytest_config else None
)
shellopt = shellopt if shellopt is not None else ""
shellopt = self.cfgopt.getoption("--shell")
shellopt = shellopt if shellopt else ""
if shellopt == "all" or "." in shellopt.split(","):
self.run_in_window("bash")
@ -2795,11 +2790,8 @@ ff02::2\tip6-allrouters
x for x in hosts if hasattr(x, "has_ready_cmd") and x.has_ready_cmd()
]
if not self.pytest_config:
pcapopt = ""
else:
pcapopt = self.pytest_config.getoption("--pcap")
pcapopt = pcapopt if pcapopt else ""
pcapopt = self.cfgopt.getoption("--pcap")
pcapopt = pcapopt if pcapopt else ""
if pcapopt == "all":
pcapopt = self.switches.keys()
if pcapopt:
@ -2868,7 +2860,7 @@ ff02::2\tip6-allrouters
self.logger.debug("%s: deleting.", self)
if self.pytest_config and self.pytest_config.getoption("--coverage"):
if self.cfgopt.getoption("--coverage"):
nodes = (
x for x in self.hosts.values() if hasattr(x, "gather_coverage_data")
)
@ -2877,11 +2869,8 @@ ff02::2\tip6-allrouters
except Exception as error:
logging.warning("Error gathering coverage data: %s", error)
if not self.pytest_config:
pause = False
else:
pause = bool(self.pytest_config.getoption("--pause-at-end"))
pause = pause or bool(self.pytest_config.getoption("--pause"))
pause = bool(self.cfgopt.getoption("--pause-at-end"))
pause = pause or bool(self.cfgopt.getoption("--pause"))
if pause:
try:
await async_pause_test("Before MUNET delete")

View File

@ -235,7 +235,7 @@ def load_kinds(args, search=None):
if search is None:
search = [cwd]
with importlib.resources.path("munet", "kinds.yaml") as datapath:
search.append(str(datapath.parent))
search.insert(0, str(datapath.parent))
configs = []
if args_config:

View File

@ -1,6 +1,8 @@
# Skip pytests example directory
[pytest]
asyncio_mode = auto
# We always turn this on inside conftest.py, default shown
# addopts = --junitxml=<rundir>/topotests.xml
@ -24,7 +26,7 @@ log_file_date_format = %Y-%m-%d %H:%M:%S
junit_logging = all
junit_log_passing_tests = true
norecursedirs = .git example_test example_topojson_test lib munet docker
norecursedirs = .git example_munet example_test example_topojson_test lib munet docker
# Directory to store test results and run logs in, default shown
# rundir = /tmp/topotests