1
16
17 package echo
18
19 import (
20 "bytes"
21 "context"
22 "fmt"
23 "strings"
24 "testing"
25 "time"
26
27 v1 "k8s.io/api/core/v1"
28 klabels "k8s.io/apimachinery/pkg/labels"
29 "k8s.io/client-go/kubernetes/scheme"
30 "k8s.io/client-go/rest"
31 "k8s.io/client-go/tools/remotecommand"
32 "sigs.k8s.io/controller-runtime/pkg/client"
33
34 "sigs.k8s.io/gateway-api/conformance/utils/config"
35 "sigs.k8s.io/gateway-api/conformance/utils/http"
36 "sigs.k8s.io/gateway-api/conformance/utils/suite"
37 )
38
39
40
41 type MeshPod struct {
42 Name string
43 Namespace string
44 Address string
45 rc rest.Interface
46 rcfg *rest.Config
47 }
48
49 type MeshApplication string
50
51 const (
52 MeshAppEchoV1 MeshApplication = "app=echo,version=v1"
53 MeshAppEchoV2 MeshApplication = "app=echo,version=v2"
54 )
55
56 func (m *MeshPod) MakeRequestAndExpectEventuallyConsistentResponse(t *testing.T, exp http.ExpectedResponse, timeoutConfig config.TimeoutConfig) {
57 t.Helper()
58
59 http.AwaitConvergence(t, timeoutConfig.RequiredConsecutiveSuccesses, timeoutConfig.MaxTimeToConsistency, func(elapsed time.Duration) bool {
60 req := makeRequest(t, exp.Request)
61
62 resp, err := m.request(req)
63 if err != nil {
64 t.Logf("Request %v failed, not ready yet: %v (after %v)", req, err.Error(), elapsed)
65 return false
66 }
67 t.Logf("Got resp %v", resp)
68 if err := compareRequest(exp, resp); err != nil {
69 t.Logf("Response expectation failed for request: %v not ready yet: %v (after %v)", req, err, elapsed)
70 return false
71 }
72 return true
73 })
74
75 t.Logf("Request passed")
76 }
77
78 func makeRequest(t *testing.T, r http.Request) []string {
79 protocol := strings.ToLower(r.Protocol)
80 if protocol == "" {
81 protocol = "http"
82 }
83 host := http.CalculateHost(t, r.Host, protocol)
84 args := []string{"client", fmt.Sprintf("%s://%s%s", protocol, host, r.Path)}
85 if r.Method != "" {
86 args = append(args, "--method="+r.Method)
87 }
88 for k, v := range r.Headers {
89 args = append(args, "-H", fmt.Sprintf("%v: %v", k, v))
90 }
91 return args
92 }
93
94 func compareRequest(exp http.ExpectedResponse, resp Response) error {
95 want := exp.Response
96 if fmt.Sprint(want.StatusCode) != resp.Code {
97 return fmt.Errorf("wanted status code %v, got %v", want.StatusCode, resp.Code)
98 }
99 for _, name := range want.AbsentHeaders {
100 if v := resp.ResponseHeaders.Values(name); len(v) != 0 {
101 return fmt.Errorf("expected no header %q, got %v", name, v)
102 }
103 }
104 for k, v := range want.Headers {
105 if got := resp.ResponseHeaders.Get(k); got != v {
106 return fmt.Errorf("expected header %v=%v, got %v", k, v, got)
107 }
108 }
109 if !strings.HasPrefix(resp.Hostname, exp.Backend) {
110 return fmt.Errorf("expected pod name to start with %s, got %s", exp.Backend, resp.Hostname)
111 }
112 return nil
113 }
114
115 func (m *MeshPod) request(args []string) (Response, error) {
116 container := "echo"
117
118 req := m.rc.Post().
119 Resource("pods").
120 Name(m.Name).
121 Namespace(m.Namespace).
122 SubResource("exec").
123 Param("container", container).
124 VersionedParams(&v1.PodExecOptions{
125 Container: container,
126 Command: args,
127 Stdin: false,
128 Stdout: true,
129 Stderr: true,
130 TTY: false,
131 }, scheme.ParameterCodec)
132
133 exec, err := remotecommand.NewSPDYExecutor(m.rcfg, "POST", req.URL())
134 if err != nil {
135 return Response{}, err
136 }
137
138 var stdoutBuf, stderrBuf bytes.Buffer
139 err = exec.StreamWithContext(context.Background(), remotecommand.StreamOptions{
140 Stdin: nil,
141 Stdout: &stdoutBuf,
142 Stderr: &stderrBuf,
143 Tty: false,
144 })
145 if err != nil {
146 return Response{}, err
147 }
148
149 return ParseResponse(stdoutBuf.String()), nil
150 }
151
152 func ConnectToApp(t *testing.T, s *suite.ConformanceTestSuite, app MeshApplication) MeshPod {
153 return ConnectToAppInNamespace(t, s, app, "gateway-conformance-mesh")
154 }
155
156 func ConnectToAppInNamespace(t *testing.T, s *suite.ConformanceTestSuite, app MeshApplication, ns string) MeshPod {
157 lbls, _ := klabels.Parse(string(app))
158
159 podsList := v1.PodList{}
160 err := s.Client.List(context.Background(), &podsList, client.InNamespace(ns), client.MatchingLabelsSelector{Selector: lbls})
161 if err != nil {
162 t.Fatalf("failed to query pods in app %v", app)
163 }
164 if len(podsList.Items) == 0 {
165 t.Fatalf("no pods found in app %v", app)
166 }
167 pod := podsList.Items[0]
168 podName := pod.Name
169 podNamespace := pod.Namespace
170
171 return MeshPod{
172 Name: podName,
173 Namespace: podNamespace,
174 Address: pod.Status.PodIP,
175 rc: s.Clientset.CoreV1().RESTClient(),
176 rcfg: s.RestConfig,
177 }
178 }
179
View as plain text