diff --git a/kill.go b/kill.go index caf84412..b60c61dd 100644 --- a/kill.go +++ b/kill.go @@ -69,6 +69,12 @@ For example, if the container id is "ubuntu01" the following will send a "KILL" signal to the init process of the "ubuntu01" container: # runv kill ubuntu01 KILL`, + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "all, a", + Usage: "send the signal to all processes in the container", + }, + }, Action: func(context *cli.Context) error { container := context.Args().First() if container == "" { @@ -88,17 +94,50 @@ signal to the init process of the "ubuntu01" container: if err != nil { return cli.NewExitError(fmt.Sprintf("failed to get client: %v", err), -1) } - if _, err = c.Signal(netcontext.Background(), &types.SignalRequest{ - Id: container, - Pid: "init", - Signal: uint32(signal), - }); err != nil { - return cli.NewExitError(fmt.Sprintf("kill signal failed, %v", err), -1) + + plist := make([]string, 0) + + if context.Bool("all") { + if plist, err = getProcessList(c, container); err != nil { + return cli.NewExitError(fmt.Sprintf("can't get process list, %v", err), -1) + } + } else { + plist = append(plist, "init") + } + + for _, p := range plist { + if _, err = c.Signal(netcontext.Background(), &types.SignalRequest{ + Id: container, + Pid: p, + Signal: uint32(signal), + }); err != nil { + return cli.NewExitError(fmt.Sprintf("kill signal failed, %v", err), -1) + } } return nil }, } +func getProcessList(c types.APIClient, container string) ([]string, error) { + s, err := c.State(netcontext.Background(), &types.StateRequest{Id: container}) + if err != nil { + return nil, fmt.Errorf("get container state failed, %v", err) + } + + for _, cc := range s.Containers { + if cc.Id == container { + plist := make([]string, 0) + for _, p := range cc.Processes { + plist = append(plist, p.Pid) + } + + return plist, nil + } + } + + return nil, fmt.Errorf("container %s not found", container) +} + func parseSignal(rawSignal string) (syscall.Signal, error) { s, err := strconv.Atoi(rawSignal) if err == nil { diff --git a/main.go b/main.go index 13de13f5..64d2aea8 100644 --- a/main.go +++ b/main.go @@ -152,12 +152,15 @@ func main() { execCommand, killCommand, listCommand, + psCommand, runCommand, specCommand, startCommand, stateCommand, manageCommand, shimCommand, + pauseCommand, + resumeCommand, containerd.ContainerdCommand, } if err := app.Run(os.Args); err != nil { diff --git a/pause.go b/pause.go new file mode 100644 index 00000000..8e5df69e --- /dev/null +++ b/pause.go @@ -0,0 +1,79 @@ +package main + +import ( + "fmt" + "path/filepath" + + "github.com/hyperhq/runv/containerd/api/grpc/types" + "github.com/hyperhq/runv/lib/linuxsignal" + "github.com/urfave/cli" + netcontext "golang.org/x/net/context" +) + +var pauseCommand = cli.Command{ + Name: "pause", + Usage: "suspend all processes in the container", + ArgsUsage: ``, + Action: func(context *cli.Context) error { + container := context.Args().First() + if container == "" { + return cli.NewExitError(fmt.Sprintf("container id cannot be empty"), -1) + } + + c, err := getClient(filepath.Join(context.GlobalString("root"), container, "namespace/namespaced.sock")) + if err != nil { + return cli.NewExitError(fmt.Sprintf("failed to get client: %v", err), -1) + } + + plist, err := getProcessList(c, container) + if err != nil { + return cli.NewExitError(fmt.Sprintf("can't get process list, %v", err), -1) + } + + for _, p := range plist { + if _, err := c.Signal(netcontext.Background(), &types.SignalRequest{ + Id: container, + Pid: p, + Signal: uint32(linuxsignal.SIGSTOP), + }); err != nil { + return cli.NewExitError(fmt.Sprintf("suspend signal failed, %v", err), -1) + } + } + + return nil + }, +} + +var resumeCommand = cli.Command{ + Name: "resume", + Usage: "resume all processes in the container", + ArgsUsage: ``, + Action: func(context *cli.Context) error { + container := context.Args().First() + if container == "" { + return cli.NewExitError(fmt.Sprintf("container id cannot be empty"), -1) + } + + c, err := getClient(filepath.Join(context.GlobalString("root"), container, "namespace/namespaced.sock")) + if err != nil { + return cli.NewExitError(fmt.Sprintf("failed to get client: %v", err), -1) + } + + plist, err := getProcessList(c, container) + if err != nil { + return cli.NewExitError(fmt.Sprintf("can't get process list, %v", err), -1) + } + + for _, p := range plist { + if _, err := c.Signal(netcontext.Background(), &types.SignalRequest{ + Id: container, + Pid: p, + Signal: uint32(linuxsignal.SIGCONT), + }); err != nil { + return cli.NewExitError(fmt.Sprintf("resume signal failed, %v", err), -1) + } + } + + return nil + }, +} diff --git a/ps.go b/ps.go new file mode 100644 index 00000000..21ff16ed --- /dev/null +++ b/ps.go @@ -0,0 +1,86 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "text/tabwriter" + + "github.com/hyperhq/runv/containerd/api/grpc/types" + "github.com/urfave/cli" + netcontext "golang.org/x/net/context" +) + +var psCommand = cli.Command{ + Name: "ps", + Usage: "ps displays the processes running inside a container", + ArgsUsage: ` [ps options]`, Flags: []cli.Flag{ + cli.StringFlag{ + Name: "format, f", + Value: "table", + Usage: `select one of: ` + formatOptions, + }, + }, + Action: func(context *cli.Context) error { + container := context.Args().First() + if container == "" { + return cli.NewExitError("container id cannot be empty", -1) + } + c, err := getContainerApi(context, container) + if err != nil { + return cli.NewExitError(fmt.Sprintf("can't access container, %v", err), -1) + } + + switch context.String("format") { + case "table": + w := tabwriter.NewWriter(os.Stdout, 12, 1, 3, ' ', 0) + fmt.Fprint(w, "PROCESS\tCMD\n") + // we are limited by the containerd interface for now + for _, p := range c.Processes { + fmt.Fprintf(w, "%s\t%s\n", + p.Pid, + p.Args) + } + if err := w.Flush(); err != nil { + fatal(err) + } + case "json": + pids := make([]string, 0) + for _, p := range c.Processes { + pids = append(pids, p.Pid) + } + + data, err := json.Marshal(pids) + if err != nil { + fatal(err) + } + os.Stdout.Write(data) + return nil + default: + return cli.NewExitError(fmt.Sprintf("invalid format option"), -1) + } + + return nil + }, +} + +func getContainerApi(context *cli.Context, container string) (*types.Container, error) { + api, err := getClient(filepath.Join(context.GlobalString("root"), container, "namespace/namespaced.sock")) + if err != nil { + return nil, fmt.Errorf("failed to get client: %v", err) + } + + s, err := api.State(netcontext.Background(), &types.StateRequest{Id: container}) + if err != nil { + return nil, fmt.Errorf("get container state failed, %v", err) + } + + for _, c := range s.Containers { + if c.Id == container { + return c, nil + } + } + + return nil, fmt.Errorf("container %s not found", container) +} diff --git a/supervisor/process.go b/supervisor/process.go index fe93aa1b..34e5dd84 100644 --- a/supervisor/process.go +++ b/supervisor/process.go @@ -93,8 +93,7 @@ func (p *Process) signal(sig int) error { // TODO: change vm.KillContainer() return p.ownerCont.ownerPod.vm.KillContainer(p.ownerCont.Id, syscall.Signal(sig)) } else { - // TODO support it - return fmt.Errorf("Kill to non-init process of container is unsupported") + return p.ownerCont.ownerPod.vm.SignalProcess(p.ownerCont.Id, p.Id, syscall.Signal(sig)) } }