mirror of
https://github.com/opencontainers/runc.git
synced 2025-10-05 07:27:03 +08:00
242 lines
6.0 KiB
Go
242 lines
6.0 KiB
Go
package integration
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"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.TB) {
|
|
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.TB) 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.TB, 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.TB, 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
|
|
}
|
|
|
|
// runContainerOk is a wrapper for runContainer, simplifying its use for cases
|
|
// when the run is expected to succeed and return exit code of 0.
|
|
func runContainerOk(t testing.TB, config *configs.Config, args ...string) *stdBuffers {
|
|
buffers, exitCode, err := runContainer(t, config, args...)
|
|
|
|
t.Helper()
|
|
if err != nil {
|
|
t.Fatalf("%s: %s", buffers, err)
|
|
}
|
|
if exitCode != 0 {
|
|
t.Fatalf("exit code not 0. code %d stderr %q", exitCode, buffers.Stderr)
|
|
}
|
|
|
|
return buffers
|
|
}
|
|
|
|
func destroyContainer(container *libcontainer.Container) {
|
|
_ = container.Destroy()
|
|
}
|
|
|
|
func needUserNS(t testing.TB) {
|
|
t.Helper()
|
|
if _, err := os.Stat("/proc/self/ns/user"); errors.Is(err, os.ErrNotExist) {
|
|
t.Skip("Test requires userns.")
|
|
}
|
|
}
|