From 4f17c6661acfeb694dcd5de5ac433d1567688566 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Tue, 26 May 2026 16:48:46 -0700 Subject: [PATCH] test: keep AllocateMiniPorts off weed mini default ports Random allocation could pick 33646 = admin.port (23646) + GrpcPortOffset. weed mini reserves that as Admin's gRPC port even when the test only overrides Master/Filer/S3/Iceberg, so the explicit Filer flag failed with "reserved for gRPC calculation" and TestRisingWaveIcebergCatalog flaked. Pre-seed the reserved set with every mini default HTTP port plus its +10000 offset so a random pick (or its own gRPC offset) cannot land on a service the caller left at its default. --- test/testutil/ports.go | 27 +++++++++++++++++++++++++-- test/testutil/ports_test.go | 23 +++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/test/testutil/ports.go b/test/testutil/ports.go index f5dc888dc..867f20761 100644 --- a/test/testutil/ports.go +++ b/test/testutil/ports.go @@ -11,6 +11,29 @@ import ( // GrpcPortOffset is the offset weed mini uses to derive gRPC ports from HTTP ports. const GrpcPortOffset = 10000 +// miniDefaultPorts are the weed mini flag defaults (see weed/command/mini.go). +// A test only overrides services it uses; unspecified services still bind +// these defaults, so allocation must avoid handing them out (or any value +// whose gRPC offset would collide with them). +var miniDefaultPorts = []int{ + 9333, // master.port + 8888, // filer.port + 9340, // volume.port + 8333, // s3.port + 8181, // s3.port.iceberg + 7333, // webdav.port + 23646, // admin.port +} + +func reservedMiniPorts() map[int]bool { + r := make(map[int]bool, len(miniDefaultPorts)*2) + for _, p := range miniDefaultPorts { + r[p] = true + r[p+GrpcPortOffset] = true + } + return r +} + // AllocatePorts allocates count unique free ports atomically. // All listeners are held open until every port is obtained, preventing // the OS from recycling a port between successive allocations. @@ -63,7 +86,7 @@ func AllocateMiniPorts(count int) ([]int, error) { minPort = 10000 maxPort = 55000 ) - reserved := make(map[int]bool) + reserved := reservedMiniPorts() ports := make([]int, 0, count) var listeners []net.Listener defer func() { @@ -135,7 +158,7 @@ func AllocatePortSet(miniCount, regularCount int) (mini []int, regular []int, er minPort = 10000 maxPort = 55000 ) - reserved := make(map[int]bool) + reserved := reservedMiniPorts() mini = make([]int, 0, miniCount) var listeners []net.Listener defer func() { diff --git a/test/testutil/ports_test.go b/test/testutil/ports_test.go index 3cfd6a11d..5c02e20a3 100644 --- a/test/testutil/ports_test.go +++ b/test/testutil/ports_test.go @@ -2,6 +2,29 @@ package testutil import "testing" +// AllocateMiniPorts must never hand out a port that weed mini will reserve +// for one of its default services (or that default's gRPC offset). A real +// failure: Filer was given 33646 (Admin default 23646 + GrpcPortOffset), +// which mini then refused as "reserved for gRPC calculation". +func TestAllocateMiniPortsAvoidsMiniDefaults(t *testing.T) { + reserved := reservedMiniPorts() + for iter := 0; iter < 200; iter++ { + ports, err := AllocateMiniPorts(4) + if err != nil { + t.Fatalf("iter %d: AllocateMiniPorts: %v", iter, err) + } + for _, p := range ports { + if reserved[p] { + t.Fatalf("iter %d: allocated port %d is a mini default (or gRPC offset)", iter, p) + } + if reserved[p+GrpcPortOffset] { + t.Fatalf("iter %d: allocated port %d has gRPC offset %d colliding with a mini default", + iter, p, p+GrpcPortOffset) + } + } + } +} + func TestAllocatePortSetNoGrpcCollision(t *testing.T) { // Run a few iterations to catch the OS-recycles-just-closed-port race // that previously hit regular ports when the mini gRPC offset was freed