blob: c5c8edbb8297e5bd1c004bbf4279a07e343a92b9 [file] [log] [blame]
Maros Ondrejickaffa3f602023-01-26 10:07:29 +01001package main
2
3import (
adrianvillin7c675472024-02-12 02:44:53 -05004 "bufio"
Filip Tehlar4b3598e2023-09-02 08:54:21 +02005 "errors"
Filip Tehlar671cf512023-01-31 10:34:18 +01006 "flag"
Filip Tehlar4b3598e2023-09-02 08:54:21 +02007 "fmt"
Maros Ondrejickaffa3f602023-01-26 10:07:29 +01008 "os"
Filip Tehlar31eaea92023-06-15 10:06:57 +02009 "os/exec"
10 "strings"
Maros Ondrejickaa2d52622023-02-24 11:26:39 +010011 "time"
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010012
13 "github.com/edwarnicke/exechelper"
14 "github.com/stretchr/testify/assert"
15 "github.com/stretchr/testify/suite"
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010016 "gopkg.in/yaml.v3"
17)
18
Maros Ondrejicka2908f8c2023-02-02 08:58:04 +010019const (
Filip Tehlar608d0062023-04-28 10:29:47 +020020 DEFAULT_NETWORK_NUM int = 1
Maros Ondrejicka2908f8c2023-02-02 08:58:04 +010021)
22
Maros Ondrejickae7625d02023-02-28 16:55:01 +010023var isPersistent = flag.Bool("persist", false, "persists topology config")
24var isVerbose = flag.Bool("verbose", false, "verbose test output")
25var isUnconfiguring = flag.Bool("unconfigure", false, "remove topology")
26var isVppDebug = flag.Bool("debug", false, "attach gdb to vpp")
Filip Tehlar608d0062023-04-28 10:29:47 +020027var nConfiguredCpus = flag.Int("cpus", 1, "number of CPUs assigned to vpp")
Filip Tehlar109f3ce2023-09-05 15:36:28 +020028var vppSourceFileDir = flag.String("vppsrc", "", "vpp source file directory")
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010029
30type HstSuite struct {
31 suite.Suite
Filip Tehlar3a910ab2023-06-08 17:39:39 +020032 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
adrianvillin28bd8f02024-02-13 06:00:02 -050041 pid string
Filip Tehlar608d0062023-04-28 10:29:47 +020042}
43
44func (s *HstSuite) SetupSuite() {
45 var err error
adrianvillin28bd8f02024-02-13 06:00:02 -050046 s.pid = fmt.Sprint(os.Getpid())
Filip Tehlar608d0062023-04-28 10:29:47 +020047 s.cpuAllocator, err = CpuAllocator()
48 if err != nil {
49 s.FailNow("failed to init cpu allocator: %v", err)
50 }
51 s.cpuPerVpp = *nConfiguredCpus
52}
53
54func (s *HstSuite) AllocateCpus() []int {
55 cpuCtx, err := s.cpuAllocator.Allocate(s.cpuPerVpp)
56 s.assertNil(err)
57 s.AddCpuContext(cpuCtx)
58 return cpuCtx.cpus
59}
60
61func (s *HstSuite) AddCpuContext(cpuCtx *CpuContext) {
62 s.cpuContexts = append(s.cpuContexts, cpuCtx)
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010063}
64
65func (s *HstSuite) TearDownSuite() {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010066 s.unconfigureNetworkTopology()
67}
68
69func (s *HstSuite) TearDownTest() {
Maros Ondrejickae7625d02023-02-28 16:55:01 +010070 if *isPersistent {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010071 return
72 }
Filip Tehlar608d0062023-04-28 10:29:47 +020073 for _, c := range s.cpuContexts {
74 c.Release()
75 }
Maros Ondrejickae7625d02023-02-28 16:55:01 +010076 s.resetContainers()
77 s.removeVolumes()
adrianvillin28bd8f02024-02-13 06:00:02 -050078 s.ip4AddrAllocator.deleteIpAddresses()
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010079}
80
Maros Ondrejickaaf004dd2023-02-27 16:52:57 +010081func (s *HstSuite) skipIfUnconfiguring() {
Maros Ondrejickae7625d02023-02-28 16:55:01 +010082 if *isUnconfiguring {
Maros Ondrejickaaf004dd2023-02-27 16:52:57 +010083 s.skip("skipping to unconfigure")
84 }
85}
86
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010087func (s *HstSuite) SetupTest() {
Maros Ondrejickaaf004dd2023-02-27 16:52:57 +010088 s.skipIfUnconfiguring()
Maros Ondrejickae7625d02023-02-28 16:55:01 +010089 s.setupVolumes()
90 s.setupContainers()
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010091}
92
Maros Ondrejickae7625d02023-02-28 16:55:01 +010093func (s *HstSuite) setupVolumes() {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010094 for _, volume := range s.volumes {
95 cmd := "docker volume create --name=" + volume
96 s.log(cmd)
97 exechelper.Run(cmd)
98 }
99}
100
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100101func (s *HstSuite) setupContainers() {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100102 for _, container := range s.containers {
Filip Tehlar608d0062023-04-28 10:29:47 +0200103 if !container.isOptional {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100104 container.run()
105 }
106 }
107}
108
adrianvillin7c675472024-02-12 02:44:53 -0500109func logVppInstance(container *Container, maxLines int){
110 if container.vppInstance == nil{
111 return
112 }
113
114 logSource := container.getHostWorkDir() + defaultLogFilePath
115 file, err := os.Open(logSource)
116
117 if err != nil{
118 return
119 }
120 defer file.Close()
121
122 scanner := bufio.NewScanner(file)
123 var lines []string
124 var counter int
125
126 for scanner.Scan(){
127 lines = append(lines, scanner.Text())
128 counter++
129 if counter > maxLines {
130 lines = lines[1:]
131 counter--
132 }
133 }
134
135 fmt.Println("vvvvvvvvvvvvvvv " + container.name + " [VPP instance]:")
136 for _, line := range lines{
137 fmt.Println(line)
138 }
139 fmt.Printf("^^^^^^^^^^^^^^^\n\n")
adrianvillin7c675472024-02-12 02:44:53 -0500140}
141
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100142func (s *HstSuite) hstFail() {
adrianvillin7c675472024-02-12 02:44:53 -0500143 fmt.Println("Containers: " + fmt.Sprint(s.containers))
144 for _, container := range s.containers{
145 out, err := container.log(20)
146 if err != nil{
147 fmt.Printf("An error occured while obtaining '%s' container logs: %s\n", container.name, fmt.Sprint(err))
148 break
149 }
150 fmt.Printf("\nvvvvvvvvvvvvvvv " +
151 container.name + ":\n" +
152 out +
153 "^^^^^^^^^^^^^^^\n\n")
154 logVppInstance(container, 20)
155 }
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100156 s.T().FailNow()
157}
158
159func (s *HstSuite) assertNil(object interface{}, msgAndArgs ...interface{}) {
160 if !assert.Nil(s.T(), object, msgAndArgs...) {
161 s.hstFail()
162 }
163}
164
165func (s *HstSuite) assertNotNil(object interface{}, msgAndArgs ...interface{}) {
166 if !assert.NotNil(s.T(), object, msgAndArgs...) {
167 s.hstFail()
168 }
169}
170
171func (s *HstSuite) assertEqual(expected, actual interface{}, msgAndArgs ...interface{}) {
172 if !assert.Equal(s.T(), expected, actual, msgAndArgs...) {
173 s.hstFail()
174 }
175}
176
177func (s *HstSuite) assertNotEqual(expected, actual interface{}, msgAndArgs ...interface{}) {
178 if !assert.NotEqual(s.T(), expected, actual, msgAndArgs...) {
179 s.hstFail()
180 }
181}
182
183func (s *HstSuite) assertContains(testString, contains interface{}, msgAndArgs ...interface{}) {
184 if !assert.Contains(s.T(), testString, contains, msgAndArgs...) {
185 s.hstFail()
186 }
187}
188
189func (s *HstSuite) assertNotContains(testString, contains interface{}, msgAndArgs ...interface{}) {
190 if !assert.NotContains(s.T(), testString, contains, msgAndArgs...) {
191 s.hstFail()
192 }
193}
194
Maros Ondrejicka2ddb2fd2023-02-15 17:44:46 +0100195func (s *HstSuite) assertNotEmpty(object interface{}, msgAndArgs ...interface{}) {
196 if !assert.NotEmpty(s.T(), object, msgAndArgs...) {
197 s.hstFail()
198 }
199}
200
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100201func (s *HstSuite) log(args ...any) {
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100202 if *isVerbose {
Maros Ondrejickaaf004dd2023-02-27 16:52:57 +0100203 s.T().Helper()
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100204 s.T().Log(args...)
205 }
206}
207
208func (s *HstSuite) skip(args ...any) {
209 s.log(args...)
210 s.T().SkipNow()
211}
212
Filip Tehlar608d0062023-04-28 10:29:47 +0200213func (s *HstSuite) SkipIfMultiWorker(args ...any) {
214 if *nConfiguredCpus > 1 {
215 s.skip("test case not supported with multiple vpp workers")
216 }
217}
218
Filip Tehlar31eaea92023-06-15 10:06:57 +0200219func (s *HstSuite) SkipUnlessExtendedTestsBuilt() {
220 imageName := "hs-test/nginx-http3"
221
222 cmd := exec.Command("docker", "images", imageName)
223 byteOutput, err := cmd.CombinedOutput()
224 if err != nil {
225 s.log("error while searching for docker image")
226 return
227 }
228 if !strings.Contains(string(byteOutput), imageName) {
229 s.skip("extended tests not built")
230 }
231}
232
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100233func (s *HstSuite) resetContainers() {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100234 for _, container := range s.containers {
235 container.stop()
236 }
237}
238
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100239func (s *HstSuite) removeVolumes() {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100240 for _, volumeName := range s.volumes {
241 cmd := "docker volume rm " + volumeName
242 exechelper.Run(cmd)
243 os.RemoveAll(volumeName)
244 }
245}
246
adrianvillin28bd8f02024-02-13 06:00:02 -0500247func (s *HstSuite) getNetNamespaceByName(name string) string {
248 return name + s.pid
249}
250
251func (s *HstSuite) getInterfaceByName(name string) *NetInterface {
252 return s.netInterfaces[name + s.pid]
253}
254
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100255func (s *HstSuite) getContainerByName(name string) *Container {
adrianvillin28bd8f02024-02-13 06:00:02 -0500256 return s.containers[name + s.pid]
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100257}
258
Maros Ondrejicka2908f8c2023-02-02 08:58:04 +0100259/*
260 * Create a copy and return its address, so that individial tests which call this
261 * are not able to modify the original container and affect other tests by doing that
262 */
263func (s *HstSuite) getTransientContainerByName(name string) *Container {
adrianvillin28bd8f02024-02-13 06:00:02 -0500264 containerCopy := *s.containers[name + s.pid]
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100265 return &containerCopy
266}
267
268func (s *HstSuite) loadContainerTopology(topologyName string) {
adrianvillin707210c2024-01-24 01:45:59 -0500269 data, err := os.ReadFile(containerTopologyDir + topologyName + ".yaml")
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100270 if err != nil {
271 s.T().Fatalf("read error: %v", err)
272 }
273 var yamlTopo YamlTopology
274 err = yaml.Unmarshal(data, &yamlTopo)
275 if err != nil {
276 s.T().Fatalf("unmarshal error: %v", err)
277 }
278
279 for _, elem := range yamlTopo.Volumes {
280 volumeMap := elem["volume"].(VolumeConfig)
281 hostDir := volumeMap["host-dir"].(string)
adrianvillin28bd8f02024-02-13 06:00:02 -0500282 workingVolumeDir := logDir + s.T().Name() + s.pid + volumeDir
Filip Tehlara1bd50c2024-01-24 11:59:44 +0100283 volDirReplacer := strings.NewReplacer("$HST_VOLUME_DIR", workingVolumeDir)
284 hostDir = volDirReplacer.Replace(hostDir)
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100285 s.volumes = append(s.volumes, hostDir)
286 }
287
288 s.containers = make(map[string]*Container)
289 for _, elem := range yamlTopo.Containers {
adrianvillin28bd8f02024-02-13 06:00:02 -0500290 newContainer, err := newContainer(s, elem, s.pid)
291 newContainer.suite = s
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100292 if err != nil {
293 s.T().Fatalf("container config error: %v", err)
294 }
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100295 s.containers[newContainer.name] = newContainer
296 }
297}
298
299func (s *HstSuite) loadNetworkTopology(topologyName string) {
adrianvillin707210c2024-01-24 01:45:59 -0500300 data, err := os.ReadFile(networkTopologyDir + topologyName + ".yaml")
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100301 if err != nil {
302 s.T().Fatalf("read error: %v", err)
303 }
304 var yamlTopo YamlTopology
305 err = yaml.Unmarshal(data, &yamlTopo)
306 if err != nil {
307 s.T().Fatalf("unmarshal error: %v", err)
308 }
309
Filip Tehlar3a910ab2023-06-08 17:39:39 +0200310 s.ip4AddrAllocator = NewIp4AddressAllocator()
Maros Ondrejicka40cba402023-02-23 13:19:15 +0100311 s.netInterfaces = make(map[string]*NetInterface)
adrianvillin28bd8f02024-02-13 06:00:02 -0500312
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100313 for _, elem := range yamlTopo.Devices {
adrianvillin28bd8f02024-02-13 06:00:02 -0500314 if _, ok := elem["name"]; ok {
315 elem["name"] = elem["name"].(string) + s.pid
316 }
317
318 if peer, ok := elem["peer"].(NetDevConfig); ok {
319 if peer["name"].(string) != ""{
320 peer["name"] = peer["name"].(string) + s.pid
321 }
322 if _, ok := peer["netns"]; ok{
323 peer["netns"] = peer["netns"].(string) + s.pid
324 }
325 }
326
327 if _, ok := elem["netns"]; ok {
328 elem["netns"] = elem["netns"].(string) + s.pid
329 }
330
331 if _, ok := elem["interfaces"]; ok {
332 interfaceCount := len(elem["interfaces"].([]interface{}))
333 for i := 0; i < interfaceCount; i++ {
334 elem["interfaces"].([]interface{})[i] = elem["interfaces"].([]interface{})[i].(string) + s.pid
335 }
336 }
337
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100338 switch elem["type"].(string) {
339 case NetNs:
340 {
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100341 if namespace, err := newNetNamespace(elem); err == nil {
Maros Ondrejicka2908f8c2023-02-02 08:58:04 +0100342 s.netConfigs = append(s.netConfigs, &namespace)
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100343 } else {
344 s.T().Fatalf("network config error: %v", err)
345 }
346 }
Maros Ondrejicka40cba402023-02-23 13:19:15 +0100347 case Veth, Tap:
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100348 {
Filip Tehlar3a910ab2023-06-08 17:39:39 +0200349 if netIf, err := newNetworkInterface(elem, s.ip4AddrAllocator); err == nil {
Maros Ondrejicka40cba402023-02-23 13:19:15 +0100350 s.netConfigs = append(s.netConfigs, netIf)
351 s.netInterfaces[netIf.Name()] = netIf
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100352 } else {
353 s.T().Fatalf("network config error: %v", err)
354 }
355 }
356 case Bridge:
357 {
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100358 if bridge, err := newBridge(elem); err == nil {
Maros Ondrejicka2908f8c2023-02-02 08:58:04 +0100359 s.netConfigs = append(s.netConfigs, &bridge)
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100360 } else {
361 s.T().Fatalf("network config error: %v", err)
362 }
363 }
364 }
365 }
366}
367
368func (s *HstSuite) configureNetworkTopology(topologyName string) {
369 s.loadNetworkTopology(topologyName)
370
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100371 if *isUnconfiguring {
Maros Ondrejickaaf004dd2023-02-27 16:52:57 +0100372 return
373 }
374
Maros Ondrejicka2908f8c2023-02-02 08:58:04 +0100375 for _, nc := range s.netConfigs {
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100376 if err := nc.configure(); err != nil {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100377 s.T().Fatalf("network config error: %v", err)
378 }
379 }
380}
381
382func (s *HstSuite) unconfigureNetworkTopology() {
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100383 if *isPersistent {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100384 return
385 }
Maros Ondrejicka2908f8c2023-02-02 08:58:04 +0100386 for _, nc := range s.netConfigs {
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100387 nc.unconfigure()
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100388 }
389}
390
Maros Ondrejickaa2d52622023-02-24 11:26:39 +0100391func (s *HstSuite) getTestId() string {
392 testName := s.T().Name()
393
394 if s.testIds == nil {
395 s.testIds = map[string]string{}
396 }
397
398 if _, ok := s.testIds[testName]; !ok {
Filip Tehlar75776f02023-03-24 13:47:45 +0100399 s.testIds[testName] = time.Now().Format("2006-01-02_15-04-05")
Maros Ondrejickaa2d52622023-02-24 11:26:39 +0100400 }
401
402 return s.testIds[testName]
403}
Filip Tehlar4b3598e2023-09-02 08:54:21 +0200404
adrianvillin28bd8f02024-02-13 06:00:02 -0500405// Returns last 4 digits of PID
406func (s *HstSuite) getPortFromPid() string {
407 port := s.pid
408 for len(port) < 4 {
409 port += "0"
410 }
411 return port[len(port)-4:]
412}
413
Filip Tehlar4b3598e2023-09-02 08:54:21 +0200414func (s *HstSuite) startServerApp(running chan error, done chan struct{}, env []string) {
adrianvillin28bd8f02024-02-13 06:00:02 -0500415 cmd := exec.Command("iperf3", "-4", "-s", "-p", s.getPortFromPid())
Filip Tehlar4b3598e2023-09-02 08:54:21 +0200416 if env != nil {
417 cmd.Env = env
418 }
419 s.log(cmd)
420 err := cmd.Start()
421 if err != nil {
422 msg := fmt.Errorf("failed to start iperf server: %v", err)
423 running <- msg
424 return
425 }
426 running <- nil
427 <-done
428 cmd.Process.Kill()
429}
430
431func (s *HstSuite) startClientApp(ipAddress string, env []string, clnCh chan error, clnRes chan string) {
432 defer func() {
433 clnCh <- nil
434 }()
435
436 nTries := 0
437
438 for {
adrianvillin28bd8f02024-02-13 06:00:02 -0500439 cmd := exec.Command("iperf3", "-c", ipAddress, "-u", "-l", "1460", "-b", "10g", "-p", s.getPortFromPid())
Filip Tehlar4b3598e2023-09-02 08:54:21 +0200440 if env != nil {
441 cmd.Env = env
442 }
443 s.log(cmd)
444 o, err := cmd.CombinedOutput()
445 if err != nil {
446 if nTries > 5 {
447 clnCh <- fmt.Errorf("failed to start client app '%s'.\n%s", err, o)
448 return
449 }
450 time.Sleep(1 * time.Second)
451 nTries++
452 continue
453 } else {
454 clnRes <- fmt.Sprintf("Client output: %s", o)
455 }
456 break
457 }
458}
459
460func (s *HstSuite) startHttpServer(running chan struct{}, done chan struct{}, addressPort, netNs string) {
adrianvillin28bd8f02024-02-13 06:00:02 -0500461 cmd := newCommand([]string{"./http_server", addressPort, s.pid}, netNs)
Filip Tehlar4b3598e2023-09-02 08:54:21 +0200462 err := cmd.Start()
463 s.log(cmd)
464 if err != nil {
adrianvillin7c675472024-02-12 02:44:53 -0500465 fmt.Println("Failed to start http server: " + fmt.Sprint(err))
Filip Tehlar4b3598e2023-09-02 08:54:21 +0200466 return
467 }
468 running <- struct{}{}
469 <-done
470 cmd.Process.Kill()
471}
472
473func (s *HstSuite) startWget(finished chan error, server_ip, port, query, netNs string) {
474 defer func() {
475 finished <- errors.New("wget error")
476 }()
477
478 cmd := newCommand([]string{"wget", "--timeout=10", "--no-proxy", "--tries=5", "-O", "/dev/null", server_ip + ":" + port + "/" + query},
479 netNs)
480 s.log(cmd)
481 o, err := cmd.CombinedOutput()
482 if err != nil {
483 finished <- fmt.Errorf("wget error: '%v\n\n%s'", err, o)
484 return
485 } else if !strings.Contains(string(o), "200 OK") {
486 finished <- fmt.Errorf("wget error: response not 200 OK")
487 return
488 }
489 finished <- nil
490}