Skip to content

Commit c62a145

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 c62a145

File tree

1 file changed

+78
-26
lines changed

1 file changed

+78
-26
lines changed

pkg/port/testsuite/testsuite.go

Lines changed: 78 additions & 26 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,43 @@ 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+
}
234245
}
246+
if portStatus == nil {
247+
panic("AddPort never succeeded")
248+
}
249+
defer func(id int) {
250+
if err := d.RemovePort(context.TODO(), id); err != nil {
251+
panic(err)
252+
}
253+
t.Logf("closed port ID %d", portStatus.ID)
254+
}(portStatus.ID)
255+
235256
t.Logf("opened port: %+v", portStatus)
236257
if proto == "udp" || proto == "udp4" {
237258
// Dial does not return an error for UDP even if the port is not exposed yet
@@ -281,10 +302,6 @@ func testProtoRoutine(t *testing.T, proto string, d port.ParentDriver, childPID,
281302
// nc -u does not exit automatically
282303
syscall.Kill(cmd.Process.Pid, syscall.SIGKILL)
283304
}
284-
if err := d.RemovePort(context.TODO(), portStatus.ID); err != nil {
285-
panic(err)
286-
}
287-
t.Logf("closed port ID %d", portStatus.ID)
288305
}
289306

290307
func ensureDeps(t testing.TB, deps ...string) {
@@ -308,3 +325,38 @@ func (w *tLogWriter) Write(p []byte) (int, error) {
308325
w.t.Logf("%s: %s", w.s, strings.TrimSuffix(string(p), "\n"))
309326
return len(p), nil
310327
}
328+
329+
func allocateAvailablePort(proto string) (int, error) {
330+
const loopback = "127.0.0.1:0"
331+
switch proto {
332+
case "tcp", "tcp4":
333+
ln, err := net.Listen(proto, loopback)
334+
if err != nil {
335+
return 0, err
336+
}
337+
defer ln.Close()
338+
return ln.Addr().(*net.TCPAddr).Port, nil
339+
case "udp", "udp4":
340+
addr, err := net.ResolveUDPAddr(proto, loopback)
341+
if err != nil {
342+
return 0, err
343+
}
344+
conn, err := net.ListenUDP(proto, addr)
345+
if err != nil {
346+
return 0, err
347+
}
348+
defer conn.Close()
349+
return conn.LocalAddr().(*net.UDPAddr).Port, nil
350+
default:
351+
return 0, fmt.Errorf("unsupported proto %q", proto)
352+
}
353+
}
354+
355+
func isAddrInUse(err error) bool {
356+
if errors.Is(err, syscall.EADDRINUSE) {
357+
return true
358+
}
359+
msg := err.Error()
360+
return strings.Contains(msg, "address already in use") ||
361+
strings.Contains(msg, "port is busy")
362+
}

0 commit comments

Comments
 (0)