-
Notifications
You must be signed in to change notification settings - Fork 24.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
Allow registering compatible handlers #64423
Allow registering compatible handlers #64423
Conversation
Co-authored-by: Jay Modi <jaymode@users.noreply.github.com>
…m:pgomulka/elasticsearch into compat/introduce_per_endpoint_media_types
…register_compatible_handlers
return null; //method not found | ||
} | ||
final RestHandler handler = versionToHandlers.get(version); | ||
return handler != null || version.equals(Version.CURRENT) ? handler : versionToHandlers.get(Version.CURRENT); |
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 really can't remember the use cases where we needed this. (returning a v8 handler when a handler was not present in v7)
@jaymode made a comment about this when we were still working on a feature branch https://github.com/elastic/elasticsearch/pull/54197/files#r414959451
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 think the reason for this is @Mpdreamz's comment #60516 (comment) where clients plan to send compatible with to all APIs. If this is the case, can we please document this in code/method so we do not lose the information
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 had to think about this waaay to long :) and wrote a unit test to help me understand
/**
* We want clients to be able to request compatibility with a prior version for all APIs. If a there does not exist a handler registered
* for the prior version, we want to return the current version handler. This effectively means that the current endpoint is compatible
* with the prior version, which should be true since if it were not compatible there would be a registered compatible endpoint.
*/
public void testMissingPriorHandlerReturnsCurrentHandler(){
RestHandler currentVersionHandler = new CurrentVersionHandler();
MethodHandlers methodHandlers = new MethodHandlers("path", currentVersionHandler, RestRequest.Method.PUT, RestRequest.Method.POST);
RestHandler handler = methodHandlers.getHandler(RestRequest.Method.PUT, Version.CURRENT.previousMajor());
assertThat(handler, sameInstance(currentVersionHandler));
}
Can you add a similar comment inline here, and add that test?
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 return can be simplified to:
return handler == null ? versionToHandlers.get(Version.CURRENT) : handler;
with a test:
public void testMissingCurrentHandler(){
RestHandler previousVersionHandler = new PreviousVersionHandler();
MethodHandlers methodHandlers = new MethodHandlers("path", previousVersionHandler, RestRequest.Method.PUT, RestRequest.Method.POST);
RestHandler handler = methodHandlers.getHandler(RestRequest.Method.PUT, Version.CURRENT);
assertNull(handler);
}
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.
to be fair, I am not super convinced about returning the CURRENT.
if an endpoint did not exist in v7 server version, client would be getting 404 when trying to access it with v7 client.
When he upgrades server to next version and he still uses v7 client, I guess he should be still getting 404?
@jakelandis I am not sure I understand this:
This effectively means that the current endpoint is compatible
* with the prior version, which should be true since if it were not compatible there would be a registered compatible endpoint.
from high level point of view, there is no knowledge if there is a compatible endpoint or not. If the handler's code did not change, user has no idea that a response came from v7 code, or if the response is compatible shape
from v8 (in ideal 100% compatible handler).
THe same for when an endpoint do not exist. Returning 404 from v8 when for not existing in v7 endpoint is 100% compatible in shape and behaviour
@Mpdreamz any views on this?
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 think we should return new APIs with compatible requested. Below is a table with my thought for how behaviour should look.
The rationale is that in a minor you can add a new API passively, therefor new APIs are compatible (as opposed to non-compatible/breaking). Further, one motivation to add a new API is to replace an old one. With a client sending compatibility all the time, they should be able to fix the compatibility warnings and migrate to the new API. This allows them to do just that.
scenario | v7 | v8 | v7 compat / v8 server result |
---|---|---|---|
new in v8 | 404 | 200 | 200 (v8) |
no change/compat change in v8 (no compat handler) | 200 | 200 | 200 (v8) |
removed in v8 (with compat handler) | 200 | 404 | 200 (v7) |
non passive change (with compat handler) | 200 | 200 | 200 (v7) |
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 rationale is that in a minor you can add a new API passively, therefor new APIs are compatible (as opposed to non-compatible/breaking)
great explanation. thank you. I think this was the initial reason we went for that behaviour on a feature branch. I will add a comment about this at least in a test
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.
Since clients generate methods for known API's the only way to talk to an API that exists in 8.0
but not (yet) in 7.x
is to call transport.Request(url)
I think the new in v8
case needs some zooming in on:
new in v8
accept/content-type | v7 compat / v8 server result |
---|---|
application/json | application/json |
application/vnd.elasticsearch+json;compatible-with=8 | application/vnd.elasticsearch+json;compatible-with=8 |
application/vnd.elasticsearch+json;compatible-with=7 | ? |
The first two seem straight forward the last one not so much It could return a
- 404, since thats what it did in 7
- 200 with
application/vnd.elasticsearch+json;compatible-with=7
. - 200 with
application/vnd.elasticsearch+json;compatible-with=8
.
Returning with compatible-with=8
is not what the user requested so seems like an unexpected return.
Returning with compatible-with=7
since its a new API it can be argued anything is compatible with 7
. But this will become a pain to track when e.g a new API gets introduced in 8.0 as experimental and GA's in 8.4
. You potentially get back two different 7
responses for 8.0
and 8.4
.
That leaves returning a 404.
cc @elastic/es-clients we have not discussed if the the transport used in the client should default to application/vnd.elasticsearch+json;compatible-with=7
or application/json
. I feel that if the transport defaults to application/json
but the API methods calling the transport default to the vendor mime type the client user has the least suprises.
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 think with the current approach where we version compatible API per major
we won't be able to give a 100% experience of previous version server. And that wasn't a goal - after all we want to achieve the compatible API, not the versioned one.
I feel all the comments from this "thread" are valid and I will summarise them on a separate issue.
The current implementation is not returning the right response header yet, so the discussion on new in v8
could continue on the new issue.
Lets use this issue to facilitate the discussion #64852
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.
took a quick a pass and things are looking good. I will take a deeper look tomorrow.
|
||
/** | ||
* An interface used to specify a function that returns a compatible API version | ||
* Intended to be used in a code base instead of a plugin. |
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.
not sure i follow the second sentence (Intended to be ...)
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.
right.. I rephrased this, but maybe it is still to vague. let me know
My intention was to not describe the actual plugin implementation, but just to mention that this abstract the way we actually provide the logic
.
It happens that we use a plugin for this, but could if we want to use any class within a server to do this as well (we did in the previous attempts)
|
||
MethodHandlers(String path, RestHandler handler, RestRequest.Method... methods) { | ||
MethodHandlers(String path, RestHandler handler, Version version, RestRequest.Method... methods) { |
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.
Can you a few unit tests for this class ?
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 also realised that changing the method signature was not necessary.
return null; //method not found | ||
} | ||
final RestHandler handler = versionToHandlers.get(version); | ||
return handler != null || version.equals(Version.CURRENT) ? handler : versionToHandlers.get(Version.CURRENT); |
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 think the reason for this is @Mpdreamz's comment #60516 (comment) where clients plan to send compatible with to all APIs. If this is the case, can we please document this in code/method so we do not lose the information
@@ -1004,6 +1006,15 @@ public XContentBuilder copyCurrentStructure(XContentParser parser) throws IOExce | |||
return this; | |||
} | |||
|
|||
public XContentBuilder withCompatibleMajorVersion(byte compatibleMajorVersion){ |
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.
public XContentBuilder withCompatibleMajorVersion(byte compatibleMajorVersion){ | |
public XContentBuilder withCompatibleMajorVersion(byte compatibleMajorVersion) { |
return this; | ||
} | ||
|
||
public byte getCompatibleMajorVersion() { |
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.
can you add javadocs to this method and the one above?
@@ -48,6 +48,8 @@ | |||
*/ | |||
public final class XContentBuilder implements Closeable, Flushable { | |||
|
|||
private byte compatibleMajorVersion; |
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.
hmm, I wonder if we should make this a SetOnce
or add validation that if it is not some sentinel value that it cannot be changed again? I'm not sure that I like it being mutable with a builder pattern and allowing it to be set multiple times
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.
with a regular builder pattern I would be ok with the field being mutable, as it is normally used within some narrow code scope.
With XContentBuilder we often pass it around so makes sense to protect against some accidental changes.
I feel like we should assert about this in testing only though.
I added assert
return Version.fromId(previousMajorId); | ||
} | ||
|
||
public static Version minimumRestCompatibilityVersion() { |
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.
hmm, should we make this non-static like minimumCompatibilityVersion
and minimumIndexCompatibilityVersion
? Also, please add javadocs :)
this.previousMajorId = major > 0 ? (major - 1) * 1000000 + 99 : major; | ||
} | ||
|
||
public Version previousMajor() { |
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.
javadocs please
@@ -539,9 +540,11 @@ protected Node(final Environment initialEnvironment, | |||
repositoriesServiceReference::get).stream()) | |||
.collect(Collectors.toList()); | |||
|
|||
|
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.
@@ -242,7 +250,11 @@ private void dispatchRequest(RestRequest request, RestChannel channel, RestHandl | |||
inFlightRequestsBreaker(circuitBreakerService).addWithoutBreaking(contentLength); | |||
} | |||
// iff we could reserve bytes for the request we need to send the response also over this channel | |||
responseChannel = new ResourceHandlingHttpChannel(channel, circuitBreakerService, contentLength); | |||
// Using a version from a handler because if no handler was found for requested version, |
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 am not sure i understand the purpose of this comment here.
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.
my intention was to indicate that even if you requested an applicaiton/vnd.elasticsearch+json;compatible-with=7
but the handler did not exist in 7, the actual resthandler will be used from v8.
but that indeed makes no difference, as there would be no compatible serialisation code.
Removed the comment.
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.
LGTM
…ompatible_handlers
@elasticmachine run elasticsearch-ci/2 |
…ompatible_handlers
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 left some minor comments to address but otherwise LGTM
libs/x-content/src/main/java/org/elasticsearch/common/xcontent/XContentBuilder.java
Outdated
Show resolved
Hide resolved
Co-authored-by: Jay Modi <jaymode@users.noreply.github.com>
…ulka/elasticsearch into compat/register_compatible_handlers
Adding an infrastructure to allow for registration of Compatible Handlers.
Compatible handlers are
RestHandler
s used for handling rest request from old version clients ( CURRENT-1 version). They might be registered under an endpoint that was removed or changed in CURRENT version (different path, method or an endpoint completely removed).But they also can be registered under the same endpoint (same path, method as the
RestHandler
in CURRENT)RestHandler's endpoint is at the moment 2dimensional - a method and a path.
This PR adds a 3rd dimension - a version.
Registration:
RestHandler
declares a newcompatibleWithVersion
method, which will be overridden by Compatible Handlers and returning a Version.CURRENT -1. By default the method returnsVersion.CURRENT
compatibleWithVersion
is used when iterating over handlers within RestController#registerHandler. The returned value is used to set a version onMethodHandlers
Lookup:
An interface
CompatibleVersion
is introduced in order to abstract a logic to calculate a compatible version requested by a user.It is not implemented in this PR. A simplified, always returning Version.CURRENT implementation is used.
Within RestController, a version is calculated with the use of
CompatibleVersion
, then the lookup forMethodHandlers
is performed (the logic is the same)Once it is find, an additional lookup for a RestHandler for requested version is made.
The requested version has to be also passed down to
XContentBuilder
in order to allow for per version serialisation logicrelates #51816