mirror of
https://github.com/opencontainers/runc.git
synced 2026-07-10 21:53:57 +08:00
102b8abd26
The only implementation of these is linuxContainer. It does not make sense to have an interface with a single implementation, and we do not foresee other types of containers being added to runc. Remove BaseContainer and Container interfaces, moving their methods documentation to linuxContainer. Rename linuxContainer to Container. Adopt users from using interface to using struct. Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
218 lines
5.4 KiB
Go
218 lines
5.4 KiB
Go
package integration
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"regexp"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/opencontainers/runc/libcontainer"
|
|
"github.com/opencontainers/runc/libcontainer/configs"
|
|
)
|
|
|
|
var busyboxTar string
|
|
|
|
// init makes sure the container images are downloaded,
|
|
// and initializes busyboxTar. If images can't be downloaded,
|
|
// we are unable to run any tests, so panic.
|
|
func init() {
|
|
// Figure out path to get-images.sh. Note it won't work
|
|
// in case the compiled test binary is moved elsewhere.
|
|
_, ex, _, _ := runtime.Caller(0)
|
|
getImages, err := filepath.Abs(filepath.Join(filepath.Dir(ex), "..", "..", "tests", "integration", "get-images.sh"))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
// Call it to make sure images are downloaded, and to get the paths.
|
|
out, err := exec.Command(getImages).CombinedOutput()
|
|
if err != nil {
|
|
panic(fmt.Errorf("getImages error %w (output: %s)", err, out))
|
|
}
|
|
// Extract the value of BUSYBOX_IMAGE.
|
|
found := regexp.MustCompile(`(?m)^BUSYBOX_IMAGE=(.*)$`).FindSubmatchIndex(out)
|
|
if len(found) < 4 {
|
|
panic(fmt.Errorf("unable to find BUSYBOX_IMAGE=<value> in %q", out))
|
|
}
|
|
busyboxTar = string(out[found[2]:found[3]])
|
|
// Finally, check the file is present
|
|
if _, err := os.Stat(busyboxTar); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func ptrInt(v int) *int {
|
|
return &v
|
|
}
|
|
|
|
func newStdBuffers() *stdBuffers {
|
|
return &stdBuffers{
|
|
Stdin: bytes.NewBuffer(nil),
|
|
Stdout: bytes.NewBuffer(nil),
|
|
Stderr: bytes.NewBuffer(nil),
|
|
}
|
|
}
|
|
|
|
type stdBuffers struct {
|
|
Stdin *bytes.Buffer
|
|
Stdout *bytes.Buffer
|
|
Stderr *bytes.Buffer
|
|
}
|
|
|
|
func (b *stdBuffers) String() string {
|
|
s := []string{}
|
|
if b.Stderr != nil {
|
|
s = append(s, b.Stderr.String())
|
|
}
|
|
if b.Stdout != nil {
|
|
s = append(s, b.Stdout.String())
|
|
}
|
|
return strings.Join(s, "|")
|
|
}
|
|
|
|
// ok fails the test if an err is not nil.
|
|
func ok(t testing.TB, err error) {
|
|
t.Helper()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func waitProcess(p *libcontainer.Process, t *testing.T) {
|
|
t.Helper()
|
|
status, err := p.Wait()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
if !status.Success() {
|
|
t.Fatalf("unexpected status: %v", status)
|
|
}
|
|
}
|
|
|
|
// newRootfs creates a new tmp directory and copies the busybox root
|
|
// filesystem to it.
|
|
func newRootfs(t *testing.T) string {
|
|
t.Helper()
|
|
dir := t.TempDir()
|
|
if err := copyBusybox(dir); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Make sure others can read+exec, so all tests (inside userns too) can
|
|
// read the rootfs.
|
|
if err := traversePath(dir); err != nil {
|
|
t.Fatalf("Error making newRootfs path traversable by others: %v", err)
|
|
}
|
|
|
|
return dir
|
|
}
|
|
|
|
// traversePath gives read+execute permissions to others for all elements in tPath below
|
|
// os.TempDir() and errors out if elements above it don't have read+exec permissions for others.
|
|
// tPath MUST be a descendant of os.TempDir(). The path returned by testing.TempDir() usually is.
|
|
func traversePath(tPath string) error {
|
|
// Check the assumption that the argument is under os.TempDir().
|
|
tempBase := os.TempDir()
|
|
if !strings.HasPrefix(tPath, tempBase) {
|
|
return fmt.Errorf("traversePath: %q is not a descendant of %q", tPath, tempBase)
|
|
}
|
|
|
|
var path string
|
|
for _, p := range strings.SplitAfter(tPath, "/") {
|
|
path = path + p
|
|
stats, err := os.Stat(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
perm := stats.Mode().Perm()
|
|
|
|
if perm&0o5 == 0o5 {
|
|
continue
|
|
}
|
|
|
|
if strings.HasPrefix(tempBase, path) {
|
|
return fmt.Errorf("traversePath: directory %q MUST have read+exec permissions for others", path)
|
|
}
|
|
|
|
if err := os.Chmod(path, perm|0o5); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func remove(dir string) {
|
|
_ = os.RemoveAll(dir)
|
|
}
|
|
|
|
// copyBusybox copies the rootfs for a busybox container created for the test image
|
|
// into the new directory for the specific test
|
|
func copyBusybox(dest string) error {
|
|
out, err := exec.Command("sh", "-c", fmt.Sprintf("tar --exclude './dev/*' -C %q -xf %q", dest, busyboxTar)).CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("untar error %w: %q", err, out)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func newContainer(t *testing.T, config *configs.Config) (*libcontainer.Container, error) {
|
|
name := strings.ReplaceAll(t.Name(), "/", "_") + strconv.FormatInt(-int64(time.Now().Nanosecond()), 35)
|
|
root := t.TempDir()
|
|
|
|
return libcontainer.Create(root, name, config)
|
|
}
|
|
|
|
// runContainer runs the container with the specific config and arguments
|
|
//
|
|
// buffers are returned containing the STDOUT and STDERR output for the run
|
|
// along with the exit code and any go error
|
|
func runContainer(t *testing.T, config *configs.Config, args ...string) (buffers *stdBuffers, exitCode int, err error) {
|
|
container, err := newContainer(t, config)
|
|
if err != nil {
|
|
return nil, -1, err
|
|
}
|
|
defer destroyContainer(container)
|
|
buffers = newStdBuffers()
|
|
process := &libcontainer.Process{
|
|
Cwd: "/",
|
|
Args: args,
|
|
Env: standardEnvironment,
|
|
Stdin: buffers.Stdin,
|
|
Stdout: buffers.Stdout,
|
|
Stderr: buffers.Stderr,
|
|
Init: true,
|
|
}
|
|
|
|
err = container.Run(process)
|
|
if err != nil {
|
|
return buffers, -1, err
|
|
}
|
|
ps, err := process.Wait()
|
|
if err != nil {
|
|
return buffers, -1, err
|
|
}
|
|
status := ps.Sys().(syscall.WaitStatus)
|
|
if status.Exited() {
|
|
exitCode = status.ExitStatus()
|
|
} else if status.Signaled() {
|
|
exitCode = -int(status.Signal())
|
|
} else {
|
|
return buffers, -1, err
|
|
}
|
|
return
|
|
}
|
|
|
|
func destroyContainer(container *libcontainer.Container) {
|
|
_ = container.Destroy()
|
|
}
|