1 package dualstack
2
3 import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "io"
8 "os"
9 "strings"
10 "testing"
11
12 "github.com/linkerd/linkerd2/testutil"
13 )
14
15 type IP struct {
16 IP string `json:"ip"`
17 }
18
19 var TestHelper *testutil.TestHelper
20
21 func TestMain(m *testing.M) {
22 TestHelper = testutil.NewTestHelper()
23
24 TestHelper.WaitUntilDeployReady(testutil.LinkerdDeployReplicasEdge)
25 os.Exit(m.Run())
26 }
27
28
29
30
31
32
33
34 func TestDualStack(t *testing.T) {
35 if !TestHelper.DualStack() {
36 t.Skip("Skipping Skip DualStack test")
37 }
38
39 TestHelper.WithDataPlaneNamespace(context.Background(), "dualstack-test", map[string]string{}, t, func(t *testing.T, ns string) {
40 out, err := TestHelper.Kubectl("",
41 "create", "configmap", "go-app",
42 "--from-file=main.go=testdata/ipfamilies-server.go",
43 "-n", ns,
44 )
45 if err != nil {
46 testutil.AnnotatedFatalf(t, "unexpected error", "unexpected error: %v\noutput:\n%s", err, out)
47 }
48
49 out, err = TestHelper.Kubectl("",
50 "apply", "-f", "testdata/ipfamilies-server-client.yml",
51 "-n", ns,
52 )
53 if err != nil {
54 testutil.AnnotatedFatalf(t, "unexpected error", "unexpected error: %v\noutput:\n%s", err, out)
55 }
56
57 checkPods(t, ns, "ipfamilies-server")
58 checkPods(t, ns, "client")
59
60 var clientIPv6, serverIPv4, serverIPv6 string
61
62 t.Run("Retrieve pod IPs", func(t *testing.T) {
63 cmd := []string{
64 "get", "po",
65 "-o", "jsonpath='{.items[*].status.podIPs}'",
66 "-n", ns,
67 }
68
69 out, err = TestHelper.Kubectl("", append(cmd, "-l", "app=server")...)
70 if err != nil {
71 testutil.AnnotatedFatalf(t, "unexpected error", "unexpected error: %v\noutput:\n%s", err, out)
72 }
73
74 var IPs []IP
75 out = strings.Trim(out, "'")
76 if err = json.Unmarshal([]byte(out), &IPs); err != nil {
77 testutil.AnnotatedFatalf(t, "error unmarshaling JSON", "error unmarshaling JSON '%s': %s", out, err)
78 }
79 if len(IPs) != 2 {
80 testutil.AnnotatedFatalf(t, "unexpected number of IPs", "expected 2 IPs, got %s", fmt.Sprint(len(IPs)))
81 }
82 serverIPv4 = IPs[0].IP
83 serverIPv6 = IPs[1].IP
84
85 out, err = TestHelper.Kubectl("", append(cmd, "-l", "app=client")...)
86 if err != nil {
87 testutil.AnnotatedFatalf(t, "unexpected error", "unexpected error: %v\noutput:\n%s", err, out)
88 }
89
90 out = strings.Trim(out, "'")
91 if err = json.Unmarshal([]byte(out), &IPs); err != nil {
92 testutil.AnnotatedFatalf(t, "error unmarshaling JSON", "error unmarshaling JSON '%s': %s", out, err)
93 }
94 if len(IPs) != 2 {
95 testutil.AnnotatedFatalf(t, "unexpected number of IPs", "expected 2 IPs, got %s", fmt.Sprint(len(IPs)))
96 }
97 clientIPv6 = IPs[1].IP
98 })
99
100 t.Run("Apply policy", func(t *testing.T) {
101 file, err := os.Open("testdata/ipfamilies-policy.yml")
102 if err != nil {
103 testutil.AnnotatedFatalf(t, "unexpected error", "unexpected error: %v", err)
104 }
105 defer file.Close()
106 manifest, err := io.ReadAll(file)
107 if err != nil {
108 testutil.AnnotatedFatalf(t, "unexpected error", "unexpected error: %v", err)
109 }
110 in := strings.ReplaceAll(string(manifest), "{IPv6}", clientIPv6)
111 out, err = TestHelper.KubectlApply(in, ns)
112 if err != nil {
113 testutil.AnnotatedFatalf(t, "unexpected error", "unexpected error: %v\noutput:\n%s", err, out)
114 }
115 })
116
117 t.Run("Hit IPv4 addr directly", func(t *testing.T) {
118 out, err = TestHelper.Kubectl("",
119 "exec", "deploy/client",
120 "-c", "curl",
121 "-n", ns,
122 "--",
123 "curl", "-s", "http://"+serverIPv4+":8080",
124 )
125 if err != nil {
126 testutil.AnnotatedFatalf(t, "unexpected error", "unexpected error: %v\noutput:\n%s", err, out)
127 }
128 if out != "IPv4\n" {
129 testutil.AnnotatedFatalf(t, "unexpected output", "expected 'IPv4', received '%s'", out)
130 }
131 })
132
133 t.Run("Hit IPv6 addr directly", func(t *testing.T) {
134 out, err = TestHelper.Kubectl("",
135 "exec", "deploy/client",
136 "-c", "curl",
137 "-n", ns,
138 "--",
139 "curl", "-s", "http://["+serverIPv6+"]:8080",
140 )
141 if err != nil {
142 testutil.AnnotatedFatalf(t, "unexpected error", "unexpected error: %v\noutput:\n%s", err, out)
143 }
144 if out != "IPv6\n" {
145 testutil.AnnotatedFatalf(t, "expected 'IPv6', received '%s'", out)
146 }
147 })
148
149 t.Run("Hit FQDN directly (should always resolve to IPv6)", func(t *testing.T) {
150 for i := 0; i < 10; i++ {
151 out, err = TestHelper.Kubectl("",
152 "exec", "deploy/client",
153 "-c", "curl",
154 "-n", ns,
155 "--",
156 "curl", "-s", "http://ipfamilies-server:8080",
157 )
158 if err != nil {
159 testutil.AnnotatedFatalf(t, "unexpected error", "unexpected error: %v\noutput:\n%s", err, out)
160 }
161 if out != "IPv6\n" {
162 testutil.AnnotatedFatalf(t, "expected 'IPv6', received '%s'", out)
163 }
164 }
165 })
166 })
167 }
168
169 func checkPods(t *testing.T, ns, pod string) {
170 t.Helper()
171
172 if err := TestHelper.CheckPods(context.Background(), ns, pod, 1); err != nil {
173
174 if rce, ok := err.(*testutil.RestartCountError); ok {
175 testutil.AnnotatedWarn(t, "CheckPods timed-out", rce)
176 } else {
177 testutil.AnnotatedError(t, "CheckPods timed-out", err)
178 }
179 }
180 }
181
View as plain text