Merge pull request #54 from crosbymichael/report-child-error

Report child error to parent
This commit is contained in:
Michael Crosby 2014-06-26 14:38:11 -07:00
commit 53cfe0a1eb
5 changed files with 131 additions and 16 deletions

View File

@ -32,6 +32,7 @@ func Exec(container *libcontainer.Config, term Terminal, rootfs, dataPath string
if err != nil { if err != nil {
return -1, err return -1, err
} }
defer syncPipe.Close()
if container.Tty { if container.Tty {
master, console, err = system.CreateMasterAndConsole() master, console, err = system.CreateMasterAndConsole()
@ -52,6 +53,9 @@ func Exec(container *libcontainer.Config, term Terminal, rootfs, dataPath string
return -1, err return -1, err
} }
// Now we passed the pipe to the child, close our side
syncPipe.CloseChild()
started, err := system.GetProcessStartTime(command.Process.Pid) started, err := system.GetProcessStartTime(command.Process.Pid)
if err != nil { if err != nil {
return -1, err return -1, err
@ -90,7 +94,11 @@ func Exec(container *libcontainer.Config, term Terminal, rootfs, dataPath string
defer libcontainer.DeleteState(dataPath) defer libcontainer.DeleteState(dataPath)
// Sync with child // Sync with child
syncPipe.Close() if err := syncPipe.ReadFromChild(); err != nil {
command.Process.Kill()
command.Wait()
return -1, err
}
if startCallback != nil { if startCallback != nil {
startCallback() startCallback()

View File

@ -27,7 +27,13 @@ import (
// Move this to libcontainer package. // Move this to libcontainer package.
// Init is the init process that first runs inside a new namespace to setup mounts, users, networking, // Init is the init process that first runs inside a new namespace to setup mounts, users, networking,
// and other options required for the new container. // and other options required for the new container.
func Init(container *libcontainer.Config, uncleanRootfs, consolePath string, syncPipe *SyncPipe, args []string) error { func Init(container *libcontainer.Config, uncleanRootfs, consolePath string, syncPipe *SyncPipe, args []string) (err error) {
defer func() {
if err != nil {
syncPipe.ReportChildError(err)
}
}()
rootfs, err := utils.ResolveRootfs(uncleanRootfs) rootfs, err := utils.ResolveRootfs(uncleanRootfs)
if err != nil { if err != nil {
return err return err
@ -42,10 +48,8 @@ func Init(container *libcontainer.Config, uncleanRootfs, consolePath string, syn
// We always read this as it is a way to sync with the parent as well // We always read this as it is a way to sync with the parent as well
networkState, err := syncPipe.ReadFromParent() networkState, err := syncPipe.ReadFromParent()
if err != nil { if err != nil {
syncPipe.Close()
return err return err
} }
syncPipe.Close()
if consolePath != "" { if consolePath != "" {
if err := console.OpenAndDup(consolePath); err != nil { if err := console.OpenAndDup(consolePath); err != nil {

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"syscall"
"github.com/docker/libcontainer/network" "github.com/docker/libcontainer/network"
) )
@ -16,24 +17,17 @@ type SyncPipe struct {
parent, child *os.File parent, child *os.File
} }
func NewSyncPipe() (s *SyncPipe, err error) { func NewSyncPipeFromFd(parentFd, childFd uintptr) (*SyncPipe, error) {
s = &SyncPipe{}
s.child, s.parent, err = os.Pipe()
if err != nil {
return nil, err
}
return s, nil
}
func NewSyncPipeFromFd(parendFd, childFd uintptr) (*SyncPipe, error) {
s := &SyncPipe{} s := &SyncPipe{}
if parendFd > 0 {
s.parent = os.NewFile(parendFd, "parendPipe") if parentFd > 0 {
s.parent = os.NewFile(parentFd, "parentPipe")
} else if childFd > 0 { } else if childFd > 0 {
s.child = os.NewFile(childFd, "childPipe") s.child = os.NewFile(childFd, "childPipe")
} else { } else {
return nil, fmt.Errorf("no valid sync pipe fd specified") return nil, fmt.Errorf("no valid sync pipe fd specified")
} }
return s, nil return s, nil
} }
@ -50,7 +44,22 @@ func (s *SyncPipe) SendToChild(networkState *network.NetworkState) error {
if err != nil { if err != nil {
return err return err
} }
s.parent.Write(data) s.parent.Write(data)
return syscall.Shutdown(int(s.parent.Fd()), syscall.SHUT_WR)
}
func (s *SyncPipe) ReadFromChild() error {
data, err := ioutil.ReadAll(s.parent)
if err != nil {
return err
}
if len(data) > 0 {
return fmt.Errorf("%s", data)
}
return nil return nil
} }
@ -66,15 +75,28 @@ func (s *SyncPipe) ReadFromParent() (*network.NetworkState, error) {
} }
} }
return networkState, nil return networkState, nil
}
func (s *SyncPipe) ReportChildError(err error) {
s.child.Write([]byte(err.Error()))
s.CloseChild()
} }
func (s *SyncPipe) Close() error { func (s *SyncPipe) Close() error {
if s.parent != nil { if s.parent != nil {
s.parent.Close() s.parent.Close()
} }
if s.child != nil { if s.child != nil {
s.child.Close() s.child.Close()
} }
return nil return nil
} }
func (s *SyncPipe) CloseChild() {
if s.child != nil {
s.child.Close()
s.child = nil
}
}

View File

@ -0,0 +1,20 @@
package namespaces
import (
"os"
"syscall"
)
func NewSyncPipe() (s *SyncPipe, err error) {
s = &SyncPipe{}
fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0)
if err != nil {
return nil, err
}
s.child = os.NewFile(uintptr(fds[0]), "child syncpipe")
s.parent = os.NewFile(uintptr(fds[1]), "parent syncpipe")
return s, nil
}

View File

@ -0,0 +1,61 @@
package namespaces
import (
"fmt"
"testing"
"github.com/docker/libcontainer/network"
)
func TestSendErrorFromChild(t *testing.T) {
pipe, err := NewSyncPipe()
if err != nil {
t.Fatal(err)
}
defer func() {
if err := pipe.Close(); err != nil {
t.Fatal(err)
}
}()
expected := "something bad happened"
pipe.ReportChildError(fmt.Errorf(expected))
childError := pipe.ReadFromChild()
if childError == nil {
t.Fatal("expected an error to be returned but did not receive anything")
}
if childError.Error() != expected {
t.Fatalf("expected %q but received error message %q", expected, childError.Error())
}
}
func TestSendPayloadToChild(t *testing.T) {
pipe, err := NewSyncPipe()
if err != nil {
t.Fatal(err)
}
defer func() {
if err := pipe.Close(); err != nil {
t.Fatal(err)
}
}()
expected := "libcontainer"
if err := pipe.SendToChild(&network.NetworkState{VethHost: expected}); err != nil {
t.Fatal(err)
}
payload, err := pipe.ReadFromParent()
if err != nil {
t.Fatal(err)
}
if payload.VethHost != expected {
t.Fatalf("expected veth host %q but received %q", expected, payload.VethHost)
}
}