mirror of
https://git.proxmox.com/git/mirror_frr
synced 2025-08-05 13:21:22 +00:00
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:
commit
766fcb6056
@ -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):
|
||||
"""
|
||||
|
17
tests/topotests/example_munet/munet.yaml
Normal file
17
tests/topotests/example_munet/munet.yaml
Normal 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"]
|
6
tests/topotests/example_munet/r1/daemons
Normal file
6
tests/topotests/example_munet/r1/daemons
Normal 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"
|
7
tests/topotests/example_munet/r1/frr.conf
Normal file
7
tests/topotests/example_munet/r1/frr.conf
Normal 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
|
1
tests/topotests/example_munet/r1/vtysh.conf
Normal file
1
tests/topotests/example_munet/r1/vtysh.conf
Normal file
@ -0,0 +1 @@
|
||||
service integrated-vtysh-config
|
6
tests/topotests/example_munet/r2/daemons
Normal file
6
tests/topotests/example_munet/r2/daemons
Normal 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"
|
10
tests/topotests/example_munet/r2/frr.conf
Normal file
10
tests/topotests/example_munet/r2/frr.conf
Normal 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
|
1
tests/topotests/example_munet/r2/vtysh.conf
Normal file
1
tests/topotests/example_munet/r2/vtysh.conf
Normal file
@ -0,0 +1 @@
|
||||
service integrated-vtysh-config
|
6
tests/topotests/example_munet/r3/daemons
Normal file
6
tests/topotests/example_munet/r3/daemons
Normal 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"
|
7
tests/topotests/example_munet/r3/frr.conf
Normal file
7
tests/topotests/example_munet/r3/frr.conf
Normal 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
|
1
tests/topotests/example_munet/r3/vtysh.conf
Normal file
1
tests/topotests/example_munet/r3/vtysh.conf
Normal file
@ -0,0 +1 @@
|
||||
service integrated-vtysh-config
|
10
tests/topotests/example_munet/test_munet.py
Normal file
10
tests/topotests/example_munet/test_munet.py
Normal 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)
|
30
tests/topotests/kinds.yaml
Normal file
30
tests/topotests/kinds.yaml
Normal 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"]
|
@ -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
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user