blob: cc2d441f7ec89411837009dc9376317e827375ce [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 Ondrejicka11a03e92022-12-01 09:56:37 +01008
9 "github.com/edwarnicke/exechelper"
10)
11
Maros Ondrejicka0e79abb2022-12-06 19:46:24 +010012type Volume struct {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010013 hostDir string
14 containerDir string
15 isDefaultWorkDir bool
Maros Ondrejicka0e79abb2022-12-06 19:46:24 +010016}
17
18type Container struct {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010019 suite *HstSuite
Filip Tehlar3f951432023-01-13 21:33:43 +010020 isOptional bool
21 name string
22 image string
Filip Tehlar3f951432023-01-13 21:33:43 +010023 extraRunningArgs string
24 volumes map[string]Volume
25 envVars map[string]string
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010026 vppInstance *VppInstance
Maros Ondrejickadb823ed2022-12-14 16:30:04 +010027}
28
29func NewContainer(yamlInput ContainerConfig) (*Container, error) {
30 containerName := yamlInput["name"].(string)
31 if len(containerName) == 0 {
32 err := fmt.Errorf("container name must not be blank")
33 return nil, err
34 }
35
36 var container = new(Container)
37 container.volumes = make(map[string]Volume)
38 container.envVars = make(map[string]string)
39 container.name = containerName
40
41 if image, ok := yamlInput["image"]; ok {
42 container.image = image.(string)
43 } else {
44 container.image = "hs-test/vpp"
45 }
46
Filip Tehlar3f951432023-01-13 21:33:43 +010047 if args, ok := yamlInput["extra-args"]; ok {
48 container.extraRunningArgs = args.(string)
49 } else {
50 container.extraRunningArgs = ""
51 }
52
Maros Ondrejickadb823ed2022-12-14 16:30:04 +010053 if isOptional, ok := yamlInput["is-optional"]; ok {
54 container.isOptional = isOptional.(bool)
55 } else {
56 container.isOptional = false
57 }
58
59 if _, ok := yamlInput["volumes"]; ok {
Filip Tehlarf3ee2b62023-01-09 12:07:09 +010060 r := strings.NewReplacer("$HST_DIR", workDir)
Maros Ondrejickadb823ed2022-12-14 16:30:04 +010061 for _, volu := range yamlInput["volumes"].([]interface{}) {
62 volumeMap := volu.(ContainerConfig)
63 hostDir := r.Replace(volumeMap["host-dir"].(string))
64 containerDir := volumeMap["container-dir"].(string)
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010065 isDefaultWorkDir := false
Maros Ondrejickadb823ed2022-12-14 16:30:04 +010066
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010067 if isDefault, ok := volumeMap["is-default-work-dir"]; ok {
68 isDefaultWorkDir = isDefault.(bool)
Maros Ondrejickadb823ed2022-12-14 16:30:04 +010069 }
70
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010071 container.addVolume(hostDir, containerDir, isDefaultWorkDir)
72
Maros Ondrejickadb823ed2022-12-14 16:30:04 +010073 }
74 }
75
76 if _, ok := yamlInput["vars"]; ok {
77 for _, envVar := range yamlInput["vars"].([]interface{}) {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010078 envVarMap := envVar.(ContainerConfig)
79 name := envVarMap["name"].(string)
80 value := envVarMap["value"].(string)
81 container.addEnvVar(name, value)
Maros Ondrejickadb823ed2022-12-14 16:30:04 +010082 }
83 }
84 return container, nil
Maros Ondrejicka11a03e92022-12-01 09:56:37 +010085}
86
Maros Ondrejicka2908f8c2023-02-02 08:58:04 +010087func (c *Container) Suite() *HstSuite {
88 return c.suite
89}
90
Maros Ondrejickaffa3f602023-01-26 10:07:29 +010091func (c *Container) getWorkDirVolume() (res Volume, exists bool) {
92 for _, v := range c.volumes {
93 if v.isDefaultWorkDir {
94 res = v
95 exists = true
96 return
97 }
98 }
99 return
100}
101
102func (c *Container) GetHostWorkDir() (res string) {
103 if v, ok := c.getWorkDirVolume(); ok {
104 res = v.hostDir
105 }
106 return
107}
108
109func (c *Container) GetContainerWorkDir() (res string) {
110 if v, ok := c.getWorkDirVolume(); ok {
111 res = v.containerDir
112 }
113 return
114}
115
Maros Ondrejicka87531802022-12-19 20:35:27 +0100116func (c *Container) getRunCommand() string {
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100117 syncPath := fmt.Sprintf(" -v %s:/tmp/sync", c.getSyncPath())
118 cmd := "docker run --cap-add=all -d --privileged --network host --rm"
Maros Ondrejicka11a03e92022-12-01 09:56:37 +0100119 cmd += syncPath
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100120 cmd += c.getVolumesAsCliOption()
121 cmd += c.getEnvVarsAsCliOption()
Filip Tehlar3f951432023-01-13 21:33:43 +0100122 cmd += " --name " + c.name + " " + c.image + " " + c.extraRunningArgs
Maros Ondrejicka87531802022-12-19 20:35:27 +0100123 return cmd
124}
125
126func (c *Container) run() error {
127 if c.name == "" {
128 return fmt.Errorf("run container failed: name is blank")
129 }
130
131 exechelper.Run(fmt.Sprintf("mkdir -p /tmp/%s/sync", c.name))
132 cmd := c.getRunCommand()
Maros Ondrejicka11a03e92022-12-01 09:56:37 +0100133 err := exechelper.Run(cmd)
134 if err != nil {
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100135 return fmt.Errorf("container run failed: %s", err)
Maros Ondrejicka11a03e92022-12-01 09:56:37 +0100136 }
137
138 return nil
139}
140
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100141func (c *Container) addVolume(hostDir string, containerDir string, isDefaultWorkDir bool) {
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100142 var volume Volume
143 volume.hostDir = hostDir
144 volume.containerDir = containerDir
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100145 volume.isDefaultWorkDir = isDefaultWorkDir
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100146 c.volumes[hostDir] = volume
Maros Ondrejicka0e79abb2022-12-06 19:46:24 +0100147}
148
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100149func (c *Container) getVolumeByHostDir(hostDir string) Volume {
150 return c.volumes[hostDir]
151}
152
153func (c *Container) getVolumesAsCliOption() string {
154 cliOption := ""
Maros Ondrejicka0e79abb2022-12-06 19:46:24 +0100155
156 if len(c.volumes) > 0 {
157 for _, volume := range c.volumes {
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100158 cliOption += fmt.Sprintf(" -v %s:%s", volume.hostDir, volume.containerDir)
Maros Ondrejicka0e79abb2022-12-06 19:46:24 +0100159 }
160 }
161
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100162 return cliOption
163}
164
165func (c *Container) getWorkDirAsCliOption() string {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100166 if _, ok := c.getWorkDirVolume(); ok {
167 return fmt.Sprintf(" --workdir=\"%s\"", c.GetContainerWorkDir())
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100168 }
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100169 return ""
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100170}
171
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100172func (c *Container) addEnvVar(name string, value string) {
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100173 c.envVars[name] = value
174}
175
176func (c *Container) getEnvVarsAsCliOption() string {
177 cliOption := ""
178 if len(c.envVars) == 0 {
179 return cliOption
180 }
181
182 for name, value := range c.envVars {
183 cliOption += fmt.Sprintf(" -e %s=%s", name, value)
184 }
185 return cliOption
186}
187
188func (c *Container) getSyncPath() string {
189 return fmt.Sprintf("/tmp/%s/sync", c.name)
190}
191
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100192func (c *Container) newVppInstance(additionalConfig ...Stanza) (*VppInstance, error) {
193 vppConfig := new(VppConfig)
194 vppConfig.CliSocketFilePath = defaultCliSocketFilePath
195 if len(additionalConfig) > 0 {
196 vppConfig.additionalConfig = additionalConfig[0]
197 }
198
199 vpp := new(VppInstance)
200 vpp.container = c
201 vpp.config = vppConfig
202
203 c.vppInstance = vpp
204
205 return vpp, nil
206}
207
208func (c *Container) copy(sourceFileName string, targetFileName string) error {
209 cmd := exec.Command("docker", "cp", sourceFileName, c.name+":"+targetFileName)
210 return cmd.Run()
211}
212
213func (c *Container) createFile(destFileName string, content string) error {
214 f, err := os.CreateTemp("/tmp", "hst-config")
215 if err != nil {
216 return err
217 }
218 defer os.Remove(f.Name())
219
220 if _, err := f.Write([]byte(content)); err != nil {
221 return err
222 }
223 if err := f.Close(); err != nil {
224 return err
225 }
226 c.copy(f.Name(), destFileName)
227 return nil
228}
229
230/*
231 * Executes in detached mode so that the started application can continue to run
232 * without blocking execution of test
233 */
Maros Ondrejicka2908f8c2023-02-02 08:58:04 +0100234func (c *Container) execServer(command string, arguments ...any) {
235 serverCommand := fmt.Sprintf(command, arguments...)
236 containerExecCommand := "docker exec -d" + c.getEnvVarsAsCliOption() +
237 " " + c.name + " " + serverCommand
238 c.Suite().log(containerExecCommand)
239 c.Suite().assertNil(exechelper.Run(containerExecCommand))
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100240}
241
Maros Ondrejicka2908f8c2023-02-02 08:58:04 +0100242func (c *Container) exec(command string, arguments ...any) string {
243 cliCommand := fmt.Sprintf(command, arguments...)
244 containerExecCommand := "docker exec" + c.getEnvVarsAsCliOption() +
245 " " + c.name + " " + cliCommand
246 c.Suite().log(containerExecCommand)
247 byteOutput, err := exechelper.CombinedOutput(containerExecCommand)
248 c.Suite().assertNil(err)
249 return string(byteOutput)
Maros Ondrejickadb823ed2022-12-14 16:30:04 +0100250}
251
252func (c *Container) execAction(args string) (string, error) {
253 syncFile := c.getSyncPath() + "/rc"
254 os.Remove(syncFile)
255
256 workDir := c.getWorkDirAsCliOption()
257 cmd := fmt.Sprintf("docker exec -d %s %s hs-test %s",
258 workDir,
259 c.name,
260 args)
261 err := exechelper.Run(cmd)
262 if err != nil {
263 return "", err
264 }
265 res, err := waitForSyncFile(syncFile)
266 if err != nil {
267 return "", fmt.Errorf("failed to read sync file while executing 'hs-test %s': %v", args, err)
268 }
269 o := res.StdOutput + res.ErrOutput
270 if res.Code != 0 {
271 return o, fmt.Errorf("cmd resulted in non-zero value %d: %s", res.Code, res.Desc)
272 }
273 return o, err
Maros Ondrejicka0e79abb2022-12-06 19:46:24 +0100274}
275
276func (c *Container) stop() error {
Maros Ondrejickaffa3f602023-01-26 10:07:29 +0100277 if c.vppInstance != nil && c.vppInstance.apiChannel != nil {
278 c.vppInstance.disconnect()
279 }
280 c.vppInstance = nil
Florin Corasf4fe0162023-01-17 13:02:51 -0800281 return exechelper.Run("docker stop " + c.name + " -t 0")
Maros Ondrejicka0e79abb2022-12-06 19:46:24 +0100282}