Re-add ReadableDuration types to health check definition

This is to fix the backwards-incompatible change made in 1.4.1 by
changing these fields to time.Duration.
This commit is contained in:
Kyle Havlovitz 2019-01-24 17:28:52 -08:00
parent 552e150536
commit 1a4978fb94
No known key found for this signature in database
GPG Key ID: 8A5E6B173056AD6C
5 changed files with 126 additions and 27 deletions

View File

@ -2003,6 +2003,9 @@ func TestAgent_PurgeCheckOnDuplicate(t *testing.T) {
Name: "memory check", Name: "memory check",
Status: api.HealthCritical, Status: api.HealthCritical,
Notes: "my cool notes", Notes: "my cool notes",
Definition: structs.HealthCheckDefinition{
Interval: 30 * time.Second,
},
} }
if got, want := result, expected; !verify.Values(t, "", got, want) { if got, want := result, expected; !verify.Values(t, "", got, want) {
t.FailNow() t.FailNow()

View File

@ -9,6 +9,7 @@ import (
"net/http/pprof" "net/http/pprof"
"net/url" "net/url"
"os" "os"
"reflect"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
@ -20,6 +21,7 @@ import (
"github.com/hashicorp/consul/agent/cache" "github.com/hashicorp/consul/agent/cache"
"github.com/hashicorp/consul/agent/consul" "github.com/hashicorp/consul/agent/consul"
"github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/api"
cleanhttp "github.com/hashicorp/go-cleanhttp" cleanhttp "github.com/hashicorp/go-cleanhttp"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -510,7 +512,10 @@ func decodeBody(req *http.Request, out interface{}, cb func(interface{}) error)
} }
decodeConf := &mapstructure.DecoderConfig{ decodeConf := &mapstructure.DecoderConfig{
DecodeHook: mapstructure.StringToTimeDurationHookFunc(), DecodeHook: mapstructure.ComposeDecodeHookFunc(
mapstructure.StringToTimeDurationHookFunc(),
stringToReadableDurationFunc(),
),
Result: &out, Result: &out,
} }
@ -522,6 +527,32 @@ func decodeBody(req *http.Request, out interface{}, cb func(interface{}) error)
return decoder.Decode(raw) return decoder.Decode(raw)
} }
// stringToReadableDurationFunc is a mapstructure hook for decoding a string
// into an api.ReadableDuration for backwards compatibility.
func stringToReadableDurationFunc() mapstructure.DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data interface{}) (interface{}, error) {
var v api.ReadableDuration
if t != reflect.TypeOf(v) {
return data, nil
}
switch {
case f.Kind() == reflect.String:
if dur, err := time.ParseDuration(data.(string)); err != nil {
return nil, err
} else {
v = api.ReadableDuration(dur)
}
return v, nil
default:
return data, nil
}
}
}
// setTranslateAddr is used to set the address translation header. This is only // setTranslateAddr is used to set the address translation header. This is only
// present if the feature is active. // present if the feature is active.
func setTranslateAddr(resp http.ResponseWriter, active bool) { func setTranslateAddr(resp http.ResponseWriter, active bool) {

View File

@ -239,9 +239,9 @@ func (s *HTTPServer) convertOps(resp http.ResponseWriter, req *http.Request) (st
Header: check.Definition.Header, Header: check.Definition.Header,
Method: check.Definition.Method, Method: check.Definition.Method,
TCP: check.Definition.TCP, TCP: check.Definition.TCP,
Interval: check.Definition.Interval, Interval: check.Definition.IntervalDuration,
Timeout: check.Definition.Timeout, Timeout: check.Definition.TimeoutDuration,
DeregisterCriticalServiceAfter: check.Definition.DeregisterCriticalServiceAfter, DeregisterCriticalServiceAfter: check.Definition.DeregisterCriticalServiceAfterDuration,
}, },
RaftIndex: structs.RaftIndex{ RaftIndex: structs.RaftIndex{
ModifyIndex: check.ModifyIndex, ModifyIndex: check.ModifyIndex,

View File

@ -51,14 +51,19 @@ type HealthCheckDefinition struct {
Method string Method string
TLSSkipVerify bool TLSSkipVerify bool
TCP string TCP string
Interval time.Duration IntervalDuration time.Duration `json:"-"`
Timeout time.Duration TimeoutDuration time.Duration `json:"-"`
DeregisterCriticalServiceAfter time.Duration DeregisterCriticalServiceAfterDuration time.Duration `json:"-"`
// DEPRECATED in Consul 1.4.1. Use the above time.Duration fields instead.
Interval ReadableDuration
Timeout ReadableDuration
DeregisterCriticalServiceAfter ReadableDuration
} }
func (d *HealthCheckDefinition) MarshalJSON() ([]byte, error) { func (d *HealthCheckDefinition) MarshalJSON() ([]byte, error) {
type Alias HealthCheckDefinition type Alias HealthCheckDefinition
return json.Marshal(&struct { out := &struct {
Interval string Interval string
Timeout string Timeout string
DeregisterCriticalServiceAfter string DeregisterCriticalServiceAfter string
@ -68,7 +73,19 @@ func (d *HealthCheckDefinition) MarshalJSON() ([]byte, error) {
Timeout: d.Timeout.String(), Timeout: d.Timeout.String(),
DeregisterCriticalServiceAfter: d.DeregisterCriticalServiceAfter.String(), DeregisterCriticalServiceAfter: d.DeregisterCriticalServiceAfter.String(),
Alias: (*Alias)(d), Alias: (*Alias)(d),
}) }
if d.IntervalDuration != 0 {
out.Interval = d.IntervalDuration.String()
}
if d.TimeoutDuration != 0 {
out.Timeout = d.TimeoutDuration.String()
}
if d.DeregisterCriticalServiceAfterDuration != 0 {
out.DeregisterCriticalServiceAfter = d.DeregisterCriticalServiceAfterDuration.String()
}
return json.Marshal(out)
} }
func (d *HealthCheckDefinition) UnmarshalJSON(data []byte) error { func (d *HealthCheckDefinition) UnmarshalJSON(data []byte) error {
@ -84,21 +101,26 @@ func (d *HealthCheckDefinition) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, &aux); err != nil { if err := json.Unmarshal(data, &aux); err != nil {
return err return err
} }
// Parse the values into both the time.Duration and old ReadableDuration fields.
var err error var err error
if aux.Interval != "" { if aux.Interval != "" {
if d.Interval, err = time.ParseDuration(aux.Interval); err != nil { if d.IntervalDuration, err = time.ParseDuration(aux.Interval); err != nil {
return err return err
} }
d.Interval = ReadableDuration(d.IntervalDuration)
} }
if aux.Timeout != "" { if aux.Timeout != "" {
if d.Timeout, err = time.ParseDuration(aux.Timeout); err != nil { if d.TimeoutDuration, err = time.ParseDuration(aux.Timeout); err != nil {
return err return err
} }
d.Timeout = ReadableDuration(d.TimeoutDuration)
} }
if aux.DeregisterCriticalServiceAfter != "" { if aux.DeregisterCriticalServiceAfter != "" {
if d.DeregisterCriticalServiceAfter, err = time.ParseDuration(aux.DeregisterCriticalServiceAfter); err != nil { if d.DeregisterCriticalServiceAfterDuration, err = time.ParseDuration(aux.DeregisterCriticalServiceAfter); err != nil {
return err return err
} }
d.DeregisterCriticalServiceAfter = ReadableDuration(d.DeregisterCriticalServiceAfterDuration)
} }
return nil return nil
} }

View File

@ -33,12 +33,26 @@ func TestAPI_ClientTxn(t *testing.T) {
ID: "foo1", ID: "foo1",
Service: "foo", Service: "foo",
}, },
Check: &AgentCheck{ Checks: HealthChecks{
{
CheckID: "bar", CheckID: "bar",
Status: "critical", Status: "critical",
Definition: HealthCheckDefinition{ Definition: HealthCheckDefinition{
TCP: "1.1.1.1", TCP: "1.1.1.1",
Interval: 5 * time.Second, IntervalDuration: 5 * time.Second,
TimeoutDuration: 10 * time.Second,
DeregisterCriticalServiceAfterDuration: 20 * time.Second,
},
},
{
CheckID: "baz",
Status: "passing",
Definition: HealthCheckDefinition{
TCP: "2.2.2.2",
Interval: ReadableDuration(40 * time.Second),
Timeout: ReadableDuration(80 * time.Second),
DeregisterCriticalServiceAfter: ReadableDuration(160 * time.Second),
},
}, },
}, },
} }
@ -93,6 +107,12 @@ func TestAPI_ClientTxn(t *testing.T) {
Check: HealthCheck{Node: "foo", CheckID: "bar"}, Check: HealthCheck{Node: "foo", CheckID: "bar"},
}, },
}, },
&TxnOp{
Check: &CheckTxnOp{
Verb: CheckGet,
Check: HealthCheck{Node: "foo", CheckID: "baz"},
},
},
} }
ok, ret, _, err := txn.Txn(ops, nil) ok, ret, _, err := txn.Txn(ops, nil)
if err != nil { if err != nil {
@ -119,7 +139,7 @@ func TestAPI_ClientTxn(t *testing.T) {
t.Fatalf("transaction failure") t.Fatalf("transaction failure")
} }
if ret == nil || len(ret.Errors) != 0 || len(ret.Results) != 5 { if ret == nil || len(ret.Errors) != 0 || len(ret.Results) != 6 {
t.Fatalf("bad: %v", ret) t.Fatalf("bad: %v", ret)
} }
expected := TxnResults{ expected := TxnResults{
@ -166,7 +186,30 @@ func TestAPI_ClientTxn(t *testing.T) {
Status: "critical", Status: "critical",
Definition: HealthCheckDefinition{ Definition: HealthCheckDefinition{
TCP: "1.1.1.1", TCP: "1.1.1.1",
Interval: 5 * time.Second, Interval: ReadableDuration(5 * time.Second),
IntervalDuration: 5 * time.Second,
Timeout: ReadableDuration(10 * time.Second),
TimeoutDuration: 10 * time.Second,
DeregisterCriticalServiceAfter: ReadableDuration(20 * time.Second),
DeregisterCriticalServiceAfterDuration: 20 * time.Second,
},
CreateIndex: ret.Results[4].Check.CreateIndex,
ModifyIndex: ret.Results[4].Check.CreateIndex,
},
},
&TxnResult{
Check: &HealthCheck{
Node: "foo",
CheckID: "baz",
Status: "passing",
Definition: HealthCheckDefinition{
TCP: "2.2.2.2",
Interval: ReadableDuration(40 * time.Second),
IntervalDuration: 40 * time.Second,
Timeout: ReadableDuration(80 * time.Second),
TimeoutDuration: 80 * time.Second,
DeregisterCriticalServiceAfter: ReadableDuration(160 * time.Second),
DeregisterCriticalServiceAfterDuration: 160 * time.Second,
}, },
CreateIndex: ret.Results[4].Check.CreateIndex, CreateIndex: ret.Results[4].Check.CreateIndex,
ModifyIndex: ret.Results[4].Check.CreateIndex, ModifyIndex: ret.Results[4].Check.CreateIndex,