From 31386185fe7c2079ee412a411e536a5bf9e9eb25 Mon Sep 17 00:00:00 2001 From: Nicolas Lacasse Date: Thu, 17 May 2018 11:54:36 -0700 Subject: Push signal-delivery and wait into the sandbox. This is another step towards multi-container support. Previously, we delivered signals directly to the sandbox process (which then forwarded the signal to PID 1 inside the sandbox). Similarly, we waited on a container by waiting on the sandbox process itself. This approach will not work when there are multiple containers inside the sandbox, and we need to signal/wait on individual containers. This CL adds two new messages, ContainerSignal and ContainerWait. These messages include the id of the container to signal/wait. The controller inside the sandbox receives these messages and signals/waits on the appropriate process inside the sandbox. The container id is plumbed into the sandbox, but it currently is not used. We still end up signaling/waiting on PID 1 in all cases. Once we actually have multiple containers inside the sandbox, we will need to keep some sort of map of container id -> pid (or possibly pid namespace), and signal/kill the appropriate process for the container. PiperOrigin-RevId: 197028366 Change-Id: I07b4d5dc91ecd2affc1447e6b4bdd6b0b7360895 --- runsc/boot/BUILD | 1 + runsc/boot/controller.go | 112 ++++++++++++++++++++++++++++++++-------------- runsc/boot/events.go | 7 +-- runsc/boot/loader.go | 21 +++++---- runsc/boot/loader_test.go | 14 +++--- 5 files changed, 100 insertions(+), 55 deletions(-) (limited to 'runsc/boot') diff --git a/runsc/boot/BUILD b/runsc/boot/BUILD index 16522c668..1746df988 100644 --- a/runsc/boot/BUILD +++ b/runsc/boot/BUILD @@ -25,6 +25,7 @@ go_library( "//pkg/control/server", "//pkg/cpuid", "//pkg/log", + "//pkg/sentry/arch", "//pkg/sentry/context", "//pkg/sentry/control", "//pkg/sentry/fs", diff --git a/runsc/boot/controller.go b/runsc/boot/controller.go index 60c42fc19..8fc0a9076 100644 --- a/runsc/boot/controller.go +++ b/runsc/boot/controller.go @@ -18,30 +18,39 @@ import ( "fmt" "gvisor.googlesource.com/gvisor/pkg/control/server" + "gvisor.googlesource.com/gvisor/pkg/sentry/arch" "gvisor.googlesource.com/gvisor/pkg/sentry/control" "gvisor.googlesource.com/gvisor/pkg/sentry/kernel" "gvisor.googlesource.com/gvisor/pkg/sentry/socket/epsocket" ) const ( - // ApplicationStart is the URPC endpoint for starting a sandboxed app. - ApplicationStart = "application.Start" + // ContainerEvent is the URPC endpoint for getting stats about the + // container used by "runsc events". + ContainerEvent = "containerManager.Event" - // ApplicationProcesses is the URPC endpoint for getting the list of - // processes running in a sandbox. - ApplicationProcesses = "application.Processes" + // ContainerExecute is the URPC endpoint for executing a command in a + // container.. + ContainerExecute = "containerManager.Execute" - // ApplicationExecute is the URPC endpoint for executing a command in a - // sandbox. - ApplicationExecute = "application.Execute" + // ContainerProcesses is the URPC endpoint for getting the list of + // processes running in a container. + ContainerProcesses = "containerManager.Processes" - // ApplicationEvent is the URPC endpoint for getting stats about the - // container used by "runsc events". - ApplicationEvent = "application.Event" + // ContainerSignal is used to send a signal to a container. + ContainerSignal = "containerManager.Signal" + + // ContainerWait is used to wait on the init process of the container + // and return its ExitStatus. + ContainerWait = "containerManager.Wait" // NetworkCreateLinksAndRoutes is the URPC endpoint for creating links // and routes in a network stack. NetworkCreateLinksAndRoutes = "Network.CreateLinksAndRoutes" + + // RootContainerStart is the URPC endpoint for starting a new sandbox + // with root container. + RootContainerStart = "containerManager.StartRoot" ) // ControlSocketAddr generates an abstract unix socket name for the given id. @@ -55,8 +64,8 @@ type controller struct { // srv is the contorl server. srv *server.Server - // app holds the application methods. - app *application + // manager holds the containerManager methods. + manager *containerManager } // newController creates a new controller and starts it listening. @@ -66,12 +75,12 @@ func newController(fd int, k *kernel.Kernel) (*controller, error) { return nil, err } - app := &application{ + manager := &containerManager{ startChan: make(chan struct{}), startResultChan: make(chan error), k: k, } - srv.Register(app) + srv.Register(manager) if eps, ok := k.NetworkStack().(*epsocket.Stack); ok { net := &Network{ @@ -85,44 +94,79 @@ func newController(fd int, k *kernel.Kernel) (*controller, error) { } return &controller{ - srv: srv, - app: app, + srv: srv, + manager: manager, }, nil } -// application contains methods that control the sandboxed application. -type application struct { - // startChan is used to signal when the application process should be - // started. +// containerManager manages sandboes containers. +type containerManager struct { + // startChan is used to signal when the root container process should + // be started. startChan chan struct{} - // startResultChan is used to signal when the application has started. Any - // errors encountered during startup will be sent to the channel. A nil value - // indicates success. + // startResultChan is used to signal when the root container has + // started. Any errors encountered during startup will be sent to the + // channel. A nil value indicates success. startResultChan chan error // k is the emulated linux kernel on which the sandboxed - // application runs. + // containers run. k *kernel.Kernel } -// Start will start the application process. -func (a *application) Start(_, _ *struct{}) error { - // Tell the application to start and wait for the result. - a.startChan <- struct{}{} - return <-a.startResultChan +// StartRoot will start the root container process. +func (cm *containerManager) StartRoot(_, _ *struct{}) error { + // Tell the root container to start and wait for the result. + cm.startChan <- struct{}{} + return <-cm.startResultChan } // Processes retrieves information about processes running in the sandbox. -func (a *application) Processes(_, out *[]*control.Process) error { - return control.Processes(a.k, out) +func (cm *containerManager) Processes(_, out *[]*control.Process) error { + return control.Processes(cm.k, out) } // Execute runs a command on a created or running sandbox. -func (a *application) Execute(e *control.ExecArgs, waitStatus *uint32) error { - proc := control.Proc{Kernel: a.k} +func (cm *containerManager) Execute(e *control.ExecArgs, waitStatus *uint32) error { + proc := control.Proc{Kernel: cm.k} if err := proc.Exec(e, waitStatus); err != nil { return fmt.Errorf("error executing: %+v: %v", e, err) } return nil } + +// Wait waits for the init process in the given container. +func (cm *containerManager) Wait(cid *string, waitStatus *uint32) error { + // TODO: Use the cid and wait on the init process in that + // container. Currently we just wait on PID 1 in the sandbox. + tg := cm.k.TaskSet().Root.ThreadGroupWithID(1) + if tg == nil { + return fmt.Errorf("cannot wait: no thread group with id 1") + } + tg.WaitExited() + *waitStatus = tg.ExitStatus().Status() + return nil +} + +// SignalArgs are arguments to the Signal method. +type SignalArgs struct { + // CID is the container id. + CID string + + // Signo is the signal to send to the process. + Signo int32 +} + +// Signal sends a signal to the init process of the container. +func (cm *containerManager) Signal(args *SignalArgs, _ *struct{}) error { + // TODO: Use the cid and send the signal to the init + // process in theat container. Currently we just signal PID 1 in the + // sandbox. + si := arch.SignalInfo{Signo: args.Signo} + t := cm.k.TaskSet().Root.TaskWithID(1) + if t == nil { + return fmt.Errorf("cannot signal: no task with id 1") + } + return t.SendSignal(&si) +} diff --git a/runsc/boot/events.go b/runsc/boot/events.go index ef6459b01..0eb75c14c 100644 --- a/runsc/boot/events.go +++ b/runsc/boot/events.go @@ -59,10 +59,11 @@ type Memory struct { Raw map[string]uint64 `json:"raw,omitempty"` } -func (a *application) Event(_ *struct{}, out *Event) error { +// Event gets the events from the container. +func (cm *containerManager) Event(_ *struct{}, out *Event) error { stats := &Stats{} - stats.populateMemory(a.k) - stats.populatePIDs(a.k) + stats.populateMemory(cm.k) + stats.populatePIDs(cm.k) *out = Event{Type: "stats", Data: stats} return nil } diff --git a/runsc/boot/loader.go b/runsc/boot/loader.go index 34a25241f..0ff54d349 100644 --- a/runsc/boot/loader.go +++ b/runsc/boot/loader.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package boot loads the kernel and runs the application. +// Package boot loads the kernel and runs a container.. package boot import ( @@ -57,7 +57,7 @@ import ( _ "gvisor.googlesource.com/gvisor/pkg/sentry/socket/unix" ) -// Loader keeps state needed to start the kernel and run the application. +// Loader keeps state needed to start the kernel and run the container.. type Loader struct { // k is the kernel. k *kernel.Kernel @@ -73,10 +73,10 @@ type Loader struct { watchdog *watchdog.Watchdog // stopSignalForwarding disables forwarding of signals to the sandboxed - // app. It should be called when a sandbox is destroyed. + // container. It should be called when a sandbox is destroyed. stopSignalForwarding func() - // procArgs refers to the initial application task. + // procArgs refers to the root container task. procArgs kernel.CreateProcessArgs } @@ -283,10 +283,10 @@ func createPlatform(conf *Config) (platform.Platform, error) { } } -// Run runs the application. +// Run runs the root container.. func (l *Loader) Run() error { err := l.run() - l.ctrl.app.startResultChan <- err + l.ctrl.manager.startResultChan <- err if err != nil { // Give the controller some time to send the error to the // runtime. If we return too quickly here the process will exit @@ -321,7 +321,7 @@ func (l *Loader) run() error { } } - // Create the initial application task. + // Create the root container init task. if _, err := l.k.CreateProcess(l.procArgs); err != nil { return fmt.Errorf("failed to create init process: %v", err) } @@ -335,13 +335,12 @@ func (l *Loader) run() error { // WaitForStartSignal waits for a start signal from the control server. func (l *Loader) WaitForStartSignal() { - <-l.ctrl.app.startChan + <-l.ctrl.manager.startChan } -// WaitExit waits for the application to exit, and returns the application's -// exit status. +// WaitExit waits for the root container to exit, and returns its exit status. func (l *Loader) WaitExit() kernel.ExitStatus { - // Wait for application. + // Wait for container. l.k.WaitExited() return l.k.GlobalInit().ExitStatus() diff --git a/runsc/boot/loader_test.go b/runsc/boot/loader_test.go index c3d9887fa..d2e5fe74e 100644 --- a/runsc/boot/loader_test.go +++ b/runsc/boot/loader_test.go @@ -72,13 +72,13 @@ func TestRun(t *testing.T) { var wg sync.WaitGroup wg.Add(1) go func() { - resultChanErr = <-s.ctrl.app.startResultChan + resultChanErr = <-s.ctrl.manager.startResultChan wg.Done() }() - // Run the application. + // Run the container.. if err := s.Run(); err != nil { - t.Errorf("error running application: %v", err) + t.Errorf("error running container: %v", err) } // We should have not gotten an error on the startResultChan. @@ -112,7 +112,7 @@ func TestStartSignal(t *testing.T) { go func() { s.WaitForStartSignal() // Pretend that Run() executed and returned no error. - s.ctrl.app.startResultChan <- nil + s.ctrl.manager.startResultChan <- nil waitFinished <- struct{}{} }() @@ -126,9 +126,9 @@ func TestStartSignal(t *testing.T) { // OK. } - // Trigger the control server Start method. - if err := s.ctrl.app.Start(nil, nil); err != nil { - t.Errorf("error calling Start: %v", err) + // Trigger the control server StartRoot method. + if err := s.ctrl.manager.StartRoot(nil, nil); err != nil { + t.Errorf("error calling StartRoot: %v", err) } // Now WaitForStartSignal should return (within a short amount of -- cgit v1.2.3