Skip to content
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

Add Get Aliases API to the high-level REST client #28799

Merged
merged 27 commits into from
Jun 12, 2018
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
6c01eeb
Add Get Aliases API to the high-level RESR client
olcbean Feb 22, 2018
1571cb3
next iteration : still work needed in GetAliasesResponseTests
olcbean Mar 19, 2018
a33ea0f
addressing some comments
olcbean Apr 10, 2018
a919d73
Merge remote-tracking branch 'origin/master' into hlrc_get_alias
olcbean Apr 13, 2018
baed505
chmod
olcbean Apr 13, 2018
f74d0b5
Merge branch 'master' into hlrc_get_alias
javanna May 11, 2018
7720b71
addressing reviewers comments
olcbean May 18, 2018
7238098
addressing reviewers comments
olcbean May 22, 2018
e2ffdab
insert random data into AliasMetaData
olcbean May 23, 2018
9a2079f
adding serialization bwc tests
olcbean May 25, 2018
df3b2fb
adding a serialization test to verify that a response serialized by 6…
olcbean May 28, 2018
0e3ad61
clean up
olcbean May 29, 2018
519992b
clean up
olcbean May 29, 2018
11811ef
Merge branch 'master' into hlrc_get_alias
javanna May 30, 2018
474238b
Merge branch 'master' into hlrc_get_alias
javanna May 30, 2018
a59a026
Merge branch 'master' into hlrc_get_alias
javanna May 31, 2018
b210f49
Merge branch 'master' into hlrc_get_alias
javanna May 31, 2018
5b3096b
Merge branch 'master' into hlrc_get_alias
javanna Jun 5, 2018
8660d43
fix bw comp issues
javanna Jun 5, 2018
f21b3ca
remove unclear if
javanna Jun 5, 2018
fa9d4e1
introduce client specific GetAliasesResponse
javanna Jun 5, 2018
939ae4d
Merge branch 'master' into hlrc_get_alias
javanna Jun 6, 2018
035d823
address review comments
javanna Jun 6, 2018
be1daf7
Merge branch 'master' into hlrc_get_alias
javanna Jun 6, 2018
5da9ed6
fix javadocs
javanna Jun 7, 2018
5bcc1ce
address review comments
javanna Jun 11, 2018
2e2217a
Merge branch 'master' into hlrc_get_alias
javanna Jun 11, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.elasticsearch.client;

import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.cluster.metadata.AliasMetaData;
import org.elasticsearch.common.xcontent.StatusToXContentObject;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentParser.Token;
import org.elasticsearch.rest.RestStatus;

import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;

public class GetAliasesResponse extends ActionResponse implements StatusToXContentObject {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about extending the GetAliasesResponse ? I know it does not bring anything but could mark a dependency ( if something is changed on the server side, the REST client needs to be modified as well ... )

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that on the very long term the client will not depend on es core anymore, I was wondering if I should even extend ActionResponse, I would prefer not to extend the original GetAliasesResponse. We do need to figure out how to manage changes, hopefully tests fail if the REST response changes though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am afraid that the tests will not catch a change. The parsing of a response is lenient : if there are unknown fields, no errors ;). What about making the tests stricter (supportsUnknownFields = false) ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point, even integration tests would work fine if we add fields. We have to address this (not necessarily in this PR though), I guess we have the same with synced flush which is also implemented with a client specific response. Let's see what Nik thinks about it.


private final RestStatus status;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels weird to have the status here. Everything else doesn't use it unless they get an exception.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that we do this because we don't throw exceptions on 404s because they could contain partial results. I think we should have documentation for this in the asciidoc because it is super uncommon for our APIs to do this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #30536. Man when I think of documenting this I think we better spend time on changing it asap :)

private final String errorMessage;
private final Map<String, Set<AliasMetaData>> aliases;

public GetAliasesResponse(RestStatus status, String errorMessage, Map<String, Set<AliasMetaData>> aliases) {
this.status = status;
this.errorMessage = errorMessage;
this.aliases = aliases;
}

@Override
public RestStatus status() {
return status;
}

public String getErrorMessage() {
return errorMessage;
}

public Map<String, Set<AliasMetaData>> getAliases() {
return aliases;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
GetAliasesResponse that = (GetAliasesResponse) o;
return status == that.status &&
Objects.equals(errorMessage, that.errorMessage) &&
Objects.equals(aliases, that.aliases);
}

@Override
public int hashCode() {
return Objects.hash(status, errorMessage, aliases);
}

