Introduce the deprecation API (elastic/x-pack-elasticsearch#1833)

Adds REST endpoint and Transport Action for retrieving breaking-changes deprecations that exist in current version. This PR is just the framework for such an API, future checks will be added to the appropriate branches.

Original commit: elastic/x-pack-elasticsearch@990e3468e9
This commit is contained in:
Tal Levy 2017-06-27 13:51:45 -07:00 committed by GitHub
parent 6d4be0e5d3
commit 8145b100f1
16 changed files with 968 additions and 0 deletions

View File

@ -51,6 +51,9 @@ public class XPackLicenseState {
messages.put(XPackPlugin.LOGSTASH, new String[] {
"Logstash specific APIs are disabled. You can continue to manage and poll stored configurations"
});
messages.put(XPackPlugin.DEPRECATION, new String[] {
"Deprecation APIs are disabled"
});
EXPIRATION_MESSAGES = Collections.unmodifiableMap(messages);
}
@ -432,4 +435,12 @@ public class XPackLicenseState {
public boolean isLogstashAllowed() {
return status.active;
}
/**
* Deprecation APIs are always allowed as long as there is an active license
* @return {@code true} as long as there is a valid license
*/
public boolean isDeprecationAllowed() {
return status.active;
}
}

View File

@ -66,6 +66,7 @@ import org.elasticsearch.xpack.common.http.auth.HttpAuthRegistry;
import org.elasticsearch.xpack.common.http.auth.basic.BasicAuth;
import org.elasticsearch.xpack.common.http.auth.basic.BasicAuthFactory;
import org.elasticsearch.xpack.common.text.TextTemplateEngine;
import org.elasticsearch.xpack.deprecation.Deprecation;
import org.elasticsearch.xpack.extensions.XPackExtension;
import org.elasticsearch.xpack.extensions.XPackExtensionsService;
import org.elasticsearch.xpack.graph.Graph;
@ -148,6 +149,9 @@ public class XPackPlugin extends Plugin implements ScriptPlugin, ActionPlugin, I
/** Name constant for the Logstash feature. */
public static final String LOGSTASH = "logstash";
/** Name constant for the Deprecation API feature. */
public static final String DEPRECATION = "deprecation";
// inside of YAML settings we still use xpack do not having handle issues with dashes
private static final String SETTINGS_NAME = "xpack";
@ -198,6 +202,7 @@ public class XPackPlugin extends Plugin implements ScriptPlugin, ActionPlugin, I
protected Graph graph;
protected MachineLearning machineLearning;
protected Logstash logstash;
protected Deprecation deprecation;
public XPackPlugin(
final Settings settings,
@ -215,6 +220,7 @@ public class XPackPlugin extends Plugin implements ScriptPlugin, ActionPlugin, I
this.graph = new Graph(settings);
this.machineLearning = new MachineLearning(settings, env, licenseState);
this.logstash = new Logstash(settings);
this.deprecation = new Deprecation();
// Check if the node is a transport client.
if (transportClientMode == false) {
this.extensionsService = new XPackExtensionsService(settings, resolveXPackExtensionsFile(env), getExtensions());
@ -418,6 +424,7 @@ public class XPackPlugin extends Plugin implements ScriptPlugin, ActionPlugin, I
actions.addAll(watcher.getActions());
actions.addAll(graph.getActions());
actions.addAll(machineLearning.getActions());
actions.addAll(deprecation.getActions());
return actions;
}
@ -451,6 +458,8 @@ public class XPackPlugin extends Plugin implements ScriptPlugin, ActionPlugin, I
indexNameExpressionResolver, nodesInCluster));
handlers.addAll(machineLearning.getRestHandlers(settings, restController, clusterSettings, indexScopedSettings, settingsFilter,
indexNameExpressionResolver, nodesInCluster));
handlers.addAll(deprecation.getRestHandlers(settings, restController, clusterSettings, indexScopedSettings, settingsFilter,
indexNameExpressionResolver, nodesInCluster));
return handlers;
}

View File

