From a09ce8881599fe44bf305206f7687649faa41271 Mon Sep 17 00:00:00 2001 From: "Hsing-Yu (David) Chen" Date: Fri, 20 Jan 2023 17:58:00 -0800 Subject: [PATCH] refactor: move stopContainer to containerutil Signed-off-by: Hsing-Yu (David) Chen --- cmd/nerdctl/restart.go | 3 +- cmd/nerdctl/stop.go | 125 +---------------------------- pkg/containerutil/containerutil.go | 123 ++++++++++++++++++++++++++++ 3 files changed, 126 insertions(+), 125 deletions(-) diff --git a/cmd/nerdctl/restart.go b/cmd/nerdctl/restart.go index f6dc8fedcc7..7dd0e502719 100644 --- a/cmd/nerdctl/restart.go +++ b/cmd/nerdctl/restart.go @@ -22,6 +22,7 @@ import ( "time" "github.com/containerd/nerdctl/pkg/clientutil" + "github.com/containerd/nerdctl/pkg/containerutil" "github.com/containerd/nerdctl/pkg/idutil/containerwalker" "github.com/spf13/cobra" ) @@ -67,7 +68,7 @@ func restartAction(cmd *cobra.Command, args []string) error { if found.MatchCount > 1 { return fmt.Errorf("multiple IDs found with provided prefix: %s", found.Req) } - if err := stopContainer(ctx, found.Container, timeout); err != nil { + if err := containerutil.Stop(ctx, found.Container, timeout); err != nil { return err } if err := startContainer(ctx, found.Container, false, client); err != nil { diff --git a/cmd/nerdctl/stop.go b/cmd/nerdctl/stop.go index bef617fa287..d4fd4ffbd3a 100644 --- a/cmd/nerdctl/stop.go +++ b/cmd/nerdctl/stop.go @@ -26,13 +26,8 @@ import ( "github.com/spf13/cobra" "github.com/containerd/containerd" - "github.com/containerd/containerd/cio" "github.com/containerd/containerd/errdefs" "github.com/containerd/nerdctl/pkg/idutil/containerwalker" - "github.com/containerd/nerdctl/pkg/labels" - - "github.com/moby/sys/signal" - "github.com/sirupsen/logrus" ) func newStopCommand() *cobra.Command { @@ -76,7 +71,7 @@ func stopAction(cmd *cobra.Command, args []string) error { if found.MatchCount > 1 { return fmt.Errorf("multiple IDs found with provided prefix: %s", found.Req) } - if err := stopContainer(ctx, found.Container, timeout); err != nil { + if err := containerutil.Stop(ctx, found.Container, timeout); err != nil { if errdefs.IsNotFound(err) { fmt.Fprintf(cmd.ErrOrStderr(), "No such container: %s\n", found.Req) return nil @@ -98,124 +93,6 @@ func stopAction(cmd *cobra.Command, args []string) error { return nil } -func stopContainer(ctx context.Context, container containerd.Container, timeout *time.Duration) error { - if err := containerutil.UpdateExplicitlyStoppedLabel(ctx, container, true); err != nil { - return err - } - - l, err := container.Labels(ctx) - if err != nil { - return err - } - - if timeout == nil { - t, ok := l[labels.StopTimout] - if !ok { - // Default is 10 seconds. - t = "10" - } - td, err := time.ParseDuration(t + "s") - if err != nil { - return err - } - timeout = &td - } - - task, err := container.Task(ctx, cio.Load) - if err != nil { - return err - } - - status, err := task.Status(ctx) - if err != nil { - return err - } - - paused := false - - switch status.Status { - case containerd.Created, containerd.Stopped: - return nil - case containerd.Paused, containerd.Pausing: - paused = true - default: - } - - // NOTE: ctx is main context so that it's ok to use for task.Wait(). - exitCh, err := task.Wait(ctx) - if err != nil { - return err - } - - if *timeout > 0 { - sig, err := signal.ParseSignal("SIGTERM") - if err != nil { - return err - } - if stopSignal, ok := l[containerd.StopSignalLabel]; ok { - sig, err = signal.ParseSignal(stopSignal) - if err != nil { - return err - } - } - - if err := task.Kill(ctx, sig); err != nil { - return err - } - - // signal will be sent once resume is finished - if paused { - if err := task.Resume(ctx); err != nil { - logrus.Warnf("Cannot unpause container %s: %s", container.ID(), err) - } else { - // no need to do it again when send sigkill signal - paused = false - } - } - - sigtermCtx, sigtermCtxCancel := context.WithTimeout(ctx, *timeout) - defer sigtermCtxCancel() - - err = waitContainerStop(sigtermCtx, exitCh, container.ID()) - if err == nil { - return nil - } - - if ctx.Err() != nil { - return ctx.Err() - } - } - - sig, err := signal.ParseSignal("SIGKILL") - if err != nil { - return err - } - - if err := task.Kill(ctx, sig); err != nil { - return err - } - - // signal will be sent once resume is finished - if paused { - if err := task.Resume(ctx); err != nil { - logrus.Warnf("Cannot unpause container %s: %s", container.ID(), err) - } - } - return waitContainerStop(ctx, exitCh, container.ID()) -} - -func waitContainerStop(ctx context.Context, exitCh <-chan containerd.ExitStatus, id string) error { - select { - case <-ctx.Done(): - if err := ctx.Err(); err != nil { - return fmt.Errorf("wait container %v: %w", id, err) - } - return nil - case status := <-exitCh: - return status.Error() - } -} - func stopShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { // show non-stopped container names statusFilterFn := func(st containerd.ProcessStatus) bool { diff --git a/pkg/containerutil/containerutil.go b/pkg/containerutil/containerutil.go index ea96028fe2f..b5c3488dc40 100644 --- a/pkg/containerutil/containerutil.go +++ b/pkg/containerutil/containerutil.go @@ -26,12 +26,16 @@ import ( "time" "github.com/containerd/containerd" + "github.com/containerd/containerd/cio" "github.com/containerd/containerd/containers" "github.com/containerd/containerd/oci" "github.com/containerd/containerd/runtime/restart" + "github.com/containerd/nerdctl/pkg/labels" "github.com/containerd/nerdctl/pkg/portutil" "github.com/containerd/nerdctl/pkg/rootlessutil" + "github.com/moby/sys/signal" "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" ) // PrintHostPort writes to `writer` the public (HostIP:HostPort) of a given `containerPort/protocol` in a container. @@ -174,3 +178,122 @@ func GenerateSharingPIDOpts(ctx context.Context, targetCon containerd.Container) } return opts, nil } + +// Stop stops `container` by sending SIGTERM. If the container is not stopped after `timeout`, it sends a SIGKILL. +func Stop(ctx context.Context, container containerd.Container, timeout *time.Duration) error { + if err := UpdateExplicitlyStoppedLabel(ctx, container, true); err != nil { + return err + } + + l, err := container.Labels(ctx) + if err != nil { + return err + } + + if timeout == nil { + t, ok := l[labels.StopTimout] + if !ok { + // Default is 10 seconds. + t = "10" + } + td, err := time.ParseDuration(t + "s") + if err != nil { + return err + } + timeout = &td + } + + task, err := container.Task(ctx, cio.Load) + if err != nil { + return err + } + + status, err := task.Status(ctx) + if err != nil { + return err + } + + paused := false + + switch status.Status { + case containerd.Created, containerd.Stopped: + return nil + case containerd.Paused, containerd.Pausing: + paused = true + default: + } + + // NOTE: ctx is main context so that it's ok to use for task.Wait(). + exitCh, err := task.Wait(ctx) + if err != nil { + return err + } + + if *timeout > 0 { + sig, err := signal.ParseSignal("SIGTERM") + if err != nil { + return err + } + if stopSignal, ok := l[containerd.StopSignalLabel]; ok { + sig, err = signal.ParseSignal(stopSignal) + if err != nil { + return err + } + } + + if err := task.Kill(ctx, sig); err != nil { + return err + } + + // signal will be sent once resume is finished + if paused { + if err := task.Resume(ctx); err != nil { + logrus.Warnf("Cannot unpause container %s: %s", container.ID(), err) + } else { + // no need to do it again when send sigkill signal + paused = false + } + } + + sigtermCtx, sigtermCtxCancel := context.WithTimeout(ctx, *timeout) + defer sigtermCtxCancel() + + err = waitContainerStop(sigtermCtx, exitCh, container.ID()) + if err == nil { + return nil + } + + if ctx.Err() != nil { + return ctx.Err() + } + } + + sig, err := signal.ParseSignal("SIGKILL") + if err != nil { + return err + } + + if err := task.Kill(ctx, sig); err != nil { + return err + } + + // signal will be sent once resume is finished + if paused { + if err := task.Resume(ctx); err != nil { + logrus.Warnf("Cannot unpause container %s: %s", container.ID(), err) + } + } + return waitContainerStop(ctx, exitCh, container.ID()) +} + +func waitContainerStop(ctx context.Context, exitCh <-chan containerd.ExitStatus, id string) error { + select { + case <-ctx.Done(): + if err := ctx.Err(); err != nil { + return fmt.Errorf("wait container %v: %w", id, err) + } + return nil + case status := <-exitCh: + return status.Error() + } +}