Define shorter timeout for ntp calls (2s) and tolerate failures (#911)

* Define shorter timeout for ntp calls (2s) and tolerate failures

* Use allowed failures instead of tolerated errorss
This commit is contained in:
Dmitry Shulyak 2018-05-09 08:10:48 +03:00 committed by GitHub
parent c673148bf4
commit 6c2a8ef1f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 72 additions and 24 deletions

View File

@ -18,13 +18,19 @@ const (
DefaultServer = "pool.ntp.org" DefaultServer = "pool.ntp.org"
// DefaultAttempts defines how many servers we will query // DefaultAttempts defines how many servers we will query
DefaultAttempts = 3 DefaultAttempts = 5
// DefaultMaxAllowedFailures defines how many failures will be tolerated.
DefaultMaxAllowedFailures = 2
// DefaultUpdatePeriod defines how often time will be queried from ntp. // DefaultUpdatePeriod defines how often time will be queried from ntp.
DefaultUpdatePeriod = 2 * time.Minute DefaultUpdatePeriod = 2 * time.Minute
// DefaultRPCTimeout defines write deadline for single ntp server request.
DefaultRPCTimeout = 2 * time.Second
) )
type ntpQuery func(string) (*ntp.Response, error) type ntpQuery func(string, ntp.QueryOptions) (*ntp.Response, error)
type queryResponse struct { type queryResponse struct {
Offset time.Duration Offset time.Duration
@ -48,15 +54,16 @@ func (e multiRPCError) Error() string {
return b.String() return b.String()
} }
func computeOffset(timeQuery ntpQuery, server string, attempts int) (time.Duration, error) { func computeOffset(timeQuery ntpQuery, server string, attempts, allowedFailures int) (time.Duration, error) {
if attempts == 0 { if attempts == 0 {
return 0, nil return 0, nil
} }
responses := make(chan queryResponse, attempts) responses := make(chan queryResponse, attempts)
for i := 0; i < attempts; i++ { for i := 0; i < attempts; i++ {
go func() { go func() {
// ntp.Query default timeout is 5s response, err := timeQuery(server, ntp.QueryOptions{
response, err := timeQuery(server) Timeout: DefaultRPCTimeout,
})
if err != nil { if err != nil {
responses <- queryResponse{Error: err} responses <- queryResponse{Error: err}
return return
@ -80,7 +87,9 @@ func computeOffset(timeQuery ntpQuery, server string, attempts int) (time.Durati
break break
} }
} }
if len(rpcErrors) != 0 { if lth := len(rpcErrors); lth > allowedFailures {
return 0, rpcErrors
} else if lth == attempts {
return 0, rpcErrors return 0, rpcErrors
} }
sort.SliceStable(offsets, func(i, j int) bool { sort.SliceStable(offsets, func(i, j int) bool {
@ -98,8 +107,9 @@ func Default() *NTPTimeSource {
return &NTPTimeSource{ return &NTPTimeSource{
server: DefaultServer, server: DefaultServer,
attempts: DefaultAttempts, attempts: DefaultAttempts,
allowedFailures: DefaultMaxAllowedFailures,
updatePeriod: DefaultUpdatePeriod, updatePeriod: DefaultUpdatePeriod,
timeQuery: ntp.Query, timeQuery: ntp.QueryWithOptions,
} }
} }
@ -108,6 +118,7 @@ func Default() *NTPTimeSource {
type NTPTimeSource struct { type NTPTimeSource struct {
server string server string
attempts int attempts int
allowedFailures int
updatePeriod time.Duration updatePeriod time.Duration
timeQuery ntpQuery // for ease of testing timeQuery ntpQuery // for ease of testing
@ -126,7 +137,7 @@ func (s *NTPTimeSource) Now() time.Time {
} }
func (s *NTPTimeSource) updateOffset() { func (s *NTPTimeSource) updateOffset() {
offset, err := computeOffset(s.timeQuery, s.server, s.attempts) offset, err := computeOffset(s.timeQuery, s.server, s.attempts, s.allowedFailures)
if err != nil { if err != nil {
log.Error("failed to compute offset", "error", err) log.Error("failed to compute offset", "error", err)
return return

View File

@ -18,6 +18,7 @@ const (
type testCase struct { type testCase struct {
description string description string
attempts int attempts int
allowedFailures int
responses []queryResponse responses []queryResponse
expected time.Duration expected time.Duration
expectError bool expectError bool
@ -27,7 +28,7 @@ type testCase struct {
actualAttempts int actualAttempts int
} }
func (tc *testCase) query(string) (*ntp.Response, error) { func (tc *testCase) query(string, ntp.QueryOptions) (*ntp.Response, error) {
tc.mu.Lock() tc.mu.Lock()
defer func() { defer func() {
tc.actualAttempts++ tc.actualAttempts++
@ -90,13 +91,48 @@ func newTestCases() []*testCase {
expected: time.Duration(0), expected: time.Duration(0),
expectError: true, expectError: true,
}, },
{
description: "TolerableError",
attempts: 3,
allowedFailures: 1,
responses: []queryResponse{
{Offset: 10 * time.Second},
{Error: errors.New("test")},
{Offset: 30 * time.Second},
},
expected: 20 * time.Second,
},
{
description: "NonTolerableError",
attempts: 3,
allowedFailures: 1,
responses: []queryResponse{
{Offset: 10 * time.Second},
{Error: errors.New("test")},
{Error: errors.New("test")},
},
expected: time.Duration(0),
expectError: true,
},
{
description: "AllFailed",
attempts: 3,
allowedFailures: 3,
responses: []queryResponse{
{Error: errors.New("test")},
{Error: errors.New("test")},
{Error: errors.New("test")},
},
expected: time.Duration(0),
expectError: true,
},
} }
} }
func TestComputeOffset(t *testing.T) { func TestComputeOffset(t *testing.T) {
for _, tc := range newTestCases() { for _, tc := range newTestCases() {
t.Run(tc.description, func(t *testing.T) { t.Run(tc.description, func(t *testing.T) {
offset, err := computeOffset(tc.query, "", tc.attempts) offset, err := computeOffset(tc.query, "", tc.attempts, tc.allowedFailures)
if tc.expectError { if tc.expectError {
assert.Error(t, err) assert.Error(t, err)
} else { } else {
@ -112,6 +148,7 @@ func TestNTPTimeSource(t *testing.T) {
t.Run(tc.description, func(t *testing.T) { t.Run(tc.description, func(t *testing.T) {
source := &NTPTimeSource{ source := &NTPTimeSource{
attempts: tc.attempts, attempts: tc.attempts,
allowedFailures: tc.allowedFailures,
timeQuery: tc.query, timeQuery: tc.query,
} }
assert.WithinDuration(t, time.Now(), source.Now(), clockCompareDelta) assert.WithinDuration(t, time.Now(), source.Now(), clockCompareDelta)