Compare commits

...
Author SHA1 Message Date
Joachim WibergandGitHub 68e7167902 Merge pull request #1184 from kernelkit/releng
Release prep for v25.08.1
2025-10-03 16:22:07 +02:00
Joachim Wiberg 25a26fe7c6 doc: update release notes for v25.08.1
[skip ci]

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-10-03 16:21:28 +02:00
Joachim Wiberg 6c8bd25cee .github; only publish to latest release from main branch
[skip ci]

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-10-03 16:21:27 +02:00
Joachim Wiberg ea300137ef test: relocate logic for creating test-report.pdf from workflow
It should be possible to create test reports manually, so logically
the GitHub workflow should call a make rule in test.mk

Untested: branding, or any case where Infix is used as a BR2_EXTERNAL

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-10-03 15:03:58 +02:00
Joachim Wiberg 44e9c87376 test/infamy: silence schema download in restconf backend
NETCONF backedn already silenced.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-10-03 15:03:58 +02:00
Joachim Wiberg e708f21444 test/infamy: refactor Furl class to support list of needles
Also, add some documentation to lower barrier of entry/use.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-10-03 15:03:58 +02:00
Joachim Wiberg 3508b8d0bb test: update container test specs.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-10-03 15:03:57 +02:00
Joachim Wiberg eec867bd5f test: verify ospf neighbors in setup with non-ospf interface
Extend OSPF basic with a regression test for issue #1169

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-10-03 15:03:57 +02:00
Joachim Wiberg daa0bd81e8 board/common: ensure efi-part.vfat is saved in build tree
Fixes #1132

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-10-03 15:03:56 +02:00
Joachim WibergandGitHub 4ccc70aeee Merge pull request #1172 from kernelkit/v25.08.x-backports
Backport fixes for v25.08.1

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-09-30 15:28:57 +02:00
Richard Alpe 923443b620 doc: update changelog for v25.08.1
Signed-off-by: Richard Alpe <richard@bit42.se>
2025-09-30 10:28:57 +02:00
Richard Alpe 9ad51c31bf OSPF: skip interfaces without OSPF enabled
Fix a bug where systems with OSPF enabled on some, but not all,
interfaces would cause the OSPF iterator to fail when accessing
iface['area'], which was missing. This caused the ospf_status.py
tool to return {}, resulting in empty OSPF data in sysrepo.

Fixes #1169 Expected neighbors not shown in sysrepocfg

Signed-off-by: Richard Alpe <richard@bit42.se>
2025-09-30 10:21:10 +02:00
Joachim WibergandRichard Alpe 6272c9c46c .github: prevent duplicate builds when ci:main label is added to PRs
Add concurrency control to trigger workflow to cancel in-progress builds
when new events (like adding ci:main label) occur on the same PR.  This
prevents resource waste from duplicate builds and handles the common
workflow where developers add the ci:main label after PR creation.

Fixes #1154
2025-09-30 10:21:09 +02:00
Joachim WibergandRichard Alpe 4750f96774 cli: fix 'show ospf' commands regression, introduced in 827dc098e
Fixes #1155

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-09-30 10:21:08 +02:00
Joachim WibergandRichard Alpe 81bc143883 board/common: set show-legacy wrapper as executable
Fix #1150

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-09-30 10:21:07 +02:00
Joachim WibergandRichard Alpe 58b3fb30be test/case/infix_services: refactor mdns-allow-deny
Major refactor to redesign how listeners and traffic is started in parallel.

Fixes #1130

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-09-30 10:21:06 +02:00
Joachim WibergandRichard Alpe 6dfc305f0b test/case/infix_container: refactor container-environment
Refactor test to use httpd container instead and return ENV with a CGI.

