gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/runsc/container/container_test.go (about) 1 // Copyright 2018 The gVisor 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 container 16 17 import ( 18 "bytes" 19 "fmt" 20 "io" 21 "io/ioutil" 22 "math" 23 "math/rand" 24 "os" 25 "os/exec" 26 "path" 27 "path/filepath" 28 "reflect" 29 "runtime" 30 "slices" 31 "strconv" 32 "strings" 33 "testing" 34 "time" 35 36 "github.com/cenkalti/backoff" 37 specs "github.com/opencontainers/runtime-spec/specs-go" 38 "golang.org/x/sys/unix" 39 "gvisor.dev/gvisor/pkg/abi/linux" 40 "gvisor.dev/gvisor/pkg/bits" 41 "gvisor.dev/gvisor/pkg/log" 42 "gvisor.dev/gvisor/pkg/sentry/control" 43 "gvisor.dev/gvisor/pkg/sentry/fsimpl/erofs" 44 "gvisor.dev/gvisor/pkg/sentry/kernel" 45 "gvisor.dev/gvisor/pkg/sentry/kernel/auth" 46 "gvisor.dev/gvisor/pkg/sentry/pgalloc" 47 "gvisor.dev/gvisor/pkg/sentry/platform" 48 "gvisor.dev/gvisor/pkg/state/statefile" 49 "gvisor.dev/gvisor/pkg/sync" 50 "gvisor.dev/gvisor/pkg/test/testutil" 51 "gvisor.dev/gvisor/runsc/boot" 52 "gvisor.dev/gvisor/runsc/cgroup" 53 "gvisor.dev/gvisor/runsc/config" 54 "gvisor.dev/gvisor/runsc/flag" 55 "gvisor.dev/gvisor/runsc/specutils" 56 ) 57 58 func TestMain(m *testing.M) { 59 config.RegisterFlags(flag.CommandLine) 60 log.SetLevel(log.Debug) 61 if err := testutil.ConfigureExePath(); err != nil { 62 panic(err.Error()) 63 } 64 if err := specutils.MaybeRunAsRoot(); err != nil { 65 fmt.Fprintf(os.Stderr, "Error running as root: %v", err) 66 os.Exit(123) 67 } 68 os.Exit(m.Run()) 69 } 70 71 func execute(conf *config.Config, cont *Container, name string, arg ...string) (unix.WaitStatus, error) { 72 args := &control.ExecArgs{ 73 Filename: name, 74 Argv: append([]string{name}, arg...), 75 } 76 return cont.executeSync(conf, args) 77 } 78 79 // executeCombinedOutput executes a process in the container and captures 80 // stdout and stderr. If execFile is supplied, a host file will be executed. 81 // Otherwise, the name argument is used to resolve the executable in the guest. 82 func executeCombinedOutput(conf *config.Config, cont *Container, execFile *os.File, name string, arg ...string) ([]byte, error) { 83 r, w, err := os.Pipe() 84 if err != nil { 85 return nil, err 86 } 87 defer r.Close() 88 89 // Unset the filename when we execute via FD. 90 if execFile != nil { 91 name = "" 92 } 93 args := &control.ExecArgs{ 94 Filename: name, 95 Argv: append([]string{name}, arg...), 96 FilePayload: control.NewFilePayload(map[int]*os.File{ 97 0: os.Stdin, 1: w, 2: w, 98 }, execFile), 99 } 100 ws, err := cont.executeSync(conf, args) 101 w.Close() 102 if err != nil { 103 return nil, err 104 } 105 out, err := ioutil.ReadAll(r) 106 switch { 107 case ws != 0 && err != nil: 108 err = fmt.Errorf("exec failed, status: %v, ioutil.ReadAll failed: %v", ws, err) 109 case ws != 0: 110 err = fmt.Errorf("exec failed, status: %v", ws) 111 } 112 return out, err 113 } 114 115 // executeSync synchronously executes a new process. 116 func (c *Container) executeSync(conf *config.Config, args *control.ExecArgs) (unix.WaitStatus, error) { 117 pid, err := c.Execute(conf, args) 118 if err != nil { 119 return 0, fmt.Errorf("error executing: %v", err) 120 } 121 ws, err := c.WaitPID(pid) 122 if err != nil { 123 return 0, fmt.Errorf("error waiting: %v", err) 124 } 125 return ws, nil 126 } 127 128 // waitForProcessList waits for the given process list to show up in the container. 129 func waitForProcessList(cont *Container, want []*control.Process) error { 130 cb := func() error { 131 got, err := cont.Processes() 132 if err != nil { 133 err = fmt.Errorf("error getting process data from container: %w", err) 134 return &backoff.PermanentError{Err: err} 135 } 136 if !procListsEqual(got, want) { 137 return fmt.Errorf("container got process list: %s, want: %s", procListToString(got), procListToString(want)) 138 } 139 return nil 140 } 141 // Gives plenty of time as tests can run slow under --race. 142 return testutil.Poll(cb, 30*time.Second) 143 } 144 145 // waitForProcess waits for the given process to show up in the container. 146 func waitForProcess(cont *Container, want *control.Process) error { 147 cb := func() error { 148 gots, err := cont.Processes() 149 if err != nil { 150 err = fmt.Errorf("error getting process data from container: %w", err) 151 return &backoff.PermanentError{Err: err} 152 } 153 for _, got := range gots { 154 if procEqual(got, want) { 155 return nil 156 } 157 } 158 return fmt.Errorf("container got process list: %s, want: %+v", procListToString(gots), want) 159 } 160 // Gives plenty of time as tests can run slow under --race. 161 return testutil.Poll(cb, 30*time.Second) 162 } 163 164 func waitForProcessCount(cont *Container, want int) error { 165 cb := func() error { 166 pss, err := cont.Processes() 167 if err != nil { 168 err = fmt.Errorf("error getting process data from container: %w", err) 169 return &backoff.PermanentError{Err: err} 170 } 171 if got := len(pss); got != want { 172 log.Infof("Waiting for process count to reach %d. Current: %d", want, got) 173 return fmt.Errorf("wrong process count, got: %d, want: %d", got, want) 174 } 175 return nil 176 } 177 // Gives plenty of time as tests can run slow under --race. 178 return testutil.Poll(cb, 30*time.Second) 179 } 180 181 func blockUntilWaitable(pid int) error { 182 _, _, err := specutils.RetryEintr(func() (uintptr, uintptr, error) { 183 var err error 184 _, _, err1 := unix.Syscall6(unix.SYS_WAITID, 1, uintptr(pid), 0, unix.WEXITED|unix.WNOWAIT, 0, 0) 185 if err1 != 0 { 186 err = err1 187 } 188 return 0, 0, err 189 }) 190 return err 191 } 192 193 // execPS executes `ps` inside the container and return the processes. 194 func execPS(conf *config.Config, c *Container) ([]*control.Process, error) { 195 out, err := executeCombinedOutput(conf, c, nil, "/bin/ps", "-e") 196 if err != nil { 197 return nil, err 198 } 199 lines := strings.Split(string(out), "\n") 200 if len(lines) < 1 { 201 return nil, fmt.Errorf("missing header: %q", lines) 202 } 203 procs := make([]*control.Process, 0, len(lines)-1) 204 for _, line := range lines[1:] { 205 if len(line) == 0 { 206 continue 207 } 208 fields := strings.Fields(line) 209 if len(fields) != 4 { 210 return nil, fmt.Errorf("malformed line: %s", line) 211 } 212 pid, err := strconv.Atoi(fields[0]) 213 if err != nil { 214 return nil, err 215 } 216 cmd := fields[3] 217 // Fill only the fields we need thus far. 218 procs = append(procs, &control.Process{ 219 PID: kernel.ThreadID(pid), 220 Cmd: cmd, 221 }) 222 } 223 return procs, nil 224 } 225 226 // procListsEqual is used to check whether 2 Process lists are equal. Fields 227 // set to -1 in wants are ignored. Timestamp and threads fields are always 228 // ignored. 229 func procListsEqual(gots, wants []*control.Process) bool { 230 if len(gots) != len(wants) { 231 return false 232 } 233 for i := range gots { 234 if !procEqual(gots[i], wants[i]) { 235 return false 236 } 237 } 238 return true 239 } 240 241 func procEqual(got, want *control.Process) bool { 242 if want.UID != math.MaxUint32 && want.UID != got.UID { 243 return false 244 } 245 if want.PID != -1 && want.PID != got.PID { 246 return false 247 } 248 if want.PPID != -1 && want.PPID != got.PPID { 249 return false 250 } 251 if len(want.TTY) != 0 && want.TTY != got.TTY { 252 return false 253 } 254 if len(want.Cmd) != 0 && want.Cmd != got.Cmd { 255 return false 256 } 257 return true 258 } 259 260 type processBuilder struct { 261 process control.Process 262 } 263 264 func newProcessBuilder() *processBuilder { 265 return &processBuilder{ 266 process: control.Process{ 267 UID: math.MaxUint32, 268 PID: -1, 269 PPID: -1, 270 }, 271 } 272 } 273 274 func (p *processBuilder) Cmd(cmd string) *processBuilder { 275 p.process.Cmd = cmd 276 return p 277 } 278 279 func (p *processBuilder) PID(pid kernel.ThreadID) *processBuilder { 280 p.process.PID = pid 281 return p 282 } 283 284 func (p *processBuilder) PPID(ppid kernel.ThreadID) *processBuilder { 285 p.process.PPID = ppid 286 return p 287 } 288 289 func (p *processBuilder) UID(uid auth.KUID) *processBuilder { 290 p.process.UID = uid 291 return p 292 } 293 294 func (p *processBuilder) Process() *control.Process { 295 return &p.process 296 } 297 298 func procListToString(pl []*control.Process) string { 299 strs := make([]string, 0, len(pl)) 300 for _, p := range pl { 301 strs = append(strs, fmt.Sprintf("%+v", p)) 302 } 303 return fmt.Sprintf("[%s]", strings.Join(strs, ",")) 304 } 305 306 // createWriteableOutputFile creates an output file that can be read and 307 // written to in the sandbox. 308 func createWriteableOutputFile(path string) (*os.File, error) { 309 outputFile, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666) 310 if err != nil { 311 return nil, fmt.Errorf("error creating file: %q, %v", path, err) 312 } 313 314 // Chmod to allow writing after umask. 315 if err := outputFile.Chmod(0666); err != nil { 316 return nil, fmt.Errorf("error chmoding file: %q, %v", path, err) 317 } 318 return outputFile, nil 319 } 320 321 func waitForFileNotEmpty(f *os.File) error { 322 op := func() error { 323 fi, err := f.Stat() 324 if err != nil { 325 return err 326 } 327 if fi.Size() == 0 { 328 return fmt.Errorf("file %q is empty", f.Name()) 329 } 330 return nil 331 } 332 333 return testutil.Poll(op, 30*time.Second) 334 } 335 336 func waitForFileExist(path string) error { 337 op := func() error { 338 if _, err := os.Stat(path); os.IsNotExist(err) { 339 return err 340 } 341 return nil 342 } 343 344 return testutil.Poll(op, 30*time.Second) 345 } 346 347 // readOutputNum reads a file at given filepath and returns the int at the 348 // requested position. 349 func readOutputNum(file string, position int) (int, error) { 350 f, err := os.Open(file) 351 if err != nil { 352 return 0, fmt.Errorf("error opening file: %q, %v", file, err) 353 } 354 355 // Ensure that there is content in output file. 356 if err := waitForFileNotEmpty(f); err != nil { 357 return 0, fmt.Errorf("error waiting for output file: %v", err) 358 } 359 360 b, err := ioutil.ReadAll(f) 361 if err != nil { 362 return 0, fmt.Errorf("error reading file: %v", err) 363 } 364 if len(b) == 0 { 365 return 0, fmt.Errorf("error no content was read") 366 } 367 368 // Strip leading null bytes caused by file offset not being 0 upon restore. 369 b = bytes.Trim(b, "\x00") 370 nums := strings.Split(string(b), "\n") 371 372 if position >= len(nums) { 373 return 0, fmt.Errorf("position %v is not within the length of content %v", position, nums) 374 } 375 if position == -1 { 376 // Expectation of newline at the end of last position. 377 position = len(nums) - 2 378 } 379 num, err := strconv.Atoi(nums[position]) 380 if err != nil { 381 return 0, fmt.Errorf("error getting number from file: %v", err) 382 } 383 return num, nil 384 } 385 386 // run starts the sandbox and waits for it to exit, checking that the 387 // application succeeded. 388 func run(spec *specs.Spec, conf *config.Config) error { 389 _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 390 if err != nil { 391 return fmt.Errorf("error setting up container: %v", err) 392 } 393 defer cleanup() 394 395 // Create, start and wait for the container. 396 args := Args{ 397 ID: testutil.RandomContainerID(), 398 Spec: spec, 399 BundleDir: bundleDir, 400 Attached: true, 401 } 402 ws, err := Run(conf, args) 403 if err != nil { 404 return fmt.Errorf("running container: %v", err) 405 } 406 if !ws.Exited() || ws.ExitStatus() != 0 { 407 return fmt.Errorf("container failed, waitStatus: %v", ws) 408 } 409 return nil 410 } 411 412 // platforms must be provided by the BUILD rule, or all platforms are included. 413 var platforms = flag.String("test_platforms", os.Getenv("TEST_PLATFORMS"), "Platforms to test with.") 414 415 // configs generates different configurations to run tests. 416 func configs(t *testing.T, noOverlay bool) map[string]*config.Config { 417 var ps []string 418 if *platforms == "" { 419 ps = platform.List() 420 } else { 421 ps = strings.Split(*platforms, ",") 422 } 423 424 // Non-overlay versions. 425 cs := make(map[string]*config.Config) 426 for _, p := range ps { 427 c := testutil.TestConfig(t) 428 c.Overlay2.Set("none") 429 c.Platform = p 430 cs[p] = c 431 } 432 433 // Overlay versions. 434 if !noOverlay { 435 for _, p := range ps { 436 c := testutil.TestConfig(t) 437 c.Platform = p 438 c.Overlay2.Set("all:memory") 439 cs[p+"-overlay"] = c 440 } 441 } 442 443 return cs 444 } 445 446 // sleepSpec generates a spec with sleep 1000 and a conf. 447 func sleepSpecConf(t *testing.T) (*specs.Spec, *config.Config) { 448 return testutil.NewSpecWithArgs("sleep", "1000"), testutil.TestConfig(t) 449 } 450 451 // TestLifecycle tests the basic Create/Start/Signal/Destroy container lifecycle. 452 // It verifies after each step that the container can be loaded from disk, and 453 // has the correct status. 454 func TestLifecycle(t *testing.T) { 455 // Start the child reaper. 456 childReaper := &testutil.Reaper{} 457 childReaper.Start() 458 defer childReaper.Stop() 459 460 for name, conf := range configs(t, false /* noOverlay */) { 461 t.Run(name, func(t *testing.T) { 462 // The container will just sleep for a long time. We will kill it before 463 // it finishes sleeping. 464 spec, _ := sleepSpecConf(t) 465 466 rootDir, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 467 if err != nil { 468 t.Fatalf("error setting up container: %v", err) 469 } 470 defer cleanup() 471 472 // expectedPL lists the expected process state of the container. 473 expectedPL := []*control.Process{ 474 newProcessBuilder().Cmd("sleep").Process(), 475 } 476 // Create the container. 477 args := Args{ 478 ID: testutil.RandomContainerID(), 479 Spec: spec, 480 BundleDir: bundleDir, 481 } 482 c, err := New(conf, args) 483 if err != nil { 484 t.Fatalf("error creating container: %v", err) 485 } 486 defer c.Destroy() 487 488 // Load the container from disk and check the status. 489 c, err = Load(rootDir, FullID{ContainerID: args.ID}, LoadOpts{}) 490 if err != nil { 491 t.Fatalf("error loading container: %v", err) 492 } 493 if got, want := c.Status, Created; got != want { 494 t.Errorf("container status got %v, want %v", got, want) 495 } 496 497 // List should return the container id. 498 ids, err := List(rootDir) 499 if err != nil { 500 t.Fatalf("error listing containers: %v", err) 501 } 502 fullID := FullID{ 503 SandboxID: args.ID, 504 ContainerID: args.ID, 505 } 506 if got, want := ids, []FullID{fullID}; !slices.Equal(got, want) { 507 t.Errorf("container list got %v, want %v", got, want) 508 } 509 510 // Start the container. 511 if err := c.Start(conf); err != nil { 512 t.Fatalf("error starting container: %v", err) 513 } 514 515 // Load the container from disk and check the status. 516 c, err = Load(rootDir, fullID, LoadOpts{Exact: true}) 517 if err != nil { 518 t.Fatalf("error loading container: %v", err) 519 } 520 if got, want := c.Status, Running; got != want { 521 t.Errorf("container status got %v, want %v", got, want) 522 } 523 524 // Verify that "sleep 100" is running. 525 if err := waitForProcessList(c, expectedPL); err != nil { 526 t.Error(err) 527 } 528 529 // Wait on the container. 530 ch := make(chan error) 531 go func() { 532 ws, err := c.Wait() 533 if err != nil { 534 ch <- err 535 return 536 } 537 if got, want := ws.Signal(), unix.SIGTERM; got != want { 538 ch <- fmt.Errorf("got signal %v, want %v", got, want) 539 return 540 } 541 ch <- nil 542 }() 543 544 // Wait a bit to ensure that we've started waiting on 545 // the container before we signal. 546 time.Sleep(time.Second) 547 548 // Send the container a SIGTERM which will cause it to stop. 549 if err := c.SignalContainer(unix.SIGTERM, false); err != nil { 550 t.Fatalf("error sending signal %v to container: %v", unix.SIGTERM, err) 551 } 552 553 // Wait for it to die. 554 if err := <-ch; err != nil { 555 t.Fatalf("error waiting for container: %v", err) 556 } 557 558 // Load the container from disk and check the status. 559 c, err = Load(rootDir, fullID, LoadOpts{Exact: true}) 560 if err != nil { 561 t.Fatalf("error loading container: %v", err) 562 } 563 if got, want := c.Status, Stopped; got != want { 564 t.Errorf("container status got %v, want %v", got, want) 565 } 566 567 // Destroy the container. 568 if err := c.Destroy(); err != nil { 569 t.Fatalf("error destroying container: %v", err) 570 } 571 572 // List should not return the container id. 573 ids, err = List(rootDir) 574 if err != nil { 575 t.Fatalf("error listing containers: %v", err) 576 } 577 if len(ids) != 0 { 578 t.Errorf("expected container list to be empty, but got %v", ids) 579 } 580 581 // Loading the container by id should fail. 582 if _, err = Load(rootDir, fullID, LoadOpts{Exact: true}); err == nil { 583 t.Errorf("expected loading destroyed container to fail, but it did not") 584 } 585 }) 586 } 587 } 588 589 // Test the we can execute the application with different path formats. 590 func TestExePath(t *testing.T) { 591 // Create two directories that will be prepended to PATH. 592 firstPath, err := ioutil.TempDir(testutil.TmpDir(), "first") 593 if err != nil { 594 t.Fatalf("error creating temporary directory: %v", err) 595 } 596 defer os.RemoveAll(firstPath) 597 secondPath, err := ioutil.TempDir(testutil.TmpDir(), "second") 598 if err != nil { 599 t.Fatalf("error creating temporary directory: %v", err) 600 } 601 defer os.RemoveAll(secondPath) 602 603 // Create two minimal executables in the second path, two of which 604 // will be masked by files in first path. 605 for _, p := range []string{"unmasked", "masked1", "masked2"} { 606 path := filepath.Join(secondPath, p) 607 f, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0777) 608 if err != nil { 609 t.Fatalf("error opening path: %v", err) 610 } 611 defer f.Close() 612 if _, err := io.WriteString(f, "#!/bin/true\n"); err != nil { 613 t.Fatalf("error writing contents: %v", err) 614 } 615 } 616 617 // Create a non-executable file in the first path which masks a healthy 618 // executable in the second. 619 nonExecutable := filepath.Join(firstPath, "masked1") 620 f2, err := os.OpenFile(nonExecutable, os.O_CREATE|os.O_EXCL, 0666) 621 if err != nil { 622 t.Fatalf("error opening file: %v", err) 623 } 624 f2.Close() 625 626 // Create a non-regular file in the first path which masks a healthy 627 // executable in the second. 628 nonRegular := filepath.Join(firstPath, "masked2") 629 if err := os.Mkdir(nonRegular, 0777); err != nil { 630 t.Fatalf("error making directory: %v", err) 631 } 632 633 defaultConf := testutil.TestConfig(t) 634 defaultConf.Overlay2.Set("none") 635 overlayConf := testutil.TestConfig(t) 636 overlayConf.Overlay2.Set("all:memory") 637 configs := map[string]*config.Config{ 638 "default": defaultConf, 639 "overlay": overlayConf, 640 } 641 642 for name, conf := range configs { 643 t.Run(name, func(t *testing.T) { 644 for _, test := range []struct { 645 path string 646 success bool 647 }{ 648 {path: "true", success: true}, 649 {path: "bin/true", success: true}, 650 {path: "/bin/true", success: true}, 651 {path: "thisfiledoesntexit", success: false}, 652 {path: "bin/thisfiledoesntexit", success: false}, 653 {path: "/bin/thisfiledoesntexit", success: false}, 654 655 {path: "unmasked", success: true}, 656 {path: filepath.Join(firstPath, "unmasked"), success: false}, 657 {path: filepath.Join(secondPath, "unmasked"), success: true}, 658 659 {path: "masked1", success: true}, 660 {path: filepath.Join(firstPath, "masked1"), success: false}, 661 {path: filepath.Join(secondPath, "masked1"), success: true}, 662 663 {path: "masked2", success: true}, 664 {path: filepath.Join(firstPath, "masked2"), success: false}, 665 {path: filepath.Join(secondPath, "masked2"), success: true}, 666 } { 667 name := fmt.Sprintf("path=%s,success=%t", test.path, test.success) 668 t.Run(name, func(t *testing.T) { 669 spec := testutil.NewSpecWithArgs(test.path) 670 spec.Process.Env = []string{ 671 fmt.Sprintf("PATH=%s:%s:%s", firstPath, secondPath, os.Getenv("PATH")), 672 } 673 674 err := run(spec, conf) 675 if test.success { 676 if err != nil { 677 t.Errorf("exec: error running container: %v", err) 678 } 679 } else if err == nil { 680 t.Errorf("exec: got: no error, want: error") 681 } 682 }) 683 } 684 }) 685 } 686 } 687 688 // Test the we can retrieve the application exit status from the container. 689 func TestAppExitStatus(t *testing.T) { 690 // First container will succeed. 691 succSpec := testutil.NewSpecWithArgs("true") 692 conf := testutil.TestConfig(t) 693 _, bundleDir, cleanup, err := testutil.SetupContainer(succSpec, conf) 694 if err != nil { 695 t.Fatalf("error setting up container: %v", err) 696 } 697 defer cleanup() 698 699 args := Args{ 700 ID: testutil.RandomContainerID(), 701 Spec: succSpec, 702 BundleDir: bundleDir, 703 Attached: true, 704 } 705 ws, err := Run(conf, args) 706 if err != nil { 707 t.Fatalf("error running container: %v", err) 708 } 709 if ws.ExitStatus() != 0 { 710 t.Errorf("got exit status %v want %v", ws.ExitStatus(), 0) 711 } 712 713 // Second container exits with non-zero status. 714 wantStatus := 123 715 errSpec := testutil.NewSpecWithArgs("bash", "-c", fmt.Sprintf("exit %d", wantStatus)) 716 717 _, bundleDir2, cleanup2, err := testutil.SetupContainer(errSpec, conf) 718 if err != nil { 719 t.Fatalf("error setting up container: %v", err) 720 } 721 defer cleanup2() 722 723 args2 := Args{ 724 ID: testutil.RandomContainerID(), 725 Spec: errSpec, 726 BundleDir: bundleDir2, 727 Attached: true, 728 } 729 ws, err = Run(conf, args2) 730 if err != nil { 731 t.Fatalf("error running container: %v", err) 732 } 733 if ws.ExitStatus() != wantStatus { 734 t.Errorf("got exit status %v want %v", ws.ExitStatus(), wantStatus) 735 } 736 } 737 738 // TestExec verifies that a container can exec a new program. 739 func TestExec(t *testing.T) { 740 for name, conf := range configs(t, false /* noOverlay */) { 741 t.Run(name, func(t *testing.T) { 742 dir, err := ioutil.TempDir(testutil.TmpDir(), "exec-test") 743 if err != nil { 744 t.Fatalf("error creating temporary directory: %v", err) 745 } 746 // Note that some shells may exec the final command in a sequence as 747 // an optimization. We avoid this here by adding the exit 0. 748 cmd := fmt.Sprintf("ln -s /bin/true %q/symlink && sleep 100 && exit 0", dir) 749 spec := testutil.NewSpecWithArgs("sh", "-c", cmd) 750 751 _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 752 if err != nil { 753 t.Fatalf("error setting up container: %v", err) 754 } 755 defer cleanup() 756 757 // Create and start the container. 758 args := Args{ 759 ID: testutil.RandomContainerID(), 760 Spec: spec, 761 BundleDir: bundleDir, 762 } 763 cont, err := New(conf, args) 764 if err != nil { 765 t.Fatalf("error creating container: %v", err) 766 } 767 defer cont.Destroy() 768 if err := cont.Start(conf); err != nil { 769 t.Fatalf("error starting container: %v", err) 770 } 771 772 // Wait until sleep is running to ensure the symlink was created. 773 expectedPL := []*control.Process{ 774 newProcessBuilder().Cmd("sh").Process(), 775 newProcessBuilder().Cmd("sleep").Process(), 776 } 777 if err := waitForProcessList(cont, expectedPL); err != nil { 778 t.Fatalf("waitForProcessList: %v", err) 779 } 780 781 for _, tc := range []struct { 782 name string 783 args control.ExecArgs 784 }{ 785 { 786 name: "complete", 787 args: control.ExecArgs{ 788 Filename: "/bin/true", 789 Argv: []string{"/bin/true"}, 790 }, 791 }, 792 { 793 name: "filename", 794 args: control.ExecArgs{ 795 Filename: "/bin/true", 796 }, 797 }, 798 { 799 name: "argv", 800 args: control.ExecArgs{ 801 Argv: []string{"/bin/true"}, 802 }, 803 }, 804 { 805 name: "filename resolution", 806 args: control.ExecArgs{ 807 Filename: "true", 808 Envv: []string{"PATH=/bin"}, 809 }, 810 }, 811 { 812 name: "argv resolution", 813 args: control.ExecArgs{ 814 Argv: []string{"true"}, 815 Envv: []string{"PATH=/bin"}, 816 }, 817 }, 818 { 819 name: "argv symlink", 820 args: control.ExecArgs{ 821 Argv: []string{filepath.Join(dir, "symlink")}, 822 }, 823 }, 824 { 825 name: "working dir", 826 args: control.ExecArgs{ 827 Argv: []string{"/bin/sh", "-c", `if [[ "${PWD}" != "/tmp" ]]; then exit 1; fi`}, 828 WorkingDirectory: "/tmp", 829 }, 830 }, 831 { 832 name: "user", 833 args: control.ExecArgs{ 834 Argv: []string{"/bin/sh", "-c", `if [[ "$(id -u)" != "343" ]]; then exit 1; fi`}, 835 KUID: 343, 836 }, 837 }, 838 { 839 name: "group", 840 args: control.ExecArgs{ 841 Argv: []string{"/bin/sh", "-c", `if [[ "$(id -g)" != "343" ]]; then exit 1; fi`}, 842 KGID: 343, 843 }, 844 }, 845 { 846 name: "env", 847 args: control.ExecArgs{ 848 Argv: []string{"/bin/sh", "-c", `if [[ "${FOO}" != "123" ]]; then exit 1; fi`}, 849 Envv: []string{"FOO=123"}, 850 }, 851 }, 852 } { 853 t.Run(tc.name, func(t *testing.T) { 854 // t.Parallel() 855 if ws, err := cont.executeSync(conf, &tc.args); err != nil { 856 t.Fatalf("executeAsync(%+v): %v", tc.args, err) 857 } else if ws != 0 { 858 t.Fatalf("executeAsync(%+v) failed with exit: %v", tc.args, ws) 859 } 860 }) 861 } 862 863 // Test for exec failure with an non-existent file. 864 t.Run("nonexist", func(t *testing.T) { 865 // b/179114837 found by Syzkaller that causes nil pointer panic when 866 // trying to dec-ref an unix socket FD. 867 fds, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM, 0) 868 if err != nil { 869 t.Fatal(err) 870 } 871 defer unix.Close(fds[0]) 872 873 _, err = cont.executeSync(conf, &control.ExecArgs{ 874 Argv: []string{"/nonexist"}, 875 FilePayload: control.NewFilePayload(map[int]*os.File{ 876 0: os.NewFile(uintptr(fds[1]), "sock"), 877 }, nil), 878 }) 879 want := "failed to load /nonexist" 880 if err == nil || !strings.Contains(err.Error(), want) { 881 t.Errorf("executeSync: want err containing %q; got err = %q", want, err) 882 } 883 }) 884 }) 885 } 886 } 887 888 // TestExecProcList verifies that a container can exec a new program and it 889 // shows correcly in the process list. 890 func TestExecProcList(t *testing.T) { 891 for name, conf := range configs(t, false /* noOverlay */) { 892 t.Run(name, func(t *testing.T) { 893 const uid = 343 894 spec, _ := sleepSpecConf(t) 895 896 _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 897 if err != nil { 898 t.Fatalf("error setting up container: %v", err) 899 } 900 defer cleanup() 901 902 // Create and start the container. 903 args := Args{ 904 ID: testutil.RandomContainerID(), 905 Spec: spec, 906 BundleDir: bundleDir, 907 } 908 cont, err := New(conf, args) 909 if err != nil { 910 t.Fatalf("error creating container: %v", err) 911 } 912 defer cont.Destroy() 913 if err := cont.Start(conf); err != nil { 914 t.Fatalf("error starting container: %v", err) 915 } 916 917 execArgs := &control.ExecArgs{ 918 Filename: "/bin/sleep", 919 Argv: []string{"/bin/sleep", "5"}, 920 WorkingDirectory: "/", 921 KUID: uid, 922 } 923 924 // Verify that "sleep 100" and "sleep 5" are running after exec. First, 925 // start running exec (which blocks). 926 ch := make(chan error) 927 go func() { 928 exitStatus, err := cont.executeSync(conf, execArgs) 929 if err != nil { 930 ch <- err 931 } else if exitStatus != 0 { 932 ch <- fmt.Errorf("failed with exit status: %v", exitStatus) 933 } else { 934 ch <- nil 935 } 936 }() 937 938 // expectedPL lists the expected process state of the container. 939 expectedPL := []*control.Process{ 940 newProcessBuilder().PID(1).PPID(0).Cmd("sleep").UID(0).Process(), 941 newProcessBuilder().PID(2).PPID(0).Cmd("sleep").UID(uid).Process(), 942 } 943 if err := waitForProcessList(cont, expectedPL); err != nil { 944 t.Fatalf("error waiting for processes: %v", err) 945 } 946 }) 947 } 948 } 949 950 // TestKillPid verifies that we can signal individual exec'd processes. 951 func TestKillPid(t *testing.T) { 952 for name, conf := range configs(t, false /* noOverlay */) { 953 t.Run(name, func(t *testing.T) { 954 app, err := testutil.FindFile("test/cmd/test_app/test_app") 955 if err != nil { 956 t.Fatal("error finding test_app:", err) 957 } 958 959 const nProcs = 4 960 spec := testutil.NewSpecWithArgs(app, "task-tree", "--depth", strconv.Itoa(nProcs-1), "--width=1", "--pause=true") 961 _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 962 if err != nil { 963 t.Fatalf("error setting up container: %v", err) 964 } 965 defer cleanup() 966 967 // Create and start the container. 968 args := Args{ 969 ID: testutil.RandomContainerID(), 970 Spec: spec, 971 BundleDir: bundleDir, 972 } 973 cont, err := New(conf, args) 974 if err != nil { 975 t.Fatalf("error creating container: %v", err) 976 } 977 defer cont.Destroy() 978 if err := cont.Start(conf); err != nil { 979 t.Fatalf("error starting container: %v", err) 980 } 981 982 // Verify that all processes are running. 983 if err := waitForProcessCount(cont, nProcs); err != nil { 984 t.Fatalf("timed out waiting for processes to start: %v", err) 985 } 986 987 // Kill the child process with the largest PID. 988 procs, err := cont.Processes() 989 if err != nil { 990 t.Fatalf("failed to get process list: %v", err) 991 } 992 t.Logf("current process list: %v", procs) 993 var pid int32 994 for _, p := range procs { 995 if pid < int32(p.PID) { 996 pid = int32(p.PID) 997 } 998 } 999 if err := cont.SignalProcess(unix.SIGKILL, pid); err != nil { 1000 t.Fatalf("failed to signal process %d: %v", pid, err) 1001 } 1002 1003 // Verify that one process is gone. 1004 if err := waitForProcessCount(cont, nProcs-1); err != nil { 1005 procs, procsErr := cont.Processes() 1006 t.Fatalf("error waiting for processes: %v; current processes: %v / %v", err, procs, procsErr) 1007 } 1008 1009 procs, err = cont.Processes() 1010 if err != nil { 1011 t.Fatalf("failed to get process list: %v", err) 1012 } 1013 for _, p := range procs { 1014 if pid == int32(p.PID) { 1015 t.Fatalf("pid %d is still alive, which should be killed", pid) 1016 } 1017 } 1018 }) 1019 } 1020 } 1021 1022 // testCheckpointRestore creates a container that continuously writes successive 1023 // integers to a file. To test checkpoint and restore functionality, the 1024 // container is checkpointed and the last number printed to the file is 1025 // recorded. Then, it is restored in two new containers and the first number 1026 // printed from these containers is checked. Both should be the next consecutive 1027 // number after the last number from the checkpointed container. 1028 func testCheckpointRestore(t *testing.T, conf *config.Config, compression statefile.CompressionLevel, newSpecWithScript func(string) *specs.Spec) { 1029 dir, err := ioutil.TempDir(testutil.TmpDir(), "checkpoint-test") 1030 if err != nil { 1031 t.Fatalf("ioutil.TempDir failed: %v", err) 1032 } 1033 defer os.RemoveAll(dir) 1034 if err := os.Chmod(dir, 0777); err != nil { 1035 t.Fatalf("error chmoding file: %q, %v", dir, err) 1036 } 1037 1038 outputPath := filepath.Join(dir, "output") 1039 outputFile, err := createWriteableOutputFile(outputPath) 1040 if err != nil { 1041 t.Fatalf("error creating output file: %v", err) 1042 } 1043 defer outputFile.Close() 1044 1045 script := fmt.Sprintf("i=0; while true; do echo $i >> %q; sleep 1; i=$((i+1)); done", outputPath) 1046 spec := newSpecWithScript(script) 1047 _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 1048 if err != nil { 1049 t.Fatalf("error setting up container: %v", err) 1050 } 1051 defer cleanup() 1052 1053 // Create and start the container. 1054 args := Args{ 1055 ID: testutil.RandomContainerID(), 1056 Spec: spec, 1057 BundleDir: bundleDir, 1058 } 1059 cont, err := New(conf, args) 1060 if err != nil { 1061 t.Fatalf("error creating container: %v", err) 1062 } 1063 defer cont.Destroy() 1064 if err := cont.Start(conf); err != nil { 1065 t.Fatalf("error starting container: %v", err) 1066 } 1067 1068 // Wait until application has ran. 1069 if err := waitForFileNotEmpty(outputFile); err != nil { 1070 t.Fatalf("Failed to wait for output file: %v", err) 1071 } 1072 1073 // Checkpoint running container; save state into new file. 1074 if err := cont.Checkpoint(dir, false /* direct */, statefile.Options{Compression: compression}, pgalloc.SaveOpts{}); err != nil { 1075 t.Fatalf("error checkpointing container to empty file: %v", err) 1076 } 1077 1078 lastNum, err := readOutputNum(outputPath, -1) 1079 if err != nil { 1080 t.Fatalf("error with outputFile: %v", err) 1081 } 1082 1083 // Delete and recreate file before restoring. 1084 if err := os.Remove(outputPath); err != nil { 1085 t.Fatalf("error removing file") 1086 } 1087 outputFile2, err := createWriteableOutputFile(outputPath) 1088 if err != nil { 1089 t.Fatalf("error creating output file: %v", err) 1090 } 1091 defer outputFile2.Close() 1092 1093 // Restore into a new container with different ID (e.g. clone). Keep the 1094 // initial container running to ensure no conflict with it. 1095 args2 := Args{ 1096 ID: testutil.RandomContainerID(), 1097 Spec: spec, 1098 BundleDir: bundleDir, 1099 } 1100 cont2, err := New(conf, args2) 1101 if err != nil { 1102 t.Fatalf("error creating container: %v", err) 1103 } 1104 defer cont2.Destroy() 1105 1106 if err := cont2.Restore(conf, dir, false /* direct */); err != nil { 1107 t.Fatalf("error restoring container: %v", err) 1108 } 1109 1110 // Wait until application has ran. 1111 if err := waitForFileNotEmpty(outputFile2); err != nil { 1112 t.Fatalf("Failed to wait for output file: %v", err) 1113 } 1114 1115 firstNum, err := readOutputNum(outputPath, 0) 1116 if err != nil { 1117 t.Fatalf("error with outputFile: %v", err) 1118 } 1119 1120 // Check that lastNum is one less than firstNum and that the container 1121 // picks up from where it left off. 1122 if lastNum+1 != firstNum { 1123 t.Errorf("error numbers not in order, previous: %d, next: %d", lastNum, firstNum) 1124 } 1125 cont2.Destroy() 1126 cont2 = nil 1127 1128 // Restore into a container using the same ID (e.g. save/resume). It requires 1129 // the original container to cease to exist because they share the same identity. 1130 cont.Destroy() 1131 cont = nil 1132 1133 // Delete and recreate file before restoring. 1134 if err := os.Remove(outputPath); err != nil { 1135 t.Fatalf("error removing file") 1136 } 1137 outputFile3, err := createWriteableOutputFile(outputPath) 1138 if err != nil { 1139 t.Fatalf("error creating output file: %v", err) 1140 } 1141 defer outputFile3.Close() 1142 1143 cont3, err := New(conf, args) 1144 if err != nil { 1145 t.Fatalf("error creating container: %v", err) 1146 } 1147 defer cont3.Destroy() 1148 1149 if err := cont3.Restore(conf, dir, false /* direct */); err != nil { 1150 t.Fatalf("error restoring container: %v", err) 1151 } 1152 1153 // Wait until application has ran. 1154 if err := waitForFileNotEmpty(outputFile3); err != nil { 1155 t.Fatalf("Failed to wait for output file: %v", err) 1156 } 1157 1158 firstNum2, err := readOutputNum(outputPath, 0) 1159 if err != nil { 1160 t.Fatalf("error with outputFile: %v", err) 1161 } 1162 1163 // Check that lastNum is one less than firstNum and that the container 1164 // picks up from where it left off. 1165 if lastNum+1 != firstNum2 { 1166 t.Errorf("error numbers not in order, previous: %d, next: %d", lastNum, firstNum2) 1167 } 1168 cont3.Destroy() 1169 } 1170 1171 // TestCheckpointRestore does the checkpoint/restore test on each platform. 1172 func TestCheckpointRestore(t *testing.T) { 1173 // Skip overlay because test requires writing to host file. 1174 for name, conf := range configs(t, true /* noOverlay */) { 1175 t.Run(name, func(t *testing.T) { 1176 compressionLevels := []statefile.CompressionLevel{ 1177 statefile.CompressionLevelNone, 1178 statefile.CompressionLevelFlateBestSpeed, 1179 } 1180 for _, compression := range compressionLevels { 1181 t.Run(string(compression), func(t *testing.T) { 1182 testCheckpointRestore(t, conf, compression, func(script string) *specs.Spec { 1183 return testutil.NewSpecWithArgs("bash", "-c", script) 1184 }) 1185 }) 1186 } 1187 }) 1188 } 1189 } 1190 1191 // TestCheckpointRestoreExecKilled checks that exec'd processes are killed 1192 // after the container is restored. 1193 func TestCheckpointRestoreExecKilled(t *testing.T) { 1194 spec, conf := sleepSpecConf(t) 1195 _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 1196 if err != nil { 1197 t.Fatalf("error setting up container: %v", err) 1198 } 1199 defer cleanup() 1200 1201 // Create and start the container. 1202 args := Args{ 1203 ID: testutil.RandomContainerID(), 1204 Spec: spec, 1205 BundleDir: bundleDir, 1206 } 1207 cont, err := New(conf, args) 1208 if err != nil { 1209 t.Fatalf("error creating container: %v", err) 1210 } 1211 defer cont.Destroy() 1212 if err := cont.Start(conf); err != nil { 1213 t.Fatalf("error starting container: %v", err) 1214 } 1215 1216 execArgs := &control.ExecArgs{ 1217 Filename: "/bin/sleep", 1218 Argv: []string{"/bin/sleep", "10000"}, 1219 } 1220 pid1, err := cont.Execute(conf, execArgs) 1221 if err != nil { 1222 t.Fatalf("error executing in container: %v", err) 1223 } 1224 pid2, err := cont.Execute(conf, execArgs) 1225 if err != nil { 1226 t.Fatalf("error executing in container: %v", err) 1227 } 1228 1229 // Since both share the same process name, ensure that the exec'd process 1230 // has a different PID than the init process. 1231 if pid1 == 1 || pid2 == 1 { 1232 t.Fatalf("exec'd PID cannot be 1") 1233 } 1234 // Wait until the init process and exec'd processes are present. 1235 expectedPL := []*control.Process{ 1236 newProcessBuilder().Cmd("sleep").PID(1).Process(), 1237 newProcessBuilder().Cmd("sleep").PID(kernel.ThreadID(pid1)).Process(), 1238 newProcessBuilder().Cmd("sleep").PID(kernel.ThreadID(pid2)).Process(), 1239 } 1240 if err := waitForProcessList(cont, expectedPL); err != nil { 1241 t.Fatalf("Failed to kill exec'ed process, err: %v", err) 1242 } 1243 1244 // Set the image path, which is where the checkpoint image will be saved. 1245 dir, err := ioutil.TempDir(testutil.TmpDir(), "checkpoint-test") 1246 if err != nil { 1247 t.Fatalf("ioutil.TempDir failed: %v", err) 1248 } 1249 defer os.RemoveAll(dir) 1250 if err := os.Chmod(dir, 0777); err != nil { 1251 t.Fatalf("error chmoding file: %q, %v", dir, err) 1252 } 1253 1254 // Checkpoint running container. 1255 if err := cont.Checkpoint(dir, false /* direct */, statefile.Options{Compression: statefile.CompressionLevelFlateBestSpeed}, pgalloc.SaveOpts{}); err != nil { 1256 t.Fatalf("error checkpointing container: %v", err) 1257 } 1258 cont.Destroy() 1259 cont = nil 1260 1261 cont2, err := New(conf, args) 1262 if err != nil { 1263 t.Fatalf("error creating container: %v", err) 1264 } 1265 defer cont2.Destroy() 1266 1267 if err := cont2.Restore(conf, dir, false /* direct */); err != nil { 1268 t.Fatalf("error restoring container: %v", err) 1269 } 1270 1271 // Check that only the init process is present and the exec'ed 1272 // processes were killed. 1273 expectedPL = []*control.Process{ 1274 newProcessBuilder().Cmd("sleep").PID(1).Process(), 1275 } 1276 if err := waitForProcessList(cont2, expectedPL); err != nil { 1277 t.Fatalf("Failed to kill exec'ed process, err: %v", err) 1278 } 1279 } 1280 1281 // TestUnixDomainSockets checks that Checkpoint/Restore works in cases 1282 // with filesystem Unix Domain Socket use. 1283 func TestUnixDomainSockets(t *testing.T) { 1284 // Skip overlay because test requires writing to host file. 1285 for name, conf := range configs(t, true /* noOverlay */) { 1286 t.Run(name, func(t *testing.T) { 1287 // UDS path is limited to 108 chars for compatibility with older systems. 1288 // Use '/tmp' (instead of testutil.TmpDir) to ensure the size limit is 1289 // not exceeded. Assumes '/tmp' exists in the system. 1290 dir, err := ioutil.TempDir("/tmp", "uds-test") 1291 if err != nil { 1292 t.Fatalf("ioutil.TempDir failed: %v", err) 1293 } 1294 defer os.RemoveAll(dir) 1295 1296 outputPath := filepath.Join(dir, "uds_output") 1297 outputFile, err := os.OpenFile(outputPath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666) 1298 if err != nil { 1299 t.Fatalf("error creating output file: %v", err) 1300 } 1301 defer outputFile.Close() 1302 1303 app, err := testutil.FindFile("test/cmd/test_app/test_app") 1304 if err != nil { 1305 t.Fatal("error finding test_app:", err) 1306 } 1307 1308 socketPath := filepath.Join(dir, "uds_socket") 1309 defer os.Remove(socketPath) 1310 1311 spec := testutil.NewSpecWithArgs(app, "uds", "--file", outputPath, "--socket", socketPath) 1312 spec.Process.User = specs.User{ 1313 UID: uint32(os.Getuid()), 1314 GID: uint32(os.Getgid()), 1315 } 1316 spec.Mounts = []specs.Mount{{ 1317 Type: "bind", 1318 Destination: dir, 1319 Source: dir, 1320 }} 1321 1322 _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 1323 if err != nil { 1324 t.Fatalf("error setting up container: %v", err) 1325 } 1326 defer cleanup() 1327 1328 // Create and start the container. 1329 args := Args{ 1330 ID: testutil.RandomContainerID(), 1331 Spec: spec, 1332 BundleDir: bundleDir, 1333 } 1334 cont, err := New(conf, args) 1335 if err != nil { 1336 t.Fatalf("error creating container: %v", err) 1337 } 1338 defer cont.Destroy() 1339 if err := cont.Start(conf); err != nil { 1340 t.Fatalf("error starting container: %v", err) 1341 } 1342 1343 // Wait until application has ran. 1344 if err := waitForFileNotEmpty(outputFile); err != nil { 1345 t.Fatalf("Failed to wait for output file: %v", err) 1346 } 1347 1348 // Checkpoint running container; save state into new file. 1349 if err := cont.Checkpoint(dir, false /* direct */, statefile.Options{Compression: statefile.CompressionLevelDefault}, pgalloc.SaveOpts{}); err != nil { 1350 t.Fatalf("error checkpointing container to empty file: %v", err) 1351 } 1352 1353 // Read last number outputted before checkpoint. 1354 lastNum, err := readOutputNum(outputPath, -1) 1355 if err != nil { 1356 t.Fatalf("error with outputFile: %v", err) 1357 } 1358 1359 // Delete and recreate file before restoring. 1360 if err := os.Remove(outputPath); err != nil { 1361 t.Fatalf("error removing file") 1362 } 1363 outputFile2, err := os.OpenFile(outputPath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666) 1364 if err != nil { 1365 t.Fatalf("error creating output file: %v", err) 1366 } 1367 defer outputFile2.Close() 1368 1369 // Restore into a new container. 1370 argsRestore := Args{ 1371 ID: testutil.RandomContainerID(), 1372 Spec: spec, 1373 BundleDir: bundleDir, 1374 } 1375 contRestore, err := New(conf, argsRestore) 1376 if err != nil { 1377 t.Fatalf("error creating container: %v", err) 1378 } 1379 defer contRestore.Destroy() 1380 1381 if err := contRestore.Restore(conf, dir, false /* direct */); err != nil { 1382 t.Fatalf("error restoring container: %v", err) 1383 } 1384 1385 // Wait until application has ran. 1386 if err := waitForFileNotEmpty(outputFile2); err != nil { 1387 t.Fatalf("Failed to wait for output file: %v", err) 1388 } 1389 1390 // Read first number outputted after restore. 1391 firstNum, err := readOutputNum(outputPath, 0) 1392 if err != nil { 1393 t.Fatalf("error with outputFile: %v", err) 1394 } 1395 1396 // Check that lastNum is one less than firstNum. 1397 if lastNum+1 != firstNum { 1398 t.Errorf("error numbers not consecutive, previous: %d, next: %d", lastNum, firstNum) 1399 } 1400 contRestore.Destroy() 1401 }) 1402 } 1403 } 1404 1405 // TestPauseResume tests that we can successfully pause and resume a container. 1406 // The container will keep touching a file to indicate it's running. The test 1407 // pauses the container, removes the file, and checks that it doesn't get 1408 // recreated. Then it resumes the container, verify that the file gets created 1409 // again. 1410 func TestPauseResume(t *testing.T) { 1411 for name, conf := range configs(t, true /* noOverlay */) { 1412 t.Run(name, func(t *testing.T) { 1413 tmpDir, err := ioutil.TempDir(testutil.TmpDir(), "lock") 1414 if err != nil { 1415 t.Fatalf("error creating temp dir: %v", err) 1416 } 1417 defer os.RemoveAll(tmpDir) 1418 1419 running := path.Join(tmpDir, "running") 1420 script := fmt.Sprintf("while [[ true ]]; do touch %q; sleep 0.1; done", running) 1421 spec := testutil.NewSpecWithArgs("/bin/bash", "-c", script) 1422 1423 _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 1424 if err != nil { 1425 t.Fatalf("error setting up container: %v", err) 1426 } 1427 defer cleanup() 1428 1429 // Create and start the container. 1430 args := Args{ 1431 ID: testutil.RandomContainerID(), 1432 Spec: spec, 1433 BundleDir: bundleDir, 1434 } 1435 cont, err := New(conf, args) 1436 if err != nil { 1437 t.Fatalf("error creating container: %v", err) 1438 } 1439 defer cont.Destroy() 1440 if err := cont.Start(conf); err != nil { 1441 t.Fatalf("error starting container: %v", err) 1442 } 1443 1444 // Wait until container starts running, observed by the existence of running 1445 // file. 1446 if err := waitForFileExist(running); err != nil { 1447 t.Errorf("error waiting for container to start: %v", err) 1448 } 1449 1450 // Pause the running container. 1451 if err := cont.Pause(); err != nil { 1452 t.Errorf("error pausing container: %v", err) 1453 } 1454 if got, want := cont.Status, Paused; got != want { 1455 t.Errorf("container status got %v, want %v", got, want) 1456 } 1457 1458 if err := os.Remove(running); err != nil { 1459 t.Fatalf("os.Remove(%q) failed: %v", running, err) 1460 } 1461 // Script touches the file every 100ms. Give a bit a time for it to run to 1462 // catch the case that pause didn't work. 1463 time.Sleep(200 * time.Millisecond) 1464 if _, err := os.Stat(running); !os.IsNotExist(err) { 1465 t.Fatalf("container did not pause: file exist check: %v", err) 1466 } 1467 1468 // Resume the running container. 1469 if err := cont.Resume(); err != nil { 1470 t.Errorf("error pausing container: %v", err) 1471 } 1472 if got, want := cont.Status, Running; got != want { 1473 t.Errorf("container status got %v, want %v", got, want) 1474 } 1475 1476 // Verify that the file is once again created by container. 1477 if err := waitForFileExist(running); err != nil { 1478 t.Fatalf("error resuming container: file exist check: %v", err) 1479 } 1480 }) 1481 } 1482 } 1483 1484 // TestPauseResumeStatus makes sure that the statuses are set correctly 1485 // with calls to pause and resume and that pausing and resuming only 1486 // occurs given the correct state. 1487 func TestPauseResumeStatus(t *testing.T) { 1488 spec, conf := sleepSpecConf(t) 1489 _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 1490 if err != nil { 1491 t.Fatalf("error setting up container: %v", err) 1492 } 1493 defer cleanup() 1494 1495 // Create and start the container. 1496 args := Args{ 1497 ID: testutil.RandomContainerID(), 1498 Spec: spec, 1499 BundleDir: bundleDir, 1500 } 1501 cont, err := New(conf, args) 1502 if err != nil { 1503 t.Fatalf("error creating container: %v", err) 1504 } 1505 defer cont.Destroy() 1506 if err := cont.Start(conf); err != nil { 1507 t.Fatalf("error starting container: %v", err) 1508 } 1509 1510 // Pause the running container. 1511 if err := cont.Pause(); err != nil { 1512 t.Errorf("error pausing container: %v", err) 1513 } 1514 if got, want := cont.Status, Paused; got != want { 1515 t.Errorf("container status got %v, want %v", got, want) 1516 } 1517 1518 // Try to Pause again. Should cause error. 1519 if err := cont.Pause(); err == nil { 1520 t.Errorf("error pausing container that was already paused: %v", err) 1521 } 1522 if got, want := cont.Status, Paused; got != want { 1523 t.Errorf("container status got %v, want %v", got, want) 1524 } 1525 1526 // Resume the running container. 1527 if err := cont.Resume(); err != nil { 1528 t.Errorf("error resuming container: %v", err) 1529 } 1530 if got, want := cont.Status, Running; got != want { 1531 t.Errorf("container status got %v, want %v", got, want) 1532 } 1533 1534 // Try to resume again. Should cause error. 1535 if err := cont.Resume(); err == nil { 1536 t.Errorf("error resuming container already running: %v", err) 1537 } 1538 if got, want := cont.Status, Running; got != want { 1539 t.Errorf("container status got %v, want %v", got, want) 1540 } 1541 } 1542 1543 // TestCapabilities verifies that: 1544 // - Running exec as non-root UID and GID will result in an error (because the 1545 // executable file can't be read). 1546 // - Running exec as non-root with CAP_DAC_OVERRIDE succeeds because it skips 1547 // this check. 1548 func TestCapabilities(t *testing.T) { 1549 // Pick uid/gid different than ours. 1550 uid := auth.KUID(os.Getuid() + 1) 1551 gid := auth.KGID(os.Getgid() + 1) 1552 1553 for name, conf := range configs(t, false /* noOverlay */) { 1554 t.Run(name, func(t *testing.T) { 1555 spec, _ := sleepSpecConf(t) 1556 rootDir, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 1557 if err != nil { 1558 t.Fatalf("error setting up container: %v", err) 1559 } 1560 defer cleanup() 1561 1562 // Create and start the container. 1563 args := Args{ 1564 ID: testutil.RandomContainerID(), 1565 Spec: spec, 1566 BundleDir: bundleDir, 1567 } 1568 cont, err := New(conf, args) 1569 if err != nil { 1570 t.Fatalf("error creating container: %v", err) 1571 } 1572 defer cont.Destroy() 1573 if err := cont.Start(conf); err != nil { 1574 t.Fatalf("error starting container: %v", err) 1575 } 1576 1577 // expectedPL lists the expected process state of the container. 1578 expectedPL := []*control.Process{ 1579 newProcessBuilder().Cmd("sleep").Process(), 1580 } 1581 if err := waitForProcessList(cont, expectedPL); err != nil { 1582 t.Fatalf("Failed to wait for sleep to start, err: %v", err) 1583 } 1584 1585 // Create an executable that can't be run with the specified UID:GID. 1586 // This shouldn't be callable within the container until we add the 1587 // CAP_DAC_OVERRIDE capability to skip the access check. 1588 exePath := filepath.Join(rootDir, "exe") 1589 if err := ioutil.WriteFile(exePath, []byte("#!/bin/sh\necho hello"), 0770); err != nil { 1590 t.Fatalf("couldn't create executable: %v", err) 1591 } 1592 defer os.Remove(exePath) 1593 1594 // Need to traverse the intermediate directory. 1595 if err := os.Chmod(rootDir, 0755); err != nil { 1596 t.Fatal(err) 1597 } 1598 1599 execArgs := &control.ExecArgs{ 1600 Filename: exePath, 1601 Argv: []string{exePath}, 1602 WorkingDirectory: "/", 1603 KUID: uid, 1604 KGID: gid, 1605 Capabilities: &auth.TaskCapabilities{}, 1606 } 1607 1608 // "exe" should fail because we don't have the necessary permissions. 1609 if _, err := cont.executeSync(conf, execArgs); err == nil { 1610 t.Fatalf("container executed without error, but an error was expected") 1611 } 1612 1613 // Now we run with the capability enabled and should succeed. 1614 execArgs.Capabilities = &auth.TaskCapabilities{ 1615 EffectiveCaps: auth.CapabilitySetOf(linux.CAP_DAC_OVERRIDE), 1616 } 1617 // "exe" should not fail this time. 1618 if _, err := cont.executeSync(conf, execArgs); err != nil { 1619 t.Fatalf("container failed to exec %v: %v", args, err) 1620 } 1621 }) 1622 } 1623 } 1624 1625 // TestRunNonRoot checks that sandbox can be configured when running as 1626 // non-privileged user. 1627 func TestRunNonRoot(t *testing.T) { 1628 for name, conf := range configs(t, true /* noOverlay */) { 1629 t.Run(name, func(t *testing.T) { 1630 spec := testutil.NewSpecWithArgs("/bin/true") 1631 1632 // Set a random user/group with no access to "blocked" dir. 1633 spec.Process.User.UID = 343 1634 spec.Process.User.GID = 2401 1635 spec.Process.Capabilities = nil 1636 1637 // User running inside container can't list '$TMP/blocked' and would fail to 1638 // mount it. 1639 dir, err := ioutil.TempDir(testutil.TmpDir(), "blocked") 1640 if err != nil { 1641 t.Fatalf("ioutil.TempDir() failed: %v", err) 1642 } 1643 if err := os.Chmod(dir, 0700); err != nil { 1644 t.Fatalf("os.MkDir(%q) failed: %v", dir, err) 1645 } 1646 dir = path.Join(dir, "test") 1647 if err := os.Mkdir(dir, 0755); err != nil { 1648 t.Fatalf("os.MkDir(%q) failed: %v", dir, err) 1649 } 1650 1651 src, err := ioutil.TempDir(testutil.TmpDir(), "src") 1652 if err != nil { 1653 t.Fatalf("ioutil.TempDir() failed: %v", err) 1654 } 1655 1656 spec.Mounts = append(spec.Mounts, specs.Mount{ 1657 Destination: dir, 1658 Source: src, 1659 Type: "bind", 1660 }) 1661 1662 if err := run(spec, conf); err != nil { 1663 t.Fatalf("error running sandbox: %v", err) 1664 } 1665 }) 1666 } 1667 } 1668 1669 // TestMountNewDir checks that runsc will create destination directory if it 1670 // doesn't exit. 1671 func TestMountNewDir(t *testing.T) { 1672 for name, conf := range configs(t, false /* noOverlay */) { 1673 t.Run(name, func(t *testing.T) { 1674 root, err := ioutil.TempDir(testutil.TmpDir(), "root") 1675 if err != nil { 1676 t.Fatal("ioutil.TempDir() failed:", err) 1677 } 1678 1679 srcDir := path.Join(root, "src", "dir", "anotherdir") 1680 if err := os.MkdirAll(srcDir, 0755); err != nil { 1681 t.Fatalf("os.MkDir(%q) failed: %v", srcDir, err) 1682 } 1683 1684 mountDir := path.Join(root, "dir", "anotherdir") 1685 1686 spec := testutil.NewSpecWithArgs("/bin/ls", mountDir) 1687 spec.Mounts = append(spec.Mounts, specs.Mount{ 1688 Destination: mountDir, 1689 Source: srcDir, 1690 Type: "bind", 1691 }) 1692 // Extra points for creating the mount with a readonly root. 1693 spec.Root.Readonly = true 1694 1695 if err := run(spec, conf); err != nil { 1696 t.Fatalf("error running sandbox: %v", err) 1697 } 1698 }) 1699 } 1700 } 1701 1702 func TestReadonlyRoot(t *testing.T) { 1703 for name, conf := range configs(t, false /* noOverlay */) { 1704 t.Run(name, func(t *testing.T) { 1705 spec, _ := sleepSpecConf(t) 1706 spec.Root.Readonly = true 1707 1708 _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 1709 if err != nil { 1710 t.Fatalf("error setting up container: %v", err) 1711 } 1712 defer cleanup() 1713 1714 args := Args{ 1715 ID: testutil.RandomContainerID(), 1716 Spec: spec, 1717 BundleDir: bundleDir, 1718 } 1719 c, err := New(conf, args) 1720 if err != nil { 1721 t.Fatalf("error creating container: %v", err) 1722 } 1723 defer c.Destroy() 1724 if err := c.Start(conf); err != nil { 1725 t.Fatalf("error starting container: %v", err) 1726 } 1727 1728 // Read mounts to check that root is readonly. 1729 out, err := executeCombinedOutput(conf, c, nil, "/bin/sh", "-c", "mount | grep ' / ' | grep -o -e '(.*)'") 1730 if err != nil { 1731 t.Fatalf("exec failed: %v", err) 1732 } 1733 t.Logf("root mount options: %q", out) 1734 if !strings.Contains(string(out), "ro") { 1735 t.Errorf("root not mounted readonly: %q", out) 1736 } 1737 1738 // Check that file cannot be created. 1739 ws, err := execute(conf, c, "/bin/touch", "/foo") 1740 if err != nil { 1741 t.Fatalf("touch file in ro mount: %v", err) 1742 } 1743 if !ws.Exited() || unix.Errno(ws.ExitStatus()) != unix.EPERM { 1744 t.Fatalf("wrong waitStatus: %v", ws) 1745 } 1746 }) 1747 } 1748 } 1749 1750 func TestReadonlyMount(t *testing.T) { 1751 for name, conf := range configs(t, false /* noOverlay */) { 1752 t.Run(name, func(t *testing.T) { 1753 dir, err := ioutil.TempDir(testutil.TmpDir(), "ro-mount") 1754 if err != nil { 1755 t.Fatalf("ioutil.TempDir() failed: %v", err) 1756 } 1757 spec, _ := sleepSpecConf(t) 1758 spec.Mounts = append(spec.Mounts, specs.Mount{ 1759 Destination: dir, 1760 Source: dir, 1761 Type: "bind", 1762 Options: []string{"ro"}, 1763 }) 1764 spec.Root.Readonly = false 1765 1766 _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 1767 if err != nil { 1768 t.Fatalf("error setting up container: %v", err) 1769 } 1770 defer cleanup() 1771 1772 args := Args{ 1773 ID: testutil.RandomContainerID(), 1774 Spec: spec, 1775 BundleDir: bundleDir, 1776 } 1777 c, err := New(conf, args) 1778 if err != nil { 1779 t.Fatalf("error creating container: %v", err) 1780 } 1781 defer c.Destroy() 1782 if err := c.Start(conf); err != nil { 1783 t.Fatalf("error starting container: %v", err) 1784 } 1785 1786 // Read mounts to check that volume is readonly. 1787 cmd := fmt.Sprintf("mount | grep ' %s ' | grep -o -e '(.*)'", dir) 1788 out, err := executeCombinedOutput(conf, c, nil, "/bin/sh", "-c", cmd) 1789 if err != nil { 1790 t.Fatalf("exec failed, err: %v", err) 1791 } 1792 t.Logf("mount options: %q", out) 1793 if !strings.Contains(string(out), "ro") { 1794 t.Errorf("volume not mounted readonly: %q", out) 1795 } 1796 1797 // Check that file cannot be created. 1798 ws, err := execute(conf, c, "/bin/touch", path.Join(dir, "file")) 1799 if err != nil { 1800 t.Fatalf("touch file in ro mount: %v", err) 1801 } 1802 if !ws.Exited() || unix.Errno(ws.ExitStatus()) != unix.EPERM { 1803 t.Fatalf("wrong WaitStatus: %v", ws) 1804 } 1805 }) 1806 } 1807 } 1808 1809 func TestUIDMap(t *testing.T) { 1810 for name, conf := range configs(t, true /* noOverlay */) { 1811 t.Run(name, func(t *testing.T) { 1812 testDir, err := ioutil.TempDir(testutil.TmpDir(), "test-mount") 1813 if err != nil { 1814 t.Fatalf("ioutil.TempDir() failed: %v", err) 1815 } 1816 defer os.RemoveAll(testDir) 1817 testFile := path.Join(testDir, "testfile") 1818 1819 spec := testutil.NewSpecWithArgs("touch", "/tmp/testfile") 1820 uid := os.Getuid() 1821 gid := os.Getgid() 1822 spec.Linux = &specs.Linux{ 1823 Namespaces: []specs.LinuxNamespace{ 1824 {Type: specs.UserNamespace}, 1825 {Type: specs.PIDNamespace}, 1826 {Type: specs.MountNamespace}, 1827 }, 1828 UIDMappings: []specs.LinuxIDMapping{ 1829 { 1830 ContainerID: 0, 1831 HostID: uint32(uid), 1832 Size: 1, 1833 }, 1834 }, 1835 GIDMappings: []specs.LinuxIDMapping{ 1836 { 1837 ContainerID: 0, 1838 HostID: uint32(gid), 1839 Size: 1, 1840 }, 1841 }, 1842 } 1843 1844 spec.Mounts = append(spec.Mounts, specs.Mount{ 1845 Destination: "/tmp", 1846 Source: testDir, 1847 Type: "bind", 1848 }) 1849 1850 _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 1851 if err != nil { 1852 t.Fatalf("error setting up container: %v", err) 1853 } 1854 defer cleanup() 1855 1856 // Create, start and wait for the container. 1857 args := Args{ 1858 ID: testutil.RandomContainerID(), 1859 Spec: spec, 1860 BundleDir: bundleDir, 1861 } 1862 c, err := New(conf, args) 1863 if err != nil { 1864 t.Fatalf("error creating container: %v", err) 1865 } 1866 defer c.Destroy() 1867 if err := c.Start(conf); err != nil { 1868 t.Fatalf("error starting container: %v", err) 1869 } 1870 1871 ws, err := c.Wait() 1872 if err != nil { 1873 t.Fatalf("error waiting on container: %v", err) 1874 } 1875 if !ws.Exited() || ws.ExitStatus() != 0 { 1876 t.Fatalf("container failed, waitStatus: %v", ws) 1877 } 1878 st := unix.Stat_t{} 1879 if err := unix.Stat(testFile, &st); err != nil { 1880 t.Fatalf("error stat /testfile: %v", err) 1881 } 1882 1883 if st.Uid != uint32(uid) || st.Gid != uint32(gid) { 1884 t.Fatalf("UID: %d (%d) GID: %d (%d)", st.Uid, uid, st.Gid, gid) 1885 } 1886 }) 1887 } 1888 } 1889 1890 // TestAbbreviatedIDs checks that runsc supports using abbreviated container 1891 // IDs in place of full IDs. 1892 func TestAbbreviatedIDs(t *testing.T) { 1893 rootDir, cleanup, err := testutil.SetupRootDir() 1894 if err != nil { 1895 t.Fatalf("error creating root dir: %v", err) 1896 } 1897 defer cleanup() 1898 1899 conf := testutil.TestConfig(t) 1900 conf.RootDir = rootDir 1901 1902 cids := []string{ 1903 "foo-" + testutil.RandomContainerID(), 1904 "bar-" + testutil.RandomContainerID(), 1905 "baz-" + testutil.RandomContainerID(), 1906 } 1907 for _, cid := range cids { 1908 spec, _ := sleepSpecConf(t) 1909 bundleDir, cleanup, err := testutil.SetupBundleDir(spec) 1910 if err != nil { 1911 t.Fatalf("error setting up container: %v", err) 1912 } 1913 defer cleanup() 1914 1915 // Create and start the container. 1916 args := Args{ 1917 ID: cid, 1918 Spec: spec, 1919 BundleDir: bundleDir, 1920 } 1921 cont, err := New(conf, args) 1922 if err != nil { 1923 t.Fatalf("error creating container: %v", err) 1924 } 1925 defer cont.Destroy() 1926 } 1927 1928 // These should all be unambigious. 1929 unambiguous := map[string]string{ 1930 "f": cids[0], 1931 cids[0]: cids[0], 1932 "bar": cids[1], 1933 cids[1]: cids[1], 1934 "baz": cids[2], 1935 cids[2]: cids[2], 1936 } 1937 for shortid, longid := range unambiguous { 1938 if _, err := Load(rootDir, FullID{ContainerID: shortid}, LoadOpts{}); err != nil { 1939 t.Errorf("%q should resolve to %q: %v", shortid, longid, err) 1940 } 1941 } 1942 1943 // These should be ambiguous. 1944 ambiguous := []string{ 1945 "b", 1946 "ba", 1947 } 1948 for _, shortid := range ambiguous { 1949 if s, err := Load(rootDir, FullID{ContainerID: shortid}, LoadOpts{}); err == nil { 1950 t.Errorf("%q should be ambiguous, but resolved to %q", shortid, s.ID) 1951 } 1952 } 1953 } 1954 1955 func TestGoferExits(t *testing.T) { 1956 spec, conf := sleepSpecConf(t) 1957 _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 1958 1959 if err != nil { 1960 t.Fatalf("error setting up container: %v", err) 1961 } 1962 defer cleanup() 1963 1964 // Create and start the container. 1965 args := Args{ 1966 ID: testutil.RandomContainerID(), 1967 Spec: spec, 1968 BundleDir: bundleDir, 1969 } 1970 c, err := New(conf, args) 1971 if err != nil { 1972 t.Fatalf("error creating container: %v", err) 1973 } 1974 defer c.Destroy() 1975 if err := c.Start(conf); err != nil { 1976 t.Fatalf("error starting container: %v", err) 1977 } 1978 1979 // Kill sandbox and expect gofer to exit on its own. 1980 sandboxProc, err := os.FindProcess(c.Sandbox.Getpid()) 1981 if err != nil { 1982 t.Fatalf("error finding sandbox process: %v", err) 1983 } 1984 if err := sandboxProc.Kill(); err != nil { 1985 t.Fatalf("error killing sandbox process: %v", err) 1986 } 1987 1988 err = blockUntilWaitable(c.GoferPid) 1989 if err != nil && err != unix.ECHILD { 1990 t.Errorf("error waiting for gofer to exit: %v", err) 1991 } 1992 } 1993 1994 func TestRootNotMount(t *testing.T) { 1995 appSym, err := testutil.FindFile("test/cmd/test_app/test_app") 1996 if err != nil { 1997 t.Fatal("error finding test_app:", err) 1998 } 1999 2000 app, err := filepath.EvalSymlinks(appSym) 2001 if err != nil { 2002 t.Fatalf("error resolving %q symlink: %v", appSym, err) 2003 } 2004 log.Infof("App path %q is a symlink to %q", appSym, app) 2005 2006 static, err := testutil.IsStatic(app) 2007 if err != nil { 2008 t.Fatalf("error reading application binary: %v", err) 2009 } 2010 if !static { 2011 // This happens during race builds; we cannot map in shared 2012 // libraries also, so we need to skip the test. 2013 t.Skip() 2014 } 2015 2016 root := filepath.Dir(app) 2017 exe := "/" + filepath.Base(app) 2018 log.Infof("Executing %q in %q", exe, root) 2019 2020 spec := testutil.NewSpecWithArgs(exe, "help") 2021 spec.Root.Path = root 2022 spec.Root.Readonly = true 2023 spec.Mounts = nil 2024 2025 conf := testutil.TestConfig(t) 2026 if err := run(spec, conf); err != nil { 2027 t.Fatalf("error running sandbox: %v", err) 2028 } 2029 } 2030 2031 func TestUserLog(t *testing.T) { 2032 app, err := testutil.FindFile("test/cmd/test_app/test_app") 2033 if err != nil { 2034 t.Fatal("error finding test_app:", err) 2035 } 2036 2037 // sched_rr_get_interval - not implemented in gvisor. 2038 num := strconv.Itoa(unix.SYS_SCHED_RR_GET_INTERVAL) 2039 spec := testutil.NewSpecWithArgs(app, "syscall", "--syscall="+num) 2040 conf := testutil.TestConfig(t) 2041 _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 2042 if err != nil { 2043 t.Fatalf("error setting up container: %v", err) 2044 } 2045 defer cleanup() 2046 2047 dir, err := ioutil.TempDir(testutil.TmpDir(), "user_log_test") 2048 if err != nil { 2049 t.Fatalf("error creating tmp dir: %v", err) 2050 } 2051 userLog := filepath.Join(dir, "user.log") 2052 2053 // Create, start and wait for the container. 2054 args := Args{ 2055 ID: testutil.RandomContainerID(), 2056 Spec: spec, 2057 BundleDir: bundleDir, 2058 UserLog: userLog, 2059 Attached: true, 2060 } 2061 ws, err := Run(conf, args) 2062 if err != nil { 2063 t.Fatalf("error running container: %v", err) 2064 } 2065 if !ws.Exited() || ws.ExitStatus() != 0 { 2066 t.Fatalf("container failed, waitStatus: %v", ws) 2067 } 2068 2069 out, err := ioutil.ReadFile(userLog) 2070 if err != nil { 2071 t.Fatalf("error opening user log file %q: %v", userLog, err) 2072 } 2073 if want := "Unsupported syscall sched_rr_get_interval("; !strings.Contains(string(out), want) { 2074 t.Errorf("user log file doesn't contain %q, out: %s", want, string(out)) 2075 } 2076 } 2077 2078 func TestWaitOnExitedSandbox(t *testing.T) { 2079 for name, conf := range configs(t, false /* noOverlay */) { 2080 t.Run(name, func(t *testing.T) { 2081 // Run a shell that sleeps for 1 second and then exits with a 2082 // non-zero code. 2083 const wantExit = 17 2084 cmd := fmt.Sprintf("sleep 1; exit %d", wantExit) 2085 spec := testutil.NewSpecWithArgs("/bin/sh", "-c", cmd) 2086 _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 2087 if err != nil { 2088 t.Fatalf("error setting up container: %v", err) 2089 } 2090 defer cleanup() 2091 2092 // Create and Start the container. 2093 args := Args{ 2094 ID: testutil.RandomContainerID(), 2095 Spec: spec, 2096 BundleDir: bundleDir, 2097 } 2098 c, err := New(conf, args) 2099 if err != nil { 2100 t.Fatalf("error creating container: %v", err) 2101 } 2102 defer c.Destroy() 2103 if err := c.Start(conf); err != nil { 2104 t.Fatalf("error starting container: %v", err) 2105 } 2106 2107 // Wait on the sandbox. This will make an RPC to the sandbox 2108 // and get the actual exit status of the application. 2109 ws, err := c.Wait() 2110 if err != nil { 2111 t.Fatalf("error waiting on container: %v", err) 2112 } 2113 if got := ws.ExitStatus(); got != wantExit { 2114 t.Errorf("got exit status %d, want %d", got, wantExit) 2115 } 2116 2117 // Now the sandbox has exited, but the zombie sandbox process 2118 // still exists. Calling Wait() now will return the sandbox 2119 // exit status. 2120 ws, err = c.Wait() 2121 if err != nil { 2122 t.Fatalf("error waiting on container: %v", err) 2123 } 2124 if got := ws.ExitStatus(); got != wantExit { 2125 t.Errorf("got exit status %d, want %d", got, wantExit) 2126 } 2127 }) 2128 } 2129 } 2130 2131 func TestDestroyNotStarted(t *testing.T) { 2132 spec, conf := sleepSpecConf(t) 2133 _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 2134 if err != nil { 2135 t.Fatalf("error setting up container: %v", err) 2136 } 2137 defer cleanup() 2138 2139 // Create the container and check that it can be destroyed. 2140 args := Args{ 2141 ID: testutil.RandomContainerID(), 2142 Spec: spec, 2143 BundleDir: bundleDir, 2144 } 2145 c, err := New(conf, args) 2146 if err != nil { 2147 t.Fatalf("error creating container: %v", err) 2148 } 2149 if err := c.Destroy(); err != nil { 2150 t.Fatalf("deleting non-started container failed: %v", err) 2151 } 2152 } 2153 2154 // TestDestroyStarting attempts to force a race between start and destroy. 2155 func TestDestroyStarting(t *testing.T) { 2156 for i := 0; i < 10; i++ { 2157 spec, conf := sleepSpecConf(t) 2158 rootDir, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 2159 if err != nil { 2160 t.Fatalf("error setting up container: %v", err) 2161 } 2162 defer cleanup() 2163 2164 // Create the container and check that it can be destroyed. 2165 args := Args{ 2166 ID: testutil.RandomContainerID(), 2167 Spec: spec, 2168 BundleDir: bundleDir, 2169 } 2170 c, err := New(conf, args) 2171 if err != nil { 2172 t.Fatalf("error creating container: %v", err) 2173 } 2174 2175 // Container is not thread safe, so load another instance to run in 2176 // concurrently. 2177 startCont, err := Load(rootDir, FullID{ContainerID: args.ID}, LoadOpts{}) 2178 if err != nil { 2179 t.Fatalf("error loading container: %v", err) 2180 } 2181 wg := sync.WaitGroup{} 2182 wg.Add(1) 2183 go func() { 2184 defer wg.Done() 2185 // Ignore failures, start can fail if destroy runs first. 2186 _ = startCont.Start(conf) 2187 }() 2188 2189 wg.Add(1) 2190 go func() { 2191 defer wg.Done() 2192 if err := c.Destroy(); err != nil { 2193 t.Errorf("deleting non-started container failed: %v", err) 2194 } 2195 }() 2196 wg.Wait() 2197 } 2198 } 2199 2200 func TestCreateWorkingDir(t *testing.T) { 2201 for name, conf := range configs(t, false /* noOverlay */) { 2202 t.Run(name, func(t *testing.T) { 2203 tmpDir, err := ioutil.TempDir(testutil.TmpDir(), "cwd-create") 2204 if err != nil { 2205 t.Fatalf("ioutil.TempDir() failed: %v", err) 2206 } 2207 dir := path.Join(tmpDir, "new/working/dir") 2208 2209 // touch will fail if the directory doesn't exist. 2210 spec := testutil.NewSpecWithArgs("/bin/touch", path.Join(dir, "file")) 2211 spec.Process.Cwd = dir 2212 spec.Root.Readonly = true 2213 2214 if err := run(spec, conf); err != nil { 2215 t.Fatalf("Error running container: %v", err) 2216 } 2217 }) 2218 } 2219 } 2220 2221 // TestMountPropagation verifies that mount propagates to slave but not to 2222 // private mounts. 2223 func TestMountPropagation(t *testing.T) { 2224 // Setup dir structure: 2225 // - src: is mounted as shared and is used as source for both private and 2226 // slave mounts 2227 // - dir: will be bind mounted inside src and should propagate to slave 2228 tmpDir, err := ioutil.TempDir(testutil.TmpDir(), "mount") 2229 if err != nil { 2230 t.Fatalf("ioutil.TempDir() failed: %v", err) 2231 } 2232 src := filepath.Join(tmpDir, "src") 2233 srcMnt := filepath.Join(src, "mnt") 2234 dir := filepath.Join(tmpDir, "dir") 2235 for _, path := range []string{src, srcMnt, dir} { 2236 if err := os.MkdirAll(path, 0777); err != nil { 2237 t.Fatalf("MkdirAll(%q): %v", path, err) 2238 } 2239 } 2240 dirFile := filepath.Join(dir, "file") 2241 f, err := os.Create(dirFile) 2242 if err != nil { 2243 t.Fatalf("os.Create(%q): %v", dirFile, err) 2244 } 2245 f.Close() 2246 2247 // Setup src as a shared mount. 2248 if err := unix.Mount(src, src, "bind", unix.MS_BIND, ""); err != nil { 2249 t.Fatalf("mount(%q, %q, MS_BIND): %v", dir, srcMnt, err) 2250 } 2251 if err := unix.Mount("", src, "", unix.MS_SHARED, ""); err != nil { 2252 t.Fatalf("mount(%q, MS_SHARED): %v", srcMnt, err) 2253 } 2254 2255 spec, conf := sleepSpecConf(t) 2256 2257 priv := filepath.Join(tmpDir, "priv") 2258 slave := filepath.Join(tmpDir, "slave") 2259 spec.Mounts = []specs.Mount{ 2260 { 2261 Source: src, 2262 Destination: priv, 2263 Type: "bind", 2264 Options: []string{"private"}, 2265 }, 2266 { 2267 Source: src, 2268 Destination: slave, 2269 Type: "bind", 2270 Options: []string{"slave"}, 2271 }, 2272 } 2273 2274 _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 2275 if err != nil { 2276 t.Fatalf("error setting up container: %v", err) 2277 } 2278 defer cleanup() 2279 2280 args := Args{ 2281 ID: testutil.RandomContainerID(), 2282 Spec: spec, 2283 BundleDir: bundleDir, 2284 } 2285 cont, err := New(conf, args) 2286 if err != nil { 2287 t.Fatalf("creating container: %v", err) 2288 } 2289 defer cont.Destroy() 2290 2291 if err := cont.Start(conf); err != nil { 2292 t.Fatalf("starting container: %v", err) 2293 } 2294 2295 // After the container is started, mount dir inside source and check what 2296 // happens to both destinations. 2297 if err := unix.Mount(dir, srcMnt, "bind", unix.MS_BIND, ""); err != nil { 2298 t.Fatalf("mount(%q, %q, MS_BIND): %v", dir, srcMnt, err) 2299 } 2300 2301 // Check that mount didn't propagate to private mount. 2302 privFile := filepath.Join(priv, "mnt", "file") 2303 if ws, err := execute(conf, cont, "/usr/bin/test", "!", "-f", privFile); err != nil || ws != 0 { 2304 t.Fatalf("exec: test ! -f %q, ws: %v, err: %v", privFile, ws, err) 2305 } 2306 2307 // Check that mount propagated to slave mount. 2308 slaveFile := filepath.Join(slave, "mnt", "file") 2309 if ws, err := execute(conf, cont, "/usr/bin/test", "-f", slaveFile); err != nil || ws != 0 { 2310 t.Fatalf("exec: test -f %q, ws: %v, err: %v", privFile, ws, err) 2311 } 2312 } 2313 2314 func TestMountSymlink(t *testing.T) { 2315 for name, conf := range configs(t, false /* noOverlay */) { 2316 t.Run(name, func(t *testing.T) { 2317 dir, err := ioutil.TempDir(testutil.TmpDir(), "mount-symlink") 2318 if err != nil { 2319 t.Fatalf("ioutil.TempDir() failed: %v", err) 2320 } 2321 defer os.RemoveAll(dir) 2322 2323 source := path.Join(dir, "source") 2324 target := path.Join(dir, "target") 2325 for _, path := range []string{source, target} { 2326 if err := os.MkdirAll(path, 0777); err != nil { 2327 t.Fatalf("os.MkdirAll(): %v", err) 2328 } 2329 } 2330 f, err := os.Create(path.Join(source, "file")) 2331 if err != nil { 2332 t.Fatalf("os.Create(): %v", err) 2333 } 2334 f.Close() 2335 2336 link := path.Join(dir, "link") 2337 if err := os.Symlink(target, link); err != nil { 2338 t.Fatalf("os.Symlink(%q, %q): %v", target, link, err) 2339 } 2340 2341 spec, _ := sleepSpecConf(t) 2342 2343 // Mount to a symlink to ensure the mount code will follow it and mount 2344 // at the symlink target. 2345 spec.Mounts = append(spec.Mounts, specs.Mount{ 2346 Type: "bind", 2347 Destination: link, 2348 Source: source, 2349 }) 2350 2351 _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 2352 if err != nil { 2353 t.Fatalf("error setting up container: %v", err) 2354 } 2355 defer cleanup() 2356 2357 args := Args{ 2358 ID: testutil.RandomContainerID(), 2359 Spec: spec, 2360 BundleDir: bundleDir, 2361 } 2362 cont, err := New(conf, args) 2363 if err != nil { 2364 t.Fatalf("creating container: %v", err) 2365 } 2366 defer cont.Destroy() 2367 2368 if err := cont.Start(conf); err != nil { 2369 t.Fatalf("starting container: %v", err) 2370 } 2371 2372 // Check that symlink was resolved and mount was created where the symlink 2373 // is pointing to. 2374 file := path.Join(target, "file") 2375 if ws, err := execute(conf, cont, "/usr/bin/test", "-f", file); err != nil || ws != 0 { 2376 t.Fatalf("exec: test -f %q, ws: %v, err: %v", file, ws, err) 2377 } 2378 }) 2379 } 2380 } 2381 2382 // Check that --net-raw disables the CAP_NET_RAW capability. 2383 func TestNetRaw(t *testing.T) { 2384 capNetRaw := strconv.FormatUint(bits.MaskOf64(int(linux.CAP_NET_RAW)), 10) 2385 app, err := testutil.FindFile("test/cmd/test_app/test_app") 2386 if err != nil { 2387 t.Fatal("error finding test_app:", err) 2388 } 2389 2390 for _, enableRaw := range []bool{true, false} { 2391 conf := testutil.TestConfig(t) 2392 conf.EnableRaw = enableRaw 2393 2394 test := "--enabled" 2395 if !enableRaw { 2396 test = "--disabled" 2397 } 2398 2399 spec := testutil.NewSpecWithArgs(app, "capability", test, capNetRaw) 2400 if err := run(spec, conf); err != nil { 2401 t.Fatalf("Error running container: %v", err) 2402 } 2403 } 2404 } 2405 2406 // TestTTYField checks TTY field returned by container.Processes(). 2407 func TestTTYField(t *testing.T) { 2408 stop := testutil.StartReaper() 2409 defer stop() 2410 2411 testApp, err := testutil.FindFile("test/cmd/test_app/test_app") 2412 if err != nil { 2413 t.Fatal("error finding test_app:", err) 2414 } 2415 2416 testCases := []struct { 2417 name string 2418 useTTY bool 2419 wantTTYField string 2420 }{ 2421 { 2422 name: "no tty", 2423 useTTY: false, 2424 wantTTYField: "?", 2425 }, 2426 { 2427 name: "tty used", 2428 useTTY: true, 2429 wantTTYField: "pts/0", 2430 }, 2431 } 2432 2433 for _, test := range testCases { 2434 t.Run(test.name, func(t *testing.T) { 2435 conf := testutil.TestConfig(t) 2436 2437 // We will run /bin/sleep, possibly with an open TTY. 2438 cmd := []string{"/bin/sleep", "10000"} 2439 if test.useTTY { 2440 // Run inside the "pty-runner". 2441 cmd = append([]string{testApp, "pty-runner"}, cmd...) 2442 } 2443 2444 spec := testutil.NewSpecWithArgs(cmd...) 2445 _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 2446 if err != nil { 2447 t.Fatalf("error setting up container: %v", err) 2448 } 2449 defer cleanup() 2450 2451 // Create and start the container. 2452 args := Args{ 2453 ID: testutil.RandomContainerID(), 2454 Spec: spec, 2455 BundleDir: bundleDir, 2456 } 2457 c, err := New(conf, args) 2458 if err != nil { 2459 t.Fatalf("error creating container: %v", err) 2460 } 2461 defer c.Destroy() 2462 if err := c.Start(conf); err != nil { 2463 t.Fatalf("error starting container: %v", err) 2464 } 2465 2466 // Wait for sleep to be running, and check the TTY 2467 // field. 2468 var gotTTYField string 2469 cb := func() error { 2470 ps, err := c.Processes() 2471 if err != nil { 2472 err = fmt.Errorf("error getting process data from container: %v", err) 2473 return &backoff.PermanentError{Err: err} 2474 } 2475 for _, p := range ps { 2476 if strings.Contains(p.Cmd, "sleep") { 2477 gotTTYField = p.TTY 2478 return nil 2479 } 2480 } 2481 return fmt.Errorf("sleep not running") 2482 } 2483 if err := testutil.Poll(cb, 30*time.Second); err != nil { 2484 t.Fatalf("error waiting for sleep process: %v", err) 2485 } 2486 2487 if gotTTYField != test.wantTTYField { 2488 t.Errorf("tty field got %q, want %q", gotTTYField, test.wantTTYField) 2489 } 2490 }) 2491 } 2492 } 2493 2494 // Test that container can run even when there are corrupt state files in the 2495 // root directiry. 2496 func TestCreateWithCorruptedStateFile(t *testing.T) { 2497 conf := testutil.TestConfig(t) 2498 spec := testutil.NewSpecWithArgs("/bin/true") 2499 _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 2500 if err != nil { 2501 t.Fatalf("error setting up container: %v", err) 2502 } 2503 defer cleanup() 2504 2505 // Create corrupted state file. 2506 corruptID := testutil.RandomContainerID() 2507 corruptState := buildPath(conf.RootDir, FullID{SandboxID: corruptID, ContainerID: corruptID}, stateFileExtension) 2508 if err := ioutil.WriteFile(corruptState, []byte("this{file(is;not[valid.json"), 0777); err != nil { 2509 t.Fatalf("createCorruptStateFile(): %v", err) 2510 } 2511 defer os.Remove(corruptState) 2512 2513 if _, err := Load(conf.RootDir, FullID{ContainerID: corruptID}, LoadOpts{SkipCheck: true}); err == nil { 2514 t.Fatalf("loading corrupted state file should have failed") 2515 } 2516 2517 args := Args{ 2518 ID: testutil.RandomContainerID(), 2519 Spec: spec, 2520 BundleDir: bundleDir, 2521 Attached: true, 2522 } 2523 if ws, err := Run(conf, args); err != nil { 2524 t.Errorf("running container: %v", err) 2525 } else if !ws.Exited() || ws.ExitStatus() != 0 { 2526 t.Errorf("container failed, waitStatus: %v", ws) 2527 } 2528 } 2529 2530 func TestBindMountByOption(t *testing.T) { 2531 for name, conf := range configs(t, false /* noOverlay */) { 2532 t.Run(name, func(t *testing.T) { 2533 dir, err := ioutil.TempDir(testutil.TmpDir(), "bind-mount") 2534 spec := testutil.NewSpecWithArgs("/bin/touch", path.Join(dir, "file")) 2535 if err != nil { 2536 t.Fatalf("ioutil.TempDir(): %v", err) 2537 } 2538 spec.Mounts = append(spec.Mounts, specs.Mount{ 2539 Destination: dir, 2540 Source: dir, 2541 Type: "none", 2542 Options: []string{"rw", "bind"}, 2543 }) 2544 if err := run(spec, conf); err != nil { 2545 t.Fatalf("error running sandbox: %v", err) 2546 } 2547 }) 2548 } 2549 } 2550 2551 // TestRlimits sets limit to number of open files and checks that the limit 2552 // is propagated to the container. 2553 func TestRlimits(t *testing.T) { 2554 file, err := ioutil.TempFile(testutil.TmpDir(), "ulimit") 2555 if err != nil { 2556 t.Fatal(err) 2557 } 2558 cmd := fmt.Sprintf("ulimit -n > %q", file.Name()) 2559 2560 spec := testutil.NewSpecWithArgs("sh", "-c", cmd) 2561 spec.Process.Rlimits = []specs.POSIXRlimit{ 2562 {Type: "RLIMIT_NOFILE", Hard: 1000, Soft: 100}, 2563 } 2564 2565 conf := testutil.TestConfig(t) 2566 if err := run(spec, conf); err != nil { 2567 t.Fatalf("Error running container: %v", err) 2568 } 2569 got, err := ioutil.ReadFile(file.Name()) 2570 if err != nil { 2571 t.Fatal(err) 2572 } 2573 if want := "100\n"; string(got) != want { 2574 t.Errorf("ulimit result, got: %q, want: %q", got, want) 2575 } 2576 } 2577 2578 // TestRlimitsExec sets limit to number of open files and checks that the limit 2579 // is propagated to exec'd processes. 2580 func TestRlimitsExec(t *testing.T) { 2581 spec, conf := sleepSpecConf(t) 2582 spec.Process.Rlimits = []specs.POSIXRlimit{ 2583 {Type: "RLIMIT_NOFILE", Hard: 1000, Soft: 100}, 2584 } 2585 2586 _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 2587 if err != nil { 2588 t.Fatalf("error setting up container: %v", err) 2589 } 2590 defer cleanup() 2591 2592 args := Args{ 2593 ID: testutil.RandomContainerID(), 2594 Spec: spec, 2595 BundleDir: bundleDir, 2596 } 2597 cont, err := New(conf, args) 2598 if err != nil { 2599 t.Fatalf("error creating container: %v", err) 2600 } 2601 defer cont.Destroy() 2602 if err := cont.Start(conf); err != nil { 2603 t.Fatalf("error starting container: %v", err) 2604 } 2605 2606 got, err := executeCombinedOutput(conf, cont, nil, "/bin/sh", "-c", "ulimit -n") 2607 if err != nil { 2608 t.Fatal(err) 2609 } 2610 if want := "100\n"; string(got) != want { 2611 t.Errorf("ulimit result, got: %q, want: %q", got, want) 2612 } 2613 } 2614 2615 // TestUsage checks that usage generates the expected memory usage. 2616 func TestUsage(t *testing.T) { 2617 spec, conf := sleepSpecConf(t) 2618 _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 2619 if err != nil { 2620 t.Fatalf("error setting up container: %v", err) 2621 } 2622 defer cleanup() 2623 2624 args := Args{ 2625 ID: testutil.RandomContainerID(), 2626 Spec: spec, 2627 BundleDir: bundleDir, 2628 } 2629 2630 cont, err := New(conf, args) 2631 if err != nil { 2632 t.Fatalf("Creating container: %v", err) 2633 } 2634 defer cont.Destroy() 2635 2636 if err := cont.Start(conf); err != nil { 2637 t.Fatalf("starting container: %v", err) 2638 } 2639 2640 for _, full := range []bool{false, true} { 2641 m, err := cont.Sandbox.Usage(full) 2642 if err != nil { 2643 t.Fatalf("error usage from container: %v", err) 2644 } 2645 if m.Mapped == 0 { 2646 t.Errorf("Usage mapped got zero") 2647 } 2648 if m.Total == 0 { 2649 t.Errorf("Usage total got zero") 2650 } 2651 if full { 2652 if m.System == 0 { 2653 t.Errorf("Usage system got zero") 2654 } 2655 if m.Anonymous == 0 { 2656 t.Errorf("Usage anonymous got zero") 2657 } 2658 } 2659 } 2660 } 2661 2662 // TestUsageFD checks that usagefd generates the expected memory usage. 2663 func TestUsageFD(t *testing.T) { 2664 spec, conf := sleepSpecConf(t) 2665 2666 _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 2667 if err != nil { 2668 t.Fatalf("error setting up container: %v", err) 2669 } 2670 defer cleanup() 2671 2672 args := Args{ 2673 ID: testutil.RandomContainerID(), 2674 Spec: spec, 2675 BundleDir: bundleDir, 2676 } 2677 2678 cont, err := New(conf, args) 2679 if err != nil { 2680 t.Fatalf("Creating container: %v", err) 2681 } 2682 defer cont.Destroy() 2683 2684 if err := cont.Start(conf); err != nil { 2685 t.Fatalf("starting container: %v", err) 2686 } 2687 2688 m, err := cont.Sandbox.UsageFD() 2689 if err != nil { 2690 t.Fatalf("error usageFD from container: %v", err) 2691 } 2692 2693 mapped, unknown, total, err := m.Fetch() 2694 if err != nil { 2695 t.Fatalf("error Fetch memory usage: %v", err) 2696 } 2697 2698 if mapped == 0 { 2699 t.Errorf("UsageFD Mapped got zero") 2700 } 2701 if unknown == 0 { 2702 t.Errorf("UsageFD unknown got zero") 2703 } 2704 if total == 0 { 2705 t.Errorf("UsageFD total got zero") 2706 } 2707 } 2708 2709 // TestProfile checks that profiling options generate profiles. 2710 func TestProfile(t *testing.T) { 2711 // Perform a non-trivial amount of work so we actually capture 2712 // something in the profiles. 2713 spec := testutil.NewSpecWithArgs("/bin/bash", "-c", "true") 2714 conf := testutil.TestConfig(t) 2715 conf.ProfileEnable = true 2716 conf.ProfileBlock = filepath.Join(t.TempDir(), "block.pprof") 2717 conf.ProfileCPU = filepath.Join(t.TempDir(), "cpu.pprof") 2718 conf.ProfileHeap = filepath.Join(t.TempDir(), "heap.pprof") 2719 conf.ProfileMutex = filepath.Join(t.TempDir(), "mutex.pprof") 2720 conf.TraceFile = filepath.Join(t.TempDir(), "trace.out") 2721 2722 _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 2723 if err != nil { 2724 t.Fatalf("error setting up container: %v", err) 2725 } 2726 defer cleanup() 2727 2728 args := Args{ 2729 ID: testutil.RandomContainerID(), 2730 Spec: spec, 2731 BundleDir: bundleDir, 2732 Attached: true, 2733 } 2734 2735 _, err = Run(conf, args) 2736 if err != nil { 2737 t.Fatalf("Creating container: %v", err) 2738 } 2739 2740 // Basic test; simply assert that the profiles are not empty. 2741 for _, name := range []string{conf.ProfileBlock, conf.ProfileCPU, conf.ProfileHeap, conf.ProfileMutex, conf.TraceFile} { 2742 fi, err := os.Stat(name) 2743 if err != nil { 2744 t.Fatalf("Unable to stat profile file %s: %v", name, err) 2745 } 2746 if fi.Size() == 0 { 2747 t.Errorf("Profile file %s is empty: %+v", name, fi) 2748 } 2749 } 2750 } 2751 2752 // TestSaveSystemdCgroup emulates a sandbox saving while configured with the 2753 // systemd cgroup driver. 2754 func TestSaveSystemdCgroup(t *testing.T) { 2755 spec, conf := sleepSpecConf(t) 2756 _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 2757 if err != nil { 2758 t.Fatalf("error setting up container: %v", err) 2759 } 2760 defer cleanup() 2761 2762 // Create and start the container. 2763 args := Args{ 2764 ID: testutil.RandomContainerID(), 2765 Spec: spec, 2766 BundleDir: bundleDir, 2767 } 2768 cont, err := New(conf, args) 2769 if err != nil { 2770 t.Fatalf("error creating container: %v", err) 2771 } 2772 defer cont.Destroy() 2773 2774 cont.CompatCgroup = cgroup.CgroupJSON{Cgroup: cgroup.CreateMockSystemdCgroup()} 2775 if err := cont.Saver.lock(BlockAcquire); err != nil { 2776 t.Fatalf("cannot lock container metadata file: %v", err) 2777 } 2778 if err := cont.saveLocked(); err != nil { 2779 t.Fatalf("error saving cgroup: %v", err) 2780 } 2781 cont.Saver.unlock() 2782 loadCont := Container{} 2783 cont.Saver.load(&loadCont, LoadOpts{}) 2784 if !reflect.DeepEqual(cont.CompatCgroup, loadCont.CompatCgroup) { 2785 t.Errorf("CompatCgroup not properly saved: want %v, got %v", cont.CompatCgroup, loadCont.CompatCgroup) 2786 } 2787 } 2788 2789 // TestSandboxCommunicationUnshare checks that communication with sandboxes do 2790 // not require being in the same network namespace. This is required to allow 2791 // Kubernetes daemonsets/containers to communicate with sandboxes without the 2792 // need to join the host network namespaces. 2793 func TestSandboxCommunicationUnshare(t *testing.T) { 2794 spec, conf := sleepSpecConf(t) 2795 _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 2796 if err != nil { 2797 t.Fatalf("error setting up container: %v", err) 2798 } 2799 defer cleanup() 2800 2801 args := Args{ 2802 ID: testutil.RandomContainerID(), 2803 Spec: spec, 2804 BundleDir: bundleDir, 2805 } 2806 2807 cont, err := New(conf, args) 2808 if err != nil { 2809 t.Fatalf("Creating container: %v", err) 2810 } 2811 defer cont.Destroy() 2812 2813 if err := cont.Start(conf); err != nil { 2814 t.Fatalf("starting container: %v", err) 2815 } 2816 2817 runtime.LockOSThread() 2818 defer runtime.UnlockOSThread() 2819 if err := unix.Unshare(unix.CLONE_NEWNET); err != nil { 2820 t.Fatalf("unix.Unshare(): %v", err) 2821 } 2822 2823 // Send a simple command to test that the sandbox can be reached. 2824 if err := cont.SignalContainer(0, true); err != nil { 2825 t.Errorf("SignalContainer(): %v", err) 2826 } 2827 } 2828 2829 // writeAndReadFromPipe writes the bytes to the write end of the pipe, then 2830 // reads from the read end and returns the result. 2831 func writeAndReadFromPipe(write, read *os.File, msg string) (string, error) { 2832 // Write the message to be read by the guest. 2833 if _, err := io.StringWriter(write).WriteString(msg); err != nil { 2834 return "", fmt.Errorf("failed to write message to pipe: %w", err) 2835 } 2836 write.Close() 2837 2838 // Read and return the message. 2839 response, err := io.ReadAll(read) 2840 if err != nil { 2841 return "", fmt.Errorf("failed to read from pipe: %w", err) 2842 } 2843 read.Close() 2844 2845 return string(response), nil 2846 } 2847 2848 func createPipes() (*os.File, *os.File, *os.File, *os.File, func(), error) { 2849 // This is the first pipe which the host writes to and the guest reads 2850 // from. 2851 guestRead, hostWrite, err := os.Pipe() 2852 if err != nil { 2853 return nil, nil, nil, nil, nil, err 2854 } 2855 2856 // This is the second pipe which the guest writes to and the host reads 2857 // from. 2858 hostRead, guestWrite, err := os.Pipe() 2859 if err != nil { 2860 guestRead.Close() 2861 hostWrite.Close() 2862 return nil, nil, nil, nil, nil, err 2863 } 2864 2865 cleanup := func() { 2866 guestRead.Close() 2867 hostWrite.Close() 2868 hostRead.Close() 2869 guestWrite.Close() 2870 } 2871 2872 return guestRead, hostWrite, hostRead, guestWrite, cleanup, nil 2873 } 2874 2875 // TestFDPassingRun checks that file descriptors passed into a new container 2876 // work as expected. 2877 func TestFDPassingRun(t *testing.T) { 2878 guestRead, hostWrite, hostRead, guestWrite, cleanup, err := createPipes() 2879 if err != nil { 2880 t.Fatalf("error creating pipes: %v", err) 2881 } 2882 defer cleanup() 2883 2884 // In the guest, read from the host and write the result back to the host. 2885 conf := testutil.TestConfig(t) 2886 cmd := fmt.Sprintf("cat /proc/self/fd/%d > /proc/self/fd/%d", int(guestRead.Fd()), int(guestWrite.Fd())) 2887 spec := testutil.NewSpecWithArgs("bash", "-c", cmd) 2888 2889 _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 2890 if err != nil { 2891 t.Fatalf("error setting up container: %v", err) 2892 } 2893 defer cleanup() 2894 2895 args := Args{ 2896 ID: testutil.RandomContainerID(), 2897 Spec: spec, 2898 BundleDir: bundleDir, 2899 PassFiles: map[int]*os.File{ 2900 int(guestRead.Fd()): guestRead, 2901 int(guestWrite.Fd()): guestWrite, 2902 }, 2903 } 2904 2905 cont, err := New(conf, args) 2906 if err != nil { 2907 t.Fatalf("Creating container: %v", err) 2908 } 2909 defer cont.Destroy() 2910 2911 if err := cont.Start(conf); err != nil { 2912 t.Fatalf("starting container: %v", err) 2913 } 2914 2915 // We close guestWrite here because it has been passed into the container. 2916 // If we do not close it, we will never see an EOF. 2917 guestWrite.Close() 2918 2919 msg := "hello" 2920 got, err := writeAndReadFromPipe(hostWrite, hostRead, msg) 2921 if err != nil { 2922 t.Fatal(err) 2923 } 2924 if got != msg { 2925 t.Errorf("got message %q, want %q", got, msg) 2926 } 2927 } 2928 2929 // TestFDPassingExec checks that file descriptors passed into an already 2930 // running container work as expected. 2931 func TestFDPassingExec(t *testing.T) { 2932 guestRead, hostWrite, hostRead, guestWrite, cleanup, err := createPipes() 2933 if err != nil { 2934 t.Fatalf("error creating pipes: %v", err) 2935 } 2936 defer cleanup() 2937 2938 // We just sleep here because we want to test file descriptor passing 2939 // inside a process executed inside an already running container. 2940 spec, conf := sleepSpecConf(t) 2941 _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 2942 if err != nil { 2943 t.Fatalf("error setting up container: %v", err) 2944 } 2945 defer cleanup() 2946 2947 args := Args{ 2948 ID: testutil.RandomContainerID(), 2949 Spec: spec, 2950 BundleDir: bundleDir, 2951 } 2952 2953 cont, err := New(conf, args) 2954 if err != nil { 2955 t.Fatalf("Creating container: %v", err) 2956 } 2957 defer cont.Destroy() 2958 2959 if err := cont.Start(conf); err != nil { 2960 t.Fatalf("starting container: %v", err) 2961 } 2962 2963 // Prepare executing a command in the running container. 2964 cmd := fmt.Sprintf("cat /proc/self/fd/%d > /proc/self/fd/%d", int(guestRead.Fd()), int(guestWrite.Fd())) 2965 execArgs := &control.ExecArgs{ 2966 Argv: []string{"/bin/bash", "-c", cmd}, 2967 FilePayload: control.NewFilePayload(map[int]*os.File{ 2968 int(guestRead.Fd()): guestRead, 2969 int(guestWrite.Fd()): guestWrite, 2970 }, nil), 2971 } 2972 2973 if _, err = cont.Execute(conf, execArgs); err != nil { 2974 t.Fatalf("Failed to execute command: %v", err) 2975 } 2976 2977 // We close guestWrite here because it has been passed into the container. 2978 // If we do not close it, we will never see an EOF. 2979 guestWrite.Close() 2980 2981 msg := "hello" 2982 got, err := writeAndReadFromPipe(hostWrite, hostRead, msg) 2983 if err != nil { 2984 t.Fatal(err) 2985 } 2986 if got != msg { 2987 t.Errorf("got message %q, want %q", got, msg) 2988 } 2989 } 2990 2991 // findInPath finds a filename in the PATH environment variable. 2992 func findInPath(filename string) string { 2993 for _, dir := range strings.Split(os.Getenv("PATH"), ":") { 2994 fullPath := filepath.Join(dir, filename) 2995 if _, err := os.Stat(fullPath); err == nil { 2996 return fullPath 2997 } 2998 } 2999 return "" 3000 } 3001 3002 // TestExecFDRun checks that an executable from the host can be started inside 3003 // a container. 3004 func TestExecFDRun(t *testing.T) { 3005 // In the guest, read from the host and write the result back to the host. 3006 conf := testutil.TestConfig(t) 3007 // Note that we do not supply the name or path of the echo binary here. 3008 // Thus, the guest does not know the binary path or name either. 3009 // argv[0] inside echo is "can be anything". 3010 spec := testutil.NewSpecWithArgs("can be anything", "hello world") 3011 3012 _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 3013 if err != nil { 3014 t.Fatalf("error setting up container: %v", err) 3015 } 3016 defer cleanup() 3017 3018 // Find the echo binary on the host. 3019 echoPath := findInPath("echo") 3020 if echoPath == "" { 3021 t.Fatalf("failed to find echo executable in PATH") 3022 } 3023 3024 // Open the echo binary as a file. 3025 echoFile, err := os.Open(echoPath) 3026 if err != nil { 3027 t.Fatalf("opening echo binary: %v", err) 3028 } 3029 defer echoFile.Close() 3030 3031 r, w, err := os.Pipe() 3032 if err != nil { 3033 t.Fatalf("creating pipe: %v", err) 3034 } 3035 defer r.Close() 3036 3037 args := Args{ 3038 ID: testutil.RandomContainerID(), 3039 Spec: spec, 3040 BundleDir: bundleDir, 3041 PassFiles: map[int]*os.File{ 3042 0: os.Stdin, 1: w, 2: w, 3043 }, 3044 ExecFile: echoFile, 3045 } 3046 3047 cont, err := New(conf, args) 3048 if err != nil { 3049 t.Fatalf("Creating container: %v", err) 3050 } 3051 defer cont.Destroy() 3052 3053 if err := cont.Start(conf); err != nil { 3054 t.Fatalf("starting container: %v", err) 3055 } 3056 3057 w.Close() 3058 3059 got, err := io.ReadAll(r) 3060 if err != nil { 3061 t.Errorf("reading container output: %v", err) 3062 } 3063 if want := "hello world\n"; string(got) != want { 3064 t.Errorf("got message %q, want %q", got, want) 3065 } 3066 } 3067 3068 // TestExecFDExec checks that an executable from the host can be started from a 3069 // file descriptor inside an already running container. 3070 func TestExecFDExec(t *testing.T) { 3071 // We just sleep here because we want to test execution in an already 3072 // running container. 3073 spec, conf := sleepSpecConf(t) 3074 _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 3075 if err != nil { 3076 t.Fatalf("error setting up container: %v", err) 3077 } 3078 defer cleanup() 3079 3080 args := Args{ 3081 ID: testutil.RandomContainerID(), 3082 Spec: spec, 3083 BundleDir: bundleDir, 3084 } 3085 3086 cont, err := New(conf, args) 3087 if err != nil { 3088 t.Fatalf("Creating container: %v", err) 3089 } 3090 defer cont.Destroy() 3091 3092 if err := cont.Start(conf); err != nil { 3093 t.Fatalf("starting container: %v", err) 3094 } 3095 3096 // Find the echo binary on the host. 3097 echoPath := findInPath("echo") 3098 if echoPath == "" { 3099 t.Fatalf("failed to find echo executable in PATH") 3100 } 3101 3102 // Open the echo binary as a file. 3103 echoFile, err := os.Open(echoPath) 3104 if err != nil { 3105 t.Fatalf("opening echo binary: %v", err) 3106 } 3107 defer echoFile.Close() 3108 3109 // Note that we do not supply the name or path of the echo binary here. 3110 // Thus, the guest does not know the binary path or name either. 3111 // argv[0] inside echo is "can be anything". 3112 got, err := executeCombinedOutput(conf, cont, echoFile, "can be anything", "hello world") 3113 if err != nil { 3114 t.Fatal(err) 3115 } 3116 if want := "hello world\n"; string(got) != want { 3117 t.Errorf("echo result, got: %q, want: %q", got, want) 3118 } 3119 } 3120 3121 // skipIfNotAvailable skips the test if the requested executable files are not available. 3122 func skipIfNotAvailable(t *testing.T, files ...string) { 3123 for _, f := range files { 3124 if _, err := exec.LookPath(f); err != nil { 3125 t.Skipf("%v is not available: %v", f, err) 3126 } 3127 } 3128 } 3129 3130 // createImageEROFS creates the EROFS image from the source directory using the requested options. 3131 func createImageEROFS(image, source string, options ...string) error { 3132 mkfs, err := exec.LookPath("mkfs.erofs") 3133 if err != nil { 3134 return fmt.Errorf("mkfs.erofs is not available: %v", err) 3135 } 3136 cmd := fmt.Sprintf("%s %s %s %s", mkfs, strings.Join(options, " "), image, source) 3137 if out, err := exec.Command("/bin/sh", "-c", cmd).CombinedOutput(); err != nil { 3138 return fmt.Errorf("exec: sh -c %q, err: %v, out: %s", cmd, err, out) 3139 } 3140 return nil 3141 } 3142 3143 // TestMountEROFS checks that the checksums from the target directory in the container 3144 // are identical with the ones from the source directory on the host. 3145 func TestMountEROFS(t *testing.T) { 3146 // Skip this test if mkfs.erofs is not available. 3147 skipIfNotAvailable(t, "mkfs.erofs") 3148 3149 // Create a temporary directory to save the test files. 3150 testDir, err := ioutil.TempDir(testutil.TmpDir(), "erofs_mount_test_") 3151 if err != nil { 3152 t.Fatalf("ioutil.TempDir() failed: %v", err) 3153 } 3154 defer os.RemoveAll(testDir) 3155 3156 // Create a temporary directory with some random files in it, which will 3157 // be used as the source directory to create the EROFS images. 3158 sourceDir := filepath.Join(testDir, "source") 3159 if err := os.Mkdir(sourceDir, 0755); err != nil { 3160 t.Fatalf("os.Mkdir() failed: %v", err) 3161 } 3162 // Create some files with leading non-alphanumeric characters in name. It's helpful 3163 // to verify the on-disk directory entries order. 3164 for _, c := range []byte("!#$%&()*+,-:;<=>?@[]^_`{|}~") { 3165 name := fmt.Sprintf("%s/%c_file", sourceDir, c) 3166 // Create the file with random data. 3167 if err := ioutil.WriteFile(name, []byte(fmt.Sprintf("%v", rand.Uint64())), 0644); err != nil { 3168 t.Fatalf("error creating %q: %v", name, err) 3169 } 3170 } 3171 testApp, err := testutil.FindFile("test/cmd/test_app/test_app") 3172 if err != nil { 3173 t.Fatalf("error finding test_app: %v", err) 3174 } 3175 // Source directory is a small directory. Let's create a big directory in it. 3176 // So we can cover both cases. 3177 cmd := fmt.Sprintf("%s fsTreeCreate --target-dir=%s --create-symlink --depth=1 --file-per-level=500 --file-size=5000", testApp, filepath.Join(sourceDir, "big-directory")) 3178 if out, err := exec.Command("/bin/sh", "-c", cmd).CombinedOutput(); err != nil { 3179 t.Fatalf("exec: sh -c %q, err: %v, out: %s", cmd, err, out) 3180 } 3181 3182 // Create a test script which can be used to get the checksums 3183 // from a specified directory. 3184 scriptFile := filepath.Join(testDir, "test-script") 3185 if err := os.WriteFile(scriptFile, []byte(`#!/bin/bash 3186 set -u -e -o pipefail 3187 dir=$1 3188 find $dir -printf "%P\n" | sort | md5sum 3189 find $dir -type l | sort | xargs -L 1 readlink | md5sum 3190 find $dir -type l -o -type f | sort | xargs cat | md5sum`), 0755); err != nil { 3191 t.Fatalf("os.WriteFile() failed: %v", err) 3192 } 3193 3194 // Get the checksums from the source directory on the host. 3195 var checksums string 3196 if out, err := exec.Command(scriptFile, sourceDir).CombinedOutput(); err != nil { 3197 t.Fatalf("exec: %s %s, err: %v, out: %s", scriptFile, sourceDir, err, out) 3198 } else { 3199 checksums = string(out) 3200 } 3201 3202 images := []struct { 3203 name string 3204 options string 3205 }{ 3206 { 3207 // Generate extended inodes. Inline regular files if possible. 3208 name: "image1", 3209 options: "-E force-inode-extended", 3210 }, 3211 { 3212 // Generate extended inodes. Do not inline regular files. 3213 name: "image2", 3214 options: "-E force-inode-extended -E noinline_data", 3215 }, 3216 { 3217 // Generate compact inodes. Inline regular files if possible. 3218 name: "image3", 3219 options: "-E force-inode-compact", 3220 }, 3221 { 3222 // Generate compact inodes. Do not inline regular files. 3223 name: "image4", 3224 options: "-E force-inode-compact -E noinline_data", 3225 }, 3226 } 3227 3228 // Create the EROFS images. 3229 for _, i := range images { 3230 if err := createImageEROFS(filepath.Join(testDir, i.name), sourceDir, i.options); err != nil { 3231 t.Fatalf("error creating EROFS image: %v", err) 3232 } 3233 } 3234 3235 spec, conf := sleepSpecConf(t) 3236 _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 3237 if err != nil { 3238 t.Fatalf("error setting up container: %v", err) 3239 } 3240 defer cleanup() 3241 3242 // Create and start the container. 3243 args := Args{ 3244 ID: testutil.RandomContainerID(), 3245 Spec: spec, 3246 BundleDir: bundleDir, 3247 } 3248 c, err := New(conf, args) 3249 if err != nil { 3250 t.Fatalf("error creating container: %v", err) 3251 } 3252 defer c.Destroy() 3253 if err := c.Start(conf); err != nil { 3254 t.Fatalf("error starting container: %v", err) 3255 } 3256 3257 targetDir := "/mnt" 3258 for _, i := range images { 3259 // Mount the EROFS image in the container. 3260 imageFile := filepath.Join(testDir, i.name) 3261 if err := c.Sandbox.Mount(c.ID, erofs.Name, imageFile, targetDir); err != nil { 3262 t.Fatalf("error mounting EROFS image %q at %q, err: %v", imageFile, targetDir, err) 3263 } 3264 3265 // Get the checksums from the target directory in the container, and check if they are 3266 // identical with the ones got from the source directory on the host. 3267 if out, err := executeCombinedOutput(conf, c, nil, scriptFile, targetDir); err != nil { 3268 t.Fatalf("exec: %s %s, err: %v, out: %s", scriptFile, targetDir, err, out) 3269 } else if checksums != string(out) { 3270 t.Errorf("checksums do not match, got: %s from %s, expected: %s", out, imageFile, checksums) 3271 } 3272 3273 // Unmount the EROFS image in the container. 3274 if out, err := executeCombinedOutput(conf, c, nil, "/bin/umount", targetDir); err != nil { 3275 t.Fatalf("exec: umount %q, err: %v, out: %s", targetDir, err, out) 3276 } 3277 } 3278 } 3279 3280 // createRootfsEROFS creates a rootfs directory and an EROFS rootfs image in 3281 // the directory dir. 3282 func createRootfsEROFS(dir string) (string, string, error) { 3283 // Create a rootfs directory with busybox in root. 3284 rootfsDir := filepath.Join(dir, "rootfs") 3285 if err := os.Mkdir(rootfsDir, 0755); err != nil { 3286 return "", "", fmt.Errorf("os.Mkdir() failed: %v", err) 3287 } 3288 busybox, err := exec.LookPath("busybox") 3289 if err != nil { 3290 return "", "", fmt.Errorf("busybox is not available: %v", err) 3291 } 3292 if err := testutil.Copy(busybox, filepath.Join(rootfsDir, "busybox")); err != nil { 3293 return "", "", fmt.Errorf("failed to copy busybox: %v", err) 3294 } 3295 3296 // Handcraft the following mount points that the sentry mounts need, because EROFS 3297 // does not support creating synthetic directories yet and we may not want to use 3298 // overlay in some tests. 3299 for _, dir := range []string{"dev", "proc", "sys", "tmp"} { 3300 if err := os.Mkdir(filepath.Join(rootfsDir, dir), 0755); err != nil { 3301 return "", "", fmt.Errorf("os.Mkdir() failed: %v", err) 3302 } 3303 } 3304 3305 // Build the EROFS rootfs image. 3306 rootfsImage := filepath.Join(dir, "rootfs.img") 3307 if err := createImageEROFS(rootfsImage, rootfsDir, "-E noinline_data"); err != nil { 3308 return "", "", fmt.Errorf("error creating EROFS image: %v", err) 3309 } 3310 3311 return rootfsDir, rootfsImage, nil 3312 } 3313 3314 // TestRootfsEROFS starts a container using an EROFS image as the rootfs and checks that 3315 // the rootfs in the container is an EROFS. 3316 func TestRootfsEROFS(t *testing.T) { 3317 // Skip this test if mkfs.erofs or busybox are not available. 3318 skipIfNotAvailable(t, "mkfs.erofs", "busybox") 3319 3320 testDir, err := ioutil.TempDir(testutil.TmpDir(), "erofs_rootfs_test_") 3321 if err != nil { 3322 t.Fatalf("ioutil.TempDir() failed: %v", err) 3323 } 3324 defer os.RemoveAll(testDir) 3325 3326 rootfsDir, rootfsImage, err := createRootfsEROFS(testDir) 3327 if err != nil { 3328 t.Fatalf("failed to create EROFS rootfs image: %v", err) 3329 } 3330 3331 // Create the spec and set the EROFS rootfs annotations. 3332 spec := testutil.NewSpecWithArgs("/busybox", "grep", "/ / ro - erofs", "/proc/self/mountinfo") 3333 spec.Root.Path = rootfsDir 3334 if spec.Annotations == nil { 3335 spec.Annotations = make(map[string]string) 3336 } 3337 spec.Annotations[boot.RootfsPrefix+"type"] = erofs.Name 3338 spec.Annotations[boot.RootfsPrefix+"source"] = rootfsImage 3339 // Disable the overlay, as we want to be sure that rootfs will always be 3340 // shown as EROFS in mountinfo. 3341 spec.Annotations[boot.RootfsPrefix+"overlay"] = config.NoOverlay.String() 3342 3343 conf := testutil.TestConfig(t) 3344 3345 for _, mounts := range [][]specs.Mount{ 3346 // Case 1: EROFS rootfs without any other gofer mount. 3347 nil, 3348 3349 // Case 2: EROFS rootfs with a LISAFS backed gofer mount. 3350 []specs.Mount{ 3351 { 3352 Type: "bind", 3353 Destination: "/tmp", 3354 Source: "/tmp", 3355 }, 3356 }, 3357 } { 3358 spec.Mounts = mounts 3359 3360 _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 3361 if err != nil { 3362 t.Fatalf("error setting up container: %v", err) 3363 } 3364 defer cleanup() 3365 3366 // Create and start the container. 3367 args := Args{ 3368 ID: testutil.RandomContainerID(), 3369 Spec: spec, 3370 BundleDir: bundleDir, 3371 Attached: true, 3372 } 3373 ws, err := Run(conf, args) 3374 if err != nil { 3375 t.Fatalf("error running container: %v", err) 3376 } 3377 if ws.ExitStatus() != 0 { 3378 t.Errorf("got exit status %v want %v", ws.ExitStatus(), 0) 3379 } 3380 } 3381 } 3382 3383 // TestCheckpointRestoreEROFS does the checkpoint/restore test on each platform using 3384 // an EROFS image as the rootfs. 3385 func TestCheckpointRestoreEROFS(t *testing.T) { 3386 // Skip this test if mkfs.erofs or busybox are not available. 3387 skipIfNotAvailable(t, "mkfs.erofs", "busybox") 3388 3389 testDir, err := ioutil.TempDir(testutil.TmpDir(), "erofs_checkpoint_restore_test_") 3390 if err != nil { 3391 t.Fatalf("ioutil.TempDir() failed: %v", err) 3392 } 3393 defer os.RemoveAll(testDir) 3394 3395 rootfsDir, rootfsImage, err := createRootfsEROFS(testDir) 3396 if err != nil { 3397 t.Fatalf("failed to create EROFS rootfs image: %v", err) 3398 } 3399 3400 // Skip overlay because test requires writing to host file. 3401 for name, conf := range configs(t, true /* noOverlay */) { 3402 t.Run(name, func(t *testing.T) { 3403 testCheckpointRestore(t, conf, statefile.CompressionLevelDefault, func(script string) *specs.Spec { 3404 spec := testutil.NewSpecWithArgs("/busybox", "sh", "-c", script) 3405 spec.Root = &specs.Root{ 3406 Path: rootfsDir, 3407 Readonly: false, 3408 } 3409 if spec.Annotations == nil { 3410 spec.Annotations = make(map[string]string) 3411 } 3412 spec.Annotations[boot.RootfsPrefix+"type"] = erofs.Name 3413 spec.Annotations[boot.RootfsPrefix+"source"] = rootfsImage 3414 // EROFS does not support creating synthetic directories yet, so let's add 3415 // a writeable and savable overlay for rootfs, which allows the sentry to 3416 // create the mount point for the bind mount of the temporary directory shared 3417 // between host and test container. 3418 spec.Annotations[boot.RootfsPrefix+"overlay"] = config.MemoryOverlay.String() 3419 return spec 3420 }) 3421 }) 3422 } 3423 } 3424 3425 // TestLookupEROFS reads the files in EROFS images, which contain some random files, 3426 // and checks if the data is as expected. 3427 func TestLookupEROFS(t *testing.T) { 3428 // Skip this test if mkfs.erofs is not available. 3429 skipIfNotAvailable(t, "mkfs.erofs") 3430 3431 // Create a temporary directory to save the test files. 3432 testDir, err := ioutil.TempDir(testutil.TmpDir(), "erofs_lookup_test_") 3433 if err != nil { 3434 t.Fatalf("ioutil.TempDir() failed: %v", err) 3435 } 3436 defer os.RemoveAll(testDir) 3437 3438 spec, conf := sleepSpecConf(t) 3439 _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) 3440 if err != nil { 3441 t.Fatalf("error setting up container: %v", err) 3442 } 3443 defer cleanup() 3444 3445 // Create and start the container. 3446 args := Args{ 3447 ID: testutil.RandomContainerID(), 3448 Spec: spec, 3449 BundleDir: bundleDir, 3450 } 3451 c, err := New(conf, args) 3452 if err != nil { 3453 t.Fatalf("error creating container: %v", err) 3454 } 3455 defer c.Destroy() 3456 if err := c.Start(conf); err != nil { 3457 t.Fatalf("error starting container: %v", err) 3458 } 3459 3460 tcs := []struct { 3461 name string 3462 size int 3463 }{ 3464 { 3465 name: "tiny", 3466 size: 1, 3467 }, 3468 { 3469 name: "small", 3470 size: 10, 3471 }, 3472 { 3473 name: "medium", 3474 size: 100, 3475 }, 3476 { 3477 name: "large", 3478 size: 1000, 3479 }, 3480 } 3481 3482 targetDir := "/mnt" 3483 for _, tc := range tcs { 3484 // Add some randomness to the number of files. 3485 size := tc.size + rand.Intn(tc.size) 3486 3487 // Create a temporary directory with some random files in it, which will 3488 // be used as the source directory to create the EROFS image. 3489 sourceDir := filepath.Join(testDir, tc.name) 3490 if err := os.Mkdir(sourceDir, 0755); err != nil { 3491 t.Fatalf("os.Mkdir() failed: %v", err) 3492 } 3493 randomFiles := make([]string, 0, size) 3494 for i := 0; i < size; i++ { 3495 file, err := ioutil.TempFile(sourceDir, "") 3496 if err != nil { 3497 t.Fatalf("ioutil.TempFile() failed: %v", err) 3498 } 3499 name := filepath.Base(file.Name()) 3500 if _, err := file.Write([]byte(name)); err != nil { 3501 t.Fatalf("file.Write() failed: %v", err) 3502 } 3503 file.Close() 3504 randomFiles = append(randomFiles, name) 3505 } 3506 3507 // Create the EROFS image. 3508 imageFile := filepath.Join(testDir, fmt.Sprintf("%s.img", tc.name)) 3509 if err := createImageEROFS(imageFile, sourceDir); err != nil { 3510 t.Fatalf("error creating EROFS image: %v", err) 3511 } 3512 3513 // Mount the EROFS image in the container. 3514 if err := c.Sandbox.Mount(c.ID, erofs.Name, imageFile, targetDir); err != nil { 3515 t.Fatalf("error mounting EROFS image %q at %q, err: %v", imageFile, targetDir, err) 3516 } 3517 3518 // Read the files in the EROFS image and check if the data is as expected. 3519 for i, inc := 0, max(size/100, 1); i < size; i += inc { 3520 targetFile := randomFiles[i] 3521 cmd := fmt.Sprintf("cat %s", filepath.Join(targetDir, targetFile)) 3522 if out, err := executeCombinedOutput(conf, c, nil, "/bin/sh", "-c", cmd); err != nil { 3523 t.Fatalf("exec: sh -c %q, err: %v, out: %s", cmd, err, out) 3524 } else if targetFile != string(out) { 3525 t.Errorf("file does not match, got: %s, expected: %s", out, targetFile) 3526 } 3527 } 3528 3529 // Test for the read failure with a non-existent file. 3530 cmd := fmt.Sprintf("cat %s", filepath.Join(targetDir, "nonexist")) 3531 if out, err := executeCombinedOutput(conf, c, nil, "/bin/sh", "-c", cmd); err == nil { 3532 t.Errorf("exec: sh -c %q, succeeded to read the non-existent file: %s", cmd, out) 3533 } 3534 3535 // Unmount the EROFS image in the container. 3536 if out, err := executeCombinedOutput(conf, c, nil, "/bin/umount", targetDir); err != nil { 3537 t.Fatalf("exec: umount %q, err: %v, out: %s", targetDir, err, out) 3538 } 3539 } 3540 }