mirror of
https://github.com/opencontainers/runc.git
synced 2025-10-05 07:27:03 +08:00

Since LinuxFactory has become the means to specify containers state top directory (aka --root), and is only used by two methods (Create and Load), it is easier to pass root to them directly. Modify all the users and the docs accordingly. While at it, fix Create and Load docs (those that were originally moved from the Factory interface docs). 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()
|
|
}
|