-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Event stream MVP #9013
Merged
Merged
Event stream MVP #9013
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
e7e2c79
Event Buffer Implemenation
drewbailey c9bf8ce
abandon current state on server shutdown
drewbailey 2626309
Events/cfg enable publisher (#8916)
drewbailey 8bd15b5
Events/event source node (#8918)
drewbailey b825ba3
Event Stream API/RPC (#8947)
drewbailey 8d16846
Events/deployment events (#9004)
drewbailey 4f97bf8
Events/eval alloc events (#9012)
drewbailey 8a57ee8
api comments
drewbailey e7e6df3
wire up enable_event_publisher
drewbailey 47d1a33
writetxn can return error, add alloc and job generic events. Add events
drewbailey 004f634
use Events to wrap index and events, store in events table
drewbailey 8a3130a
event durability count and cfg
drewbailey 66bda44
go-memdb v1.3.0
drewbailey 1288b18
rehydrate event publisher on snapshot restore
drewbailey 39ef326
Add EvictCallbackFn to handle removing entries from go-memdb when they
drewbailey 8711376
handle txn returning error
drewbailey 7102820
namespace filtering
drewbailey 3c15f41
filter on additional filter keys, remove switch statement duplication
drewbailey 8c5f3a0
event stream changelog
drewbailey File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,98 @@ | ||
package api | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
) | ||
|
||
// Events is a set of events for a corresponding index. Events returned for the | ||
// index depend on which topics are subscribed to when a request is made. | ||
type Events struct { | ||
Index uint64 | ||
Events []Event | ||
Err error | ||
} | ||
|
||
// Topic is an event Topic | ||
type Topic string | ||
|
||
// Event holds information related to an event that occurred in Nomad. | ||
// The Payload is a hydrated object related to the Topic | ||
type Event struct { | ||
Topic Topic | ||
Type string | ||
Key string | ||
FilterKeys []string | ||
Index uint64 | ||
Payload map[string]interface{} | ||
} | ||
|
||
// IsHeartbeat specifies if the event is an empty heartbeat used to | ||
// keep a connection alive. | ||
func (e *Events) IsHeartbeat() bool { | ||
return e.Index == 0 && len(e.Events) == 0 | ||
} | ||
|
||
// EventStream is used to stream events from Nomad | ||
type EventStream struct { | ||
client *Client | ||
} | ||
|
||
// EventStream returns a handle to the Events endpoint | ||
func (c *Client) EventStream() *EventStream { | ||
return &EventStream{client: c} | ||
} | ||
|
||
// Stream establishes a new subscription to Nomad's event stream and streams | ||
// results back to the returned channel. | ||
func (e *EventStream) Stream(ctx context.Context, topics map[Topic][]string, index uint64, q *QueryOptions) (<-chan *Events, error) { | ||
r, err := e.client.newRequest("GET", "/v1/event/stream") | ||
if err != nil { | ||
return nil, err | ||
} | ||
q = q.WithContext(ctx) | ||
r.setQueryOptions(q) | ||
|
||
// Build topic query params | ||
for topic, keys := range topics { | ||
for _, k := range keys { | ||
r.params.Add("topic", fmt.Sprintf("%s:%s", topic, k)) | ||
} | ||
} | ||
|
||
_, resp, err := requireOK(e.client.doRequest(r)) | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
eventsCh := make(chan *Events, 10) | ||
go func() { | ||
defer resp.Body.Close() | ||
defer close(eventsCh) | ||
|
||
dec := json.NewDecoder(resp.Body) | ||
|
||
for ctx.Err() == nil { | ||
// Decode next newline delimited json of events | ||
var events Events | ||
if err := dec.Decode(&events); err != nil { | ||
// set error and fallthrough to | ||
// select eventsCh | ||
events = Events{Err: err} | ||
} | ||
if events.Err == nil && events.IsHeartbeat() { | ||
continue | ||
} | ||
|
||
select { | ||
case <-ctx.Done(): | ||
return | ||
case eventsCh <- &events: | ||
} | ||
} | ||
}() | ||
|
||
return eventsCh, nil | ||
} |
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,113 @@ | ||
package api | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestEvent_Stream(t *testing.T) { | ||
schmichael marked this conversation as resolved.
Show resolved
Hide resolved
|
||
t.Parallel() | ||
|
||
c, s := makeClient(t, nil, nil) | ||
defer s.Stop() | ||
|
||
// register job to generate events | ||
jobs := c.Jobs() | ||
job := testJob() | ||
resp2, _, err := jobs.Register(job, nil) | ||
require.Nil(t, err) | ||
require.NotNil(t, resp2) | ||
|
||
// build event stream request | ||
events := c.EventStream() | ||
q := &QueryOptions{} | ||
topics := map[Topic][]string{ | ||
"Eval": {"*"}, | ||
} | ||
|
||
ctx, cancel := context.WithCancel(context.Background()) | ||
defer cancel() | ||
|
||
streamCh, err := events.Stream(ctx, topics, 0, q) | ||
require.NoError(t, err) | ||
|
||
select { | ||
case event := <-streamCh: | ||
if event.Err != nil { | ||
require.Fail(t, err.Error()) | ||
} | ||
require.Equal(t, len(event.Events), 1) | ||
require.Equal(t, "Eval", string(event.Events[0].Topic)) | ||
case <-time.After(5 * time.Second): | ||
require.Fail(t, "failed waiting for event stream event") | ||
} | ||
} | ||
|
||
func TestEvent_Stream_Err_InvalidQueryParam(t *testing.T) { | ||
t.Parallel() | ||
|
||
c, s := makeClient(t, nil, nil) | ||
defer s.Stop() | ||
|
||
// register job to generate events | ||
jobs := c.Jobs() | ||
job := testJob() | ||
resp2, _, err := jobs.Register(job, nil) | ||
require.Nil(t, err) | ||
require.NotNil(t, resp2) | ||
|
||
// build event stream request | ||
events := c.EventStream() | ||
q := &QueryOptions{} | ||
topics := map[Topic][]string{ | ||
"Eval": {"::*"}, | ||
} | ||
|
||
ctx, cancel := context.WithCancel(context.Background()) | ||
defer cancel() | ||
|
||
_, err = events.Stream(ctx, topics, 0, q) | ||
require.Error(t, err) | ||
drewbailey marked this conversation as resolved.
Show resolved
Hide resolved
|
||
require.Contains(t, err.Error(), "400") | ||
require.Contains(t, err.Error(), "Invalid key value pair") | ||
} | ||
|
||
func TestEvent_Stream_CloseCtx(t *testing.T) { | ||
t.Parallel() | ||
|
||
c, s := makeClient(t, nil, nil) | ||
defer s.Stop() | ||
|
||
// register job to generate events | ||
jobs := c.Jobs() | ||
job := testJob() | ||
resp2, _, err := jobs.Register(job, nil) | ||
require.Nil(t, err) | ||
require.NotNil(t, resp2) | ||
|
||
// build event stream request | ||
events := c.EventStream() | ||
q := &QueryOptions{} | ||
topics := map[Topic][]string{ | ||
"Eval": {"*"}, | ||
} | ||
|
||
ctx, cancel := context.WithCancel(context.Background()) | ||
|
||
streamCh, err := events.Stream(ctx, topics, 0, q) | ||
require.NoError(t, err) | ||
|
||
// cancel the request | ||
cancel() | ||
|
||
select { | ||
case event, ok := <-streamCh: | ||
require.False(t, ok) | ||
require.Nil(t, event) | ||
case <-time.After(5 * time.Second): | ||
require.Fail(t, "failed waiting for event stream event") | ||
} | ||
} |
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
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
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.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect we need to cancel the request as well (by setting the request ctx field) and consume what's left of the response body before closing the body. I don't recall http.Get semantics exactly now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've only seen us defer the closing of response body, I think if anything this would result in an event getting dropped after the user intended to cancel the stream, which seems ok?