-
Notifications
You must be signed in to change notification settings - Fork 2k
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
api: add related evals to eval details #12305
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
```release-note:improvement | ||
api: Add `related` query parameter to the Evaluation details endpoint | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3177,6 +3177,55 @@ func (s *StateStore) EvalByID(ws memdb.WatchSet, id string) (*structs.Evaluation | |
return nil, nil | ||
} | ||
|
||
// EvalsRelatedToID is used to retrieve the evals that are related (next, | ||
// previous, or blocked) to the provided eval ID. | ||
func (s *StateStore) EvalsRelatedToID(ws memdb.WatchSet, id string) ([]*structs.EvaluationStub, error) { | ||
txn := s.db.ReadTxn() | ||
|
||
raw, err := txn.First("evals", "id", id) | ||
if err != nil { | ||
return nil, fmt.Errorf("eval lookup failed: %v", err) | ||
} | ||
if raw == nil { | ||
return nil, nil | ||
} | ||
eval := raw.(*structs.Evaluation) | ||
|
||
relatedEvals := []*structs.EvaluationStub{} | ||
todo := eval.RelatedIDs() | ||
done := map[string]bool{ | ||
eval.ID: true, // don't place the requested eval in the related list. | ||
} | ||
|
||
for len(todo) > 0 { | ||
// Pop the first value from the todo list. | ||
current := todo[0] | ||
todo = todo[1:] | ||
if current == "" { | ||
continue | ||
} | ||
|
||
// Skip value if we already have it in the results. | ||
if done[current] { | ||
continue | ||
} | ||
|
||
eval, err := s.EvalByID(ws, current) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if eval == nil { | ||
continue | ||
} | ||
|
||
todo = append(todo, eval.RelatedIDs()...) | ||
relatedEvals = append(relatedEvals, eval.Stub()) | ||
done[eval.ID] = true | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Currently the only way out of this loop is to scan all related evals (or experience an error). Is there a way to limit the scanning we do, like on the size of the accumulated There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hum...good question. I think the problem with setting an upper bound is that the response then becomes incomplete, and the idea of this flag is to give all the data needed quickly. But guarding against potential infinite loops, or slow requests sounds like a good idea. I don't know what a good number would be, so perhaps we can have a time limit, like 1s? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe the most correct thing to do would be to plumb the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah good point. Is this the channel I should be listening for connection close? https://pkg.go.dev/github.com/hashicorp/yamux#Session.CloseChan There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we want the http request context https://pkg.go.dev/net/http#Request.Context stemming from the request handler http.Request https://github.com/hashicorp/nomad/blob/main/command/agent/eval_endpoint.go#L10 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah ok. Unfortunately that's currently not possible 😬 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Whelp so much for that! It's probably fine as-is anyway, I mean how many related evals could there be anyway ... 🤔 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fwiw, another endpoint returns all the evaluations for a job in one go, so this is strictly less than that... can't be too bad, right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah yeah, good point. One of the differences here is that we have a (potentially) infinite loop if the |
||
|
||
return relatedEvals, nil | ||
} | ||
|
||
// EvalsByIDPrefix is used to lookup evaluations by prefix in a particular | ||
// namespace | ||
func (s *StateStore) EvalsByIDPrefix(ws memdb.WatchSet, namespace, id string, sort SortOption) (memdb.ResultIterator, 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.
This call will add the eval watch channel to the watchset, which means we can have multiple channels in
ws
. I don't know if there are any performance implications in this approach.A alternative would be to set the watchset using the job ID prefix. This would use a single watch channel, but may fire in situations where an eval for another with the same prefix changes.