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:
parent
c673148bf4
commit
6c2a8ef1f9
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue