Skip to content

Commit

Permalink
Merge pull request #999 from junnplus/restart
Browse files Browse the repository at this point in the history
enhancement restart policy
  • Loading branch information
AkihiroSuda committed May 5, 2022
2 parents 6eae937 + 97ece6a commit d110fea
Show file tree
Hide file tree
Showing 14 changed files with 274 additions and 319 deletions.
6 changes: 4 additions & 2 deletions README.md
Expand Up @@ -358,9 +358,11 @@ Basic flags:
- :whale: :blue_square: `-t, --tty`: Allocate a pseudo-TTY
- :warning: WIP: currently `-t` conflicts with `-d`
- :whale: :blue_square: `-d, --detach`: Run container in background and print container ID
- :whale: `--restart=(no|always)`: Restart policy to apply when a container exits
- :whale: `--restart=(no|always|on-failure|unless-stopped)`: Restart policy to apply when a container exits
- Default: "no"
- :warning: No support for `on-failure` and `unless-stopped`
- always: Always restart the container if it stops.
- on-failure[:max-retries]: Restart only if the container exits with a non-zero exit status. Optionally, limit the number of times attempts to restart the container using the :max-retries option.
- unless-stopped: Always restart the container unless it is stopped.
- :whale: `--rm`: Automatically remove the container when it exits
- :whale: `--pull=(always|missing|never)`: Pull image before running
- Default: "missing"
Expand Down
4 changes: 2 additions & 2 deletions cmd/nerdctl/info.go
Expand Up @@ -163,15 +163,15 @@ func prettyPrintInfoNative(w io.Writer, info *native.Info) error {
fmt.Fprintf(w, "Rootless: %v\n", info.Rootless)
fmt.Fprintf(w, "containerd Version: %s (%s)\n", info.Daemon.Version.Version, info.Daemon.Version.Revision)
fmt.Fprintf(w, "containerd UUID: %s\n", info.Daemon.Server.UUID)
var disabledPlugins, enabledPlugins []introspection.Plugin
var disabledPlugins, enabledPlugins []*introspection.Plugin
for _, f := range info.Daemon.Plugins.Plugins {
if f.InitErr == nil {
enabledPlugins = append(enabledPlugins, f)
} else {
disabledPlugins = append(disabledPlugins, f)
}
}
sorter := func(x []introspection.Plugin) func(int, int) bool {
sorter := func(x []*introspection.Plugin) func(int, int) bool {
return func(i, j int) bool {
return x[i].Type+"."+x[j].ID < x[j].Type+"."+x[j].ID
}
Expand Down
20 changes: 2 additions & 18 deletions cmd/nerdctl/run.go
Expand Up @@ -37,7 +37,6 @@ import (
"github.com/containerd/containerd/cmd/ctr/commands/tasks"
"github.com/containerd/containerd/containers"
"github.com/containerd/containerd/oci"
"github.com/containerd/containerd/runtime/restart"
gocni "github.com/containerd/go-cni"
"github.com/containerd/nerdctl/pkg/defaults"
"github.com/containerd/nerdctl/pkg/idgen"
Expand Down Expand Up @@ -102,7 +101,7 @@ func setCreateFlags(cmd *cobra.Command) {
cmd.Flags().BoolP("interactive", "i", false, "Keep STDIN open even if not attached")
cmd.Flags().String("restart", "no", `Restart policy to apply when a container exits (implemented values: "no"|"always")`)
cmd.RegisterFlagCompletionFunc("restart", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return []string{"no", "always"}, cobra.ShellCompDirectiveNoFileComp
return []string{"no", "always", "on-failure", "unless-stopped"}, cobra.ShellCompDirectiveNoFileComp
})
cmd.Flags().Bool("rm", false, "Automatically remove the container when it exits")
cmd.Flags().String("pull", "missing", `Pull image before running ("always"|"missing"|"never")`)
Expand Down Expand Up @@ -475,7 +474,7 @@ func createContainer(cmd *cobra.Command, ctx context.Context, client *containerd
if err != nil {
return nil, "", nil, err
}
restartOpts, err := generateRestartOpts(restartValue, logURI)
restartOpts, err := generateRestartOpts(ctx, client, restartValue, logURI)
if err != nil {
return nil, "", nil, err
}
Expand Down Expand Up @@ -783,21 +782,6 @@ func withNerdctlOCIHook(cmd *cobra.Command, id, stateDir string) (oci.SpecOpts,
}, nil
}

func generateRestartOpts(restartFlag, logURI string) ([]containerd.NewContainerOpts, error) {
switch restartFlag {
case "", "no":
return nil, nil
case "always":
opts := []containerd.NewContainerOpts{restart.WithStatus(containerd.Running)}
if logURI != "" {
opts = append(opts, restart.WithLogURIString(logURI))
}
return opts, nil
default:
return nil, fmt.Errorf("unsupported restart type %q, supported types are: \"no\", \"always\"", restartFlag)
}
}

func getContainerStateDirPath(cmd *cobra.Command, dataStore, id string) (string, error) {
ns, err := cmd.Flags().GetString("namespace")
if err != nil {
Expand Down
67 changes: 67 additions & 0 deletions cmd/nerdctl/run_restart.go
@@ -0,0 +1,67 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
"context"
"fmt"
"strconv"
"strings"

"github.com/containerd/containerd"
"github.com/containerd/containerd/runtime/restart"
"github.com/containerd/nerdctl/pkg/strutil"
)

func generateRestartOpts(ctx context.Context, client *containerd.Client, restartFlag, logURI string) ([]containerd.NewContainerOpts, error) {
policySlice := strings.Split(restartFlag, ":")
switch policySlice[0] {
case "", "no":
return nil, nil
}
res, err := client.IntrospectionService().Plugins(ctx, []string{"id==restart"})
if err != nil {
return nil, err
}
if len(res.Plugins) == 0 {
return nil, fmt.Errorf("no restart plugin found")
}
restartPlugin := res.Plugins[0]
capabilities := restartPlugin.Capabilities
if len(capabilities) == 0 {
capabilities = []string{"always"}
}
if !strutil.InStringSlice(capabilities, policySlice[0]) {
return nil, fmt.Errorf("unsupported restart policy %q, supported policies are: %q", policySlice[0], restartPlugin.Capabilities)
}
policy, err := restart.NewPolicy(restartFlag)
if err != nil {
return nil, err
}
opts := []containerd.NewContainerOpts{restart.WithPolicy(policy), restart.WithStatus(containerd.Running)}
if logURI != "" {
opts = append(opts, restart.WithLogURIString(logURI))
}
return opts, nil
}

func updateContainerStoppedLabel(ctx context.Context, container containerd.Container, stopped bool) error {
opt := containerd.WithAdditionalContainerLabels(map[string]string{
restart.ExplicitlyStoppedLabel: strconv.FormatBool(stopped),
})
return container.Update(ctx, containerd.UpdateContainerOpts(opt))
}
46 changes: 46 additions & 0 deletions cmd/nerdctl/run_restart_linux_test.go
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/containerd/nerdctl/pkg/testutil/nettestutil"

"gotest.tools/v3/assert"
"gotest.tools/v3/poll"
)

func TestRunRestart(t *testing.T) {
Expand Down Expand Up @@ -87,3 +88,48 @@ func TestRunRestart(t *testing.T) {
base.DumpDaemonLogs(10)
t.Fatalf("the container does not seem to be restarted")
}

func TestRunRestartWithOnFailure(t *testing.T) {
base := testutil.NewBase(t)
if testutil.GetTarget() == testutil.Nerdctl {
testutil.RequireContainerdPlugin(base, "io.containerd.internal.v1", "restart", []string{"on-failure"})
}
tID := testutil.Identifier(t)
defer base.Cmd("rm", "-f", tID).Run()
base.Cmd("run", "-d", "--restart=on-failure:2", "--name", tID, testutil.AlpineImage, "sh", "-c", "exit 1").AssertOK()

check := func(log poll.LogT) poll.Result {
inspect := base.InspectContainer(tID)
if inspect.State != nil && inspect.State.Status == "exited" {
return poll.Success()
}
return poll.Continue("container is not yet exited")
}
poll.WaitOn(t, check, poll.WithDelay(100*time.Microsecond), poll.WithTimeout(60*time.Second))
inspect := base.InspectContainer(tID)
assert.Equal(t, inspect.RestartCount, 2)
}

func TestRunRestartWithUnlessStopped(t *testing.T) {
base := testutil.NewBase(t)
if testutil.GetTarget() == testutil.Nerdctl {
testutil.RequireContainerdPlugin(base, "io.containerd.internal.v1", "restart", []string{"unless-stopped"})
}
tID := testutil.Identifier(t)
defer base.Cmd("rm", "-f", tID).Run()
base.Cmd("run", "-d", "--restart=unless-stopped", "--name", tID, testutil.AlpineImage, "sh", "-c", "exit 1").AssertOK()

check := func(log poll.LogT) poll.Result {
inspect := base.InspectContainer(tID)
if inspect.State != nil && inspect.State.Status == "exited" {
return poll.Success()
}
if inspect.RestartCount == 2 {
base.Cmd("stop", tID).AssertOK()
}
return poll.Continue("container is not yet exited")
}
poll.WaitOn(t, check, poll.WithDelay(100*time.Microsecond), poll.WithTimeout(60*time.Second))
inspect := base.InspectContainer(tID)
assert.Equal(t, inspect.RestartCount, 2)
}
3 changes: 3 additions & 0 deletions cmd/nerdctl/start.go
Expand Up @@ -90,6 +90,9 @@ func startContainer(ctx context.Context, container containerd.Container) error {
logrus.Warnf("container %s is already running", container.ID())
return nil
}
if err := updateContainerStoppedLabel(ctx, container, false); err != nil {
return err
}
if oldTask, err := container.Task(ctx, nil); err == nil {
if _, err := oldTask.Delete(ctx); err != nil {
logrus.WithError(err).Debug("failed to delete old task")
Expand Down
4 changes: 4 additions & 0 deletions cmd/nerdctl/stop.go
Expand Up @@ -90,6 +90,10 @@ func stopAction(cmd *cobra.Command, args []string) error {
}

func stopContainer(ctx context.Context, container containerd.Container, timeout time.Duration) error {
if err := updateContainerStoppedLabel(ctx, container, true); err != nil {
return err
}

task, err := container.Task(ctx, cio.Load)
if err != nil {
return err
Expand Down
2 changes: 1 addition & 1 deletion cmd/nerdctl/volume_rm.go
Expand Up @@ -67,7 +67,7 @@ func volumeRmAction(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
err = json.Unmarshal(n.Container.Spec.Value, &mount)
err = json.Unmarshal(n.Container.Spec.GetValue(), &mount)
if err != nil {
return err
}
Expand Down
34 changes: 21 additions & 13 deletions go.mod
Expand Up @@ -11,11 +11,11 @@ require (
github.com/containerd/containerd v1.6.3
github.com/containerd/continuity v0.3.0
github.com/containerd/go-cni v1.1.5
github.com/containerd/imgcrypt v1.1.4
github.com/containerd/imgcrypt v1.1.5-0.20220421044638-8ba028dca028
github.com/containerd/stargz-snapshotter v0.11.4
github.com/containerd/stargz-snapshotter/estargz v0.11.4
github.com/containerd/stargz-snapshotter/ipfs v0.11.4
github.com/containerd/typeurl v1.0.2
github.com/containerd/typeurl v1.0.3-0.20220422153119-7f6e6d160d67
github.com/containernetworking/cni v1.1.0
github.com/containernetworking/plugins v1.1.1
github.com/cyphar/filepath-securejoin v0.2.3
Expand All @@ -24,7 +24,6 @@ require (
github.com/docker/go-connections v0.4.0
github.com/docker/go-units v0.4.0
github.com/fatih/color v1.13.0
github.com/gogo/protobuf v1.3.2
github.com/hashicorp/go-multierror v1.1.1
github.com/ipfs/go-cid v0.1.0
github.com/ipfs/go-ipfs-files v0.1.1
Expand All @@ -46,15 +45,23 @@ require (
github.com/vishvananda/netlink v1.2.0-beta
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064
golang.org/x/net v0.0.0-20220225172249-27dd8689420f
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20220405210540-1e041c57c461
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
gopkg.in/yaml.v2 v2.4.0
gotest.tools/v3 v3.2.0
)

require github.com/multiformats/go-multicodec v0.4.1 // indirect
require (
github.com/blang/semver v3.5.1+incompatible // indirect
github.com/container-orchestrated-devices/container-device-interface v0.3.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/multiformats/go-multicodec v0.4.1 // indirect
github.com/opencontainers/runtime-tools v0.0.0-20190417131837-cd1349b7c47e // indirect
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)

require (
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
Expand All @@ -64,7 +71,7 @@ require (
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cilium/ebpf v0.7.0 // indirect
github.com/containerd/fifo v1.0.0 // indirect
github.com/containerd/ttrpc v1.1.0 // indirect
github.com/containerd/ttrpc v1.1.1-0.20220420014843-944ef4a40df3 // indirect
github.com/containers/ocicrypt v1.1.3 // indirect
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
Expand All @@ -75,7 +82,6 @@ require (
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/godbus/dbus/v5 v5.0.6 // indirect
github.com/gogo/googleapis v1.4.1 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.6 // indirect
Expand Down Expand Up @@ -123,7 +129,7 @@ require (
github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/moby/locker v1.0.1 // indirect
github.com/moby/sys/mountinfo v0.6.1 // indirect
github.com/moby/sys/signal v0.6.0 // indirect
github.com/moby/sys/signal v0.7.0 // indirect
github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect
Expand All @@ -133,7 +139,7 @@ require (
github.com/multiformats/go-multihash v0.0.15 // indirect
github.com/multiformats/go-varint v0.0.6 // indirect
github.com/opencontainers/runc v1.1.1 // indirect
github.com/opencontainers/selinux v1.10.0 // indirect
github.com/opencontainers/selinux v1.10.1 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/polydawn/refmt v0.0.0-20201211092308-30ac6d18308e // indirect
Expand All @@ -160,11 +166,13 @@ require (
go.uber.org/zap v1.17.0 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350 // indirect
google.golang.org/grpc v1.45.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect
google.golang.org/genproto v0.0.0-20220426171045-31bebdecfb46 // indirect
google.golang.org/grpc v1.46.0 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
)

// Temporary fork for avoiding importing patent-protected code: https://github.com/hashicorp/golang-lru/issues/73
replace github.com/hashicorp/golang-lru => github.com/ktock/golang-lru v0.5.5-0.20211029085301-ec551be6f75c

replace github.com/containerd/containerd => github.com/containerd/containerd v1.6.1-0.20220428184543-bb8b134a1797

0 comments on commit d110fea

Please sign in to comment.