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