hs-test: add http client connect test
[vpp.git] / extras / hs-test / utils.go
1 package main
2
3 import (
4         "context"
5         "encoding/json"
6         "errors"
7         "fmt"
8         "io"
9         "io/ioutil"
10         "os"
11         "os/exec"
12         "strings"
13         "testing"
14         "time"
15
16         "github.com/edwarnicke/exechelper"
17 )
18
19 const configTemplate = `unix {
20   nodaemon
21   log %[1]s/var/log/vpp/vpp.log
22   full-coredump
23   cli-listen %[1]s/var/run/vpp/cli.sock
24   runtime-dir %[1]s/var/run
25   gid vpp
26 }
27
28 api-trace {
29   on
30 }
31
32 api-segment {
33   gid vpp
34 }
35
36 socksvr {
37   socket-name %[1]s/var/run/vpp/api.sock
38 }
39
40 statseg {
41   socket-name %[1]s/var/run/vpp/stats.sock
42 }
43
44 plugins {
45         plugin unittest_plugin.so { enable }
46     plugin dpdk_plugin.so { disable }
47     plugin crypto_aesni_plugin.so { enable }
48     plugin quic_plugin.so { enable }
49     plugin crypto_ipsecmb_plugin.so { disable }
50 }
51
52 `
53
54 const vclTemplate = `vcl {
55   app-socket-api %[1]s
56   app-scope-global
57   app-scope-local
58   namespace-id %[2]s
59   namespace-secret %[2]s
60   use-mq-eventfd
61 }
62 `
63
64 const TopologyDir string = "topo/"
65
66 type Stanza struct {
67         content string
68         pad     int
69 }
70
71 type ActionResult struct {
72         Err       error
73         Desc      string
74         ErrOutput string
75         StdOutput string
76 }
77
78 type JsonResult struct {
79         Code      int
80         Desc      string
81         ErrOutput string
82         StdOutput string
83 }
84
85 func StartServerApp(running chan error, done chan struct{}, env []string) {
86         cmd := exec.Command("iperf3", "-4", "-s")
87         if env != nil {
88                 cmd.Env = env
89         }
90         err := cmd.Start()
91         if err != nil {
92                 msg := fmt.Errorf("failed to start iperf server: %v", err)
93                 running <- msg
94                 return
95         }
96         running <- nil
97         <-done
98         cmd.Process.Kill()
99 }
100
101 func StartClientApp(env []string, clnCh chan error) {
102         defer func() {
103                 clnCh <- nil
104         }()
105
106         nTries := 0
107
108         for {
109                 cmd := exec.Command("iperf3", "-c", "10.10.10.1", "-u", "-l", "1460", "-b", "10g")
110                 if env != nil {
111                         cmd.Env = env
112                 }
113                 o, err := cmd.CombinedOutput()
114                 if err != nil {
115                         if nTries > 5 {
116                                 clnCh <- fmt.Errorf("failed to start client app '%s'.\n%s", err, o)
117                                 return
118                         }
119                         time.Sleep(1 * time.Second)
120                         nTries++
121                         continue
122                 } else {
123                         fmt.Printf("Client output: %s", o)
124                 }
125                 break
126         }
127 }
128
129 // run vpphelper in docker
130 func hstExec(args string, instance string) (string, error) {
131         syncFile := fmt.Sprintf("/tmp/%s/sync/rc", instance)
132         os.Remove(syncFile)
133
134         c := "docker exec -d " + instance + " /hs-test " + args
135         err := exechelper.Run(c)
136         if err != nil {
137                 return "", err
138         }
139
140         res, err := waitForSyncFile(syncFile)
141
142         if err != nil {
143                 return "", fmt.Errorf("failed to read sync file while executing './hs-test %s': %v", args, err)
144         }
145
146         o := res.StdOutput + res.ErrOutput
147         if res.Code != 0 {
148                 return o, fmt.Errorf("cmd resulted in non-zero value %d: %s", res.Code, res.Desc)
149         }
150         return o, err
151 }
152
153 func waitForSyncFile(fname string) (*JsonResult, error) {
154         var res JsonResult
155
156         for i := 0; i < 360; i++ {
157                 f, err := os.Open(fname)
158                 if err == nil {
159                         defer f.Close()
160
161                         data, err := ioutil.ReadFile(fname)
162                         if err != nil {
163                                 return nil, fmt.Errorf("read error: %v", err)
164                         }
165                         err = json.Unmarshal(data, &res)
166                         if err != nil {
167                                 return nil, fmt.Errorf("json unmarshal error: %v", err)
168                         }
169                         return &res, nil
170                 }
171                 time.Sleep(1 * time.Second)
172         }
173         return nil, fmt.Errorf("no sync file found")
174 }
175
176 func dockerRun(instance, args string) error {
177         exechelper.Run(fmt.Sprintf("mkdir -p /tmp/%s/sync", instance))
178         syncPath := fmt.Sprintf("-v /tmp/%s/sync:/tmp/sync", instance)
179         cmd := "docker run --cap-add=all -d --privileged --network host --rm "
180         cmd += syncPath
181         cmd += " " + args
182         cmd += " --name " + instance + " hs-test/vpp"
183         fmt.Println(cmd)
184         return exechelper.Run(cmd)
185 }
186
187 func assertFileSize(f1, f2 string) error {
188         fi1, err := os.Stat(f1)
189         if err != nil {
190                 return err
191         }
192
193         fi2, err1 := os.Stat(f2)
194         if err1 != nil {
195                 return err1
196         }
197
198         if fi1.Size() != fi2.Size() {
199                 return fmt.Errorf("file sizes differ (%d vs %d)", fi1.Size(), fi2.Size())
200         }
201         return nil
202 }
203
204 func dockerExec(cmd string, instance string) ([]byte, error) {
205         c := "docker exec -d " + instance + " " + cmd
206         return exechelper.CombinedOutput(c)
207 }
208
209 func startEnvoy(ctx context.Context, dockerInstance string) <-chan error {
210         errCh := make(chan error)
211         wd, err := os.Getwd()
212         if err != nil {
213                 errCh <- err
214                 return errCh
215         }
216
217         c := []string{"docker", "run", "--rm", "--name", "envoy",
218                 "-v", fmt.Sprintf("%s/envoy/proxy.yaml:/etc/envoy/envoy.yaml", wd),
219                 "-v", fmt.Sprintf("shared-vol:/tmp/%s", dockerInstance),
220                 "-v", fmt.Sprintf("%s/envoy:/tmp", wd),
221                 "-e", "ENVOY_UID=0",
222                 "-e", "VCL_CONFIG=/tmp/vcl.conf",
223                 "envoyproxy/envoy-contrib:v1.21-latest"}
224         fmt.Println(c)
225
226         go func(errCh chan error) {
227                 count := 0
228                 var cmd *exec.Cmd
229                 for ; ; count++ {
230                         cmd = NewCommand(c, "")
231                         err = cmd.Start()
232                         if err == nil {
233                                 break
234                         }
235                         if count > 5 {
236                                 errCh <- fmt.Errorf("failed to start envoy docker after %d attempts", count)
237                                 return
238                         }
239                 }
240
241                 err = cmd.Wait()
242                 if err != nil {
243                         errCh <- fmt.Errorf("failed to start docker: %v", err)
244                         return
245                 }
246                 <-ctx.Done()
247         }(errCh)
248         return errCh
249 }
250
251 func setupEnvoy(t *testing.T, ctx context.Context, dockerInstance string) error {
252         errCh := startEnvoy(ctx, dockerInstance)
253         select {
254         case err := <-errCh:
255                 return err
256         default:
257         }
258
259         go func(ctx context.Context, errCh <-chan error) {
260                 for {
261                         select {
262                         // handle cancel() call from outside to gracefully stop the routine
263                         case <-ctx.Done():
264                                 return
265                         default:
266                                 select {
267                                 case err := <-errCh:
268                                         fmt.Printf("error while running envoy: %v", err)
269                                 default:
270                                 }
271                         }
272                 }
273         }(ctx, errCh)
274         return nil
275 }
276
277 func configureVppProxy() error {
278         _, err := dockerExec("vppctl test proxy server server-uri tcp://10.0.0.2/555 client-uri tcp://10.0.1.1/666",
279                 "vpp-proxy")
280         if err != nil {
281                 return fmt.Errorf("error while configuring vpp proxy test: %v", err)
282         }
283         return nil
284 }
285
286 func startHttpServer(running chan struct{}, done chan struct{}, addressPort, netNs string) {
287         cmd := NewCommand([]string{"./http_server", addressPort}, netNs)
288         err := cmd.Start()
289         if err != nil {
290                 fmt.Println("Failed to start http server")
291                 return
292         }
293         running <- struct{}{}
294         <-done
295         cmd.Process.Kill()
296 }
297
298 func startWget(finished chan error, server_ip, port string, netNs string) {
299         fname := "test_file_10M"
300         defer func() {
301                 finished <- errors.New("wget error")
302         }()
303
304         cmd := NewCommand([]string{"wget", "--tries=5", "-q", "-O", "/dev/null", server_ip + ":" + port + "/" + fname},
305                 netNs)
306         o, err := cmd.CombinedOutput()
307         if err != nil {
308                 fmt.Printf("wget error: '%s'.\n%s", err, o)
309                 return
310         }
311         fmt.Printf("Client output: %s", o)
312         finished <- nil
313 }
314
315 func (c *Stanza) NewStanza(name string) *Stanza {
316         c.Append("\n" + name + " {")
317         c.pad += 2
318         return c
319 }
320
321 func (c *Stanza) Append(name string) *Stanza {
322         c.content += strings.Repeat(" ", c.pad)
323         c.content += name + "\n"
324         return c
325 }
326
327 func (c *Stanza) Close() *Stanza {
328         c.content += "}\n"
329         c.pad -= 2
330         return c
331 }
332
333 func (s *Stanza) ToString() string {
334         return s.content
335 }
336
337 func (s *Stanza) SaveToFile(fileName string) error {
338         fo, err := os.Create(fileName)
339         if err != nil {
340                 return err
341         }
342         defer fo.Close()
343
344         _, err = io.Copy(fo, strings.NewReader(s.content))
345         return err
346 }