1 package envoytest
2
3 import (
4 "context"
5 "fmt"
6 "net"
7 "net/http"
8 "os"
9 "path/filepath"
10 "strings"
11 "sync"
12
13 "github.com/pkg/errors"
14
15 "github.com/datawire/dlib/dexec"
16 "github.com/datawire/dlib/dgroup"
17 "github.com/datawire/dlib/dhttp"
18 )
19
20 func GetLoopbackAddr(ctx context.Context, port int) (string, error) {
21 ip, err := GetLoopbackIp(ctx)
22 if err != nil {
23 return "", err
24 }
25 return fmt.Sprintf("%s:%d", ip, port), nil
26 }
27
28 func GetLoopbackIp(ctx context.Context) (string, error) {
29 cmd := dexec.CommandContext(ctx, "docker", "network", "inspect", "bridge", "--format={{(index .IPAM.Config 0).Gateway}}")
30 bs, err := cmd.Output()
31 if err != nil {
32 return "", errors.Wrapf(err, "error finding loopback ip")
33 }
34 return strings.TrimSpace(string(bs)), nil
35 }
36
37 func getOSSHome(ctx context.Context) (string, error) {
38 dat, err := dexec.CommandContext(ctx, "git", "rev-parse", "--show-toplevel").Output()
39 if err != nil {
40 return "", err
41 }
42 return strings.TrimSpace(string(dat)), nil
43 }
44
45 func getLocalEnvoyImage(ctx context.Context) (string, error) {
46
47
48 if env := os.Getenv("ENVOY_DOCKER_TAG"); env != "" {
49 return env, nil
50 }
51
52 ossHome, err := getOSSHome(ctx)
53 if err != nil {
54 return "", err
55 }
56
57 if err := dexec.CommandContext(ctx, "make", "-C", ossHome, "docker/base-envoy.docker.tag.local").Run(); err != nil {
58 return "", err
59 }
60 dat, err := os.ReadFile(filepath.Join(ossHome, "docker/base-envoy.docker"))
61 if err != nil {
62 return "", err
63 }
64 return strings.TrimSpace(string(dat)), nil
65 }
66
67 var (
68 cacheDevNullMu sync.Mutex
69 cacheDevNull *os.File
70 )
71
72 func getDevNull() (*os.File, error) {
73 cacheDevNullMu.Lock()
74 defer cacheDevNullMu.Unlock()
75 if cacheDevNull != nil {
76 return cacheDevNull, nil
77 }
78 var err error
79 cacheDevNull, err = os.OpenFile(os.DevNull, os.O_WRONLY, 0)
80 return cacheDevNull, err
81 }
82
83 func LocalEnvoyCmd(ctx context.Context, dockerFlags, envoyFlags []string) (*dexec.Cmd, error) {
84 image, err := getLocalEnvoyImage(ctx)
85 if err != nil {
86 return nil, err
87 }
88
89 cmdline := []string{"docker", "run", "--rm"}
90 cmdline = append(cmdline, dockerFlags...)
91 cmdline = append(cmdline, image, "/usr/local/bin/envoy-static-stripped")
92 cmdline = append(cmdline, envoyFlags...)
93
94 cmd := dexec.CommandContext(ctx, cmdline[0], cmdline[1:]...)
95 if os.Getenv("DEV_SHUTUP_ENVOY") != "" {
96 devNull, _ := getDevNull()
97 cmd.Stdout = devNull
98 cmd.Stderr = devNull
99 }
100 return cmd, nil
101 }
102
103
104
105
106 func RunEnvoy(ctx context.Context, adsAddress string, portmaps ...string) error {
107 dockerFlags := []string{
108 "--interactive",
109 }
110 for _, pm := range portmaps {
111 dockerFlags = append(dockerFlags,
112 "--publish="+pm)
113 }
114
115 host, port, err := net.SplitHostPort(adsAddress)
116 if err != nil {
117 return err
118 }
119 envoyFlags := []string{
120 "--config-yaml", fmt.Sprintf(bootstrap, host, port),
121 }
122
123 cmd, err := LocalEnvoyCmd(ctx, dockerFlags, envoyFlags)
124 if err != nil {
125 return err
126 }
127
128 return cmd.Run()
129 }
130
131
132
133
134 const bootstrap = `
135 {
136 "node": {
137 "cluster": "ambassador-default",
138 "id": "test-id"
139 },
140 "layered_runtime": {
141 "layers": [
142 {
143 "name": "static_layer",
144 "static_layer": {
145 "envoy.reloadable_features.no_extension_lookup_by_name": false,
146 "re2.max_program_size.error_level": 200
147 }
148 }
149 ]
150 },
151 "dynamic_resources": {
152 "ads_config": {
153 "api_type": "GRPC",
154 "grpc_services": [
155 {
156 "envoy_grpc": {
157 "cluster_name": "ads_cluster"
158 }
159 }
160 ],
161 "transport_api_version": "V3"
162 },
163 "cds_config": {
164 "ads": {},
165 "resource_api_version": "V3"
166 },
167 "lds_config": {
168 "ads": {},
169 "resource_api_version": "V3"
170 }
171 },
172 "static_resources": {
173 "clusters": [
174 {
175 "connect_timeout": "1s",
176 "dns_lookup_family": "V4_ONLY",
177 "http2_protocol_options": {},
178 "lb_policy": "ROUND_ROBIN",
179 "load_assignment": {
180 "cluster_name": "ads_cluster",
181 "endpoints": [
182 {
183 "lb_endpoints": [
184 {
185 "endpoint": {
186 "address": {
187 "socket_address": {
188 "address": "%s",
189 "port_value": %s,
190 "protocol": "TCP"
191 }
192 }
193 }
194 }
195 ]
196 }
197 ]
198 },
199 "name": "ads_cluster"
200 }
201 ]
202 }
203 }
204 `
205
206
207
208 type RequestLogger struct {
209 Requests []*http.Request
210 }
211
212 func (rl *RequestLogger) Log(r *http.Request) {
213 rl.Requests = append(rl.Requests, r)
214 }
215
216 func (rl *RequestLogger) ListenAndServeHTTP(ctx context.Context, addresses ...string) error {
217 sc := &dhttp.ServerConfig{
218 Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
219 rl.Log(r)
220 _, _ = w.Write([]byte("Hello World"))
221 }),
222 }
223
224 grp := dgroup.NewGroup(ctx, dgroup.GroupConfig{
225 ShutdownOnNonError: true,
226 })
227
228 for _, addr := range addresses {
229 addr := addr
230 grp.Go(addr, func(ctx context.Context) error {
231 return sc.ListenAndServe(ctx, addr)
232 })
233 }
234
235 return grp.Wait()
236 }
237
View as plain text