add restart policy for enhanced restart manager
Signed-off-by: Ye Sijun <junnplus@gmail.com>
This commit is contained in:
		@@ -19,20 +19,24 @@ package client
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"bytes"
 | 
						"bytes"
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"path/filepath"
 | 
						"path/filepath"
 | 
				
			||||||
	"runtime"
 | 
						"runtime"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
	"syscall"
 | 
						"syscall"
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	. "github.com/containerd/containerd"
 | 
						. "github.com/containerd/containerd"
 | 
				
			||||||
	"github.com/containerd/containerd/containers"
 | 
						eventtypes "github.com/containerd/containerd/api/events"
 | 
				
			||||||
	"github.com/containerd/containerd/oci"
 | 
						"github.com/containerd/containerd/oci"
 | 
				
			||||||
	"github.com/containerd/containerd/pkg/testutil"
 | 
						"github.com/containerd/containerd/pkg/testutil"
 | 
				
			||||||
 | 
						"github.com/containerd/containerd/runtime/restart"
 | 
				
			||||||
	srvconfig "github.com/containerd/containerd/services/server/config"
 | 
						srvconfig "github.com/containerd/containerd/services/server/config"
 | 
				
			||||||
	"github.com/containerd/containerd/sys"
 | 
						"github.com/containerd/containerd/sys"
 | 
				
			||||||
 | 
						"github.com/containerd/typeurl"
 | 
				
			||||||
	exec "golang.org/x/sys/execabs"
 | 
						exec "golang.org/x/sys/execabs"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -148,7 +152,7 @@ version = 2
 | 
				
			|||||||
			oci.WithImageConfig(image),
 | 
								oci.WithImageConfig(image),
 | 
				
			||||||
			longCommand,
 | 
								longCommand,
 | 
				
			||||||
		),
 | 
							),
 | 
				
			||||||
		withRestartStatus(Running),
 | 
							restart.WithStatus(Running),
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		t.Fatal(err)
 | 
							t.Fatal(err)
 | 
				
			||||||
