1 package envoytest
2
3 import (
4 "context"
5 "fmt"
6 "io"
7 "io/ioutil"
8 "net"
9 "net/http"
10 "os"
11 "path"
12 "strings"
13 "sync"
14 "sync/atomic"
15 "testing"
16 "time"
17
18 "github.com/pkg/errors"
19 "github.com/stretchr/testify/require"
20
21 "github.com/datawire/dlib/dexec"
22 "github.com/datawire/dlib/dhttp"
23 "github.com/datawire/dlib/dlog"
24 )
25
26 func GetLoopbackAddr(ctx context.Context, port int) (string, error) {
27 ip, err := GetLoopbackIp(ctx)
28 if err != nil {
29 return "", err
30 }
31 return fmt.Sprintf("%s:%d", ip, port), nil
32 }
33
34 func GetLoopbackIp(ctx context.Context) (string, error) {
35 if _, err := dexec.LookPath("envoy"); err == nil {
36 return "127.0.0.1", nil
37 }
38 cmd := dexec.CommandContext(ctx, "docker", "network", "inspect", "bridge", "--format={{(index .IPAM.Config 0).Gateway}}")
39 bs, err := cmd.Output()
40 if err != nil {
41 return "", errors.Wrapf(err, "error finding loopback ip")
42 }
43 return strings.TrimSpace(string(bs)), nil
44 }
45
46 var cidCounter int64
47
48
49
50
51 func SetupEnvoy(t *testing.T, adsAddress string, portmaps ...string) {
52 ctx := dlog.NewTestContext(t, false)
53
54 host, port, err := net.SplitHostPort(adsAddress)
55 require.NoError(t, err)
56
57 yaml := fmt.Sprintf(bootstrap, host, port)
58
59 var cmd *dexec.Cmd
60 var cidfile string
61 if _, err := dexec.LookPath("envoy"); err == nil {
62 cmd = dexec.CommandContext(ctx, "envoy", "--config-yaml", yaml)
63 } else {
64 counter := atomic.AddInt64(&cidCounter, 1)
65 cidfile = path.Join(os.TempDir(), fmt.Sprintf("envoy-%d-%d-cid", os.Getpid(), counter))
66
67 args := []string{"docker", "run", "--cidfile", cidfile}
68 for _, pm := range portmaps {
69 args = append(args, "-p", pm)
70 }
71 args = append(args, "--rm", "--entrypoint", "envoy", "docker.io/datawire/aes:1.6.2", "--config-yaml", yaml)
72 cmd = dexec.CommandContext(ctx, args[0], args[1:]...)
73 }
74
75 var out io.Writer
76 if os.Getenv("SHUTUP_ENVOY") != "" {
77 var err error
78 out, err = os.OpenFile(os.DevNull, os.O_WRONLY, 0)
79 if err != nil {
80 t.Error(err)
81 return
82 }
83 }
84 cmd.Stdout = out
85 cmd.Stderr = out
86 if err := cmd.Start(); err != nil {
87 t.Errorf("error starting envoy: %v", err)
88 return
89 }
90
91 if cidfile == "" {
92
93 t.Cleanup(func() {
94 if err := cmd.Process.Kill(); err != nil {
95 t.Error(err)
96 }
97 if _, err := cmd.Process.Wait(); err != nil {
98 t.Errorf("error tearing down envoy: %+v", err)
99 }
100 })
101 } else {
102
103 t.Cleanup(func() {
104
105 delay := 1 * time.Second
106 var cidBytes []byte
107 for {
108 var err error
109 cidBytes, err = ioutil.ReadFile(cidfile)
110 if err != nil {
111 if delay < 8*time.Second {
112 time.Sleep(delay)
113 delay = 2 * delay
114 continue
115 }
116
117 t.Logf("error reading envoy container id: %+v", err)
118 return
119 }
120 break
121 }
122 defer os.Remove(cidfile)
123
124 cid := strings.TrimSpace(string(cidBytes))
125
126 if err := dexec.CommandContext(ctx, "docker", "kill", cid).Run(); err != nil {
127 t.Logf("error killing envoy container %s: %+v", cid, err)
128 return
129 }
130
131 if err := dexec.CommandContext(ctx, "docker", "wait", cid).Run(); err != nil {
132
133
134 if !strings.Contains(err.Error(), "No such container") {
135 t.Logf("error waiting for envoy container %s: %+v", cid, err)
136 return
137 }
138 }
139 })
140 }
141 }
142
143
144
145 const bootstrap = `
146 {
147 "node": {
148 "cluster": "ambassador-default",
149 "id": "test-id"
150 },
151 "layered_runtime": {
152 "layers": [
153 {
154 "name": "static_layer",
155 "static_layer": {
156 "envoy.deprecated_features:envoy.api.v2.route.HeaderMatcher.regex_match": true,
157 "envoy.deprecated_features:envoy.api.v2.route.RouteMatch.regex": true,
158 "envoy.deprecated_features:envoy.config.filter.http.ext_authz.v2.ExtAuthz.use_alpha": true,
159 "envoy.deprecated_features:envoy.config.trace.v2.ZipkinConfig.HTTP_JSON_V1": true,
160 "envoy.reloadable_features.ext_authz_http_service_enable_case_sensitive_string_matcher": false
161 }
162 }
163 ]
164 },
165 "dynamic_resources": {
166 "ads_config": {
167 "api_type": "GRPC",
168 "grpc_services": [
169 {
170 "envoy_grpc": {
171 "cluster_name": "ads_cluster"
172 }
173 }
174 ]
175 },
176 "cds_config": {
177 "ads": {}
178 },
179 "lds_config": {
180 "ads": {}
181 }
182 },
183 "static_resources": {
184 "clusters": [
185 {
186 "connect_timeout": "1s",
187 "dns_lookup_family": "V4_ONLY",
188 "http2_protocol_options": {},
189 "lb_policy": "ROUND_ROBIN",
190 "load_assignment": {
191 "cluster_name": "ads_cluster",
192 "endpoints": [
193 {
194 "lb_endpoints": [
195 {
196 "endpoint": {
197 "address": {
198 "socket_address": {
199 "address": "%s",
200 "port_value": %s,
201 "protocol": "TCP"
202 }
203 }
204 }
205 }
206 ]
207 }
208 ]
209 },
210 "name": "ads_cluster"
211 }
212 ]
213 }
214 }
215 `
216
217
218
219 func SetupRequestLogger(t *testing.T, addresses ...string) *RequestLogger {
220 rl := NewRequestLogger()
221 SetupServer(t, rl, addresses...)
222 return rl
223 }
224
225 type RequestLogger struct {
226 Requests []*http.Request
227 }
228
229 var _ http.Handler = &RequestLogger{}
230
231 func NewRequestLogger() *RequestLogger {
232 return &RequestLogger{}
233 }
234
235 func (rl *RequestLogger) ServeHTTP(w http.ResponseWriter, r *http.Request) {
236 rl.Log(r)
237 _, _ = w.Write([]byte("Hello World"))
238 }
239
240 func (rl *RequestLogger) Log(r *http.Request) {
241 rl.Requests = append(rl.Requests, r)
242 }
243
244
245
246 func SetupServer(t *testing.T, handler http.Handler, addresses ...string) {
247 ctx, cancel := context.WithCancel(dlog.NewTestContext(t, false))
248 wg := &sync.WaitGroup{}
249 t.Cleanup(func() {
250 cancel()
251 wg.Wait()
252 })
253
254 sc := &dhttp.ServerConfig{Handler: handler}
255 for _, address := range addresses {
256
257 addr := address
258 wg.Add(1)
259 go func() {
260 err := sc.ListenAndServe(ctx, addr)
261 if err != nil && err != context.Canceled {
262 t.Errorf("server exited with error: %+v", err)
263 }
264 wg.Done()
265 }()
266 }
267 }
268
View as plain text