diff --git a/cmd/containerd-shim/main.go b/cmd/containerd-shim/main.go index 8dcfb2f7b..2ea56eec6 100644 --- a/cmd/containerd-shim/main.go +++ b/cmd/containerd-shim/main.go @@ -67,6 +67,7 @@ func main() { } }() for s := range signals { + logrus.WithField("signal", s).Debug("received signal") switch s { case syscall.SIGCHLD: exits, err := utils.Reap(false) diff --git a/cmd/containerd-shim/process.go b/cmd/containerd-shim/process.go index d901e4867..58d4d0cec 100644 --- a/cmd/containerd-shim/process.go +++ b/cmd/containerd-shim/process.go @@ -35,6 +35,7 @@ type checkpoint struct { EmptyNS []string `json:"emptyNS,omitempty"` } +/* type processState struct { Terminal bool `json:"terminal"` Exec bool `json:"exec"` @@ -48,6 +49,7 @@ type processState struct { RootUID int `json:"rootUID"` RootGID int `json:"rootGID"` } +*/ type process struct { sync.WaitGroup @@ -62,9 +64,14 @@ type process struct { stdinCloser io.Closer console *os.File consolePath string - state *processState runtime string exitStatus int + Stdin string `json:"containerdStdin"` + Stdout string `json:"containerdStdout"` + Stderr string `json:"containerdStderr"` + Terminal bool `json:"terminal"` + RootUID int `json:"rootUID"` + RootGID int `json:"rootGID"` } func newProcess(id, bundle, runtimeName string) (*process, error) { @@ -73,38 +80,12 @@ func newProcess(id, bundle, runtimeName string) (*process, error) { bundle: bundle, runtime: runtimeName, } - s, err := loadProcess() - if err != nil { - return nil, err - } - p.state = s - if s.CheckpointPath != "" { - cpt, err := loadCheckpoint(s.CheckpointPath) - if err != nil { - return nil, err - } - p.checkpoint = cpt - p.checkpointPath = s.CheckpointPath - } if err := p.openIO(); err != nil { return nil, err } return p, nil } -func loadProcess() (*processState, error) { - f, err := os.Open("process.json") - if err != nil { - return nil, err - } - defer f.Close() - var s processState - if err := json.NewDecoder(f).Decode(&s); err != nil { - return nil, err - } - return &s, nil -} - func loadCheckpoint(checkpointPath string) (*checkpoint, error) { f, err := os.Open(filepath.Join(checkpointPath, "config.json")) if err != nil { @@ -118,21 +99,21 @@ func loadCheckpoint(checkpointPath string) (*checkpoint, error) { return &cpt, nil } -func (p *process) create() error { +func (p *process) create(isExec bool) error { cwd, err := os.Getwd() if err != nil { return err } logPath := filepath.Join(cwd, "log.json") - args := append([]string{ + args := []string{ "--log", logPath, "--log-format", "json", - }, p.state.RuntimeArgs...) - if p.state.Exec { + } + if isExec { args = append(args, "exec", "-d", "--process", filepath.Join(cwd, "process.json"), - "--console", p.consolePath, + "--console-socket", p.consolePath, ) } else if p.checkpoint != nil { args = append(args, "restore", @@ -152,9 +133,11 @@ func (p *process) create() error { if p.checkpoint.UnixSockets { add("--ext-unix-sk") } - if p.state.NoPivotRoot { - add("--no-pivot") - } + /* + if p.state.NoPivotRoot { + add("--no-pivot") + } + */ for _, ns := range p.checkpoint.EmptyNS { add("--empty-ns", ns) } @@ -162,11 +145,13 @@ func (p *process) create() error { } else { args = append(args, "create", "--bundle", p.bundle, - "--console", p.consolePath, + "--console-socket", p.consolePath, ) - if p.state.NoPivotRoot { - args = append(args, "--no-pivot") - } + /* + if p.state.NoPivotRoot { + args = append(args, "--no-pivot") + } + */ } args = append(args, "--pid-file", filepath.Join(cwd, "pid"), @@ -174,9 +159,11 @@ func (p *process) create() error { ) cmd := exec.Command(p.runtime, args...) cmd.Dir = p.bundle - cmd.Stdin = p.stdio.stdin - cmd.Stdout = p.stdio.stdout - cmd.Stderr = p.stdio.stderr + if p.stdio != nil { + cmd.Stdin = p.stdio.stdin + cmd.Stdout = p.stdio.stdout + cmd.Stderr = p.stdio.stderr + } // Call out to setPDeathSig to set SysProcAttr as elements are platform specific cmd.SysProcAttr = setPDeathSig() @@ -192,8 +179,10 @@ func (p *process) create() error { // Since current logic dictates that we need a pid at the end of p.create // we need to call runtime start as well on Solaris hence we need the // pipes to stay open. - p.stdio.stdout.Close() - p.stdio.stderr.Close() + if p.stdio != nil { + p.stdio.stdout.Close() + p.stdio.stderr.Close() + } } if err := cmd.Wait(); err != nil { if _, ok := err.(*exec.ExitError); ok { @@ -218,13 +207,11 @@ func (p *process) pid() int { } func (p *process) delete() error { - if !p.state.Exec { - cmd := exec.Command(p.runtime, append(p.state.RuntimeArgs, "delete", p.id)...) - cmd.SysProcAttr = setPDeathSig() - out, err := cmd.CombinedOutput() - if err != nil { - return fmt.Errorf("%s: %v", out, err) - } + cmd := exec.Command(p.runtime, "delete", p.id) + cmd.SysProcAttr = setPDeathSig() + out, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("%s: %v", out, err) } return nil } @@ -276,11 +263,14 @@ func (p *process) initializeIO(rootuid int) (i *IO, err error) { } func (p *process) Close() error { + if p.stdio == nil { + return nil + } return p.stdio.Close() } func (p *process) start() error { - cmd := exec.Command(p.runtime, append(p.state.RuntimeArgs, "start", p.id)...) + cmd := exec.Command(p.runtime, "start", p.id) cmd.SysProcAttr = setPDeathSig() out, err := cmd.CombinedOutput() if err != nil { diff --git a/cmd/containerd-shim/process_linux.go b/cmd/containerd-shim/process_linux.go index f42af5784..453bb42bf 100644 --- a/cmd/containerd-shim/process_linux.go +++ b/cmd/containerd-shim/process_linux.go @@ -24,37 +24,38 @@ func setPDeathSig() *syscall.SysProcAttr { // openIO opens the pre-created fifo's for use with the container // in RDWR so that they remain open if the other side stops listening func (p *process) openIO() error { + return nil p.stdio = &stdio{} var ( - uid = p.state.RootUID - gid = p.state.RootGID + uid = p.RootUID + gid = p.RootGID ) ctx, _ := context.WithTimeout(context.Background(), 15*time.Second) - stdinCloser, err := fifo.OpenFifo(ctx, p.state.Stdin, syscall.O_WRONLY|syscall.O_NONBLOCK, 0) + stdinCloser, err := fifo.OpenFifo(ctx, p.Stdin, syscall.O_WRONLY|syscall.O_NONBLOCK, 0) if err != nil { return err } p.stdinCloser = stdinCloser - if p.state.Terminal { + if p.Terminal { master, console, err := newConsole(uid, gid) if err != nil { return err } p.console = master p.consolePath = console - stdin, err := fifo.OpenFifo(ctx, p.state.Stdin, syscall.O_RDONLY, 0) + stdin, err := fifo.OpenFifo(ctx, p.Stdin, syscall.O_RDONLY, 0) if err != nil { return err } go io.Copy(master, stdin) - stdoutw, err := fifo.OpenFifo(ctx, p.state.Stdout, syscall.O_WRONLY, 0) + stdoutw, err := fifo.OpenFifo(ctx, p.Stdout, syscall.O_WRONLY, 0) if err != nil { return err } - stdoutr, err := fifo.OpenFifo(ctx, p.state.Stdout, syscall.O_RDONLY, 0) + stdoutr, err := fifo.OpenFifo(ctx, p.Stdout, syscall.O_RDONLY, 0) if err != nil { return err } @@ -75,7 +76,7 @@ func (p *process) openIO() error { p.shimIO = i // non-tty for name, dest := range map[string]func(wc io.WriteCloser, rc io.Closer){ - p.state.Stdout: func(wc io.WriteCloser, rc io.Closer) { + p.Stdout: func(wc io.WriteCloser, rc io.Closer) { p.Add(1) go func() { io.Copy(wc, i.Stdout) @@ -84,7 +85,7 @@ func (p *process) openIO() error { rc.Close() }() }, - p.state.Stderr: func(wc io.WriteCloser, rc io.Closer) { + p.Stderr: func(wc io.WriteCloser, rc io.Closer) { p.Add(1) go func() { io.Copy(wc, i.Stderr) @@ -105,9 +106,9 @@ func (p *process) openIO() error { dest(fw, fr) } - f, err := fifo.OpenFifo(ctx, p.state.Stdin, syscall.O_RDONLY, 0) + f, err := fifo.OpenFifo(ctx, p.Stdin, syscall.O_RDONLY, 0) if err != nil { - return fmt.Errorf("containerd-shim: opening %s failed: %s", p.state.Stdin, err) + return fmt.Errorf("containerd-shim: opening %s failed: %s", p.Stdin, err) } go func() { io.Copy(i.Stdin, f) @@ -119,13 +120,11 @@ func (p *process) openIO() error { } func (p *process) killAll() error { - if !p.state.Exec { - cmd := exec.Command(p.runtime, append(p.state.RuntimeArgs, "kill", "--all", p.id, "SIGKILL")...) - cmd.SysProcAttr = setPDeathSig() - out, err := cmd.CombinedOutput() - if err != nil { - return fmt.Errorf("%s: %v", out, err) - } + cmd := exec.Command(p.runtime, "kill", "--all", p.id, "SIGKILL") + cmd.SysProcAttr = setPDeathSig() + out, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("%s: %v", out, err) } return nil } diff --git a/cmd/containerd-shim/service.go b/cmd/containerd-shim/service.go index 548c19a9a..063ca2e61 100644 --- a/cmd/containerd-shim/service.go +++ b/cmd/containerd-shim/service.go @@ -8,6 +8,8 @@ import ( "golang.org/x/net/context" ) +var emptyResponse = &google_protobuf.Empty{} + type service struct { init *process } @@ -18,7 +20,7 @@ func (s *service) Create(ctx context.Context, r *shim.CreateRequest) (*shim.Crea return nil, err } s.init = process - if err := process.create(); err != nil { + if err := process.create(false); err != nil { return nil, err } return &shim.CreateResponse{ @@ -27,8 +29,10 @@ func (s *service) Create(ctx context.Context, r *shim.CreateRequest) (*shim.Crea } func (s *service) Start(ctx context.Context, r *shim.StartRequest) (*google_protobuf.Empty, error) { - err := s.init.start() - return nil, err + if err := s.init.start(); err != nil { + return nil, err + } + return emptyResponse, nil } func (s *service) Delete(ctx context.Context, r *shim.DeleteRequest) (*shim.DeleteResponse, error) { @@ -52,7 +56,7 @@ func (s *service) Exec(ctx context.Context, r *shim.ExecRequest) (*shim.ExecResp func (s *service) Pty(ctx context.Context, r *shim.PtyRequest) (*google_protobuf.Empty, error) { if s.init.console == nil { - return nil, nil + return emptyResponse, nil } ws := term.Winsize{ Width: uint16(r.Width), @@ -61,7 +65,7 @@ func (s *service) Pty(ctx context.Context, r *shim.PtyRequest) (*google_protobuf if err := term.SetWinsize(s.init.console.Fd(), &ws); err != nil { return nil, err } - return nil, nil + return emptyResponse, nil } func (s *service) processExited(e utils.Exit) error { diff --git a/cmd/ctr/main.go b/cmd/ctr/main.go index 04004bce0..f9dd5c1f2 100644 --- a/cmd/ctr/main.go +++ b/cmd/ctr/main.go @@ -40,6 +40,7 @@ containerd client deleteCommand, listCommand, inspectCommand, + shimCommand, } app.Before = func(context *cli.Context) error { if context.GlobalBool("debug") { @@ -48,7 +49,7 @@ containerd client return nil } if err := app.Run(os.Args); err != nil { - fmt.Fprintf(os.Stderr, "containerd: %s\n", err) + fmt.Fprintf(os.Stderr, "ctr: %s\n", err) os.Exit(1) } } diff --git a/cmd/ctr/shim.go b/cmd/ctr/shim.go new file mode 100644 index 000000000..002fa9712 --- /dev/null +++ b/cmd/ctr/shim.go @@ -0,0 +1,124 @@ +package main + +import ( + "fmt" + "io/ioutil" + "log" + "net" + "os/exec" + "syscall" + "time" + + gocontext "context" + + "google.golang.org/grpc" + "google.golang.org/grpc/grpclog" + + "github.com/docker/containerd/api/shim" + "github.com/urfave/cli" +) + +var shimCommand = cli.Command{ + Name: "shim", + Usage: "interact with a shim directly", + Subcommands: []cli.Command{ + shimCreateCommand, + shimStartCommand, + shimDeleteCommand, + }, + Action: func(context *cli.Context) error { + cmd := exec.Command("containerd-shim", "--debug") + cmd.SysProcAttr = &syscall.SysProcAttr{} + cmd.SysProcAttr.Setpgid = true + if err := cmd.Start(); err != nil { + return err + } + fmt.Println("new shim started @ ./shim.sock") + return nil + }, +} + +var shimCreateCommand = cli.Command{ + Name: "create", + Usage: "create a container with a shim", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "bundle", + Usage: "bundle path for the container", + }, + cli.StringFlag{ + Name: "runtime", + Value: "runc", + Usage: "runtime to use for the container", + }, + }, + Action: func(context *cli.Context) error { + id := context.Args().First() + if id == "" { + return fmt.Errorf("container id must be provided") + } + service, err := getShimService() + if err != nil { + return err + } + r, err := service.Create(gocontext.Background(), &shim.CreateRequest{ + ID: id, + Bundle: context.String("bundle"), + Runtime: context.String("runtime"), + }) + if err != nil { + return err + } + fmt.Printf("container created with id %s and pid %d\n", id, r.Pid) + return nil + }, +} + +var shimStartCommand = cli.Command{ + Name: "start", + Usage: "start a container with a shim", + Action: func(context *cli.Context) error { + service, err := getShimService() + if err != nil { + return err + } + _, err = service.Start(gocontext.Background(), &shim.StartRequest{}) + return err + }, +} + +var shimDeleteCommand = cli.Command{ + Name: "delete", + Usage: "delete a container with a shim", + Action: func(context *cli.Context) error { + service, err := getShimService() + if err != nil { + return err + } + r, err := service.Delete(gocontext.Background(), &shim.DeleteRequest{}) + if err != nil { + return err + } + fmt.Printf("container deleted and returned exit status %d", r.ExitStatus) + return nil + }, +} + +func getShimService() (shim.ShimServiceClient, error) { + bindSocket := "shim.sock" + + // reset the logger for grpc to log to dev/null so that it does not mess with our stdio + grpclog.SetLogger(log.New(ioutil.Discard, "", log.LstdFlags)) + dialOpts := []grpc.DialOption{grpc.WithInsecure(), grpc.WithTimeout(100 * time.Second)} + dialOpts = append(dialOpts, + grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) { + return net.DialTimeout("unix", bindSocket, timeout) + }, + )) + conn, err := grpc.Dial(fmt.Sprintf("unix://%s", bindSocket), dialOpts...) + if err != nil { + return nil, err + } + return shim.NewShimServiceClient(conn), nil + +}