blob: 10b5933b17f5348f6795726c153b9268989ef1d4 [file] [log] [blame]
Maros Ondrejicka11a03e92022-12-01 09:56:37 +01001package main
2
3import (
4 "fmt"
Maros Ondrejickadb823ed2022-12-14 16:30:04 +01005 "os"
Maros Ondrejickaffa3f602023-01-26 10:07:29 +01006 "os/exec"
Maros Ondrejickadb823ed2022-12-14 16:30:04 +01007 "strings"
Maros Ondrejicka85396a52023-02-28 12:49:43 +01008 "text/template"
Filip Tehlar4fa0ba62023-12-06 11:35:11 +01009 "time"
Maros Ondrejicka11a03e92022-12-01 09:56:37 +010010
11 "github.com/edwarnicke/exechelper"
12)
13
Maros Ondrejickaa2d52622023-02-24 11:26:39 +010014const (
Filip Tehlara1bd50c2024-01-24 11:59:44 +010015 logDir string = "/tmp/hs-test/"
16 volumeDir string = "/volumes"
Maros Ondrejickaa2d52622023-02-24 11:26:39 +010017)
18
Maros Ondrejicka7550dd22023-02-07 20:40:27 +010019var (
20 workDir, _ = os.Getwd()
21)
22
Maros Ondrejicka0e79abb2022-12-06 19:46:24 +010023type Volume struct {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010024 hostDir string
25 containerDir string
26 isDefaultWorkDir bool
Maros Ondrejicka0e79abb2022-12-06 19:46:24 +010027}
28
29type Container struct {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010030 suite *HstSuite
Filip Tehlar3f951432023-01-13 21:33:43 +010031 isOptional bool
Filip Tehlarb41b0af2023-03-20 12:39:20 +010032 runDetached bool
Filip Tehlar3f951432023-01-13 21:33:43 +010033 name string
34 image string
Filip Tehlar3f951432023-01-13 21:33:43 +010035 extraRunningArgs string
36 volumes map[string]Volume
37 envVars map[string]string
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010038 vppInstance *VppInstance
Maros Ondrejickadb823ed2022-12-14 16:30:04 +010039}
40
Filip Tehlara1bd50c2024-01-24 11:59:44 +010041func newContainer(suite *HstSuite, yamlInput ContainerConfig) (*Container, error) {
Maros Ondrejickadb823ed2022-12-14 16:30:04 +010042 containerName := yamlInput["name"].(string)
43 if len(containerName) == 0 {
44 err := fmt.Errorf("container name must not be blank")
45 return nil, err
46 }
47
48 var container = new(Container)
49 container.volumes = make(map[string]Volume)
50 container.envVars = make(map[string]string)
51 container.name = containerName
Filip Tehlara1bd50c2024-01-24 11:59:44 +010052 container.suite = suite
Maros Ondrejickadb823ed2022-12-14 16:30:04 +010053
54 if image, ok := yamlInput["image"]; ok {
55 container.image = image.(string)
56 } else {
57 container.image = "hs-test/vpp"
58 }
59
Filip Tehlar3f951432023-01-13 21:33:43 +010060 if args, ok := yamlInput["extra-args"]; ok {
61 container.extraRunningArgs = args.(string)
62 } else {
63 container.extraRunningArgs = ""
64 }
65
Maros Ondrejickadb823ed2022-12-14 16:30:04 +010066 if isOptional, ok := yamlInput["is-optional"]; ok {
67 container.isOptional = isOptional.(bool)
68 } else {
69 container.isOptional = false
70 }
71
Filip Tehlarb41b0af2023-03-20 12:39:20 +010072 if runDetached, ok := yamlInput["run-detached"]; ok {
73 container.runDetached = runDetached.(bool)
74 } else {
75 container.runDetached = true
76 }
77
Maros Ondrejickadb823ed2022-12-14 16:30:04 +010078 if _, ok := yamlInput["volumes"]; ok {
Filip Tehlara1bd50c2024-01-24 11:59:44 +010079 workingVolumeDir := logDir + container.suite.T().Name() + volumeDir
80 workDirReplacer := strings.NewReplacer("$HST_DIR", workDir)
81 volDirReplacer := strings.NewReplacer("$HST_VOLUME_DIR", workingVolumeDir)
Maros Ondrejickadb823ed2022-12-14 16:30:04 +010082 for _, volu := range yamlInput["volumes"].([]interface{}) {
83 volumeMap := volu.(ContainerConfig)
Filip Tehlara1bd50c2024-01-24 11:59:44 +010084 hostDir := workDirReplacer.Replace(volumeMap["host-dir"].(string))
85 hostDir = volDirReplacer.Replace(hostDir)
Maros Ondrejickadb823ed2022-12-14 16:30:04 +010086 containerDir := volumeMap["container-dir"].(string)
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010087 isDefaultWorkDir := false
Maros Ondrejickadb823ed2022-12-14 16:30:04 +010088
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010089 if isDefault, ok := volumeMap["is-default-work-dir"]; ok {
90 isDefaultWorkDir = isDefault.(bool)
Maros Ondrejickadb823ed2022-12-14 16:30:04 +010091 }
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010092 container.addVolume(hostDir, containerDir, isDefaultWorkDir)
Maros Ondrejickadb823ed2022-12-14 16:30:04 +010093 }
94 }
95
96 if _, ok := yamlInput["vars"]; ok {
97 for _, envVar := range yamlInput["vars"].([]interface{}) {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010098 envVarMap := envVar.(ContainerConfig)
99 name := envVarMap["name"].(string)
100 value := envVarMap["value"].(string)
101 container.addEnvVar(name, value)
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100102 }
103 }
104 return container, nil
Maros Ondrejicka11a03e92022-12-01 09:56:37 +0100105}
106
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100107func (c *Container) getWorkDirVolume() (res Volume, exists bool) {
108 for _, v := range c.volumes {
109 if v.isDefaultWorkDir {
110 res = v
111 exists = true
112 return
113 }
114 }
115 return
116}
117
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100118func (c *Container) getHostWorkDir() (res string) {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100119 if v, ok := c.getWorkDirVolume(); ok {
120 res = v.hostDir
121 }
122 return
123}
124
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100125func (c *Container) getContainerWorkDir() (res string) {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100126 if v, ok := c.getWorkDirVolume(); ok {
127 res = v.containerDir
128 }
129 return
130}
131
Maros Ondrejickac2f76f42023-02-27 13:22:45 +0100132func (c *Container) getContainerArguments() string {
Filip Tehlarb41b0af2023-03-20 12:39:20 +0100133 args := "--ulimit nofile=90000:90000 --cap-add=all --privileged --network host --rm"
Maros Ondrejickac2f76f42023-02-27 13:22:45 +0100134 args += c.getVolumesAsCliOption()
135 args += c.getEnvVarsAsCliOption()
Filip Tehlar109f3ce2023-09-05 15:36:28 +0200136 if *vppSourceFileDir != "" {
137 args += fmt.Sprintf(" -v %s:%s", *vppSourceFileDir, *vppSourceFileDir)
138 }
Maros Ondrejickac2f76f42023-02-27 13:22:45 +0100139 args += " --name " + c.name + " " + c.image
Filip Tehlar9abba112023-03-07 10:13:19 +0100140 args += " " + c.extraRunningArgs
Maros Ondrejickac2f76f42023-02-27 13:22:45 +0100141 return args
142}
143
Filip Tehlar4fa0ba62023-12-06 11:35:11 +0100144func (c *Container) runWithRetry(cmd string) error {
145 nTries := 5
146 for i := 0; i < nTries; i++ {
147 err := exechelper.Run(cmd)
148 if err == nil {
149 return nil
150 }
151 time.Sleep(1 * time.Second)
152 }
153 return fmt.Errorf("failed to run container command")
154}
155
Filip Tehlar9abba112023-03-07 10:13:19 +0100156func (c *Container) create() error {
Maros Ondrejickac2f76f42023-02-27 13:22:45 +0100157 cmd := "docker create " + c.getContainerArguments()
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100158 c.suite.log(cmd)
Filip Tehlar9abba112023-03-07 10:13:19 +0100159 return exechelper.Run(cmd)
Maros Ondrejickac2f76f42023-02-27 13:22:45 +0100160}
161
Filip Tehlar9abba112023-03-07 10:13:19 +0100162func (c *Container) start() error {
Maros Ondrejickac2f76f42023-02-27 13:22:45 +0100163 cmd := "docker start " + c.name
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100164 c.suite.log(cmd)
Filip Tehlar4fa0ba62023-12-06 11:35:11 +0100165 return c.runWithRetry(cmd)
Maros Ondrejicka87531802022-12-19 20:35:27 +0100166}
167
Filip Tehlarb41b0af2023-03-20 12:39:20 +0100168func (c *Container) prepareCommand() (string, error) {
Maros Ondrejicka87531802022-12-19 20:35:27 +0100169 if c.name == "" {
Filip Tehlarb41b0af2023-03-20 12:39:20 +0100170 return "", fmt.Errorf("run container failed: name is blank")
Maros Ondrejicka87531802022-12-19 20:35:27 +0100171 }
172
Filip Tehlarb41b0af2023-03-20 12:39:20 +0100173 cmd := "docker run "
174 if c.runDetached {
175 cmd += " -d"
176 }
177 cmd += " " + c.getContainerArguments()
178
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100179 c.suite.log(cmd)
Filip Tehlarb41b0af2023-03-20 12:39:20 +0100180 return cmd, nil
181}
182
183func (c *Container) combinedOutput() (string, error) {
184 cmd, err := c.prepareCommand()
Maros Ondrejicka11a03e92022-12-01 09:56:37 +0100185 if err != nil {
Filip Tehlarb41b0af2023-03-20 12:39:20 +0100186 return "", err
Maros Ondrejicka11a03e92022-12-01 09:56:37 +0100187 }
188
Filip Tehlarb41b0af2023-03-20 12:39:20 +0100189 byteOutput, err := exechelper.CombinedOutput(cmd)
190 return string(byteOutput), err
191}
192
193func (c *Container) run() error {
194 cmd, err := c.prepareCommand()
195 if err != nil {
196 return err
197 }
Filip Tehlar4fa0ba62023-12-06 11:35:11 +0100198 return c.runWithRetry(cmd)
Maros Ondrejicka11a03e92022-12-01 09:56:37 +0100199}
200
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100201func (c *Container) addVolume(hostDir string, containerDir string, isDefaultWorkDir bool) {
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100202 var volume Volume
203 volume.hostDir = hostDir
204 volume.containerDir = containerDir
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100205 volume.isDefaultWorkDir = isDefaultWorkDir
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100206 c.volumes[hostDir] = volume
Maros Ondrejicka0e79abb2022-12-06 19:46:24 +0100207}
208
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100209func (c *Container) getVolumesAsCliOption() string {
210 cliOption := ""
Maros Ondrejicka0e79abb2022-12-06 19:46:24 +0100211
212 if len(c.volumes) > 0 {
213 for _, volume := range c.volumes {
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100214 cliOption += fmt.Sprintf(" -v %s:%s", volume.hostDir, volume.containerDir)
Maros Ondrejicka0e79abb2022-12-06 19:46:24 +0100215 }
216 }
217
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100218 return cliOption
219}
220
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100221func (c *Container) addEnvVar(name string, value string) {
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100222 c.envVars[name] = value
223}
224
225func (c *Container) getEnvVarsAsCliOption() string {
226 cliOption := ""
227 if len(c.envVars) == 0 {
228 return cliOption
229 }
230
231 for name, value := range c.envVars {
232 cliOption += fmt.Sprintf(" -e %s=%s", name, value)
233 }
234 return cliOption
235}
236
Filip Tehlar608d0062023-04-28 10:29:47 +0200237func (c *Container) newVppInstance(cpus []int, additionalConfigs ...Stanza) (*VppInstance, error) {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100238 vpp := new(VppInstance)
239 vpp.container = c
Filip Tehlar608d0062023-04-28 10:29:47 +0200240 vpp.cpus = cpus
241 vpp.additionalConfig = append(vpp.additionalConfig, additionalConfigs...)
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100242 c.vppInstance = vpp
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100243 return vpp, nil
244}
245
246func (c *Container) copy(sourceFileName string, targetFileName string) error {
247 cmd := exec.Command("docker", "cp", sourceFileName, c.name+":"+targetFileName)
248 return cmd.Run()
249}
250
251func (c *Container) createFile(destFileName string, content string) error {
252 f, err := os.CreateTemp("/tmp", "hst-config")
253 if err != nil {
254 return err
255 }
256 defer os.Remove(f.Name())
257
258 if _, err := f.Write([]byte(content)); err != nil {
259 return err
260 }
261 if err := f.Close(); err != nil {
262 return err
263 }
264 c.copy(f.Name(), destFileName)
265 return nil
266}
267
268/*
269 * Executes in detached mode so that the started application can continue to run
270 * without blocking execution of test
271 */
Maros Ondrejicka2908f8c2023-02-02 08:58:04 +0100272func (c *Container) execServer(command string, arguments ...any) {
273 serverCommand := fmt.Sprintf(command, arguments...)
274 containerExecCommand := "docker exec -d" + c.getEnvVarsAsCliOption() +
275 " " + c.name + " " + serverCommand
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100276 c.suite.T().Helper()
277 c.suite.log(containerExecCommand)
278 c.suite.assertNil(exechelper.Run(containerExecCommand))
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100279}
280
Maros Ondrejicka2908f8c2023-02-02 08:58:04 +0100281func (c *Container) exec(command string, arguments ...any) string {
282 cliCommand := fmt.Sprintf(command, arguments...)
283 containerExecCommand := "docker exec" + c.getEnvVarsAsCliOption() +
284 " " + c.name + " " + cliCommand
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100285 c.suite.T().Helper()
286 c.suite.log(containerExecCommand)
Maros Ondrejicka2908f8c2023-02-02 08:58:04 +0100287 byteOutput, err := exechelper.CombinedOutput(containerExecCommand)
adrianvillin7c675472024-02-12 02:44:53 -0500288 c.suite.assertNil(err, err)
Maros Ondrejicka2908f8c2023-02-02 08:58:04 +0100289 return string(byteOutput)
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100290}
291
Maros Ondrejickaa2d52622023-02-24 11:26:39 +0100292func (c *Container) getLogDirPath() string {
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100293 testId := c.suite.getTestId()
294 testName := c.suite.T().Name()
Maros Ondrejickaa2d52622023-02-24 11:26:39 +0100295 logDirPath := logDir + testName + "/" + testId + "/"
296
297 cmd := exec.Command("mkdir", "-p", logDirPath)
298 if err := cmd.Run(); err != nil {
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100299 c.suite.T().Fatalf("mkdir error: %v", err)
Maros Ondrejickaa2d52622023-02-24 11:26:39 +0100300 }
301
302 return logDirPath
303}
304
305func (c *Container) saveLogs() {
306 cmd := exec.Command("docker", "inspect", "--format='{{.State.Status}}'", c.name)
307 if output, _ := cmd.CombinedOutput(); !strings.Contains(string(output), "running") {
308 return
309 }
310
311 testLogFilePath := c.getLogDirPath() + "container-" + c.name + ".log"
312
313 cmd = exec.Command("docker", "logs", "--details", "-t", c.name)
314 output, err := cmd.CombinedOutput()
315 if err != nil {
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100316 c.suite.T().Fatalf("fetching logs error: %v", err)
Maros Ondrejickaa2d52622023-02-24 11:26:39 +0100317 }
318
319 f, err := os.Create(testLogFilePath)
320 if err != nil {
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100321 c.suite.T().Fatalf("file create error: %v", err)
Maros Ondrejickaa2d52622023-02-24 11:26:39 +0100322 }
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100323 fmt.Fprint(f, string(output))
Maros Ondrejickaa2d52622023-02-24 11:26:39 +0100324 f.Close()
325}
326
adrianvillin7c675472024-02-12 02:44:53 -0500327// Outputs logs from docker containers. Set 'maxLines' to 0 to output the full log.
328func (c *Container) log(maxLines int) (string, error) {
329 var cmd string
330 if maxLines == 0 {
331 cmd = "docker logs " + c.name
332 } else {
333 cmd = fmt.Sprintf("docker logs --tail %d %s", maxLines, c.name)
334 }
335
Maros Ondrejickae7625d02023-02-28 16:55:01 +0100336 c.suite.log(cmd)
Maros Ondrejickac2f76f42023-02-27 13:22:45 +0100337 o, err := exechelper.CombinedOutput(cmd)
adrianvillin7c675472024-02-12 02:44:53 -0500338 return string(o), err
Maros Ondrejickac2f76f42023-02-27 13:22:45 +0100339}
340
Maros Ondrejicka0e79abb2022-12-06 19:46:24 +0100341func (c *Container) stop() error {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100342 if c.vppInstance != nil && c.vppInstance.apiChannel != nil {
Maros Ondrejickaa2d52622023-02-24 11:26:39 +0100343 c.vppInstance.saveLogs()
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100344 c.vppInstance.disconnect()
345 }
346 c.vppInstance = nil
Maros Ondrejickaa2d52622023-02-24 11:26:39 +0100347 c.saveLogs()
Florin Corasf4fe0162023-01-17 13:02:51 -0800348 return exechelper.Run("docker stop " + c.name + " -t 0")
Maros Ondrejicka0e79abb2022-12-06 19:46:24 +0100349}
Maros Ondrejicka85396a52023-02-28 12:49:43 +0100350
351func (c *Container) createConfig(targetConfigName string, templateName string, values any) {
352 template := template.Must(template.ParseFiles(templateName))
353
Filip Tehlara1bd50c2024-01-24 11:59:44 +0100354 f, err := os.CreateTemp(logDir, "hst-config")
adrianvillin7c675472024-02-12 02:44:53 -0500355 c.suite.assertNil(err, err)
Maros Ondrejicka85396a52023-02-28 12:49:43 +0100356 defer os.Remove(f.Name())
357
358 err = template.Execute(f, values)
adrianvillin7c675472024-02-12 02:44:53 -0500359 c.suite.assertNil(err, err)
Maros Ondrejicka85396a52023-02-28 12:49:43 +0100360
361 err = f.Close()
adrianvillin7c675472024-02-12 02:44:53 -0500362 c.suite.assertNil(err, err)
Maros Ondrejicka85396a52023-02-28 12:49:43 +0100363
364 c.copy(f.Name(), targetConfigName)
365}
Maros Ondrejickaf4ddf162023-03-08 16:01:43 +0100366
367func init() {
368 cmd := exec.Command("mkdir", "-p", logDir)
369 if err := cmd.Run(); err != nil {
370 panic(err)
371 }
372}