diff --git a/src/statd/python/yanger/ietf_system.py b/src/statd/python/yanger/ietf_system.py index 671c4beca0..c94de06c38 100644 --- a/src/statd/python/yanger/ietf_system.py +++ b/src/statd/python/yanger/ietf_system.py @@ -266,6 +266,8 @@ def add_software(out): installer_status = HOST.run_json(["rauc-installation-status"], {}) if installer_status.get("operation", {}): installer["operation"] = installer_status["operation"] + if installer_status.get("last-error"): + installer["last-error"] = installer_status["last-error"] if "progress" in installer_status: progress = {} diff --git a/test/case/dhcp/server_boot/test.py b/test/case/dhcp/server_boot/test.py index d4f580cfec..5c6880d16e 100755 --- a/test/case/dhcp/server_boot/test.py +++ b/test/case/dhcp/server_boot/test.py @@ -80,7 +80,7 @@ until(lambda: client.lease() == (SERVER, SUBNET_FILE)) with test.step("Verify static host gets its own boot file"): - ns.run(["ip", "link", "set", "iface", "address", HOST_MAC]) + until(lambda: ns.run(["ip", "link", "set", "iface", "address", HOST_MAC], check=True)) until(lambda: client.lease() == (SERVER, HOST_FILE)) with test.step("Remove subnet and host boot, verify client falls back to global boot file and server"): diff --git a/test/case/interfaces/Readme.adoc b/test/case/interfaces/Readme.adoc index cee1f2aacb..23d7c34564 100644 --- a/test/case/interfaces/Readme.adoc +++ b/test/case/interfaces/Readme.adoc @@ -102,6 +102,10 @@ include::igmp_vlan/Readme.adoc[] <<< +include::igmp_basic_3duts/Readme.adoc[] + +<<< + include::static_multicast_filters/Readme.adoc[] <<< diff --git a/test/case/interfaces/igmp.yaml b/test/case/interfaces/igmp.yaml index 232e60437f..9a4d7ad52b 100644 --- a/test/case/interfaces/igmp.yaml +++ b/test/case/interfaces/igmp.yaml @@ -5,5 +5,8 @@ - name: IGMP VLAN case: igmp_vlan/test.py +- name: IGMP Basic 3 DUTs + case: igmp_basic_3duts/test.py + - name: Static Multicast Filters case: static_multicast_filters/test.py diff --git a/test/case/interfaces/wifi_station_from_scan/Readme.adoc b/test/case/interfaces/igmp_basic_3duts/Readme.adoc similarity index 100% rename from test/case/interfaces/wifi_station_from_scan/Readme.adoc rename to test/case/interfaces/igmp_basic_3duts/Readme.adoc diff --git a/test/case/interfaces/igmp_basic_3duts/test.adoc b/test/case/interfaces/igmp_basic_3duts/test.adoc new file mode 100644 index 0000000000..f4921303fb --- /dev/null +++ b/test/case/interfaces/igmp_basic_3duts/test.adoc @@ -0,0 +1,49 @@ +=== IGMP Basic 3 DUTs + +ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/igmp_basic_3duts] + +==== Description + +The same check as IGMP basic, spread over three bridges in a chain so +that each DUT only needs a single port facing the host. A multicast sender sits +behind dut1, a non-member behind dut2, and the receiver behind dut3. +The membership report from the receiver must propagate through dut3 +and dut2 up to the querier on dut1 before dut2 prunes the group from +its own host port. + +.... + 10.0.0.1 + .-----------. .----------------. .-----------. + | dut1 | | dut2 | | dut3 | + '-data-link-'-------'-west-data-east-'-------'-link-data-' + | | | 10.0.0.0/24 + .-data1-. .-data2-. .-data3-. + | msend | | !memb | | mrecv | + '-------' '-------' '-------' + .2 .3 .4 + HOST +.... + +With no membership the group 224.1.1.1 is flooded to both `!memb` and +`mrecv`. Once `mrecv` joins, dut2 and dut1 learn the group on their +links toward dut3, and `!memb` no longer receives it. + +==== Topology + +image::topology.svg[IGMP Basic 3 DUTs topology, align=center, scaledwidth=75%] + +==== Sequence + +. Set up topology and attach to target DUTs +. Configure bridges with IGMP snooping on all DUTs, querier on dut1 with 10.0.0.1/24 +. Verify host:data1, host:data2 and host:data3 can reach 10.0.0.1 +. Start multicast sender on host:data1, group 224.1.1.1 +. Verify group 224.1.1.1 is flooded to host:data2 and host:data3 +. Join multicast group 224.1.1.1 on host:data3 +. Verify dut3 has learned group 224.1.1.1 on dut3:data +. Verify dut2 has learned group 224.1.1.1 on dut2:east +. Verify dut1 has learned group 224.1.1.1 on dut1:link +. Verify group 224.1.1.1 is still received on host:data3 +. Verify group 224.1.1.1 is no longer received on host:data2 + + diff --git a/test/case/interfaces/igmp_basic_3duts/test.py b/test/case/interfaces/igmp_basic_3duts/test.py new file mode 100755 index 0000000000..e33a307405 --- /dev/null +++ b/test/case/interfaces/igmp_basic_3duts/test.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +"""IGMP basic with three DUTs + +The same check as IGMP basic, spread over three bridges in a chain so +that each DUT only needs a single port facing the host. A multicast sender sits +behind dut1, a non-member behind dut2, and the receiver behind dut3. +The membership report from the receiver must propagate through dut3 +and dut2 up to the querier on dut1 before dut2 prunes the group from +its own host port. + +.... + 10.0.0.1 + .-----------. .----------------. .-----------. + | dut1 | | dut2 | | dut3 | + '-data-link-'-------'-west-data-east-'-------'-link-data-' + | | | 10.0.0.0/24 + .-data1-. .-data2-. .-data3-. + | msend | | !memb | | mrecv | + '-------' '-------' '-------' + .2 .3 .4 + HOST +.... + +With no membership the group 224.1.1.1 is flooded to both `!memb` and +`mrecv`. Once `mrecv` joins, dut2 and dut1 learn the group on their +links toward dut3, and `!memb` no longer receives it. +""" + +import infamy +import infamy.iface as iface +import infamy.multicast as mcast +from infamy.util import until, parallel + +query_interval = 4 + + +def bridge(ports, address=None): + br0 = { + "name": "br0", + "enabled": True, + "type": "infix-if-type:bridge", + "bridge": { + "multicast": { + "snooping": True, + "query-interval": query_interval + } + } + } + if address: + br0["ipv4"] = {"address": [{"ip": address, "prefix-length": 24}]} + + return { + "ietf-interfaces": { + "interfaces": { + "interface": [br0] + [{ + "name": port, + "enabled": True, + "bridge-port": {"bridge": "br0"} + } for port in ports] + } + } + } + + +with infamy.Test() as test: + with test.step("Set up topology and attach to target DUTs"): + env = infamy.Env() + dut1, dut2, dut3 = parallel(lambda: env.attach("dut1", "mgmt"), + lambda: env.attach("dut2", "mgmt"), + lambda: env.attach("dut3", "mgmt")) + _, d1data = env.ltop.xlate("dut1", "data") + _, d1link = env.ltop.xlate("dut1", "link") + _, d2data = env.ltop.xlate("dut2", "data") + _, d2west = env.ltop.xlate("dut2", "west") + _, d2east = env.ltop.xlate("dut2", "east") + _, d3data = env.ltop.xlate("dut3", "data") + _, d3link = env.ltop.xlate("dut3", "link") + + _, hsend = env.ltop.xlate("host", "data1") + _, hnojoin = env.ltop.xlate("host", "data2") + _, hreceive = env.ltop.xlate("host", "data3") + + with test.step("Configure bridges with IGMP snooping on all DUTs, querier on dut1 with 10.0.0.1/24"): + parallel(lambda: dut1.put_config_dicts(bridge([d1data, d1link], "10.0.0.1")), + lambda: dut2.put_config_dicts(bridge([d2data, d2west, d2east])), + lambda: dut3.put_config_dicts(bridge([d3data, d3link]))) + + with infamy.IsolatedMacVlan(hsend) as send_ns, \ + infamy.IsolatedMacVlan(hnojoin) as nojoin_ns, \ + infamy.IsolatedMacVlan(hreceive) as receive_ns: + + with test.step("Verify host:data1, host:data2 and host:data3 can reach 10.0.0.1"): + send_ns.addip("10.0.0.2") + nojoin_ns.addip("10.0.0.3") + receive_ns.addip("10.0.0.4") + parallel(lambda: send_ns.must_reach("10.0.0.1"), + lambda: nojoin_ns.must_reach("10.0.0.1"), + lambda: receive_ns.must_reach("10.0.0.1")) + + with test.step("Start multicast sender on host:data1, group 224.1.1.1"): + with mcast.MCastSender(send_ns, "224.1.1.1"): + with test.step("Verify group 224.1.1.1 is flooded to host:data2 and host:data3"): + parallel(lambda: nojoin_ns.must_receive("ip dst 224.1.1.1"), + lambda: receive_ns.must_receive("ip dst 224.1.1.1")) + + with test.step("Join multicast group 224.1.1.1 on host:data3"): + with mcast.MCastReceiver(receive_ns, "224.1.1.1"): + with test.step("Verify dut3 has learned group 224.1.1.1 on dut3:data"): + until(lambda: iface.exist_bridge_multicast_filter(dut3, "224.1.1.1", d3data, "br0"), attempts=10) + + with test.step("Verify dut2 has learned group 224.1.1.1 on dut2:east"): + until(lambda: iface.exist_bridge_multicast_filter(dut2, "224.1.1.1", d2east, "br0"), attempts=10) + + with test.step("Verify dut1 has learned group 224.1.1.1 on dut1:link"): + until(lambda: iface.exist_bridge_multicast_filter(dut1, "224.1.1.1", d1link, "br0"), attempts=10) + + with test.step("Verify group 224.1.1.1 is still received on host:data3"): + receive_ns.must_receive("ip dst 224.1.1.1") + + with test.step("Verify group 224.1.1.1 is no longer received on host:data2"): + nojoin_ns.must_not_receive("ip dst 224.1.1.1") + + test.succeed() diff --git a/test/case/interfaces/igmp_basic_3duts/topology.dot b/test/case/interfaces/igmp_basic_3duts/topology.dot new file mode 100644 index 0000000000..93a2852a74 --- /dev/null +++ b/test/case/interfaces/igmp_basic_3duts/topology.dot @@ -0,0 +1,43 @@ +graph "igmp-basic-3duts" { + layout="neato"; + overlap="false"; + esep="+40"; + + node [shape=record, fontname="DejaVu Sans Mono, Book"]; + edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"]; + + host [ + label="host | { mgmt1 | data1 | mgmt2 | data2 | mgmt3 | data3 }", + pos="0,10!", + requires="controller", + ]; + + dut1 [ + label="{ mgmt | data | link } | dut1 \n\n(br0)\n10.0.0.1/24", + pos="12,20!", + requires="infix", + ]; + + dut2 [ + label="{ west | mgmt | data | east } | dut2 \n\n(br0)", + pos="12,10!", + requires="infix", + ]; + + dut3 [ + label="{ link | mgmt | data } | dut3 \n\n(br0)", + pos="12,0!", + requires="infix", + ]; + + host:mgmt1 -- dut1:mgmt [requires="mgmt", color="lightgray"] + host:mgmt2 -- dut2:mgmt [requires="mgmt", color="lightgray"] + host:mgmt3 -- dut3:mgmt [requires="mgmt", color="lightgray"] + + host:data1 -- dut1:data [color=black, fontcolor=black, taillabel="10.0.0.2/24"] + host:data2 -- dut2:data [color=black, fontcolor=black, taillabel="10.0.0.3/24"] + host:data3 -- dut3:data [color=black, fontcolor=black, taillabel="10.0.0.4/24"] + + dut1:link -- dut2:west [color=black] + dut2:east -- dut3:link [color=black] +} diff --git a/test/case/interfaces/igmp_basic_3duts/topology.svg b/test/case/interfaces/igmp_basic_3duts/topology.svg new file mode 100644 index 0000000000..82e44582df --- /dev/null +++ b/test/case/interfaces/igmp_basic_3duts/topology.svg @@ -0,0 +1,115 @@ + + + + + + +igmp-basic-3duts + + + +host + +host + +mgmt1 + +data1 + +mgmt2 + +data2 + +mgmt3 + +data3 + + + +dut1 + +mgmt + +data + +link + +dut1 +(br0) +10.0.0.1/24 + + + +host:mgmt1--dut1:mgmt + + + + +host:data1--dut1:data + +10.0.0.2/24 + + + +dut2 + +west + +mgmt + +data + +east + +dut2 +(br0) + + + +host:mgmt2--dut2:mgmt + + + + +host:data2--dut2:data + +10.0.0.3/24 + + + +dut3 + +link + +mgmt + +data + +dut3 +(br0) + + + +host:mgmt3--dut3:mgmt + + + + +host:data3--dut3:data + +10.0.0.4/24 + + + +dut1:link--dut2:west + + + + +dut2:east--dut3:link + + + + diff --git a/test/case/interfaces/wifi.yaml b/test/case/interfaces/wifi.yaml index 55efbeaf47..824e2fa697 100644 --- a/test/case/interfaces/wifi.yaml +++ b/test/case/interfaces/wifi.yaml @@ -17,8 +17,5 @@ - name: WiFi repeater with two SSIDs in VLANs over a 4-address (WDS) backhaul case: wifi_wds_repeater/test.py -- name: WiFi station set up from a scan-only interface - case: wifi_station_from_scan/test.py - - name: WiFi channel survey on a connected radio case: wifi_channel_survey/test.py diff --git a/test/case/interfaces/wifi_station_from_scan/test.adoc b/test/case/interfaces/wifi_station_from_scan/test.adoc deleted file mode 100644 index 3eb98e127d..0000000000 --- a/test/case/interfaces/wifi_station_from_scan/test.adoc +++ /dev/null @@ -1,32 +0,0 @@ -=== WiFi station set up from a scan-only interface - -ifdef::topdoc[:imagesdir: {topdoc}../../test/case/interfaces/wifi_station_from_scan] - -==== Description - -A WiFi interface with only a radio, no station or access point, is in -scan-only mode. That is how the factory configuration of boards with a -built-in radio ships, so the usual way to get online is to add the station -settings to that existing interface. The connection has to come up from -that change alone, without a reboot or a service restart. - -Topology: -.... - host ==(mgmt)== ap ))) ~ cell ~ ((( station ==(mgmt)== host -.... - -==== Topology - -image::topology.svg[WiFi station set up from a scan-only interface topology, align=center, scaledwidth=75%] - -==== Sequence - -. Set up topology and attach to the ap and the station -. Configure the ap with access point 'infix-scan' and a DHCP server on 192.168.21.1 -. Configure wifi0 on the station with only radio0, scan-only mode -. Verify the station sees 'infix-scan' in its scan results -. Add station settings for 'infix-scan' to wifi0 on the station -. Verify the station associates to 'infix-scan' without a restart -. Verify the station leases 192.168.21.100 over wifi - - diff --git a/test/case/interfaces/wifi_station_from_scan/test.py b/test/case/interfaces/wifi_station_from_scan/test.py deleted file mode 100755 index b48488ad96..0000000000 --- a/test/case/interfaces/wifi_station_from_scan/test.py +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/env python3 -r""" -WiFi station set up from a scan-only interface - -A WiFi interface with only a radio, no station or access point, is in -scan-only mode. That is how the factory configuration of boards with a -built-in radio ships, so the usual way to get online is to add the station -settings to that existing interface. The connection has to come up from -that change alone, without a reboot or a service restart. - -Topology: -.... - host ==(mgmt)== ap ))) ~ cell ~ ((( station ==(mgmt)== host -.... -""" -import infamy -import infamy.iface as iface -import infamy.wifi as wifi -from infamy.util import until, parallel - -SSID = "infix-scan" -PSK = "infixinfix" - -SUBNET = "192.168.21.0/24" -AP_IP = "192.168.21.1" -LEASE = "192.168.21.100" - - -with infamy.Test() as test: - with test.step("Set up topology and attach to the ap and the station"): - env = infamy.Env() - ap, station = parallel( - lambda: env.attach("ap", "mgmt"), - lambda: env.attach("station", "mgmt"), - ) - wifi.skip_unless_supported(test, ap, station) - - with test.step("Configure the ap with access point 'infix-scan' and a DHCP server on 192.168.21.1"): - ap.put_config_dicts({ - "ietf-hardware": wifi.hardware(wifi.radio("radio0", band="2.4GHz", channel=1)), - "ietf-keystore": wifi.keystore({"wifi": PSK}), - "ietf-interfaces": {"interfaces": {"interface": [ - wifi.iface("wifi0", { - "radio": "radio0", - "access-point": { - "ssid": SSID, - "security": {"mode": "wpa2-wpa3-personal", "secret": "wifi"}, - }, - }, ipv4={"address": [{"ip": AP_IP, "prefix-length": 24}]}), - ]}}, - "infix-dhcp-server": {"dhcp-server": {"subnet": [{ - "subnet": SUBNET, - "pool": {"start-address": LEASE, "end-address": LEASE}, - }]}}, - }) - - with test.step("Configure wifi0 on the station with only radio0, scan-only mode"): - station.put_config_dicts({ - "ietf-hardware": wifi.hardware(wifi.radio("radio0")), - "ietf-interfaces": {"interfaces": {"interface": [ - wifi.iface("wifi0", {"radio": "radio0"}, - ipv4={"infix-dhcp-client:dhcp": {}}), - ]}}, - }) - - with test.step("Verify the station sees 'infix-scan' in its scan results"): - until(lambda: SSID in {n.get("ssid") for n in - wifi.station(station).get("scan-results") or []}, - attempts=60, interval=2) - - with test.step("Add station settings for 'infix-scan' to wifi0 on the station"): - station.put_config_dicts({ - "ietf-keystore": wifi.keystore({"wifi": PSK}), - "ietf-interfaces": {"interfaces": {"interface": [ - {"name": "wifi0", "infix-interfaces:wifi": { - "radio": "radio0", - "station": { - "ssid": SSID, - "security": {"mode": "auto", "secret": "wifi"}, - }, - }}, - ]}}, - }) - - with test.step("Verify the station associates to 'infix-scan' without a restart"): - until(lambda: wifi.associated(station, SSID), attempts=60, interval=2) - - with test.step("Verify the station leases 192.168.21.100 over wifi"): - until(lambda: iface.address_exist(station, "wifi0", LEASE), - attempts=60, interval=2) - - test.succeed() diff --git a/test/case/interfaces/wifi_station_from_scan/topology.dot b/test/case/interfaces/wifi_station_from_scan/topology.dot deleted file mode 100644 index 7a05357a1d..0000000000 --- a/test/case/interfaces/wifi_station_from_scan/topology.dot +++ /dev/null @@ -1,44 +0,0 @@ -graph "wifi-station-from-scan" { - layout="neato"; - overlap="false"; - esep="+40"; - - node [shape=record, fontname="DejaVu Sans Mono, Book"]; - edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"]; - - host [ - label="host | { mgmt1 | mgmt2 }", - pos="0,0!", - requires="controller", - ]; - - ap [ - label="{ mgmt | wifi } | ap", - pos="6,2!", - requires="infix", - ]; - - station [ - label="{ mgmt | wifi } | station", - pos="6,-2!", - requires="infix", - ]; - - // The wireless cell the ap and the station share, modelled as a medium - // node both join (one radio each, same index -> same cell): - // * physical: maps onto a real over-the-air RF cell; - // * virtual (qeneth): maps onto one of the multicast cells over which - // the wifimedium relay bridges mac80211_hwsim frames. - // The mapper guarantees the two DUTs actually share this medium. - cell [ - label="cell", - pos="9,0!", - requires="wifi-radio", - ]; - - host:mgmt1 -- ap:mgmt [requires="mgmt", color="lightgray"] - host:mgmt2 -- station:mgmt [requires="mgmt", color="lightgray"] - - ap:wifi -- cell [requires="wifi2.4GHz", style="dashed"] - station:wifi -- cell [requires="wifi2.4GHz", style="dashed"] -} diff --git a/test/case/interfaces/wifi_station_from_scan/topology.svg b/test/case/interfaces/wifi_station_from_scan/topology.svg deleted file mode 100644 index 9c8741affb..0000000000 --- a/test/case/interfaces/wifi_station_from_scan/topology.svg +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - -wifi-station-from-scan - - - -host - -host - -mgmt1 - -mgmt2 - - - -ap - -mgmt - -wifi - -ap - - - -host:mgmt1--ap:mgmt - - - - -station - -mgmt - -wifi - -station - - - -host:mgmt2--station:mgmt - - - - -cell - -cell - - - -ap:wifi--cell - - - - -station:wifi--cell - - - - diff --git a/test/case/routing/ospf_multiarea/ospfv2.adoc b/test/case/routing/ospf_multiarea/ospfv2.adoc index 540ab41618..4327989426 100644 --- a/test/case/routing/ospf_multiarea/ospfv2.adoc +++ b/test/case/routing/ospf_multiarea/ospfv2.adoc @@ -62,7 +62,11 @@ image::topology.svg[OSPFv2 with Multiple Areas topology, align=center, scaledwid . Set up topology and attach to target DUTs . Configure targets -. Wait for all neighbors to peer +. Wait for R1 to reach full adjacency with R2 (router-id 1.1.1.1) on R1:ring1 in area 0.0.0.0 +. Wait for R1 to reach full adjacency with R3 (router-id 10.0.0.3) on R1:cross in area 0.0.0.1 +. Wait for R2 to reach full adjacency with R3 (router-id 10.0.0.3) on R2:ring1 in area 0.0.0.1 +. Wait for R2 to reach full adjacency with R1 (router-id 10.0.0.1) on R2:ring2 in area 0.0.0.0 +. Wait for R2 to reach full adjacency with R4 (router-id 10.0.0.4) on R2:cross in area 0.0.0.2 . Wait for routes from OSPFv2 on all routers . Verify Area 0.0.0.1 on R3 is NSSA area . Verify R1:ring2 is of type point-to-point diff --git a/test/case/routing/ospf_multiarea/ospfv3.adoc b/test/case/routing/ospf_multiarea/ospfv3.adoc index e46f8234b6..17be7c49a9 100644 --- a/test/case/routing/ospf_multiarea/ospfv3.adoc +++ b/test/case/routing/ospf_multiarea/ospfv3.adoc @@ -62,7 +62,11 @@ image::topology.svg[OSPFv3 with Multiple Areas topology, align=center, scaledwid . Set up topology and attach to target DUTs . Configure targets -. Wait for all neighbors to peer +. Wait for R1 to reach full adjacency with R2 (router-id 1.1.1.1) on R1:ring1 in area 0.0.0.0 +. Wait for R1 to reach full adjacency with R3 (router-id 10.0.0.3) on R1:cross in area 0.0.0.1 +. Wait for R2 to reach full adjacency with R3 (router-id 10.0.0.3) on R2:ring1 in area 0.0.0.1 +. Wait for R2 to reach full adjacency with R1 (router-id 10.0.0.1) on R2:ring2 in area 0.0.0.0 +. Wait for R2 to reach full adjacency with R4 (router-id 10.0.0.4) on R2:cross in area 0.0.0.2 . Wait for routes from OSPFv3 on all routers . Verify Area 0.0.0.1 on R3 is NSSA area . Verify R1:ring2 is of type point-to-point diff --git a/test/case/routing/ospf_multiarea/test.py b/test/case/routing/ospf_multiarea/test.py index b48cb9e7b1..ade6dfbcf8 100755 --- a/test/case/routing/ospf_multiarea/test.py +++ b/test/case/routing/ospf_multiarea/test.py @@ -330,12 +330,19 @@ def lo(name): lambda: config_target3(R3, R3ring2, R3cross, R3data, param), lambda: config_target4(R4, R4ring1, R4cross, R4data, param)) - with test.step("Wait for all neighbors to peer"): - print("Waiting for neighbors to peer") + with test.step("Wait for R1 to reach full adjacency with R2 (router-id 1.1.1.1) on R1:ring1 in area 0.0.0.0"): until(lambda: route.ospf_get_neighbor(R1, "0.0.0.0", R1ring1, "1.1.1.1", proto=instance), attempts=200) + + with test.step("Wait for R1 to reach full adjacency with R3 (router-id 10.0.0.3) on R1:cross in area 0.0.0.1"): until(lambda: route.ospf_get_neighbor(R1, "0.0.0.1", R1cross, "10.0.0.3", proto=instance), attempts=200) + + with test.step("Wait for R2 to reach full adjacency with R3 (router-id 10.0.0.3) on R2:ring1 in area 0.0.0.1"): until(lambda: route.ospf_get_neighbor(R2, "0.0.0.1", R2ring1, "10.0.0.3", proto=instance), attempts=200) + + with test.step("Wait for R2 to reach full adjacency with R1 (router-id 10.0.0.1) on R2:ring2 in area 0.0.0.0"): until(lambda: route.ospf_get_neighbor(R2, "0.0.0.0", R2ring2, "10.0.0.1", proto=instance), attempts=200) + + with test.step("Wait for R2 to reach full adjacency with R4 (router-id 10.0.0.4) on R2:cross in area 0.0.0.2"): until(lambda: route.ospf_get_neighbor(R2, "0.0.0.2", R2cross, "10.0.0.4", proto=instance), attempts=200) with test.step(f"Wait for routes from {version} on all routers"): diff --git a/test/case/system/upgrade/test.py b/test/case/system/upgrade/test.py index 80931dcc76..48faec740c 100755 --- a/test/case/system/upgrade/test.py +++ b/test/case/system/upgrade/test.py @@ -112,9 +112,10 @@ def cleanup(env, old_bootorder): installer = oper["system-state"]["software"]["installer"] if installer["operation"] == "idle": print(installer) - if "last-error" in installer: - print("Install failed:", installer["last-error"]) - test.fail() + error = installer.get("last-error") + message = installer.get("progress", {}).get("message", "") + if error or "failed" in message.lower(): + test.fail(f"Install failed: {error or message}") break time.sleep(1)