Skip to content

Commit 9ddf7b1

Browse files
authored
NR-126476: update NTP interval check on error (#1699)
* NR-126476: move Offset interval call responsability to caller * NR-126476: return error if ntp interval * NR-126476: add test case for host sample * feat: remove internal cached offset
1 parent 0c798a7 commit 9ddf7b1

File tree

4 files changed

+71
-16
lines changed

4 files changed

+71
-16
lines changed

pkg/metrics/host.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package metrics
55

66
import (
7+
"errors"
78
"fmt"
89
"time"
910

@@ -17,6 +18,7 @@ type HostSample struct {
1718

1819
type HostMonitor struct {
1920
ntpMonitor NtpMonitor
21+
ntpOffset *float64 // cache for last offset value retrieved
2022
}
2123

2224
type NtpMonitor interface {
@@ -38,11 +40,16 @@ func (m *HostMonitor) Sample() (*HostSample, error) {
3840
if m.ntpMonitor != nil {
3941
ntpOffset, err := m.ntpMonitor.Offset()
4042
if err != nil {
41-
syslog.WithError(err).Error("cannot get ntp offset")
43+
// skip the error and use cached offset if interval error
44+
if !errors.Is(err, ErrNotInInterval) {
45+
syslog.WithError(err).Error("cannot get ntp offset")
46+
m.ntpOffset = nil
47+
}
4248
} else {
4349
seconds := ntpOffset.Seconds()
44-
hostSample.NtpOffset = &seconds
50+
m.ntpOffset = &seconds
4551
}
52+
hostSample.NtpOffset = m.ntpOffset
4653
}
4754

4855
return hostSample, nil

pkg/metrics/host_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright New Relic Corporation. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package metrics
5+
6+
import (
7+
"testing"
8+
"time"
9+
10+
"github.com/stretchr/testify/assert"
11+
)
12+
13+
func TestHostSample_CachedNtpOffset(t *testing.T) {
14+
timeout := uint(5000)
15+
interval := uint(15)
16+
ntpMonitor := NewNtp([]string{"one"}, timeout, interval)
17+
ntpMonitor.ntpQuery = ntpQueryMock([]ntpResp{
18+
{
19+
resp: buildValidNtpResponse(50 * time.Millisecond),
20+
err: nil,
21+
},
22+
}...)
23+
24+
hostMonitor := NewHostMonitor(ntpMonitor)
25+
26+
expectedOffset := (50 * time.Millisecond).Seconds()
27+
expectedNtpSample := &expectedOffset
28+
29+
// valid ntp response
30+
ntpMonitor.ntpQuery = ntpQueryMock([]ntpResp{{buildValidNtpResponse(50 * time.Millisecond), nil}}...)
31+
sample, err := hostMonitor.Sample()
32+
assert.Nil(t, err)
33+
assert.Equal(t, expectedNtpSample, sample.NtpOffset)
34+
35+
// query inside interval should return cached value
36+
sample, err = hostMonitor.Sample()
37+
assert.Nil(t, err)
38+
assert.Equal(t, expectedNtpSample, sample.NtpOffset)
39+
}

pkg/metrics/ntp.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,14 @@ const (
2323
var (
2424
ErrEmptyNtpHosts = errors.New("ntp host list is empty")
2525
ErrGettingNtpOffset = errors.New("cannot get ntp offset")
26+
ErrNotInInterval = errors.New("cannot query ntp servers offset inside interval")
2627
)
2728

2829
type Ntp struct {
2930
pool []string
3031
timeout time.Duration // ntp request timeout in seconds
3132
interval time.Duration // ntp request interval in minutes
3233
updatedAt time.Time // last time the ntp offset was fetched
33-
offset time.Duration // cache for last offset value retrieved
3434
now func() time.Time
3535
ntpQuery func(host string, opt ntp.QueryOptions) (*ntp.Response, error)
3636
}
@@ -69,16 +69,22 @@ func guardInterval(interval uint) uint {
6969
return interval
7070
}
7171

72+
// Offset returns the Ntp servers offset.
7273
func (p *Ntp) Offset() (time.Duration, error) {
7374
if len(p.pool) == 0 {
7475
return 0, ErrEmptyNtpHosts
7576
}
7677

77-
// We only query the servers once every p.interval
7878
if p.now().Sub(p.updatedAt) < p.interval {
79-
return p.offset, nil
79+
// return error in case outside query interval
80+
return 0, ErrNotInInterval
8081
}
8182

83+
// update current interval even if error
84+
defer func() {
85+
p.updatedAt = p.now()
86+
}()
87+
8288
var offsets []time.Duration
8389

8490
var ntpQueryErr error
@@ -113,9 +119,5 @@ func (p *Ntp) Offset() (time.Duration, error) {
113119
total += offset
114120
}
115121

116-
// cache the value to be reused
117-
p.offset = total / time.Duration(len(offsets))
118-
p.updatedAt = p.now()
119-
120-
return p.offset, nil
122+
return total / time.Duration(len(offsets)), nil
121123
}

pkg/metrics/ntp_test.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ func TestNewNtp_Timeout(t *testing.T) {
8585
}
8686
}
8787

88-
func TestOffset_Cache(t *testing.T) {
88+
func TestOffset_Interval(t *testing.T) {
8989
testCases := []struct {
9090
name string
9191
interval uint
@@ -113,12 +113,11 @@ func TestOffset_Cache(t *testing.T) {
113113
expectedUpdatedAt: mustParse("2006-01-02 15:04:05", "2022-09-28 16:02:45"), // same as now
114114
},
115115
{
116-
name: "request before interval should use cached value",
116+
name: "request before interval should return interval error",
117117
now: nowMock("2022-09-28 16:02:45"),
118118
updatedAt: mustParse("2006-01-02 15:04:05", "2022-09-28 15:52:45"),
119119
pool: []string{"one", "two"},
120-
offset: 20 * time.Millisecond,
121-
expectedOffset: 20 * time.Millisecond,
120+
expectedError: ErrNotInInterval,
122121
expectedUpdatedAt: mustParse("2006-01-02 15:04:05", "2022-09-28 15:52:45"),
123122
},
124123
{
@@ -131,17 +130,25 @@ func TestOffset_Cache(t *testing.T) {
131130
expectedOffset: 30 * time.Millisecond,
132131
expectedUpdatedAt: mustParse("2006-01-02 15:04:05", "2022-09-28 16:02:45"), // same as now
133132
},
133+
{
134+
name: "request with query error should update updatedAt",
135+
now: nowMock("2022-09-28 16:02:45"),
136+
updatedAt: mustParse("2006-01-02 15:04:05", "2022-09-28 15:42:45"),
137+
pool: []string{"one"},
138+
ntpResponses: []ntpResp{{nil, errors.New("this is an error")}},
139+
expectedError: ErrGettingNtpOffset,
140+
expectedUpdatedAt: mustParse("2006-01-02 15:04:05", "2022-09-28 16:02:45"), // same as now
141+
},
134142
}
135143
for _, testCase := range testCases {
136144
t.Run(testCase.name, func(t *testing.T) {
137145
ntpMonitor := NewNtp(testCase.pool, testCase.timeout, testCase.interval)
138146
ntpMonitor.ntpQuery = ntpQueryMock(testCase.ntpResponses...)
139147
ntpMonitor.now = testCase.now
140148
ntpMonitor.updatedAt = testCase.updatedAt
141-
ntpMonitor.offset = testCase.offset
142149
offset, err := ntpMonitor.Offset()
143150
assert.Equal(t, testCase.expectedOffset, offset)
144-
assert.Equal(t, testCase.expectedError, err)
151+
assert.ErrorIs(t, err, testCase.expectedError)
145152
assert.Equal(t, testCase.expectedUpdatedAt, ntpMonitor.updatedAt)
146153
})
147154
}

0 commit comments

Comments
 (0)