Skip to content

Commit

Permalink
runtime: send version report less often when long-running
Browse files Browse the repository at this point in the history
When an OPA instance runs for a long time, it seems odd to send version reports
every hour. I think it's unlikely that someone watches the logs at that point.

So this change makes OPA report every 6 hours (plus a random time between 0 and
60 minutes), after it has reported hourly (+spray) for 6 times.

Signed-off-by: Stephan Renatus <[email protected]>
  • Loading branch information
srenatus committed Dec 12, 2024
1 parent a179a24 commit 415ff5d
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 10 deletions.
29 changes: 21 additions & 8 deletions v1/runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,10 @@ var (
)

const (
// default interval between OPA version report uploads
defaultUploadIntervalSec = int64(3600)
// default interval between OPA version report uploads after startup (1h)
defaultInitialUploadInterval = time.Hour
// upload interval when OPA has been running for 6+ hrs (6h)
defaultLaterUploadInterval = 6 * time.Hour
)

// RegisterPlugin registers a plugin factory with the runtime
Expand Down Expand Up @@ -634,9 +636,8 @@ func (rt *Runtime) Serve(ctx context.Context) error {
}

if rt.Params.EnableVersionCheck {
d := time.Duration(int64(time.Second) * defaultUploadIntervalSec)
rt.done = make(chan struct{})
go rt.checkOPAUpdateLoop(ctx, d, rt.done)
go rt.checkOPAUpdateLoop(ctx, rt.done)
}

defer func() {
Expand Down Expand Up @@ -765,8 +766,13 @@ func (rt *Runtime) checkOPAUpdate(ctx context.Context) *report.DataResponse {
return resp
}

func (rt *Runtime) checkOPAUpdateLoop(ctx context.Context, uploadDuration time.Duration, done chan struct{}) {
ticker := time.NewTicker(uploadDuration)
func (rt *Runtime) checkOPAUpdateLoop(ctx context.Context, done chan struct{}) {
rt.checkOPAUpdateLoopDurations(ctx, done, defaultInitialUploadInterval, defaultLaterUploadInterval)
}

func (rt *Runtime) checkOPAUpdateLoopDurations(ctx context.Context, done chan struct{}, initialDur, laterDur time.Duration) {
ticker := time.NewTicker(initialDur)
i := 0
mr.New(mr.NewSource(time.Now().UnixNano())) // Seed the PRNG.

for {
Expand All @@ -790,8 +796,15 @@ func (rt *Runtime) checkOPAUpdateLoop(ctx context.Context, uploadDuration time.D
select {
case <-ticker.C:
ticker.Stop()
newInterval := mr.Int63n(defaultUploadIntervalSec) + defaultUploadIntervalSec
ticker = time.NewTicker(time.Duration(int64(time.Second) * newInterval))
i++ // count the attempts

newInterval := time.Duration(mr.Int63n(int64(time.Hour / time.Second))) // spray, between 0 and 1 hr
if i < 6 {
newInterval += initialDur
} else {
newInterval += laterDur
}
ticker = time.NewTicker(newInterval)
case <-done:
ticker.Stop()
return
Expand Down
47 changes: 45 additions & 2 deletions v1/runtime/runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,48 @@ func TestCheckOPAUpdateLoopNoUpdate(t *testing.T) {
testCheckOPAUpdateLoop(t, baseURL, "OPA is up to date.")
}

func TestCheckOPAUpdateLoopLaterRequests(t *testing.T) {
resp := &report.DataResponse{Latest: report.ReleaseDetails{
OPAUpToDate: true,
}}

// test server
baseURL, teardown := getTestServer(resp, http.StatusOK)
defer teardown()

t.Setenv("OPA_TELEMETRY_SERVICE_URL", baseURL)

ctx := context.Background()

logger := logging.New()
stdout := bytes.NewBuffer(nil)
logger.SetOutput(stdout)
logger.SetLevel(logging.Debug)

rt := getTestRuntime(ctx, t, logger)

done := make(chan struct{})
go func() {
initial := time.Millisecond
later := 100 * time.Millisecond
rt.checkOPAUpdateLoopDurations(ctx, done, initial, later)
}()
time.Sleep(150 * time.Millisecond)
done <- struct{}{}

// NOTE(sr): We'll assert that within 200ms, we have gotten less than
// 10 requests. This is a little less strict than we could be, to not
// make this test too sensitive to timing and noise test environments.
// However, it's strict enbough: If the "later" duration wasn't
// respected, we'd see a lot more requests.
needle := "OPA is up to date."
act := strings.Count(stdout.String(), needle)
exp := 7
if act > exp+1 || act < exp {
t.Fatalf("Expected output to contain: %q >= 7 times, less than 8, got %d", needle, act)
}
}

func TestCheckOPAUpdateLoopWithNewUpdate(t *testing.T) {
exp := &report.DataResponse{Latest: report.ReleaseDetails{
Download: "https://openpolicyagent.org/downloads/v100.0.0/opa_darwin_amd64",
Expand Down Expand Up @@ -1475,8 +1517,9 @@ func testCheckOPAUpdateLoop(t *testing.T, url, expected string) {

done := make(chan struct{})
go func() {
d := time.Duration(int64(time.Millisecond) * 1)
rt.checkOPAUpdateLoop(ctx, d, done)
initial := time.Millisecond
later := initial
rt.checkOPAUpdateLoopDurations(ctx, done, initial, later)
}()
time.Sleep(2 * time.Millisecond)
done <- struct{}{}
Expand Down

0 comments on commit 415ff5d

Please sign in to comment.