Fixes #1131

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-09-30 10:21:06 +02:00
Mattias WalströmandRichard Alpe 8ca3d3a3ff test: Add new sanity check tests
One to verify it is the correct version on the duts and one
that check that it is the correct bootorder.
2025-09-30 10:21:05 +02:00
Joachim Wiberg 7ae4de3a17 doc: prepare for v25.08.1
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-09-30 09:30:51 +02:00
29 changed files with 510 additions and 331 deletions
+4 -9
View File
@@ -39,7 +39,7 @@ env:
jobs:
test:
name: Regression Test ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.name || inputs.name }} ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.target || inputs.target }}
runs-on: [ self-hosted, regression ]
runs-on: [self-hosted, regression]
steps:
- name: Checkout infix repo
uses: actions/checkout@v4
@@ -87,7 +87,7 @@ jobs:
fi
make test
- name: Publish Test Result for ${{ env.TARGET }}
- name: Publish Test Result for ${{ env.TARGET }}
# Ensure this runs even if Regression Test fails
if: always()
run: cat $TEST_PATH/.log/last/result-gh.md >> $GITHUB_STEP_SUMMARY
@@ -96,15 +96,10 @@ jobs:
# Ensure this runs even if Regression Test fails
if: always()
run: |
asciidoctor-pdf \
--theme $TEST_PATH/9pm/report/theme.yml \
-a pdf-fontsdir=$TEST_PATH/9pm/report/fonts \
$TEST_PATH/.log/last/report.adoc \
-o $TEST_PATH/.log/last/report.pdf
make test-dir="$(pwd)/$TEST_PATH" test-report
- name: Upload Test Report as Artifact
uses: actions/upload-artifact@v4
with:
name: test-report
path: ${{ env.TEST_PATH }}/.log/last/report.pdf
path: output/images/test-report.pdf
+5 -1
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/')
@@ -33,6 +37,6 @@ jobs:
name: "infix"
test-publish-x86_64:
if: startsWith(github.repository, 'kernelkit/')
if: ${{ github.repository_owner == 'kernelkit' && github.ref_name == 'main' }}
needs: test-run-x86_64
uses: ./.github/workflows/publish.yml
+2 -2
View File
@@ -83,7 +83,7 @@ genboot()
{
if [ -d "$bootdata" ]; then
bootimg=$(cat <<EOF
image efi-part.vfat {
image $BINARIES_DIR/efi-part.vfat {
size = $bootsize
vfat {
file EFI {
@@ -98,7 +98,7 @@ EOF
offset = $bootoffs
partition-type-uuid = U
bootable = true
image = efi-part.vfat
image = $BINARIES_DIR/efi-part.vfat
}
EOF
)
View File
+13
View File
@@ -3,6 +3,18 @@ Change Log
All notable changes to the project are documented in this file.
[v25.08.1][] - 2025-10-03
-------------------------
### Changes
- N/A
### Fixes
- Fix #1150: `show-legacy` wrapper permissions
- Fix #1155: `show ospf` commands regression
- Fix #1169: Expected OSPF neighbors not shown in `sysrepocfg` when the
system has at least one non-OSPF interface
[v25.08.0][] - 2025-09-01
-------------------------
@@ -1613,6 +1625,7 @@ Supported YANG models in addition to those used by sysrepo and netopeer:
[buildroot]: https://buildroot.org/
[UNRELEASED]: https://github.com/kernelkit/infix/compare/v25.08.0...HEAD
[v25.08.1]: https://github.com/kernelkit/infix/compare/v25.08.0...v26.08.1
[v25.08.0]: https://github.com/kernelkit/infix/compare/v25.06.1...v26.08.0
[v25.06.0]: https://github.com/kernelkit/infix/compare/v25.05.1...v26.06.0
[v25.05.1]: https://github.com/kernelkit/infix/compare/v25.05.0...v25.05.1
+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
@@ -1,7 +1,13 @@
=== OSPF Basic
==== Description
Very basic OSPF test just test that OSPF sends HELLO packets between the DUTs
and that they exchange routes, ending with a simple connectivity check.
Verifies basic OSPF functionality by configuring two routers (R1 and R2)
with OSPF on their interconnecting link. The test ensures OSPF
neighbors are established, routes are exchanged between the routers, and
end-to-end connectivity is achieved.
An end-device (HOST) is connected to R2 on an interface without OSPF enabled.
This verifies that OSPF status information remains accessible when a router
has non-OSPF interfaces.
==== Topology
ifdef::topdoc[]
@@ -19,6 +25,7 @@ endif::topdoc[]
. Set up topology and attach to target DUTs
. Configure targets
. Wait for OSPF routes
. Verify R2 OSPF neighbors with non-OSPF interface
. Test connectivity from PC:data to 192.168.200.1
+105 -63
View File
@@ -1,15 +1,24 @@
#!/usr/bin/env python3
"""
OSPF Basic
"""OSPF Basic
Verifies basic OSPF functionality by configuring two routers (R1 and R2)
with OSPF on their interconnecting link. The test ensures OSPF
neighbors are established, routes are exchanged between the routers, and
end-to-end connectivity is achieved.
An end-device (HOST) is connected to R2 on an interface without OSPF enabled.
This verifies that OSPF status information remains accessible when a router
has non-OSPF interfaces.
Very basic OSPF test just test that OSPF sends HELLO packets between the DUTs
and that they exchange routes, ending with a simple connectivity check.
"""
# TODO: Remove HOST node once Infamy supports unconnected ports in topologies
import infamy
import infamy.route as route
from infamy.util import until, parallel
def config_target1(target, data, link):
target.put_config_dict("ietf-interfaces", {
"interfaces": {
@@ -30,8 +39,8 @@ def config_target1(target, data, link):
"ipv4": {
"forwarding": True,
"address": [{
"ip": "192.168.50.1",
"prefix-length": 24
"ip": "192.168.50.1",
"prefix-length": 24
}]
}
},
@@ -40,8 +49,8 @@ def config_target1(target, data, link):
"enabled": True,
"ipv4": {
"address": [{
"ip": "192.168.100.1",
"prefix-length": 32
"ip": "192.168.100.1",
"prefix-length": 32
}]
}
}
@@ -56,38 +65,34 @@ def config_target1(target, data, link):
target.put_config_dict("ietf-routing", {
"routing": {
"control-plane-protocols": {
"control-plane-protocol": [
{
"type": "infix-routing:static",
"name": "default",
"static-routes": {
"ipv4": {
"route": [{
"destination-prefix": "192.168.33.1/32",
"next-hop": {
"special-next-hop": "blackhole"
}
}]
}
"control-plane-protocol": [{
"type": "infix-routing:static",
"name": "default",
"static-routes": {
"ipv4": {
"route": [{
"destination-prefix": "192.168.33.1/32",
"next-hop": {
"special-next-hop": "blackhole"
}
}]
}
},
{
}
}, {
"type": "infix-routing:ospfv2",
"name": "default",
"ospf": {
"redistribute": {
"redistribute": [{
"protocol": "static"
},
{
}, {
"protocol": "connected"
}]
},
"areas": {
"area": [{
"area-id": "0.0.0.0",
"interfaces":
{
"interfaces": {
"interface": [{
"enabled": True,
"name": link,
@@ -103,35 +108,43 @@ def config_target1(target, data, link):
}
})
def config_target2(target, link):
def config_target2(target, link, data):
target.put_config_dict("ietf-interfaces", {
"interfaces": {
"interface": [
{
"name": link,
"enabled": True,
"ipv4": {
"forwarding": True,
"address": [{
"ip": "192.168.50.2",
"prefix-length": 24
}]
}
},
{
"name": "lo",
"enabled": True,
"forwarding": True,
"ipv4": {
"address": [{
"ip": "192.168.200.1",
"prefix-length": 32
}]
}
}
]
}
})
"interfaces": {
"interface": [{
"name": link,
"enabled": True,
"ipv4": {
"forwarding": True,
"address": [{
"ip": "192.168.50.2",
"prefix-length": 24
}]
}
}, {
"name": data,
"enabled": True,
"ipv4": {
"forwarding": True,
"address": [{
"ip": "192.168.60.1",
"prefix-length": 24
}]
}
}, {
"name": "lo",
"enabled": True,
"forwarding": True,
"ipv4": {
"address": [{
"ip": "192.168.200.1",
"prefix-length": 32
}]
}
}]
}
})
target.put_config_dict("ietf-system", {
"system": {
@@ -141,8 +154,7 @@ def config_target2(target, link):
target.put_config_dict("ietf-routing", {
"routing": {
"control-plane-protocols": {
"control-plane-protocol": [
{
"control-plane-protocol": [{
"type": "infix-routing:ospfv2",
"name": "default",
"ospf": {
@@ -154,7 +166,7 @@ def config_target2(target, link):
"areas": {
"area": [{
"area-id": "0.0.0.0",
"interfaces":{
"interfaces": {
"interface": [{
"enabled": True,
"name": link,
@@ -165,31 +177,61 @@ def config_target2(target, link):
}]
}
}
}
]
}]
}
}
})
def config_host(target, link):
target.put_config_dict("ietf-interfaces", {
"interfaces": {
"interface": [{
"name": link,
"enabled": True,
"ipv4": {
"address": [{
"ip": "192.168.60.2",
"prefix-length": 24
}]
}
}]
}
})
target.put_config_dict("ietf-system", {
"system": {
"hostname": "HOST"
}
})
with infamy.Test() as test:
with test.step("Set up topology and attach to target DUTs"):
env = infamy.Env()
R1 = env.attach("R1", "mgmt")
R2 = env.attach("R2", "mgmt")
HOST = env.attach("HOST", "mgmt")
with test.step("Configure targets"):
_, R1data = env.ltop.xlate("R1", "data")
_, R2link = env.ltop.xlate("R2", "link")
_, R1link= env.ltop.xlate("R1", "link")
_, R1link = env.ltop.xlate("R1", "link")
_, R2data = env.ltop.xlate("R2", "data")
_, HOSTlink = env.ltop.xlate("HOST", "link")
parallel(config_target1(R1, R1data, R1link),
config_target2(R2, R2link))
config_target2(R2, R2link, R2data),
config_host(HOST, HOSTlink))
with test.step("Wait for OSPF routes"):
print("Waiting for OSPF routes..")
until(lambda: route.ipv4_route_exist(R1, "192.168.200.1/32", proto="ietf-ospf:ospfv2"), attempts=200)
until(lambda: route.ipv4_route_exist(R2, "192.168.100.1/32", proto="ietf-ospf:ospfv2"), attempts=200)
until(lambda: route.ipv4_route_exist(R2, "192.168.33.1/32", proto="ietf-ospf:ospfv2"), attempts=200)
with test.step("Verify R2 OSPF neighbors with non-OSPF interface"):
# Regression test for #1169
assert route.ospf_has_neighbors(R2)
with test.step("Test connectivity from PC:data to 192.168.200.1"):
_, hport0 = env.ltop.xlate("PC", "data")
with infamy.IsolatedMacVlan(hport0) as ns0:
+14 -6
View File
@@ -8,26 +8,34 @@ graph "2x2" {
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
PC [
label="PC | { <mgmt1> mgmt1 | <data> data | <mgmt2> mgmt2 }",
pos="20,80!",
label="PC | { <mgmt1> mgmt1 | <data> data | <mgmt2> mgmt2 | <mgmt3> mgmt3 }",
pos="20,30!",
requires="controller",
];
R1 [
label="{ <mgmt> mgmt | <data> data | <link> link} | R1 \n 192.168.100.1/32 \n(lo)",
pos="250,80!",
pos="160,60!",
requires="infix",
];
R2 [
label="{ <link> link | <mgmt> mgmt | <data> data } | R2 \n 192.168.200.1/32 \n(lo)",
pos="250,30!",
pos="160,30!",
requires="infix",
];
HOST [
label="{ <link> link | <mgmt> mgmt } | HOST \n end-device",
pos="153,0!",
requires="infix",
];
PC:mgmt1 -- R1:mgmt [requires="mgmt", color="lightgray"]
PC:mgmt2 -- R2:mgmt [requires="mgmt", color="lightgray"]
PC:data -- R1:data [color="black", headlabel="192.168.10.1/24", taillabel="192.168.10.2/24", fontcolor="black"]
PC:mgmt3 -- HOST:mgmt [requires="mgmt", color="lightgray"]
PC:data -- R1:data [color="black", headlabel="192.168.10.1/24", taillabel="192.168.10.2/24", labeldistance=6, fontcolor="black"]
R1:link -- R2:link [headlabel="192.168.50.2/24", taillabel="192.168.50.1/24", labeldistance=1, fontcolor="black", color="black"]
R2:data -- HOST:link [headlabel="192.168.60.2/24", taillabel="192.168.60.1/24", labeldistance=1, fontcolor="black", color="black"]
}
+62 -37
View File
@@ -3,74 +3,99 @@
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Title: 2x2 Pages: 1 -->
<svg width="713pt" height="198pt"
viewBox="0.00 0.00 713.06 198.01" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 194.01)">
<svg width="720pt" height="306pt"
viewBox="0.00 0.00 720.00 306.08" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 302.52)">
<title>2x2</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-194.01 709.06,-194.01 709.06,4 -4,4"/>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-302.52 717.06,-302.52 717.06,4 -4,4"/>
<!-- PC -->
<g id="node1" class="node">
<title>PC</title>
<polygon fill="none" stroke="black" points="0,-120.51 0,-189.51 91,-189.51 91,-120.51 0,-120.51"/>
<text text-anchor="middle" x="16.5" y="-151.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">PC</text>
<polyline fill="none" stroke="black" points="33,-120.51 33,-189.51 "/>
<polygon fill="none" stroke="black" points="0,-97.51 0,-189.51 91,-189.51 91,-97.51 0,-97.51"/>
<text text-anchor="middle" x="16.5" y="-139.81" font-family="DejaVu Sans Mono, Book" font-size="14.00">PC</text>
<polyline fill="none" stroke="black" points="33,-97.51 33,-189.51 "/>
<text text-anchor="middle" x="62" y="-174.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt1</text>
<polyline fill="none" stroke="black" points="33,-166.51 91,-166.51 "/>
<text text-anchor="middle" x="62" y="-151.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">data</text>
<polyline fill="none" stroke="black" points="33,-143.51 91,-143.51 "/>
<text text-anchor="middle" x="62" y="-128.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt2</text>
<polyline fill="none" stroke="black" points="33,-120.51 91,-120.51 "/>
<text text-anchor="middle" x="62" y="-105.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt3</text>
</g>
<!-- R1 -->
<g id="node2" class="node">
<title>R1</title>
<polygon fill="none" stroke="black" points="490.06,-120.51 490.06,-189.51 705.06,-189.51 705.06,-120.51 490.06,-120.51"/>
<text text-anchor="middle" x="515.06" y="-174.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="490.06,-166.51 540.06,-166.51 "/>
<text text-anchor="middle" x="515.06" y="-151.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">data</text>
<polyline fill="none" stroke="black" points="490.06,-143.51 540.06,-143.51 "/>
<text text-anchor="middle" x="515.06" y="-128.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">link</text>
<polyline fill="none" stroke="black" points="540.06,-120.51 540.06,-189.51 "/>
<text text-anchor="middle" x="622.56" y="-166.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">R1 </text>
<text text-anchor="middle" x="622.56" y="-151.31" font-family="DejaVu Sans Mono, Book" font-size="14.00"> 192.168.100.1/32 </text>
<text text-anchor="middle" x="622.56" y="-136.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">(lo)</text>
<polygon fill="none" stroke="black" points="498.06,-229.02 498.06,-298.02 713.06,-298.02 713.06,-229.02 498.06,-229.02"/>
<text text-anchor="middle" x="523.06" y="-282.82" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="498.06,-275.02 548.06,-275.02 "/>
<text text-anchor="middle" x="523.06" y="-259.82" font-family="DejaVu Sans Mono, Book" font-size="14.00">data</text>
<polyline fill="none" stroke="black" points="498.06,-252.02 548.06,-252.02 "/>
<text text-anchor="middle" x="523.06" y="-236.82" font-family="DejaVu Sans Mono, Book" font-size="14.00">link</text>
<polyline fill="none" stroke="black" points="548.06,-229.02 548.06,-298.02 "/>
<text text-anchor="middle" x="630.56" y="-274.82" font-family="DejaVu Sans Mono, Book" font-size="14.00">R1 </text>
<text text-anchor="middle" x="630.56" y="-259.82" font-family="DejaVu Sans Mono, Book" font-size="14.00"> 192.168.100.1/32 </text>
<text text-anchor="middle" x="630.56" y="-244.82" font-family="DejaVu Sans Mono, Book" font-size="14.00">(lo)</text>
</g>
<!-- PC&#45;&#45;R1 -->
<g id="edge1" class="edge">
<title>PC:mgmt1&#45;&#45;R1:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M91.5,-178.01C91.5,-178.01 489.56,-178.01 489.56,-178.01"/>
<path fill="none" stroke="lightgray" stroke-width="2" d="M91.5,-178.51C91.5,-178.51 497.56,-286.52 497.56,-286.52"/>
</g>
<!-- PC&#45;&#45;R1 -->
<g id="edge3" class="edge">
<g id="edge4" class="edge">
<title>PC:data&#45;&#45;R1:data</title>
<path fill="none" stroke="black" stroke-width="2" d="M91.5,-155.01C91.5,-155.01 489.56,-155.01 489.56,-155.01"/>
<text text-anchor="middle" x="430.56" y="-158.81" font-family="DejaVu Serif, Book" font-size="14.00">192.168.10.1/24</text>
<text text-anchor="middle" x="150.5" y="-158.81" font-family="DejaVu Serif, Book" font-size="14.00">192.168.10.2/24</text>
<path fill="none" stroke="black" stroke-width="2" d="M91.5,-155.51C91.5,-155.51 497.56,-263.52 497.56,-263.52"/>
<text text-anchor="middle" x="438.49" y="-270.35" font-family="DejaVu Serif, Book" font-size="14.00">192.168.10.1/24</text>
<text text-anchor="middle" x="150.57" y="-141.29" font-family="DejaVu Serif, Book" font-size="14.00">192.168.10.2/24</text>
</g>
<!-- R2 -->
<g id="node3" class="node">
<title>R2</title>
<polygon fill="none" stroke="black" points="490.06,-0.5 490.06,-69.5 705.06,-69.5 705.06,-0.5 490.06,-0.5"/>
<text text-anchor="middle" x="515.06" y="-54.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">link</text>
<polyline fill="none" stroke="black" points="490.06,-46.5 540.06,-46.5 "/>
<text text-anchor="middle" x="515.06" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="490.06,-23.5 540.06,-23.5 "/>
<text text-anchor="middle" x="515.06" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data</text>
<polyline fill="none" stroke="black" points="540.06,-0.5 540.06,-69.5 "/>
<text text-anchor="middle" x="622.56" y="-46.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">R2 </text>
<text text-anchor="middle" x="622.56" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00"> 192.168.200.1/32 </text>
<text text-anchor="middle" x="622.56" y="-16.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">(lo)</text>
<polygon fill="none" stroke="black" points="498.06,-109.01 498.06,-178.01 713.06,-178.01 713.06,-109.01 498.06,-109.01"/>
<text text-anchor="middle" x="523.06" y="-162.81" font-family="DejaVu Sans Mono, Book" font-size="14.00">link</text>
<polyline fill="none" stroke="black" points="498.06,-155.01 548.06,-155.01 "/>
<text text-anchor="middle" x="523.06" y="-139.81" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="498.06,-132.01 548.06,-132.01 "/>
<text text-anchor="middle" x="523.06" y="-116.81" font-family="DejaVu Sans Mono, Book" font-size="14.00">data</text>
<polyline fill="none" stroke="black" points="548.06,-109.01 548.06,-178.01 "/>
<text text-anchor="middle" x="630.56" y="-154.81" font-family="DejaVu Sans Mono, Book" font-size="14.00">R2 </text>
<text text-anchor="middle" x="630.56" y="-139.81" font-family="DejaVu Sans Mono, Book" font-size="14.00"> 192.168.200.1/32 </text>
<text text-anchor="middle" x="630.56" y="-124.81" font-family="DejaVu Sans Mono, Book" font-size="14.00">(lo)</text>
</g>
<!-- PC&#45;&#45;R2 -->
<g id="edge2" class="edge">
<title>PC:mgmt2&#45;&#45;R2:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M91.5,-132.01C91.5,-132.01 489.56,-35 489.56,-35"/>
<path fill="none" stroke="lightgray" stroke-width="2" d="M91.5,-131.51C91.5,-131.51 497.56,-143.51 497.56,-143.51"/>
</g>
<!-- HOST -->
<g id="node4" class="node">
<title>HOST</title>
<polygon fill="none" stroke="black" points="499.05,-0.5 499.05,-46.5 656.05,-46.5 656.05,-0.5 499.05,-0.5"/>
<text text-anchor="middle" x="524.05" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">link</text>
<polyline fill="none" stroke="black" points="499.05,-23.5 549.05,-23.5 "/>
<text text-anchor="middle" x="524.05" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="549.05,-0.5 549.05,-46.5 "/>
<text text-anchor="middle" x="602.55" y="-27.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">HOST </text>
<text text-anchor="middle" x="602.55" y="-12.3" font-family="DejaVu Sans Mono, Book" font-size="14.00"> end&#45;device</text>
</g>
<!-- PC&#45;&#45;HOST -->
<g id="edge3" class="edge">
<title>PC:mgmt3&#45;&#45;HOST:mgmt</title>
<path fill="none" stroke="lightgray" stroke-width="2" d="M91.5,-108.51C91.5,-108.51 498.55,-11.5 498.55,-11.5"/>
</g>
<!-- R1&#45;&#45;R2 -->
<g id="edge4" class="edge">
<g id="edge5" class="edge">
<title>R1:link&#45;&#45;R2:link</title>
<path fill="none" stroke="black" stroke-width="2" d="M514.56,-120.01C514.56,-120.01 514.56,-70 514.56,-70"/>
<text text-anchor="middle" x="518.78" y="-75.36" font-family="DejaVu Serif, Book" font-size="14.00">192.168.50.2/24</text>
<text text-anchor="middle" x="510.33" y="-107.25" font-family="DejaVu Serif, Book" font-size="14.00">192.168.50.1/24</text>
<path fill="none" stroke="black" stroke-width="2" d="M522.56,-228.52C522.56,-228.52 522.56,-178.51 522.56,-178.51"/>
<text text-anchor="middle" x="526.78" y="-183.88" font-family="DejaVu Serif, Book" font-size="14.00">192.168.50.2/24</text>
<text text-anchor="middle" x="518.33" y="-215.76" font-family="DejaVu Serif, Book" font-size="14.00">192.168.50.1/24</text>
</g>
<!-- R2&#45;&#45;HOST -->
<g id="edge6" class="edge">
<title>R2:data&#45;&#45;HOST:link</title>
<path fill="none" stroke="black" stroke-width="2" d="M522.56,-108.51C522.56,-108.51 523.55,-46.5 523.55,-46.5"/>
<text text-anchor="middle" x="527.63" y="-51.93" font-family="DejaVu Serif, Book" font-size="14.00">192.168.60.2/24</text>
<text text-anchor="middle" x="518.48" y="-95.68" font-family="DejaVu Serif, Book" font-size="14.00">192.168.60.1/24</text>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

@@ -26,7 +26,6 @@ endif::topdoc[]
. Set hostname to 'container-host'
. Create enabled container from bundled OCI image
. Verify container has started
. Let container settle
. Disable container
. Verify container has stopped
. Re-enable container
@@ -1,14 +1,11 @@
=== Container environment variables
==== Description
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
==== Topology
ifdef::topdoc[]
@@ -25,11 +22,10 @@ endif::topdoc[]
==== Test sequence
. Set up topology and attach to target DUT
. Configure data interface with static IPv4
. Create container with environment variables
. Set up container with environment variables
. Verify container has started
. Verify environment variables are available via HTTP
. Verify basic connectivity to data interface
. Verify environment variables in HTTP response
. Verify environment variables in CGI response
<<<
@@ -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 \
+55 -9
View File
@@ -1,26 +1,72 @@
"""Fugly URL fetcher"""
"""Simple HTTP URL fetcher and content checker
This module provides the a lightweight wrapper around urllib for testing
HTTP endpoints. It's designed specifically for test scenarios where you
need to:
- Fetch HTTP content and verify it contains expected strings
- Perform checks from within network namespaces
- Validate multiple content patterns in a single request
- Handle network errors gracefully in test environments
Example usage:
# Single needle check
url = Furl("http://example.com/api")
if url.check("success"):
print("API returned success")
# Multiple needle check (all must match)
if url.check(["user: alice", "status: active", "role: admin"]):
print("All user details found")
# Check from network namespace
with IsolatedMacVlan("eth0") as ns:
if url.nscheck(ns, "expected content"):
print("Content verified from namespace")
"""
import urllib.error
import urllib.parse
import urllib.request
class Furl:
"""Furl wraps urllib in a way similar to curl"""
def __init__(self, url):
"""Create new URL checker"""
self.url = urllib.parse.quote(url, safe='/:')
def check(self, needle, timeout=10):
"""Connect to web server URL, fetch body and check for needle"""
def check(self, needles, timeout=10):
"""Connect to web server URL, fetch body and check for needle(s)
Args:
needles: String or list of strings to search for in response
timeout: Request timeout in seconds
Returns:
bool: True if all needles found in response, False otherwise
"""
# Backwards compat, make needles a list for uniform processing
if isinstance(needles, str):
needles = [needles]
try:
with urllib.request.urlopen(self.url, timeout=timeout) as response:
text = response.read().decode('utf-8')
#print(text)
return needle in text
except urllib.error.URLError as _:
return all(needle in text for needle in needles)
except urllib.error.URLError:
return False
except ConnectionResetError:
return False
def nscheck(self, netns, needle):
""""Call check() from netns"""
return netns.call(lambda: self.check(needle))
def nscheck(self, netns, needles):
""""Call check() from netns
Args:
netns: Network namespace to call from
needles: String or list of strings to search for in response
Returns:
bool: True if all needles found in response, False otherwise
"""
return netns.call(lambda: self.check(needles))
-1
View File
@@ -150,7 +150,6 @@ class Device(Transport):
url = f"{self.yang_url}/{schema_name}"
data = self._get_raw(url=url, parse=False)
sys.stdout.write(f"Downloading YANG model {schema_name} ...\r\033[K")
data = data.decode('utf-8')
with open(f"{yangdir}/{schema_name}", 'w') as json_file:
json_file.write(data)
+10
View File
@@ -114,3 +114,13 @@ def ospf_is_area_nssa(target, area_id):
return True
return False
def ospf_has_neighbors(target):
ospf = _get_ospf_status(target)
for area in ospf.get("areas", {}).get("area", []):
for interface in area.get("interfaces", {}).get("interface", []):
if interface.get("neighbors"):
return True
return False
+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))
+16 -7
View File
@@ -1,9 +1,10 @@
base-dir := $(lastword $(subst :, ,$(BR2_EXTERNAL)))
test-dir := $(BR2_EXTERNAL_INFIX_PATH)/test
test-dir ?= $(BR2_EXTERNAL_INFIX_PATH)/test
ninepm := $(BR2_EXTERNAL_INFIX_PATH)/test/9pm/9pm.py
NINEPM_PROJ_CONF ?= $(BR2_EXTERNAL_INFIX_PATH)/test/9pm-proj.yaml
spec-dir := $(test-dir)/spec
test-specification := $(O)/images/test-specification.pdf
test-specification := $(BINARIES_DIR)/test-specification.pdf
test-report := $(BINARIES_DIR)/test-report.pdf
UNIT_TESTS ?= $(test-dir)/case/all-repo.yaml $(test-dir)/case/all-unit.yaml
TESTS ?= $(test-dir)/case/all.yaml
@@ -14,8 +15,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
@@ -34,9 +34,18 @@ test-sh:
test-spec:
@esc_infix_name="$(echo $(INFIX_NAME) | sed 's/\//\\\//g')"; \
sed 's/{REPLACE}/$(subst ",,$(esc_infix_name)) $(INFIX_VERSION)/' $(spec-dir)/Readme.adoc.in > $(spec-dir)/Readme.adoc
sed 's/{REPLACE}/$(subst ",,$(esc_infix_name)) $(INFIX_VERSION)/' \
$(spec-dir)/Readme.adoc.in > $(spec-dir)/Readme.adoc
@$(spec-dir)/generate_spec.py -s $(test-dir)/case/all.yaml -r $(BR2_EXTERNAL_INFIX_PATH)
@asciidoctor-pdf --failure-level INFO --theme $(spec-dir)/theme.yml -a pdf-fontsdir=$(spec-dir)/fonts -o $(test-specification) $(spec-dir)/Readme.adoc
@asciidoctor-pdf --failure-level INFO --theme $(spec-dir)/theme.yml \
-a pdf-fontsdir=$(spec-dir)/fonts \
-o $(test-specification) $(spec-dir)/Readme.adoc
test-report:
asciidoctor-pdf --theme $(spec-dir)/theme.yml \
-a logo="image:$(LOGO)" \
-a pdf-fontsdir=$(spec-dir)/fonts \
-o $(test-report) $(test-dir)/.log/last/report.adoc
# Unit tests run with random (-r) hostname and container name to
# prevent race conditions when running in CI environments.
+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"