Skip to content

Commit

Permalink
Add Get Aliases API to the high-level REST client (#28799)
Browse files Browse the repository at this point in the history
Given the weirdness of the response returned by the get alias API, we went for a client specific response, which allows us to hold the error message, exception and status returned as part of the response together with aliases. See #30536 .

Relates to #27205
  • Loading branch information
olcbean authored and javanna committed Jun 12, 2018
1 parent a178290 commit 7d7ead9
Show file tree
Hide file tree
Showing 17 changed files with 1,001 additions and 48 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
/*
* 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.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

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

/**
* Response obtained from the get aliases API.
* The format is pretty horrible as it holds aliases, but at the same time errors can come back through the status and error fields.
* Such errors are mostly 404 - NOT FOUND for aliases that were specified but not found. In such case the client won't throw exception
* so it allows to retrieve the returned aliases, while at the same time checking if errors were returned.
* There's also the case where an exception is returned, like for instance an {@link org.elasticsearch.index.IndexNotFoundException}.
* We would usually throw such exception, but we configure the client to not throw for 404 to support the case above, hence we also not
* throw in case an index is not found, although it is a hard error that doesn't come back with aliases.
*/
public class GetAliasesResponse extends ActionResponse implements StatusToXContentObject {

private final RestStatus status;
private final String error;
private final ElasticsearchException exception;

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

GetAliasesResponse(RestStatus status, String error, Map<String, Set<AliasMetaData>> aliases) {
this.status = status;
this.error = error;
this.aliases = aliases;
this.exception = null;
}

private GetAliasesResponse(RestStatus status, ElasticsearchException exception) {
this.status = status;
this.error = null;
this.aliases = Collections.emptyMap();
this.exception = exception;
}

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

/**
* Return the possibly returned error, null otherwise
*/
public String getError() {
return error;
}

/**
* Return the exception that may have been returned
*/
public ElasticsearchException getException() {
return exception;
}

/**
* Return the requested aliases
*/
public Map<String, Set<AliasMetaData>> getAliases() {
return aliases;
}

@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
{
if (status != RestStatus.OK) {
builder.field("error", error);
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;
}

/**
* Parse the get aliases response
*/
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 error = null;
ElasticsearchException exception = 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());
}
} else if ("error".equals(currentFieldName)) {
token = parser.nextToken();
if (token == Token.VALUE_STRING) {
error = parser.text();
} else if (token == Token.START_OBJECT) {
parser.nextToken();
exception = ElasticsearchException.innerFromXContent(parser, true);
} else if (token == Token.START_ARRAY) {
parser.skipChildren();
}
} else {
String indexName = parser.currentName();
if (parser.nextToken() == Token.START_OBJECT) {
Set<AliasMetaData> parseInside = parseAliases(parser);
aliases.put(indexName, parseInside);
}
}
}
}
if (exception != null) {
assert error == null;
assert aliases.isEmpty();
return new GetAliasesResponse(status, exception);
}
return new GetAliasesResponse(status, error, 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 @@ -58,11 +58,13 @@
import org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesResponse;
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 @@ -978,6 +980,33 @@ public void rolloverAsync(RolloverRequest rolloverRequest, ActionListener<Rollov
listener, emptySet(), headers);
}

/**
* Gets one or more aliases using the Get Index Aliases API.
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html"> Indices Aliases API on
* elastic.co</a>
* @param getAliasesRequest the request
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
* @return the response
* @throws IOException in case there is a problem sending the request or parsing back the response
*/
public GetAliasesResponse getAlias(GetAliasesRequest getAliasesRequest, RequestOptions options) throws IOException {
return restHighLevelClient.performRequestAndParseEntity(getAliasesRequest, RequestConverters::getAlias, options,
GetAliasesResponse::fromXContent, singleton(RestStatus.NOT_FOUND.getStatus()));
}

/**
* Asynchronously gets one or more aliases using the Get Index Aliases API.
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html"> Indices Aliases API on
* elastic.co</a>
* @param getAliasesRequest the request
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
* @param listener the listener to be notified upon request completion
*/
public void getAliasAsync(GetAliasesRequest getAliasesRequest, RequestOptions options, ActionListener<GetAliasesResponse> listener) {
restHighLevelClient.performRequestAsyncAndParseEntity(getAliasesRequest, RequestConverters::getAlias, options,
GetAliasesResponse::fromXContent, listener, singleton(RestStatus.NOT_FOUND.getStatus()));
}

/**
* Updates specific index level settings using the Update Indices Settings API.
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-update-settings.html"> Update Indices Settings
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,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;
}

static Request getTemplates(GetIndexTemplatesRequest getIndexTemplatesRequest) throws IOException {
String[] names = getIndexTemplatesRequest.names();
String endpoint = new EndpointBuilder().addPathPartAsIs("_template").addCommaSeparatedPathParts(names).build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1021,10 +1021,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 @@ -1109,10 +1109,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

0 comments on commit 7d7ead9

Please sign in to comment.