@ -0,0 +1,43 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.deprecation;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.cluster.node.DiscoveryNodes;
import org.elasticsearch.common.settings.ClusterSettings;
import org.elasticsearch.common.settings.IndexScopedSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.settings.SettingsFilter;
import org.elasticsearch.plugins.ActionPlugin;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestHandler;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;
/**
* The plugin class for the Deprecation API
*/
public class Deprecation implements ActionPlugin {
@Override
public List<ActionHandler<? extends ActionRequest, ? extends ActionResponse>> getActions() {
return Collections.singletonList(new ActionHandler<>(DeprecationInfoAction.INSTANCE, DeprecationInfoAction.TransportAction.class));
}
@Override
public List<RestHandler> getRestHandlers(Settings settings, RestController restController, ClusterSettings clusterSettings,
IndexScopedSettings indexScopedSettings, SettingsFilter settingsFilter,
IndexNameExpressionResolver indexNameExpressionResolver,
Supplier<DiscoveryNodes> nodesInCluster) {
return Collections.singletonList(new RestDeprecationInfoAction(settings, restController));
}
}

View File

@ -0,0 +1,83 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.deprecation;
import com.carrotsearch.hppc.cursors.ObjectCursor;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.Version;
import org.elasticsearch.action.admin.cluster.node.info.NodeInfo;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.cluster.metadata.MappingMetaData;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* Class containing all the cluster, node, and index deprecation checks that will be served
* by the {@link DeprecationInfoAction}.
*/
public class DeprecationChecks {
private DeprecationChecks() {
}
static List<BiFunction<List<NodeInfo>, ClusterState, DeprecationIssue>> CLUSTER_SETTINGS_CHECKS =
Collections.unmodifiableList(Arrays.asList(
// STUB: TODO(talevy): add checks
));
static List<BiFunction<List<NodeInfo>, ClusterState, DeprecationIssue>> NODE_SETTINGS_CHECKS =
Collections.unmodifiableList(Arrays.asList(
// STUB: TODO(talevy): add checks
));
@SuppressWarnings("unchecked")
static List<Function<IndexMetaData, DeprecationIssue>> INDEX_SETTINGS_CHECKS =
Collections.unmodifiableList(Arrays.asList(
indexMetaData -> {
List<String> issues = new ArrayList<>();
if (indexMetaData.getCreationVersion().onOrBefore(Version.V_5_6_0)) {
for (ObjectCursor<MappingMetaData> mappingMetaData : indexMetaData.getMappings().values()) {
Map<String, Object> sourceAsMap = mappingMetaData.value.sourceAsMap();
((Map<String, Object>) sourceAsMap.getOrDefault("properties", Collections.emptyMap()))
.forEach((key, value) -> {
Map<String, Object> valueMap = ((Map<String, Object>) value);
if ("boolean".equals(valueMap.get("type"))) {
issues.add("type: " + mappingMetaData.value.type() + ", field: " + key);
}
});
}
}
return new DeprecationIssue(DeprecationIssue.Level.INFO, "Coercion of boolean fields",
"https://www.elastic.co/guide/en/elasticsearch/reference/master/" +
"breaking_60_mappings_changes.html#_coercion_of_boolean_fields",
Arrays.toString(issues.toArray()));
}
));
/**
* helper utility function to reduce repeat of running a specific {@link Set} of checks.
*
* @param checks The functional checks to execute using the mapper function
* @param mapper The function that executes the lambda check with the appropriate arguments
* @param <T> The signature of the check (BiFunction, Function, including the appropriate arguments)
* @return The list of {@link DeprecationIssue} that were found in the cluster
*/
static <T> List<DeprecationIssue> filterChecks(List<T> checks, Function<T, DeprecationIssue> mapper) {
return checks.stream().map(mapper).filter(Objects::nonNull).collect(Collectors.toList());
}
}

View File

