-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
Adds explicit SavedObjectsRespository error type for 404 that do not originate from Elasticsearch responses #107104
Adds explicit SavedObjectsRespository error type for 404 that do not originate from Elasticsearch responses #107104
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.
Initial comments to reviewers.
const notFoundError = this.createGenericNotFoundError(type, id); | ||
return this.decorateEsUnavailableError( | ||
new Error(`${notFoundError.message}`), | ||
`x-elastic-product not present or not recognized` |
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'm not sure this is the best text string to use as a descriptor for the "missing" header. I took inspiration from the client's team but made it a lot more specific. We need to get Product's input on wording if we choose to follow this implementation.
public static isNotFoundEsUnavailableError(error: Error | DecoratedError) { | ||
return ( | ||
isSavedObjectsClientError(error) && | ||
error[code] === CODE_ES_UNAVAILABLE && |
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.
Specifically set a status code of 503 to indicate an ES availability error. This overrides the 404 we would otherwise have thrown.
df0c75c
to
e1e93eb
Compare
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 is looking great. I really only have feedback on which error we'd like to raise in this case and subsequently, the condition we should use to throw this error.
An integration test for this would be really nice to have but it's not going to be simple. We'd need to setup a proxy server in front of Elasticsearch that allows us to 'hi-jack' the response to test this condition. I imagine this would be easier to accomplish in a jest integration test than with the FTR. As a starting place, I'd experiment with using a custom Hapi server with the h2o2
plugin (same one we use for the bath path proxy). We can also do this as a follow up if needed.
@@ -202,4 +202,23 @@ export class SavedObjectsErrorHelpers { | |||
public static isGeneralError(error: Error | DecoratedError) { | |||
return isSavedObjectsClientError(error) && error[code] === CODE_GENERAL_ERROR; | |||
} | |||
|
|||
public static createGenericNotFoundEsUnavailableError( |
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 we want to differentiate this error from isEsUnavailableError
? I'm inclined to say that we want applications to handle this condition in the same way they would handle any other issue reaching Elasticsearch. I'm not sure if there's anything different a consumer would or can do in this case vs. any other isEsUnavailableError
. Another benefit of using the existing error is that any existing code that is handling it will also handle this new case.
Curious what @mshustov thinks as well.
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.
Agree, it's ES server is unavailable
rather than a Kibana entity is not found
.
A user reacts to them in different ways: in the case of a Kibana entity is not found
, the user needs to adjust the request params and try again.
While in the case of ES server is unavailable
, the error is not actionable: the user needs to contact an admin to address the availability problem.
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.
Initially, I did try throwing isEsUnavailableError
but that returns a boolean, not an actual Error
or DecoratedError
. If you mean that we should rather throw a decorated EsUnavailableError
that will return true
on checking that the error is isEsUnavailable
, then we can do that.
If I'm mis-reading the types here, please let me know!.
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.
f you mean that we should rather throw a decorated EsUnavailableError that will return true on checking that the error is isEsUnavailable, then we can do that.
Yeah, I was thinking about something like this. @joshdover Did you mean the same?
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 initially replied with this but then deleted it again. I'm adding it back for more context on the error that I'm using:
createGenericNotFoundEsUnavailableError
will return a 503 error that adds the message from a NotFoundError
to the EsUnavailableError
. The decorated error that's thrown will assert true
to a check for isEsUnavailableError
, since the latter returns a boolean
for the statusCode
being 503.
const indexNotFound = statusCode === 404; | ||
|
||
// check if we have the elasticsearch header when doc.found is not true (false, undefined or null) and if we do, ensure it is Elasticsearch | ||
if (!isFoundGetResponse(body) && !isEsHeaderValid(headers)) { |
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.
If we decide to use the same isEsUnavailable
error (see comment above), then I think we should simplify this condition to only if (!isEsHeaderValid(headers))
to catch this issue more broadly. Same for the other repository functions.
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.
isEsUnavailable
does not return an error. It returns a boolean
.
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.
In addition, what we're hoping to accomplish with this work is to differentiate between a saved object not actually being found (404) vs the response returning a 404 because ES is unavailable.
For the initial work (this PR), we're only scoping the work to checking if the response is from ES when we get a 404, rather than a larger check on all or any responses.
We will need to implement something for ensuring all responses received are valid ES responses in the long run but IMO, is too risky to try and get in for a patch release.
* @returns boolean | ||
*/ | ||
const isEsHeaderValid = (headers: Record<string, unknown> | null) => { | ||
return headers && headers['x-elastic-product'] |
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'm fairly certain this is the case, but have we validated that the casing of the header names are always normalized to lowercase?
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.
Good question! I did do a cursory check but we could handle the case by casting it to lowercase before checking the condition to be safe.
return ( | ||
isSavedObjectsClientError(error) && | ||
error[code] === CODE_ES_UNAVAILABLE && | ||
error.message.startsWith('x-elastic-product not present or not recognized:') |
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 we need :
at the end?
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.
Nope. Good catch!
// check if we have the elasticsearch header when doc.found is not true (false, undefined or null) and if we do, ensure it is Elasticsearch | ||
const esHeaderFound = isEsHeaderValid(headers); | ||
|
||
if (!isFoundGetResponse(body) && !esHeaderFound) { |
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.
Question: Might it lead to false positives if an intermediate proxy doesn't forward x-elastic-product
header? In v7.14
, we do allow Kibana starts even if x-elastic-product
header is missing. Kibana won't start unless the header presents starting from v7.15
#105557
Does it mean that we cannot backport this change to v7.14.1
?
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.
Might it lead to false positives if an intermediate proxy doesn't forward
x-elastic-product
header?
If the header is missing from the response then yes, we would get 503
errors rather than 404
(which is what we're getting now). We would, however, still be getting an error because ATM, the header check is only done if we are already getting a 404
response.
If we did backport the work to 7.14.1, then there is a chance we'd get false positives but I don't know how likely that would be. We rely on headers for other Kibana functionality (e.g. accessing internal indices). In what cases wouldn't a proxy forward response headers on?
++ to adding a jest integration test. I also agree adding one as a follow up would be better because, as you say, it's not going to be simple. |
@mshustov @joshdover I've either addressed or answered your initial comments. Could you please take another look at the PR? I've kept the scope small for this one in the hope that we can get it in for the 7.14.1 patch. I'm not sure what's up with all those doc changes. I'll investigate and potentially open a new PR if needed. Converting back to draft to figure this out. Sorry folks! |
@elasticmachine merge upstream |
💚 Build SucceededMetrics [docs]Unknown metric groupsAPI count
API count missing comments
History
To update your PR or re-run it, just comment with: |
@joshdover @mshustov @YulNaumenko I've opened a new PR without all those weird doc changes. Sorry about that! |
Summary
Resolves #102353
There may be cases where a 404 can be returned during calls to the Saved Objects Repository for reasons other than the saved object not being found.
The Saved Objects service should distinguish between a Saved Object is not found and other unknown reasons that do not originate from responses to an Elasticsearch call.
This PR adds an explicit 503 NotFoundEsUnavailable Error that will wrap 404 errors from
get
,update
anddelete
requests when the response does not contain thex-elastic-product: Elasticsearch
header.Checklist
Delete any items that are not applicable to this PR.
Risk Matrix
x-elastic-product
header .For maintainers