api: add a CleanupContainer api for VC

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 <lifupan@gmail.com>
This commit is contained in:
lifupan 2019-06-27 16:43:18 +08:00
parent 346d96ce4e
commit c91556aa41
11 changed files with 126 additions and 54 deletions

View File

@ -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
}

View File

@ -11,7 +11,6 @@ import (
"fmt"
"os"
"path/filepath"
"syscall"
"time"
"github.com/containerd/containerd/mount"
@ -36,53 +35,19 @@ 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")
err := vci.CleanupContainer(ctx, sid, cid, true)
if err != nil {
logrus.WithError(err).WithField("container", cid).Warn("failed to cleanup container")
return err
}
rootfs := filepath.Join(bundlePath, "rootfs")
sandbox, err := vci.FetchSandbox(ctx, sid)
if err != nil {
return err
}
status, err := sandbox.StatusContainer(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")
return err
}
if _, err := sandbox.DeleteContainer(cid); err != nil {
logrus.WithError(err).WithField("container", cid).Warn("failed to remove container")
}
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 nil
}

View File

@ -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")
}
}

View File

@ -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.
@ -929,3 +929,56 @@ 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, force bool) 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, force)
if err != nil && !force {
return err
}
_, err = s.DeleteContainer(containerID)
if err != nil && !force {
return err
}
if len(s.GetAllContainers()) > 0 {
return nil
}
if err = s.Stop(force); err != nil && !force {
return err
}
if err = s.Delete(); err != nil {
return err
}
return nil
}

View File

@ -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(), true)
}
sandboxDir := store.SandboxConfigurationRootPath(p.ID())
_, err = os.Stat(sandboxDir)
if err == nil {
t.Fatal(err)
}
}

View File

@ -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, force bool) error {
return CleanupContainer(ctx, sandboxID, containerID, force)
}

View File

@ -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, force bool) error
}
// VCSandbox is the Sandbox interface
@ -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)

View File

@ -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, force bool) error {
if m.CleanupContainerFunc != nil {
return m.CleanupContainerFunc(ctx, sandboxID, containerID, true)
}
return fmt.Errorf("%s: %s (%+v): sandboxID: %v", mockErrorPrefix, getSelf(), m, sandboxID)
}

View File

@ -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
}

View File

@ -75,4 +75,5 @@ type VCMock struct {
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, force bool) error
}

View File

@ -1181,7 +1181,7 @@ 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 {
@ -1189,7 +1189,7 @@ func (s *Sandbox) StopContainer(containerID string) (VCContainer, error) {
}
// Stop it.
if err := c.stop(false); err != nil {
if err := c.stop(force); err != nil {
return nil, err
}