github.com/rkt/rkt@v1.30.1-0.20200224141603-171c416fac02/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  	"bytes"
    19  	"crypto/sha512"
    20  	"encoding/hex"
    21  	"encoding/json"
    22  	"fmt"
    23  	"io"
    24  	"io/ioutil"
    25  	"os"
    26  	"os/exec"
    27  	"path"
    28  	"path/filepath"
    29  	"regexp"
    30  	"strconv"
    31  	"strings"
    32  	"syscall"
    33  	"testing"
    34  	"time"
    35  
    36  	"golang.org/x/crypto/openpgp"
    37  
    38  	"github.com/appc/spec/schema"
    39  	"github.com/appc/spec/schema/types"
    40  	"github.com/coreos/gexpect"
    41  	shellquote "github.com/kballard/go-shellquote"
    42  	"github.com/rkt/rkt/api/v1alpha"
    43  	"github.com/rkt/rkt/common"
    44  	"github.com/rkt/rkt/tests/testutils"
    45  	taas "github.com/rkt/rkt/tests/testutils/aci-server"
    46  	"google.golang.org/grpc"
    47  )
    48  
    49  const (
    50  	defaultTimeLayout = "2006-01-02 15:04:05.999 -0700 MST"
    51  
    52  	baseAppName = "rkt-inspect"
    53  )
    54  
    55  func expectCommon(p *gexpect.ExpectSubprocess, searchString string, timeout time.Duration) error {
    56  	var err error
    57  
    58  	p.Capture()
    59  	if timeout == 0 {
    60  		err = p.Expect(searchString)
    61  	} else {
    62  		err = p.ExpectTimeout(searchString, timeout)
    63  	}
    64  	if err != nil {
    65  		return fmt.Errorf(string(p.Collect()))
    66  	}
    67  
    68  	return nil
    69  }
    70  
    71  func expectWithOutput(p *gexpect.ExpectSubprocess, searchString string) error {
    72  	return expectCommon(p, searchString, 0)
    73  }
    74  
    75  func expectRegexWithOutput(p *gexpect.ExpectSubprocess, searchPattern string) ([]string, string, error) {
    76  	return p.ExpectRegexFindWithOutput(searchPattern)
    77  }
    78  
    79  func expectRegexTimeoutWithOutput(p *gexpect.ExpectSubprocess, searchPattern string, timeout time.Duration) ([]string, string, error) {
    80  	return p.ExpectTimeoutRegexFindWithOutput(searchPattern, timeout)
    81  }
    82  
    83  func expectTimeoutWithOutput(p *gexpect.ExpectSubprocess, searchString string, timeout time.Duration) error {
    84  	return expectCommon(p, searchString, timeout)
    85  }
    86  
    87  func patchACI(inputFileName, newFileName string, args ...string) string {
    88  	var allArgs []string
    89  
    90  	actool := testutils.GetValueFromEnvOrPanic("ACTOOL")
    91  	tmpDir := testutils.GetValueFromEnvOrPanic("FUNCTIONAL_TMP")
    92  
    93  	imagePath, err := filepath.Abs(filepath.Join(tmpDir, newFileName))
    94  	if err != nil {
    95  		panic(fmt.Sprintf("Cannot create ACI: %v\n", err))
    96  	}
    97  	allArgs = append(allArgs, "patch-manifest")
    98  	allArgs = append(allArgs, "--no-compression")
    99  	allArgs = append(allArgs, "--overwrite")
   100  	allArgs = append(allArgs, args...)
   101  	allArgs = append(allArgs, inputFileName)
   102  	allArgs = append(allArgs, imagePath)
   103  
   104  	output, err := exec.Command(actool, allArgs...).CombinedOutput()
   105  	if err != nil {
   106  		panic(fmt.Sprintf("Cannot create ACI: %v: %s\n", err, output))
   107  	}
   108  	return imagePath
   109  }
   110  
   111  func patchTestACI(newFileName string, args ...string) string {
   112  	image := getInspectImagePath()
   113  	return patchACI(image, newFileName, args...)
   114  }
   115  
   116  func spawnOrFail(t *testing.T, cmd string) *gexpect.ExpectSubprocess {
   117  	t.Logf("Spawning command: %v\n", cmd)
   118  	child, err := gexpect.Spawn(cmd)
   119  	if err != nil {
   120  		t.Fatalf("Cannot exec rkt: %v", err)
   121  	}
   122  	return child
   123  }
   124  
   125  // waitOrFail waits for the child to exit, draining all its output.
   126  // If a non-negative return value is provided, child exit status must match.
   127  func waitOrFail(t *testing.T, child *gexpect.ExpectSubprocess, expectedStatus int) {
   128  	bufOut := []string{}
   129  	// TODO(lucab): gexpect should accept those channels from the caller
   130  	ttyIn, ttyOut := child.AsyncInteractChannels()
   131  	close(ttyIn)
   132  	// drain output till gexpect closes the channel (on EOF or error)
   133  	for line := range ttyOut {
   134  		bufOut = append(bufOut, line)
   135  	}
   136  	err := child.Wait()
   137  	status, _ := common.GetExitStatus(err)
   138  	if expectedStatus >= 0 && status != expectedStatus {
   139  		t.Fatalf("rkt terminated with unexpected status %d, expected %d\nOutput:\n%s", status, expectedStatus, bufOut)
   140  	}
   141  }
   142  
   143  // waitPodReady waits for the pod supervisor to get ready, busy-looping until `timeout`
   144  // while waiting for it. It returns the pod UUID or an error on failure.
   145  func waitPodReady(ctx *testutils.RktRunCtx, t *testing.T, uuidFile string, timeout time.Duration) (string, error) {
   146  	var podUUID []byte
   147  	var err error
   148  	interval := 500 * time.Millisecond
   149  	elapsed := time.Duration(0)
   150  
   151  	for elapsed < timeout {
   152  		time.Sleep(interval)
   153  		elapsed += interval
   154  		podUUID, err = ioutil.ReadFile(uuidFile)
   155  		if err == nil {
   156  			break
   157  		}
   158  	}
   159  	if err != nil {
   160  		return "", fmt.Errorf("Can't read pod UUID: %v", err)
   161  	}
   162  
   163  	// wait up to one minute for the pod supervisor to be ready
   164  	cmd := strings.Fields(fmt.Sprintf("%s status --wait-ready=%s %s", ctx.Cmd(), timeout, podUUID))
   165  	statusCmd := exec.Command(cmd[0], cmd[1:]...)
   166  	t.Logf("Running command: %v\n", cmd)
   167  	output, err := statusCmd.CombinedOutput()
   168  	if err != nil {
   169  		return "", fmt.Errorf("Failed to wait for pod readiness, error %v output %v", err, string(output))
   170  	}
   171  
   172  	return string(podUUID), nil
   173  }
   174  
   175  // waitAppAttachable waits for an attachable application to get ready, busy-looping until `timeout`
   176  // while waiting for it. It returns an error on failure.
   177  func waitAppAttachable(ctx *testutils.RktRunCtx, t *testing.T, podUUID, appName string, timeout time.Duration) error {
   178  	var (
   179  		err         error
   180  		output      []byte
   181  		appNameFlag string
   182  	)
   183  
   184  	if appName != "" {
   185  		appNameFlag = "--app=" + appName
   186  	}
   187  	cmd := strings.Fields(fmt.Sprintf("%s attach --mode=list %s %s", ctx.Cmd(), appNameFlag, podUUID))
   188  
   189  	interval := 500 * time.Millisecond
   190  	elapsed := time.Duration(0)
   191  	for elapsed < timeout {
   192  		time.Sleep(interval)
   193  		elapsed += interval
   194  		statusCmd := exec.Command(cmd[0], cmd[1:]...)
   195  		output, err = statusCmd.CombinedOutput()
   196  		if err == nil {
   197  			break
   198  		}
   199  	}
   200  	if err != nil {
   201  		return fmt.Errorf("%s", output)
   202  	}
   203  	return nil
   204  }
   205  
   206  func spawnAndWaitOrFail(t *testing.T, cmd string, expectedStatus int) {
   207  	child := spawnOrFail(t, cmd)
   208  	waitOrFail(t, child, expectedStatus)
   209  }
   210  
   211  func getEmptyImagePath() string {
   212  	return testutils.GetValueFromEnvOrPanic("RKT_EMPTY_IMAGE")
   213  }
   214  
   215  func getInspectImagePath() string {
   216  	return testutils.GetValueFromEnvOrPanic("RKT_INSPECT_IMAGE")
   217  }
   218  
   219  func getHashOrPanic(path string) string {
   220  	hash, err := getHash(path)
   221  	if err != nil {
   222  		panic(fmt.Sprintf("Cannot get hash from file located at %v", path))
   223  	}
   224  	return hash
   225  }
   226  
   227  func getHash(filePath string) (string, error) {
   228  	f, err := os.Open(filePath)
   229  	if err != nil {
   230  		return "", fmt.Errorf("error opening file: %v", err)
   231  	}
   232  
   233  	hash := sha512.New()
   234  	r := io.TeeReader(f, hash)
   235  
   236  	if _, err := io.Copy(ioutil.Discard, r); err != nil {
   237  		return "", fmt.Errorf("error reading file: %v", err)
   238  	}
   239  
   240  	return hex.EncodeToString(hash.Sum(nil)), nil
   241  }
   242  
   243  func mustTempDir(dirName string) string {
   244  	tmpDir, err := ioutil.TempDir("", dirName)
   245  	if err != nil {
   246  		panic(fmt.Sprintf("Cannot create temp dir: %v", err))
   247  	}
   248  	return tmpDir
   249  }
   250  
   251  // createFileOrPanic creates an empty file within the given directory
   252  // with a specified name. Panics if file creation fails for any reason.
   253  func createFileOrPanic(dirName, fileName string) string {
   254  	name := filepath.Join(dirName, fileName)
   255  	file, err := os.Create(name)
   256  	if err != nil {
   257  		panic(err)
   258  	}
   259  
   260  	defer file.Close()
   261  	return name
   262  }
   263  
   264  func importImageAndFetchHashAsUidGid(t *testing.T, ctx *testutils.RktRunCtx, img string, fetchArgs string, uid int, gid int) (string, error) {
   265  	// Import the test image into store manually.
   266  	cmd := fmt.Sprintf("%s --insecure-options=image,tls fetch --pull-policy=new %s %s", ctx.Cmd(), fetchArgs, img)
   267  
   268  	// TODO(jonboulle): non-root user breaks trying to read root-written
   269  	// config directories. Should be a better way to approach this. Should
   270  	// config directories be readable by the rkt group too?
   271  	if gid != 0 {
   272  		cmd = fmt.Sprintf("%s --insecure-options=image,tls fetch --pull-policy=new %s %s", ctx.CmdNoConfig(), fetchArgs, img)
   273  	}
   274  	child, err := gexpect.Command(cmd)
   275  	if err != nil {
   276  		t.Fatalf("cannot create rkt command: %v", err)
   277  	}
   278  	if gid != 0 {
   279  		child.Cmd.SysProcAttr = &syscall.SysProcAttr{}
   280  		child.Cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
   281  	}
   282  
   283  	err = child.Start()
   284  	if err != nil {
   285  		t.Fatalf("cannot exec rkt: %v", err)
   286  	}
   287  
   288  	// Read out the image hash.
   289  	result, out, err := expectRegexWithOutput(child, "sha512-[0-9a-f]{32,64}")
   290  	if exitErr := checkExitStatus(child); exitErr != nil {
   291  		t.Logf("%v", exitErr)
   292  		return "", fmt.Errorf("fetching of %q failed", img)
   293  	}
   294  	if err != nil || len(result) != 1 {
   295  		t.Fatalf("Error: %v\nOutput: %v", err, out)
   296  	}
   297  
   298  	return result[0], nil
   299  }
   300  
   301  func importImageAndFetchHash(t *testing.T, ctx *testutils.RktRunCtx, fetchArgs string, img string) (string, error) {
   302  	return importImageAndFetchHashAsUidGid(t, ctx, fetchArgs, img, 0, 0)
   303  }
   304  
   305  func importImageAndRun(imagePath string, t *testing.T, ctx *testutils.RktRunCtx) {
   306  	cmd := fmt.Sprintf("%s --insecure-options=image run %s", ctx.Cmd(), imagePath)
   307  	spawnAndWaitOrFail(t, cmd, 0)
   308  }
   309  
   310  func importImageAndPrepare(imagePath string, t *testing.T, ctx *testutils.RktRunCtx) {
   311  	cmd := fmt.Sprintf("%s --insecure-options=image prepare %s", ctx.Cmd(), imagePath)
   312  	spawnAndWaitOrFail(t, cmd, 0)
   313  }
   314  
   315  func patchImportAndFetchHash(image string, patches []string, t *testing.T, ctx *testutils.RktRunCtx) (string, error) {
   316  	imagePath := patchTestACI(image, patches...)
   317  	defer os.Remove(imagePath)
   318  
   319  	return importImageAndFetchHash(t, ctx, "", imagePath)
   320  }
   321  
   322  func runGC(t *testing.T, ctx *testutils.RktRunCtx) {
   323  	cmd := fmt.Sprintf("%s gc --grace-period=0s", ctx.Cmd())
   324  	spawnAndWaitOrFail(t, cmd, 0)
   325  }
   326  
   327  func runImageGC(t *testing.T, ctx *testutils.RktRunCtx) {
   328  	cmd := fmt.Sprintf("%s image gc", ctx.Cmd())
   329  	spawnAndWaitOrFail(t, cmd, 0)
   330  }
   331  
   332  func removeFromCas(t *testing.T, ctx *testutils.RktRunCtx, hash string) {
   333  	cmd := fmt.Sprintf("%s image rm %s", ctx.Cmd(), hash)
   334  	spawnAndWaitOrFail(t, cmd, 0)
   335  }
   336  
   337  func runRktAndGetUUID(t *testing.T, rktCmd string) string {
   338  	child := spawnOrFail(t, rktCmd)
   339  	defer waitOrFail(t, child, 0)
   340  
   341  	result, out, err := expectRegexWithOutput(child, "[0-9a-f-]{36}")
   342  	if err != nil || len(result) != 1 {
   343  		t.Fatalf("Error: %v\nOutput: %v", err, out)
   344  	}
   345  
   346  	podIDStr := strings.TrimSpace(result[0])
   347  	podID, err := types.NewUUID(podIDStr)
   348  	if err != nil {
   349  		t.Fatalf("%q is not a valid UUID: %v", podIDStr, err)
   350  	}
   351  
   352  	return podID.String()
   353  }
   354  
   355  func runRktAsGidAndCheckOutput(t *testing.T, rktCmd, expectedLine string, expectError bool, gid int) {
   356  	nobodyUid, _ := testutils.GetUnprivilegedUidGid()
   357  	runRktAsUidGidAndCheckOutput(t, rktCmd, expectedLine, false, expectError, nobodyUid, gid)
   358  }
   359  
   360  func runRktAsGidAndCheckREOutput(t *testing.T, rktCmd, expectedLine string, expectError bool, gid int) {
   361  	nobodyUid, _ := testutils.GetUnprivilegedUidGid()
   362  	runRktAsUidGidAndCheckOutput(t, rktCmd, expectedLine, true, expectError, nobodyUid, gid)
   363  }
   364  
   365  func runRktAsUidGidAndCheckOutput(t *testing.T, rktCmd, expectedLine string, lineIsRegex, expectError bool, uid, gid int) {
   366  	child, err := gexpect.Command(rktCmd)
   367  	if err != nil {
   368  		t.Fatalf("cannot exec rkt: %v", err)
   369  	}
   370  	if gid != 0 {
   371  		child.Cmd.SysProcAttr = &syscall.SysProcAttr{}
   372  		child.Cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
   373  	}
   374  
   375  	err = child.Start()
   376  	if err != nil {
   377  		t.Fatalf("cannot start rkt: %v", err)
   378  	}
   379  	expectedStatus := 0
   380  	if expectError {
   381  		expectedStatus = 254
   382  	}
   383  	defer waitOrFail(t, child, expectedStatus)
   384  
   385  	if expectedLine != "" {
   386  		if lineIsRegex == true {
   387  			_, _, err := expectRegexWithOutput(child, expectedLine)
   388  			if err != nil {
   389  				t.Fatalf("didn't receive expected regex %q in output: %v", expectedLine, err)
   390  			}
   391  		} else {
   392  			err = expectWithOutput(child, expectedLine)
   393  			if err != nil {
   394  				t.Fatalf("didn't receive expected output %q: %v", expectedLine, err)
   395  			}
   396  		}
   397  
   398  	}
   399  }
   400  
   401  func runRkt(t *testing.T, rktCmd string, uid, gid int) (string, int) {
   402  	child, err := gexpect.Command(rktCmd)
   403  	if err != nil {
   404  		t.Fatalf("cannot exec rkt: %v", err)
   405  	}
   406  	if gid != 0 {
   407  		child.Cmd.SysProcAttr = &syscall.SysProcAttr{}
   408  		child.Cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
   409  	}
   410  
   411  	err = child.Start()
   412  	if err != nil {
   413  		t.Fatalf("cannot start rkt: %v", err)
   414  	}
   415  
   416  	_, linesChan := child.AsyncInteractChannels()
   417  
   418  	var buf bytes.Buffer
   419  	for line := range linesChan {
   420  		buf.WriteString(line + "\n") // reappend newline
   421  	}
   422  
   423  	status, _ := common.GetExitStatus(child.Wait())
   424  	return buf.String(), status
   425  }
   426  
   427  func startRktAsGidAndCheckOutput(t *testing.T, rktCmd, expectedLine string, gid int) *gexpect.ExpectSubprocess {
   428  	child, err := gexpect.Command(rktCmd)
   429  	if err != nil {
   430  		t.Fatalf("cannot exec rkt: %v", err)
   431  	}
   432  	if gid != 0 {
   433  		child.Cmd.SysProcAttr = &syscall.SysProcAttr{}
   434  		nobodyUid, _ := testutils.GetUnprivilegedUidGid()
   435  		child.Cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(nobodyUid), Gid: uint32(gid)}
   436  	}
   437  
   438  	if err := child.Start(); err != nil {
   439  		t.Fatalf("cannot exec rkt: %v", err)
   440  	}
   441  
   442  	if expectedLine != "" {
   443  		if err := expectWithOutput(child, expectedLine); err != nil {
   444  			t.Fatalf("didn't receive expected output %q: %v", expectedLine, err)
   445  		}
   446  	}
   447  	return child
   448  }
   449  
   450  func startRktAsUidGidAndCheckOutput(t *testing.T, rktCmd, expectedLine string, expectError bool, uid, gid int) *gexpect.ExpectSubprocess {
   451  	child, err := gexpect.Command(rktCmd)
   452  	if err != nil {
   453  		t.Fatalf("cannot exec rkt: %v", err)
   454  	}
   455  	if gid != 0 {
   456  		child.Cmd.SysProcAttr = &syscall.SysProcAttr{}
   457  		child.Cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}
   458  	}
   459  
   460  	err = child.Start()
   461  	if err != nil {
   462  		t.Fatalf("cannot start rkt: %v", err)
   463  	}
   464  
   465  	if expectedLine != "" {
   466  		if err := expectWithOutput(child, expectedLine); err != nil {
   467  			t.Fatalf("didn't receive expected output %q: %v", expectedLine, err)
   468  		}
   469  	}
   470  	return child
   471  }
   472  
   473  func runRktAndCheckRegexOutput(t *testing.T, rktCmd, match string) error {
   474  	re, err := regexp.Compile(match)
   475  	if err != nil {
   476  		t.Fatalf("error compiling regex %q: %v", match, err)
   477  	}
   478  
   479  	args, err := shellquote.Split(rktCmd)
   480  	if err != nil {
   481  		t.Fatalf("error splitting cmd %q: %v", rktCmd, err)
   482  	}
   483  
   484  	path, err := exec.LookPath(args[0])
   485  	cmd := exec.Command(path, args[1:]...)
   486  
   487  	out, err := cmd.CombinedOutput()
   488  
   489  	result := re.MatchString(string(out))
   490  	if !result {
   491  		t.Fatalf("%q regex must be found\nOutput: %q", match, string(out))
   492  	}
   493  
   494  	return err
   495  }
   496  
   497  func runRktAndCheckOutput(t *testing.T, rktCmd, expectedLine string, expectError bool) {
   498  	runRktAsGidAndCheckOutput(t, rktCmd, expectedLine, expectError, 0)
   499  }
   500  
   501  func runRktAndCheckREOutput(t *testing.T, rktCmd, expectedLine string, expectError bool) {
   502  	runRktAsGidAndCheckREOutput(t, rktCmd, expectedLine, expectError, 0)
   503  }
   504  
   505  func startRktAndCheckOutput(t *testing.T, rktCmd, expectedLine string) *gexpect.ExpectSubprocess {
   506  	return startRktAsGidAndCheckOutput(t, rktCmd, expectedLine, 0)
   507  }
   508  
   509  func checkAppStatus(t *testing.T, ctx *testutils.RktRunCtx, multiApps bool, appName, expected string) {
   510  	cmd := fmt.Sprintf(`/bin/sh -c "`+
   511  		`UUID=$(%s list --full|grep '%s'|awk '{print $1}') ;`+
   512  		`echo -n 'status=' ;`+
   513  		`%s status $UUID|grep '^app-%s.*=[0-9]*$'|cut -d= -f2"`,
   514  		ctx.Cmd(), appName, ctx.Cmd(), appName)
   515  
   516  	if multiApps {
   517  		cmd = fmt.Sprintf(`/bin/sh -c "`+
   518  			`UUID=$(%s list --full|grep '^[a-f0-9]'|awk '{print $1}') ;`+
   519  			`echo -n 'status=' ;`+
   520  			`%s status $UUID|grep '^app-%s.*=[0-9]*$'|cut -d= -f2"`,
   521  			ctx.Cmd(), ctx.Cmd(), appName)
   522  	}
   523  
   524  	t.Logf("Get status for app %s\n", appName)
   525  	child := spawnOrFail(t, cmd)
   526  	defer waitOrFail(t, child, 0)
   527  
   528  	if err := expectWithOutput(child, expected); err != nil {
   529  		// For debugging purposes, print the full output of
   530  		// "rkt list" and "rkt status"
   531  		cmd := fmt.Sprintf(`%s list --full ;`+
   532  			`UUID=$(%s list --full|grep  '^[a-f0-9]'|awk '{print $1}') ;`+
   533  			`%s status $UUID`,
   534  			ctx.Cmd(), ctx.Cmd(), ctx.Cmd())
   535  		out, err2 := exec.Command("/bin/sh", "-c", cmd).CombinedOutput()
   536  		if err2 != nil {
   537  			t.Logf("Could not run rkt status: %v. %s", err2, out)
   538  		} else {
   539  			t.Logf("%s\n", out)
   540  		}
   541  
   542  		t.Fatalf("Failed to get the status for app %s: expected: %s. %v",
   543  			appName, expected, err)
   544  	}
   545  }
   546  
   547  type imageInfo struct {
   548  	id         string
   549  	name       string
   550  	version    string
   551  	importTime int64
   552  	size       int64
   553  	manifest   []byte
   554  }
   555  
   556  type appInfo struct {
   557  	name     string
   558  	exitCode int
   559  	image    *imageInfo
   560  	// TODO(yifan): Add app state.
   561  }
   562  
   563  type networkInfo struct {
   564  	name string
   565  	ipv4 string
   566  }
   567  
   568  type podInfo struct {
   569  	id        string
   570  	pid       int
   571  	state     string
   572  	apps      map[string]*appInfo
   573  	networks  map[string]*networkInfo
   574  	manifest  *schema.PodManifest
   575  	createdAt int64
   576  	startedAt int64
   577  }
   578  
   579  type imagePatch struct {
   580  	name    string
   581  	patches []string
   582  }
   583  
   584  // parsePodInfo parses the 'rkt status $UUID' result into podInfo struct.
   585  // For example, the 'result' can be:
   586  // state=running
   587  // networks=default:ip4=172.16.28.103
   588  // pid=14352
   589  // exited=false
   590  // created=2016-04-01 19:12:03.447 -0700 PDT
   591  // started=2016-04-01 19:12:04.279 -0700 PDT
   592  func parsePodInfoOutput(t *testing.T, result string, p *podInfo) {
   593  	lines := strings.Split(strings.TrimSuffix(result, "\n"), "\n")
   594  	for _, line := range lines {
   595  		tuples := strings.SplitN(line, "=", 2)
   596  		if len(tuples) != 2 {
   597  			t.Logf("Unexpected line: %v", line)
   598  			continue
   599  		}
   600  
   601  		switch tuples[0] {
   602  		case "state":
   603  			p.state = tuples[1]
   604  		case "networks":
   605  			if tuples[1] == "" {
   606  				break
   607  			}
   608  			networks := strings.Split(tuples[1], ",")
   609  			for _, n := range networks {
   610  				fields := strings.Split(n, ":")
   611  				if len(fields) != 2 {
   612  					t.Fatalf("Unexpected network info format: %v", n)
   613  				}
   614  
   615  				ip4 := strings.Split(fields[1], "=")
   616  				if len(ip4) != 2 {
   617  					t.Fatalf("Unexpected network info format: %v", n)
   618  				}
   619  
   620  				networkName := fields[0]
   621  				p.networks[networkName] = &networkInfo{
   622  					name: networkName,
   623  					ipv4: ip4[1],
   624  				}
   625  			}
   626  		case "pid":
   627  			pid, err := strconv.Atoi(tuples[1])
   628  			if err != nil {
   629  				t.Fatalf("Cannot parse the pod's pid %q: %v", tuples[1], err)
   630  			}
   631  			p.pid = pid
   632  		case "created":
   633  			createdAt, err := time.Parse(defaultTimeLayout, tuples[1])
   634  			if err != nil {
   635  				t.Fatalf("Cannot parse the pod's creation time %q: %v", tuples[1], err)
   636  			}
   637  			p.createdAt = createdAt.UnixNano()
   638  		case "started":
   639  			startedAt, err := time.Parse(defaultTimeLayout, tuples[1])
   640  			if err != nil {
   641  				t.Fatalf("Cannot parse the pod's start time %q: %v", tuples[1], err)
   642  			}
   643  			p.startedAt = startedAt.UnixNano()
   644  		}
   645  		if strings.HasPrefix(tuples[0], "app-") {
   646  			exitCode, err := strconv.Atoi(tuples[1])
   647  			if err != nil {
   648  				t.Fatalf("cannot parse exit code from %q : %v", tuples[1], err)
   649  			}
   650  			appName := strings.TrimPrefix(tuples[0], "app-")
   651  
   652  			for _, app := range p.apps {
   653  				if app.name == appName {
   654  					app.exitCode = exitCode
   655  					break
   656  				}
   657  			}
   658  		}
   659  	}
   660  
   661  	if p.state == "" {
   662  		t.Fatalf("Unexpected empty state for pod")
   663  	}
   664  
   665  	if p.createdAt <= 0 {
   666  		t.Fatalf("Unexpected createdAt <= 0 for pod")
   667  	}
   668  }
   669  
   670  func getPodDir(t *testing.T, ctx *testutils.RktRunCtx, podID string) string {
   671  	podsDir := path.Join(ctx.DataDir(), "pods")
   672  
   673  	dirs, err := ioutil.ReadDir(podsDir)
   674  	if err != nil {
   675  		t.Fatalf("Unexpected error: %v", err)
   676  	}
   677  
   678  	for _, dir := range dirs {
   679  		podDir := path.Join(podsDir, dir.Name(), podID)
   680  		if _, err := os.Stat(podDir); err == nil {
   681  			return podDir
   682  		}
   683  	}
   684  	t.Fatalf("Failed to find pod directory for pod %q", podID)
   685  	return ""
   686  }
   687  
   688  // getPodInfo returns the pod info for the given pod ID.
   689  func getPodInfo(t *testing.T, ctx *testutils.RktRunCtx, podID string) *podInfo {
   690  	p := &podInfo{
   691  		id:       podID,
   692  		pid:      -1,
   693  		apps:     make(map[string]*appInfo),
   694  		networks: make(map[string]*networkInfo),
   695  	}
   696  
   697  	// Read pod manifest.
   698  	output, err := exec.Command("/bin/bash", "-c", fmt.Sprintf("%s cat-manifest %s", ctx.Cmd(), podID)).CombinedOutput()
   699  	if err != nil {
   700  		t.Fatalf("Unexpected error: %v", err)
   701  	}
   702  
   703  	// Trim the last '\n' character.
   704  	mfst := bytes.TrimSpace(output)
   705  
   706  	// Fill app infos.
   707  	if err := json.Unmarshal(mfst, &p.manifest); err != nil {
   708  		t.Fatalf("Unexpected error: %v", err)
   709  	}
   710  	for _, app := range p.manifest.Apps {
   711  		appName := app.Name.String()
   712  		p.apps[appName] = &appInfo{
   713  			name: appName,
   714  			// TODO(yifan): Get the image's name.
   715  			image: &imageInfo{id: app.Image.ID.String()},
   716  		}
   717  	}
   718  
   719  	// Fill other infos.
   720  	output, _ = exec.Command("/bin/bash", "-c", fmt.Sprintf("%s status %s", ctx.Cmd(), podID)).CombinedOutput()
   721  	parsePodInfoOutput(t, string(output), p)
   722  
   723  	return p
   724  }
   725  
   726  // parseImageInfoOutput parses the 'rkt image list' result into imageInfo struct.
   727  // For example, the 'result' can be:
   728  // 'sha512-e9b77714dbbfda12cb9e136318b103a6f0ce082004d09d0224a620d2bbf38133 nginx:latest 2015-10-16 17:42:57.741 -0700 PDT true'
   729  func parseImageInfoOutput(t *testing.T, result string) *imageInfo {
   730  	fields := regexp.MustCompile("\t+").Split(result, -1)
   731  	nameVersion := strings.Split(fields[1], ":")
   732  	if len(nameVersion) != 2 {
   733  		t.Fatalf("Failed to parse name version string: %q", fields[1])
   734  	}
   735  	importTime, err := time.Parse(defaultTimeLayout, fields[3])
   736  	if err != nil {
   737  		t.Fatalf("Failed to parse time string: %q", fields[3])
   738  	}
   739  	size, err := strconv.Atoi(fields[2])
   740  	if err != nil {
   741  		t.Fatalf("Failed to parse image size string: %q", fields[2])
   742  	}
   743  
   744  	return &imageInfo{
   745  		id:         fields[0],
   746  		name:       nameVersion[0],
   747  		version:    nameVersion[1],
   748  		importTime: importTime.Unix(),
   749  		size:       int64(size),
   750  	}
   751  }
   752  
   753  // getImageInfo returns the image info for the given image ID.
   754  func getImageInfo(t *testing.T, ctx *testutils.RktRunCtx, imageID string) *imageInfo {
   755  	output, err := exec.Command("/bin/bash", "-c", fmt.Sprintf("%s image list --full | grep %s", ctx.Cmd(), imageID)).CombinedOutput()
   756  	if err != nil {
   757  		t.Fatalf("Unexpected error: %v", err)
   758  	}
   759  	imgInfo := parseImageInfoOutput(t, string(output))
   760  
   761  	// Get manifest
   762  	output, err = exec.Command("/bin/bash", "-c",
   763  		fmt.Sprintf("%s image cat-manifest --pretty-print=false %s", ctx.Cmd(), imageID)).CombinedOutput()
   764  	if err != nil {
   765  		t.Fatalf("Unexpected error: %v", err)
   766  	}
   767  	imgInfo.manifest = bytes.TrimSuffix(output, []byte{'\n'})
   768  	return imgInfo
   769  }
   770  
   771  func newAPIClientOrFail(t *testing.T, address string) (v1alpha.PublicAPIClient, *grpc.ClientConn) {
   772  	conn, err := grpc.Dial(address, grpc.WithInsecure())
   773  	if err != nil {
   774  		t.Fatalf("Unexpected error: %v", err)
   775  	}
   776  	c := v1alpha.NewPublicAPIClient(conn)
   777  	return c, conn
   778  }
   779  
   780  func runServer(t *testing.T, setup *taas.ServerSetup) *taas.Server {
   781  	server := taas.NewServer(setup)
   782  	go serverHandler(t, server)
   783  	return server
   784  }
   785  
   786  func serverHandler(t *testing.T, server *taas.Server) {
   787  	for {
   788  		select {
   789  		case msg, ok := <-server.Msg:
   790  			if ok {
   791  				t.Logf("server: %v", msg)
   792  			} else {
   793  				return
   794  			}
   795  		}
   796  	}
   797  }
   798  
   799  func runSignImage(t *testing.T, imagePath string, keyIndex int) string {
   800  	// keys stored in tests/secring.gpg.
   801  	keyFingerprint := ""
   802  	switch keyIndex {
   803  	case 1:
   804  		keyFingerprint = "D9DCEF41"
   805  	case 2:
   806  		keyFingerprint = "585091E3"
   807  	default:
   808  		panic("unknown key")
   809  	}
   810  
   811  	secringFile, err := os.Open("./secring.gpg")
   812  	if err != nil {
   813  		t.Fatalf("Cannot open secring.gpg file: %v", err)
   814  	}
   815  	defer secringFile.Close()
   816  
   817  	entityList, err := openpgp.ReadKeyRing(secringFile)
   818  	if err != nil {
   819  		t.Fatalf("Failed to read secring.gpg file: %v", err)
   820  	}
   821  
   822  	var signingEntity *openpgp.Entity
   823  	for _, entity := range entityList {
   824  		if entity.PrivateKey.KeyIdShortString() == keyFingerprint {
   825  			signingEntity = entity
   826  		}
   827  	}
   828  
   829  	imageFile, err := os.Open(imagePath)
   830  	if err != nil {
   831  		t.Fatalf("Cannot open image file %s: %v", imagePath, err)
   832  	}
   833  	defer imageFile.Close()
   834  
   835  	ascPath := fmt.Sprintf("%s.asc", imagePath)
   836  	ascFile, err := os.Create(ascPath)
   837  	if err != nil {
   838  		t.Fatalf("Cannot create asc file %s: %v", ascPath, err)
   839  	}
   840  	defer ascFile.Close()
   841  
   842  	err = openpgp.ArmoredDetachSign(ascFile, signingEntity, imageFile, nil)
   843  	if err != nil {
   844  		t.Fatalf("Cannot create armored detached signature: %v", err)
   845  	}
   846  
   847  	return ascPath
   848  }
   849  
   850  func runRktTrust(t *testing.T, ctx *testutils.RktRunCtx, prefix string, keyIndex int) {
   851  	var cmd string
   852  	keyFile := fmt.Sprintf("key%d.gpg", keyIndex)
   853  	if prefix == "" {
   854  		cmd = fmt.Sprintf(`%s trust --root %s`, ctx.Cmd(), keyFile)
   855  	} else {
   856  		cmd = fmt.Sprintf(`%s trust --prefix %s %s`, ctx.Cmd(), prefix, keyFile)
   857  	}
   858  
   859  	child := spawnOrFail(t, cmd)
   860  	defer waitOrFail(t, child, 0)
   861  
   862  	expected := "Are you sure you want to trust this key"
   863  	if err := expectWithOutput(child, expected); err != nil {
   864  		t.Fatalf("Expected but didn't find %q in %v", expected, err)
   865  	}
   866  
   867  	if err := child.SendLine("yes"); err != nil {
   868  		t.Fatalf("Cannot confirm rkt trust: %s", err)
   869  	}
   870  
   871  	if prefix == "" {
   872  		expected = "Added root key at"
   873  	} else {
   874  		expected = fmt.Sprintf(`Added key for prefix "%s" at`, prefix)
   875  	}
   876  	if err := expectWithOutput(child, expected); err != nil {
   877  		t.Fatalf("Expected but didn't find %q in %v", expected, err)
   878  	}
   879  }
   880  
   881  func generatePodManifestFile(t *testing.T, manifest *schema.PodManifest) string {
   882  	tmpDir := testutils.GetValueFromEnvOrPanic("FUNCTIONAL_TMP")
   883  	f, err := ioutil.TempFile(tmpDir, "rkt-test-manifest-")
   884  	if err != nil {
   885  		t.Fatalf("Cannot create tmp pod manifest: %v", err)
   886  	}
   887  
   888  	data, err := json.Marshal(manifest)
   889  	if err != nil {
   890  		t.Fatalf("Cannot marshal pod manifest: %v", err)
   891  	}
   892  	if err := ioutil.WriteFile(f.Name(), data, 0600); err != nil {
   893  		t.Fatalf("Cannot write pod manifest file: %v", err)
   894  	}
   895  	return f.Name()
   896  }
   897  
   898  func checkUserNS() error {
   899  	// CentOS 7 pretends to support user namespaces, but does not.
   900  	// See https://bugzilla.redhat.com/show_bug.cgi?id=1168776#c5
   901  	// Check if it really works
   902  	return exec.Command("/bin/bash", "-c", "unshare -U true").Run()
   903  }
   904  
   905  func authDir(confDir string) string {
   906  	return filepath.Join(confDir, "auth.d")
   907  }
   908  
   909  func pathsDir(confDir string) string {
   910  	return filepath.Join(confDir, "paths.d")
   911  }
   912  
   913  func stage1Dir(confDir string) string {
   914  	return filepath.Join(confDir, "stage1.d")
   915  }
   916  
   917  func writeConfig(t *testing.T, dir, filename, contents string) {
   918  	if err := os.MkdirAll(dir, 0755); err != nil {
   919  		t.Fatalf("Failed to create config directory %q: %v", dir, err)
   920  	}
   921  	path := filepath.Join(dir, filename)
   922  	os.Remove(path)
   923  	if err := ioutil.WriteFile(path, []byte(contents), 0644); err != nil {
   924  		t.Fatalf("Failed to write file %q: %v", path, err)
   925  	}
   926  }
   927  
   928  func verifyHostFile(t *testing.T, tmpdir, filename string, i int, expectedResult string) {
   929  	filePath := path.Join(tmpdir, filename)
   930  	defer os.Remove(filePath)
   931  
   932  	// Verify the file is written to host.
   933  	if strings.Contains(expectedResult, "host:") {
   934  		data, err := ioutil.ReadFile(filePath)
   935  		if err != nil {
   936  			t.Fatalf("%d: Cannot read the host file: %v", i, err)
   937  		}
   938  		if string(data) != expectedResult {
   939  			t.Fatalf("%d: Expecting %q in the host file, but saw %q", i, expectedResult, data)
   940  		}
   941  	}
   942  }
   943  
   944  func executeFuncsReverse(funcs []func()) {
   945  	n := len(funcs)
   946  	for i := n - 1; i >= 0; i-- {
   947  		funcs[i]()
   948  	}
   949  }
   950  
   951  func unmountPod(t *testing.T, ctx *testutils.RktRunCtx, uuid string, rmNetns bool) {
   952  	podDir := filepath.Join(ctx.DataDir(), "pods", "run", uuid)
   953  	stage1MntPath := filepath.Join(podDir, "stage1", "rootfs")
   954  	stage2MntPath := filepath.Join(stage1MntPath, "opt", "stage2", "rkt-inspect", "rootfs")
   955  
   956  	netnsPath := filepath.Join(podDir, "netns")
   957  	podNetNSPathBytes, err := ioutil.ReadFile(netnsPath)
   958  	// There may be no netns, e.g. kvm or --net=host
   959  	if err != nil {
   960  		if !os.IsNotExist(err) {
   961  			t.Fatalf(`cannot read "netns" stage1: %v`, err)
   962  		} else {
   963  			rmNetns = false
   964  		}
   965  	}
   966  
   967  	if err := syscall.Unmount(stage2MntPath, 0); err != nil {
   968  		t.Fatalf("cannot umount stage2: %v", err)
   969  	}
   970  
   971  	if err := syscall.Unmount(stage1MntPath, 0); err != nil {
   972  		t.Fatalf("cannot umount stage1: %v", err)
   973  	}
   974  
   975  	if rmNetns {
   976  		podNetNSPath := string(podNetNSPathBytes)
   977  
   978  		if err := syscall.Unmount(podNetNSPath, 0); err != nil {
   979  			t.Fatalf("cannot umount pod netns: %v", err)
   980  		}
   981  
   982  		_ = os.RemoveAll(podNetNSPath)
   983  	}
   984  }
   985  
   986  func checkExitStatus(child *gexpect.ExpectSubprocess) error {
   987  	err := child.Wait()
   988  	status, _ := common.GetExitStatus(err)
   989  	if status != 0 {
   990  		return fmt.Errorf("rkt terminated with unexpected status %d, expected %d\nOutput:\n%s", status, 0, child.Collect())
   991  	}
   992  
   993  	return nil
   994  }
   995  
   996  // combinedOutput executes the given command c for the given test context t
   997  // and fails test t if command execution failed.
   998  // It returns the command output.
   999  func combinedOutput(t *testing.T, c *exec.Cmd) string {
  1000  	t.Log("Running", c.Args)
  1001  	out, err := c.CombinedOutput()
  1002  
  1003  	if err != nil {
  1004  		t.Fatal(err, "output", string(out))
  1005  	}
  1006  
  1007  	return string(out)
  1008  }
  1009  
  1010  // retry is the struct that represents retrying function calls.
  1011  type retry struct {
  1012  	n int
  1013  	t time.Duration
  1014  }
  1015  
  1016  // Retry retries the given function f n times with a delay t between invocations
  1017  // until no error is returned from f or n is exceeded.
  1018  // The last occurred error is returned.
  1019  func (r retry) Retry(f func() error) error {
  1020  	var err error
  1021  
  1022  	for i := 0; i < r.n; i++ {
  1023  		err = f()
  1024  		if err == nil {
  1025  			return nil
  1026  		}
  1027  		time.Sleep(r.t)
  1028  	}
  1029  
  1030  	return err
  1031  }