summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorFabricio Voznika <fvoznika@google.com>2018-08-21 13:13:34 -0700
committerShentubot <shentubot@google.com>2018-08-21 13:14:43 -0700
commitd6d165cb0b8147461388287ffd4cfee221940123 (patch)
tree8beb9ea0e792c6691385f5b9d7ca65f0a1eaf9dc
parent9c407382b031f16160f83383ef8b0d419457829a (diff)
Initial change for multi-gofer support
PiperOrigin-RevId: 209647293 Change-Id: I980fca1257ea3fcce796388a049c353b0303a8a5
-rw-r--r--runsc/cmd/debug.go2
-rw-r--r--runsc/container/BUILD2
-rw-r--r--runsc/container/container.go111
-rw-r--r--runsc/container/container_test.go47
-rw-r--r--runsc/sandbox/BUILD16
-rw-r--r--runsc/sandbox/sandbox.go85
-rw-r--r--runsc/sandbox/sandbox_test.go75
7 files changed, 139 insertions, 199 deletions
diff --git a/runsc/cmd/debug.go b/runsc/cmd/debug.go
index 87ad21c9a..7952489de 100644
--- a/runsc/cmd/debug.go
+++ b/runsc/cmd/debug.go
@@ -92,7 +92,7 @@ func (d *Debug) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
}
log.Infof("Found sandbox %q, PID: %d", c.Sandbox.ID, c.Sandbox.Pid)
- if !c.Sandbox.IsRunning() {
+ if !c.IsRunning() {
Fatalf("sandbox %q is not running", c.Sandbox.ID)
}
diff --git a/runsc/container/BUILD b/runsc/container/BUILD
index d4c650892..1171355c8 100644
--- a/runsc/container/BUILD
+++ b/runsc/container/BUILD
@@ -23,10 +23,10 @@ go_library(
deps = [
"//pkg/log",
"//pkg/sentry/control",
- "//pkg/syserror",
"//runsc/boot",
"//runsc/sandbox",
"//runsc/specutils",
+ "@com_github_cenkalti_backoff//:go_default_library",
"@com_github_opencontainers_runtime-spec//specs-go:go_default_library",
],
)
diff --git a/runsc/container/container.go b/runsc/container/container.go
index da2ce0d25..8bd47aac1 100644
--- a/runsc/container/container.go
+++ b/runsc/container/container.go
@@ -16,6 +16,7 @@
package container
import (
+ "context"
"encoding/json"
"fmt"
"io/ioutil"
@@ -27,10 +28,10 @@ import (
"syscall"
"time"
+ "github.com/cenkalti/backoff"
specs "github.com/opencontainers/runtime-spec/specs-go"
"gvisor.googlesource.com/gvisor/pkg/log"
"gvisor.googlesource.com/gvisor/pkg/sentry/control"
- "gvisor.googlesource.com/gvisor/pkg/syserror"
"gvisor.googlesource.com/gvisor/runsc/boot"
"gvisor.googlesource.com/gvisor/runsc/sandbox"
"gvisor.googlesource.com/gvisor/runsc/specutils"
@@ -89,6 +90,10 @@ type Container struct {
// Status is the current container Status.
Status Status `json:"status"`
+ // GoferPid is the pid of the gofer running along side the sandbox. May
+ // be 0 if the gofer has been killed or it's not being used.
+ GoferPid int `json:"goferPid"`
+
// Sandbox is the sandbox this container is running in. It will be nil
// if the container is not in state Running or Created.
Sandbox *sandbox.Sandbox `json:"sandbox"`
@@ -130,12 +135,11 @@ func Load(rootDir, id string) (*Container, error) {
// This is inherently racey.
if c.Status == Running || c.Status == Created {
// Check if the sandbox process is still running.
- if c.Sandbox.IsRunning() {
+ if c.IsRunning() {
// TODO: Send a message into the sandbox to
// see if this particular container is still running.
} else {
- // Sandbox no longer exists, so this container
- // definitely does not exist.
+ // Sandbox no longer exists, so this container definitely does not exist.
c.Status = Stopped
c.Sandbox = nil
}
@@ -221,12 +225,13 @@ func Create(id string, spec *specs.Spec, conf *boot.Config, bundleDir, consoleSo
log.Debugf("Creating new sandbox for container %q", id)
// Start a new sandbox for this container. Any errors after this point
// must destroy the container.
- s, err := sandbox.Create(id, spec, conf, bundleDir, consoleSocket)
+ s, goferPid, err := sandbox.Create(id, spec, conf, bundleDir, consoleSocket)
if err != nil {
c.Destroy()
return nil, err
}
c.Sandbox = s
+ c.GoferPid = goferPid
} else {
// This is sort of confusing. For a sandbox with a root
// container and a child container in it, runsc sees:
@@ -398,7 +403,18 @@ func (c *Container) WaitPID(pid int32) (syscall.WaitStatus, error) {
if c.Status == Stopped {
return 0, fmt.Errorf("container is stopped")
}
- return c.Sandbox.WaitPID(pid, c.ID)
+ ws, err := c.Sandbox.WaitPID(pid, c.ID)
+ if err != nil {
+ return 0, err
+ }
+ if c.Sandbox.IsRootContainer(c.ID) {
+ // If waiting for the root, give some time for the sandbox process to exit
+ // to prevent races with resources that might still be in use.
+ if err := c.waitForStopped(); err != nil {
+ return 0, err
+ }
+ }
+ return ws, nil
}
// Signal sends the signal to the container.
@@ -502,6 +518,14 @@ func (c *Container) Destroy() error {
log.Warningf("Failed to destroy sandbox %q: %v", c.Sandbox.ID, err)
}
}
+ if c.GoferPid != 0 {
+ log.Debugf("Killing gofer for container %q, PID: %d", c.ID, c.GoferPid)
+ if err := syscall.Kill(c.GoferPid, syscall.SIGKILL); err != nil {
+ log.Warningf("error sending signal %d to pid %d: %v", syscall.SIGKILL, c.GoferPid, err)
+ } else {
+ c.GoferPid = 0
+ }
+ }
c.Sandbox = nil
c.Status = Stopped
@@ -509,29 +533,38 @@ func (c *Container) Destroy() error {
return nil
}
-// DestroyAndWait frees all resources associated with the container
-// and waits for destroy to finish before returning.
-func (c *Container) DestroyAndWait() error {
- sandboxPid := c.Sandbox.Pid
- goferPid := c.Sandbox.GoferPid
-
- if err := c.Destroy(); err != nil {
- return fmt.Errorf("error destroying container %v: %v", c, err)
+// IsRunning returns true if the sandbox or gofer process is running.
+func (c *Container) IsRunning() bool {
+ if c.Status == Stopped {
+ return false
}
-
- if sandboxPid != 0 {
- if err := waitForDeath(sandboxPid, 5*time.Second); err != nil {
- return fmt.Errorf("error waiting for sandbox death: %v", err)
- }
+ if c.Sandbox != nil && c.Sandbox.IsRunning() {
+ return true
}
+ if c.GoferPid != 0 {
+ // Send a signal 0 to the gofer process.
+ if err := syscall.Kill(c.GoferPid, 0); err == nil {
+ log.Warningf("Found orphan gofer process, pid: %d", c.GoferPid)
+ // Attempt to kill gofer if it's orphan.
+ syscall.Kill(c.GoferPid, syscall.SIGKILL)
- if goferPid != 0 {
- if err := waitForDeath(goferPid, 5*time.Second); err != nil {
- return fmt.Errorf("error waiting for gofer death: %v", err)
+ // Don't wait for gofer to die. Return 'running' and hope gofer is dead
+ // next time around.
+ return true
}
}
+ return false
+}
- return nil
+// DestroyAndWait frees all resources associated with the container
+// and waits for destroy to finish before returning.
+//
+// TODO: This only works for single container.
+func (c *Container) DestroyAndWait() error {
+ if err := c.Destroy(); err != nil {
+ return fmt.Errorf("error destroying container %v: %v", c, err)
+ }
+ return c.waitForStopped()
}
// save saves the container metadata to a file.
@@ -551,29 +584,15 @@ func (c *Container) save() error {
return nil
}
-// waitForDeath ensures that process is dead before proceeding.
-//
-// This is racy because the kernel can potentially reuse the pid in the time
-// between the process' death and the first check after the process has ended.
-func waitForDeath(pid int, timeout time.Duration) error {
- backoff := 1 * time.Millisecond
- for start := time.Now(); time.Now().Sub(start) < timeout; {
-
- if err := syscall.Kill(pid, 0); err != nil {
- if err == syserror.ESRCH {
- // pid does not exist so process must have died
- return nil
- }
- return fmt.Errorf("error killing pid (%d): %v", pid, err)
- }
- // pid is still alive.
-
- // Process continues to run, backoff and retry.
- time.Sleep(backoff)
- backoff *= 2
- if backoff > 1*time.Second {
- backoff = 1 * time.Second
+func (c *Container) waitForStopped() error {
+ ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
+ defer cancel()
+ b := backoff.WithContext(backoff.NewConstantBackOff(100*time.Millisecond), ctx)
+ op := func() error {
+ if !c.IsRunning() {
+ return fmt.Errorf("container is still running")
}
+ return nil
}
- return fmt.Errorf("timed out waiting for process (%d)", pid)
+ return backoff.Retry(op, b)
}
diff --git a/runsc/container/container_test.go b/runsc/container/container_test.go
index a2da63afd..dadf8445b 100644
--- a/runsc/container/container_test.go
+++ b/runsc/container/container_test.go
@@ -350,7 +350,7 @@ func TestLifecycle(t *testing.T) {
// ourselves.
p, _ := os.FindProcess(s.Sandbox.Pid)
p.Wait()
- g, _ := os.FindProcess(s.Sandbox.GoferPid)
+ g, _ := os.FindProcess(s.GoferPid)
g.Wait()
// Load the container from disk and check the status.
@@ -1701,3 +1701,48 @@ func TestContainerVolumeContentsShared(t *testing.T) {
t.Errorf("stat %q got error %v, wanted ErrNotExist", filename, err)
}
}
+
+func TestGoferExits(t *testing.T) {
+ spec := testutil.NewSpecWithArgs("/bin/sleep", "10000")
+ conf := testutil.TestConfig()
+ rootDir, bundleDir, err := testutil.SetupContainer(spec, conf)
+ if err != nil {
+ t.Fatalf("error setting up container: %v", err)
+ }
+ defer os.RemoveAll(rootDir)
+ defer os.RemoveAll(bundleDir)
+
+ // Create and start the container.
+ c, err := container.Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "")
+ if err != nil {
+ t.Fatalf("error creating container: %v", err)
+ }
+ defer c.Destroy()
+ if err := c.Start(conf); err != nil {
+ t.Fatalf("error starting container: %v", err)
+ }
+
+ sandboxProc, err := os.FindProcess(c.Sandbox.Pid)
+ if err != nil {
+ t.Fatalf("error finding sandbox process: %v", err)
+ }
+ gofer, err := os.FindProcess(c.GoferPid)
+ if err != nil {
+ t.Fatalf("error finding sandbox process: %v", err)
+ }
+
+ // Kill sandbox and expect gofer to exit on its own.
+ if err := sandboxProc.Kill(); err != nil {
+ t.Fatalf("error killing sandbox process: %v", err)
+ }
+ if _, err := sandboxProc.Wait(); err != nil {
+ t.Fatalf("error waiting for sandbox process: %v", err)
+ }
+
+ if _, err := gofer.Wait(); err != nil {
+ t.Fatalf("error waiting for gofer process: %v", err)
+ }
+ if c.IsRunning() {
+ t.Errorf("container shouldn't be running, container: %+v", c)
+ }
+}
diff --git a/runsc/sandbox/BUILD b/runsc/sandbox/BUILD
index cdacc5e22..d26a4dac6 100644
--- a/runsc/sandbox/BUILD
+++ b/runsc/sandbox/BUILD
@@ -1,6 +1,6 @@
package(licenses = ["notice"]) # Apache 2.0
-load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "sandbox",
@@ -29,17 +29,3 @@ go_library(
"@org_golang_x_sys//unix:go_default_library",
],
)
-
-go_test(
- name = "sandbox_test",
- size = "small",
- srcs = ["sandbox_test.go"],
- data = [
- "//runsc",
- ],
- embed = [":sandbox"],
- deps = [
- "//pkg/log",
- "//runsc/test/testutil",
- ],
-)
diff --git a/runsc/sandbox/sandbox.go b/runsc/sandbox/sandbox.go
index a10b79856..e5d1f791d 100644
--- a/runsc/sandbox/sandbox.go
+++ b/runsc/sandbox/sandbox.go
@@ -40,46 +40,45 @@ import (
// It is used to start/stop sandbox process (and associated processes like
// gofers), as well as for running and manipulating containers inside a running
// sandbox.
+//
+// Note: Sandbox must be immutable because a copy of it is saved for each
+// container and changes would not be synchronized to all of them.
type Sandbox struct {
- // ID is the id of the sandbox. By convention, this is the same ID as
- // the first container run in the sandbox.
+ // ID is the id of the sandbox (immutable). By convention, this is the same
+ // ID as the first container run in the sandbox.
ID string `json:"id"`
- // Pid is the pid of the running sandbox. May be 0 is the sandbox is
- // not running.
+ // Pid is the pid of the running sandbox (immutable). May be 0 is the sandbox
+ // is not running.
Pid int `json:"pid"`
-
- // GoferPid is the pid of the gofer running along side the sandbox. May
- // be 0 if the gofer has been killed or it's not being used.
- GoferPid int `json:"goferPid"`
}
// Create creates the sandbox process.
-func Create(id string, spec *specs.Spec, conf *boot.Config, bundleDir, consoleSocket string) (*Sandbox, error) {
+func Create(id string, spec *specs.Spec, conf *boot.Config, bundleDir, consoleSocket string) (*Sandbox, int, error) {
s := &Sandbox{ID: id}
binPath, err := specutils.BinPath()
if err != nil {
- return nil, err
+ return nil, 0, err
}
// Create the gofer process.
- ioFiles, err := s.createGoferProcess(spec, conf, bundleDir, binPath)
+ goferPid, ioFiles, err := s.createGoferProcess(spec, conf, bundleDir, binPath)
if err != nil {
- return nil, err
+ return nil, 0, err
}
// Create the sandbox process.
if err := s.createSandboxProcess(spec, conf, bundleDir, consoleSocket, binPath, ioFiles); err != nil {
- return nil, err
+ return nil, 0, err
}
// Wait for the control server to come up (or timeout).
if err := s.waitForCreated(10 * time.Second); err != nil {
- return nil, err
+ return nil, 0, err
}
- return s, nil
+ return s, goferPid, nil
}
// StartRoot starts running the root container process inside the sandbox.
@@ -288,10 +287,10 @@ func (s *Sandbox) connError(err error) error {
return fmt.Errorf("error connecting to control server at pid %d: %v", s.Pid, err)
}
-func (s *Sandbox) createGoferProcess(spec *specs.Spec, conf *boot.Config, bundleDir, binPath string) ([]*os.File, error) {
+func (s *Sandbox) createGoferProcess(spec *specs.Spec, conf *boot.Config, bundleDir, binPath string) (int, []*os.File, error) {
if conf.FileAccess == boot.FileAccessDirect {
// Don't start a gofer. The sandbox will access host FS directly.
- return nil, nil
+ return 0, nil, nil
}
// Start with the general config flags.
@@ -315,7 +314,7 @@ func (s *Sandbox) createGoferProcess(spec *specs.Spec, conf *boot.Config, bundle
for nextFD = 3; nextFD-3 < mountCount; nextFD++ {
sandEnd, goferEnd, err := createSocketPair()
if err != nil {
- return nil, err
+ return 0, nil, err
}
defer goferEnd.Close()
sandEnds = append(sandEnds, sandEnd)
@@ -327,7 +326,7 @@ func (s *Sandbox) createGoferProcess(spec *specs.Spec, conf *boot.Config, bundle
addr := fsgofer.ControlSocketAddr(s.ID)
serverFD, err := server.CreateSocket(addr)
if err != nil {
- return nil, fmt.Errorf("error creating control server socket for sandbox %q: %v", s.ID, err)
+ return 0, nil, fmt.Errorf("error creating control server socket for sandbox %q: %v", s.ID, err)
}
// Add the control server fd.
@@ -349,11 +348,10 @@ func (s *Sandbox) createGoferProcess(spec *specs.Spec, conf *boot.Config, bundle
// Start the gofer in the given namespace.
log.Debugf("Starting gofer: %s %v", binPath, args)
if err := startInNS(cmd, nss); err != nil {
- return nil, err
+ return 0, nil, err
}
- s.GoferPid = cmd.Process.Pid
log.Infof("Gofer started, pid: %d", cmd.Process.Pid)
- return sandEnds, nil
+ return cmd.Process.Pid, sandEnds, nil
}
// createSocketPair creates a pair of files wrapping a socket pair.
@@ -562,24 +560,9 @@ func (s *Sandbox) WaitPID(pid int32, cid string) (syscall.WaitStatus, error) {
PID: pid,
CID: cid,
}
-
if err := conn.Call(boot.ContainerWaitPID, args, &ws); err != nil {
return ws, fmt.Errorf("error waiting on PID %d in sandbox %q: %v", pid, s.ID, err)
}
-
- if s.IsRootContainer(cid) {
- // If waiting for the root, give some time for the sandbox process to exit
- // to prevent races with resources that might still be in use.
- timeout := time.Now().Add(time.Second)
- log.Debugf("Waiting for the sandbox process to exit")
- for s.IsRunning() {
- if time.Now().After(timeout) {
- log.Debugf("Timeout waiting for sandbox process to exit")
- break
- }
- time.Sleep(100 * time.Millisecond)
- }
- }
return ws, nil
}
@@ -602,15 +585,8 @@ func (s *Sandbox) Destroy() error {
if s.Pid != 0 {
// TODO: Too harsh?
log.Debugf("Killing sandbox %q", s.ID)
- killProcess(s.Pid, unix.SIGKILL)
- s.Pid = 0
+ signalProcess(s.Pid, unix.SIGKILL)
}
- if s.GoferPid != 0 {
- log.Debugf("Killing gofer for sandbox %q", s.ID)
- killProcess(s.GoferPid, unix.SIGKILL)
- s.GoferPid = 0
- }
-
return nil
}
@@ -689,19 +665,8 @@ func (s *Sandbox) Resume(cid string) error {
func (s *Sandbox) IsRunning() bool {
if s.Pid != 0 {
// Send a signal 0 to the sandbox process.
- if err := killProcess(s.Pid, 0); err == nil {
- return true
- }
- }
- if s.GoferPid != 0 {
- // Send a signal 0 to the gofer process.
- if err := killProcess(s.GoferPid, 0); err == nil {
- log.Warningf("Found orphan gofer process, pid: %d", s.GoferPid)
- // Attempt to kill gofer if it's orphan.
- killProcess(s.GoferPid, unix.SIGKILL)
-
- // Don't wait for gofer to die. Return 'running' and hope gofer is dead
- // next time around.
+ if err := signalProcess(s.Pid, 0); err == nil {
+ // Succeeded, process is running.
return true
}
}
@@ -724,10 +689,10 @@ func (s *Sandbox) Stacks() (string, error) {
return stacks, nil
}
-// killProcess sends a signal to the host process (i.e. a sandbox or gofer
+// signalProcess sends a signal to the host process (i.e. a sandbox or gofer
// process). Sandbox.Signal should be used to send a signal to a process
// running inside the sandbox.
-func killProcess(pid int, sig syscall.Signal) error {
+func signalProcess(pid int, sig syscall.Signal) error {
if err := syscall.Kill(pid, sig); err != nil {
return fmt.Errorf("error sending signal %d to pid %d: %v", sig, pid, err)
}
diff --git a/runsc/sandbox/sandbox_test.go b/runsc/sandbox/sandbox_test.go
deleted file mode 100644
index 40337bc53..000000000
--- a/runsc/sandbox/sandbox_test.go
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright 2018 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package sandbox
-
-import (
- "os"
- "testing"
-
- "gvisor.googlesource.com/gvisor/pkg/log"
- "gvisor.googlesource.com/gvisor/runsc/test/testutil"
-)
-
-func init() {
- log.SetLevel(log.Debug)
- if err := testutil.ConfigureExePath(); err != nil {
- panic(err.Error())
- }
-}
-
-func TestGoferExits(t *testing.T) {
- spec := testutil.NewSpecWithArgs("/bin/sleep", "10000")
- conf := testutil.TestConfig()
- rootDir, bundleDir, err := testutil.SetupContainer(spec, conf)
- if err != nil {
- t.Fatalf("error setting up container: %v", err)
- }
- defer os.RemoveAll(rootDir)
- defer os.RemoveAll(bundleDir)
-
- // Create, start and wait for the container.
- s, err := Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "")
- if err != nil {
- t.Fatalf("error creating container: %v", err)
- }
- defer s.Destroy()
- if err := s.StartRoot(spec, conf); err != nil {
- t.Fatalf("error starting container: %v", err)
- }
-
- sandboxProc, err := os.FindProcess(s.Pid)
- if err != nil {
- t.Fatalf("error finding sandbox process: %v", err)
- }
- gofer, err := os.FindProcess(s.GoferPid)
- if err != nil {
- t.Fatalf("error finding sandbox process: %v", err)
- }
-
- // Kill sandbox and expect gofer to exit on its own.
- if err := sandboxProc.Kill(); err != nil {
- t.Fatalf("error killing sandbox process: %v", err)
- }
- if _, err := sandboxProc.Wait(); err != nil {
- t.Fatalf("error waiting for sandbox process: %v", err)
- }
-
- if _, err := gofer.Wait(); err != nil {
- t.Fatalf("error waiting for gofer process: %v", err)
- }
- if s.IsRunning() {
- t.Errorf("Sandbox shouldn't be running, sandbox: %+v", s)
- }
-}