-
Notifications
You must be signed in to change notification settings - Fork 7
Conversation
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.
This looks really nice in general, thanks. LGTM modulo a bunch of minor comments and suggestions, and one likely bug...
api/etcd/watch.go
Outdated
// Watch the given prefix for anything new. | ||
func (s *RulesetService) Watch(ctx context.Context, prefix string, revision string) (*api.RulesetEvents, error) { | ||
// Watch a list of paths for changes and return a list of events. If the list is empty or nil, | ||
// watch all paths. If the revision is empty, watch from the latest revision. |
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.
// watch all paths. If the revision is empty, watch from the latest revision. | |
// watch all paths. If the revision holds a non-negative decimal number, Watch | |
// will block until the revision is greater than that number, otherwise | |
// it will return immediately with the initial events. |
?
@@ -50,6 +39,15 @@ func (s *rulesetAPI) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |||
s.get(w, r, path) | |||
return | |||
case "POST": | |||
if _, ok := r.URL.Query()["watch"]; ok { | |||
if len(path) != 0 { |
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 wonder if it might be nicer to have an explicit path to use to watch (e.g. /watch-rulesets
), rather than a POST to /rulesets
. Not sure.
🌷
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.
Actually, we need a POST anyway because we need to send a body in the request
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.
It was really the re-purposing of the endpoint for both purposes that felt odd to me. I don't mind much - we should really just be using gRPC and then we wouldn't need to care about such trivialities :)
Co-Authored-By: Roger Peppe <[email protected]>
🆙 |
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.
👍
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.
Couple of minor things.
Co-Authored-By: Geoffrey J. Teale <[email protected]>
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.
Looks great in general, with one simplification suggestion, and perhaps it's worth improving that Watch test?
|
||
// shouldIncludeEvent reports whether the given event should be included | ||
// in the Watch data for the given paths. | ||
func (s *RulesetService) shouldIncludeEvent(ev *clientv3.Event, paths []string) bool { |
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.
This could be a little bit simpler, I think:
if len(paths) == 0 {
return true
}
key := string(ev.Kv.Key)
key = key[:strings.Index(key, versionSeparator)]
for _, path := range paths {
if s.rulesPath(path) == key {
return true
}
}
return false
Also, I'm a bit concerned that this code will panic if the key doesn't contain versionSeparator
. How sure are we of that?
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.
Put
is the only function responsible for writing in that path, I'd say that as long as Put
is tested it should be fine, but I'm open to suggestions
var events api.RulesetEvents | ||
var rev int64 | ||
var watchCount int | ||
for len(events.Events) != len(test.expected) && watchCount < 4 { |
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.
ISTM that this won't fail if the watcher produces more events than you want. Perhaps the loop should wait for a while to check that no more arrive?
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.
It can't produce more events because each call to createBoolRuleset
in the goroutine above will produce exactly one event
for len(events.Events) != len(test.expected) && watchCount < 4 { | ||
evs, err := s.Watch(ctx, api.WatchOptions{Paths: test.paths, Revision: rev}) | ||
if err != nil { | ||
if err != nil { |
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.
This condition looks redundant to me.
|
||
r := rule.New(rule.True(), rule.BoolValue(true)) | ||
time.Sleep(time.Second) |
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.
Do you actually need the watch statement to execute before this code? How about adding some rulesets, wait for an event to arrive, then adding some more, so you check both the initial case and the later case? (I'm not entirely sure of the intended semantics when there are rulesets already there though - what's meant to happen?)
// List of paths to watch for changes. | ||
// If the slice is empty, watch all paths. | ||
Paths []string | ||
// Indicates from which revision start watching. |
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.
// Indicates from which revision start watching. | |
// Indicates from which revision to start watching. |
@@ -50,6 +39,15 @@ func (s *rulesetAPI) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |||
s.get(w, r, path) | |||
return | |||
case "POST": | |||
if _, ok := r.URL.Query()["watch"]; ok { | |||
if len(path) != 0 { |
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.
It was really the re-purposing of the endpoint for both purposes that felt odd to me. I don't mind much - we should really just be using gRPC and then we wouldn't need to care about such trivialities :)
{"UnexpectedError", "/rulesets/someprefix?list", http.StatusInternalServerError, new(api.Rulesets), api.ListOptions{}, errors.New("unexpected error")}, | ||
{"InvalidLimit", "/rulesets/someprefix?list&limit=badlimit", http.StatusBadRequest, nil, api.ListOptions{}, nil}, | ||
{"InvalidCursor", "/rulesets/?list&cursor=abc123", http.StatusBadRequest, new(api.Rulesets), api.ListOptions{Cursor: "abc123"}, api.ErrInvalidCursor}, | ||
{"UnexpectedError", "/rulesets/?list", http.StatusInternalServerError, new(api.Rulesets), api.ListOptions{}, errors.New("unexpected error")}, |
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.
Why is this an internal server error rather than a bad request?
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.
The 500 will not be caused by the URL by the error errors.New("unexpected error")
that will be returned by the mock.
Basically, it's to simulate an unexpected error (network, etc.) happening in the store.
This PR refreshes the Watch API according to what has been decided in issue #112
The Watch API is now accessible under the following endpoint:
The body specifies which paths you want to watch.
An empty body means watch everything.
This PR also contains a few improvements in the
server
package