diff --git a/containerd/api/grpc/server/server.go b/containerd/api/grpc/server/server.go index fbf5175e..be29033c 100644 --- a/containerd/api/grpc/server/server.go +++ b/containerd/api/grpc/server/server.go @@ -147,7 +147,17 @@ func (s *apiServer) State(ctx context.Context, r *types.StateRequest) (*types.St } func (s *apiServer) UpdateContainer(ctx context.Context, r *types.UpdateContainerRequest) (*types.UpdateContainerResponse, error) { - return nil, errors.New("UpdateContainer() not implemented yet") + glog.V(3).Infof("gRPC handle UpdateContainer") + + if r.Status != supervisor.ContainerStateDeleted { + return nil, fmt.Errorf("UpdateContainer() status %s not supported", r.Status) + } + + err := s.sv.DeleteContainer(r.Id) + if err != nil { + return nil, err + } + return &types.UpdateContainerResponse{}, nil } func (s *apiServer) UpdateProcess(ctx context.Context, r *types.UpdateProcessRequest) (*types.UpdateProcessResponse, error) { @@ -230,7 +240,7 @@ func supervisorContainer2ApiContainer(c *supervisor.Container) *types.Container return &types.Container{ Id: c.Id, BundlePath: c.BundlePath, - Status: "running", + Status: c.Status, Runtime: "runv", } } diff --git a/containerd/containerd.go b/containerd/containerd.go index 0088a9d6..c12c1672 100644 --- a/containerd/containerd.go +++ b/containerd/containerd.go @@ -189,9 +189,9 @@ func namespaceShare(sv *supervisor.Supervisor, namespace, state string) { events := sv.Events.Events(time.Time{}) containerCount := 0 for e := range events { - if e.Type == supervisor.EventContainerStart { + if e.Type == supervisor.EventContainerCreate { containerCount++ - } else if e.Type == supervisor.EventExit && e.PID == "init" { + } else if e.Type == supervisor.EventContainerDelete { containerCount-- if containerCount == 0 { syscall.Kill(0, syscall.SIGQUIT) diff --git a/delete.go b/delete.go new file mode 100644 index 00000000..ec812175 --- /dev/null +++ b/delete.go @@ -0,0 +1,52 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/hyperhq/runv/containerd/api/grpc/types" + "github.com/hyperhq/runv/supervisor" + "github.com/urfave/cli" + netcontext "golang.org/x/net/context" +) + +var deleteCommand = cli.Command{ + Name: "delete", + Usage: "delete a stopped container", + ArgsUsage: ``, + Action: func(context *cli.Context) error { + container := context.Args().First() + if container == "" { + return cli.NewExitError("container id cannot be empty", -1) + } + + containerPath := filepath.Join(context.GlobalString("root"), container) + + if dir, err := os.Stat(containerPath); err != nil || !dir.IsDir() { + return fmt.Errorf("container %s does not exist", container) + } + + api, err := getClient(filepath.Join(containerPath, "namespace/namespaced.sock")) + if err != nil { + return fmt.Errorf("failed to get client: %v", err) + } + + _, err = api.GetServerVersion(netcontext.Background(), nil) + if err != nil { + // if we can't connect to the api, runv was killed before it could clean up the stopped containers + err := os.RemoveAll(containerPath) + if err != nil { + return fmt.Errorf("delete stale container %s failed, %v", container, err) + } + return nil + } + + _, err = api.UpdateContainer(netcontext.Background(), &types.UpdateContainerRequest{Id: container, Status: supervisor.ContainerStateDeleted}) + if err != nil { + return fmt.Errorf("delete container %s failed, %v", container, err) + } + + return nil + }, +} diff --git a/integration-test/kill_test.go b/integration-test/kill_test.go index 199ad0d7..d1382533 100644 --- a/integration-test/kill_test.go +++ b/integration-test/kill_test.go @@ -35,9 +35,9 @@ func (s *RunVSuite) TestKillKILL(c *check.C) { timeout := true for count := 0; count < 10; count++ { - out, exitCode := s.runvCommand(c, "list") + out, exitCode := s.runvCommand(c, "state", ctrName) c.Assert(exitCode, checker.Equals, 0) - if !strings.Contains(out, ctrName) { + if !strings.Contains(out, "running") { timeout = false break } diff --git a/list.go b/list.go index 5aabc4a4..6980448c 100644 --- a/list.go +++ b/list.go @@ -9,6 +9,7 @@ import ( "text/tabwriter" "time" + "github.com/hyperhq/runv/supervisor" "github.com/opencontainers/runtime-spec/specs-go" "github.com/urfave/cli" ) @@ -110,15 +111,22 @@ func getContainers(context *cli.Context) ([]containerState, error) { if err != nil && !os.IsNotExist(err) { return nil, fmt.Errorf("Stat file %s error: %s", stateFile, err.Error()) } + state, err := loadStateFile(stateFile) if err != nil { return nil, fmt.Errorf("Load state file %s failed: %s", stateFile, err.Error()) } + status := supervisor.ContainerStateStopped // if we can't connect to runv-containerd then the container is stopped + if c, err := getContainerApi(context, item.Name()); err == nil { + status = c.Status + } + + // FIXME: refactor to get container state only via API s = append(s, containerState{ ID: state.ID, InitProcessPid: state.Pid, - Status: "running", + Status: status, Bundle: state.Bundle, Created: fi.ModTime(), }) diff --git a/main.go b/main.go index 64d2aea8..e833ba98 100644 --- a/main.go +++ b/main.go @@ -162,6 +162,7 @@ func main() { pauseCommand, resumeCommand, containerd.ContainerdCommand, + deleteCommand, } if err := app.Run(os.Args); err != nil { fmt.Fprintf(os.Stderr, "%v", err) diff --git a/state.go b/state.go index a3a7bfb4..3c79d8c1 100644 --- a/state.go +++ b/state.go @@ -9,6 +9,7 @@ import ( "path/filepath" "time" + "github.com/hyperhq/runv/supervisor" "github.com/urfave/cli" ) @@ -79,11 +80,17 @@ func getContainer(context *cli.Context, name string) (*cState, error) { return nil, fmt.Errorf("Load state file %s failed: %s", stateFile, err.Error()) } + status := supervisor.ContainerStateStopped // if we can't connect to runv-containerd then the container is stopped + if c, err := getContainerApi(context, name); err == nil { + status = c.Status + } + + // FIXME: refactor to get container state only via API s := &cState{ Version: state.Version, ID: state.ID, InitProcessPid: state.Pid, - Status: "running", + Status: status, Bundle: state.Bundle, Rootfs: filepath.Join(state.Bundle, "rootfs"), Created: fi.ModTime(), diff --git a/supervisor/container.go b/supervisor/container.go index e31ccb81..1b34b34e 100644 --- a/supervisor/container.go +++ b/supervisor/container.go @@ -20,8 +20,18 @@ import ( "github.com/opencontainers/runtime-spec/specs-go" ) +// container states +const ( + ContainerStateCreating = "creating" + ContainerStateCreated = "created" + ContainerStateRunning = "running" + ContainerStateStopped = "stopped" + ContainerStateDeleted = "deleted" // special state that will lead to deleting the container resources +) + type Container struct { Id string + Status string BundlePath string Spec *specs.Spec Processes map[string]*Process @@ -50,6 +60,7 @@ func (c *Container) start(p *Process) error { } go func() { + c.Status = ContainerStateRunning e := Event{ ID: c.Id, Type: EventContainerStart, @@ -58,6 +69,7 @@ func (c *Container) start(p *Process) error { c.ownerPod.sv.Events.notifySubscribers(e) exit, err := c.wait(p, res) + c.Status = ContainerStateStopped e = Event{ ID: c.Id, Type: EventExit, @@ -75,6 +87,7 @@ func (c *Container) start(p *Process) error { } func (c *Container) create() error { + c.Status = ContainerStateCreating glog.V(3).Infof("prepare hypervisor info") config := api.ContainerDescriptionFromOCF(c.Id, c.Spec) @@ -182,6 +195,14 @@ func (c *Container) create() error { return err } + c.Status = ContainerStateCreated + e := Event{ + ID: c.Id, + Type: EventContainerCreate, + Timestamp: time.Now(), + } + c.ownerPod.sv.Events.notifySubscribers(e) + return nil } @@ -354,6 +375,13 @@ func (c *Container) reap() { utils.Umount(containerRoot) os.RemoveAll(containerRoot) os.RemoveAll(filepath.Join(c.ownerPod.sv.StateDir, c.Id)) + + e := Event{ + ID: c.Id, + Type: EventContainerDelete, + Timestamp: time.Now(), + } + c.ownerPod.sv.Events.notifySubscribers(e) } func mountToRootfs(m *specs.Mount, rootfs, mountLabel string) error { diff --git a/supervisor/events.go b/supervisor/events.go index 3ac2ea49..bc433089 100644 --- a/supervisor/events.go +++ b/supervisor/events.go @@ -12,9 +12,11 @@ import ( ) const ( - EventExit = "exit" - EventContainerStart = "start-container" - EventProcessStart = "start-process" + EventExit = "exit" + EventContainerCreate = "create-container" + EventContainerStart = "start-container" + EventContainerDelete = "delete-container" + EventProcessStart = "start-process" ) var ( diff --git a/supervisor/supervisor.go b/supervisor/supervisor.go index ca92672e..374b9695 100644 --- a/supervisor/supervisor.go +++ b/supervisor/supervisor.go @@ -94,6 +94,26 @@ func (sv *Supervisor) StartContainer(container string, spec *specs.Spec) (c *Con return nil, nil, fmt.Errorf("container %s is not found for StartContainer()", container) } +func (sv *Supervisor) DeleteContainer(container string) error { + glog.Infof("delete container %s", container) + sv.Lock() + defer sv.Unlock() + + if c, ok := sv.Containers[container]; ok { + c.reap() + delete(c.ownerPod.Containers, container) + delete(sv.Containers, container) + + if len(c.ownerPod.Containers) == 0 { + c.ownerPod.reap() + } + + return nil + } + + return fmt.Errorf("Container %s not found", container) +} + func (sv *Supervisor) AddProcess(container, processId, stdin, stdout, stderr string, spec *specs.Process) (*Process, error) { sv.Lock() defer sv.Unlock() @@ -164,14 +184,6 @@ func (sv *Supervisor) reap(container, processId string) { // TODO: kill all the other existing processes in the same container } } - if len(c.Processes) == 0 { - c.reap() - delete(c.ownerPod.Containers, container) - delete(sv.Containers, container) - } - if len(c.ownerPod.Containers) == 0 { - c.ownerPod.reap() - } } }