...

Source file src/github.com/opencontainers/runc/libcontainer/integration/utils_test.go

Documentation: github.com/opencontainers/runc/libcontainer/integration

     1  package integration
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"os"
     7  	"os/exec"
     8  	"path/filepath"
     9  	"regexp"
    10  	"runtime"
    11  	"strconv"
    12  	"strings"
    13  	"syscall"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/opencontainers/runc/libcontainer"
    18  	"github.com/opencontainers/runc/libcontainer/configs"
    19  )
    20  
    21  var busyboxTar string
    22  
    23  // init makes sure the container images are downloaded,
    24  // and initializes busyboxTar. If images can't be downloaded,
    25  // we are unable to run any tests, so panic.
    26  func init() {
    27  	// Figure out path to get-images.sh. Note it won't work
    28  	// in case the compiled test binary is moved elsewhere.
    29  	_, ex, _, _ := runtime.Caller(0)
    30  	getImages, err := filepath.Abs(filepath.Join(filepath.Dir(ex), "..", "..", "tests", "integration", "get-images.sh"))
    31  	if err != nil {
    32  		panic(err)
    33  	}
    34  	// Call it to make sure images are downloaded, and to get the paths.
    35  	out, err := exec.Command(getImages).CombinedOutput()
    36  	if err != nil {
    37  		panic(fmt.Errorf("getImages error %w (output: %s)", err, out))
    38  	}
    39  	// Extract the value of BUSYBOX_IMAGE.
    40  	found := regexp.MustCompile(`(?m)^BUSYBOX_IMAGE=(.*)$`).FindSubmatchIndex(out)
    41  	if len(found) < 4 {
    42  		panic(fmt.Errorf("unable to find BUSYBOX_IMAGE=<value> in %q", out))
    43  	}
    44  	busyboxTar = string(out[found[2]:found[3]])
    45  	// Finally, check the file is present
    46  	if _, err := os.Stat(busyboxTar); err != nil {
    47  		panic(err)
    48  	}
    49  }
    50  
    51  func ptrInt(v int) *int {
    52  	return &v
    53  }
    54  
    55  func newStdBuffers() *stdBuffers {
    56  	return &stdBuffers{
    57  		Stdin:  bytes.NewBuffer(nil),
    58  		Stdout: bytes.NewBuffer(nil),
    59  		Stderr: bytes.NewBuffer(nil),
    60  	}
    61  }
    62  
    63  type stdBuffers struct {
    64  	Stdin  *bytes.Buffer
    65  	Stdout *bytes.Buffer
    66  	Stderr *bytes.Buffer
    67  }
    68  
    69  func (b *stdBuffers) String() string {
    70  	s := []string{}
    71  	if b.Stderr != nil {
    72  		s = append(s, b.Stderr.String())
    73  	}
    74  	if b.Stdout != nil {
    75  		s = append(s, b.Stdout.String())
    76  	}
    77  	return strings.Join(s, "|")
    78  }
    79  
    80  // ok fails the test if an err is not nil.
    81  func ok(t testing.TB, err error) {
    82  	t.Helper()
    83  	if err != nil {
    84  		t.Fatalf("unexpected error: %v", err)
    85  	}
    86  }
    87  
    88  func waitProcess(p *libcontainer.Process, t *testing.T) {
    89  	t.Helper()
    90  	status, err := p.Wait()
    91  	if err != nil {
    92  		t.Fatalf("unexpected error: %v", err)
    93  	}
    94  
    95  	if !status.Success() {
    96  		t.Fatalf("unexpected status: %v", status)
    97  	}
    98  }
    99  
   100  // newRootfs creates a new tmp directory and copies the busybox root
   101  // filesystem to it.
   102  func newRootfs(t *testing.T) string {
   103  	t.Helper()
   104  	dir := t.TempDir()
   105  	if err := copyBusybox(dir); err != nil {
   106  		t.Fatal(err)
   107  	}
   108  
   109  	// Make sure others can read+exec, so all tests (inside userns too) can
   110  	// read the rootfs.
   111  	if err := traversePath(dir); err != nil {
   112  		t.Fatalf("Error making newRootfs path traversable by others: %v", err)
   113  	}
   114  
   115  	return dir
   116  }
   117  
   118  // traversePath gives read+execute permissions to others for all elements in tPath below
   119  // os.TempDir() and errors out if elements above it don't have read+exec permissions for others.
   120  // tPath MUST be a descendant of os.TempDir(). The path returned by testing.TempDir() usually is.
   121  func traversePath(tPath string) error {
   122  	// Check the assumption that the argument is under os.TempDir().
   123  	tempBase := os.TempDir()
   124  	if !strings.HasPrefix(tPath, tempBase) {
   125  		return fmt.Errorf("traversePath: %q is not a descendant of %q", tPath, tempBase)
   126  	}
   127  
   128  	var path string
   129  	for _, p := range strings.SplitAfter(tPath, "/") {
   130  		path = path + p
   131  		stats, err := os.Stat(path)
   132  		if err != nil {
   133  			return err
   134  		}
   135  
   136  		perm := stats.Mode().Perm()
   137  
   138  		if perm&0o5 == 0o5 {
   139  			continue
   140  		}
   141  
   142  		if strings.HasPrefix(tempBase, path) {
   143  			return fmt.Errorf("traversePath: directory %q MUST have read+exec permissions for others", path)
   144  		}
   145  
   146  		if err := os.Chmod(path, perm|0o5); err != nil {
   147  			return err
   148  		}
   149  	}
   150  
   151  	return nil
   152  }
   153  
   154  func remove(dir string) {
   155  	_ = os.RemoveAll(dir)
   156  }
   157  
   158  // copyBusybox copies the rootfs for a busybox container created for the test image
   159  // into the new directory for the specific test
   160  func copyBusybox(dest string) error {
   161  	out, err := exec.Command("sh", "-c", fmt.Sprintf("tar --exclude './dev/*' -C %q -xf %q", dest, busyboxTar)).CombinedOutput()
   162  	if err != nil {
   163  		return fmt.Errorf("untar error %w: %q", err, out)
   164  	}
   165  	return nil
   166  }
   167  
   168  func newContainer(t *testing.T, config *configs.Config) (libcontainer.Container, error) {
   169  	name := strings.ReplaceAll(t.Name(), "/", "_") + strconv.FormatInt(-int64(time.Now().Nanosecond()), 35)
   170  	root := t.TempDir()
   171  
   172  	f, err := libcontainer.New(root)
   173  	if err != nil {
   174  		return nil, err
   175  	}
   176  	return f.Create(name, config)
   177  }
   178  
   179  // runContainer runs the container with the specific config and arguments
   180  //
   181  // buffers are returned containing the STDOUT and STDERR output for the run
   182  // along with the exit code and any go error
   183  func runContainer(t *testing.T, config *configs.Config, args ...string) (buffers *stdBuffers, exitCode int, err error) {
   184  	container, err := newContainer(t, config)
   185  	if err != nil {
   186  		return nil, -1, err
   187  	}
   188  	defer destroyContainer(container)
   189  	buffers = newStdBuffers()
   190  	process := &libcontainer.Process{
   191  		Cwd:    "/",
   192  		Args:   args,
   193  		Env:    standardEnvironment,
   194  		Stdin:  buffers.Stdin,
   195  		Stdout: buffers.Stdout,
   196  		Stderr: buffers.Stderr,
   197  		Init:   true,
   198  	}
   199  
   200  	err = container.Run(process)
   201  	if err != nil {
   202  		return buffers, -1, err
   203  	}
   204  	ps, err := process.Wait()
   205  	if err != nil {
   206  		return buffers, -1, err
   207  	}
   208  	status := ps.Sys().(syscall.WaitStatus)
   209  	if status.Exited() {
   210  		exitCode = status.ExitStatus()
   211  	} else if status.Signaled() {
   212  		exitCode = -int(status.Signal())
   213  	} else {
   214  		return buffers, -1, err
   215  	}
   216  	return
   217  }
   218  
   219  // runContainerOk is a wrapper for runContainer, simplifying its use for cases
   220  // when the run is expected to succeed and return exit code of 0.
   221  func runContainerOk(t *testing.T, config *configs.Config, args ...string) *stdBuffers {
   222  	buffers, exitCode, err := runContainer(t, config, args...)
   223  
   224  	t.Helper()
   225  	if err != nil {
   226  		t.Fatalf("%s: %s", buffers, err)
   227  	}
   228  	if exitCode != 0 {
   229  		t.Fatalf("exit code not 0. code %d stderr %q", exitCode, buffers.Stderr)
   230  	}
   231  
   232  	return buffers
   233  }
   234  
   235  func destroyContainer(container libcontainer.Container) {
   236  	_ = container.Destroy()
   237  }
   238  

View as plain text