diff --git a/integration/client/restart_monitor_test.go b/integration/client/restart_monitor_test.go index e7d0025e6..3765c895d 100644 --- a/integration/client/restart_monitor_test.go +++ b/integration/client/restart_monitor_test.go @@ -37,6 +37,7 @@ import ( "github.com/containerd/containerd/runtime/restart" srvconfig "github.com/containerd/containerd/services/server/config" "github.com/containerd/typeurl/v2" + "github.com/stretchr/testify/require" exec "golang.org/x/sys/execabs" ) @@ -144,6 +145,9 @@ version = 2 t.Run("Always", func(t *testing.T) { testRestartMonitorAlways(t, client, interval) }) + t.Run("Paused Task", func(t *testing.T) { + testRestartMonitorPausedTaskWithAlways(t, client, interval) + }) t.Run("Failure Policy", func(t *testing.T) { testRestartMonitorWithOnFailurePolicy(t, client, interval) }) @@ -254,6 +258,84 @@ func testRestartMonitorAlways(t *testing.T, client *Client, interval time.Durati t.Logf("%v: the task was restarted since %v", time.Now(), lastCheck) } +func testRestartMonitorPausedTaskWithAlways(t *testing.T, client *Client, interval time.Duration) { + if runtime.GOOS == "windows" { + t.Skip("Pause task is not supported on Windows") + } + + const ( + epsilon = 1 * time.Second + count = 20 + ) + + var ( + ctx, cancel = testContext(t) + id = strings.ReplaceAll(t.Name(), "/", "_") + ) + defer cancel() + + image, err := client.GetImage(ctx, testImage) + if err != nil { + t.Fatal(err) + } + + container, err := client.NewContainer(ctx, id, + WithNewSnapshot(id, image), + WithNewSpec( + oci.WithImageConfig(image), + longCommand, + ), + restart.WithStatus(Running), + ) + 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) + } + + statusC, err := task.Wait(ctx) + if err != nil { + t.Fatal(err) + } + + t.Log("pause the task") + require.NoError(t, task.Pause(ctx)) + defer func() { + require.NoError(t, task.Resume(ctx)) + }() + + select { + case <-statusC: + t.Fatal("the paused task is killed") + case <-time.After(30 * time.Second): + } + + status, err := task.Status(ctx) + if err != nil { + t.Fatal(err) + } + if status.Status != Paused { + t.Fatalf("the paused task's status is changed to %s", status.Status) + } +} + // testRestartMonitorWithOnFailurePolicy restarts its container with `on-failure:1` func testRestartMonitorWithOnFailurePolicy(t *testing.T, client *Client, interval time.Duration) { var ( diff --git a/runtime/restart/monitor/monitor.go b/runtime/restart/monitor/monitor.go index 67f0d8011..63d3552c4 100644 --- a/runtime/restart/monitor/monitor.go +++ b/runtime/restart/monitor/monitor.go @@ -171,9 +171,15 @@ func (m *monitor) monitor(ctx context.Context) ([]change, error) { // which will result in an `on-failure` restart policy reconcile error. switch desiredStatus { case containerd.Running: + switch status.Status { + case containerd.Paused, containerd.Pausing: + continue + default: + } if !restart.Reconcile(status, labels) { continue } + restartCount, _ := strconv.Atoi(labels[restart.CountLabel]) if labels["containerd.io/restart.logpath"] != "" { logrus.Warn(`Label "containerd.io/restart.logpath" is no longer supported since containerd v2.0. Use "containerd.io/restart.loguri" instead.`)