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