mirror of
				https://github.com/opencontainers/runc.git
				synced 2025-10-31 11:06:21 +08:00 
			
		
		
		
	 cb20148703
			
		
	
	cb20148703
	
	
	
		
			
			...so that they can be used for benchmarks, too. Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
		
			
				
	
	
		
			234 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			234 lines
		
	
	
		
			5.8 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.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()
 | |
| }
 |