@ -0,0 +1,324 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.deprecation;
import org.elasticsearch.action.Action;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.action.IndicesRequest;
import org.elasticsearch.action.admin.cluster.node.info.NodeInfo;
import org.elasticsearch.action.admin.cluster.node.info.NodesInfoRequest;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.action.support.master.MasterNodeReadOperationRequestBuilder;
import org.elasticsearch.action.support.master.MasterNodeReadRequest;
import org.elasticsearch.action.support.master.TransportMasterNodeReadAction;
import org.elasticsearch.client.ElasticsearchClient;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.block.ClusterBlockException;
import org.elasticsearch.cluster.block.ClusterBlockLevel;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.IndexNotFoundException;
import org.elasticsearch.license.LicenseUtils;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.XPackPlugin;
import org.elasticsearch.xpack.deprecation.DeprecationIssue.Level;
import org.elasticsearch.xpack.security.InternalClient;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
import static org.elasticsearch.action.ValidateActions.addValidationError;
import static org.elasticsearch.xpack.deprecation.DeprecationChecks.CLUSTER_SETTINGS_CHECKS;
import static org.elasticsearch.xpack.deprecation.DeprecationChecks.INDEX_SETTINGS_CHECKS;
import static org.elasticsearch.xpack.deprecation.DeprecationChecks.NODE_SETTINGS_CHECKS;
import static org.elasticsearch.xpack.deprecation.DeprecationChecks.filterChecks;
public class DeprecationInfoAction extends Action<DeprecationInfoAction.Request,
DeprecationInfoAction.Response, DeprecationInfoAction.RequestBuilder> {
public static final DeprecationInfoAction INSTANCE = new DeprecationInfoAction();
public static final String NAME = "cluster:admin/xpack/deprecation/info";
private DeprecationInfoAction() {
super(NAME);
}
@Override
public RequestBuilder newRequestBuilder(ElasticsearchClient client) {
return new RequestBuilder(client, this);
}
@Override
public Response newResponse() {
return new Response();
}
public static class Response extends ActionResponse implements ToXContentObject {
private List<DeprecationIssue> clusterSettingsIssues;
private List<DeprecationIssue> nodeSettingsIssues;
private Map<String, List<DeprecationIssue>> indexSettingsIssues;
Response() {
}
public Response(List<DeprecationIssue> clusterSettingsIssues,
List<DeprecationIssue> nodeSettingsIssues,
Map<String, List<DeprecationIssue>> indexSettingsIssues) {
this.clusterSettingsIssues = clusterSettingsIssues;
this.nodeSettingsIssues = nodeSettingsIssues;
this.indexSettingsIssues = indexSettingsIssues;
}
public List<DeprecationIssue> getClusterSettingsIssues() {
return clusterSettingsIssues;
}
public List<DeprecationIssue> getNodeSettingsIssues() {
return nodeSettingsIssues;
}
public Map<String, List<DeprecationIssue>> getIndexSettingsIssues() {
return indexSettingsIssues;
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
clusterSettingsIssues = in.readList(DeprecationIssue::new);
nodeSettingsIssues = in.readList(DeprecationIssue::new);
indexSettingsIssues = in.readMapOfLists(StreamInput::readString, DeprecationIssue::new);
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeList(clusterSettingsIssues);
out.writeList(nodeSettingsIssues);
out.writeMapOfLists(indexSettingsIssues, StreamOutput::writeString, (o, v) -> v.writeTo(o));
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.startObject()
.array("cluster_settings", clusterSettingsIssues.toArray())
.array("node_settings", nodeSettingsIssues.toArray())
.field("index_settings")
.map(indexSettingsIssues)
.endObject();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Response response = (Response) o;
return Objects.equals(clusterSettingsIssues, response.clusterSettingsIssues) &&
Objects.equals(nodeSettingsIssues, response.nodeSettingsIssues) &&
Objects.equals(indexSettingsIssues, response.indexSettingsIssues);
}
@Override
public int hashCode() {
return Objects.hash(clusterSettingsIssues, nodeSettingsIssues, indexSettingsIssues);
}
/**
* This is the function that does the bulk of the logic of taking the appropriate ES dependencies
* like {@link NodeInfo}, {@link ClusterState}. Alongside these objects and the list of deprecation checks,
* this function will run through all the checks and build out the final list of issues that exist in the
* cluster.
*
* @param nodesInfo The list of {@link NodeInfo} metadata objects for retrieving node-level information
* @param state The cluster state
* @param indexNameExpressionResolver Used to resolve indices into their concrete names
* @param indices The list of index expressions to evaluate using `indexNameExpressionResolver`
* @param indicesOptions The options to use when resolving and filtering which indices to check
* @param clusterSettingsChecks The list of cluster-level checks
* @param nodeSettingsChecks The list of node-level checks
* @param indexSettingsChecks The list of index-level checks that will be run across all specified
* concrete indices
* @return The list of deprecation issues found in the cluster
*/
static DeprecationInfoAction.Response from(List<NodeInfo> nodesInfo, ClusterState state,
IndexNameExpressionResolver indexNameExpressionResolver,
String[] indices, IndicesOptions indicesOptions,
List<BiFunction<List<NodeInfo>, ClusterState,DeprecationIssue>>clusterSettingsChecks,
List<BiFunction<List<NodeInfo>, ClusterState, DeprecationIssue>> nodeSettingsChecks,
List<Function<IndexMetaData, DeprecationIssue>> indexSettingsChecks) {
List<DeprecationIssue> clusterSettingsIssues = filterChecks(clusterSettingsChecks,
(c) -> c.apply(nodesInfo, state));
List<DeprecationIssue> nodeSettingsIssues = filterChecks(nodeSettingsChecks,
(c) -> c.apply(nodesInfo, state));
String[] concreteIndexNames = indexNameExpressionResolver.concreteIndexNames(state, indicesOptions, indices);
Map<String, List<DeprecationIssue>> indexSettingsIssues = new HashMap<>();
for (String concreteIndex : concreteIndexNames) {
IndexMetaData indexMetaData = state.getMetaData().index(concreteIndex);
List<DeprecationIssue> singleIndexIssues = filterChecks(indexSettingsChecks,
c -> c.apply(indexMetaData));
if (singleIndexIssues.size() > 0) {
indexSettingsIssues.put(concreteIndex, singleIndexIssues);
}
}
return new DeprecationInfoAction.Response(clusterSettingsIssues, nodeSettingsIssues, indexSettingsIssues);
}
}
public static class Request extends MasterNodeReadRequest<Request> implements IndicesRequest.Replaceable {
private String[] indices = Strings.EMPTY_ARRAY;
private static final IndicesOptions INDICES_OPTIONS = IndicesOptions.fromOptions(false, true,
true, true);
public Request() {
}
public Request(String... indices) {
this.indices = indices;
}
@Override
public String[] indices() {
return indices;
}
@Override
public Request indices(String... indices) {
this.indices = indices;
return this;
}
@Override
public IndicesOptions indicesOptions() {
return INDICES_OPTIONS;
}
@Override
public ActionRequestValidationException validate() {
ActionRequestValidationException validationException = null;
if (indices == null) {
validationException = addValidationError("index/indices is missing", validationException);
}
return validationException;
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
indices = in.readStringArray();
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeStringArray(indices);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Request request = (Request) o;
return Arrays.equals(indices, request.indices);
}
@Override
public int hashCode() {
return Objects.hash(Arrays.hashCode(indices));
}
}
public static class RequestBuilder extends MasterNodeReadOperationRequestBuilder<Request, Response, RequestBuilder> {
protected RequestBuilder(ElasticsearchClient client, DeprecationInfoAction action) {
super(client, action, new Request());
}
public RequestBuilder setIndices(String... indices) {
request.indices(indices);
return this;
}
}
public static class TransportAction extends TransportMasterNodeReadAction<Request, Response> {
private final XPackLicenseState licenseState;
private final InternalClient client;
private final IndexNameExpressionResolver indexNameExpressionResolver;
@Inject
public TransportAction(Settings settings, TransportService transportService, ClusterService clusterService,
ThreadPool threadPool, ActionFilters actionFilters,
IndexNameExpressionResolver indexNameExpressionResolver,
XPackLicenseState licenseState, InternalClient client) {
super(settings, DeprecationInfoAction.NAME, transportService, clusterService, threadPool, actionFilters,
indexNameExpressionResolver, Request::new);
this.licenseState = licenseState;
this.client = client;
this.indexNameExpressionResolver = indexNameExpressionResolver;
}
@Override
protected String executor() {
return ThreadPool.Names.GENERIC;
}
@Override
protected Response newResponse() {
return new Response();
}
@Override
protected ClusterBlockException checkBlock(Request request, ClusterState state) {
// Cluster is not affected but we look up repositories in metadata
return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_READ);
}
@Override
protected final void masterOperation(final Request request, ClusterState state, final ActionListener<Response> listener) {
if (licenseState.isDeprecationAllowed()) {
NodesInfoRequest nodesInfoRequest = new NodesInfoRequest("_local").settings(true).plugins(true);
client.admin().cluster().nodesInfo(nodesInfoRequest, ActionListener.wrap(nodesInfoResponse -> {
// if there's a failure, then we failed to work with the
// _local node (guaranteed a single exception)
if (nodesInfoResponse.hasFailures()) {
throw nodesInfoResponse.failures().get(0);
}
listener.onResponse(Response.from(nodesInfoResponse.getNodes(), state,
indexNameExpressionResolver, request.indices(), request.indicesOptions(),
CLUSTER_SETTINGS_CHECKS, NODE_SETTINGS_CHECKS, INDEX_SETTINGS_CHECKS));
}, listener::onFailure));
} else {
listener.onFailure(LicenseUtils.newComplianceException(XPackPlugin.DEPRECATION));
}
}
}
}