@@ -229,14 +233,115 @@ version = 2
 | 
				
			|||||||
	t.Logf("%v: the task was restarted since %v", time.Now(), lastCheck)
 | 
						t.Logf("%v: the task was restarted since %v", time.Now(), lastCheck)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// withRestartStatus is a copy of "github.com/containerd/containerd/runtime/restart".WithStatus.
 | 
					func TestRestartMonitorWithOnFailurePolicy(t *testing.T) {
 | 
				
			||||||
// This copy is needed because `go test` refuses circular imports.
 | 
						const (
 | 
				
			||||||
func withRestartStatus(status ProcessStatus) func(context.Context, *Client, *containers.Container) error {
 | 
							interval = 5 * time.Second
 | 
				
			||||||
	return func(_ context.Context, _ *Client, c *containers.Container) error {
 | 
						)
 | 
				
			||||||
		if c.Labels == nil {
 | 
						configTOML := fmt.Sprintf(`
 | 
				
			||||||
			c.Labels = make(map[string]string)
 | 
					version = 2
 | 
				
			||||||
 | 
					[plugins]
 | 
				
			||||||
 | 
					  [plugins."io.containerd.internal.v1.restart"]
 | 
				
			||||||
 | 
						  interval = "%s"
 | 
				
			||||||
 | 
					`, interval.String())
 | 
				
			||||||
 | 
						client, _, cleanup := newDaemonWithConfig(t, configTOML)
 | 
				
			||||||
 | 
						defer cleanup()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var (
 | 
				
			||||||
 | 
							ctx, cancel = testContext(t)
 | 
				
			||||||
 | 
							id          = t.Name()
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						defer cancel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						image, err := client.Pull(ctx, testImage, WithPullUnpack)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
		c.Labels["containerd.io/restart.status"] = string(status)
 | 
					
 | 
				
			||||||
		return nil
 | 
						policy, _ := restart.NewPolicy("on-failure:1")
 | 
				
			||||||
 | 
						container, err := client.NewContainer(ctx, id,
 | 
				
			||||||
 | 
							WithNewSnapshot(id, image),
 | 
				
			||||||
 | 
							WithNewSpec(
 | 
				
			||||||
 | 
								oci.WithImageConfig(image),
 | 
				
			||||||
 | 
								// always exited with 1
 | 
				
			||||||
 | 
								withExitStatus(1),
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
 | 
							restart.WithStatus(Running),
 | 
				
			||||||
 | 
							restart.WithPolicy(policy),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer func() {
 | 
				
			||||||
 | 
							if err := container.Delete(ctx, WithSnapshotCleanup); err != nil {
 | 
				
			||||||
 | 
								t.Logf("failed to delete container: %v", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						task, err := container.NewTask(ctx, empty())
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer func() {
 | 
				
			||||||
 | 
							if _, err := task.Delete(ctx, WithProcessKill); err != nil {
 | 
				
			||||||
 | 
								t.Logf("failed to delete task: %v", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := task.Start(ctx); err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						statusCh, err := task.Wait(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						eventCh, eventErrCh := client.Subscribe(ctx, `topic=="/tasks/create"`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						select {
 | 
				
			||||||
 | 
						case <-statusCh:
 | 
				
			||||||
 | 
						case <-time.After(30 * time.Second):
 | 
				
			||||||
 | 
							t.Fatal("should receive exit event in time")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						select {
 | 
				
			||||||
 | 
						case e := <-eventCh:
 | 
				
			||||||
 | 
							cid, err := convertTaskCreateEvent(e.Event)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								t.Fatal(err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if cid != id {
 | 
				
			||||||
 | 
								t.Fatalf("expected task id = %s, but got %s", id, cid)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case err := <-eventErrCh:
 | 
				
			||||||
 | 
							t.Fatalf("unexpected error from event channel: %v", err)
 | 
				
			||||||
 | 
						case <-time.After(1 * time.Minute):
 | 
				
			||||||
 | 
							t.Fatal("should receive create event in time")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						labels, err := container.Labels(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						restartCount, _ := strconv.Atoi(labels[restart.CountLabel])
 | 
				
			||||||
 | 
						if restartCount != 1 {
 | 
				
			||||||
 | 
							t.Fatalf("expected restart count to be 1, got %d", restartCount)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func convertTaskCreateEvent(e typeurl.Any) (string, error) {
 | 
				
			||||||
 | 
						id := ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						evt, err := typeurl.UnmarshalAny(e)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return "", fmt.Errorf("failed to unmarshalany: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch e := evt.(type) {
 | 
				
			||||||
 | 
						case *eventtypes.TaskCreate:
 | 
				
			||||||
 | 
							id = e.ContainerID
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							return "", errors.New("unsupported event")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return id, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,10 +20,12 @@ import (
 | 
				
			|||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"net/url"
 | 
						"net/url"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
	"syscall"
 | 
						"syscall"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/containerd/containerd"
 | 
						"github.com/containerd/containerd"
 | 
				
			||||||
	"github.com/containerd/containerd/cio"
 | 
						"github.com/containerd/containerd/cio"
 | 
				
			||||||
 | 
						"github.com/containerd/containerd/runtime/restart"
 | 
				
			||||||
	"github.com/sirupsen/logrus"
 | 
						"github.com/sirupsen/logrus"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -38,6 +40,7 @@ func (s *stopChange) apply(ctx context.Context, client *containerd.Client) error
 | 
				
			|||||||
type startChange struct {
 | 
					type startChange struct {
 | 
				
			||||||
	container containerd.Container
 | 
						container containerd.Container
 | 
				
			||||||
	logURI    string
 | 
						logURI    string
 | 
				
			||||||
 | 
						count     int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Deprecated(in release 1.5): but recognized now, prefer to use logURI
 | 
						// Deprecated(in release 1.5): but recognized now, prefer to use logURI
 | 
				
			||||||
	logPath string
 | 
						logPath string
 | 
				
			||||||
@@ -61,6 +64,15 @@ func (s *startChange) apply(ctx context.Context, client *containerd.Client) erro
 | 
				
			|||||||
			s.logPath, s.logURI)
 | 
								s.logPath, s.logURI)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if s.count > 0 {
 | 
				
			||||||
 | 
							labels := map[string]string{
 | 
				
			||||||
 | 
								restart.CountLabel: strconv.Itoa(s.count),
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							opt := containerd.WithAdditionalContainerLabels(labels)
 | 
				
			||||||
 | 
							if err := s.container.Update(ctx, containerd.UpdateContainerOpts(opt)); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	killTask(ctx, s.container)
 | 
						killTask(ctx, s.container)
 | 
				
			||||||
	task, err := s.container.NewTask(ctx, log)
 | 
						task, err := s.container.NewTask(ctx, log)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,6 +19,7 @@ package monitor
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
	"sync"
 | 
						"sync"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -72,6 +73,7 @@ func init() {
 | 
				
			|||||||
			},
 | 
								},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		InitFn: func(ic *plugin.InitContext) (interface{}, error) {
 | 
							InitFn: func(ic *plugin.InitContext) (interface{}, error) {
 | 
				
			||||||
 | 
								ic.Meta.Capabilities = []string{"no", "always", "on-failure", "unless-stopped"}
 | 
				
			||||||
			opts, err := getServicesOpts(ic)
 | 
								opts, err := getServicesOpts(ic)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				return nil, err
 | 
									return nil, err
 | 
				
			||||||
@@ -213,15 +215,29 @@ func (m *monitor) monitor(ctx context.Context) ([]change, error) {
 | 
				
			|||||||
			return nil, err
 | 
								return nil, err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		desiredStatus := containerd.ProcessStatus(labels[restart.StatusLabel])
 | 
							desiredStatus := containerd.ProcessStatus(labels[restart.StatusLabel])
 | 
				
			||||||
		if m.isSameStatus(ctx, desiredStatus, c) {
 | 
							task, err := c.Task(ctx, nil)
 | 
				
			||||||
 | 
							if err != nil && desiredStatus == containerd.Stopped {
 | 
				
			||||||
			continue
 | 
								continue
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							status, err := task.Status(ctx)
 | 
				
			||||||
 | 
							if err != nil && desiredStatus == containerd.Stopped {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if desiredStatus == status.Status {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		switch desiredStatus {
 | 
							switch desiredStatus {
 | 
				
			||||||
		case containerd.Running:
 | 
							case containerd.Running:
 | 
				
			||||||
 | 
								if !restart.Reconcile(status, labels) {
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								restartCount, _ := strconv.Atoi(labels[restart.CountLabel])
 | 
				
			||||||
			changes = append(changes, &startChange{
 | 
								changes = append(changes, &startChange{
 | 
				
			||||||
				container: c,
 | 
									container: c,
 | 
				
			||||||
				logPath:   labels[restart.LogPathLabel],
 | 
									logPath:   labels[restart.LogPathLabel],
 | 
				
			||||||
				logURI:    labels[restart.LogURILabel],
 | 
									logURI:    labels[restart.LogURILabel],
 | 
				
			||||||
 | 
									count:     restartCount + 1,
 | 
				
			||||||
			})
 | 
								})
 | 
				
			||||||
		case containerd.Stopped:
 | 
							case containerd.Stopped:
 | 
				
			||||||
			changes = append(changes, &stopChange{
 | 
								changes = append(changes, &stopChange{
 | 
				
			||||||
@@ -231,15 +247,3 @@ func (m *monitor) monitor(ctx context.Context) ([]change, error) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	return changes, nil
 | 
						return changes, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
func (m *monitor) isSameStatus(ctx context.Context, desired containerd.ProcessStatus, container containerd.Container) bool {
 | 
					 | 
				
			||||||
	task, err := container.Task(ctx, nil)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return desired == containerd.Stopped
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	state, err := task.Status(ctx)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return desired == containerd.Stopped
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return desired == state.Status
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,11 +31,15 @@ package restart
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
	"net/url"
 | 
						"net/url"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/containerd/containerd"
 | 
						"github.com/containerd/containerd"
 | 
				
			||||||
	"github.com/containerd/containerd/cio"
 | 
						"github.com/containerd/containerd/cio"
 | 
				
			||||||
	"github.com/containerd/containerd/containers"
 | 
						"github.com/containerd/containerd/containers"
 | 
				
			||||||
 | 
						"github.com/sirupsen/logrus"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
@@ -44,12 +48,106 @@ const (
 | 
				
			|||||||
	// LogURILabel sets the restart log uri label for a container
 | 
						// LogURILabel sets the restart log uri label for a container
 | 
				
			||||||
	LogURILabel = "containerd.io/restart.loguri"
 | 
						LogURILabel = "containerd.io/restart.loguri"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// PolicyLabel sets the restart policy label for a container
 | 
				
			||||||
 | 
						PolicyLabel = "containerd.io/restart.policy"
 | 
				
			||||||
 | 
						// CountLabel sets the restart count label for a container
 | 
				
			||||||
 | 
						CountLabel = "containerd.io/restart.count"
 | 
				
			||||||
 | 
						// ExplicitlyStoppedLabel sets the restart explicitly stopped label for a container
 | 
				
			||||||
 | 
						ExplicitlyStoppedLabel = "containerd.io/restart.explicitly-stopped"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// LogPathLabel sets the restart log path label for a container
 | 
						// LogPathLabel sets the restart log path label for a container
 | 
				
			||||||
	//
 | 
						//
 | 
				
			||||||
	// Deprecated(in release 1.5): use LogURILabel
 | 
						// Deprecated(in release 1.5): use LogURILabel
 | 
				
			||||||
	LogPathLabel = "containerd.io/restart.logpath"
 | 
						LogPathLabel = "containerd.io/restart.logpath"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Policy represents the restart policies of a container.
 | 
				
			||||||
 | 
					type Policy struct {
 | 
				
			||||||
 | 
						name              string
 | 
				
			||||||
 | 
						maximumRetryCount int
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewPolicy creates a restart policy with the specified name.
 | 
				
			||||||
 | 
					// supports the following restart policies:
 | 
				
			||||||
 | 
					// - no, Do not restart the container.
 | 
				
			||||||
 | 
					// - always, Always restart the container regardless of the exit status.
 | 
				
			||||||
 | 
					// - on-failure[:max-retries], Restart only if the container exits with a non-zero exit status.
 | 
				
			||||||
 | 
					// - unless-stopped, Always restart the container unless it is stopped.
 | 
				
			||||||
 | 
					func NewPolicy(policy string) (*Policy, error) {
 | 
				
			||||||
 | 
						policySlice := strings.Split(policy, ":")
 | 
				
			||||||
 | 
						var (
 | 
				
			||||||
 | 
							err        error
 | 
				
			||||||
 | 
							retryCount int
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						switch policySlice[0] {
 | 
				
			||||||
 | 
						case "", "no", "always", "unless-stopped":
 | 
				
			||||||
 | 
							policy = policySlice[0]
 | 
				
			||||||
 | 
							if policy == "" {
 | 
				
			||||||
 | 
								policy = "always"
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if len(policySlice) > 1 {
 | 
				
			||||||
 | 
								return nil, fmt.Errorf("restart policy %q not support max retry count", policySlice[0])
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case "on-failure":
 | 
				
			||||||
 | 
							policy = policySlice[0]
 | 
				
			||||||
 | 
							if len(policySlice) > 1 {
 | 
				
			||||||
 | 
								retryCount, err = strconv.Atoi(policySlice[1])
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return nil, fmt.Errorf("invalid max retry count: %s", policySlice[1])
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("restart policy %q not supported", policy)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return &Policy{
 | 
				
			||||||
 | 
							name:              policy,
 | 
				
			||||||
 | 
							maximumRetryCount: retryCount,
 | 
				
			||||||
 | 
						}, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (rp *Policy) String() string {
 | 
				
			||||||
 | 
						if rp.maximumRetryCount > 0 {
 | 
				
			||||||
 | 
							return fmt.Sprintf("%s:%d", rp.name, rp.maximumRetryCount)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return rp.name
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (rp *Policy) Name() string {
 | 
				
			||||||
 | 
						return rp.name
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (rp *Policy) MaximumRetryCount() int {
 | 
				
			||||||
 | 
						return rp.maximumRetryCount
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Reconcile reconciles the restart policy of a container.
 | 
				
			||||||
 | 
					func Reconcile(status containerd.Status, labels map[string]string) bool {
 | 
				
			||||||
 | 
						rp, err := NewPolicy(labels[PolicyLabel])
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							logrus.WithError(err).Error("policy reconcile")
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						switch rp.Name() {
 | 
				
			||||||
 | 
						case "", "always":
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						case "on-failure":
 | 
				
			||||||
 | 
							restartCount, err := strconv.Atoi(labels[CountLabel])
 | 
				
			||||||
 | 
							if err != nil && labels[CountLabel] != "" {
 | 
				
			||||||
 | 
								logrus.WithError(err).Error("policy reconcile")
 | 
				
			||||||
 | 
								return false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if status.ExitStatus != 0 && (rp.maximumRetryCount == 0 || restartCount < rp.maximumRetryCount) {
 | 
				
			||||||
 | 
								return true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case "unless-stopped":
 | 
				
			||||||
 | 
							explicitlyStopped, _ := strconv.ParseBool(labels[ExplicitlyStoppedLabel])
 | 
				
			||||||
 | 
							if !explicitlyStopped {
 | 
				
			||||||
 | 
								return true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// WithLogURI sets the specified log uri for a container.
 | 
					// WithLogURI sets the specified log uri for a container.
 | 
				
			||||||
func WithLogURI(uri *url.URL) func(context.Context, *containerd.Client, *containers.Container) error {
 | 
					func WithLogURI(uri *url.URL) func(context.Context, *containerd.Client, *containers.Container) error {
 | 
				
			||||||
	return WithLogURIString(uri.String())
 | 
						return WithLogURIString(uri.String())
 | 
				
			||||||
@@ -110,12 +208,22 @@ func WithStatus(status containerd.ProcessStatus) func(context.Context, *containe
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// WithPolicy sets the restart policy for a container
 | 
				
			||||||
 | 
					func WithPolicy(policy *Policy) func(context.Context, *containerd.Client, *containers.Container) error {
 | 
				
			||||||
 | 
						return func(_ context.Context, _ *containerd.Client, c *containers.Container) error {
 | 
				
			||||||
 | 
							ensureLabels(c)
 | 
				
			||||||
 | 
							c.Labels[PolicyLabel] = policy.String()
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// WithNoRestarts clears any restart information from the container
 | 
					// WithNoRestarts clears any restart information from the container
 | 
				
			||||||
func WithNoRestarts(_ context.Context, _ *containerd.Client, c *containers.Container) error {
 | 
					func WithNoRestarts(_ context.Context, _ *containerd.Client, c *containers.Container) error {
 | 
				
			||||||
	if c.Labels == nil {
 | 
						if c.Labels == nil {
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	delete(c.Labels, StatusLabel)
 | 
						delete(c.Labels, StatusLabel)
 | 
				
			||||||
 | 
						delete(c.Labels, PolicyLabel)
 | 
				
			||||||
	delete(c.Labels, LogPathLabel)
 | 
						delete(c.Labels, LogPathLabel)
 | 
				
			||||||
	delete(c.Labels, LogURILabel)
 | 
						delete(c.Labels, LogURILabel)
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										221
									
								
								runtime/restart/restart_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										221
									
								
								runtime/restart/restart_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,221 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					   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 restart
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/containerd/containerd"
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestNewRestartPolicy(t *testing.T) {
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							policy string
 | 
				
			||||||
 | 
							want   *Policy
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								policy: "unknow",
 | 
				
			||||||
 | 
								want:   nil,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								policy: "",
 | 
				
			||||||
 | 
								want:   &Policy{name: "always"},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								policy: "always",
 | 
				
			||||||
 | 
								want:   &Policy{name: "always"},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								policy: "always:3",
 | 
				
			||||||
 | 
								want:   nil,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								policy: "on-failure",
 | 
				
			||||||
 | 
								want:   &Policy{name: "on-failure"},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								policy: "on-failure:10",
 | 
				
			||||||
 | 
								want: &Policy{
 | 
				
			||||||
 | 
									name:              "on-failure",
 | 
				
			||||||
 | 
									maximumRetryCount: 10,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								policy: "unless-stopped",
 | 
				
			||||||
 | 
								want: &Policy{
 | 
				
			||||||
 | 
									name: "unless-stopped",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, testCase := range tests {
 | 
				
			||||||
 | 
							result, _ := NewPolicy(testCase.policy)
 | 
				
			||||||
 | 
							assert.Equal(t, testCase.want, result)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestRestartPolicyToString(t *testing.T) {
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							policy string
 | 
				
			||||||
 | 
							want   string
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								policy: "",
 | 
				
			||||||
 | 
								want:   "always",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								policy: "always",
 | 
				
			||||||
 | 
								want:   "always",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								policy: "on-failure",
 | 
				
			||||||
 | 
								want:   "on-failure",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								policy: "on-failure:10",
 | 
				
			||||||
 | 
								want:   "on-failure:10",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								policy: "unless-stopped",
 | 
				
			||||||
 | 
								want:   "unless-stopped",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, testCase := range tests {
 | 
				
			||||||
 | 
							policy, err := NewPolicy(testCase.policy)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								t.Fatal(err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							result := policy.String()
 | 
				
			||||||
 | 
							assert.Equal(t, testCase.want, result)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestRestartPolicyReconcile(t *testing.T) {
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							status containerd.Status
 | 
				
			||||||
 | 
							labels map[string]string
 | 
				
			||||||
 | 
							want   bool
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								status: containerd.Status{
 | 
				
			||||||
 | 
									Status: containerd.Stopped,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								labels: map[string]string{
 | 
				
			||||||
 | 
									PolicyLabel: "always",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								status: containerd.Status{
 | 
				
			||||||
 | 
									Status: containerd.Unknown,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								labels: map[string]string{
 | 
				
			||||||
 | 
									PolicyLabel: "always",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								status: containerd.Status{
 | 
				
			||||||
 | 
									Status: containerd.Stopped,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								labels: map[string]string{
 | 
				
			||||||
 | 
									PolicyLabel: "on-failure:10",
 | 
				
			||||||
 | 
									CountLabel:  "1",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								status: containerd.Status{
 | 
				
			||||||
 | 
									Status:     containerd.Unknown,
 | 
				
			||||||
 | 
									ExitStatus: 1,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								// test without count label
 | 
				
			||||||
 | 
								labels: map[string]string{
 | 
				
			||||||
 | 
									PolicyLabel: "on-failure:10",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								status: containerd.Status{
 | 
				
			||||||
 | 
									Status:     containerd.Unknown,
 | 
				
			||||||
 | 
									ExitStatus: 1,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								// test without valid count label
 | 
				
			||||||
 | 
								labels: map[string]string{
 | 
				
			||||||
 | 
									PolicyLabel: "on-failure:10",
 | 
				
			||||||
 | 
									CountLabel:  "invalid",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								status: containerd.Status{
 | 
				
			||||||
 | 
									Status:     containerd.Unknown,
 | 
				
			||||||
 | 
									ExitStatus: 1,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								labels: map[string]string{
 | 
				
			||||||
 | 
									PolicyLabel: "on-failure:10",
 | 
				
			||||||
 | 
									CountLabel:  "1",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								status: containerd.Status{
 | 
				
			||||||
 | 
									Status:     containerd.Unknown,
 | 
				
			||||||
 | 
									ExitStatus: 1,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								labels: map[string]string{
 | 
				
			||||||
 | 
									PolicyLabel: "on-failure:3",
 | 
				
			||||||
 | 
									CountLabel:  "3",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								status: containerd.Status{
 | 
				
			||||||
 | 
									Status: containerd.Unknown,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								labels: map[string]string{
 | 
				
			||||||
 | 
									PolicyLabel: "unless-stopped",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								status: containerd.Status{
 | 
				
			||||||
 | 
									Status: containerd.Stopped,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								labels: map[string]string{
 | 
				
			||||||
 | 
									PolicyLabel: "unless-stopped",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								status: containerd.Status{
 | 
				
			||||||
 | 
									Status: containerd.Stopped,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								labels: map[string]string{
 | 
				
			||||||
 | 
									PolicyLabel:            "unless-stopped",
 | 
				
			||||||
 | 
									ExplicitlyStoppedLabel: "true",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, testCase := range tests {
 | 
				
			||||||
 | 
							result := Reconcile(testCase.status, testCase.labels)
 | 
				
			||||||
 | 
							assert.Equal(t, testCase.want, result, testCase)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user