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 }