Skip to content

Commit 7a3f426

Browse files
committed
fix(testsuite):fix flaky by ensure port is free to use
Signed-off-by: arshia-rgh <arshiarezagholi1212@gmail.com>
1 parent 8059d35 commit 7a3f426

File tree

1 file changed

+83
-22
lines changed

1 file changed

+83
-22
lines changed

pkg/port/testsuite/testsuite.go

Lines changed: 83 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import (
44
"bytes"
55
"context"
66
"encoding/json"
7+
"errors"
78
"fmt"
89
"io"
10+
"math/rand"
911
"net"
1012
"os"
1113
"os/exec"
@@ -25,6 +27,11 @@ const (
2527
reexecKeyQuitFD = "rootlesskit-port-testsuite.quitfd"
2628
)
2729

30+
var (
31+
randSrc = rand.New(rand.NewSource(time.Now().UnixNano()))
32+
randMu sync.Mutex
33+
)
34+
2835
func Main(m *testing.M, cf func() port.ChildDriver) {
2936
switch mode := os.Getenv(reexecKeyMode); mode {
3037
case "":
@@ -139,20 +146,13 @@ func TestProto(t *testing.T, proto string, d port.ParentDriver) {
139146
func testProtoWithPID(t *testing.T, proto string, d port.ParentDriver, childPID int) {
140147
ensureDeps(t, "nsenter", "ip", "nc")
141148
// [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-
}
149+
pairs := make(map[int]int, 2)
150+
for _, childPort := range []int{80, 8080} {
151+
parentPort, err := allocateAvailablePort(proto)
152+
if err != nil {
153+
t.Fatalf("failed to allocate parent port for %s: %v", proto, err)
155154
}
155+
pairs[childPort] = parentPort
156156
}
157157

158158
t.Logf("namespace pid: %d", childPID)
@@ -222,16 +222,42 @@ func testProtoRoutine(t *testing.T, proto string, d port.ParentDriver, childPID,
222222
panic(err)
223223
}
224224
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)
225+
226+
const maxAttempts = 10
227+
var (
228+
currentParent = parentP
229+
portStatus *port.Status
230+
err error
231+
)
232+
for attempt := 0; attempt < maxAttempts; attempt++ {
233+
portStatus, err = d.AddPort(context.TODO(),
234+
port.Spec{
235+
Proto: proto,
236+
ParentIP: "127.0.0.1",
237+
ParentPort: currentParent,
238+
ChildPort: childP,
239+
})
240+
if err == nil {
241+
parentP = currentParent
242+
break
243+
}
244+
if attempt == maxAttempts-1 || !isAddrInUse(err) {
245+
panic(err)
246+
}
247+
currentParent, err = allocateAvailablePort(proto)
248+
if err != nil {
249+
panic(err)
250+
}
234251
}
252+
if portStatus == nil {
253+
panic("AddPort never succeeded")
254+
}
255+
defer func(id int) {
256+
if err := d.RemovePort(context.TODO(), id); err != nil {
257+
panic(err)
258+
}
259+
}(portStatus.ID)
260+
235261
t.Logf("opened port: %+v", portStatus)
236262
if proto == "udp" || proto == "udp4" {
237263
// Dial does not return an error for UDP even if the port is not exposed yet
@@ -308,3 +334,38 @@ func (w *tLogWriter) Write(p []byte) (int, error) {
308334
w.t.Logf("%s: %s", w.s, strings.TrimSuffix(string(p), "\n"))
309335
return len(p), nil
310336
}
337+
338+
func allocateAvailablePort(proto string) (int, error) {
339+
const loopback = "127.0.0.1:0"
340+
switch proto {
341+
case "tcp", "tcp4":
342+
ln, err := net.Listen(proto, loopback)
343+
if err != nil {
344+
return 0, err
345+
}
346+
defer ln.Close()
347+
return ln.Addr().(*net.TCPAddr).Port, nil
348+
case "udp", "udp4":
349+
addr, err := net.ResolveUDPAddr(proto, loopback)
350+
if err != nil {
351+
return 0, err
352+
}
353+
conn, err := net.ListenUDP(proto, addr)
354+
if err != nil {
355+
return 0, err
356+
}
357+
defer conn.Close()
358+
return conn.LocalAddr().(*net.UDPAddr).Port, nil
359+
default:
360+
return 0, fmt.Errorf("unsupported proto %q", proto)
361+
}
362+
}
363+
364+
func isAddrInUse(err error) bool {
365+
if errors.Is(err, syscall.EADDRINUSE) {
366+
return true
367+
}
368+
msg := err.Error()
369+
return strings.Contains(msg, "address already in use") ||
370+
strings.Contains(msg, "port is busy")
371+
}

0 commit comments

Comments
 (0)