Skip to content

Commit d887e14

Browse files
committed
fix(testsuite):fix flaky by ensure port is free to use
Signed-off-by: arshia-rgh <[email protected]>
1 parent 8059d35 commit d887e14

File tree

1 file changed

+77
-22
lines changed

1 file changed

+77
-22
lines changed

pkg/port/testsuite/testsuite.go

Lines changed: 77 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"context"
66
"encoding/json"
7+
"errors"
78
"fmt"
89
"io"
910
"net"
@@ -139,20 +140,13 @@ func TestProto(t *testing.T, proto string, d port.ParentDriver) {
139140
func testProtoWithPID(t *testing.T, proto string, d port.ParentDriver, childPID int) {
140141
ensureDeps(t, "nsenter", "ip", "nc")
141142
// [child]parent
142-
pairs := map[int]int{
143-
// FIXME: flaky
144-
80: (childPID + 80) % 60000,
145-
8080: (childPID + 8080) % 60000,
146-
}
147-
if proto == "tcp" {
148-
for _, parentPort := range pairs {
149-
var d net.Dialer
150-
d.Timeout = 50 * time.Millisecond
151-
_, err := d.Dial(proto, fmt.Sprintf("127.0.0.1:%d", parentPort))
152-
if err == nil {
153-
t.Fatalf("port %d is already used?", parentPort)
154-
}
143+
pairs := make(map[int]int, 2)
144+
for _, childPort := range []int{80, 8080} {
145+
parentPort, err := allocateAvailablePort(proto)
146+
if err != nil {
147+
t.Fatalf("failed to allocate parent port for %s: %v", proto, err)
155148
}
149+
pairs[childPort] = parentPort
156150
}
157151

158152
t.Logf("namespace pid: %d", childPID)
@@ -222,16 +216,42 @@ func testProtoRoutine(t *testing.T, proto string, d port.ParentDriver, childPID,
222216
panic(err)
223217
}
224218
defer cmd.Process.Kill()
225-
portStatus, err := d.AddPort(context.TODO(),
226-
port.Spec{
227-
Proto: proto,
228-
ParentIP: "127.0.0.1",
229-
ParentPort: parentP,
230-
ChildPort: childP,
231-
})
232-
if err != nil {
233-
panic(err)
219+
220+
const maxAttempts = 10
221+
var (
222+
currentParent = parentP
223+
portStatus *port.Status
224+
err error
225+
)
226+
for attempt := 0; attempt < maxAttempts; attempt++ {
227+
portStatus, err = d.AddPort(context.TODO(),
228+
port.Spec{
229+
Proto: proto,
230+
ParentIP: "127.0.0.1",
231+
ParentPort: currentParent,
232+
ChildPort: childP,
233+
})
234+
if err == nil {
235+
parentP = currentParent
236+
break
237+
}
238+
if attempt == maxAttempts-1 || !isAddrInUse(err) {
239+
panic(err)
240+
}
241+
currentParent, err = allocateAvailablePort(proto)
242+
if err != nil {
243+
panic(err)
244+
}
245+
}
246+
if portStatus == nil {
247+
panic("AddPort never succeeded")
234248
}
249+
defer func(id int) {
250+
if err := d.RemovePort(context.TODO(), id); err != nil {
251+
panic(err)
252+
}
253+
}(portStatus.ID)
254+
235255
t.Logf("opened port: %+v", portStatus)
236256
if proto == "udp" || proto == "udp4" {
237257
// Dial does not return an error for UDP even if the port is not exposed yet
@@ -308,3 +328,38 @@ func (w *tLogWriter) Write(p []byte) (int, error) {
308328
w.t.Logf("%s: %s", w.s, strings.TrimSuffix(string(p), "\n"))
309329
return len(p), nil
310330
}
331+
332+
func allocateAvailablePort(proto string) (int, error) {
333+
const loopback = "127.0.0.1:0"
334+
switch proto {
335+
case "tcp", "tcp4":
336+
ln, err := net.Listen(proto, loopback)
337+
if err != nil {
338+
return 0, err
339+
}
340+
defer ln.Close()
341+
return ln.Addr().(*net.TCPAddr).Port, nil
342+
case "udp", "udp4":
343+
addr, err := net.ResolveUDPAddr(proto, loopback)
344+
if err != nil {
345+
return 0, err
346+
}
347+
conn, err := net.ListenUDP(proto, addr)
348+
if err != nil {
349+
return 0, err
350+
}
351+
defer conn.Close()
352+
return conn.LocalAddr().(*net.UDPAddr).Port, nil
353+
default:
354+
return 0, fmt.Errorf("unsupported proto %q", proto)
355+
}
356+
}
357+
358+
func isAddrInUse(err error) bool {
359+
if errors.Is(err, syscall.EADDRINUSE) {
360+
return true
361+
}
362+
msg := err.Error()
363+
return strings.Contains(msg, "address already in use") ||
364+
strings.Contains(msg, "port is busy")
365+
}

0 commit comments

Comments
 (0)