From 8fa8534eeb6d359db12e37f614cc274ac3482f2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Fri, 30 Oct 2026 18:44:30 +0100 Subject: [PATCH 1/2] test: give the virtual DUTs two USB buses again MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On q35, -usb adds an EHCI with three UHCI companions. Bus numbering then depends on which driver loads first, and the stick often ends up outside usb1 and usb2, the only ports listed for QEMU. make run has the same problem with a USB disk. Signed-off-by: Mattias Walström --- board/common/qemu/ddi/run.sh | 6 +++++- test/templates/inc/infix-usb.mustache | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/board/common/qemu/ddi/run.sh b/board/common/qemu/ddi/run.sh index 4f57ae7d8e..1128e2764c 100755 --- a/board/common/qemu/ddi/run.sh +++ b/board/common/qemu/ddi/run.sh @@ -140,7 +140,11 @@ q_add_disk() q_sect_device_usb() { if [ -z "$usb_bus_added" ]; then - q sect usb + case "$IX_QEMU_MACHINE" in + q35*|pc*) + q sect device piix3-usb-uhci + ;; + esac q sect device usb-ehci q param id ehci usb_bus_added=YES diff --git a/test/templates/inc/infix-usb.mustache b/test/templates/inc/infix-usb.mustache index 9dabb2ca17..e7f4aa8544 100644 --- a/test/templates/inc/infix-usb.mustache +++ b/test/templates/inc/infix-usb.mustache @@ -5,5 +5,5 @@ if [ -n "{{qn_usb}}" ]; then dd if=/dev/zero of={{qn_usb}} bs=8M count=1 >/dev/null 2>&1 mkfs.vfat {{qn_usb}} >/dev/null 2>&1 fi - usb_cmd=" -drive if=none,id=usbstick,format=raw,file={{qn_usb}} -usb -device usb-ehci,id=ehci -device usb-storage,bus=ehci.0,drive=usbstick " + usb_cmd=" -drive if=none,id=usbstick,format=raw,file={{qn_usb}} -device piix3-usb-uhci,id=uhci -device usb-ehci,id=ehci -device usb-storage,bus=ehci.0,drive=usbstick " fi From 68208c03ea2515a5fb86e9f50b3ee3bb67854452 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Fri, 30 Oct 2026 21:16:53 +0100 Subject: [PATCH 2/2] yangerd: skip NTP sources chronyd has not resolved yet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A server configured by hostname has only an ID placeholder address until DNS answers. Chrony reports it as "ID#0000000001", which fails the inet:ip-address type in statd and drops the whole ietf-system:system-state overlay for that read. Treat it like a refclock and leave it out. Signed-off-by: Mattias Walström --- src/yangerd/internal/collector/ntp.go | 15 ++++++++--- src/yangerd/internal/collector/ntp_test.go | 30 ++++++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/yangerd/internal/collector/ntp.go b/src/yangerd/internal/collector/ntp.go index 75ea8dd230..7492403b91 100644 --- a/src/yangerd/internal/collector/ntp.go +++ b/src/yangerd/internal/collector/ntp.go @@ -245,8 +245,7 @@ func addAssociations(ntp map[string]interface{}, srcs []ntpSource) { for _, src := range srcs { d := src.data - // Skip reference clocks — they have refids, not addresses - if d.Mode == chrony.SourceModeRef { + if !resolved(d) { continue } @@ -320,6 +319,16 @@ var sourceModeMap = map[chrony.ModeType]string{ chrony.SourceModeRef: "local-clock", } +// resolved reports whether a chrony source has an IP address: refclocks +// carry a refid instead, and a server named by hostname has only a +// placeholder ID until DNS answers. +func resolved(d *chrony.ReplySourceData) bool { + if d.Mode == chrony.SourceModeRef || d.IPAddr == nil { + return false + } + return d.IPAddr.Family == chrony.IPAddrInet4 || d.IPAddr.Family == chrony.IPAddrInet6 +} + // addSources builds the infix-system:ntp/sources/source list. // Reference clocks and sources with invalid stratum are skipped, // matching the Python yanger ietf_system.py add_ntp() behaviour. @@ -328,7 +337,7 @@ func addSources(srcs []ntpSource) map[string]interface{} { for _, src := range srcs { d := src.data - if d.Mode == chrony.SourceModeRef { + if !resolved(d) { continue } if d.Stratum > 16 { diff --git a/src/yangerd/internal/collector/ntp_test.go b/src/yangerd/internal/collector/ntp_test.go index b4f479fdf9..1cf839330e 100644 --- a/src/yangerd/internal/collector/ntp_test.go +++ b/src/yangerd/internal/collector/ntp_test.go @@ -155,6 +155,16 @@ func fullFakeChrony() *fakeChrony { } } +// unresolvedSource is a server configured by hostname that chronyd has +// not resolved yet; cmdmon reports it with an ID placeholder address. +func unresolvedSource() *chrony.ReplySourceData { + d := sourceData(nil, chrony.SourceModeClient, chrony.SourceStateUnreach, + 0, 6, 0, 0, 0, 0) + d.IPAddr.Family = chrony.IPAddrID + d.IPAddr.IP[3] = 1 + return d +} + func unsyncFakeChrony() *fakeChrony { return &fakeChrony{ tracking: &chrony.ReplyTracking{ @@ -398,6 +408,26 @@ func ntpSourceCount(t *testing.T, tr *tree.Tree) int { return len(ntp.Sources.Source) } +func TestNTPUnresolvedSourceSkipped(t *testing.T) { + fake := fullFakeChrony() + fake.data = append(fake.data, unresolvedSource()) + fake.stats = append(fake.stats, nil) + + out, tr := ntpCollect(t, fake) + assocs := out["associations"].(map[string]interface{})["association"].([]interface{}) + if len(assocs) != 3 { + t.Fatalf("expected 3 associations, got %d", len(assocs)) + } + for _, a := range assocs { + if addr := a.(map[string]interface{})["address"].(string); addr == "ID#0000000001" { + t.Fatal("unresolved source leaked into associations") + } + } + if n := ntpSourceCount(t, tr); n != 4 { + t.Fatalf("expected 4 NTP sources, got %d", n) + } +} + func TestNTPSourcesEmpty(t *testing.T) { fake := &fakeChrony{tracking: fullFakeChrony().tracking}