blob: d6b4006a4e4defc971386c8df01b21e6e4d29597 [file] [log] [blame]
Adrian Villin4677d922024-06-14 09:32:39 +02001package hst
2
3import (
4 "bufio"
Adrian Villin4677d922024-06-14 09:32:39 +02005 "flag"
6 "fmt"
7 "io"
8 "log"
Matus Fabiand46e6742024-07-31 16:08:40 +02009 "net/http"
10 "net/http/httputil"
Adrian Villin4677d922024-06-14 09:32:39 +020011 "os"
12 "os/exec"
13 "path/filepath"
14 "runtime"
Matus Fabiana647a832024-08-26 18:01:14 +020015 "strconv"
Adrian Villin4677d922024-06-14 09:32:39 +020016 "strings"
17 "time"
18
Adrian Villin5a4c7a92024-09-26 11:24:34 +020019 "github.com/edwarnicke/exechelper"
20
Adrian Villin25140012024-07-09 15:31:36 +020021 containerTypes "github.com/docker/docker/api/types/container"
22 "github.com/docker/docker/client"
Adrian Villin4677d922024-06-14 09:32:39 +020023 "github.com/onsi/gomega/gmeasure"
24 "gopkg.in/yaml.v3"
25
Adrian Villin4677d922024-06-14 09:32:39 +020026 . "github.com/onsi/ginkgo/v2"
27 . "github.com/onsi/gomega"
28)
29
30const (
31 DEFAULT_NETWORK_NUM int = 1
32)
33
34var IsPersistent = flag.Bool("persist", false, "persists topology config")
35var IsVerbose = flag.Bool("verbose", false, "verbose test output")
36var IsUnconfiguring = flag.Bool("unconfigure", false, "remove topology")
37var IsVppDebug = flag.Bool("debug", false, "attach gdb to vpp")
38var NConfiguredCpus = flag.Int("cpus", 1, "number of CPUs assigned to vpp")
39var VppSourceFileDir = flag.String("vppsrc", "", "vpp source file directory")
Adrian Villinb4516bb2024-06-19 06:20:00 -040040var IsDebugBuild = flag.Bool("debug_build", false, "some paths are different with debug build")
Adrian Villin5d171eb2024-06-17 08:51:27 +020041var UseCpu0 = flag.Bool("cpu0", false, "use cpu0")
Matus Fabiane99d2662024-07-19 16:04:09 +020042var IsLeakCheck = flag.Bool("leak_check", false, "run leak-check tests")
Adrian Villin4995d0d2024-07-29 17:54:58 +020043var ParallelTotal = flag.Lookup("ginkgo.parallel.total")
Adrian Villin2acdf1e2024-09-25 14:49:11 +020044var DryRun = flag.Bool("dryrun", false, "set up containers but don't run tests")
Adrian Villin5d171eb2024-06-17 08:51:27 +020045var NumaAwareCpuAlloc bool
Adrian Villin514098e2024-10-15 14:56:16 +020046var TestTimeout time.Duration
Adrian Villin4677d922024-06-14 09:32:39 +020047
48type HstSuite struct {
49 Containers map[string]*Container
50 StartedContainers []*Container
51 Volumes []string
52 NetConfigs []NetConfig
53 NetInterfaces map[string]*NetInterface
54 Ip4AddrAllocator *Ip4AddressAllocator
55 TestIds map[string]string
56 CpuAllocator *CpuAllocatorT
57 CpuContexts []*CpuContext
Adrian Villinb69ee002024-07-17 14:38:48 +020058 CpuCount int
Adrian Villin4677d922024-06-14 09:32:39 +020059 Ppid string
60 ProcessIndex string
61 Logger *log.Logger
62 LogFile *os.File
Adrian Villin25140012024-07-09 15:31:36 +020063 Docker *client.Client
Adrian Villin4677d922024-06-14 09:32:39 +020064}
65
Adrian Villin2acdf1e2024-09-25 14:49:11 +020066type colors struct {
67 grn string
68 pur string
69 rst string
70}
71
72var Colors = colors{
73 grn: "\033[32m",
74 pur: "\033[35m",
75 rst: "\033[0m",
76}
77
Adrian Villin4995d0d2024-07-29 17:54:58 +020078// used for colorful ReportEntry
79type StringerStruct struct {
80 Label string
81}
82
83// ColorableString for ReportEntry to use
84func (s StringerStruct) ColorableString() string {
85 return fmt.Sprintf("{{red}}%s{{/}}", s.Label)
86}
87
88// non-colorable String() is used by go's string formatting support but ignored by ReportEntry
89func (s StringerStruct) String() string {
90 return s.Label
91}
92
Adrian Villin4677d922024-06-14 09:32:39 +020093func getTestFilename() string {
94 _, filename, _, _ := runtime.Caller(2)
95 return filepath.Base(filename)
96}
97
Adrian Villin4995d0d2024-07-29 17:54:58 +020098func (s *HstSuite) getLogDirPath() string {
99 testId := s.GetTestId()
100 testName := s.GetCurrentTestName()
101 logDirPath := logDir + testName + "/" + testId + "/"
102
103 cmd := exec.Command("mkdir", "-p", logDirPath)
104 if err := cmd.Run(); err != nil {
105 Fail("mkdir error: " + fmt.Sprint(err))
106 }
107
108 return logDirPath
109}
110
Adrian Villin25140012024-07-09 15:31:36 +0200111func (s *HstSuite) newDockerClient() {
112 var err error
113 s.Docker, err = client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
114 s.AssertNil(err)
115 s.Log("docker client created")
116}
117
Adrian Villin4677d922024-06-14 09:32:39 +0200118func (s *HstSuite) SetupSuite() {
119 s.CreateLogger()
Adrian Villin2acdf1e2024-09-25 14:49:11 +0200120 s.Log("[* SUITE SETUP]")
Adrian Villin25140012024-07-09 15:31:36 +0200121 s.newDockerClient()
Adrian Villin4677d922024-06-14 09:32:39 +0200122 RegisterFailHandler(func(message string, callerSkip ...int) {
123 s.HstFail()
124 Fail(message, callerSkip...)
125 })
126 var err error
127 s.Ppid = fmt.Sprint(os.Getppid())
128 // remove last number so we have space to prepend a process index (interfaces have a char limit)
129 s.Ppid = s.Ppid[:len(s.Ppid)-1]
130 s.ProcessIndex = fmt.Sprint(GinkgoParallelProcess())
131 s.CpuAllocator, err = CpuAllocator()
132 if err != nil {
133 Fail("failed to init cpu allocator: " + fmt.Sprint(err))
134 }
Adrian Villinb69ee002024-07-17 14:38:48 +0200135 s.CpuCount = *NConfiguredCpus
Adrian Villin4677d922024-06-14 09:32:39 +0200136}
137
Adrian Villind05f16d2024-11-20 11:11:35 +0100138func (s *HstSuite) AllocateCpus(containerName string) []int {
139 var cpuCtx *CpuContext
140 var err error
141 currentTestName := CurrentSpecReport().LeafNodeText
142
143 if strings.Contains(currentTestName, "MTTest") {
144 prevContainerCount := s.CpuAllocator.maxContainerCount
145 if strings.Contains(containerName, "vpp") {
146 // CPU range is assigned based on the Ginkgo process index (or build number if
147 // running in the CI), *NConfiguredCpus and a maxContainerCount.
148 // maxContainerCount is set to 4 when CpuAllocator is initialized.
149 // 4 is not a random number - all of our suites use a maximum of 4 containers simultaneously,
150 // and it's also the maximum number of containers we can run with *NConfiguredCpus=2 (with CPU0=true)
151 // on processors with 8 threads. Currently, the CpuAllocator puts all cores into a slice,
152 // makes the length of the slice divisible by 4x*NConfiguredCpus, and then the minCpu and
153 // maxCpu (range) for each container is calculated. Then we just offset based on minCpu,
154 // the number of started containers and *NConfiguredCpus. This way, every container
155 // uses the correct CPUs, even if multiple NUMA nodes are available.
156 // However, because of this, if we want to assign different number of cores to different containers,
157 // we have to change maxContainerCount to manipulate the CPU range. Hopefully a temporary workaround.
158 s.CpuAllocator.maxContainerCount = 1
159 cpuCtx, err = s.CpuAllocator.Allocate(1, 3, 0)
160 } else {
161 s.CpuAllocator.maxContainerCount = 3
162 cpuCtx, err = s.CpuAllocator.Allocate(len(s.StartedContainers), s.CpuCount, 2)
163 }
164 s.CpuAllocator.maxContainerCount = prevContainerCount
165 } else {
166 cpuCtx, err = s.CpuAllocator.Allocate(len(s.StartedContainers), s.CpuCount, 0)
Adrian Villin5d171eb2024-06-17 08:51:27 +0200167 }
Adrian Villind05f16d2024-11-20 11:11:35 +0100168
169 s.AssertNil(err)
Adrian Villin4677d922024-06-14 09:32:39 +0200170 s.AddCpuContext(cpuCtx)
171 return cpuCtx.cpus
172}
173
174func (s *HstSuite) AddCpuContext(cpuCtx *CpuContext) {
175 s.CpuContexts = append(s.CpuContexts, cpuCtx)
176}
177
178func (s *HstSuite) TearDownSuite() {
179 defer s.LogFile.Close()
Adrian Villin25140012024-07-09 15:31:36 +0200180 defer s.Docker.Close()
Adrian Villin2acdf1e2024-09-25 14:49:11 +0200181 if *IsPersistent || *DryRun {
182 return
183 }
184 s.Log("[* SUITE TEARDOWN]")
Adrian Villin4677d922024-06-14 09:32:39 +0200185 s.UnconfigureNetworkTopology()
186}
187
188func (s *HstSuite) TearDownTest() {
Adrian Villin2acdf1e2024-09-25 14:49:11 +0200189 s.Log("[* TEST TEARDOWN]")
190 if *IsPersistent || *DryRun {
Adrian Villin4677d922024-06-14 09:32:39 +0200191 return
192 }
Matus Fabiana3efc382024-10-07 12:56:32 +0200193 coreDump := s.WaitForCoreDump()
Adrian Villin4677d922024-06-14 09:32:39 +0200194 s.ResetContainers()
Adrian Villinb69ee002024-07-17 14:38:48 +0200195
196 if s.Ip4AddrAllocator != nil {
197 s.Ip4AddrAllocator.DeleteIpAddresses()
198 }
Matus Fabiana3efc382024-10-07 12:56:32 +0200199
200 if coreDump {
201 Fail("VPP crashed")
202 }
Adrian Villin4677d922024-06-14 09:32:39 +0200203}
204
205func (s *HstSuite) SkipIfUnconfiguring() {
206 if *IsUnconfiguring {
207 s.Skip("skipping to unconfigure")
208 }
209}
210
211func (s *HstSuite) SetupTest() {
Adrian Villin2acdf1e2024-09-25 14:49:11 +0200212 s.Log("[* TEST SETUP]")
Adrian Villin4677d922024-06-14 09:32:39 +0200213 s.StartedContainers = s.StartedContainers[:0]
214 s.SkipIfUnconfiguring()
Adrian Villin4677d922024-06-14 09:32:39 +0200215 s.SetupContainers()
216}
217
Adrian Villin4677d922024-06-14 09:32:39 +0200218func (s *HstSuite) SetupContainers() {
219 for _, container := range s.Containers {
220 if !container.IsOptional {
221 container.Run()
222 }
223 }
224}
225
226func (s *HstSuite) LogVppInstance(container *Container, maxLines int) {
227 if container.VppInstance == nil {
228 return
229 }
230
231 logSource := container.GetHostWorkDir() + defaultLogFilePath
232 file, err := os.Open(logSource)
233
234 if err != nil {
235 return
236 }
237 defer file.Close()
238
239 scanner := bufio.NewScanner(file)
240 var lines []string
241 var counter int
242
243 for scanner.Scan() {
244 lines = append(lines, scanner.Text())
245 counter++
246 if counter > maxLines {
247 lines = lines[1:]
248 counter--
249 }
250 }
251
252 s.Log("vvvvvvvvvvvvvvv " + container.Name + " [VPP instance]:")
253 for _, line := range lines {
254 s.Log(line)
255 }
256 s.Log("^^^^^^^^^^^^^^^\n\n")
257}
258
259func (s *HstSuite) HstFail() {
260 for _, container := range s.StartedContainers {
261 out, err := container.log(20)
262 if err != nil {
263 s.Log("An error occured while obtaining '" + container.Name + "' container logs: " + fmt.Sprint(err))
Adrian Villin4995d0d2024-07-29 17:54:58 +0200264 s.Log("The container might not be running - check logs in " + s.getLogDirPath())
Adrian Villin4677d922024-06-14 09:32:39 +0200265 continue
266 }
267 s.Log("\nvvvvvvvvvvvvvvv " +
268 container.Name + ":\n" +
269 out +
270 "^^^^^^^^^^^^^^^\n\n")
271 s.LogVppInstance(container, 20)
272 }
273}
274
275func (s *HstSuite) AssertNil(object interface{}, msgAndArgs ...interface{}) {
Matus Fabian225e6f82024-08-22 15:32:44 +0200276 ExpectWithOffset(2, object).To(BeNil(), msgAndArgs...)
Adrian Villin4677d922024-06-14 09:32:39 +0200277}
278
279func (s *HstSuite) AssertNotNil(object interface{}, msgAndArgs ...interface{}) {
Matus Fabian225e6f82024-08-22 15:32:44 +0200280 ExpectWithOffset(2, object).ToNot(BeNil(), msgAndArgs...)
Adrian Villin4677d922024-06-14 09:32:39 +0200281}
282
283func (s *HstSuite) AssertEqual(expected, actual interface{}, msgAndArgs ...interface{}) {
Matus Fabian225e6f82024-08-22 15:32:44 +0200284 ExpectWithOffset(2, actual).To(Equal(expected), msgAndArgs...)
Adrian Villin4677d922024-06-14 09:32:39 +0200285}
286
287func (s *HstSuite) AssertNotEqual(expected, actual interface{}, msgAndArgs ...interface{}) {
Matus Fabian225e6f82024-08-22 15:32:44 +0200288 ExpectWithOffset(2, actual).ToNot(Equal(expected), msgAndArgs...)
Adrian Villin4677d922024-06-14 09:32:39 +0200289}
290
291func (s *HstSuite) AssertContains(testString, contains interface{}, msgAndArgs ...interface{}) {
Matus Fabian225e6f82024-08-22 15:32:44 +0200292 ExpectWithOffset(2, testString).To(ContainSubstring(fmt.Sprint(contains)), msgAndArgs...)
Adrian Villin4677d922024-06-14 09:32:39 +0200293}
294
295func (s *HstSuite) AssertNotContains(testString, contains interface{}, msgAndArgs ...interface{}) {
Matus Fabian225e6f82024-08-22 15:32:44 +0200296 ExpectWithOffset(2, testString).ToNot(ContainSubstring(fmt.Sprint(contains)), msgAndArgs...)
Adrian Villin4677d922024-06-14 09:32:39 +0200297}
298
Adrian Villin25140012024-07-09 15:31:36 +0200299func (s *HstSuite) AssertEmpty(object interface{}, msgAndArgs ...interface{}) {
Matus Fabian225e6f82024-08-22 15:32:44 +0200300 ExpectWithOffset(2, object).To(BeEmpty(), msgAndArgs...)
Adrian Villin25140012024-07-09 15:31:36 +0200301}
302
Adrian Villin4677d922024-06-14 09:32:39 +0200303func (s *HstSuite) AssertNotEmpty(object interface{}, msgAndArgs ...interface{}) {
Matus Fabian225e6f82024-08-22 15:32:44 +0200304 ExpectWithOffset(2, object).ToNot(BeEmpty(), msgAndArgs...)
Adrian Villin4677d922024-06-14 09:32:39 +0200305}
306
Matus Fabiand086a362024-06-27 13:20:10 +0200307func (s *HstSuite) AssertMatchError(actual, expected error, msgAndArgs ...interface{}) {
Matus Fabiana647a832024-08-26 18:01:14 +0200308 ExpectWithOffset(2, actual).To(MatchError(expected), msgAndArgs...)
309}
310
311func (s *HstSuite) AssertGreaterThan(actual, expected interface{}, msgAndArgs ...interface{}) {
312 ExpectWithOffset(2, actual).Should(BeNumerically(">=", expected), msgAndArgs...)
313}
314
315func (s *HstSuite) AssertTimeEqualWithinThreshold(actual, expected time.Time, threshold time.Duration, msgAndArgs ...interface{}) {
316 ExpectWithOffset(2, actual).Should(BeTemporally("~", expected, threshold), msgAndArgs...)
317}
318
319func (s *HstSuite) AssertHttpStatus(resp *http.Response, expectedStatus int, msgAndArgs ...interface{}) {
320 ExpectWithOffset(2, resp).To(HaveHTTPStatus(expectedStatus), msgAndArgs...)
321}
322
323func (s *HstSuite) AssertHttpHeaderWithValue(resp *http.Response, key string, value interface{}, msgAndArgs ...interface{}) {
324 ExpectWithOffset(2, resp).To(HaveHTTPHeaderWithValue(key, value), msgAndArgs...)
325}
326
327func (s *HstSuite) AssertHttpHeaderNotPresent(resp *http.Response, key string, msgAndArgs ...interface{}) {
328 ExpectWithOffset(2, resp.Header.Get(key)).To(BeEmpty(), msgAndArgs...)
329}
330
331func (s *HstSuite) AssertHttpContentLength(resp *http.Response, expectedContentLen int64, msgAndArgs ...interface{}) {
332 ExpectWithOffset(2, resp).To(HaveHTTPHeaderWithValue("Content-Length", strconv.FormatInt(expectedContentLen, 10)), msgAndArgs...)
333}
334
335func (s *HstSuite) AssertHttpBody(resp *http.Response, expectedBody string, msgAndArgs ...interface{}) {
336 ExpectWithOffset(2, resp).To(HaveHTTPBody(expectedBody), msgAndArgs...)
Matus Fabiand086a362024-06-27 13:20:10 +0200337}
338
Adrian Villin514098e2024-10-15 14:56:16 +0200339func (s *HstSuite) AssertChannelClosed(timeout time.Duration, channel chan error) {
340 EventuallyWithOffset(2, channel).WithTimeout(timeout).Should(BeClosed())
341}
342
Adrian Villin4677d922024-06-14 09:32:39 +0200343func (s *HstSuite) CreateLogger() {
344 suiteName := s.GetCurrentSuiteName()
345 var err error
346 s.LogFile, err = os.Create("summary/" + suiteName + ".log")
347 if err != nil {
348 Fail("Unable to create log file.")
349 }
350 s.Logger = log.New(io.Writer(s.LogFile), "", log.LstdFlags)
351}
352
353// Logs to files by default, logs to stdout when VERBOSE=true with GinkgoWriter
354// to keep console tidy
Adrian Villin2acdf1e2024-09-25 14:49:11 +0200355func (s *HstSuite) Log(log any, arg ...any) {
Adrian Villin514098e2024-10-15 14:56:16 +0200356 var logStr string
357 if len(arg) == 0 {
358 logStr = fmt.Sprint(log)
359 } else {
360 logStr = fmt.Sprintf(fmt.Sprint(log), arg...)
361 }
362 logs := strings.Split(logStr, "\n")
363
Adrian Villin4677d922024-06-14 09:32:39 +0200364 for _, line := range logs {
365 s.Logger.Println(line)
366 }
367 if *IsVerbose {
Adrian Villin514098e2024-10-15 14:56:16 +0200368 GinkgoWriter.Println(logStr)
Adrian Villin4677d922024-06-14 09:32:39 +0200369 }
370}
371
372func (s *HstSuite) Skip(args string) {
373 Skip(args)
374}
375
376func (s *HstSuite) SkipIfMultiWorker(args ...any) {
377 if *NConfiguredCpus > 1 {
378 s.Skip("test case not supported with multiple vpp workers")
379 }
380}
381
Adrian Villin46ab0b22024-09-19 17:19:39 +0200382func (s *HstSuite) SkipIfNotEnoughAvailableCpus() {
383 var maxRequestedCpu int
384 availableCpus := len(s.CpuAllocator.cpus) - 1
385
386 if *UseCpu0 {
387 availableCpus++
388 }
Adrian Villinb69ee002024-07-17 14:38:48 +0200389
390 if s.CpuAllocator.runningInCi {
Adrian Villin46ab0b22024-09-19 17:19:39 +0200391 maxRequestedCpu = ((s.CpuAllocator.buildNumber + 1) * s.CpuAllocator.maxContainerCount * s.CpuCount)
Adrian Villinb69ee002024-07-17 14:38:48 +0200392 } else {
Adrian Villin46ab0b22024-09-19 17:19:39 +0200393 maxRequestedCpu = (GinkgoParallelProcess() * s.CpuAllocator.maxContainerCount * s.CpuCount)
Adrian Villinb69ee002024-07-17 14:38:48 +0200394 }
Hadi Rayan Al-Sandide0e85132024-06-24 10:28:58 +0200395
Adrian Villin46ab0b22024-09-19 17:19:39 +0200396 if availableCpus < maxRequestedCpu {
397 s.Skip(fmt.Sprintf("Test case cannot allocate requested cpus "+
Adrian Villind05f16d2024-11-20 11:11:35 +0100398 "(%d containers * %d cpus, %d available). Try using 'CPU0=true'",
399 s.CpuAllocator.maxContainerCount, s.CpuCount, availableCpus))
Hadi Rayan Al-Sandide0e85132024-06-24 10:28:58 +0200400 }
Hadi Rayan Al-Sandide0e85132024-06-24 10:28:58 +0200401}
402
Matus Fabiane99d2662024-07-19 16:04:09 +0200403func (s *HstSuite) SkipUnlessLeakCheck() {
404 if !*IsLeakCheck {
405 s.Skip("leak-check tests excluded")
406 }
407}
Adrian Villin4995d0d2024-07-29 17:54:58 +0200408
Matus Fabiana3efc382024-10-07 12:56:32 +0200409func (s *HstSuite) WaitForCoreDump() bool {
Adrian Villin4995d0d2024-07-29 17:54:58 +0200410 var filename string
Adrian Villin4995d0d2024-07-29 17:54:58 +0200411 dir, err := os.Open(s.getLogDirPath())
412 if err != nil {
413 s.Log(err)
Matus Fabiana3efc382024-10-07 12:56:32 +0200414 return false
Adrian Villin4995d0d2024-07-29 17:54:58 +0200415 }
416 defer dir.Close()
417
418 files, err := dir.Readdirnames(0)
419 if err != nil {
420 s.Log(err)
Matus Fabiana3efc382024-10-07 12:56:32 +0200421 return false
Adrian Villin4995d0d2024-07-29 17:54:58 +0200422 }
423 for _, file := range files {
424 if strings.Contains(file, "core") {
425 filename = file
426 }
427 }
428 timeout := 60
429 waitTime := 5
430
431 if filename != "" {
Matus Fabian4306a3e2024-08-23 15:52:54 +0200432 corePath := s.getLogDirPath() + filename
433 s.Log(fmt.Sprintf("WAITING FOR CORE DUMP (%s)", corePath))
Adrian Villin4995d0d2024-07-29 17:54:58 +0200434 for i := waitTime; i <= timeout; i += waitTime {
Matus Fabian4306a3e2024-08-23 15:52:54 +0200435 fileInfo, err := os.Stat(corePath)
Adrian Villin4995d0d2024-07-29 17:54:58 +0200436 if err != nil {
437 s.Log("Error while reading file info: " + fmt.Sprint(err))
Matus Fabiana3efc382024-10-07 12:56:32 +0200438 return true
Adrian Villin4995d0d2024-07-29 17:54:58 +0200439 }
440 currSize := fileInfo.Size()
441 s.Log(fmt.Sprintf("Waiting %ds/%ds...", i, timeout))
442 time.Sleep(time.Duration(waitTime) * time.Second)
Matus Fabian4306a3e2024-08-23 15:52:54 +0200443 fileInfo, _ = os.Stat(corePath)
Adrian Villin4995d0d2024-07-29 17:54:58 +0200444
445 if currSize == fileInfo.Size() {
Matus Fabian4306a3e2024-08-23 15:52:54 +0200446 debug := ""
Adrian Villin4995d0d2024-07-29 17:54:58 +0200447 if *IsDebugBuild {
Matus Fabian4306a3e2024-08-23 15:52:54 +0200448 debug = "_debug"
Adrian Villin4995d0d2024-07-29 17:54:58 +0200449 }
Matus Fabian4306a3e2024-08-23 15:52:54 +0200450 vppBinPath := fmt.Sprintf("../../build-root/build-vpp%s-native/vpp/bin/vpp", debug)
451 pluginsLibPath := fmt.Sprintf("build-root/build-vpp%s-native/vpp/lib/x86_64-linux-gnu/vpp_plugins", debug)
452 cmd := fmt.Sprintf("sudo gdb %s -c %s -ex 'set solib-search-path %s/%s' -ex 'bt full' -batch", vppBinPath, corePath, *VppSourceFileDir, pluginsLibPath)
Adrian Villin4995d0d2024-07-29 17:54:58 +0200453 s.Log(cmd)
Matus Fabian4306a3e2024-08-23 15:52:54 +0200454 output, _ := exechelper.Output(cmd)
Adrian Villin4995d0d2024-07-29 17:54:58 +0200455 AddReportEntry("VPP Backtrace", StringerStruct{Label: string(output)})
456 os.WriteFile(s.getLogDirPath()+"backtrace.log", output, os.FileMode(0644))
457 if s.CpuAllocator.runningInCi {
Matus Fabian4306a3e2024-08-23 15:52:54 +0200458 err = os.Remove(corePath)
Adrian Villin4995d0d2024-07-29 17:54:58 +0200459 if err == nil {
Matus Fabian4306a3e2024-08-23 15:52:54 +0200460 s.Log("removed " + corePath)
Adrian Villin4995d0d2024-07-29 17:54:58 +0200461 } else {
462 s.Log(err)
463 }
464 }
Matus Fabiana3efc382024-10-07 12:56:32 +0200465 return true
Adrian Villin4995d0d2024-07-29 17:54:58 +0200466 }
467 }
468 }
Matus Fabiana3efc382024-10-07 12:56:32 +0200469 return false
Adrian Villin4995d0d2024-07-29 17:54:58 +0200470}
471
Adrian Villin4677d922024-06-14 09:32:39 +0200472func (s *HstSuite) ResetContainers() {
473 for _, container := range s.StartedContainers {
474 container.stop()
Adrian Villin25140012024-07-09 15:31:36 +0200475 s.Log("Removing container " + container.Name)
Adrian Villind74e4402024-11-04 13:16:24 +0100476 if err := s.Docker.ContainerRemove(container.ctx, container.ID, containerTypes.RemoveOptions{RemoveVolumes: true, Force: true}); err != nil {
Adrian Villin25140012024-07-09 15:31:36 +0200477 s.Log(err)
478 }
Adrian Villin4677d922024-06-14 09:32:39 +0200479 }
480}
481
482func (s *HstSuite) GetNetNamespaceByName(name string) string {
483 return s.ProcessIndex + name + s.Ppid
484}
485
486func (s *HstSuite) GetInterfaceByName(name string) *NetInterface {
487 return s.NetInterfaces[s.ProcessIndex+name+s.Ppid]
488}
489
490func (s *HstSuite) GetContainerByName(name string) *Container {
491 return s.Containers[s.ProcessIndex+name+s.Ppid]
492}
493
494/*
495 * Create a copy and return its address, so that individial tests which call this
496 * are not able to modify the original container and affect other tests by doing that
497 */
498func (s *HstSuite) GetTransientContainerByName(name string) *Container {
499 containerCopy := *s.Containers[s.ProcessIndex+name+s.Ppid]
500 return &containerCopy
501}
502
503func (s *HstSuite) LoadContainerTopology(topologyName string) {
504 data, err := os.ReadFile(containerTopologyDir + topologyName + ".yaml")
505 if err != nil {
506 Fail("read error: " + fmt.Sprint(err))
507 }
508 var yamlTopo YamlTopology
509 err = yaml.Unmarshal(data, &yamlTopo)
510 if err != nil {
511 Fail("unmarshal error: " + fmt.Sprint(err))
512 }
513
514 for _, elem := range yamlTopo.Volumes {
515 volumeMap := elem["volume"].(VolumeConfig)
516 hostDir := volumeMap["host-dir"].(string)
517 workingVolumeDir := logDir + s.GetCurrentTestName() + volumeDir
518 volDirReplacer := strings.NewReplacer("$HST_VOLUME_DIR", workingVolumeDir)
519 hostDir = volDirReplacer.Replace(hostDir)
520 s.Volumes = append(s.Volumes, hostDir)
521 }
522
523 s.Containers = make(map[string]*Container)
524 for _, elem := range yamlTopo.Containers {
525 newContainer, err := newContainer(s, elem)
526 newContainer.Suite = s
527 newContainer.Name = newContainer.Suite.ProcessIndex + newContainer.Name + newContainer.Suite.Ppid
528 if err != nil {
529 Fail("container config error: " + fmt.Sprint(err))
530 }
531 s.Containers[newContainer.Name] = newContainer
532 }
Adrian Villin2acdf1e2024-09-25 14:49:11 +0200533
534 if *DryRun {
535 s.Log(Colors.pur + "* Containers used by this suite (some might already be running):" + Colors.rst)
536 for name := range s.Containers {
537 s.Log("%sdocker start %s && docker exec -it %s bash%s", Colors.pur, name, name, Colors.rst)
538 }
539 }
Adrian Villin4677d922024-06-14 09:32:39 +0200540}
541
542func (s *HstSuite) LoadNetworkTopology(topologyName string) {
543 data, err := os.ReadFile(networkTopologyDir + topologyName + ".yaml")
544 if err != nil {
545 Fail("read error: " + fmt.Sprint(err))
546 }
547 var yamlTopo YamlTopology
548 err = yaml.Unmarshal(data, &yamlTopo)
549 if err != nil {
550 Fail("unmarshal error: " + fmt.Sprint(err))
551 }
552
553 s.Ip4AddrAllocator = NewIp4AddressAllocator()
554 s.NetInterfaces = make(map[string]*NetInterface)
555
556 for _, elem := range yamlTopo.Devices {
557 if _, ok := elem["name"]; ok {
558 elem["name"] = s.ProcessIndex + elem["name"].(string) + s.Ppid
559 }
560
561 if peer, ok := elem["peer"].(NetDevConfig); ok {
562 if peer["name"].(string) != "" {
563 peer["name"] = s.ProcessIndex + peer["name"].(string) + s.Ppid
564 }
565 if _, ok := peer["netns"]; ok {
566 peer["netns"] = s.ProcessIndex + peer["netns"].(string) + s.Ppid
567 }
568 }
569
570 if _, ok := elem["netns"]; ok {
571 elem["netns"] = s.ProcessIndex + elem["netns"].(string) + s.Ppid
572 }
573
574 if _, ok := elem["interfaces"]; ok {
575 interfaceCount := len(elem["interfaces"].([]interface{}))
576 for i := 0; i < interfaceCount; i++ {
577 elem["interfaces"].([]interface{})[i] = s.ProcessIndex + elem["interfaces"].([]interface{})[i].(string) + s.Ppid
578 }
579 }
580
581 switch elem["type"].(string) {
582 case NetNs:
583 {
584 if namespace, err := newNetNamespace(elem); err == nil {
585 s.NetConfigs = append(s.NetConfigs, &namespace)
586 } else {
587 Fail("network config error: " + fmt.Sprint(err))
588 }
589 }
590 case Veth, Tap:
591 {
592 if netIf, err := newNetworkInterface(elem, s.Ip4AddrAllocator); err == nil {
593 s.NetConfigs = append(s.NetConfigs, netIf)
594 s.NetInterfaces[netIf.Name()] = netIf
595 } else {
596 Fail("network config error: " + fmt.Sprint(err))
597 }
598 }
599 case Bridge:
600 {
601 if bridge, err := newBridge(elem); err == nil {
602 s.NetConfigs = append(s.NetConfigs, &bridge)
603 } else {
604 Fail("network config error: " + fmt.Sprint(err))
605 }
606 }
607 }
608 }
609}
610
611func (s *HstSuite) ConfigureNetworkTopology(topologyName string) {
612 s.LoadNetworkTopology(topologyName)
613
614 if *IsUnconfiguring {
615 return
616 }
617
618 for _, nc := range s.NetConfigs {
619 s.Log(nc.Name())
620 if err := nc.configure(); err != nil {
621 Fail("Network config error: " + fmt.Sprint(err))
622 }
623 }
624}
625
626func (s *HstSuite) UnconfigureNetworkTopology() {
Adrian Villin4677d922024-06-14 09:32:39 +0200627 for _, nc := range s.NetConfigs {
628 nc.unconfigure()
629 }
630}
631
Adrian Villin2acdf1e2024-09-25 14:49:11 +0200632func (s *HstSuite) LogStartedContainers() {
633 s.Log("%s* Started containers:%s", Colors.grn, Colors.rst)
634 for _, container := range s.StartedContainers {
635 s.Log(Colors.grn + container.Name + Colors.rst)
636 }
637}
638
Adrian Villin4677d922024-06-14 09:32:39 +0200639func (s *HstSuite) GetTestId() string {
640 testName := s.GetCurrentTestName()
641
642 if s.TestIds == nil {
643 s.TestIds = map[string]string{}
644 }
645
646 if _, ok := s.TestIds[testName]; !ok {
647 s.TestIds[testName] = time.Now().Format("2006-01-02_15-04-05")
648 }
649
650 return s.TestIds[testName]
651}
652
653func (s *HstSuite) GetCurrentTestName() string {
654 return strings.Split(CurrentSpecReport().LeafNodeText, "/")[1]
655}
656
657func (s *HstSuite) GetCurrentSuiteName() string {
658 return CurrentSpecReport().ContainerHierarchyTexts[0]
659}
660
661// Returns last 3 digits of PID + Ginkgo process index as the 4th digit
662func (s *HstSuite) GetPortFromPpid() string {
663 port := s.Ppid
664 for len(port) < 3 {
665 port += "0"
666 }
667 return port[len(port)-3:] + s.ProcessIndex
668}
669
Adrian Villin4677d922024-06-14 09:32:39 +0200670/*
Matus Fabiand46e6742024-07-31 16:08:40 +0200671RunBenchmark 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 +0200672passing in suite context, experiment and your data.
673
674You can also instruct runBenchmark to run with multiple concurrent workers.
Matus Fabian5c4c1b62024-06-28 16:11:04 +0200675Note 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 +0200676You can record multiple named measurements (float64 or duration) within passed-in callback.
677runBenchmark then produces report to show statistical distribution of measurements.
678*/
679func (s *HstSuite) RunBenchmark(name string, samplesNum, parallelNum int, callback func(s *HstSuite, e *gmeasure.Experiment, data interface{}), data interface{}) {
680 experiment := gmeasure.NewExperiment(name)
681
682 experiment.Sample(func(idx int) {
683 defer GinkgoRecover()
684 callback(s, experiment, data)
685 }, gmeasure.SamplingConfig{N: samplesNum, NumParallel: parallelNum})
686 AddReportEntry(experiment.Name, experiment)
687}
Matus Fabiand46e6742024-07-31 16:08:40 +0200688
689/*
690LogHttpReq is Gomega's ghttp server handler which logs received HTTP request.
691
692You should put it at the first place, so request is logged always.
693*/
694func (s *HstSuite) LogHttpReq(body bool) http.HandlerFunc {
695 return func(w http.ResponseWriter, req *http.Request) {
696 dump, err := httputil.DumpRequest(req, body)
697 if err == nil {
698 s.Log("\n> Received request (" + req.RemoteAddr + "):\n" +
699 string(dump) +
700 "\n------------------------------\n")
701 }
702 }
703}