Skip to content

Commit 9f3efd9

Browse files
committed
Update healthcheck orchestration logic
Signed-off-by: Arjun Raja Yogidas <[email protected]>
1 parent 3cdfccb commit 9f3efd9

19 files changed

+206
-274
lines changed

cmd/nerdctl/container/container_create.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -279,10 +279,6 @@ func createOptions(cmd *cobra.Command) (types.ContainerCreateOptions, error) {
279279
if err != nil {
280280
return opt, err
281281
}
282-
opt.HealthStartInterval, err = cmd.Flags().GetDuration("health-start-interval")
283-
if err != nil {
284-
return opt, err
285-
}
286282
opt.NoHealthcheck, err = cmd.Flags().GetBool("no-healthcheck")
287283
if err != nil {
288284
return opt, err

cmd/nerdctl/container/container_health_check_test.go renamed to cmd/nerdctl/container/container_health_check_linux_test.go

Lines changed: 121 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package container
1919
import (
2020
"encoding/json"
2121
"errors"
22-
"fmt"
2322
"strings"
2423
"testing"
2524
"time"
@@ -32,11 +31,16 @@ import (
3231
"github.com/containerd/nerdctl/mod/tigron/tig"
3332

3433
"github.com/containerd/nerdctl/v2/pkg/healthcheck"
34+
"github.com/containerd/nerdctl/v2/pkg/rootlessutil"
3535
"github.com/containerd/nerdctl/v2/pkg/testutil"
3636
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
3737
)
3838

3939
func TestContainerHealthCheckBasic(t *testing.T) {
40+
if rootlessutil.IsRootless() {
41+
t.Skip("healthcheck tests are skipped in rootless environment")
42+
}
43+
4044
testCase := nerdtest.Setup()
4145

4246
// Docker CLI does not provide a standalone healthcheck command.
@@ -134,6 +138,10 @@ func TestContainerHealthCheckBasic(t *testing.T) {
134138
}
135139

136140
func TestContainerHealthCheckAdvance(t *testing.T) {
141+
if rootlessutil.IsRootless() {
142+
t.Skip("healthcheck tests are skipped in rootless environment")
143+
}
144+
137145
testCase := nerdtest.Setup()
138146

139147
// Docker CLI does not provide a standalone healthcheck command.
@@ -391,43 +399,6 @@ func TestContainerHealthCheckAdvance(t *testing.T) {
391399
}
392400
},
393401
},
394-
{
395-
Description: "Healthcheck emits large output repeatedly",
396-
Setup: func(data test.Data, helpers test.Helpers) {
397-
helpers.Ensure("run", "-d", "--name", data.Identifier(),
398-
"--health-cmd", "yes X | head -c 60000",
399-
"--health-interval", "1s", "--health-timeout", "2s",
400-
testutil.CommonImage, "sleep", nerdtest.Infinity)
401-
nerdtest.EnsureContainerStarted(helpers, data.Identifier())
402-
},
403-
Cleanup: func(data test.Data, helpers test.Helpers) {
404-
helpers.Anyhow("rm", "-f", data.Identifier())
405-
},
406-
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
407-
for i := 0; i < 3; i++ {
408-
helpers.Ensure("container", "healthcheck", data.Identifier())
409-
time.Sleep(2 * time.Second)
410-
}
411-
return helpers.Command("inspect", data.Identifier())
412-
},
413-
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
414-
return &test.Expected{
415-
ExitCode: 0,
416-
Output: expect.All(func(_ string, t tig.T) {
417-
inspect := nerdtest.InspectContainer(helpers, data.Identifier())
418-
h := inspect.State.Health
419-
debug, _ := json.MarshalIndent(h, "", " ")
420-
t.Log(string(debug))
421-
assert.Assert(t, h != nil, "expected health state")
422-
assert.Equal(t, h.Status, healthcheck.Healthy)
423-
assert.Assert(t, len(h.Log) >= 3, "expected at least 3 health log entries")
424-
for _, log := range h.Log {
425-
assert.Assert(t, len(log.Output) >= 1024, fmt.Sprintf("each output should be >= 1024 bytes, was: %s", log.Output))
426-
}
427-
}),
428-
}
429-
},
430-
},
431402
{
432403
Description: "Health log in inspect keeps only the latest 5 entries",
433404
Setup: func(data test.Data, helpers test.Helpers) {
@@ -603,121 +574,121 @@ func TestContainerHealthCheckAdvance(t *testing.T) {
603574
testCase.Run(t)
604575
}
605576

606-
func TestHealthCheck_SystemdIntegration_Basic(t *testing.T) {
607-
testCase := nerdtest.Setup()
608-
testCase.Require = require.Not(nerdtest.Docker)
577+
// func TestHealthCheck_SystemdIntegration_Basic(t *testing.T) {
578+
// testCase := nerdtest.Setup()
579+
// testCase.Require = require.Not(nerdtest.Docker)
609580

610-
testCase.SubTests = []*test.Case{
611-
//{
612-
// Description: "Basic healthy container with systemd-triggered healthcheck",
613-
// Setup: func(data test.Data, helpers test.Helpers) {
614-
// helpers.Ensure("run", "-d", "--name", data.Identifier(),
615-
// "--health-cmd", "echo healthy",
616-
// "--health-interval", "2s",
617-
// testutil.CommonImage, "sleep", "30")
618-
// // Wait for a couple of healthchecks to execute
619-
// time.Sleep(5 * time.Second)
620-
// },
621-
// Cleanup: func(data test.Data, helpers test.Helpers) {
622-
// helpers.Anyhow("rm", "-f", data.Identifier())
623-
// },
624-
// Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
625-
// return &test.Expected{
626-
// ExitCode: 0,
627-
// Output: expect.All(func(stdout, _ string, t *testing.T) {
628-
// inspect := nerdtest.InspectContainer(helpers, data.Identifier())
629-
// h := inspect.State.Health
630-
// assert.Assert(t, h != nil, "expected health state to be present")
631-
// assert.Equal(t, h.Status, "healthy")
632-
// assert.Assert(t, len(h.Log) > 0, "expected at least one health check log entry")
633-
// }),
634-
// }
635-
// },
636-
//},
637-
//{
638-
// Description: "Kill stops healthcheck execution",
639-
// Setup: func(data test.Data, helpers test.Helpers) {
640-
// helpers.Ensure("run", "-d", "--name", data.Identifier(),
641-
// "--health-cmd", "echo healthy",
642-
// "--health-interval", "1s",
643-
// testutil.CommonImage, "sleep", "30")
644-
// time.Sleep(5 * time.Second) // Wait for at least one health check to execute
645-
// helpers.Ensure("kill", data.Identifier()) // Kill the container
646-
// time.Sleep(3 * time.Second) // Wait to allow any potential extra healthchecks (shouldn't happen)
647-
// },
648-
// Cleanup: func(data test.Data, helpers test.Helpers) {
649-
// helpers.Anyhow("rm", "-f", data.Identifier())
650-
// },
651-
// Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
652-
// return &test.Expected{
653-
// ExitCode: 0,
654-
// Output: expect.All(func(stdout, _ string, t *testing.T) {
655-
// inspect := nerdtest.InspectContainer(helpers, data.Identifier())
656-
// h := inspect.State.Health
657-
// assert.Assert(t, h != nil, "expected health state to be present")
658-
// assert.Assert(t, len(h.Log) > 0, "expected at least one health check log entry")
659-
//
660-
// // Get container FinishedAt timestamp
661-
// containerEnd, err := time.Parse(time.RFC3339Nano, inspect.State.FinishedAt)
662-
// assert.NilError(t, err, "parsing container FinishedAt")
663-
//
664-
// // Assert all healthcheck log start times are before container finished
665-
// for _, entry := range h.Log {
666-
// assert.NilError(t, err, "parsing healthcheck Start time")
667-
// assert.Assert(t, entry.Start.Before(containerEnd), "healthcheck ran after container was killed")
668-
// }
669-
// }),
670-
// }
671-
// },
672-
//},
581+
// testCase.SubTests = []*test.Case{
582+
// //{
583+
// // Description: "Basic healthy container with systemd-triggered healthcheck",
584+
// // Setup: func(data test.Data, helpers test.Helpers) {
585+
// // helpers.Ensure("run", "-d", "--name", data.Identifier(),
586+
// // "--health-cmd", "echo healthy",
587+
// // "--health-interval", "2s",
588+
// // testutil.CommonImage, "sleep", "30")
589+
// // // Wait for a couple of healthchecks to execute
590+
// // time.Sleep(5 * time.Second)
591+
// // },
592+
// // Cleanup: func(data test.Data, helpers test.Helpers) {
593+
// // helpers.Anyhow("rm", "-f", data.Identifier())
594+
// // },
595+
// // Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
596+
// // return &test.Expected{
597+
// // ExitCode: 0,
598+
// // Output: expect.All(func(stdout, _ string, t *testing.T) {
599+
// // inspect := nerdtest.InspectContainer(helpers, data.Identifier())
600+
// // h := inspect.State.Health
601+
// // assert.Assert(t, h != nil, "expected health state to be present")
602+
// // assert.Equal(t, h.Status, "healthy")
603+
// // assert.Assert(t, len(h.Log) > 0, "expected at least one health check log entry")
604+
// // }),
605+
// // }
606+
// // },
607+
// //},
608+
// //{
609+
// // Description: "Kill stops healthcheck execution",
610+
// // Setup: func(data test.Data, helpers test.Helpers) {
611+
// // helpers.Ensure("run", "-d", "--name", data.Identifier(),
612+
// // "--health-cmd", "echo healthy",
613+
// // "--health-interval", "1s",
614+
// // testutil.CommonImage, "sleep", "30")
615+
// // time.Sleep(5 * time.Second) // Wait for at least one health check to execute
616+
// // helpers.Ensure("kill", data.Identifier()) // Kill the container
617+
// // time.Sleep(3 * time.Second) // Wait to allow any potential extra healthchecks (shouldn't happen)
618+
// // },
619+
// // Cleanup: func(data test.Data, helpers test.Helpers) {
620+
// // helpers.Anyhow("rm", "-f", data.Identifier())
621+
// // },
622+
// // Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
623+
// // return &test.Expected{
624+
// // ExitCode: 0,
625+
// // Output: expect.All(func(stdout, _ string, t *testing.T) {
626+
// // inspect := nerdtest.InspectContainer(helpers, data.Identifier())
627+
// // h := inspect.State.Health
628+
// // assert.Assert(t, h != nil, "expected health state to be present")
629+
// // assert.Assert(t, len(h.Log) > 0, "expected at least one health check log entry")
630+
// //
631+
// // // Get container FinishedAt timestamp
632+
// // containerEnd, err := time.Parse(time.RFC3339Nano, inspect.State.FinishedAt)
633+
// // assert.NilError(t, err, "parsing container FinishedAt")
634+
// //
635+
// // // Assert all healthcheck log start times are before container finished
636+
// // for _, entry := range h.Log {
637+
// // assert.NilError(t, err, "parsing healthcheck Start time")
638+
// // assert.Assert(t, entry.Start.Before(containerEnd), "healthcheck ran after container was killed")
639+
// // }
640+
// // }),
641+
// // }
642+
// // },
643+
// //},
673644

674-
// {
675-
// Description: "Pause/unpause halts and resumes healthcheck execution",
676-
// Setup: func(data test.Data, helpers test.Helpers) {
677-
// data.Labels().Set("cID", data.Identifier())
678-
// helpers.Ensure("run", "-d", "--name", data.Identifier(),
679-
// "--health-cmd", "echo healthy",
680-
// "--health-interval", "1s",
681-
// testutil.CommonImage, "sleep", "30")
682-
// time.Sleep(4 * time.Second)
645+
// // {
646+
// // Description: "Pause/unpause halts and resumes healthcheck execution",
647+
// // Setup: func(data test.Data, helpers test.Helpers) {
648+
// // data.Labels().Set("cID", data.Identifier())
649+
// // helpers.Ensure("run", "-d", "--name", data.Identifier(),
650+
// // "--health-cmd", "echo healthy",
651+
// // "--health-interval", "1s",
652+
// // testutil.CommonImage, "sleep", "30")
653+
// // time.Sleep(4 * time.Second)
683654

684-
// // Inspect using raw command
685-
// helpers.Command("container", "inspect", data.Labels().Get("cID")).
686-
// Run(&test.Expected{
687-
// ExitCode: expect.ExitCodeNoCheck,
688-
// Output: func(stdout string, _ string, t *testing.T) {
689-
// var dc []dockercompat.Container
690-
// err := json.Unmarshal([]byte(stdout), &dc)
691-
// assert.NilError(t, err)
692-
// assert.Equal(t, len(dc), 1)
693-
// h := dc[0].State.Health
694-
// assert.Assert(t, h != nil, "expected health state to be present")
695-
// data.Labels().Set("healthStatus", h.Status)
696-
// data.Labels().Set("logCount", strconv.Itoa(len(h.Log)))
697-
// fmt.Printf("📋 Setup Inspect: Status=%s, LogCount=%s\n", h.Status, strconv.Itoa(len(h.Log)))
698-
// },
699-
// })
700-
// },
701-
// Cleanup: func(data test.Data, helpers test.Helpers) {
702-
// helpers.Anyhow("rm", "-f", data.Identifier())
703-
// },
704-
// Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
705-
// return &test.Expected{
706-
// ExitCode: 0,
707-
// Output: expect.All(func(stdout, _ string, t *testing.T) {
708-
// before := data.Labels().Get("logCountBeforePause")
709-
// after := data.Labels().Get("logCountAfterUnpause")
655+
// // // Inspect using raw command
656+
// // helpers.Command("container", "inspect", data.Labels().Get("cID")).
657+
// // Run(&test.Expected{
658+
// // ExitCode: expect.ExitCodeNoCheck,
659+
// // Output: func(stdout string, _ string, t *testing.T) {
660+
// // var dc []dockercompat.Container
661+
// // err := json.Unmarshal([]byte(stdout), &dc)
662+
// // assert.NilError(t, err)
663+
// // assert.Equal(t, len(dc), 1)
664+
// // h := dc[0].State.Health
665+
// // assert.Assert(t, h != nil, "expected health state to be present")
666+
// // data.Labels().Set("healthStatus", h.Status)
667+
// // data.Labels().Set("logCount", strconv.Itoa(len(h.Log)))
668+
// // fmt.Printf("📋 Setup Inspect: Status=%s, LogCount=%s\n", h.Status, strconv.Itoa(len(h.Log)))
669+
// // },
670+
// // })
671+
// // },
672+
// // Cleanup: func(data test.Data, helpers test.Helpers) {
673+
// // helpers.Anyhow("rm", "-f", data.Identifier())
674+
// // },
675+
// // Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
676+
// // return &test.Expected{
677+
// // ExitCode: 0,
678+
// // Output: expect.All(func(stdout, _ string, t *testing.T) {
679+
// // before := data.Labels().Get("logCountBeforePause")
680+
// // after := data.Labels().Get("logCountAfterUnpause")
710681

711-
// beforeCount, _ := strconv.Atoi(before)
712-
// afterCount, _ := strconv.Atoi(after)
682+
// // beforeCount, _ := strconv.Atoi(before)
683+
// // afterCount, _ := strconv.Atoi(after)
713684

714-
// assert.Assert(t, afterCount > beforeCount,
715-
// "expected more healthchecks after unpause (got %d → %d)", beforeCount, afterCount)
716-
// }),
717-
// }
718-
// },
719-
// },
720-
}
685+
// // assert.Assert(t, afterCount > beforeCount,
686+
// // "expected more healthchecks after unpause (got %d → %d)", beforeCount, afterCount)
687+
// // }),
688+
// // }
689+
// // },
690+
// // },
691+
// }
721692

722-
testCase.Run(t)
723-
}
693+
// testCase.Run(t)
694+
// }

cmd/nerdctl/container/container_run.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package container
1919
import (
2020
"errors"
2121
"fmt"
22-
"github.com/containerd/nerdctl/v2/pkg/healthcheck"
2322
"runtime"
2423
"strings"
2524

@@ -28,7 +27,6 @@ import (
2827

2928
"github.com/containerd/console"
3029
"github.com/containerd/log"
31-
3230
"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion"
3331
"github.com/containerd/nerdctl/v2/pkg/annotations"
3432
"github.com/containerd/nerdctl/v2/pkg/api/types"
@@ -38,6 +36,7 @@ import (
3836
"github.com/containerd/nerdctl/v2/pkg/containerutil"
3937
"github.com/containerd/nerdctl/v2/pkg/defaults"
4038
"github.com/containerd/nerdctl/v2/pkg/errutil"
39+
"github.com/containerd/nerdctl/v2/pkg/healthcheck"
4140
"github.com/containerd/nerdctl/v2/pkg/labels"
4241
"github.com/containerd/nerdctl/v2/pkg/logging"
4342
"github.com/containerd/nerdctl/v2/pkg/netutil"
@@ -241,7 +240,6 @@ func setCreateFlags(cmd *cobra.Command) {
241240
cmd.Flags().Duration("health-timeout", 0, "Maximum time to allow one check to run (default: 30s)")
242241
cmd.Flags().Int("health-retries", 0, "Consecutive failures needed to report unhealthy (default: 3)")
243242
cmd.Flags().Duration("health-start-period", 0, "Start period for the container to initialize before starting health-retries countdown")
244-
cmd.Flags().Duration("health-start-interval", 0, "Time between running the checks during the start period")
245243
cmd.Flags().Bool("no-healthcheck", false, "Disable any container-specified HEALTHCHECK")
246244

247245
// #region env flags

cmd/nerdctl/container/container_run_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,9 @@ func TestRunDomainname(t *testing.T) {
841841
}
842842

843843
func TestRunHealthcheckFlags(t *testing.T) {
844+
if rootlessutil.IsRootless() {
845+
t.Skip("healthcheck tests are skipped in rootless environment")
846+
}
844847
testCase := nerdtest.Setup()
845848

846849
testCases := []struct {
@@ -990,6 +993,9 @@ func TestRunHealthcheckFlags(t *testing.T) {
990993
}
991994

992995
func TestRunHealthcheckFromImage(t *testing.T) {
996+
if rootlessutil.IsRootless() {
997+
t.Skip("healthcheck tests are skipped in rootless environment")
998+
}
993999
nerdtest.Setup()
9941000

9951001
dockerfile := fmt.Sprintf(`FROM %s

cmd/nerdctl/helpers/flagutil.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,7 @@ func ValidateHealthcheckFlags(options types.ContainerCreateOptions) error {
5252
options.HealthInterval != 0 ||
5353
options.HealthTimeout != 0 ||
5454
options.HealthRetries != 0 ||
55-
options.HealthStartPeriod != 0 ||
56-
options.HealthStartInterval != 0
55+
options.HealthStartPeriod != 0
5756

5857
if options.NoHealthcheck {
5958
if options.HealthCmd != "" || healthFlagsSet {
@@ -74,9 +73,6 @@ func ValidateHealthcheckFlags(options types.ContainerCreateOptions) error {
7473
if options.HealthStartPeriod < 0 {
7574
return fmt.Errorf("--health-start-period cannot be negative")
7675
}
77-
if options.HealthStartInterval < 0 {
78-
return fmt.Errorf("--health-start-interval cannot be negative")
79-
}
8076
return nil
8177
}
8278

0 commit comments

Comments
 (0)