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