Merge pull request #1172 from kernelkit/v25.08.x-backports

Backport fixes for v25.08.1

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2025-09-30 15:28:57 +02:00
committed by GitHub
18 changed files with 217 additions and 186 deletions
+4
View File
@@ -9,6 +9,10 @@ on:
- ci-work
workflow_dispatch:
concurrency:
group: ci-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
build-x86_64:
if: startsWith(github.repository, 'kernelkit/')
View File
+3 -1
View File
@@ -3,7 +3,7 @@ Change Log
All notable changes to the project are documented in this file.
[v25.08.1][] - 2025-10-01
[v25.08.1][] - 2025-09-30
-------------------------
### Changes
@@ -12,6 +12,8 @@ All notable changes to the project are documented in this file.
### Fixes
- Fix #1150: `show-legacy` wrapper permissions
- Fix #1155: `show ospf` commands regression
- Fix #1169: Expected neighbors not shown in sysrepocfg
- Various test stability improvments
[v25.08.0][] - 2025-09-01
-------------------------
+6 -6
View File
@@ -338,17 +338,17 @@
</PARAM>
<ACTION sym="script" in="tty" out="tty" interrupt="true">
if [ -z "$KLISH_PARAM_name" ]; then
doas vtysh -c "show-legacy ip ospf" |pager
doas vtysh -c "show ip ospf" |pager
elif [ "$KLISH_PARAM_name" == "neighbor" ];then
doas vtysh -c "show-legacy ip ospf neighbor" |pager
doas vtysh -c "show ip ospf neighbor" |pager
elif [ "$KLISH_PARAM_name" == "interfaces" ];then
doas vtysh -c "show-legacy ip ospf interface" |pager
doas vtysh -c "show ip ospf interface" |pager
elif [ "$KLISH_PARAM_name" == "routes" ];then
doas vtysh -c "show-legacy ip ospf route" |pager
doas vtysh -c "show ip ospf route" |pager
elif [ "$KLISH_PARAM_name" == "database" ];then
doas vtysh -c "show-legacy ip ospf database" |pager
doas vtysh -c "show ip ospf database" |pager
elif [ "$KLISH_PARAM_name" == "bfd" ];then
doas vtysh -c "show-legacy bfd peers" |pager
doas vtysh -c "show bfd peers" |pager
fi
</ACTION>
</COMMAND>
@@ -40,6 +40,11 @@ def main():
for ifname, iface in interfaces["interfaces"].items():
iface["name"] = ifname
iface["neighbors"] = []
# Skip interfaces that don't have OSPF enabled or area configured
if not iface.get("ospfEnabled", False) or not iface.get("area"):
continue
for area_id in ospf["areas"]:
area_type=""
+14
View File
@@ -6,12 +6,26 @@
infamy:
specification: False
- case: meta/bootorder.py
infamy:
specification: False
- case: meta/check-version.py
infamy:
specification: False
- name: Misc tests
suite: misc/misc.yaml
- name: ietf-system
suite: ietf_system/ietf_system.yaml
# Upgrade may leave wrong boot order
- case: meta/bootorder.py
infamy:
specification: False
- name: ietf-syslog
suite: ietf_syslog/ietf_syslog.yaml
@@ -3,27 +3,32 @@
Container environment variables
Verify that environment variables can be set in container configuration
and are available inside the running container. Tests the 'env' list
functionality by:
and are available inside the running container.
1. Creating a container with multiple environment variables
2. Using a custom script to extract env vars and serve them via HTTP
3. Fetching the served content to verify environment variables are set correctly
Uses the nftables container image with custom rc.local script.
1 Set up a container config with multiple environment variables
2. Serve variables back to host using a CGI script in container
3. Verify served content against environment variables
"""
import infamy
from infamy.util import until, to_binary
with infamy.Test() as test:
ENV_VARS = [
{"key": "TEST_VAR", "value": "hello-world"},
{"key": "APP_PORT", "value": "8080"},
{"key": "DEBUG_MODE", "value": "true"},
{"key": "PATH_WITH_SPACES", "value": "/path with spaces/test"}
]
NAME = "web-env"
DUTIP = "10.0.0.2"
OURIP = "10.0.0.1"
url = infamy.Furl(f"http://{DUTIP}:8080/cgi-bin/env.cgi")
with test.step("Set up topology and attach to target DUT"):
env = infamy.Env()
target = env.attach("target", "mgmt")
_, hport = env.ltop.xlate("host", "data")
if not target.has_model("infix-containers"):
test.skip()
@@ -44,44 +49,33 @@ with infamy.Test() as test:
}
})
with test.step("Create container with environment variables"):
script = to_binary("""#!/bin/sh
# Create HTTP response with environment variables
printf "HTTP/1.1 200 OK\\r\\n" > /var/www/response.txt
printf "Content-Type: text/plain\\r\\n" >> /var/www/response.txt
printf "Connection: close\\r\\n\\r\\n" >> /var/www/response.txt
with test.step("Set up container with environment variables"):
cgi = [
'#!/bin/sh',
'# CGI script to output environment variables',
'echo "Content-Type: text/plain"',
'echo ""'
]
# Add environment variables using printf to control encoding
printf "TEST_VAR=\\"%s\\"\\n" "$TEST_VAR" >> /var/www/response.txt
printf "APP_PORT=%s\\n" "$APP_PORT" >> /var/www/response.txt
printf "DEBUG_MODE=\\"%s\\"\\n" "$DEBUG_MODE" >> /var/www/response.txt
printf "PATH_WITH_SPACES=\\"%s\\"\\n" "$PATH_WITH_SPACES" >> /var/www/response.txt
while true; do
nc -l -p 8080 < /var/www/response.txt 2>>/var/www/debug.log || sleep 1
done
""")
for var in ENV_VARS:
cgi.append(f'echo "{var["key"]}=${var["key"]}"')
target.put_config_dict("infix-containers", {
"containers": {
"container": [
{
"name": f"{NAME}",
"image": f"oci-archive:{infamy.Container.NFTABLES_IMAGE}",
"env": [
{"key": "TEST_VAR", "value": "hello-world"},
{"key": "APP_PORT", "value": "8080"},
{"key": "DEBUG_MODE", "value": "true"},
{"key": "PATH_WITH_SPACES", "value": "/path with spaces/test"}
],
"image": f"oci-archive:{infamy.Container.HTTPD_IMAGE}",
"command": "/usr/sbin/httpd -f -v -p 8080",
"env": ENV_VARS,
"network": {
"host": True
},
"mount": [
{
"name": "rc.local",
"content": script,
"target": "/etc/rc.local",
"name": "env.cgi",
"content": to_binary('\n'.join(cgi) + '\n'),
"target": "/var/www/cgi-bin/env.cgi",
"mode": "0755"
}
],
@@ -98,20 +92,17 @@ done
c = infamy.Container(target)
until(lambda: c.running(NAME), attempts=60)
with test.step("Verify environment variables are available via HTTP"):
_, hport = env.ltop.xlate("host", "data")
url = infamy.Furl(f"http://{DUTIP}:8080/env.html")
with infamy.IsolatedMacVlan(hport) as ns:
ns.addip(OURIP)
with infamy.IsolatedMacVlan(hport) as ns:
ns.addip(OURIP)
with test.step("Verify basic connectivity to data interface"):
ns.must_reach(DUTIP)
with test.step("Verify basic connectivity to data interface"):
ns.must_reach(DUTIP)
with test.step("Verify environment variables in CGI response"):
expected_strings = []
for var in ENV_VARS:
expected_strings.append(f'{var["key"]}={var["value"]}')
with test.step("Verify environment variables in HTTP response"):
until(lambda: url.nscheck(ns, "TEST_VAR=\"hello-world\""), attempts=10)
until(lambda: url.nscheck(ns, "APP_PORT=8080"), attempts=10)
until(lambda: url.nscheck(ns, "DEBUG_MODE=\"true\""), attempts=10)
until(lambda: url.nscheck(ns, "PATH_WITH_SPACES=\"/path with spaces/test\""), attempts=10)
until(lambda: url.nscheck(ns, expected_strings))
test.succeed()
@@ -6,9 +6,8 @@
- name: container_enabled
case: container_enabled/test.py
# Disabled for v25.08, new/unstable
# - name: container_environment
# case: container_environment/test.py
- name: container_environment
case: container_environment/test.py
- name: container_bridge
case: container_bridge/test.py
+2 -3
View File
@@ -2,6 +2,5 @@
- name: mdns_enable_disable
case: mdns_enable_disable/test.py
# Disabled for v25.08, unstable
# - name: mdns_allow_deny
# case: mdns_allow_deny/test.py
- name: mdns_allow_deny
case: mdns_allow_deny/test.py
@@ -10,22 +10,55 @@ with three scenarios:
3. Allow p1 and p3, deny p2 and p3, traffic only on p1
"""
import time
import re
import infamy
from infamy.util import parallel
def mdns_scan(tgt):
"""Trigger Avahi to send traffic on allowed interfaces"""
time.sleep(2)
tgt.runsh("logger -t scan 'calling avahi-browse ...'")
tgt.runsh("avahi-browse -lat")
def mdns_scan():
"""Start packet captures, trigger mDNS scan, return capture results"""
pcap1 = ns1.pcap("host 10.0.1.1 and port 5353")
pcap2 = ns2.pcap("host 10.0.2.1 and port 5353")
pcap3 = ns3.pcap("host 10.0.3.1 and port 5353")
with pcap1, pcap2, pcap3:
ssh.runsh("logger -t scan 'calling avahi-browse ...'")
ssh.runsh("avahi-browse -lat")
def has_packets(output):
if not output:
return False
lines = output.strip().split('\n')
m = re.search(r'^(\d+) packets.*', lines[1])
if m and int(m.group(1)) > 0:
return True
return False
out1 = pcap1.tcpdump("--count")
out2 = pcap2.tcpdump("--count")
out3 = pcap3.tcpdump("--count")
return (has_packets(out1), has_packets(out2), has_packets(out3))
def check(ns, expr, must):
"""Wrap netns.must_receive() with common defaults"""
return ns.must_receive(expr, timeout=3, must=must)
def check(expected, allow=None, deny=None):
"""Execute complete mDNS test scenario"""
try:
dut.delete_xpath("/infix-services:mdns/interfaces")
except ValueError:
# Ignore if xpath doesn't exist (first run)
pass
mdns_config = {"mdns": {"interfaces": {}}}
if allow:
mdns_config["mdns"]["interfaces"]["allow"] = allow
if deny:
mdns_config["mdns"]["interfaces"]["deny"] = deny
dut.put_config_dict("infix-services", mdns_config)
actual = mdns_scan()
if actual != tuple(expected):
raise AssertionError(f"Expected {expected}, got {actual}")
with infamy.Test() as test:
@@ -45,58 +78,41 @@ with infamy.Test() as test:
{
"ietf-interfaces": {
"interfaces": {
"interface": [
{
"name": p1,
"enabled": True,
"ipv4": {
"address": [
{
"ip": "10.0.1.1",
"prefix-length": 24
}
]
}
},
{
"name": p2,
"enabled": True,
"ipv4": {
"address": [
{
"ip": "10.0.2.1",
"prefix-length": 24
}
]
}
},
{
"name": p3,
"enabled": True,
"ipv4": {
"address": [
{
"ip": "10.0.3.1",
"prefix-length": 24
}
]
}
},
]
}
"interface": [{
"name": p1,
"enabled": True,
"ipv4": {
"address": [{
"ip": "10.0.1.1",
"prefix-length": 24
}]
}
}, {
"name": p2,
"enabled": True,
"ipv4": {
"address": [{
"ip": "10.0.2.1",
"prefix-length": 24
}]
}
}, {
"name": p3,
"enabled": True,
"ipv4": {
"address": [{
"ip": "10.0.3.1",
"prefix-length": 24
}]
}
}]
}
},
"ietf-system": {
"system": {
"hostname": "dut"
}
"system": {"hostname": "dut"}
},
"infix-services": {
"mdns": {
"enabled": True
}
"mdns": {"enabled": True}
}
}
)
@@ -108,53 +124,16 @@ with infamy.Test() as test:
ns2.addip("10.0.2.2")
ns3.addip("10.0.3.2")
EXPR1 = "host 10.0.1.1 and port 5353"
EXPR2 = "host 10.0.2.1 and port 5353"
EXPR3 = "host 10.0.3.1 and port 5353"
with test.step("Allow mDNS on a single interface: p2"):
dut.put_config_dict("infix-services", {
"mdns": {
"interfaces": {
"allow": [p2],
}
}
})
parallel(lambda: mdns_scan(ssh),
lambda: check(ns1, EXPR1, False),
lambda: check(ns2, EXPR2, True),
lambda: check(ns3, EXPR3, False))
# p1:no, p2:yes, p3:no
check([False, True, False], allow=[p2])
with test.step("Deny mDNS on a single interface: p2"):
dut.delete_xpath("/infix-services:mdns/interfaces")
dut.put_config_dict("infix-services", {
"mdns": {
"interfaces": {
"deny": [p2],
}
}
})
parallel(lambda: mdns_scan(ssh),
lambda: check(ns1, EXPR1, True),
lambda: check(ns2, EXPR2, False),
lambda: check(ns3, EXPR3, True))
# p1:yes, p2:no, p3:yes
check([True, False, True], deny=[p2])
with test.step("Allow mDNS on p1, p3 deny on p2, p3"):
dut.delete_xpath("/infix-services:mdns/interfaces")
dut.put_config_dict("infix-services", {
"mdns": {
"interfaces": {
"allow": [p1, p3],
"deny": [p2, p3],
}
}
})
parallel(lambda: mdns_scan(ssh),
lambda: check(ns1, EXPR1, True),
lambda: check(ns2, EXPR2, False),
lambda: check(ns3, EXPR3, False))
# p1:yes, p2:no, p3:no (deny overrides allow)
check([True, False, False], allow=[p1, p3], deny=[p2, p3])
test.succeed()
+24
View File
@@ -0,0 +1,24 @@
#!/usr/bin/env python3
import infamy
with infamy.Test() as test:
with test.step("Discover topology and attach to available DUTs"):
env = infamy.Env(False)
ctrl = env.ptop.get_ctrl()
duts = {}
duts_state = {}
for ix in env.ptop.get_infixen():
cport, ixport = env.ptop.get_mgmt_link(ctrl, ix)
print(f"Attaching to {ix}:{ixport} via {ctrl}:{cport}")
duts[ix] = env.attach(ix, ixport)
with test.step("Verify bootorder"):
for name, tgt in duts.items():
expected = env.ptop.get_expected_boot(name)
running = tgt.get_data("/ietf-system:system-state")
running = running['system-state']['software']['booted']
print(f"{name}: booted: {running} expected: {expected}")
if running != expected:
test.fail()
test.succeed()
+24
View File
@@ -0,0 +1,24 @@
#!/usr/bin/env python3
import infamy
import os
with infamy.Test() as test:
with test.step("Discover topology and attach to available DUTs"):
env = infamy.Env(False)
ctrl = env.ptop.get_ctrl()
duts = {}
duts_state = {}
for ix in env.ptop.get_infixen():
cport, ixport = env.ptop.get_mgmt_link(ctrl, ix)
print(f"Attaching to {ix}:{ixport} via {ctrl}:{cport}")
duts[ix] = env.attach(ix, ixport)
with test.step("Verify software version"):
expected=os.environ.get("VERSION")
for name, tgt in duts.items():
running = tgt.get_data("/ietf-system:system-state")
running = running['system-state']['platform']['os-version']
print(f"{name}: booted: {running} expected {expected}")
if running != expected:
test.fail()
test.succeed()
-23
View File
@@ -13,27 +13,4 @@ with infamy.Test() as test:
else:
print(f"Specify PYTHONHASHSEED={seed} to reproduce this test environment")
with test.step("Discover topology and attach to available DUTs"):
env = infamy.Env(False)
ctrl = env.ptop.get_ctrl()
duts = {}
for ix in env.ptop.get_infixen():
cport, ixport = env.ptop.get_mgmt_link(ctrl, ix)
print(f"Attaching to {ix}:{ixport} via {ctrl}:{cport}")
duts[ix] = env.attach(ix, ixport)
with test.step("Log running software versions"):
for name, tgt in duts.items():
sys = tgt.get_data("/ietf-system:system-state")
sw = sys["system-state"]["software"]
plt = sys["system-state"]["platform"]
print(f"{name}:")
for k,v in plt.items():
print(f" {k:<16s} {v}")
for k in ("compatible", "booted"):
print(f" {k:<16s} {sw[k]}")
test.succeed()
+2
View File
@@ -184,6 +184,7 @@ while getopts "b:cCDf:hiKp:q:rt:" opt; do
kvm=
;;
p)
version=$(unsquashfs -cat $OPTARG manifest.raucm | awk -F'=' '/^version=/ {print $2}')
INFAMY_ARGS="$INFAMY_ARGS -p $OPTARG"
;;
q)
@@ -234,6 +235,7 @@ if [ "$containerize" ]; then
--env NINEPM_PROJ_CONFIG="$NINEPM_PROJ_CONFIG" \
--env QENETH_PATH="$testdir/templates:$testdir" \
--env PS1="$(build_ps1)" \
--env VERSION="$version" \
$extra_env \
--expose 9001-9010 --publish-all \
-v "$HOME/.infix/.ash_history":/root/.ash_history \
+7 -1
View File
@@ -131,6 +131,13 @@ class Topology:
return qstrip(password) if password is not None else "admin"
def get_expected_boot(self, node):
n = self.dotg.get_node(node)
b = n[0] if n else {}
boot = b.get("expected_boot")
return _qstrip(boot)
def get_link(self, src, dst, flt=lambda _: True):
es = self.g.get_edge_data(src, dst)
for e in es.values():
@@ -150,7 +157,6 @@ class Topology:
def get_infixen(self):
return self.get_nodes(lambda _, attrs: compatible(attrs, {"requires": {"infix"}}))
def get_attr(self, name, default=None):
return _qstrip(self.dotg.get_attributes().get(name, default))
+1 -2
View File
@@ -14,8 +14,7 @@ mode-qeneth := -q $(test-dir)/virt/quad
mode-host := -t $(or $(TOPOLOGY),/etc/infamy.dot)
mode-run := -t $(BINARIES_DIR)/qemu.dot
mode := $(mode-$(TEST_MODE))
INFIX_IMAGE_ID := $(call qstrip,$(INFIX_IMAGE_ID))
INFIX_IMAGE_ID := $(call qstrip,$(INFIX_IMAGE_ID))
binaries-$(ARCH) := $(addprefix $(INFIX_IMAGE_ID),.img -disk.qcow2)
pkg-$(ARCH) := -p $(O)/images/$(addprefix $(INFIX_IMAGE_ID),.pkg)
binaries-x86_64 += OVMF.fd
+2
View File
@@ -25,6 +25,7 @@ graph "dual" {
label="{ <e1> e1 | <e2> e2 | <e3> e3 } | dut1 | { <e4> e4 | <e5> e5 | <e6> e6 }",
pos="10,18!",
provides="infix",
expected_boot="primary",
qn_console=9001,
qn_mem="384M",
qn_usb="dut1.usb"
@@ -33,6 +34,7 @@ graph "dual" {
label="{ <e1> e1 | <e2> e2 | <e3> e3 } | dut2 | { <e4> e4 | <e5> e5 | <e6> e6 }",
pos="10,12!",
provides="infix",
expected_boot="primary",
qn_console=9002,
qn_mem="384M",
qn_usb="dut2.usb"
+4
View File
@@ -24,6 +24,7 @@ graph "quad" {
label="{ <e1> e1 | <e2> e2 | <e3> e3 | <e4> e4 } | dut1 | { <e5> e5 | <e6> e6 | <e7> e7 | <e8> e8}",
pos="10,30!",
provides="infix",
expected_boot="primary",
qn_console=9001,
qn_mem="384M",
qn_usb="dut1.usb"
@@ -32,6 +33,7 @@ graph "quad" {
label="{ <e1> e1 | <e2> e2 | <e3> e3 | <e4> e4 } | dut2 | { <e5> e5 | <e6> e6 | <e7> e7 | <e8> e8}",
pos="0,20!",
provides="infix",
expected_boot="primary",
qn_console=9002,
qn_mem="384M",
qn_usb="dut2.usb"
@@ -40,6 +42,7 @@ graph "quad" {
label="{ <e1> e1 | <e2> e2 | <e3> e3 | <e4> e4 } | dut3 | { <e5> e5 | <e6> e6 | <e7> e7 | <e8> e8}",
pos="0,10!",
provides="infix",
expected_boot="primary",
qn_console=9003,
qn_mem="384M",
qn_usb="dut3.usb"
@@ -49,6 +52,7 @@ graph "quad" {
label="{ <e1> e1 | <e2> e2 | <e3> e3 | <e4> e4 } | dut4 | { <e5> e5 | <e6> e6 | <e7> e7 | <e8> e8}",
pos="10,0!",
provides="infix",
expected_boot="primary",
qn_console=9004,
qn_mem="384M",
qn_usb="dut4.usb"