-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathiperfhint_container_test.go
More file actions
100 lines (94 loc) · 5.16 KB
/
Copy pathiperfhint_container_test.go
File metadata and controls
100 lines (94 loc) · 5.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
package main
import (
"context"
"strings"
"testing"
"time"
"github.com/pingular/pingularity/internal/settings"
"github.com/pingular/pingularity/internal/store"
)
// iperfContainerHint is the container-networking matcher main.go injects into
// the iperf3 engine (Iperf.EnvHint) in a BRIDGED container (see iperfEnvHintFn).
// Each failure class from the docker-vs-native analysis must map to a hint
// naming the fix, and anything else must map to NO hint - the matcher may only
// explain, never guess. It is a pure function over the error text and the run's
// server/bind/family settings, so the classes are pinned here without a
// container.
func TestIperfHintContainerClasses(t *testing.T) {
cases := []struct {
name, errText, server, bind, ipver string
want string // required substring; "" = exactly no hint
}{
// I1: loopback server target - the container has its own localhost.
{"refused v4 loopback", "download: unable to connect to server: Connection refused", "127.0.0.1:5201", "", "auto", "host.docker.internal"},
{"refused localhost", "connect failed: Connection refused", "localhost", "", "auto", "host.docker.internal"},
{"refused bracketed v6 loopback", "iperf3: error - unable to connect to server: Connection refused", "[::1]:5201", "", "auto", "host.docker.internal"},
// A refused NON-loopback server is a plain dead server, not a container issue.
{"refused non-loopback stays bare", "unable to connect to server: Connection refused", "192.168.1.50:5201", "", "auto", ""},
// I3: --bind with a host IP fails EADDRNOTAVAIL inside the namespace.
{"bind EADDRNOTAVAIL", "iperf3: error - unable to connect to server: Cannot assign requested address", "10.0.0.2:5201", "192.168.1.23", "auto", `bind address "192.168.1.23"`},
// I4: --bind-dev with a host NIC name fails ENODEV inside the namespace.
{"bind-dev ENODEV", "iperf3: error - unable to connect to server: No such device", "10.0.0.2:5201", "eno1", "auto", `host interface "eno1"`},
{"bind-dev bad interface", "unable to connect to server: Bad interface name", "10.0.0.2:5201", "eno1", "auto", `host interface "eno1"`},
// I5: a forced -6 on the IPv6-less default bridge.
{"forced v6 unreachable", "upload: unable to connect to server: Network unreachable", "iperf.example.com:5201", "", "6", "no IPv6"},
{"forced v6 cannot assign", "unable to connect to server: Cannot assign requested address", "iperf.example.com:5201", "", "6", "no IPv6"},
// An explicit bind is the likelier direct cause than the -6 pin.
{"bind precedes v6 on cannot-assign", "Cannot assign requested address", "iperf.example.com:5201", "192.168.1.23", "6", `bind address "192.168.1.23"`},
// cannot-assign with neither a bind nor a -6 pin matches no class.
{"cannot assign, no bind, auto family", "Cannot assign requested address", "10.0.0.2:5201", "", "auto", ""},
// Unrelated failures map to none, whatever the settings look like.
{"unrelated auth", "test authorization failed", "127.0.0.1:5201", "", "auto", ""},
{"unrelated busy", "the server is busy running a test. Try again later.", "localhost", "", "auto", ""},
{"unrelated no-data with container-y settings", "no data transferred", "10.0.0.2:5201", "192.168.1.23", "6", ""},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got := iperfContainerHint(c.errText, c.server, c.bind, c.ipver)
if c.want == "" {
if got != "" {
t.Fatalf("hint = %q, want none", got)
}
return
}
if !strings.Contains(got, c.want) {
t.Fatalf("hint = %q, want substring %q", got, c.want)
}
})
}
}
// The matcher's injection gate keys on BRIDGED (util.BridgedContainer), not
// merely containerized: in a host-network container localhost IS the host and
// host NICs exist in our namespace, so every bridged hint - host.docker.internal
// for a refused loopback server above all - would send the operator away from a
// server plain 127.0.0.1 does reach. iperfEnvHintFn is that gate as a named
// function, so the key is pinned here instead of buried in run()'s wiring.
func TestIperfEnvHintGateKeysOnBridged(t *testing.T) {
ctx := context.Background()
st, err := store.Open(":memory:")
if err != nil {
t.Fatalf("open store: %v", err)
}
t.Cleanup(func() { st.Close() })
set, err := settings.New(ctx, st, settings.Values{
Latency: 5 * time.Second, Speed: time.Hour, Timeout: 2 * time.Second,
DownAfter: 3, UpAfter: 2, IperfServer: "127.0.0.1:5201",
})
if err != nil {
t.Fatalf("new settings: %v", err)
}
// Host-network container (and native): no matcher at all - the engine's
// nil-check keeps errors bare, exactly as they should read there.
if fn := iperfEnvHintFn(false, set); fn != nil {
t.Fatal("non-bridged got an EnvHint matcher; host-network hints would be wrong (localhost IS the host)")
}
// Bridged: the matcher fires on the loopback-server class using the live
// settings (the configured server feeds the match).
fn := iperfEnvHintFn(true, set)
if fn == nil {
t.Fatal("bridged container got no EnvHint matcher")
}
if h := fn("unable to connect to server: Connection refused"); !strings.Contains(h, "host.docker.internal") {
t.Fatalf("bridged loopback-refused hint = %q, want host.docker.internal", h)
}
}