Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add health event manager and rules readiness probe (#674)
- Loading branch information
1 parent
0823fe7
commit 01d8588
Showing
14 changed files
with
375 additions
and
266 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package health | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/ory/x/healthx" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
type EventManager interface { | ||
Dispatch(event ReadinessProbeEvent) | ||
Watch(ctx context.Context) | ||
HealthxReadyCheckers() healthx.ReadyCheckers | ||
} | ||
|
||
var ErrEventTypeAlreadyRegistered = errors.New("event type already registered") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package health | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/ory/x/healthx" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
type DefaultHealthEventManager struct { | ||
evtChan chan ReadinessProbeEvent | ||
listeners []ReadinessProbe | ||
listenerEventTypeCache map[string]ReadinessProbe | ||
} | ||
|
||
func NewDefaultHealthEventManager(listeners ...ReadinessProbe) (*DefaultHealthEventManager, error) { | ||
var listenerEventTypeCache = make(map[string]ReadinessProbe) | ||
for _, listener := range listeners { | ||
for _, events := range listener.EventTypes() { | ||
if _, ok := listenerEventTypeCache[events.ReadinessProbeListenerID()]; ok { | ||
return nil, errors.WithStack(ErrEventTypeAlreadyRegistered) | ||
} | ||
listenerEventTypeCache[events.ReadinessProbeListenerID()] = listener | ||
} | ||
} | ||
return &DefaultHealthEventManager{ | ||
evtChan: make(chan ReadinessProbeEvent), | ||
listeners: listeners, | ||
listenerEventTypeCache: listenerEventTypeCache, | ||
}, nil | ||
} | ||
|
||
func (h *DefaultHealthEventManager) Dispatch(event ReadinessProbeEvent) { | ||
if event == nil { | ||
return | ||
} | ||
go func() { | ||
h.evtChan <- event | ||
}() | ||
} | ||
|
||
func (h *DefaultHealthEventManager) Watch(ctx context.Context) { | ||
go func() { | ||
for { | ||
select { | ||
case <-ctx.Done(): | ||
return | ||
case evt, ok := <-h.evtChan: | ||
if !ok { | ||
return | ||
} | ||
if listener, ok := h.listenerEventTypeCache[evt.ReadinessProbeListenerID()]; ok { | ||
listener.EventsReceiver(evt) | ||
} | ||
} | ||
} | ||
}() | ||
} | ||
|
||
func (h *DefaultHealthEventManager) HealthxReadyCheckers() healthx.ReadyCheckers { | ||
var checkers = make(healthx.ReadyCheckers) | ||
for _, listener := range h.listeners { | ||
checkers[listener.ID()] = listener.Validate | ||
} | ||
return checkers | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package health | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
const mockReadinessProbeName = "mock-readiness-probe" | ||
|
||
type ( | ||
mockReadinessProbe struct { | ||
hasReceivedEvent bool | ||
testData string | ||
} | ||
mockReadinessEvent struct { | ||
testData string | ||
} | ||
) | ||
|
||
func (m *mockReadinessProbe) ID() string { | ||
return mockReadinessProbeName | ||
} | ||
|
||
func (m *mockReadinessProbe) Validate() error { | ||
return nil | ||
} | ||
|
||
func (m *mockReadinessProbe) EventTypes() []ReadinessProbeEvent { | ||
return []ReadinessProbeEvent{&mockReadinessEvent{}} | ||
} | ||
|
||
func (m *mockReadinessProbe) EventsReceiver(evt ReadinessProbeEvent) { | ||
switch castedEvent := evt.(type) { | ||
case *mockReadinessEvent: | ||
m.hasReceivedEvent = true | ||
m.testData = castedEvent.testData | ||
} | ||
} | ||
|
||
func (m *mockReadinessEvent) ReadinessProbeListenerID() string { | ||
return mockReadinessProbeName | ||
} | ||
|
||
func TestNewDefaultHealthEventManager(t *testing.T) { | ||
ctx, cancel := context.WithCancel(context.Background()) | ||
defer cancel() | ||
t.Run("health event manager", func(t *testing.T) { | ||
readinessProbe := &mockReadinessProbe{} | ||
|
||
// Create a new default health event manager with twice same probe | ||
_, err := NewDefaultHealthEventManager(readinessProbe, readinessProbe) | ||
require.Error(t, err) | ||
|
||
// Create a new default health event manager | ||
hem, err := NewDefaultHealthEventManager(readinessProbe) | ||
require.NoError(t, err) | ||
|
||
// Test healthx ready checkers generation | ||
checkers := hem.HealthxReadyCheckers() | ||
require.Len(t, checkers, 1) | ||
_, ok := checkers[readinessProbe.ID()] | ||
require.True(t, ok, "health checker was not found") | ||
|
||
// Readiness probe must be empty before event dispatch | ||
require.False(t, readinessProbe.hasReceivedEvent) | ||
require.Equal(t, readinessProbe.testData, "") | ||
|
||
// Nil events should be ignored | ||
hem.Dispatch(nil) | ||
require.False(t, readinessProbe.hasReceivedEvent) | ||
|
||
// Dispatch event without watching (should not block) | ||
const testData = "a sample string that will be passed along the event" | ||
hem.Dispatch(&mockReadinessEvent{ | ||
testData: testData, | ||
}) | ||
|
||
// Watching for incoming events | ||
hem.Watch(ctx) | ||
|
||
// Waiting for watcher to be ready, then verify the event has been received | ||
time.Sleep(100 * time.Millisecond) | ||
require.True(t, readinessProbe.hasReceivedEvent) | ||
require.Equal(t, readinessProbe.testData, testData) | ||
|
||
// Reset probe | ||
readinessProbe.hasReceivedEvent = false | ||
readinessProbe.testData = "" | ||
|
||
// Dispatch a new event | ||
hem.Dispatch(&mockReadinessEvent{ | ||
testData: testData, | ||
}) | ||
|
||
// Wait for event propagation, then verify the event has been received | ||
time.Sleep(100 * time.Millisecond) | ||
require.True(t, readinessProbe.hasReceivedEvent) | ||
require.Equal(t, readinessProbe.testData, testData) | ||
cancel() | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package health | ||
|
||
type ( | ||
ReadinessProbe interface { | ||
ID() string | ||
Validate() error | ||
|
||
EventTypes() []ReadinessProbeEvent | ||
EventsReceiver(event ReadinessProbeEvent) | ||
} | ||
|
||
ReadinessProbeEvent interface { | ||
ReadinessProbeListenerID() string | ||
} | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.