diff options
Diffstat (limited to 'extras/hs-test/infra/container.go')
-rw-r--r-- | extras/hs-test/infra/container.go | 205 |
1 files changed, 165 insertions, 40 deletions
diff --git a/extras/hs-test/infra/container.go b/extras/hs-test/infra/container.go index 3e8ccb4550f..d71761b2920 100644 --- a/extras/hs-test/infra/container.go +++ b/extras/hs-test/infra/container.go @@ -1,13 +1,21 @@ package hst import ( + "bytes" + "context" "fmt" "os" "os/exec" + "slices" + "strconv" "strings" "text/template" "time" + containerTypes "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/image" + "github.com/docker/docker/pkg/stdcopy" + "github.com/docker/go-units" "github.com/edwarnicke/exechelper" . "github.com/onsi/ginkgo/v2" ) @@ -32,12 +40,14 @@ type Container struct { IsOptional bool RunDetached bool Name string + ID string Image string ExtraRunningArgs string Volumes map[string]Volume EnvVars map[string]string VppInstance *VppInstance AllocatedCpus []int + ctx context.Context } func newContainer(suite *HstSuite, yamlInput ContainerConfig) (*Container, error) { @@ -52,6 +62,7 @@ func newContainer(suite *HstSuite, yamlInput ContainerConfig) (*Container, error container.EnvVars = make(map[string]string) container.Name = containerName container.Suite = suite + container.ctx = context.Background() if Image, ok := yamlInput["image"]; ok { container.Image = Image.(string) @@ -133,8 +144,6 @@ func (c *Container) GetContainerWorkDir() (res string) { func (c *Container) getContainerArguments() string { args := "--ulimit nofile=90000:90000 --cap-add=all --privileged --network host" - c.allocateCpus() - args += fmt.Sprintf(" --cpuset-cpus=\"%d-%d\"", c.AllocatedCpus[0], c.AllocatedCpus[len(c.AllocatedCpus)-1]) args += c.getVolumesAsCliOption() args += c.getEnvVarsAsCliOption() if *VppSourceFileDir != "" { @@ -145,22 +154,57 @@ func (c *Container) getContainerArguments() string { return args } -func (c *Container) runWithRetry(cmd string) error { - nTries := 5 - for i := 0; i < nTries; i++ { - err := exechelper.Run(cmd) - if err == nil { - return nil - } - time.Sleep(1 * time.Second) - } - return fmt.Errorf("failed to run container command") +func (c *Container) PullDockerImage(name string, ctx context.Context) { + // "func (*Client) ImagePull" doesn't work, returns "No such image" + c.Suite.Log("Pulling image: " + name) + _, err := exechelper.CombinedOutput("docker pull " + name) + c.Suite.AssertNil(err) } +// Creates a container func (c *Container) Create() error { - cmd := "docker create " + c.getContainerArguments() - c.Suite.Log(cmd) - return exechelper.Run(cmd) + var sliceOfImageNames []string + images, err := c.Suite.Docker.ImageList(c.ctx, image.ListOptions{}) + c.Suite.AssertNil(err) + + for _, image := range images { + sliceOfImageNames = append(sliceOfImageNames, strings.Split(image.RepoTags[0], ":")[0]) + } + if !slices.Contains(sliceOfImageNames, c.Image) { + c.PullDockerImage(c.Image, c.ctx) + } + + c.allocateCpus() + cpuSet := fmt.Sprintf("%d-%d", c.AllocatedCpus[0], c.AllocatedCpus[len(c.AllocatedCpus)-1]) + resp, err := c.Suite.Docker.ContainerCreate( + c.ctx, + &containerTypes.Config{ + Image: c.Image, + Env: c.getEnvVars(), + Cmd: strings.Split(c.ExtraRunningArgs, " "), + }, + &containerTypes.HostConfig{ + Resources: containerTypes.Resources{ + Ulimits: []*units.Ulimit{ + { + Name: "nofile", + Soft: 90000, + Hard: 90000, + }, + }, + CpusetCpus: cpuSet, + }, + CapAdd: []string{"ALL"}, + Privileged: true, + NetworkMode: "host", + Binds: c.getVolumesAsSlice(), + }, + nil, + nil, + c.Name, + ) + c.ID = resp.ID + return err } func (c *Container) allocateCpus() { @@ -169,10 +213,45 @@ func (c *Container) allocateCpus() { c.Suite.Log("Allocated CPUs " + fmt.Sprint(c.AllocatedCpus) + " to container " + c.Name) } +// Starts a container func (c *Container) Start() error { - cmd := "docker start " + c.Name - c.Suite.Log(cmd) - return c.runWithRetry(cmd) + var err error + for nTries := 0; nTries < 5; nTries++ { + err = c.Suite.Docker.ContainerStart(c.ctx, c.ID, containerTypes.StartOptions{}) + if err == nil { + continue + } + c.Suite.Log("Error while starting " + c.Name + ". Retrying...") + time.Sleep(1 * time.Second) + } + + return err +} + +func (c *Container) GetOutput() (string, string) { + // Wait for the container to finish executing + statusCh, errCh := c.Suite.Docker.ContainerWait(c.ctx, c.ID, containerTypes.WaitConditionNotRunning) + select { + case err := <-errCh: + c.Suite.AssertNil(err) + case <-statusCh: + } + + // Get the logs from the container + logOptions := containerTypes.LogsOptions{ShowStdout: true, ShowStderr: true} + logReader, err := c.Suite.Docker.ContainerLogs(c.ctx, c.ID, logOptions) + c.Suite.AssertNil(err) + defer logReader.Close() + + var stdoutBuf, stderrBuf bytes.Buffer + + // Use stdcopy.StdCopy to demultiplex the multiplexed stream + _, err = stdcopy.StdCopy(&stdoutBuf, &stderrBuf, logReader) + c.Suite.AssertNil(err) + + stdout := stdoutBuf.String() + stderr := stderrBuf.String() + return stdout, stderr } func (c *Container) prepareCommand() (string, error) { @@ -180,7 +259,7 @@ func (c *Container) prepareCommand() (string, error) { return "", fmt.Errorf("run container failed: name is blank") } - cmd := "docker run " + cmd := "docker exec " if c.RunDetached { cmd += " -d" } @@ -201,12 +280,10 @@ func (c *Container) CombinedOutput() (string, error) { return string(byteOutput), err } -func (c *Container) Run() error { - cmd, err := c.prepareCommand() - if err != nil { - return err - } - return c.runWithRetry(cmd) +// Creates and starts a container +func (c *Container) Run() { + c.Suite.AssertNil(c.Create()) + c.Suite.AssertNil(c.Start()) } func (c *Container) addVolume(hostDir string, containerDir string, isDefaultWorkDir bool) { @@ -217,6 +294,22 @@ func (c *Container) addVolume(hostDir string, containerDir string, isDefaultWork c.Volumes[hostDir] = volume } +func (c *Container) getVolumesAsSlice() []string { + var volumeSlice []string + + if *VppSourceFileDir != "" { + volumeSlice = append(volumeSlice, fmt.Sprintf("%s:%s", *VppSourceFileDir, *VppSourceFileDir)) + } + + if len(c.Volumes) > 0 { + for _, volume := range c.Volumes { + volumeSlice = append(volumeSlice, fmt.Sprintf("%s:%s", volume.HostDir, volume.ContainerDir)) + } + } + + return volumeSlice +} + func (c *Container) getVolumesAsCliOption() string { cliOption := "" @@ -245,6 +338,18 @@ func (c *Container) getEnvVarsAsCliOption() string { return cliOption } +func (c *Container) getEnvVars() []string { + var envVars []string + if len(c.EnvVars) == 0 { + return envVars + } + + for name, value := range c.EnvVars { + envVars = append(envVars, fmt.Sprintf("%s=%s", name, value)) + } + return envVars +} + func (c *Container) newVppInstance(cpus []int, additionalConfigs ...Stanza) (*VppInstance, error) { vpp := new(VppInstance) vpp.Container = c @@ -317,33 +422,48 @@ func (c *Container) getLogDirPath() string { func (c *Container) saveLogs() { testLogFilePath := c.getLogDirPath() + "container-" + c.Name + ".log" - cmd := exec.Command("docker", "logs", "--details", "-t", c.Name) - c.Suite.Log(cmd) - output, err := cmd.CombinedOutput() + logs, err := c.log(0) if err != nil { c.Suite.Log(err) + return } f, err := os.Create(testLogFilePath) if err != nil { - Fail("file create error: " + fmt.Sprint(err)) + c.Suite.Log(err) + return } - fmt.Fprint(f, string(output)) - f.Close() + defer f.Close() + fmt.Fprint(f, logs) } -// Outputs logs from docker containers. Set 'maxLines' to 0 to output the full log. +// Returns logs from docker containers. Set 'maxLines' to 0 to output the full log. func (c *Container) log(maxLines int) (string, error) { - var cmd string + var logOptions containerTypes.LogsOptions if maxLines == 0 { - cmd = "docker logs " + c.Name + logOptions = containerTypes.LogsOptions{ShowStdout: true, ShowStderr: true, Details: true} } else { - cmd = fmt.Sprintf("docker logs --tail %d %s", maxLines, c.Name) + logOptions = containerTypes.LogsOptions{ShowStdout: true, ShowStderr: true, Details: true, Tail: strconv.Itoa(maxLines)} } - c.Suite.Log(cmd) - o, err := exechelper.CombinedOutput(cmd) - return string(o), err + out, err := c.Suite.Docker.ContainerLogs(c.ctx, c.ID, logOptions) + if err != nil { + c.Suite.Log(err) + return "", err + } + defer out.Close() + + var stdoutBuf, stderrBuf bytes.Buffer + + _, err = stdcopy.StdCopy(&stdoutBuf, &stderrBuf, out) + if err != nil { + c.Suite.Log(err) + } + + stdout := stdoutBuf.String() + stderr := stderrBuf.String() + + return stdout + " " + stderr, err } func (c *Container) stop() error { @@ -353,8 +473,13 @@ func (c *Container) stop() error { } c.VppInstance = nil c.saveLogs() - c.Suite.Log("docker stop " + c.Name + " -t 0") - return exechelper.Run("docker stop " + c.Name + " -t 0") + + c.Suite.Log("Stopping container " + c.Name) + timeout := 0 + if err := c.Suite.Docker.ContainerStop(c.ctx, c.ID, containerTypes.StopOptions{Timeout: &timeout}); err != nil { + return err + } + return nil } func (c *Container) CreateConfig(targetConfigName string, templateName string, values any) { |