@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we bind this to the rest action buildResponse ? Otherwise it would be easy to get out of sync if anything changes

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is here only for testing, it is not really needed as the response only needs to be parsed back. I do see how there is some duplication, but this toXContent is much more straight-forward than what the REST action currently does, so I am not sure what to do. how did you mean to bind the two?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant that if code is shared between toXContent and REST buildResponse, then XContentTests is more realistic. I was thinking of something like toXContent ( status, errorMessage, aliases, builder, params ) which can be called after REST layer determined the status and the error message. Just a thought :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we wanted to we could move this class to the server and use it to build the response that we return to users. It is a good idea but I prefer not to move this thing into the server.

builder.startObject();
{
if (status != RestStatus.OK) {
builder.field("error", errorMessage);
builder.field("status", status.getStatus());
}

for (Map.Entry<String, Set<AliasMetaData>> entry : aliases.entrySet()) {
builder.startObject(entry.getKey());
{
builder.startObject("aliases");
{
for (final AliasMetaData alias : entry.getValue()) {
AliasMetaData.Builder.toXContent(alias, builder, ToXContent.EMPTY_PARAMS);
}
}
builder.endObject();
}
builder.endObject();
}
}
builder.endObject();
return builder;
}

public static GetAliasesResponse fromXContent(XContentParser parser) throws IOException {
if (parser.currentToken() == null) {
parser.nextToken();
}
ensureExpectedToken(Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation);
Map<String, Set<AliasMetaData>> aliases = new HashMap<>();

String currentFieldName;
Token token;
String exceptionMessage = null;
RestStatus status = RestStatus.OK;

while (parser.nextToken() != Token.END_OBJECT) {
if (parser.currentToken() == Token.FIELD_NAME) {
currentFieldName = parser.currentName();

if ("status".equals(currentFieldName)) {
if ((token = parser.nextToken()) != Token.FIELD_NAME) {
ensureExpectedToken(Token.VALUE_NUMBER, token, parser::getTokenLocation);
status = RestStatus.fromCode(parser.intValue());
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see why you can't use ObjectParser here. declareNamedObjects is the closest thing to this but it isn't right.

Could you add error handling for some of this? Like, we should throw an exception if the status comes back as something other than a START_OBJECT I think.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get what you mean, can you elaborate?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this if statement confused me:

if ((token = parser.nextToken()) != Token.FIELD_NAME) {

I find myself asking "how can this be a field name and, if it is, what will we do?" Or, "what happens if someone sends and "error": ["an", "array"]. I just get bugged by hand written parsing code because there are so many cases to think through. And I get that we are dealing with the response from the server which we mostly control, but I think we're better safe than sorry here. I think this one is missing some exception handling cases.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok I see what you mean, in the high-level REST client we are rather lenient, in this case we should either throw or most likely skipChildren if we find a start_array

} else if ("error".equals(currentFieldName)) {
if ((token = parser.nextToken()) != Token.FIELD_NAME) {
if (token == Token.VALUE_STRING) {
exceptionMessage = parser.text();
} else if (token == Token.START_OBJECT) {
parser.nextToken();
exceptionMessage = ElasticsearchException.innerFromXContent(parser, true).getMessage();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't want the whole exception?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would be good to throw, but the problem is that this is in reality a 404, so for which 404s do we throw and for which we don't? We went for never throwing then, but the response doesn't have a place to hold the whole exception. Shall we add that at this point? This response is probably going to look even weirder.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one alternative is what @olcbean tried before, that I didn't like at first: throw exception in the fromXContent when we find the complete exception, that is the only way to differentiate between the different 404 responses I think. Let me know what you prefer.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could save the exception as a member variable. It is weird but the API is weird.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

then you would either have the message or the exception?

}
}
} else {
String indexName = parser.currentName();
if (parser.nextToken() == Token.START_OBJECT) {
Set<AliasMetaData> parseInside = parseAliases(parser);
aliases.put(indexName, parseInside);
}
}
}
}
return new GetAliasesResponse(status, exceptionMessage, aliases);
}

private static Set<AliasMetaData> parseAliases(XContentParser parser) throws IOException {
Set<AliasMetaData> aliases = new HashSet<>();
Token token;
String currentFieldName = null;
while ((token = parser.nextToken()) != Token.END_OBJECT) {
if (token == Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (token == Token.START_OBJECT) {
if ("aliases".equals(currentFieldName)) {
while (parser.nextToken() != Token.END_OBJECT) {
AliasMetaData fromXContent = AliasMetaData.Builder.fromXContent(parser);
aliases.add(fromXContent);
}
} else {
parser.skipChildren();
}
} else if (token == Token.START_ARRAY) {
parser.skipChildren();
}
}
return aliases;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
package org.elasticsearch.client;

import org.apache.http.Header;
import org.elasticsearch.action.Action;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesResponse;
Expand All @@ -47,21 +46,23 @@
import org.elasticsearch.action.admin.indices.open.OpenIndexResponse;
import org.elasticsearch.action.admin.indices.refresh.RefreshRequest;
import org.elasticsearch.action.admin.indices.refresh.RefreshResponse;
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest;
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse;
import org.elasticsearch.action.admin.indices.rollover.RolloverRequest;
import org.elasticsearch.action.admin.indices.rollover.RolloverResponse;
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest;
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse;
import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsRequest;
import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsResponse;
import org.elasticsearch.action.admin.indices.shrink.ResizeRequest;
import org.elasticsearch.action.admin.indices.shrink.ResizeResponse;
import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequest;
import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateResponse;
import org.elasticsearch.rest.RestStatus;

import java.io.IOException;
import java.util.Collections;

import static java.util.Collections.emptySet;
import static java.util.Collections.singleton;

/**
* A wrapper for the {@link RestHighLevelClient} that provides methods for accessing the Indices API.
Expand Down Expand Up @@ -484,6 +485,28 @@ public void rolloverAsync(RolloverRequest rolloverRequest, ActionListener<Rollov
listener, emptySet(), headers);
}

/**
* Gets one or more aliases using the Get Index Aliases API
* <p>
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html"> Indices Aliases API on
* elastic.co</a>
*/
public GetAliasesResponse getAlias(GetAliasesRequest getAliasesRequest, Header... headers) throws IOException {
return restHighLevelClient.performRequestAndParseEntity(getAliasesRequest, RequestConverters::getAlias,
GetAliasesResponse::fromXContent, singleton(RestStatus.NOT_FOUND.getStatus()), headers);
}

/**
* Asynchronously gets one or more aliases using the Get Index Aliases API
* <p>
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html"> Indices Aliases API on
* elastic.co</a>
*/
public void getAliasAsync(GetAliasesRequest getAliasesRequest, ActionListener<GetAliasesResponse> listener, Header... headers) {
restHighLevelClient.performRequestAsyncAndParseEntity(getAliasesRequest, RequestConverters::getAlias,
GetAliasesResponse::fromXContent, listener, singleton(RestStatus.NOT_FOUND.getStatus()), headers);
}

/**
* Updates specific index level settings using the Update Indices Settings API
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,17 @@ static Request putTemplate(PutIndexTemplateRequest putIndexTemplateRequest) thro
return request;
}

static Request getAlias(GetAliasesRequest getAliasesRequest) {
String[] indices = getAliasesRequest.indices() == null ? Strings.EMPTY_ARRAY : getAliasesRequest.indices();
String[] aliases = getAliasesRequest.aliases() == null ? Strings.EMPTY_ARRAY : getAliasesRequest.aliases();
String endpoint = endpoint(indices, "_alias", aliases);
Request request = new Request(HttpGet.METHOD_NAME, endpoint);
Params params = new Params(request);
params.withIndicesOptions(getAliasesRequest.indicesOptions());
params.withLocal(getAliasesRequest.local());
return request;
}

private static HttpEntity createEntity(ToXContent toXContent, XContentType xContentType) throws IOException {
BytesRef source = XContentHelper.toXContent(toXContent, xContentType, false).toBytesRef();
return new ByteArrayEntity(source.bytes, source.offset, source.length, createContentType(xContentType));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -660,10 +660,10 @@ protected final <Req extends ActionRequest, Resp> Resp performRequest(Req reques
try {
return responseConverter.apply(e.getResponse());
} catch (Exception innerException) {
//the exception is ignored as we now try to parse the response as an error.
//this covers cases like get where 404 can either be a valid document not found response,
//or an error for which parsing is completely different. We try to consider the 404 response as a valid one
//first. If parsing of the response breaks, we fall back to parsing it as an error.
// the exception is ignored as we now try to parse the response as an error.
// this covers cases like get where 404 can either be a valid document not found response,
// or an error for which parsing is completely different. We try to consider the 404 response as a valid one
// first. If parsing of the response breaks, we fall back to parsing it as an error.
throw parseResponseException(e);
}
}
Expand Down Expand Up @@ -748,10 +748,10 @@ public void onFailure(Exception exception) {
try {
actionListener.onResponse(responseConverter.apply(response));
} catch (Exception innerException) {
//the exception is ignored as we now try to parse the response as an error.
//this covers cases like get where 404 can either be a valid document not found response,
//or an error for which parsing is completely different. We try to consider the 404 response as a valid one
//first. If parsing of the response breaks, we fall back to parsing it as an error.
// the exception is ignored as we now try to parse the response as an error.
// this covers cases like get where 404 can either be a valid document not found response,
// or an error for which parsing is completely different. We try to consider the 404 response as a valid one
// first. If parsing of the response breaks, we fall back to parsing it as an error.
actionListener.onFailure(parseResponseException(responseException));
}
} else {
Expand Down
Loading