From a58f798fdf87a6986e3c50538342e3b713421e9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20Fax=C3=B6?= Date: Tue, 29 Nov 2016 10:58:47 +0100 Subject: [PATCH] Added start period option to health check. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Elias Faxö --- command/container/opts.go | 16 ++++++++++++---- command/container/opts_test.go | 4 ++-- command/service/opts.go | 18 +++++++++++++----- command/service/opts_test.go | 18 ++++++++++-------- command/service/update.go | 8 ++++++-- command/service/update_test.go | 5 +++++ compose/convert/service.go | 21 ++++++++++++++------- compose/types/types.go | 11 ++++++----- 8 files changed, 68 insertions(+), 33 deletions(-) diff --git a/command/container/opts.go b/command/container/opts.go index fc4ac855d5..7480bfaced 100644 --- a/command/container/opts.go +++ b/command/container/opts.go @@ -113,6 +113,7 @@ type containerOptions struct { healthCmd string healthInterval time.Duration healthTimeout time.Duration + healthStartPeriod time.Duration healthRetries int runtime string autoRemove bool @@ -232,6 +233,8 @@ func addFlags(flags *pflag.FlagSet) *containerOptions { flags.DurationVar(&copts.healthInterval, "health-interval", 0, "Time between running the check (ns|us|ms|s|m|h) (default 0s)") flags.IntVar(&copts.healthRetries, "health-retries", 0, "Consecutive failures needed to report unhealthy") flags.DurationVar(&copts.healthTimeout, "health-timeout", 0, "Maximum time to allow one check to run (ns|us|ms|s|m|h) (default 0s)") + flags.DurationVar(&copts.healthStartPeriod, "health-start-period", 0, "Start period for the container to initialize before starting health-retries countdown (ns|us|ms|s|m|h) (default 0s)") + flags.SetAnnotation("health-start-period", "version", []string{"1.29"}) flags.BoolVar(&copts.noHealthcheck, "no-healthcheck", false, "Disable any container-specified HEALTHCHECK") // Resource management @@ -464,6 +467,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*containerConfig, err haveHealthSettings := copts.healthCmd != "" || copts.healthInterval != 0 || copts.healthTimeout != 0 || + copts.healthStartPeriod != 0 || copts.healthRetries != 0 if copts.noHealthcheck { if haveHealthSettings { @@ -486,12 +490,16 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*containerConfig, err if copts.healthRetries < 0 { return nil, errors.Errorf("--health-retries cannot be negative") } + if copts.healthStartPeriod < 0 { + return nil, fmt.Errorf("--health-start-period cannot be negative") + } healthConfig = &container.HealthConfig{ - Test: probe, - Interval: copts.healthInterval, - Timeout: copts.healthTimeout, - Retries: copts.healthRetries, + Test: probe, + Interval: copts.healthInterval, + Timeout: copts.healthTimeout, + StartPeriod: copts.healthStartPeriod, + Retries: copts.healthRetries, } } diff --git a/command/container/opts_test.go b/command/container/opts_test.go index b628c0b625..575b214edc 100644 --- a/command/container/opts_test.go +++ b/command/container/opts_test.go @@ -501,8 +501,8 @@ func TestParseHealth(t *testing.T) { checkError("--no-healthcheck conflicts with --health-* options", "--no-healthcheck", "--health-cmd=/check.sh -q", "img", "cmd") - health = checkOk("--health-timeout=2s", "--health-retries=3", "--health-interval=4.5s", "img", "cmd") - if health.Timeout != 2*time.Second || health.Retries != 3 || health.Interval != 4500*time.Millisecond { + health = checkOk("--health-timeout=2s", "--health-retries=3", "--health-interval=4.5s", "--health-start-period=5s", "img", "cmd") + if health.Timeout != 2*time.Second || health.Retries != 3 || health.Interval != 4500*time.Millisecond || health.StartPeriod != 5*time.Second { t.Fatalf("--health-*: got %#v", health) } } diff --git a/command/service/opts.go b/command/service/opts.go index cdfe513177..3300f34d83 100644 --- a/command/service/opts.go +++ b/command/service/opts.go @@ -282,6 +282,7 @@ type healthCheckOptions struct { interval PositiveDurationOpt timeout PositiveDurationOpt retries int + startPeriod PositiveDurationOpt noHealthcheck bool } @@ -301,18 +302,22 @@ func (opts *healthCheckOptions) toHealthConfig() (*container.HealthConfig, error if opts.cmd != "" { test = []string{"CMD-SHELL", opts.cmd} } - var interval, timeout time.Duration + var interval, timeout, startPeriod time.Duration if ptr := opts.interval.Value(); ptr != nil { interval = *ptr } if ptr := opts.timeout.Value(); ptr != nil { timeout = *ptr } + if ptr := opts.startPeriod.Value(); ptr != nil { + startPeriod = *ptr + } healthConfig = &container.HealthConfig{ - Test: test, - Interval: interval, - Timeout: timeout, - Retries: opts.retries, + Test: test, + Interval: interval, + Timeout: timeout, + Retries: opts.retries, + StartPeriod: startPeriod, } } return healthConfig, nil @@ -555,6 +560,8 @@ func addServiceFlags(flags *pflag.FlagSet, opts *serviceOptions) { flags.SetAnnotation(flagHealthTimeout, "version", []string{"1.25"}) flags.IntVar(&opts.healthcheck.retries, flagHealthRetries, 0, "Consecutive failures needed to report unhealthy") flags.SetAnnotation(flagHealthRetries, "version", []string{"1.25"}) + flags.Var(&opts.healthcheck.startPeriod, flagHealthStartPeriod, "Start period for the container to initialize before counting retries towards unstable (ns|us|ms|s|m|h)") + flags.SetAnnotation(flagHealthStartPeriod, "version", []string{"1.29"}) flags.BoolVar(&opts.healthcheck.noHealthcheck, flagNoHealthcheck, false, "Disable any container-specified HEALTHCHECK") flags.SetAnnotation(flagNoHealthcheck, "version", []string{"1.25"}) @@ -644,6 +651,7 @@ const ( flagHealthInterval = "health-interval" flagHealthRetries = "health-retries" flagHealthTimeout = "health-timeout" + flagHealthStartPeriod = "health-start-period" flagNoHealthcheck = "no-healthcheck" flagSecret = "secret" flagSecretAdd = "secret-add" diff --git a/command/service/opts_test.go b/command/service/opts_test.go index ac5106793b..46db5fc838 100644 --- a/command/service/opts_test.go +++ b/command/service/opts_test.go @@ -71,18 +71,20 @@ func TestUint64OptSetAndValue(t *testing.T) { func TestHealthCheckOptionsToHealthConfig(t *testing.T) { dur := time.Second opt := healthCheckOptions{ - cmd: "curl", - interval: PositiveDurationOpt{DurationOpt{value: &dur}}, - timeout: PositiveDurationOpt{DurationOpt{value: &dur}}, - retries: 10, + cmd: "curl", + interval: PositiveDurationOpt{DurationOpt{value: &dur}}, + timeout: PositiveDurationOpt{DurationOpt{value: &dur}}, + startPeriod: PositiveDurationOpt{DurationOpt{value: &dur}}, + retries: 10, } config, err := opt.toHealthConfig() assert.NilError(t, err) assert.Equal(t, reflect.DeepEqual(config, &container.HealthConfig{ - Test: []string{"CMD-SHELL", "curl"}, - Interval: time.Second, - Timeout: time.Second, - Retries: 10, + Test: []string{"CMD-SHELL", "curl"}, + Interval: time.Second, + Timeout: time.Second, + StartPeriod: time.Second, + Retries: 10, }), true) } diff --git a/command/service/update.go b/command/service/update.go index afa0f807e9..b2d77e6bad 100644 --- a/command/service/update.go +++ b/command/service/update.go @@ -897,7 +897,7 @@ func updateLogDriver(flags *pflag.FlagSet, taskTemplate *swarm.TaskSpec) error { } func updateHealthcheck(flags *pflag.FlagSet, containerSpec *swarm.ContainerSpec) error { - if !anyChanged(flags, flagNoHealthcheck, flagHealthCmd, flagHealthInterval, flagHealthRetries, flagHealthTimeout) { + if !anyChanged(flags, flagNoHealthcheck, flagHealthCmd, flagHealthInterval, flagHealthRetries, flagHealthTimeout, flagHealthStartPeriod) { return nil } if containerSpec.Healthcheck == nil { @@ -908,7 +908,7 @@ func updateHealthcheck(flags *pflag.FlagSet, containerSpec *swarm.ContainerSpec) return err } if noHealthcheck { - if !anyChanged(flags, flagHealthCmd, flagHealthInterval, flagHealthRetries, flagHealthTimeout) { + if !anyChanged(flags, flagHealthCmd, flagHealthInterval, flagHealthRetries, flagHealthTimeout, flagHealthStartPeriod) { containerSpec.Healthcheck = &container.HealthConfig{ Test: []string{"NONE"}, } @@ -927,6 +927,10 @@ func updateHealthcheck(flags *pflag.FlagSet, containerSpec *swarm.ContainerSpec) val := *flags.Lookup(flagHealthTimeout).Value.(*PositiveDurationOpt).Value() containerSpec.Healthcheck.Timeout = val } + if flags.Changed(flagHealthStartPeriod) { + val := *flags.Lookup(flagHealthStartPeriod).Value.(*PositiveDurationOpt).Value() + containerSpec.Healthcheck.StartPeriod = val + } if flags.Changed(flagHealthRetries) { containerSpec.Healthcheck.Retries, _ = flags.GetInt(flagHealthRetries) } diff --git a/command/service/update_test.go b/command/service/update_test.go index d71e065f91..7a588d7fef 100644 --- a/command/service/update_test.go +++ b/command/service/update_test.go @@ -311,6 +311,11 @@ func TestUpdateHealthcheckTable(t *testing.T) { initial: &container.HealthConfig{Test: []string{"CMD", "cmd1"}, Retries: 10}, expected: &container.HealthConfig{Test: []string{"CMD", "cmd1"}}, }, + { + flags: [][2]string{{"health-start-period", "1m"}}, + initial: &container.HealthConfig{Test: []string{"CMD", "cmd1"}}, + expected: &container.HealthConfig{Test: []string{"CMD", "cmd1"}, StartPeriod: time.Minute}, + }, { flags: [][2]string{{"health-cmd", "cmd1"}, {"no-healthcheck", "true"}}, err: "--no-healthcheck conflicts with --health-* options", diff --git a/compose/convert/service.go b/compose/convert/service.go index fe9c281ae9..7af24b2ec7 100644 --- a/compose/convert/service.go +++ b/compose/convert/service.go @@ -255,9 +255,9 @@ func convertHealthcheck(healthcheck *composetypes.HealthCheckConfig) (*container return nil, nil } var ( - err error - timeout, interval time.Duration - retries int + err error + timeout, interval, startPeriod time.Duration + retries int ) if healthcheck.Disable { if len(healthcheck.Test) != 0 { @@ -280,14 +280,21 @@ func convertHealthcheck(healthcheck *composetypes.HealthCheckConfig) (*container return nil, err } } + if healthcheck.StartPeriod != "" { + startPeriod, err = time.ParseDuration(healthcheck.StartPeriod) + if err != nil { + return nil, err + } + } if healthcheck.Retries != nil { retries = int(*healthcheck.Retries) } return &container.HealthConfig{ - Test: healthcheck.Test, - Timeout: timeout, - Interval: interval, - Retries: retries, + Test: healthcheck.Test, + Timeout: timeout, + Interval: interval, + Retries: retries, + StartPeriod: startPeriod, }, nil } diff --git a/compose/types/types.go b/compose/types/types.go index 1b4c4015ec..1a3772dada 100644 --- a/compose/types/types.go +++ b/compose/types/types.go @@ -163,11 +163,12 @@ type DeployConfig struct { // HealthCheckConfig the healthcheck configuration for a service type HealthCheckConfig struct { - Test HealthCheckTest - Timeout string - Interval string - Retries *uint64 - Disable bool + Test HealthCheckTest + Timeout string + Interval string + Retries *uint64 + StartPeriod string + Disable bool } // HealthCheckTest is the command run to test the health of a service