LifeTimeWatcher SleepDuration calculation testing (#17919)

* factor out sleep duration calc
* property based sleep duration test

Co-authored-by: peteski22 <peter.wilson@hashicorp.com>
This commit is contained in:
Fulton Byrne 2023-02-14 09:57:25 -05:00 committed by GitHub
parent 9338c22c53
commit 000e643ecf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 73 additions and 17 deletions

View File

@ -337,24 +337,14 @@ func (r *LifetimeWatcher) doRenewWithOptions(tokenMode bool, nonRenewable bool,
var sleepDuration time.Duration var sleepDuration time.Duration
if errorBackoff != nil { if errorBackoff == nil {
sleepDuration = errorBackoff.NextBackOff() sleepDuration = r.calculateSleepDuration(remainingLeaseDuration, priorDuration)
if sleepDuration == backoff.Stop { } else if errorBackoff.NextBackOff() == backoff.Stop {
return err return err
} }
} else {
// We keep evaluating a new grace period so long as the lease is
// extending. Once it stops extending, we've hit the max and need to
// rely on the grace duration.
if remainingLeaseDuration > priorDuration {
r.calculateGrace(remainingLeaseDuration, time.Duration(r.increment)*time.Second)
}
priorDuration = remainingLeaseDuration
// The sleep duration is set to 2/3 of the current lease duration plus // remainingLeaseDuration becomes the priorDuration for the next loop
// 1/3 of the current grace period, which adds jitter. priorDuration = remainingLeaseDuration
sleepDuration = time.Duration(float64(remainingLeaseDuration.Nanoseconds())*2/3 + float64(r.grace.Nanoseconds())/3)
}
// If we are within grace, return now; or, if the amount of time we // If we are within grace, return now; or, if the amount of time we
// would sleep would land us in the grace period. This helps with short // would sleep would land us in the grace period. This helps with short
@ -377,6 +367,21 @@ func (r *LifetimeWatcher) doRenewWithOptions(tokenMode bool, nonRenewable bool,
} }
} }
// calculateSleepDuration calculates the amount of time the LifeTimeWatcher should sleep
// before re-entering its loop.
func (r *LifetimeWatcher) calculateSleepDuration(remainingLeaseDuration, priorDuration time.Duration) time.Duration {
// We keep evaluating a new grace period so long as the lease is
// extending. Once it stops extending, we've hit the max and need to
// rely on the grace duration.
if remainingLeaseDuration > priorDuration {
r.calculateGrace(remainingLeaseDuration, time.Duration(r.increment)*time.Second)
}
// The sleep duration is set to 2/3 of the current lease duration plus
// 1/3 of the current grace period, which adds jitter.
return time.Duration(float64(remainingLeaseDuration.Nanoseconds())*2/3 + float64(r.grace.Nanoseconds())/3)
}
// calculateGrace calculates the grace period based on the minimum of the // calculateGrace calculates the grace period based on the minimum of the
// remaining lease duration and the token increment value; it also adds some // remaining lease duration and the token increment value; it also adds some
// jitter to not have clients be in sync. // jitter to not have clients be in sync.

View File

@ -3,7 +3,11 @@ package api
import ( import (
"errors" "errors"
"fmt" "fmt"
"math"
"math/rand"
"reflect"
"testing" "testing"
"testing/quick"
"time" "time"
"github.com/go-test/deep" "github.com/go-test/deep"
@ -233,3 +237,47 @@ func TestLifetimeWatcher(t *testing.T) {
}) })
} }
} }
// TestCalcSleepPeriod uses property based testing to evaluate the calculateSleepDuration
// function of LifeTimeWatchers, but also incidentally tests "calculateGrace".
// This is on account of "calculateSleepDuration" performing the "calculateGrace"
// function in particular instances.
// Both of these functions support the vital functionality of the LifeTimeWatcher
// and therefore should be tested rigorously.
func TestCalcSleepPeriod(t *testing.T) {
c := quick.Config{
MaxCount: 1000,
Values: func(values []reflect.Value, r *rand.Rand) {
leaseDuration := r.Intn(math.MaxInt64)
remainingLeaseDuration := r.Intn(leaseDuration)
priorDuration := remainingLeaseDuration
increment := r.Intn(leaseDuration + 1)
values[0] = reflect.ValueOf(r)
values[1] = reflect.ValueOf(time.Duration(leaseDuration))
values[2] = reflect.ValueOf(time.Duration(priorDuration))
values[3] = reflect.ValueOf(time.Duration(remainingLeaseDuration))
values[4] = reflect.ValueOf(increment) // integer truncation... could be interesting.
},
}
// tests that "calculateSleepDuration" will always return a value less than
// the remaining lease duration given a random leaseDuration, priorDuration, remainingLeaseDuration, and increment.
// Inputs are generated so that:
// leaseDuration > priorDuration > remainingLeaseDuration
// and remainingLeaseDuration > increment
if err := quick.Check(func(r *rand.Rand, leaseDuration, priorDuration, remainingLeaseDuration time.Duration, increment int) bool {
lw := LifetimeWatcher{
grace: 0,
increment: increment,
random: r,
}
lw.calculateGrace(remainingLeaseDuration, time.Duration(increment))
// ensure that we sleep for less than the remaining lease.
return lw.calculateSleepDuration(remainingLeaseDuration, priorDuration) < remainingLeaseDuration
}, &c); err != nil {
t.Error(err)
}
}

3
changelog/17919.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
api: property based testing for LifetimeWatcher sleep duration calculation
```