github.com/stackdocker/rkt@v0.10.1-0.20151109095037-1aa827478248/tests/rkt_tests.go (about)

     1  // Copyright 2015 The rkt Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package main
    16  
    17  import (
    18  	"crypto/sha512"
    19  	"encoding/hex"
    20  	"fmt"
    21  	"io"
    22  	"io/ioutil"
    23  	"os"
    24  	"os/exec"
    25  	"path/filepath"
    26  	"strings"
    27  	"syscall"
    28  	"testing"
    29  	"time"
    30  
    31  	"github.com/coreos/rkt/Godeps/_workspace/src/github.com/appc/spec/schema/types"
    32  	"github.com/coreos/rkt/Godeps/_workspace/src/github.com/steveeJ/gexpect"
    33  	"github.com/coreos/rkt/tests/testutils"
    34  )
    35  
    36  const (
    37  	nobodyUid = uint32(65534)
    38  )
    39  
    40  func expectCommon(p *gexpect.ExpectSubprocess, searchString string, timeout time.Duration) error {
    41  	var err error
    42  
    43  	p.Capture()
    44  	if timeout == 0 {
    45  		err = p.Expect(searchString)
    46  	} else {
    47  		err = p.ExpectTimeout(searchString, timeout)
    48  	}
    49  	if err != nil {
    50  		return fmt.Errorf(string(p.Collect()))
    51  	}
    52  
    53  	return nil
    54  }
    55  
    56  func expectWithOutput(p *gexpect.ExpectSubprocess, searchString string) error {
    57  	return expectCommon(p, searchString, 0)
    58  }
    59  
    60  func expectRegexWithOutput(p *gexpect.ExpectSubprocess, searchPattern string) ([]string, string, error) {
    61  	return p.ExpectRegexFindWithOutput(searchPattern)
    62  }
    63  
    64  func expectRegexTimeoutWithOutput(p *gexpect.ExpectSubprocess, searchPattern string, timeout time.Duration) ([]string, string, error) {
    65  	return p.ExpectTimeoutRegexFindWithOutput(searchPattern, timeout)
    66  }
    67  
    68  func expectTimeoutWithOutput(p *gexpect.ExpectSubprocess, searchString string, timeout time.Duration) error {
    69  	return expectCommon(p, searchString, timeout)
    70  }
    71  
    72  func patchACI(inputFileName, newFileName string, args ...string) string {
    73  	var allArgs []string
    74  
    75  	actool := testutils.GetValueFromEnvOrPanic("ACTOOL")
    76  	tmpDir := testutils.GetValueFromEnvOrPanic("FUNCTIONAL_TMP")
    77  
    78  	imagePath, err := filepath.Abs(filepath.Join(tmpDir, newFileName))
    79  	if err != nil {
    80  		panic(fmt.Sprintf("Cannot create ACI: %v\n", err))
    81  	}
    82  	allArgs = append(allArgs, "patch-manifest")
    83  	allArgs = append(allArgs, "--no-compression")
    84  	allArgs = append(allArgs, "--overwrite")
    85  	allArgs = append(allArgs, args...)
    86  	allArgs = append(allArgs, inputFileName)
    87  	allArgs = append(allArgs, imagePath)
    88  
    89  	output, err := exec.Command(actool, allArgs...).CombinedOutput()
    90  	if err != nil {
    91  		panic(fmt.Sprintf("Cannot create ACI: %v: %s\n", err, output))
    92  	}
    93  	return imagePath
    94  }
    95  
    96  func patchTestACI(newFileName string, args ...string) string {
    97  	image := getInspectImagePath()
    98  	return patchACI(image, newFileName, args...)
    99  }
   100  
   101  func spawnOrFail(t *testing.T, cmd string) *gexpect.ExpectSubprocess {
   102  	t.Logf("Running command: %v", cmd)
   103  	child, err := gexpect.Spawn(cmd)
   104  	if err != nil {
   105  		t.Fatalf("Cannot exec rkt: %v", err)
   106  	}
   107  	return child
   108  }
   109  
   110  func waitOrFail(t *testing.T, child *gexpect.ExpectSubprocess, shouldSucceed bool) {
   111  	err := child.Wait()
   112  	switch {
   113  	case !shouldSucceed && err == nil:
   114  		t.Fatalf("Expected test to fail but it didn't")
   115  	case shouldSucceed && err != nil:
   116  		t.Fatalf("rkt didn't terminate correctly: %v", err)
   117  	case err != nil && err.Error() != "exit status 1":
   118  		t.Fatalf("rkt terminated with unexpected error: %v", err)
   119  	}
   120  }
   121  
   122  func spawnAndWaitOrFail(t *testing.T, cmd string, shouldSucceed bool) {
   123  	child := spawnOrFail(t, cmd)
   124  	waitOrFail(t, child, shouldSucceed)
   125  }
   126  
   127  func getEmptyImagePath() string {
   128  	return testutils.GetValueFromEnvOrPanic("RKT_EMPTY_IMAGE")
   129  }
   130  
   131  func getInspectImagePath() string {
   132  	return testutils.GetValueFromEnvOrPanic("RKT_INSPECT_IMAGE")
   133  }
   134  
   135  func getHashOrPanic(path string) string {
   136  	hash, err := getHash(path)
   137  	if err != nil {
   138  		panic(fmt.Sprintf("Cannot get hash from file located at %v", path))
   139  	}
   140  	return hash
   141  }
   142  
   143  func getHash(filePath string) (string, error) {
   144  	f, err := os.Open(filePath)
   145  	if err != nil {
   146  		return "", fmt.Errorf("error opening file: %v", err)
   147  	}
   148  
   149  	hash := sha512.New()
   150  	r := io.TeeReader(f, hash)
   151  
   152  	if _, err := io.Copy(ioutil.Discard, r); err != nil {
   153  		return "", fmt.Errorf("error reading file: %v", err)
   154  	}
   155  
   156  	return hex.EncodeToString(hash.Sum(nil)), nil
   157  }
   158  
   159  func createTempDirOrPanic(dirName string) string {
   160  	tmpDir, err := ioutil.TempDir("", dirName)
   161  	if err != nil {
   162  		panic(fmt.Sprintf("Cannot create temp dir: %v", err))
   163  	}
   164  	return tmpDir
   165  }
   166  
   167  func importImageAndFetchHashAsGid(t *testing.T, ctx *testutils.RktRunCtx, img string, gid int) string {
   168  	// Import the test image into store manually.
   169  	cmd := fmt.Sprintf("%s --insecure-skip-verify fetch %s", ctx.Cmd(), img)
   170  
   171  	// TODO(jonboulle): non-root user breaks trying to read root-written
   172  	// config directories. Should be a better way to approach this. Should
   173  	// config directories be readable by the rkt group too?
   174  	if gid != 0 {
   175  		cmd = fmt.Sprintf("%s --insecure-skip-verify fetch %s", ctx.CmdNoConfig(), img)
   176  	}
   177  	child, err := gexpect.Command(cmd)
   178  	if err != nil {
   179  		t.Fatalf("cannot create rkt command: %v", err)
   180  	}
   181  	if gid != 0 {
   182  		child.Cmd.SysProcAttr = &syscall.SysProcAttr{}
   183  		child.Cmd.SysProcAttr.Credential = &syscall.Credential{Uid: nobodyUid, Gid: uint32(gid)}
   184  	}
   185  
   186  	err = child.Start()
   187  	if err != nil {
   188  		t.Fatalf("cannot exec rkt: %v", err)
   189  	}
   190  
   191  	// Read out the image hash.
   192  	result, out, err := expectRegexWithOutput(child, "sha512-[0-9a-f]{32}")
   193  	if err != nil || len(result) != 1 {
   194  		t.Fatalf("Error: %v\nOutput: %v", err, out)
   195  	}
   196  
   197  	waitOrFail(t, child, true)
   198  
   199  	return result[0]
   200  }
   201  
   202  func importImageAndFetchHash(t *testing.T, ctx *testutils.RktRunCtx, img string) string {
   203  	return importImageAndFetchHashAsGid(t, ctx, img, 0)
   204  }
   205  
   206  func patchImportAndFetchHash(image string, patches []string, t *testing.T, ctx *testutils.RktRunCtx) string {
   207  	imagePath := patchTestACI(image, patches...)
   208  	defer os.Remove(imagePath)
   209  
   210  	return importImageAndFetchHash(t, ctx, imagePath)
   211  }
   212  
   213  func runGC(t *testing.T, ctx *testutils.RktRunCtx) {
   214  	cmd := fmt.Sprintf("%s gc --grace-period=0s", ctx.Cmd())
   215  	spawnAndWaitOrFail(t, cmd, true)
   216  }
   217  
   218  func runImageGC(t *testing.T, ctx *testutils.RktRunCtx) {
   219  	cmd := fmt.Sprintf("%s image gc", ctx.Cmd())
   220  	spawnAndWaitOrFail(t, cmd, true)
   221  }
   222  
   223  func removeFromCas(t *testing.T, ctx *testutils.RktRunCtx, hash string) {
   224  	cmd := fmt.Sprintf("%s image rm %s", ctx.Cmd(), hash)
   225  	spawnAndWaitOrFail(t, cmd, true)
   226  }
   227  
   228  func runRktAndGetUUID(t *testing.T, rktCmd string) string {
   229  	child := spawnOrFail(t, rktCmd)
   230  	defer waitOrFail(t, child, true)
   231  
   232  	result, out, err := expectRegexWithOutput(child, "\n[0-9a-f-]{36}")
   233  	if err != nil || len(result) != 1 {
   234  		t.Fatalf("Error: %v\nOutput: %v", err, out)
   235  	}
   236  
   237  	podIDStr := strings.TrimSpace(result[0])
   238  	podID, err := types.NewUUID(podIDStr)
   239  	if err != nil {
   240  		t.Fatalf("%q is not a valid UUID: %v", podIDStr, err)
   241  	}
   242  
   243  	return podID.String()
   244  }
   245  
   246  func runRktAsGidAndCheckOutput(t *testing.T, rktCmd, expectedLine string, expectError bool, gid int) {
   247  	child, err := gexpect.Command(rktCmd)
   248  	if err != nil {
   249  		t.Fatalf("cannot exec rkt: %v", err)
   250  	}
   251  	if gid != 0 {
   252  		child.Cmd.SysProcAttr = &syscall.SysProcAttr{}
   253  		child.Cmd.SysProcAttr.Credential = &syscall.Credential{Uid: nobodyUid, Gid: uint32(gid)}
   254  	}
   255  
   256  	err = child.Start()
   257  	if err != nil {
   258  		t.Fatalf("cannot exec rkt: %v", err)
   259  	}
   260  	defer waitOrFail(t, child, !expectError)
   261  
   262  	if expectedLine != "" {
   263  		if err := expectWithOutput(child, expectedLine); err != nil {
   264  			t.Fatalf("didn't receive expected output %q: %v", expectedLine, err)
   265  		}
   266  	}
   267  }
   268  
   269  func runRktAndCheckRegexOutput(t *testing.T, rktCmd, match string) {
   270  	child := spawnOrFail(t, rktCmd)
   271  	defer child.Wait()
   272  
   273  	result, out, err := expectRegexWithOutput(child, match)
   274  	if err != nil || len(result) != 1 {
   275  		t.Fatalf("%q regex must be found one time, Error: %v\nOutput: %v", match, err, out)
   276  	}
   277  }
   278  
   279  func runRktAndCheckOutput(t *testing.T, rktCmd, expectedLine string, expectError bool) {
   280  	runRktAsGidAndCheckOutput(t, rktCmd, expectedLine, expectError, 0)
   281  }
   282  
   283  func checkAppStatus(t *testing.T, ctx *testutils.RktRunCtx, multiApps bool, appName, expected string) {
   284  	cmd := fmt.Sprintf(`/bin/sh -c "`+
   285  		`UUID=$(%s list --full|grep '%s'|awk '{print $1}') ;`+
   286  		`echo -n 'status=' ;`+
   287  		`%s status $UUID|grep '^app-%s.*=[0-9]*$'|cut -d= -f2"`,
   288  		ctx.Cmd(), appName, ctx.Cmd(), appName)
   289  
   290  	if multiApps {
   291  		cmd = fmt.Sprintf(`/bin/sh -c "`+
   292  			`UUID=$(%s list --full|grep '^[a-f0-9]'|awk '{print $1}') ;`+
   293  			`echo -n 'status=' ;`+
   294  			`%s status $UUID|grep '^app-%s.*=[0-9]*$'|cut -d= -f2"`,
   295  			ctx.Cmd(), ctx.Cmd(), appName)
   296  	}
   297  
   298  	t.Logf("Get status for app %s\n", appName)
   299  	child := spawnOrFail(t, cmd)
   300  	defer waitOrFail(t, child, true)
   301  
   302  	if err := expectWithOutput(child, expected); err != nil {
   303  		// For debugging purposes, print the full output of
   304  		// "rkt list" and "rkt status"
   305  		cmd := fmt.Sprintf(`%s list --full ;`+
   306  			`UUID=$(%s list --full|grep  '^[a-f0-9]'|awk '{print $1}') ;`+
   307  			`%s status $UUID`,
   308  			ctx.Cmd(), ctx.Cmd(), ctx.Cmd())
   309  		out, err2 := exec.Command("/bin/sh", "-c", cmd).CombinedOutput()
   310  		if err2 != nil {
   311  			t.Logf("Could not run rkt status: %v. %s", err2, out)
   312  		} else {
   313  			t.Logf("%s\n", out)
   314  		}
   315  
   316  		t.Fatalf("Failed to get the status for app %s: expected: %s. %v",
   317  			appName, expected, err)
   318  	}
   319  }