blob: bf46dfdef7e2bd55fe411cb1618fb665dec9bfc6 [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
138func (s *HstSuite) AllocateCpus() []int {
Adrian Villinb69ee002024-07-17 14:38:48 +0200139 cpuCtx, err := s.CpuAllocator.Allocate(len(s.StartedContainers), s.CpuCount)
Adrian Villin5d171eb2024-06-17 08:51:27 +0200140 // using Fail instead of AssertNil to make error message more readable
141 if err != nil {
142 Fail(fmt.Sprint(err))
143 }
Adrian Villin4677d922024-06-14 09:32:39 +0200144 s.AddCpuContext(cpuCtx)
145 return cpuCtx.cpus
146}
147
148func (s *HstSuite) AddCpuContext(cpuCtx *CpuContext) {
149 s.CpuContexts = append(s.CpuContexts, cpuCtx)
150}
151
152func (s *HstSuite) TearDownSuite() {
153 defer s.LogFile.Close()
Adrian Villin25140012024-07-09 15:31:36 +0200154 defer s.Docker.Close()
Adrian Villin2acdf1e2024-09-25 14:49:11 +0200155 if *IsPersistent || *DryRun {
156 return
157 }
158 s.Log("[* SUITE TEARDOWN]")
Adrian Villin4677d922024-06-14 09:32:39 +0200159 s.UnconfigureNetworkTopology()
160}
161
162func (s *HstSuite) TearDownTest() {
Adrian Villin2acdf1e2024-09-25 14:49:11 +0200163 s.Log("[* TEST TEARDOWN]")
164 if *IsPersistent || *DryRun {
Adrian Villin4677d922024-06-14 09:32:39 +0200165 return
166 }
Matus Fabiana3efc382024-10-07 12:56:32 +0200167 coreDump := s.WaitForCoreDump()
Adrian Villin4677d922024-06-14 09:32:39 +0200168 s.ResetContainers()
Adrian Villinb69ee002024-07-17 14:38:48 +0200169
170 if s.Ip4AddrAllocator != nil {
171 s.Ip4AddrAllocator.DeleteIpAddresses()
172 }
Matus Fabiana3efc382024-10-07 12:56:32 +0200173
174 if coreDump {
175 Fail("VPP crashed")
176 }
Adrian Villin4677d922024-06-14 09:32:39 +0200177}
178
179func (s *HstSuite) SkipIfUnconfiguring() {
180 if *IsUnconfiguring {
181 s.Skip("skipping to unconfigure")
182 }
183}
184
185func (s *HstSuite) SetupTest() {
Adrian Villin2acdf1e2024-09-25 14:49:11 +0200186 s.Log("[* TEST SETUP]")
Adrian Villin4677d922024-06-14 09:32:39 +0200187 s.StartedContainers = s.StartedContainers[:0]
188 s.SkipIfUnconfiguring()
Adrian Villin4677d922024-06-14 09:32:39 +0200189 s.SetupContainers()
190}
191
Adrian Villin4677d922024-06-14 09:32:39 +0200192func (s *HstSuite) SetupContainers() {
193 for _, container := range s.Containers {
194 if !container.IsOptional {
195 container.Run()
196 }
197 }
198}
199
200func (s *HstSuite) LogVppInstance(container *Container, maxLines int) {
201 if container.VppInstance == nil {
202 return
203 }
204
205 logSource := container.GetHostWorkDir() + defaultLogFilePath
206 file, err := os.Open(logSource)
207
208 if err != nil {
209 return
210 }
211 defer file.Close()
212
213 scanner := bufio.NewScanner(file)
214 var lines []string
215 var counter int
216
217 for scanner.Scan() {
218 lines = append(lines, scanner.Text())
219 counter++
220 if counter > maxLines {
221 lines = lines[1:]
222 counter--
223 }
224 }
225
226 s.Log("vvvvvvvvvvvvvvv " + container.Name + " [VPP instance]:")
227 for _, line := range lines {
228 s.Log(line)
229 }
230 s.Log("^^^^^^^^^^^^^^^\n\n")
231}
232
233func (s *HstSuite) HstFail() {
234 for _, container := range s.StartedContainers {
235 out, err := container.log(20)
236 if err != nil {
237 s.Log("An error occured while obtaining '" + container.Name + "' container logs: " + fmt.Sprint(err))
Adrian Villin4995d0d2024-07-29 17:54:58 +0200238 s.Log("The container might not be running - check logs in " + s.getLogDirPath())
Adrian Villin4677d922024-06-14 09:32:39 +0200239 continue
240 }
241 s.Log("\nvvvvvvvvvvvvvvv " +
242 container.Name + ":\n" +
243 out +
244 "^^^^^^^^^^^^^^^\n\n")
245 s.LogVppInstance(container, 20)
246 }
247}
248
249func (s *HstSuite) AssertNil(object interface{}, msgAndArgs ...interface{}) {
Matus Fabian225e6f82024-08-22 15:32:44 +0200250 ExpectWithOffset(2, object).To(BeNil(), msgAndArgs...)
Adrian Villin4677d922024-06-14 09:32:39 +0200251}
252
253func (s *HstSuite) AssertNotNil(object interface{}, msgAndArgs ...interface{}) {
Matus Fabian225e6f82024-08-22 15:32:44 +0200254 ExpectWithOffset(2, object).ToNot(BeNil(), msgAndArgs...)
Adrian Villin4677d922024-06-14 09:32:39 +0200255}
256
257func (s *HstSuite) AssertEqual(expected, actual interface{}, msgAndArgs ...interface{}) {
Matus Fabian225e6f82024-08-22 15:32:44 +0200258 ExpectWithOffset(2, actual).To(Equal(expected), msgAndArgs...)
Adrian Villin4677d922024-06-14 09:32:39 +0200259}
260
261func (s *HstSuite) AssertNotEqual(expected, actual interface{}, msgAndArgs ...interface{}) {
Matus Fabian225e6f82024-08-22 15:32:44 +0200262 ExpectWithOffset(2, actual).ToNot(Equal(expected), msgAndArgs...)
Adrian Villin4677d922024-06-14 09:32:39 +0200263}
264
265func (s *HstSuite) AssertContains(testString, contains interface{}, msgAndArgs ...interface{}) {
Matus Fabian225e6f82024-08-22 15:32:44 +0200266 ExpectWithOffset(2, testString).To(ContainSubstring(fmt.Sprint(contains)), msgAndArgs...)
Adrian Villin4677d922024-06-14 09:32:39 +0200267}
268
269func (s *HstSuite) AssertNotContains(testString, contains interface{}, msgAndArgs ...interface{}) {
Matus Fabian225e6f82024-08-22 15:32:44 +0200270 ExpectWithOffset(2, testString).ToNot(ContainSubstring(fmt.Sprint(contains)), msgAndArgs...)
Adrian Villin4677d922024-06-14 09:32:39 +0200271}
272
Adrian Villin25140012024-07-09 15:31:36 +0200273func (s *HstSuite) AssertEmpty(object interface{}, msgAndArgs ...interface{}) {
Matus Fabian225e6f82024-08-22 15:32:44 +0200274 ExpectWithOffset(2, object).To(BeEmpty(), msgAndArgs...)
Adrian Villin25140012024-07-09 15:31:36 +0200275}
276
Adrian Villin4677d922024-06-14 09:32:39 +0200277func (s *HstSuite) AssertNotEmpty(object interface{}, msgAndArgs ...interface{}) {
Matus Fabian225e6f82024-08-22 15:32:44 +0200278 ExpectWithOffset(2, object).ToNot(BeEmpty(), msgAndArgs...)
Adrian Villin4677d922024-06-14 09:32:39 +0200279}
280
Matus Fabiand086a362024-06-27 13:20:10 +0200281func (s *HstSuite) AssertMatchError(actual, expected error, msgAndArgs ...interface{}) {
Matus Fabiana647a832024-08-26 18:01:14 +0200282 ExpectWithOffset(2, actual).To(MatchError(expected), msgAndArgs...)
283}
284
285func (s *HstSuite) AssertGreaterThan(actual, expected interface{}, msgAndArgs ...interface{}) {
286 ExpectWithOffset(2, actual).Should(BeNumerically(">=", expected), msgAndArgs...)
287}
288
289func (s *HstSuite) AssertTimeEqualWithinThreshold(actual, expected time.Time, threshold time.Duration, msgAndArgs ...interface{}) {
290 ExpectWithOffset(2, actual).Should(BeTemporally("~", expected, threshold), msgAndArgs...)
291}
292
293func (s *HstSuite) AssertHttpStatus(resp *http.Response, expectedStatus int, msgAndArgs ...interface{}) {
294 ExpectWithOffset(2, resp).To(HaveHTTPStatus(expectedStatus), msgAndArgs...)
295}
296
297func (s *HstSuite) AssertHttpHeaderWithValue(resp *http.Response, key string, value interface{}, msgAndArgs ...interface{}) {
298 ExpectWithOffset(2, resp).To(HaveHTTPHeaderWithValue(key, value), msgAndArgs...)
299}
300
301func (s *HstSuite) AssertHttpHeaderNotPresent(resp *http.Response, key string, msgAndArgs ...interface{}) {
302 ExpectWithOffset(2, resp.Header.Get(key)).To(BeEmpty(), msgAndArgs...)
303}
304
305func (s *HstSuite) AssertHttpContentLength(resp *http.Response, expectedContentLen int64, msgAndArgs ...interface{}) {
306 ExpectWithOffset(2, resp).To(HaveHTTPHeaderWithValue("Content-Length", strconv.FormatInt(expectedContentLen, 10)), msgAndArgs...)
307}
308
309func (s *HstSuite) AssertHttpBody(resp *http.Response, expectedBody string, msgAndArgs ...interface{}) {
310 ExpectWithOffset(2, resp).To(HaveHTTPBody(expectedBody), msgAndArgs...)
Matus Fabiand086a362024-06-27 13:20:10 +0200311}
312
Adrian Villin514098e2024-10-15 14:56:16 +0200313func (s *HstSuite) AssertChannelClosed(timeout time.Duration, channel chan error) {
314 EventuallyWithOffset(2, channel).WithTimeout(timeout).Should(BeClosed())
315}
316
Adrian Villin4677d922024-06-14 09:32:39 +0200317func (s *HstSuite) CreateLogger() {
318 suiteName := s.GetCurrentSuiteName()
319 var err error
320 s.LogFile, err = os.Create("summary/" + suiteName + ".log")
321 if err != nil {
322 Fail("Unable to create log file.")
323 }
324 s.Logger = log.New(io.Writer(s.LogFile), "", log.LstdFlags)
325}
326
327// Logs to files by default, logs to stdout when VERBOSE=true with GinkgoWriter
328// to keep console tidy
Adrian Villin2acdf1e2024-09-25 14:49:11 +0200329func (s *HstSuite) Log(log any, arg ...any) {
Adrian Villin514098e2024-10-15 14:56:16 +0200330 var logStr string
331 if len(arg) == 0 {
332 logStr = fmt.Sprint(log)
333 } else {
334 logStr = fmt.Sprintf(fmt.Sprint(log), arg...)
335 }
336 logs := strings.Split(logStr, "\n")
337
Adrian Villin4677d922024-06-14 09:32:39 +0200338 for _, line := range logs {
339 s.Logger.Println(line)
340 }
341 if *IsVerbose {
Adrian Villin514098e2024-10-15 14:56:16 +0200342 GinkgoWriter.Println(logStr)
Adrian Villin4677d922024-06-14 09:32:39 +0200343 }
344}
345
346func (s *HstSuite) Skip(args string) {
347 Skip(args)
348}
349
350func (s *HstSuite) SkipIfMultiWorker(args ...any) {
351 if *NConfiguredCpus > 1 {
352 s.Skip("test case not supported with multiple vpp workers")
353 }
354}
355
Adrian Villin46ab0b22024-09-19 17:19:39 +0200356func (s *HstSuite) SkipIfNotEnoughAvailableCpus() {
357 var maxRequestedCpu int
358 availableCpus := len(s.CpuAllocator.cpus) - 1
359
360 if *UseCpu0 {
361 availableCpus++
362 }
Adrian Villinb69ee002024-07-17 14:38:48 +0200363
364 if s.CpuAllocator.runningInCi {
Adrian Villin46ab0b22024-09-19 17:19:39 +0200365 maxRequestedCpu = ((s.CpuAllocator.buildNumber + 1) * s.CpuAllocator.maxContainerCount * s.CpuCount)
Adrian Villinb69ee002024-07-17 14:38:48 +0200366 } else {
Adrian Villin46ab0b22024-09-19 17:19:39 +0200367 maxRequestedCpu = (GinkgoParallelProcess() * s.CpuAllocator.maxContainerCount * s.CpuCount)
Adrian Villinb69ee002024-07-17 14:38:48 +0200368 }
Hadi Rayan Al-Sandide0e85132024-06-24 10:28:58 +0200369
Adrian Villin46ab0b22024-09-19 17:19:39 +0200370 if availableCpus < maxRequestedCpu {
371 s.Skip(fmt.Sprintf("Test case cannot allocate requested cpus "+
372 "(%d cpus * %d containers, %d available). Try using 'CPU0=true'",
373 s.CpuCount, s.CpuAllocator.maxContainerCount, availableCpus))
Hadi Rayan Al-Sandide0e85132024-06-24 10:28:58 +0200374 }
Hadi Rayan Al-Sandide0e85132024-06-24 10:28:58 +0200375}
376
Matus Fabiane99d2662024-07-19 16:04:09 +0200377func (s *HstSuite) SkipUnlessLeakCheck() {
378 if !*IsLeakCheck {
379 s.Skip("leak-check tests excluded")
380 }
381}
Adrian Villin4995d0d2024-07-29 17:54:58 +0200382
Matus Fabiana3efc382024-10-07 12:56:32 +0200383func (s *HstSuite) WaitForCoreDump() bool {
Adrian Villin4995d0d2024-07-29 17:54:58 +0200384 var filename string
Adrian Villin4995d0d2024-07-29 17:54:58 +0200385 dir, err := os.Open(s.getLogDirPath())
386 if err != nil {
387 s.Log(err)
Matus Fabiana3efc382024-10-07 12:56:32 +0200388 return false
Adrian Villin4995d0d2024-07-29 17:54:58 +0200389 }
390 defer dir.Close()
391
392 files, err := dir.Readdirnames(0)
393 if err != nil {
394 s.Log(err)
Matus Fabiana3efc382024-10-07 12:56:32 +0200395 return false
Adrian Villin4995d0d2024-07-29 17:54:58 +0200396 }
397 for _, file := range files {
398 if strings.Contains(file, "core") {
399 filename = file
400 }
401 }
402 timeout := 60
403 waitTime := 5
404
405 if filename != "" {
Matus Fabian4306a3e2024-08-23 15:52:54 +0200406 corePath := s.getLogDirPath() + filename
407 s.Log(fmt.Sprintf("WAITING FOR CORE DUMP (%s)", corePath))
Adrian Villin4995d0d2024-07-29 17:54:58 +0200408 for i := waitTime; i <= timeout; i += waitTime {
Matus Fabian4306a3e2024-08-23 15:52:54 +0200409 fileInfo, err := os.Stat(corePath)
Adrian Villin4995d0d2024-07-29 17:54:58 +0200410 if err != nil {
411 s.Log("Error while reading file info: " + fmt.Sprint(err))
Matus Fabiana3efc382024-10-07 12:56:32 +0200412 return true
Adrian Villin4995d0d2024-07-29 17:54:58 +0200413 }
414 currSize := fileInfo.Size()
415 s.Log(fmt.Sprintf("Waiting %ds/%ds...", i, timeout))
416 time.Sleep(time.Duration(waitTime) * time.Second)
Matus Fabian4306a3e2024-08-23 15:52:54 +0200417 fileInfo, _ = os.Stat(corePath)
Adrian Villin4995d0d2024-07-29 17:54:58 +0200418
419 if currSize == fileInfo.Size() {
Matus Fabian4306a3e2024-08-23 15:52:54 +0200420 debug := ""
Adrian Villin4995d0d2024-07-29 17:54:58 +0200421 if *IsDebugBuild {
Matus Fabian4306a3e2024-08-23 15:52:54 +0200422 debug = "_debug"
Adrian Villin4995d0d2024-07-29 17:54:58 +0200423 }
Matus Fabian4306a3e2024-08-23 15:52:54 +0200424 vppBinPath := fmt.Sprintf("../../build-root/build-vpp%s-native/vpp/bin/vpp", debug)
425 pluginsLibPath := fmt.Sprintf("build-root/build-vpp%s-native/vpp/lib/x86_64-linux-gnu/vpp_plugins", debug)
426 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 +0200427 s.Log(cmd)
Matus Fabian4306a3e2024-08-23 15:52:54 +0200428 output, _ := exechelper.Output(cmd)
Adrian Villin4995d0d2024-07-29 17:54:58 +0200429 AddReportEntry("VPP Backtrace", StringerStruct{Label: string(output)})
430 os.WriteFile(s.getLogDirPath()+"backtrace.log", output, os.FileMode(0644))
431 if s.CpuAllocator.runningInCi {
Matus Fabian4306a3e2024-08-23 15:52:54 +0200432 err = os.Remove(corePath)
Adrian Villin4995d0d2024-07-29 17:54:58 +0200433 if err == nil {
Matus Fabian4306a3e2024-08-23 15:52:54 +0200434 s.Log("removed " + corePath)
Adrian Villin4995d0d2024-07-29 17:54:58 +0200435 } else {
436 s.Log(err)
437 }
438 }
Matus Fabiana3efc382024-10-07 12:56:32 +0200439 return true
Adrian Villin4995d0d2024-07-29 17:54:58 +0200440 }
441 }
442 }
Matus Fabiana3efc382024-10-07 12:56:32 +0200443 return false
Adrian Villin4995d0d2024-07-29 17:54:58 +0200444}
445
Adrian Villin4677d922024-06-14 09:32:39 +0200446func (s *HstSuite) ResetContainers() {
447 for _, container := range s.StartedContainers {
448 container.stop()
Adrian Villin25140012024-07-09 15:31:36 +0200449 s.Log("Removing container " + container.Name)
Adrian Villind74e4402024-11-04 13:16:24 +0100450 if err := s.Docker.ContainerRemove(container.ctx, container.ID, containerTypes.RemoveOptions{RemoveVolumes: true, Force: true}); err != nil {
Adrian Villin25140012024-07-09 15:31:36 +0200451 s.Log(err)
452 }
Adrian Villin4677d922024-06-14 09:32:39 +0200453 }
454}
455
456func (s *HstSuite) GetNetNamespaceByName(name string) string {
457 return s.ProcessIndex + name + s.Ppid
458}
459
460func (s *HstSuite) GetInterfaceByName(name string) *NetInterface {
461 return s.NetInterfaces[s.ProcessIndex+name+s.Ppid]
462}
463
464func (s *HstSuite) GetContainerByName(name string) *Container {
465 return s.Containers[s.ProcessIndex+name+s.Ppid]
466}
467
468/*
469 * Create a copy and return its address, so that individial tests which call this
470 * are not able to modify the original container and affect other tests by doing that
471 */
472func (s *HstSuite) GetTransientContainerByName(name string) *Container {
473 containerCopy := *s.Containers[s.ProcessIndex+name+s.Ppid]
474 return &containerCopy
475}
476
477func (s *HstSuite) LoadContainerTopology(topologyName string) {
478 data, err := os.ReadFile(containerTopologyDir + topologyName + ".yaml")
479 if err != nil {
480 Fail("read error: " + fmt.Sprint(err))
481 }
482 var yamlTopo YamlTopology
483 err = yaml.Unmarshal(data, &yamlTopo)
484 if err != nil {
485 Fail("unmarshal error: " + fmt.Sprint(err))
486 }
487
488 for _, elem := range yamlTopo.Volumes {
489 volumeMap := elem["volume"].(VolumeConfig)
490 hostDir := volumeMap["host-dir"].(string)
491 workingVolumeDir := logDir + s.GetCurrentTestName() + volumeDir
492 volDirReplacer := strings.NewReplacer("$HST_VOLUME_DIR", workingVolumeDir)
493 hostDir = volDirReplacer.Replace(hostDir)
494 s.Volumes = append(s.Volumes, hostDir)
495 }
496
497 s.Containers = make(map[string]*Container)
498 for _, elem := range yamlTopo.Containers {
499 newContainer, err := newContainer(s, elem)
500 newContainer.Suite = s
501 newContainer.Name = newContainer.Suite.ProcessIndex + newContainer.Name + newContainer.Suite.Ppid
502 if err != nil {
503 Fail("container config error: " + fmt.Sprint(err))
504 }
505 s.Containers[newContainer.Name] = newContainer
506 }
Adrian Villin2acdf1e2024-09-25 14:49:11 +0200507
508 if *DryRun {
509 s.Log(Colors.pur + "* Containers used by this suite (some might already be running):" + Colors.rst)
510 for name := range s.Containers {
511 s.Log("%sdocker start %s && docker exec -it %s bash%s", Colors.pur, name, name, Colors.rst)
512 }
513 }
Adrian Villin4677d922024-06-14 09:32:39 +0200514}
515
516func (s *HstSuite) LoadNetworkTopology(topologyName string) {
517 data, err := os.ReadFile(networkTopologyDir + topologyName + ".yaml")
518 if err != nil {
519 Fail("read error: " + fmt.Sprint(err))
520 }
521 var yamlTopo YamlTopology
522 err = yaml.Unmarshal(data, &yamlTopo)
523 if err != nil {
524 Fail("unmarshal error: " + fmt.Sprint(err))
525 }
526
527 s.Ip4AddrAllocator = NewIp4AddressAllocator()
528 s.NetInterfaces = make(map[string]*NetInterface)
529
530 for _, elem := range yamlTopo.Devices {
531 if _, ok := elem["name"]; ok {
532 elem["name"] = s.ProcessIndex + elem["name"].(string) + s.Ppid
533 }
534
535 if peer, ok := elem["peer"].(NetDevConfig); ok {
536 if peer["name"].(string) != "" {
537 peer["name"] = s.ProcessIndex + peer["name"].(string) + s.Ppid
538 }
539 if _, ok := peer["netns"]; ok {
540 peer["netns"] = s.ProcessIndex + peer["netns"].(string) + s.Ppid
541 }
542 }
543
544 if _, ok := elem["netns"]; ok {
545 elem["netns"] = s.ProcessIndex + elem["netns"].(string) + s.Ppid
546 }
547
548 if _, ok := elem["interfaces"]; ok {
549 interfaceCount := len(elem["interfaces"].([]interface{}))
550 for i := 0; i < interfaceCount; i++ {
551 elem["interfaces"].([]interface{})[i] = s.ProcessIndex + elem["interfaces"].([]interface{})[i].(string) + s.Ppid
552 }
553 }
554
555 switch elem["type"].(string) {
556 case NetNs:
557 {
558 if namespace, err := newNetNamespace(elem); err == nil {
559 s.NetConfigs = append(s.NetConfigs, &namespace)
560 } else {
561 Fail("network config error: " + fmt.Sprint(err))
562 }
563 }
564 case Veth, Tap:
565 {
566 if netIf, err := newNetworkInterface(elem, s.Ip4AddrAllocator); err == nil {
567 s.NetConfigs = append(s.NetConfigs, netIf)
568 s.NetInterfaces[netIf.Name()] = netIf
569 } else {
570 Fail("network config error: " + fmt.Sprint(err))
571 }
572 }
573 case Bridge:
574 {
575 if bridge, err := newBridge(elem); err == nil {
576 s.NetConfigs = append(s.NetConfigs, &bridge)
577 } else {
578 Fail("network config error: " + fmt.Sprint(err))
579 }
580 }
581 }
582 }
583}
584
585func (s *HstSuite) ConfigureNetworkTopology(topologyName string) {
586 s.LoadNetworkTopology(topologyName)
587
588 if *IsUnconfiguring {
589 return
590 }
591
592 for _, nc := range s.NetConfigs {
593 s.Log(nc.Name())
594 if err := nc.configure(); err != nil {
595 Fail("Network config error: " + fmt.Sprint(err))
596 }
597 }
598}
599
600func (s *HstSuite) UnconfigureNetworkTopology() {
Adrian Villin4677d922024-06-14 09:32:39 +0200601 for _, nc := range s.NetConfigs {
602 nc.unconfigure()
603 }
604}
605
Adrian Villin2acdf1e2024-09-25 14:49:11 +0200606func (s *HstSuite) LogStartedContainers() {
607 s.Log("%s* Started containers:%s", Colors.grn, Colors.rst)
608 for _, container := range s.StartedContainers {
609 s.Log(Colors.grn + container.Name + Colors.rst)
610 }
611}
612
Adrian Villin4677d922024-06-14 09:32:39 +0200613func (s *HstSuite) GetTestId() string {
614 testName := s.GetCurrentTestName()
615
616 if s.TestIds == nil {
617 s.TestIds = map[string]string{}
618 }
619
620 if _, ok := s.TestIds[testName]; !ok {
621 s.TestIds[testName] = time.Now().Format("2006-01-02_15-04-05")
622 }
623
624 return s.TestIds[testName]
625}
626
627func (s *HstSuite) GetCurrentTestName() string {
628 return strings.Split(CurrentSpecReport().LeafNodeText, "/")[1]
629}
630
631func (s *HstSuite) GetCurrentSuiteName() string {
632 return CurrentSpecReport().ContainerHierarchyTexts[0]
633}
634
635// Returns last 3 digits of PID + Ginkgo process index as the 4th digit
636func (s *HstSuite) GetPortFromPpid() string {
637 port := s.Ppid
638 for len(port) < 3 {
639 port += "0"
640 }
641 return port[len(port)-3:] + s.ProcessIndex
642}
643
Adrian Villin4677d922024-06-14 09:32:39 +0200644/*
Matus Fabiand46e6742024-07-31 16:08:40 +0200645RunBenchmark 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 +0200646passing in suite context, experiment and your data.
647
648You can also instruct runBenchmark to run with multiple concurrent workers.
Matus Fabian5c4c1b62024-06-28 16:11:04 +0200649Note 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 +0200650You can record multiple named measurements (float64 or duration) within passed-in callback.
651runBenchmark then produces report to show statistical distribution of measurements.
652*/
653func (s *HstSuite) RunBenchmark(name string, samplesNum, parallelNum int, callback func(s *HstSuite, e *gmeasure.Experiment, data interface{}), data interface{}) {
654 experiment := gmeasure.NewExperiment(name)
655
656 experiment.Sample(func(idx int) {
657 defer GinkgoRecover()
658 callback(s, experiment, data)
659 }, gmeasure.SamplingConfig{N: samplesNum, NumParallel: parallelNum})
660 AddReportEntry(experiment.Name, experiment)
661}
Matus Fabiand46e6742024-07-31 16:08:40 +0200662
663/*
664LogHttpReq is Gomega's ghttp server handler which logs received HTTP request.
665
666You should put it at the first place, so request is logged always.
667*/
668func (s *HstSuite) LogHttpReq(body bool) http.HandlerFunc {
669 return func(w http.ResponseWriter, req *http.Request) {
670 dump, err := httputil.DumpRequest(req, body)
671 if err == nil {
672 s.Log("\n> Received request (" + req.RemoteAddr + "):\n" +
673 string(dump) +
674 "\n------------------------------\n")
675 }
676 }
677}