Skip to content
This repository has been archived by the owner on May 12, 2021. It is now read-only.

Commit

Permalink
api: add a CleanupContainer api for VC
Browse files Browse the repository at this point in the history
When shimv2 was killed by accident, containerd would try to
launch a new shimv2 binarry to cleanup the container. In order
to avoid race condition, the cleanup should be done serialized
in a sandbox. Thus adding a new api to do this by locking the
sandbox.

Fixes:#1832

Signed-off-by: lifupan <[email protected]>
  • Loading branch information
lifupan committed Aug 23, 2019
1 parent 346d96c commit 8a8dedc
Show file tree
Hide file tree
Showing 11 changed files with 122 additions and 51 deletions.
2 changes: 1 addition & 1 deletion containerd-shim-v2/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func deleteContainer(ctx context.Context, s *service, c *container) error {
return err
}
if status.State.State != types.StateStopped {
_, err = s.sandbox.StopContainer(c.id)
_, err = s.sandbox.StopContainer(c.id, false)
if err != nil {
return err
}
Expand Down
43 changes: 4 additions & 39 deletions containerd-shim-v2/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"fmt"
"os"
"path/filepath"
"syscall"
"time"

"github.com/containerd/containerd/mount"
Expand All @@ -36,51 +35,17 @@ func cReap(s *service, status int, id, execid string, exitat time.Time) {
func cleanupContainer(ctx context.Context, sid, cid, bundlePath string) error {
logrus.WithField("Service", "Cleanup").WithField("container", cid).Info("Cleanup container")

rootfs := filepath.Join(bundlePath, "rootfs")
sandbox, err := vci.FetchSandbox(ctx, sid)
if err != nil {
return err
}

status, err := sandbox.StatusContainer(cid)
err := vci.CleanupContainer(ctx, sid, cid)
if err != nil {
logrus.WithError(err).WithField("container", cid).Warn("failed to get container status")
return err
}

if oci.StateToOCIState(status.State.State) != oci.StateStopped {
err := sandbox.KillContainer(cid, syscall.SIGKILL, true)
if err != nil {
logrus.WithError(err).WithField("container", cid).Warn("failed to kill container")
return err
}
}

if _, err = sandbox.StopContainer(cid); err != nil {
logrus.WithError(err).WithField("container", cid).Warn("failed to stop container")
logrus.WithError(err).WithField("container", cid).Warn("failed to cleanup container")
return err
}

if _, err := sandbox.DeleteContainer(cid); err != nil {
logrus.WithError(err).WithField("container", cid).Warn("failed to remove container")
}
rootfs := filepath.Join(bundlePath, "rootfs")

if err := mount.UnmountAll(rootfs, 0); err != nil {
logrus.WithError(err).WithField("container", cid).Warn("failed to cleanup container rootfs")
}

if len(sandbox.GetAllContainers()) == 0 {
err = sandbox.Stop(true)
if err != nil {
logrus.WithError(err).WithField("sandbox", sid).Warn("failed to stop sandbox")
return err
}

err = sandbox.Delete()
if err != nil {
logrus.WithError(err).WithField("sandbox", sid).Warnf("failed to delete sandbox")
return err
}
return err
}

return nil
Expand Down
2 changes: 1 addition & 1 deletion containerd-shim-v2/wait.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func wait(s *service, c *container, execID string) (int32, error) {
logrus.WithField("sandbox", s.sandbox.ID()).Error("failed to delete sandbox")
}
} else {
if _, err = s.sandbox.StopContainer(c.id); err != nil {
if _, err = s.sandbox.StopContainer(c.id, false); err != nil {
logrus.WithError(err).WithField("container", c.id).Warn("stop container failed")
}
}
Expand Down
54 changes: 53 additions & 1 deletion virtcontainers/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,7 @@ func StopContainer(ctx context.Context, sandboxID, containerID string) (VCContai
}
defer s.releaseStatelessSandbox()

return s.StopContainer(containerID)
return s.StopContainer(containerID, false)
}

// EnterContainer is the virtcontainers container command execution entry point.
Expand Down Expand Up @@ -929,3 +929,55 @@ func ListRoutes(ctx context.Context, sandboxID string) ([]*vcTypes.Route, error)

return s.ListRoutes()
}

// CleanupContaienr is used by shimv2 to stop and delete a container exclusively, once there is no container
// in the sandbox left, do stop the sandbox and delete it. Those serial operations will be done exclusively by
// locking the sandbox.
func CleanupContainer(ctx context.Context, sandboxID, containerID string) error {
span, ctx := trace(ctx, "CleanupContainer")
defer span.Finish()

if sandboxID == "" {
return vcTypes.ErrNeedSandboxID
}

if containerID == "" {
return vcTypes.ErrNeedContainerID
}

lockFile, err := rwLockSandbox(ctx, sandboxID)
if err != nil {
return err
}
defer unlockSandbox(ctx, sandboxID, lockFile)

s, err := fetchSandbox(ctx, sandboxID)
if err != nil {
return err
}

defer s.Release()

_, err = s.StopContainer(containerID, true)
if err != nil {
return err
}

if _, err := s.DeleteContainer(containerID); err != nil {
return err
}

if len(s.GetAllContainers()) > 0 {
return nil
}

if err = s.Stop(true); err != nil {
return err
}

if err = s.Delete(); err != nil {
return err
}

return nil
}
37 changes: 37 additions & 0 deletions virtcontainers/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1704,3 +1704,40 @@ func TestNetworkOperation(t *testing.T) {
_, err = ListRoutes(ctx, s.ID())
assert.NoError(err)
}

func TestCleanupContainer(t *testing.T) {
config := newTestSandboxConfigNoop()

ctx := context.Background()

p, _, err := createAndStartSandbox(ctx, config)
if p == nil || err != nil {
t.Fatal(err)
}

contIDs := []string{"100", "101", "102", "103", "104"}
for _, contID := range contIDs {
contConfig := newTestContainerConfigNoop(contID)

c, err := p.CreateContainer(contConfig)
if c == nil || err != nil {
t.Fatal(err)
}

c, err = p.StartContainer(c.ID())
if c == nil || err != nil {
t.Fatal(err)
}
}

for _, c := range p.GetAllContainers() {
CleanupContainer(ctx, p.ID(), c.ID())
}

sandboxDir := store.SandboxConfigurationRootPath(p.ID())

_, err = os.Stat(sandboxDir)
if err == nil {
t.Fatal(err)
}
}
7 changes: 7 additions & 0 deletions virtcontainers/implementation.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,10 @@ func (impl *VCImpl) UpdateRoutes(ctx context.Context, sandboxID string, routes [
func (impl *VCImpl) ListRoutes(ctx context.Context, sandboxID string) ([]*vcTypes.Route, error) {
return ListRoutes(ctx, sandboxID)
}

// CleanupContaienr is used by shimv2 to stop and delete a container exclusively, once there is no container
// in the sandbox left, do stop the sandbox and delete it. Those serial operations will be done exclusively by
// locking the sandbox.
func (impl *VCImpl) CleanupContainer(ctx context.Context, sandboxID, containerID string) error {
return CleanupContainer(ctx, sandboxID, containerID)
}
4 changes: 3 additions & 1 deletion virtcontainers/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ type VC interface {
ListInterfaces(ctx context.Context, sandboxID string) ([]*vcTypes.Interface, error)
UpdateRoutes(ctx context.Context, sandboxID string, routes []*vcTypes.Route) ([]*vcTypes.Route, error)
ListRoutes(ctx context.Context, sandboxID string) ([]*vcTypes.Route, error)

CleanupContainer(ctx context.Context, sandboxID, containerID string) error
}

// VCSandbox is the Sandbox interface
Expand All @@ -78,7 +80,7 @@ type VCSandbox interface {
CreateContainer(contConfig ContainerConfig) (VCContainer, error)
DeleteContainer(contID string) (VCContainer, error)
StartContainer(containerID string) (VCContainer, error)
StopContainer(containerID string) (VCContainer, error)
StopContainer(containerID string, force bool) (VCContainer, error)
KillContainer(containerID string, signal syscall.Signal, all bool) error
StatusContainer(containerID string) (ContainerStatus, error)
StatsContainer(containerID string) (ContainerStats, error)
Expand Down
7 changes: 7 additions & 0 deletions virtcontainers/pkg/vcmock/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,3 +298,10 @@ func (m *VCMock) ListRoutes(ctx context.Context, sandboxID string) ([]*vcTypes.R

return nil, fmt.Errorf("%s: %s (%+v): sandboxID: %v", mockErrorPrefix, getSelf(), m, sandboxID)
}

func (m *VCMock) CleanupContainer(ctx context.Context, sandboxID, containerID string) error {
if m.CleanupContainerFunc != nil {
return m.CleanupContainerFunc(ctx, sandboxID, containerID)
}
return fmt.Errorf("%s: %s (%+v): sandboxID: %v", mockErrorPrefix, getSelf(), m, sandboxID)
}
2 changes: 1 addition & 1 deletion virtcontainers/pkg/vcmock/sandbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func (s *Sandbox) StartContainer(contID string) (vc.VCContainer, error) {
}

// StopContainer implements the VCSandbox function of the same name.
func (s *Sandbox) StopContainer(contID string) (vc.VCContainer, error) {
func (s *Sandbox) StopContainer(contID string, force bool) (vc.VCContainer, error) {
return &Container{}, nil
}

Expand Down
11 changes: 6 additions & 5 deletions virtcontainers/pkg/vcmock/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,10 @@ type VCMock struct {

AddDeviceFunc func(ctx context.Context, sandboxID string, info config.DeviceInfo) (api.Device, error)

AddInterfaceFunc func(ctx context.Context, sandboxID string, inf *vcTypes.Interface) (*vcTypes.Interface, error)
RemoveInterfaceFunc func(ctx context.Context, sandboxID string, inf *vcTypes.Interface) (*vcTypes.Interface, error)
ListInterfacesFunc func(ctx context.Context, sandboxID string) ([]*vcTypes.Interface, error)
UpdateRoutesFunc func(ctx context.Context, sandboxID string, routes []*vcTypes.Route) ([]*vcTypes.Route, error)
ListRoutesFunc func(ctx context.Context, sandboxID string) ([]*vcTypes.Route, error)
AddInterfaceFunc func(ctx context.Context, sandboxID string, inf *vcTypes.Interface) (*vcTypes.Interface, error)
RemoveInterfaceFunc func(ctx context.Context, sandboxID string, inf *vcTypes.Interface) (*vcTypes.Interface, error)
ListInterfacesFunc func(ctx context.Context, sandboxID string) ([]*vcTypes.Interface, error)
UpdateRoutesFunc func(ctx context.Context, sandboxID string, routes []*vcTypes.Route) ([]*vcTypes.Route, error)
ListRoutesFunc func(ctx context.Context, sandboxID string) ([]*vcTypes.Route, error)
CleanupContainerFunc func(ctx context.Context, sandboxID, containerID string) error
}
4 changes: 2 additions & 2 deletions virtcontainers/sandbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -1181,15 +1181,15 @@ func (s *Sandbox) StartContainer(containerID string) (VCContainer, error) {
}

// StopContainer stops a container in the sandbox
func (s *Sandbox) StopContainer(containerID string) (VCContainer, error) {
func (s *Sandbox) StopContainer(containerID string, force bool) (VCContainer, error) {
// Fetch the container.
c, err := s.findContainer(containerID)
if err != nil {
return nil, err
}

// Stop it.
if err := c.stop(false); err != nil {
if err := c.stop(force); err != nil {
return nil, err
}

Expand Down

0 comments on commit 8a8dedc

Please sign in to comment.