mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-06-14 19:01:10 +02:00
lnd/healthcheck: add tests for callbacks and dynamic checks
This commit is contained in:
parent
6a3a2ee61d
commit
39f94a150f
@ -60,6 +60,8 @@ func TestMonitor(t *testing.T) {
|
|||||||
Attempts: 2,
|
Attempts: 2,
|
||||||
Backoff: 0,
|
Backoff: 0,
|
||||||
Timeout: time.Hour,
|
Timeout: time.Hour,
|
||||||
|
OnSuccess: noOpCallback,
|
||||||
|
OnFailure: noOpCallback,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Shutdown: func(string, ...interface{}) {
|
Shutdown: func(string, ...interface{}) {
|
||||||
@ -206,6 +208,8 @@ func TestRetryCheck(t *testing.T) {
|
|||||||
Attempts: test.attempts,
|
Attempts: test.attempts,
|
||||||
Timeout: test.timeout,
|
Timeout: test.timeout,
|
||||||
Backoff: 0,
|
Backoff: 0,
|
||||||
|
OnSuccess: noOpCallback,
|
||||||
|
OnFailure: noOpCallback,
|
||||||
}
|
}
|
||||||
quit := make(chan struct{})
|
quit := make(chan struct{})
|
||||||
|
|
||||||
@ -238,3 +242,187 @@ func TestRetryCheck(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestCallbacks verifies that we fire the OnSuccess/OnFailure callbacks
|
||||||
|
// as expected.
|
||||||
|
//
|
||||||
|
// - When the health check succeeds, the OnSuccess callback should fire.
|
||||||
|
// - When the failure threshold is reached, the OnFailure callback should fire.
|
||||||
|
func TestCallbacks(t *testing.T) {
|
||||||
|
intervalTicker := ticker.NewForce(time.Hour)
|
||||||
|
|
||||||
|
mock := newMockCheck(t)
|
||||||
|
failureThreshold := 3
|
||||||
|
|
||||||
|
successChan := make(chan struct{})
|
||||||
|
failChan := make(chan struct{})
|
||||||
|
shutdown := make(chan struct{})
|
||||||
|
|
||||||
|
// Create our config for monitoring. We will use a 0 back off so that
|
||||||
|
// out test does not need to wait.
|
||||||
|
observation := &Observation{
|
||||||
|
Check: mock.call,
|
||||||
|
Interval: intervalTicker,
|
||||||
|
Attempts: failureThreshold,
|
||||||
|
Backoff: 0,
|
||||||
|
Timeout: time.Hour,
|
||||||
|
OnSuccess: func() {
|
||||||
|
select {
|
||||||
|
case successChan <- struct{}{}:
|
||||||
|
case <-time.After(timeout):
|
||||||
|
t.Fatal("unable to fire onSuccess callback")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
OnFailure: func() {
|
||||||
|
close(failChan)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := &Config{
|
||||||
|
Checks: []*Observation{observation},
|
||||||
|
Shutdown: func(string, ...interface{}) {
|
||||||
|
shutdown <- struct{}{}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
monitor := NewMonitor(cfg)
|
||||||
|
require.NoError(t, monitor.Start(), "could not start monitor")
|
||||||
|
|
||||||
|
// Tick is a helper we will use to tick our interval.
|
||||||
|
tick := func() {
|
||||||
|
select {
|
||||||
|
case intervalTicker.Force <- testTime:
|
||||||
|
case <-time.After(timeout):
|
||||||
|
t.Fatal("could not tick timer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We expect that the onSuccess callback is fired after each successful
|
||||||
|
// check.
|
||||||
|
for i := 0; i < failureThreshold; i++ {
|
||||||
|
tick()
|
||||||
|
mock.sendError(nil)
|
||||||
|
|
||||||
|
// We expect that the onSuccess callback will have fired.
|
||||||
|
select {
|
||||||
|
case <-successChan:
|
||||||
|
case <-time.After(timeout):
|
||||||
|
t.Fatal("expected success callback")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kick off another health check iteration. The monitor's internal
|
||||||
|
// retry mechanism will re-attempt the check until it has reached
|
||||||
|
// the configured maximum # of attempts.
|
||||||
|
//
|
||||||
|
// This mocks our check function failing the maximum # of times
|
||||||
|
// consecutively that it is allowed.
|
||||||
|
tick()
|
||||||
|
for i := 1; i <= failureThreshold; i++ {
|
||||||
|
mock.sendError(errNonNil)
|
||||||
|
|
||||||
|
// Verify that the onFailure callback does not fire unless
|
||||||
|
// the failure threshold (maximum # of attempts) is reached.
|
||||||
|
if i < failureThreshold {
|
||||||
|
select {
|
||||||
|
case <-failChan:
|
||||||
|
t.Fatal("unexpected onFailure callback")
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// After reaching the failure threshold for this health check,
|
||||||
|
// we expect that the onFailure callback will have fired.
|
||||||
|
select {
|
||||||
|
case <-failChan:
|
||||||
|
case <-time.After(timeout):
|
||||||
|
t.Fatal("expected onFailure callback")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since we have failed within our allowed number of retries, we now
|
||||||
|
// expect a call to our shutdown function.
|
||||||
|
select {
|
||||||
|
case <-shutdown:
|
||||||
|
case <-time.After(timeout):
|
||||||
|
t.Fatal("expected shutdown")
|
||||||
|
}
|
||||||
|
require.NoError(t, monitor.Stop(), "could not stop monitor")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDynamicChecks verifies that we actually kick off health check routines
|
||||||
|
// for observations that are added after starting the monitor.
|
||||||
|
func TestDynamicChecks(t *testing.T) {
|
||||||
|
intervalTicker := ticker.NewForce(time.Hour)
|
||||||
|
|
||||||
|
mock := newMockCheck(t)
|
||||||
|
|
||||||
|
successChan := make(chan struct{})
|
||||||
|
shutdown := make(chan struct{})
|
||||||
|
|
||||||
|
// Don't configure any health checks for this monitor.
|
||||||
|
// We'd like to verify that we can add checks after startup.
|
||||||
|
cfg := &Config{
|
||||||
|
Checks: []*Observation{},
|
||||||
|
Shutdown: func(string, ...interface{}) {
|
||||||
|
shutdown <- struct{}{}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
monitor := NewMonitor(cfg)
|
||||||
|
require.NoError(t, monitor.Start(), "could not start monitor")
|
||||||
|
|
||||||
|
// Tick is a helper we will use to tick our interval.
|
||||||
|
tick := func() {
|
||||||
|
select {
|
||||||
|
case intervalTicker.Force <- testTime:
|
||||||
|
case <-time.After(timeout):
|
||||||
|
t.Fatal("could not tick timer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
observation := &Observation{
|
||||||
|
Check: mock.call,
|
||||||
|
Interval: intervalTicker,
|
||||||
|
Attempts: 2,
|
||||||
|
Backoff: 0,
|
||||||
|
Timeout: time.Hour,
|
||||||
|
OnSuccess: func() {
|
||||||
|
select {
|
||||||
|
case successChan <- struct{}{}:
|
||||||
|
case <-time.After(timeout):
|
||||||
|
t.Fatal("unable to fire onSuccess callback")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
OnFailure: noOpCallback,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the check after having started the monitor.
|
||||||
|
err := monitor.AddCheck(observation)
|
||||||
|
require.NoError(t, err, "could not add new observation")
|
||||||
|
|
||||||
|
// This should initiate the check we dynamically added above.
|
||||||
|
tick()
|
||||||
|
|
||||||
|
// Verify that we can fire the OnSuccess callback.
|
||||||
|
mock.sendError(errNonNil)
|
||||||
|
mock.sendError(nil)
|
||||||
|
select {
|
||||||
|
case <-successChan:
|
||||||
|
case <-time.After(timeout):
|
||||||
|
t.Fatal("expected success callback")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that we correctly shutdown if the added health check fails.
|
||||||
|
tick()
|
||||||
|
mock.sendError(errNonNil)
|
||||||
|
mock.sendError(errNonNil)
|
||||||
|
|
||||||
|
// Since we have failed within our allowed number of retries, we now
|
||||||
|
// expect a call to our shutdown function.
|
||||||
|
select {
|
||||||
|
case <-shutdown:
|
||||||
|
case <-time.After(timeout):
|
||||||
|
t.Fatal("expected shutdown")
|
||||||
|
}
|
||||||
|
require.NoError(t, monitor.Stop(), "could not stop monitor")
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user