f0ebdca5919f5268dc5a1c0a91aef217c32ff5d6
[vpp.git] / extras / hs-test / hst_suite.go
1 package main
2
3 import (
4         "errors"
5         "flag"
6         "fmt"
7         "io/ioutil"
8         "os"
9         "os/exec"
10         "strings"
11         "time"
12
13         "github.com/edwarnicke/exechelper"
14         "github.com/stretchr/testify/assert"
15         "github.com/stretchr/testify/suite"
16         "gopkg.in/yaml.v3"
17 )
18
19 const (
20         DEFAULT_NETWORK_NUM int = 1
21 )
22
23 var isPersistent = flag.Bool("persist", false, "persists topology config")
24 var isVerbose = flag.Bool("verbose", false, "verbose test output")
25 var isUnconfiguring = flag.Bool("unconfigure", false, "remove topology")
26 var isVppDebug = flag.Bool("debug", false, "attach gdb to vpp")
27 var nConfiguredCpus = flag.Int("cpus", 1, "number of CPUs assigned to vpp")
28 var vppSourceFileDir = flag.String("vppsrc", "", "vpp source file directory")
29
30 type HstSuite struct {
31         suite.Suite
32         containers       map[string]*Container
33         volumes          []string
34         netConfigs       []NetConfig
35         netInterfaces    map[string]*NetInterface
36         ip4AddrAllocator *Ip4AddressAllocator
37         testIds          map[string]string
38         cpuAllocator     *CpuAllocatorT
39         cpuContexts      []*CpuContext
40         cpuPerVpp        int
41 }
42
43 func (s *HstSuite) SetupSuite() {
44         var err error
45         s.cpuAllocator, err = CpuAllocator()
46         if err != nil {
47                 s.FailNow("failed to init cpu allocator: %v", err)
48         }
49         s.cpuPerVpp = *nConfiguredCpus
50 }
51
52 func (s *HstSuite) AllocateCpus() []int {
53         cpuCtx, err := s.cpuAllocator.Allocate(s.cpuPerVpp)
54         s.assertNil(err)
55         s.AddCpuContext(cpuCtx)
56         return cpuCtx.cpus
57 }
58
59 func (s *HstSuite) AddCpuContext(cpuCtx *CpuContext) {
60         s.cpuContexts = append(s.cpuContexts, cpuCtx)
61 }
62
63 func (s *HstSuite) TearDownSuite() {
64         s.unconfigureNetworkTopology()
65 }
66
67 func (s *HstSuite) TearDownTest() {
68         if *isPersistent {
69                 return
70         }
71         for _, c := range s.cpuContexts {
72                 c.Release()
73         }
74         s.resetContainers()
75         s.removeVolumes()
76 }
77
78 func (s *HstSuite) skipIfUnconfiguring() {
79         if *isUnconfiguring {
80                 s.skip("skipping to unconfigure")
81         }
82 }
83
84 func (s *HstSuite) SetupTest() {
85         s.skipIfUnconfiguring()
86         s.setupVolumes()
87         s.setupContainers()
88 }
89
90 func (s *HstSuite) setupVolumes() {
91         for _, volume := range s.volumes {
92                 cmd := "docker volume create --name=" + volume
93                 s.log(cmd)
94                 exechelper.Run(cmd)
95         }
96 }
97
98 func (s *HstSuite) setupContainers() {
99         for _, container := range s.containers {
100                 if !container.isOptional {
101                         container.run()
102                 }
103         }
104 }
105
106 func (s *HstSuite) hstFail() {
107         s.T().FailNow()
108 }
109
110 func (s *HstSuite) assertNil(object interface{}, msgAndArgs ...interface{}) {
111         if !assert.Nil(s.T(), object, msgAndArgs...) {
112                 s.hstFail()
113         }
114 }
115
116 func (s *HstSuite) assertNotNil(object interface{}, msgAndArgs ...interface{}) {
117         if !assert.NotNil(s.T(), object, msgAndArgs...) {
118                 s.hstFail()
119         }
120 }
121
122 func (s *HstSuite) assertEqual(expected, actual interface{}, msgAndArgs ...interface{}) {
123         if !assert.Equal(s.T(), expected, actual, msgAndArgs...) {
124                 s.hstFail()
125         }
126 }
127
128 func (s *HstSuite) assertNotEqual(expected, actual interface{}, msgAndArgs ...interface{}) {
129         if !assert.NotEqual(s.T(), expected, actual, msgAndArgs...) {
130                 s.hstFail()
131         }
132 }
133
134 func (s *HstSuite) assertContains(testString, contains interface{}, msgAndArgs ...interface{}) {
135         if !assert.Contains(s.T(), testString, contains, msgAndArgs...) {
136                 s.hstFail()
137         }
138 }
139
140 func (s *HstSuite) assertNotContains(testString, contains interface{}, msgAndArgs ...interface{}) {
141         if !assert.NotContains(s.T(), testString, contains, msgAndArgs...) {
142                 s.hstFail()
143         }
144 }
145
146 func (s *HstSuite) assertNotEmpty(object interface{}, msgAndArgs ...interface{}) {
147         if !assert.NotEmpty(s.T(), object, msgAndArgs...) {
148                 s.hstFail()
149         }
150 }
151
152 func (s *HstSuite) log(args ...any) {
153         if *isVerbose {
154                 s.T().Helper()
155                 s.T().Log(args...)
156         }
157 }
158
159 func (s *HstSuite) skip(args ...any) {
160         s.log(args...)
161         s.T().SkipNow()
162 }
163
164 func (s *HstSuite) SkipIfMultiWorker(args ...any) {
165         if *nConfiguredCpus > 1 {
166                 s.skip("test case not supported with multiple vpp workers")
167         }
168 }
169
170 func (s *HstSuite) SkipUnlessExtendedTestsBuilt() {
171         imageName := "hs-test/nginx-http3"
172
173         cmd := exec.Command("docker", "images", imageName)
174         byteOutput, err := cmd.CombinedOutput()
175         if err != nil {
176                 s.log("error while searching for docker image")
177                 return
178         }
179         if !strings.Contains(string(byteOutput), imageName) {
180                 s.skip("extended tests not built")
181         }
182 }
183
184 func (s *HstSuite) resetContainers() {
185         for _, container := range s.containers {
186                 container.stop()
187         }
188 }
189
190 func (s *HstSuite) removeVolumes() {
191         for _, volumeName := range s.volumes {
192                 cmd := "docker volume rm " + volumeName
193                 exechelper.Run(cmd)
194                 os.RemoveAll(volumeName)
195         }
196 }
197
198 func (s *HstSuite) getContainerByName(name string) *Container {
199         return s.containers[name]
200 }
201
202 /*
203  * Create a copy and return its address, so that individial tests which call this
204  * are not able to modify the original container and affect other tests by doing that
205  */
206 func (s *HstSuite) getTransientContainerByName(name string) *Container {
207         containerCopy := *s.containers[name]
208         return &containerCopy
209 }
210
211 func (s *HstSuite) loadContainerTopology(topologyName string) {
212         data, err := ioutil.ReadFile(containerTopologyDir + topologyName + ".yaml")
213         if err != nil {
214                 s.T().Fatalf("read error: %v", err)
215         }
216         var yamlTopo YamlTopology
217         err = yaml.Unmarshal(data, &yamlTopo)
218         if err != nil {
219                 s.T().Fatalf("unmarshal error: %v", err)
220         }
221
222         for _, elem := range yamlTopo.Volumes {
223                 volumeMap := elem["volume"].(VolumeConfig)
224                 hostDir := volumeMap["host-dir"].(string)
225                 s.volumes = append(s.volumes, hostDir)
226         }
227
228         s.containers = make(map[string]*Container)
229         for _, elem := range yamlTopo.Containers {
230                 newContainer, err := newContainer(elem)
231                 newContainer.suite = s
232                 if err != nil {
233                         s.T().Fatalf("container config error: %v", err)
234                 }
235                 s.containers[newContainer.name] = newContainer
236         }
237 }
238
239 func (s *HstSuite) loadNetworkTopology(topologyName string) {
240         data, err := ioutil.ReadFile(networkTopologyDir + topologyName + ".yaml")
241         if err != nil {
242                 s.T().Fatalf("read error: %v", err)
243         }
244         var yamlTopo YamlTopology
245         err = yaml.Unmarshal(data, &yamlTopo)
246         if err != nil {
247                 s.T().Fatalf("unmarshal error: %v", err)
248         }
249
250         s.ip4AddrAllocator = NewIp4AddressAllocator()
251         s.netInterfaces = make(map[string]*NetInterface)
252         for _, elem := range yamlTopo.Devices {
253                 switch elem["type"].(string) {
254                 case NetNs:
255                         {
256                                 if namespace, err := newNetNamespace(elem); err == nil {
257                                         s.netConfigs = append(s.netConfigs, &namespace)
258                                 } else {
259                                         s.T().Fatalf("network config error: %v", err)
260                                 }
261                         }
262                 case Veth, Tap:
263                         {
264                                 if netIf, err := newNetworkInterface(elem, s.ip4AddrAllocator); err == nil {
265                                         s.netConfigs = append(s.netConfigs, netIf)
266                                         s.netInterfaces[netIf.Name()] = netIf
267                                 } else {
268                                         s.T().Fatalf("network config error: %v", err)
269                                 }
270                         }
271                 case Bridge:
272                         {
273                                 if bridge, err := newBridge(elem); err == nil {
274                                         s.netConfigs = append(s.netConfigs, &bridge)
275                                 } else {
276                                         s.T().Fatalf("network config error: %v", err)
277                                 }
278                         }
279                 }
280         }
281 }
282
283 func (s *HstSuite) configureNetworkTopology(topologyName string) {
284         s.loadNetworkTopology(topologyName)
285
286         if *isUnconfiguring {
287                 return
288         }
289
290         for _, nc := range s.netConfigs {
291                 if err := nc.configure(); err != nil {
292                         s.T().Fatalf("network config error: %v", err)
293                 }
294         }
295 }
296
297 func (s *HstSuite) unconfigureNetworkTopology() {
298         if *isPersistent {
299                 return
300         }
301         for _, nc := range s.netConfigs {
302                 nc.unconfigure()
303         }
304 }
305
306 func (s *HstSuite) getTestId() string {
307         testName := s.T().Name()
308
309         if s.testIds == nil {
310                 s.testIds = map[string]string{}
311         }
312
313         if _, ok := s.testIds[testName]; !ok {
314                 s.testIds[testName] = time.Now().Format("2006-01-02_15-04-05")
315         }
316
317         return s.testIds[testName]
318 }
319
320 func (s *HstSuite) startServerApp(running chan error, done chan struct{}, env []string) {
321         cmd := exec.Command("iperf3", "-4", "-s")
322         if env != nil {
323                 cmd.Env = env
324         }
325         s.log(cmd)
326         err := cmd.Start()
327         if err != nil {
328                 msg := fmt.Errorf("failed to start iperf server: %v", err)
329                 running <- msg
330                 return
331         }
332         running <- nil
333         <-done
334         cmd.Process.Kill()
335 }
336
337 func (s *HstSuite) startClientApp(ipAddress string, env []string, clnCh chan error, clnRes chan string) {
338         defer func() {
339                 clnCh <- nil
340         }()
341
342         nTries := 0
343
344         for {
345                 cmd := exec.Command("iperf3", "-c", ipAddress, "-u", "-l", "1460", "-b", "10g")
346                 if env != nil {
347                         cmd.Env = env
348                 }
349                 s.log(cmd)
350                 o, err := cmd.CombinedOutput()
351                 if err != nil {
352                         if nTries > 5 {
353                                 clnCh <- fmt.Errorf("failed to start client app '%s'.\n%s", err, o)
354                                 return
355                         }
356                         time.Sleep(1 * time.Second)
357                         nTries++
358                         continue
359                 } else {
360                         clnRes <- fmt.Sprintf("Client output: %s", o)
361                 }
362                 break
363         }
364 }
365
366 func (s *HstSuite) startHttpServer(running chan struct{}, done chan struct{}, addressPort, netNs string) {
367         cmd := newCommand([]string{"./http_server", addressPort}, netNs)
368         err := cmd.Start()
369         s.log(cmd)
370         if err != nil {
371                 fmt.Println("Failed to start http server")
372                 return
373         }
374         running <- struct{}{}
375         <-done
376         cmd.Process.Kill()
377 }
378
379 func (s *HstSuite) startWget(finished chan error, server_ip, port, query, netNs string) {
380         defer func() {
381                 finished <- errors.New("wget error")
382         }()
383
384         cmd := newCommand([]string{"wget", "--timeout=10", "--no-proxy", "--tries=5", "-O", "/dev/null", server_ip + ":" + port + "/" + query},
385                 netNs)
386         s.log(cmd)
387         o, err := cmd.CombinedOutput()
388         if err != nil {
389                 finished <- fmt.Errorf("wget error: '%v\n\n%s'", err, o)
390                 return
391         } else if !strings.Contains(string(o), "200 OK") {
392                 finished <- fmt.Errorf("wget error: response not 200 OK")
393                 return
394         }
395         finished <- nil
396 }