View File

@ -0,0 +1,136 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.deprecation;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException;
import java.util.Locale;
import java.util.Objects;
/**
* Information about deprecated items
*/
public class DeprecationIssue implements Writeable, ToXContent {
public enum Level implements Writeable {
NONE,
INFO,
WARNING,
CRITICAL
;
public static Level fromString(String value) {
return Level.valueOf(value.toUpperCase(Locale.ROOT));
}
public static Level readFromStream(StreamInput in) throws IOException {
int ordinal = in.readVInt();
if (ordinal < 0 || ordinal >= values().length) {
throw new IOException("Unknown Level ordinal [" + ordinal + "]");
}
return values()[ordinal];
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeVInt(ordinal());
}
@Override
public String toString() {
return name().toLowerCase(Locale.ROOT);
}
}
private Level level;
private String message;
private String url;
private String details;
// pkg-private for tests
DeprecationIssue() {
}
public DeprecationIssue(Level level, String message, String url, @Nullable String details) {
this.level = level;
this.message = message;
this.url = url;
this.details = details;
}
public DeprecationIssue(StreamInput in) throws IOException {
level = Level.readFromStream(in);
message = in.readString();
url = in.readString();
details = in.readOptionalString();
}
public Level getLevel() {
return level;
}
public String getMessage() {
return message;
}
public String getUrl() {
return url;
}
public String getDetails() {
return details;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
level.writeTo(out);
out.writeString(message);
out.writeString(url);
out.writeOptionalString(details);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject()
.field("level", level)
.field("message", message)
.field("url", url);
if (details != null) {
builder.field("details", details);
}
return builder.endObject();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
DeprecationIssue that = (DeprecationIssue) o;
return Objects.equals(level, that.level) &&
Objects.equals(message, that.message) &&
Objects.equals(url, that.url) &&
Objects.equals(details, that.details);
}
@Override
public int hashCode() {
return Objects.hash(level, message, url, details);
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.deprecation;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.action.RestToXContentListener;
import org.elasticsearch.xpack.deprecation.DeprecationInfoAction.Request;
import java.io.IOException;
public class RestDeprecationInfoAction extends BaseRestHandler {
public RestDeprecationInfoAction(Settings settings, RestController controller) {
super(settings);
controller.registerHandler(RestRequest.Method.GET, "/_xpack/migration/deprecations", this);
controller.registerHandler(RestRequest.Method.GET, "/{index}/_xpack/migration/deprecations", this);
}
@Override
public String getName() {
return "deprecation_info_action";
}
@Override
public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException {
if (request.method().equals(RestRequest.Method.GET)) {
return handleGet(request, client);
} else {
throw new IllegalArgumentException("illegal method [" + request.method() + "] for request [" + request.path() + "]");
}
}
private RestChannelConsumer handleGet(final RestRequest request, NodeClient client) {
Request infoRequest = new Request(Strings.splitStringByCommaToArray(request.param("index")));
return channel -> client.execute(DeprecationInfoAction.INSTANCE, infoRequest, new RestToXContentListener<>(channel));
}
}

View File

@ -19,6 +19,7 @@ import org.elasticsearch.test.ESIntegTestCase.ClusterScope;
import org.elasticsearch.test.SecurityIntegTestCase;
import org.elasticsearch.test.discovery.TestZenDiscovery;
import org.elasticsearch.xpack.XPackPlugin;
import org.elasticsearch.xpack.deprecation.Deprecation;
import org.junit.BeforeClass;
import java.io.IOException;
@ -137,6 +138,9 @@ public class KnownActionsTests extends SecurityIntegTestCase {
// also load stuff from Reindex in org.elasticsearch.index.reindex package
loadActions(collectSubClasses(Action.class, ReindexPlugin.class), actions);
// also load stuff from Deprecation in org.elasticsearch.deprecation
loadActions(collectSubClasses(Action.class, Deprecation.class), actions);
return unmodifiableSet(actions);
}

View File

@ -0,0 +1,68 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.deprecation;
import org.elasticsearch.Version;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.test.ESTestCase;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Supplier;
import static org.elasticsearch.xpack.deprecation.DeprecationChecks.INDEX_SETTINGS_CHECKS;
import static org.hamcrest.core.IsEqual.equalTo;
public class DeprecationChecksTests extends ESTestCase {
public void testFilterChecks() throws IOException {
DeprecationIssue issue = DeprecationIssueTests.createTestInstance();
int numChecksPassed = randomIntBetween(0, 5);
int numChecksFailed = 10 - numChecksPassed;
List<Supplier<DeprecationIssue>> checks = new ArrayList<>();
for (int i = 0; i < numChecksFailed; i++) {
checks.add(() -> issue);
}
for (int i = 0; i < numChecksPassed; i++) {
checks.add(() -> null);
}
List<DeprecationIssue> filteredIssues = DeprecationChecks.filterChecks(checks, Supplier::get);
assertThat(filteredIssues.size(), equalTo(numChecksFailed));
}
public void testCoerceBooleanDeprecation() throws IOException {
XContentBuilder mapping = XContentFactory.jsonBuilder();
mapping.startObject(); {
mapping.startObject("properties"); {
mapping.startObject("my_boolean"); {
mapping.field("type", "boolean");
}
mapping.endObject();
}
mapping.endObject();
}
mapping.endObject();
IndexMetaData indexMetaData = IndexMetaData.builder("test")
.putMapping("testBooleanCoercion", mapping.string())
.settings(settings(Version.V_5_6_0))
.numberOfShards(1)
.numberOfReplicas(0)
.build();
DeprecationIssue expected = new DeprecationIssue(DeprecationIssue.Level.INFO,
"Coercion of boolean fields",
"https://www.elastic.co/guide/en/elasticsearch/reference/master/" +
"breaking_60_mappings_changes.html#_coercion_of_boolean_fields",
Arrays.toString(new String[] { "type: testBooleanCoercion, field: my_boolean" }));
List<DeprecationIssue> issues = DeprecationChecks.filterChecks(INDEX_SETTINGS_CHECKS, c -> c.apply(indexMetaData));
assertThat(issues.size(), equalTo(1));
assertThat(issues.get(0), equalTo(expected));
}
}

View File

@ -0,0 +1,21 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.deprecation;
import org.elasticsearch.test.AbstractStreamableTestCase;
public class DeprecationInfoActionRequestTests extends AbstractStreamableTestCase<DeprecationInfoAction.Request> {
@Override
protected DeprecationInfoAction.Request createTestInstance() {
return new DeprecationInfoAction.Request(randomAlphaOfLength(10));
}
@Override
protected DeprecationInfoAction.Request createBlankInstance() {
return new DeprecationInfoAction.Request();
}
}

View File

@ -0,0 +1,123 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.deprecation;
import org.elasticsearch.Build;
import org.elasticsearch.Version;
import org.elasticsearch.action.admin.cluster.node.info.NodeInfo;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.cluster.ClusterName;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.test.AbstractStreamableTestCase;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.core.IsEqual.equalTo;
public class DeprecationInfoActionResponseTests extends AbstractStreamableTestCase<DeprecationInfoAction.Response> {
@Override
protected DeprecationInfoAction.Response createTestInstance() {
List<DeprecationIssue> clusterIssues = Stream.generate(DeprecationIssueTests::createTestInstance)
.limit(randomIntBetween(0, 10)).collect(Collectors.toList());
List<DeprecationIssue> nodeIssues = Stream.generate(DeprecationIssueTests::createTestInstance)
.limit(randomIntBetween(0, 10)).collect(Collectors.toList());
Map<String, List<DeprecationIssue>> indexIssues = new HashMap<>();
for (int i = 0; i < randomIntBetween(0, 10); i++) {
List<DeprecationIssue> perIndexIssues = Stream.generate(DeprecationIssueTests::createTestInstance)
.limit(randomIntBetween(0, 10)).collect(Collectors.toList());
indexIssues.put(randomAlphaOfLength(10), perIndexIssues);
}
return new DeprecationInfoAction.Response(clusterIssues, nodeIssues, indexIssues);
}
@Override
protected DeprecationInfoAction.Response createBlankInstance() {
return new DeprecationInfoAction.Response();
}
public void testFrom() throws IOException {
XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject("_all");
mapping.field("enabled", false);
mapping.endObject().endObject();
MetaData metadata = MetaData.builder().put(IndexMetaData.builder("test")
.putMapping("testUnderscoreAll", mapping.string())
.settings(settings(Version.CURRENT))
.numberOfShards(1)
.numberOfReplicas(0))
.build();
DiscoveryNode discoveryNode = DiscoveryNode.createLocal(Settings.EMPTY,
new TransportAddress(TransportAddress.META_ADDRESS, 9300), "test");
ClusterState state = ClusterState.builder(ClusterName.DEFAULT).metaData(metadata).build();
List<NodeInfo> nodeInfos = Collections.singletonList(new NodeInfo(Version.CURRENT, Build.CURRENT,
discoveryNode, null, null, null, null,
null, null, null, null, null, null));
IndexNameExpressionResolver resolver = new IndexNameExpressionResolver(Settings.EMPTY);
IndicesOptions indicesOptions = IndicesOptions.fromOptions(false, false,
true, true);
boolean clusterIssueFound = randomBoolean();
boolean nodeIssueFound = randomBoolean();
boolean indexIssueFound = randomBoolean();
DeprecationIssue foundIssue = DeprecationIssueTests.createTestInstance();
List<BiFunction<List<NodeInfo>, ClusterState, DeprecationIssue>> clusterSettingsChecks =
Collections.unmodifiableList(Arrays.asList(
(ln, s) -> clusterIssueFound ? foundIssue : null
));
List<BiFunction<List<NodeInfo>, ClusterState, DeprecationIssue>> nodeSettingsChecks =
Collections.unmodifiableList(Arrays.asList(
(ln, s) -> nodeIssueFound ? foundIssue : null
));
List<Function<IndexMetaData, DeprecationIssue>> indexSettingsChecks =
Collections.unmodifiableList(Arrays.asList(
(idx) -> indexIssueFound ? foundIssue : null
));
DeprecationInfoAction.Response response = DeprecationInfoAction.Response.from(nodeInfos, state,
resolver, Strings.EMPTY_ARRAY, indicesOptions,
clusterSettingsChecks, nodeSettingsChecks, indexSettingsChecks);
if (clusterIssueFound) {
assertThat(response.getClusterSettingsIssues(), equalTo(Collections.singletonList(foundIssue)));
} else {
assertThat(response.getClusterSettingsIssues(), empty());
}
if (nodeIssueFound) {
assertThat(response.getNodeSettingsIssues(), equalTo(Collections.singletonList(foundIssue)));
} else {
assertTrue(response.getNodeSettingsIssues().isEmpty());
}
if (indexIssueFound) {
assertThat(response.getIndexSettingsIssues(), equalTo(Collections.singletonMap("test",
Collections.singletonList(foundIssue))));
} else {
assertTrue(response.getIndexSettingsIssues().isEmpty());
}
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.deprecation;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.test.ESTestCase;
import org.junit.Before;
import java.io.IOException;
import java.util.Map;
import static org.elasticsearch.common.xcontent.ToXContent.EMPTY_PARAMS;
import static org.elasticsearch.xpack.deprecation.DeprecationIssue.Level;
import static org.hamcrest.core.IsEqual.equalTo;
public class DeprecationIssueTests extends ESTestCase {
DeprecationIssue issue;
static DeprecationIssue createTestInstance() {
String details = randomBoolean() ? randomAlphaOfLength(10) : null;
return new DeprecationIssue(randomFrom(Level.values()), randomAlphaOfLength(10),
randomAlphaOfLength(10), details);
}
@Before
public void setup() {
issue = createTestInstance();
}
public void testEqualsAndHashCode() {
DeprecationIssue other = new DeprecationIssue(issue.getLevel(), issue.getMessage(), issue.getUrl(), issue.getDetails());
assertThat(issue, equalTo(other));
assertThat(other, equalTo(issue));
assertThat(issue.hashCode(), equalTo(other.hashCode()));
}
public void testSerialization() throws IOException {
BytesStreamOutput out = new BytesStreamOutput();
issue.writeTo(out);
StreamInput in = out.bytes().streamInput();
DeprecationIssue other = new DeprecationIssue(in);
assertThat(issue, equalTo(other));
}
public void testToXContent() throws IOException {
XContentBuilder builder = XContentFactory.jsonBuilder();
issue.toXContent(builder, EMPTY_PARAMS);
Map<String, Object> toXContentMap = XContentHelper.convertToMap(builder.bytes(), false, builder.contentType()).v2();
String level = (String) toXContentMap.get("level");
String message = (String) toXContentMap.get("message");
String url = (String) toXContentMap.get("url");
if (issue.getDetails() != null) {
assertTrue(toXContentMap.containsKey("details"));
}
String details = (String) toXContentMap.get("details");
DeprecationIssue other = new DeprecationIssue(Level.fromString(level), message, url, details);
assertThat(issue, equalTo(other));
}
}

View File

@ -155,3 +155,4 @@ cluster:admin/reindex/rethrottle
indices:data/write/update/byquery
indices:data/write/delete/byquery
indices:data/write/reindex
cluster:admin/xpack/deprecation/info

View File

@ -133,3 +133,4 @@ cluster:admin/reindex/rethrottle[n]
indices:data/write/update/byquery
indices:data/write/delete/byquery
indices:data/write/reindex
cluster:admin/xpack/deprecation/info

View File

@ -0,0 +1,19 @@
{
"xpack.deprecation.info": {
"documentation": "http://www.elastic.co/guide/en/migration/current/appendix-api-deprecation-info.html",
"methods": [ "GET" ],
"url": {
"path": "/{index}/_xpack/migration/deprecations",
"paths": ["/_xpack/migration/deprecations", "/{index}/_xpack/migration/deprecations"],
"parts": {
"index": {
"type" : "string",
"description" : "Index pattern"
}
},
"params": {
}
},
"body": null
}
}

View File

@ -0,0 +1,15 @@
---
setup:
- do:
cluster.health:
wait_for_status: yellow
---
"Test Deprecations":
- do:
xpack.deprecation.info:
index: "*"
- length: { cluster_settings: 0 }
- length: { node_settings: 0 }
- length: { index_settings: 0 }