blob: 908c046d082095f59979405487e91c6c6949a1e2 [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
Filip Tehlar608d0062023-04-28 10:29:47 +020041}
42
43func (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
52func (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
59func (s *HstSuite) AddCpuContext(cpuCtx *CpuContext) {
60 s.cpuContexts = append(s.cpuContexts, cpuCtx)
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010061}
62
63func (s *HstSuite) TearDownSuite() {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010064 s.unconfigureNetworkTopology()
65}
66
67func (s *HstSuite) TearDownTest() {
Maros Ondrejickae7625d02023-02-28 16:55:01 +010068 if *isPersistent {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010069 return
70 }
Filip Tehlar608d0062023-04-28 10:29:47 +020071 for _, c := range s.cpuContexts {
72 c.Release()
73 }
Maros Ondrejickae7625d02023-02-28 16:55:01 +010074 s.resetContainers()
75 s.removeVolumes()
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010076}
77
Maros Ondrejickaaf004dd2023-02-27 16:52:57 +010078func (s *HstSuite) skipIfUnconfiguring() {
Maros Ondrejickae7625d02023-02-28 16:55:01 +010079 if *isUnconfiguring {
Maros Ondrejickaaf004dd2023-02-27 16:52:57 +010080 s.skip("skipping to unconfigure")
81 }
82}
83
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010084func (s *HstSuite) SetupTest() {
Maros Ondrejickaaf004dd2023-02-27 16:52:57 +010085 s.skipIfUnconfiguring()
Maros Ondrejickae7625d02023-02-28 16:55:01 +010086 s.setupVolumes()
87 s.setupContainers()
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010088}
89
Maros Ondrejickae7625d02023-02-28 16:55:01 +010090func (s *HstSuite) setupVolumes() {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010091 for _, volume := range s.volumes {
92 cmd := "docker volume create --name=" + volume
93 s.log(cmd)
94 exechelper.Run(cmd)
95 }
96}
97
Maros Ondrejickae7625d02023-02-28 16:55:01 +010098func (s *HstSuite) setupContainers() {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010099 for _, container := range s.containers {
Filip Tehlar608d0062023-04-28 10:29:47 +0200100 if !container.isOptional {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100101 container.run()
102 }
103 }
104}
105
adrianvillin7c675472024-02-12 02:44:53 -0500106func logVppInstance(container *Container, maxLines int){
107 if container.vppInstance == nil{
108 return
109 }
110
111 logSource := container.getHostWorkDir() + defaultLogFilePath
112 file, err := os.Open(logSource)
113
114 if err != nil{
115 return
116 }
117 defer file.Close()
118
119 scanner := bufio.NewScanner(file)
120 var lines []string
121 var counter int
122
123 for scanner.Scan(){
124 lines = append(lines, scanner.Text())
125 counter++
126 if counter > maxLines {
127 lines = lines[1:]
128 counter--
129 }
130 }
131
132 fmt.Println("vvvvvvvvvvvvvvv " + container.name + " [VPP instance]:")
133 for _, line := range lines{
134 fmt.Println(line)
135 }
136 fmt.Printf("^^^^^^^^^^^^^^^\n\n")
137
138}
139
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100140func (s *HstSuite) hstFail() {
adrianvillin7c675472024-02-12 02:44:53 -0500141 fmt.Println("Containers: " + fmt.Sprint(s.containers))
142 for _, container := range s.containers{
143 out, err := container.log(20)
144 if err != nil{
145 fmt.Printf("An error occured while obtaining '%s' container logs: %s\n", container.name, fmt.Sprint(err))
146 break
147 }
148 fmt.Printf("\nvvvvvvvvvvvvvvv " +
149 container.name + ":\n" +
150 out +
151 "^^^^^^^^^^^^^^^\n\n")
152 logVppInstance(container, 20)
153 }
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100154 s.T().FailNow()
155}
156
157func (s *HstSuite) assertNil(object interface{}, msgAndArgs ...interface{}) {
158 if !assert.Nil(s.T(), object, msgAndArgs...) {
159 s.hstFail()
160 }
161}
162
163func (s *HstSuite) assertNotNil(object interface{}, msgAndArgs ...interface{}) {
164 if !assert.NotNil(s.T(), object, msgAndArgs...) {
165 s.hstFail()
166 }
167}
168
169func (s *HstSuite) assertEqual(expected, actual interface{}, msgAndArgs ...interface{}) {
170 if !assert.Equal(s.T(), expected, actual, msgAndArgs...) {
171 s.hstFail()
172 }
173}
174
175func (s *HstSuite) assertNotEqual(expected, actual interface{}, msgAndArgs ...interface{}) {
176 if !assert.NotEqual(s.T(), expected, actual, msgAndArgs...) {
177 s.hstFail()
178 }
179}
180
181func (s *HstSuite) assertContains(testString, contains interface{}, msgAndArgs ...interface{}) {
182 if !assert.Contains(s.T(), testString, contains, msgAndArgs...) {
183 s.hstFail()
184 }
185}
186
187func (s *HstSuite) assertNotContains(testString, contains interface{}, msgAndArgs ...interface{}) {
188 if !assert.NotContains(s.T(), testString, contains, msgAndArgs...) {
189 s.hstFail()
190 }
191}
192
Maros Ondrejicka2ddb2fd2023-02-15 17:44:46 +0100193func (s *HstSuite) assertNotEmpty(object interface{}, msgAndArgs ...interface{}) {
194 if !assert.NotEmpty(s.T(), object, msgAndArgs...) {
195 s.hstFail()
196 }
197}
198
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100199func (s *HstSuite) log(args ...any) {
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100200 if *isVerbose {
Maros Ondrejickaaf004dd2023-02-27 16:52:57 +0100201 s.T().Helper()
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100202 s.T().Log(args...)
203 }
204}
205
206func (s *HstSuite) skip(args ...any) {
207 s.log(args...)
208 s.T().SkipNow()
209}
210
Filip Tehlar608d0062023-04-28 10:29:47 +0200211func (s *HstSuite) SkipIfMultiWorker(args ...any) {
212 if *nConfiguredCpus > 1 {
213 s.skip("test case not supported with multiple vpp workers")
214 }
215}
216
Filip Tehlar31eaea92023-06-15 10:06:57 +0200217func (s *HstSuite) SkipUnlessExtendedTestsBuilt() {
218 imageName := "hs-test/nginx-http3"
219
220 cmd := exec.Command("docker", "images", imageName)
221 byteOutput, err := cmd.CombinedOutput()
222 if err != nil {
223 s.log("error while searching for docker image")
224 return
225 }
226 if !strings.Contains(string(byteOutput), imageName) {
227 s.skip("extended tests not built")
228 }
229}
230
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100231func (s *HstSuite) resetContainers() {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100232 for _, container := range s.containers {
233 container.stop()
234 }
235}
236
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100237func (s *HstSuite) removeVolumes() {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100238 for _, volumeName := range s.volumes {
239 cmd := "docker volume rm " + volumeName
240 exechelper.Run(cmd)
241 os.RemoveAll(volumeName)
242 }
243}
244
245func (s *HstSuite) getContainerByName(name string) *Container {
246 return s.containers[name]
247}
248
Maros Ondrejicka2908f8c2023-02-02 08:58:04 +0100249/*
250 * Create a copy and return its address, so that individial tests which call this
251 * are not able to modify the original container and affect other tests by doing that
252 */
253func (s *HstSuite) getTransientContainerByName(name string) *Container {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100254 containerCopy := *s.containers[name]
255 return &containerCopy
256}
257
258func (s *HstSuite) loadContainerTopology(topologyName string) {
adrianvillin707210c2024-01-24 01:45:59 -0500259 data, err := os.ReadFile(containerTopologyDir + topologyName + ".yaml")
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100260 if err != nil {
261 s.T().Fatalf("read error: %v", err)
262 }
263 var yamlTopo YamlTopology
264 err = yaml.Unmarshal(data, &yamlTopo)
265 if err != nil {
266 s.T().Fatalf("unmarshal error: %v", err)
267 }
268
269 for _, elem := range yamlTopo.Volumes {
270 volumeMap := elem["volume"].(VolumeConfig)
271 hostDir := volumeMap["host-dir"].(string)
Filip Tehlara1bd50c2024-01-24 11:59:44 +0100272 workingVolumeDir := logDir + s.T().Name() + volumeDir
273 volDirReplacer := strings.NewReplacer("$HST_VOLUME_DIR", workingVolumeDir)
274 hostDir = volDirReplacer.Replace(hostDir)
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100275 s.volumes = append(s.volumes, hostDir)
276 }
277
278 s.containers = make(map[string]*Container)
279 for _, elem := range yamlTopo.Containers {
Filip Tehlara1bd50c2024-01-24 11:59:44 +0100280 newContainer, err := newContainer(s, elem)
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100281 if err != nil {
282 s.T().Fatalf("container config error: %v", err)
283 }
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100284 s.containers[newContainer.name] = newContainer
285 }
286}
287
288func (s *HstSuite) loadNetworkTopology(topologyName string) {
adrianvillin707210c2024-01-24 01:45:59 -0500289 data, err := os.ReadFile(networkTopologyDir + topologyName + ".yaml")
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100290 if err != nil {
291 s.T().Fatalf("read error: %v", err)
292 }
293 var yamlTopo YamlTopology
294 err = yaml.Unmarshal(data, &yamlTopo)
295 if err != nil {
296 s.T().Fatalf("unmarshal error: %v", err)
297 }
298
Filip Tehlar3a910ab2023-06-08 17:39:39 +0200299 s.ip4AddrAllocator = NewIp4AddressAllocator()
Maros Ondrejicka40cba402023-02-23 13:19:15 +0100300 s.netInterfaces = make(map[string]*NetInterface)
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100301 for _, elem := range yamlTopo.Devices {
302 switch elem["type"].(string) {
303 case NetNs:
304 {
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100305 if namespace, err := newNetNamespace(elem); err == nil {
Maros Ondrejicka2908f8c2023-02-02 08:58:04 +0100306 s.netConfigs = append(s.netConfigs, &namespace)
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100307 } else {
308 s.T().Fatalf("network config error: %v", err)
309 }
310 }
Maros Ondrejicka40cba402023-02-23 13:19:15 +0100311 case Veth, Tap:
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100312 {
Filip Tehlar3a910ab2023-06-08 17:39:39 +0200313 if netIf, err := newNetworkInterface(elem, s.ip4AddrAllocator); err == nil {
Maros Ondrejicka40cba402023-02-23 13:19:15 +0100314 s.netConfigs = append(s.netConfigs, netIf)
315 s.netInterfaces[netIf.Name()] = netIf
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100316 } else {
317 s.T().Fatalf("network config error: %v", err)
318 }
319 }
320 case Bridge:
321 {
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100322 if bridge, err := newBridge(elem); err == nil {
Maros Ondrejicka2908f8c2023-02-02 08:58:04 +0100323 s.netConfigs = append(s.netConfigs, &bridge)
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100324 } else {
325 s.T().Fatalf("network config error: %v", err)
326 }
327 }
328 }
329 }
330}
331
332func (s *HstSuite) configureNetworkTopology(topologyName string) {
333 s.loadNetworkTopology(topologyName)
334
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100335 if *isUnconfiguring {
Maros Ondrejickaaf004dd2023-02-27 16:52:57 +0100336 return
337 }
338
Maros Ondrejicka2908f8c2023-02-02 08:58:04 +0100339 for _, nc := range s.netConfigs {
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100340 if err := nc.configure(); err != nil {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100341 s.T().Fatalf("network config error: %v", err)
342 }
343 }
344}
345
346func (s *HstSuite) unconfigureNetworkTopology() {
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100347 if *isPersistent {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100348 return
349 }
Maros Ondrejicka2908f8c2023-02-02 08:58:04 +0100350 for _, nc := range s.netConfigs {
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100351 nc.unconfigure()
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100352 }
353}
354
Maros Ondrejickaa2d52622023-02-24 11:26:39 +0100355func (s *HstSuite) getTestId() string {
356 testName := s.T().Name()
357
358 if s.testIds == nil {
359 s.testIds = map[string]string{}
360 }
361
362 if _, ok := s.testIds[testName]; !ok {
Filip Tehlar75776f02023-03-24 13:47:45 +0100363 s.testIds[testName] = time.Now().Format("2006-01-02_15-04-05")
Maros Ondrejickaa2d52622023-02-24 11:26:39 +0100364 }
365
366 return s.testIds[testName]
367}
Filip Tehlar4b3598e2023-09-02 08:54:21 +0200368
369func (s *HstSuite) startServerApp(running chan error, done chan struct{}, env []string) {
370 cmd := exec.Command("iperf3", "-4", "-s")
371 if env != nil {
372 cmd.Env = env
373 }
374 s.log(cmd)
375 err := cmd.Start()
376 if err != nil {
377 msg := fmt.Errorf("failed to start iperf server: %v", err)
378 running <- msg
379 return
380 }
381 running <- nil
382 <-done
383 cmd.Process.Kill()
384}
385
386func (s *HstSuite) startClientApp(ipAddress string, env []string, clnCh chan error, clnRes chan string) {
387 defer func() {
388 clnCh <- nil
389 }()
390
391 nTries := 0
392
393 for {
394 cmd := exec.Command("iperf3", "-c", ipAddress, "-u", "-l", "1460", "-b", "10g")
395 if env != nil {
396 cmd.Env = env
397 }
398 s.log(cmd)
399 o, err := cmd.CombinedOutput()
400 if err != nil {
401 if nTries > 5 {
402 clnCh <- fmt.Errorf("failed to start client app '%s'.\n%s", err, o)
403 return
404 }
405 time.Sleep(1 * time.Second)
406 nTries++
407 continue
408 } else {
409 clnRes <- fmt.Sprintf("Client output: %s", o)
410 }
411 break
412 }
413}
414
415func (s *HstSuite) startHttpServer(running chan struct{}, done chan struct{}, addressPort, netNs string) {
416 cmd := newCommand([]string{"./http_server", addressPort}, netNs)
417 err := cmd.Start()
418 s.log(cmd)
419 if err != nil {
adrianvillin7c675472024-02-12 02:44:53 -0500420 fmt.Println("Failed to start http server: " + fmt.Sprint(err))
Filip Tehlar4b3598e2023-09-02 08:54:21 +0200421 return
422 }
423 running <- struct{}{}
424 <-done
425 cmd.Process.Kill()
426}
427
428func (s *HstSuite) startWget(finished chan error, server_ip, port, query, netNs string) {
429 defer func() {
430 finished <- errors.New("wget error")
431 }()
432
433 cmd := newCommand([]string{"wget", "--timeout=10", "--no-proxy", "--tries=5", "-O", "/dev/null", server_ip + ":" + port + "/" + query},
434 netNs)
435 s.log(cmd)
436 o, err := cmd.CombinedOutput()
437 if err != nil {
438 finished <- fmt.Errorf("wget error: '%v\n\n%s'", err, o)
439 return
440 } else if !strings.Contains(string(o), "200 OK") {
441 finished <- fmt.Errorf("wget error: response not 200 OK")
442 return
443 }
444 finished <- nil
445}