blob: 271ef99f24974f313e930920aab64bda922cea83 [file] [log] [blame]
Adrian Villin4677d922024-06-14 09:32:39 +02001package hst
2
3import (
4 "bufio"
5 "errors"
6 "flag"
7 "fmt"
8 "io"
9 "log"
Matus Fabiand46e6742024-07-31 16:08:40 +020010 "net/http"
11 "net/http/httputil"
Adrian Villin4677d922024-06-14 09:32:39 +020012 "os"
13 "os/exec"
14 "path/filepath"
15 "runtime"
16 "strings"
17 "time"
18
Adrian Villin25140012024-07-09 15:31:36 +020019 containerTypes "github.com/docker/docker/api/types/container"
20 "github.com/docker/docker/client"
Adrian Villin4677d922024-06-14 09:32:39 +020021 "github.com/onsi/gomega/gmeasure"
22 "gopkg.in/yaml.v3"
23
Adrian Villin4677d922024-06-14 09:32:39 +020024 . "github.com/onsi/ginkgo/v2"
25 . "github.com/onsi/gomega"
26)
27
28const (
29 DEFAULT_NETWORK_NUM int = 1
30)
31
32var IsPersistent = flag.Bool("persist", false, "persists topology config")
33var IsVerbose = flag.Bool("verbose", false, "verbose test output")
34var IsUnconfiguring = flag.Bool("unconfigure", false, "remove topology")
35var IsVppDebug = flag.Bool("debug", false, "attach gdb to vpp")
36var NConfiguredCpus = flag.Int("cpus", 1, "number of CPUs assigned to vpp")
37var VppSourceFileDir = flag.String("vppsrc", "", "vpp source file directory")
Adrian Villinb4516bb2024-06-19 06:20:00 -040038var IsDebugBuild = flag.Bool("debug_build", false, "some paths are different with debug build")
Adrian Villin5d171eb2024-06-17 08:51:27 +020039var UseCpu0 = flag.Bool("cpu0", false, "use cpu0")
Matus Fabiane99d2662024-07-19 16:04:09 +020040var IsLeakCheck = flag.Bool("leak_check", false, "run leak-check tests")
Adrian Villin4995d0d2024-07-29 17:54:58 +020041var ParallelTotal = flag.Lookup("ginkgo.parallel.total")
Adrian Villin5d171eb2024-06-17 08:51:27 +020042var NumaAwareCpuAlloc bool
Adrian Villin4677d922024-06-14 09:32:39 +020043var SuiteTimeout time.Duration
44
45type HstSuite struct {
46 Containers map[string]*Container
47 StartedContainers []*Container
48 Volumes []string
49 NetConfigs []NetConfig
50 NetInterfaces map[string]*NetInterface
51 Ip4AddrAllocator *Ip4AddressAllocator
52 TestIds map[string]string
53 CpuAllocator *CpuAllocatorT
54 CpuContexts []*CpuContext
Adrian Villinb69ee002024-07-17 14:38:48 +020055 CpuCount int
Adrian Villin4677d922024-06-14 09:32:39 +020056 Ppid string
57 ProcessIndex string
58 Logger *log.Logger
59 LogFile *os.File
Adrian Villin25140012024-07-09 15:31:36 +020060 Docker *client.Client
Adrian Villin4677d922024-06-14 09:32:39 +020061}
62
Adrian Villin4995d0d2024-07-29 17:54:58 +020063// used for colorful ReportEntry
64type StringerStruct struct {
65 Label string
66}
67
68// ColorableString for ReportEntry to use
69func (s StringerStruct) ColorableString() string {
70 return fmt.Sprintf("{{red}}%s{{/}}", s.Label)
71}
72
73// non-colorable String() is used by go's string formatting support but ignored by ReportEntry
74func (s StringerStruct) String() string {
75 return s.Label
76}
77
Adrian Villin4677d922024-06-14 09:32:39 +020078func getTestFilename() string {
79 _, filename, _, _ := runtime.Caller(2)
80 return filepath.Base(filename)
81}
82
Adrian Villin4995d0d2024-07-29 17:54:58 +020083func (s *HstSuite) getLogDirPath() string {
84 testId := s.GetTestId()
85 testName := s.GetCurrentTestName()
86 logDirPath := logDir + testName + "/" + testId + "/"
87
88 cmd := exec.Command("mkdir", "-p", logDirPath)
89 if err := cmd.Run(); err != nil {
90 Fail("mkdir error: " + fmt.Sprint(err))
91 }
92
93 return logDirPath
94}
95
Adrian Villin25140012024-07-09 15:31:36 +020096func (s *HstSuite) newDockerClient() {
97 var err error
98 s.Docker, err = client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
99 s.AssertNil(err)
100 s.Log("docker client created")
101}
102
Adrian Villin4677d922024-06-14 09:32:39 +0200103func (s *HstSuite) SetupSuite() {
104 s.CreateLogger()
Adrian Villin25140012024-07-09 15:31:36 +0200105 s.newDockerClient()
Adrian Villin4677d922024-06-14 09:32:39 +0200106 s.Log("Suite Setup")
107 RegisterFailHandler(func(message string, callerSkip ...int) {
108 s.HstFail()
109 Fail(message, callerSkip...)
110 })
111 var err error
112 s.Ppid = fmt.Sprint(os.Getppid())
113 // remove last number so we have space to prepend a process index (interfaces have a char limit)
114 s.Ppid = s.Ppid[:len(s.Ppid)-1]
115 s.ProcessIndex = fmt.Sprint(GinkgoParallelProcess())
116 s.CpuAllocator, err = CpuAllocator()
117 if err != nil {
118 Fail("failed to init cpu allocator: " + fmt.Sprint(err))
119 }
Adrian Villinb69ee002024-07-17 14:38:48 +0200120 s.CpuCount = *NConfiguredCpus
Adrian Villin4677d922024-06-14 09:32:39 +0200121}
122
123func (s *HstSuite) AllocateCpus() []int {
Adrian Villinb69ee002024-07-17 14:38:48 +0200124 cpuCtx, err := s.CpuAllocator.Allocate(len(s.StartedContainers), s.CpuCount)
Adrian Villin5d171eb2024-06-17 08:51:27 +0200125 // using Fail instead of AssertNil to make error message more readable
126 if err != nil {
127 Fail(fmt.Sprint(err))
128 }
Adrian Villin4677d922024-06-14 09:32:39 +0200129 s.AddCpuContext(cpuCtx)
130 return cpuCtx.cpus
131}
132
133func (s *HstSuite) AddCpuContext(cpuCtx *CpuContext) {
134 s.CpuContexts = append(s.CpuContexts, cpuCtx)
135}
136
137func (s *HstSuite) TearDownSuite() {
138 defer s.LogFile.Close()
Adrian Villin25140012024-07-09 15:31:36 +0200139 defer s.Docker.Close()
Adrian Villin4677d922024-06-14 09:32:39 +0200140 s.Log("Suite Teardown")
141 s.UnconfigureNetworkTopology()
142}
143
144func (s *HstSuite) TearDownTest() {
145 s.Log("Test Teardown")
146 if *IsPersistent {
147 return
148 }
Adrian Villin4995d0d2024-07-29 17:54:58 +0200149 s.WaitForCoreDump()
Adrian Villin4677d922024-06-14 09:32:39 +0200150 s.ResetContainers()
Adrian Villinb69ee002024-07-17 14:38:48 +0200151
152 if s.Ip4AddrAllocator != nil {
153 s.Ip4AddrAllocator.DeleteIpAddresses()
154 }
Adrian Villin4677d922024-06-14 09:32:39 +0200155}
156
157func (s *HstSuite) SkipIfUnconfiguring() {
158 if *IsUnconfiguring {
159 s.Skip("skipping to unconfigure")
160 }
161}
162
163func (s *HstSuite) SetupTest() {
164 s.Log("Test Setup")
165 s.StartedContainers = s.StartedContainers[:0]
166 s.SkipIfUnconfiguring()
Adrian Villin4677d922024-06-14 09:32:39 +0200167 s.SetupContainers()
168}
169
Adrian Villin4677d922024-06-14 09:32:39 +0200170func (s *HstSuite) SetupContainers() {
171 for _, container := range s.Containers {
172 if !container.IsOptional {
173 container.Run()
174 }
175 }
176}
177
178func (s *HstSuite) LogVppInstance(container *Container, maxLines int) {
179 if container.VppInstance == nil {
180 return
181 }
182
183 logSource := container.GetHostWorkDir() + defaultLogFilePath
184 file, err := os.Open(logSource)
185
186 if err != nil {
187 return
188 }
189 defer file.Close()
190
191 scanner := bufio.NewScanner(file)
192 var lines []string
193 var counter int
194
195 for scanner.Scan() {
196 lines = append(lines, scanner.Text())
197 counter++
198 if counter > maxLines {
199 lines = lines[1:]
200 counter--
201 }
202 }
203
204 s.Log("vvvvvvvvvvvvvvv " + container.Name + " [VPP instance]:")
205 for _, line := range lines {
206 s.Log(line)
207 }
208 s.Log("^^^^^^^^^^^^^^^\n\n")
209}
210
211func (s *HstSuite) HstFail() {
212 for _, container := range s.StartedContainers {
213 out, err := container.log(20)
214 if err != nil {
215 s.Log("An error occured while obtaining '" + container.Name + "' container logs: " + fmt.Sprint(err))
Adrian Villin4995d0d2024-07-29 17:54:58 +0200216 s.Log("The container might not be running - check logs in " + s.getLogDirPath())
Adrian Villin4677d922024-06-14 09:32:39 +0200217 continue
218 }
219 s.Log("\nvvvvvvvvvvvvvvv " +
220 container.Name + ":\n" +
221 out +
222 "^^^^^^^^^^^^^^^\n\n")
223 s.LogVppInstance(container, 20)
224 }
225}
226
227func (s *HstSuite) AssertNil(object interface{}, msgAndArgs ...interface{}) {
228 Expect(object).To(BeNil(), msgAndArgs...)
229}
230
231func (s *HstSuite) AssertNotNil(object interface{}, msgAndArgs ...interface{}) {
232 Expect(object).ToNot(BeNil(), msgAndArgs...)
233}
234
235func (s *HstSuite) AssertEqual(expected, actual interface{}, msgAndArgs ...interface{}) {
236 Expect(actual).To(Equal(expected), msgAndArgs...)
237}
238
239func (s *HstSuite) AssertNotEqual(expected, actual interface{}, msgAndArgs ...interface{}) {
240 Expect(actual).ToNot(Equal(expected), msgAndArgs...)
241}
242
243func (s *HstSuite) AssertContains(testString, contains interface{}, msgAndArgs ...interface{}) {
244 Expect(testString).To(ContainSubstring(fmt.Sprint(contains)), msgAndArgs...)
245}
246
247func (s *HstSuite) AssertNotContains(testString, contains interface{}, msgAndArgs ...interface{}) {
248 Expect(testString).ToNot(ContainSubstring(fmt.Sprint(contains)), msgAndArgs...)
249}
250
Adrian Villin25140012024-07-09 15:31:36 +0200251func (s *HstSuite) AssertEmpty(object interface{}, msgAndArgs ...interface{}) {
252 Expect(object).To(BeEmpty(), msgAndArgs...)
253}
254
Adrian Villin4677d922024-06-14 09:32:39 +0200255func (s *HstSuite) AssertNotEmpty(object interface{}, msgAndArgs ...interface{}) {
256 Expect(object).ToNot(BeEmpty(), msgAndArgs...)
257}
258
Matus Fabiand086a362024-06-27 13:20:10 +0200259func (s *HstSuite) AssertMatchError(actual, expected error, msgAndArgs ...interface{}) {
260 Expect(actual).To(MatchError(expected))
261}
262
Adrian Villin4677d922024-06-14 09:32:39 +0200263func (s *HstSuite) CreateLogger() {
264 suiteName := s.GetCurrentSuiteName()
265 var err error
266 s.LogFile, err = os.Create("summary/" + suiteName + ".log")
267 if err != nil {
268 Fail("Unable to create log file.")
269 }
270 s.Logger = log.New(io.Writer(s.LogFile), "", log.LstdFlags)
271}
272
273// Logs to files by default, logs to stdout when VERBOSE=true with GinkgoWriter
274// to keep console tidy
275func (s *HstSuite) Log(arg any) {
276 logs := strings.Split(fmt.Sprint(arg), "\n")
277 for _, line := range logs {
278 s.Logger.Println(line)
279 }
280 if *IsVerbose {
281 GinkgoWriter.Println(arg)
282 }
283}
284
285func (s *HstSuite) Skip(args string) {
286 Skip(args)
287}
288
289func (s *HstSuite) SkipIfMultiWorker(args ...any) {
290 if *NConfiguredCpus > 1 {
291 s.Skip("test case not supported with multiple vpp workers")
292 }
293}
294
Adrian Villinb69ee002024-07-17 14:38:48 +0200295func (s *HstSuite) SkipIfNotEnoughAvailableCpus() bool {
296 var MaxRequestedCpu int
297
298 if s.CpuAllocator.runningInCi {
299 MaxRequestedCpu = ((s.CpuAllocator.buildNumber + 1) * s.CpuAllocator.maxContainerCount * s.CpuCount)
300 } else {
301 MaxRequestedCpu = (GinkgoParallelProcess() * s.CpuAllocator.maxContainerCount * s.CpuCount)
302 }
Hadi Rayan Al-Sandide0e85132024-06-24 10:28:58 +0200303
304 if len(s.CpuAllocator.cpus)-1 < MaxRequestedCpu {
Adrian Villinb69ee002024-07-17 14:38:48 +0200305 s.Skip(fmt.Sprintf("test case cannot allocate requested cpus (%d cpus * %d containers)", s.CpuCount, s.CpuAllocator.maxContainerCount))
Hadi Rayan Al-Sandide0e85132024-06-24 10:28:58 +0200306 }
307
308 return true
309}
310
Adrian Villin4677d922024-06-14 09:32:39 +0200311func (s *HstSuite) SkipUnlessExtendedTestsBuilt() {
312 imageName := "hs-test/nginx-http3"
313
314 cmd := exec.Command("docker", "images", imageName)
315 byteOutput, err := cmd.CombinedOutput()
316 if err != nil {
317 s.Log("error while searching for docker image")
318 return
319 }
320 if !strings.Contains(string(byteOutput), imageName) {
321 s.Skip("extended tests not built")
322 }
323}
324
Matus Fabiane99d2662024-07-19 16:04:09 +0200325func (s *HstSuite) SkipUnlessLeakCheck() {
326 if !*IsLeakCheck {
327 s.Skip("leak-check tests excluded")
328 }
329}
Adrian Villin4995d0d2024-07-29 17:54:58 +0200330
331func (s *HstSuite) WaitForCoreDump() {
332 var filename string
333 var cmd *exec.Cmd
334 dir, err := os.Open(s.getLogDirPath())
335 if err != nil {
336 s.Log(err)
337 return
338 }
339 defer dir.Close()
340
341 files, err := dir.Readdirnames(0)
342 if err != nil {
343 s.Log(err)
344 return
345 }
346 for _, file := range files {
347 if strings.Contains(file, "core") {
348 filename = file
349 }
350 }
351 timeout := 60
352 waitTime := 5
353
354 if filename != "" {
355 filename := s.getLogDirPath() + filename
356 s.Log(fmt.Sprintf("WAITING FOR CORE DUMP (%s)", filename))
357 for i := waitTime; i <= timeout; i += waitTime {
358 fileInfo, err := os.Stat(filename)
359 if err != nil {
360 s.Log("Error while reading file info: " + fmt.Sprint(err))
361 return
362 }
363 currSize := fileInfo.Size()
364 s.Log(fmt.Sprintf("Waiting %ds/%ds...", i, timeout))
365 time.Sleep(time.Duration(waitTime) * time.Second)
366 fileInfo, _ = os.Stat(filename)
367
368 if currSize == fileInfo.Size() {
369 if *IsDebugBuild {
370 cmd = exec.Command("sudo", "gdb", "../../build-root/build-vpp_debug-native/vpp/bin/vpp",
371 "-c", filename, "-ex", "bt", "full", "-ex", "quit")
372 } else {
373 cmd = exec.Command("sudo", "gdb", "../../build-root/build-vpp-native/vpp/bin/vpp",
374 "-c", filename, "-ex", "bt", "full", "-ex", "quit")
375 }
376
377 s.Log(cmd)
378 output, _ := cmd.Output()
379 AddReportEntry("VPP Backtrace", StringerStruct{Label: string(output)})
380 os.WriteFile(s.getLogDirPath()+"backtrace.log", output, os.FileMode(0644))
381 if s.CpuAllocator.runningInCi {
382 err = os.Remove(filename)
383 if err == nil {
384 s.Log("removed " + filename)
385 } else {
386 s.Log(err)
387 }
388 }
389 return
390 }
391 }
392 }
393}
394
Adrian Villin4677d922024-06-14 09:32:39 +0200395func (s *HstSuite) ResetContainers() {
396 for _, container := range s.StartedContainers {
397 container.stop()
Adrian Villin25140012024-07-09 15:31:36 +0200398 s.Log("Removing container " + container.Name)
399 if err := s.Docker.ContainerRemove(container.ctx, container.ID, containerTypes.RemoveOptions{RemoveVolumes: true}); err != nil {
400 s.Log(err)
401 }
Adrian Villin4677d922024-06-14 09:32:39 +0200402 }
403}
404
405func (s *HstSuite) GetNetNamespaceByName(name string) string {
406 return s.ProcessIndex + name + s.Ppid
407}
408
409func (s *HstSuite) GetInterfaceByName(name string) *NetInterface {
410 return s.NetInterfaces[s.ProcessIndex+name+s.Ppid]
411}
412
413func (s *HstSuite) GetContainerByName(name string) *Container {
414 return s.Containers[s.ProcessIndex+name+s.Ppid]
415}
416
417/*
418 * Create a copy and return its address, so that individial tests which call this
419 * are not able to modify the original container and affect other tests by doing that
420 */
421func (s *HstSuite) GetTransientContainerByName(name string) *Container {
422 containerCopy := *s.Containers[s.ProcessIndex+name+s.Ppid]
423 return &containerCopy
424}
425
426func (s *HstSuite) LoadContainerTopology(topologyName string) {
427 data, err := os.ReadFile(containerTopologyDir + topologyName + ".yaml")
428 if err != nil {
429 Fail("read error: " + fmt.Sprint(err))
430 }
431 var yamlTopo YamlTopology
432 err = yaml.Unmarshal(data, &yamlTopo)
433 if err != nil {
434 Fail("unmarshal error: " + fmt.Sprint(err))
435 }
436
437 for _, elem := range yamlTopo.Volumes {
438 volumeMap := elem["volume"].(VolumeConfig)
439 hostDir := volumeMap["host-dir"].(string)
440 workingVolumeDir := logDir + s.GetCurrentTestName() + volumeDir
441 volDirReplacer := strings.NewReplacer("$HST_VOLUME_DIR", workingVolumeDir)
442 hostDir = volDirReplacer.Replace(hostDir)
443 s.Volumes = append(s.Volumes, hostDir)
444 }
445
446 s.Containers = make(map[string]*Container)
447 for _, elem := range yamlTopo.Containers {
448 newContainer, err := newContainer(s, elem)
449 newContainer.Suite = s
450 newContainer.Name = newContainer.Suite.ProcessIndex + newContainer.Name + newContainer.Suite.Ppid
451 if err != nil {
452 Fail("container config error: " + fmt.Sprint(err))
453 }
454 s.Containers[newContainer.Name] = newContainer
455 }
456}
457
458func (s *HstSuite) LoadNetworkTopology(topologyName string) {
459 data, err := os.ReadFile(networkTopologyDir + topologyName + ".yaml")
460 if err != nil {
461 Fail("read error: " + fmt.Sprint(err))
462 }
463 var yamlTopo YamlTopology
464 err = yaml.Unmarshal(data, &yamlTopo)
465 if err != nil {
466 Fail("unmarshal error: " + fmt.Sprint(err))
467 }
468
469 s.Ip4AddrAllocator = NewIp4AddressAllocator()
470 s.NetInterfaces = make(map[string]*NetInterface)
471
472 for _, elem := range yamlTopo.Devices {
473 if _, ok := elem["name"]; ok {
474 elem["name"] = s.ProcessIndex + elem["name"].(string) + s.Ppid
475 }
476
477 if peer, ok := elem["peer"].(NetDevConfig); ok {
478 if peer["name"].(string) != "" {
479 peer["name"] = s.ProcessIndex + peer["name"].(string) + s.Ppid
480 }
481 if _, ok := peer["netns"]; ok {
482 peer["netns"] = s.ProcessIndex + peer["netns"].(string) + s.Ppid
483 }
484 }
485
486 if _, ok := elem["netns"]; ok {
487 elem["netns"] = s.ProcessIndex + elem["netns"].(string) + s.Ppid
488 }
489
490 if _, ok := elem["interfaces"]; ok {
491 interfaceCount := len(elem["interfaces"].([]interface{}))
492 for i := 0; i < interfaceCount; i++ {
493 elem["interfaces"].([]interface{})[i] = s.ProcessIndex + elem["interfaces"].([]interface{})[i].(string) + s.Ppid
494 }
495 }
496
497 switch elem["type"].(string) {
498 case NetNs:
499 {
500 if namespace, err := newNetNamespace(elem); err == nil {
501 s.NetConfigs = append(s.NetConfigs, &namespace)
502 } else {
503 Fail("network config error: " + fmt.Sprint(err))
504 }
505 }
506 case Veth, Tap:
507 {
508 if netIf, err := newNetworkInterface(elem, s.Ip4AddrAllocator); err == nil {
509 s.NetConfigs = append(s.NetConfigs, netIf)
510 s.NetInterfaces[netIf.Name()] = netIf
511 } else {
512 Fail("network config error: " + fmt.Sprint(err))
513 }
514 }
515 case Bridge:
516 {
517 if bridge, err := newBridge(elem); err == nil {
518 s.NetConfigs = append(s.NetConfigs, &bridge)
519 } else {
520 Fail("network config error: " + fmt.Sprint(err))
521 }
522 }
523 }
524 }
525}
526
527func (s *HstSuite) ConfigureNetworkTopology(topologyName string) {
528 s.LoadNetworkTopology(topologyName)
529
530 if *IsUnconfiguring {
531 return
532 }
533
534 for _, nc := range s.NetConfigs {
535 s.Log(nc.Name())
536 if err := nc.configure(); err != nil {
537 Fail("Network config error: " + fmt.Sprint(err))
538 }
539 }
540}
541
542func (s *HstSuite) UnconfigureNetworkTopology() {
543 if *IsPersistent {
544 return
545 }
546 for _, nc := range s.NetConfigs {
547 nc.unconfigure()
548 }
549}
550
551func (s *HstSuite) GetTestId() string {
552 testName := s.GetCurrentTestName()
553
554 if s.TestIds == nil {
555 s.TestIds = map[string]string{}
556 }
557
558 if _, ok := s.TestIds[testName]; !ok {
559 s.TestIds[testName] = time.Now().Format("2006-01-02_15-04-05")
560 }
561
562 return s.TestIds[testName]
563}
564
565func (s *HstSuite) GetCurrentTestName() string {
566 return strings.Split(CurrentSpecReport().LeafNodeText, "/")[1]
567}
568
569func (s *HstSuite) GetCurrentSuiteName() string {
570 return CurrentSpecReport().ContainerHierarchyTexts[0]
571}
572
573// Returns last 3 digits of PID + Ginkgo process index as the 4th digit
574func (s *HstSuite) GetPortFromPpid() string {
575 port := s.Ppid
576 for len(port) < 3 {
577 port += "0"
578 }
579 return port[len(port)-3:] + s.ProcessIndex
580}
581
582func (s *HstSuite) StartServerApp(running chan error, done chan struct{}, env []string) {
583 cmd := exec.Command("iperf3", "-4", "-s", "-p", s.GetPortFromPpid())
584 if env != nil {
585 cmd.Env = env
586 }
587 s.Log(cmd)
588 err := cmd.Start()
589 if err != nil {
590 msg := fmt.Errorf("failed to start iperf server: %v", err)
591 running <- msg
592 return
593 }
594 running <- nil
595 <-done
596 cmd.Process.Kill()
597}
598
599func (s *HstSuite) StartClientApp(ipAddress string, env []string, clnCh chan error, clnRes chan string) {
600 defer func() {
601 clnCh <- nil
602 }()
603
604 nTries := 0
605
606 for {
607 cmd := exec.Command("iperf3", "-c", ipAddress, "-u", "-l", "1460", "-b", "10g", "-p", s.GetPortFromPpid())
608 if env != nil {
609 cmd.Env = env
610 }
611 s.Log(cmd)
612 o, err := cmd.CombinedOutput()
613 if err != nil {
614 if nTries > 5 {
615 clnCh <- fmt.Errorf("failed to start client app '%s'.\n%s", err, o)
616 return
617 }
618 time.Sleep(1 * time.Second)
619 nTries++
620 continue
621 } else {
622 clnRes <- fmt.Sprintf("Client output: %s", o)
623 }
624 break
625 }
626}
627
628func (s *HstSuite) StartHttpServer(running chan struct{}, done chan struct{}, addressPort, netNs string) {
629 cmd := newCommand([]string{"./http_server", addressPort, s.Ppid, s.ProcessIndex}, netNs)
630 err := cmd.Start()
631 s.Log(cmd)
632 if err != nil {
633 s.Log("Failed to start http server: " + fmt.Sprint(err))
634 return
635 }
636 running <- struct{}{}
637 <-done
638 cmd.Process.Kill()
639}
640
641func (s *HstSuite) StartWget(finished chan error, server_ip, port, query, netNs string) {
642 defer func() {
643 finished <- errors.New("wget error")
644 }()
645
646 cmd := newCommand([]string{"wget", "--timeout=10", "--no-proxy", "--tries=5", "-O", "/dev/null", server_ip + ":" + port + "/" + query},
647 netNs)
648 s.Log(cmd)
649 o, err := cmd.CombinedOutput()
650 if err != nil {
651 finished <- fmt.Errorf("wget error: '%v\n\n%s'", err, o)
652 return
653 } else if !strings.Contains(string(o), "200 OK") {
654 finished <- fmt.Errorf("wget error: response not 200 OK")
655 return
656 }
657 finished <- nil
658}
659
660/*
Matus Fabiand46e6742024-07-31 16:08:40 +0200661RunBenchmark creates Gomega's experiment with the passed-in name and samples the passed-in callback repeatedly (samplesNum times),
Adrian Villin4677d922024-06-14 09:32:39 +0200662passing in suite context, experiment and your data.
663
664You can also instruct runBenchmark to run with multiple concurrent workers.
Matus Fabian5c4c1b62024-06-28 16:11:04 +0200665Note that if running in parallel Gomega returns from Sample when spins up all samples and does not wait until all finished.
Adrian Villin4677d922024-06-14 09:32:39 +0200666You can record multiple named measurements (float64 or duration) within passed-in callback.
667runBenchmark then produces report to show statistical distribution of measurements.
668*/
669func (s *HstSuite) RunBenchmark(name string, samplesNum, parallelNum int, callback func(s *HstSuite, e *gmeasure.Experiment, data interface{}), data interface{}) {
670 experiment := gmeasure.NewExperiment(name)
671
672 experiment.Sample(func(idx int) {
673 defer GinkgoRecover()
674 callback(s, experiment, data)
675 }, gmeasure.SamplingConfig{N: samplesNum, NumParallel: parallelNum})
676 AddReportEntry(experiment.Name, experiment)
677}
Matus Fabiand46e6742024-07-31 16:08:40 +0200678
679/*
680LogHttpReq is Gomega's ghttp server handler which logs received HTTP request.
681
682You should put it at the first place, so request is logged always.
683*/
684func (s *HstSuite) LogHttpReq(body bool) http.HandlerFunc {
685 return func(w http.ResponseWriter, req *http.Request) {
686 dump, err := httputil.DumpRequest(req, body)
687 if err == nil {
688 s.Log("\n> Received request (" + req.RemoteAddr + "):\n" +
689 string(dump) +
690 "\n------------------------------\n")
691 }
692 }
693}