mirror of
https://github.com/elastic/elasticsearch.git
synced 2025-04-24 23:27:25 -04:00
Add a capabilities API to check node and cluster capabilities (#106820)
This adds a /_capabilities rest endpoint for checking the capabilities of a cluster - what endpoints, parameters, and endpoint capabilities the cluster supports
This commit is contained in:
parent
8864058f83
commit
e7350dce29
15 changed files with 504 additions and 0 deletions
5
docs/changelog/106820.yaml
Normal file
5
docs/changelog/106820.yaml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
pr: 106820
|
||||||
|
summary: Add a capabilities API to check node and cluster capabilities
|
||||||
|
area: Infra/REST API
|
||||||
|
type: feature
|
||||||
|
issues: []
|
|
@ -61,4 +61,15 @@ public enum RestApiVersion {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static RestApiVersion forMajor(int major) {
|
||||||
|
switch (major) {
|
||||||
|
case 7 -> {
|
||||||
|
return V_7;
|
||||||
|
}
|
||||||
|
case 8 -> {
|
||||||
|
return V_8;
|
||||||
|
}
|
||||||
|
default -> throw new IllegalArgumentException("Unknown REST API version " + major);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.elasticsearch.nodescapabilities;
|
||||||
|
|
||||||
|
import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
|
||||||
|
import org.elasticsearch.action.admin.cluster.node.capabilities.NodesCapabilitiesRequest;
|
||||||
|
import org.elasticsearch.action.admin.cluster.node.capabilities.NodesCapabilitiesResponse;
|
||||||
|
import org.elasticsearch.test.ESIntegTestCase;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.hasSize;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
|
||||||
|
@ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST, numDataNodes = 0)
|
||||||
|
public class SimpleNodesCapabilitiesIT extends ESIntegTestCase {
|
||||||
|
|
||||||
|
public void testNodesCapabilities() throws IOException {
|
||||||
|
internalCluster().startNodes(2);
|
||||||
|
|
||||||
|
ClusterHealthResponse clusterHealth = clusterAdmin().prepareHealth().setWaitForGreenStatus().setWaitForNodes("2").get();
|
||||||
|
logger.info("--> done cluster_health, status {}", clusterHealth.getStatus());
|
||||||
|
|
||||||
|
// check we support the capabilities API itself. Which we do.
|
||||||
|
NodesCapabilitiesResponse response = clusterAdmin().nodesCapabilities(new NodesCapabilitiesRequest().path("_capabilities"))
|
||||||
|
.actionGet();
|
||||||
|
assertThat(response.getNodes(), hasSize(2));
|
||||||
|
assertThat(response.isSupported(), is(true));
|
||||||
|
|
||||||
|
// check we support some parameters of the capabilities API
|
||||||
|
response = clusterAdmin().nodesCapabilities(new NodesCapabilitiesRequest().path("_capabilities").parameters("method", "path"))
|
||||||
|
.actionGet();
|
||||||
|
assertThat(response.getNodes(), hasSize(2));
|
||||||
|
assertThat(response.isSupported(), is(true));
|
||||||
|
|
||||||
|
// check we don't support some other parameters of the capabilities API
|
||||||
|
response = clusterAdmin().nodesCapabilities(new NodesCapabilitiesRequest().path("_capabilities").parameters("method", "invalid"))
|
||||||
|
.actionGet();
|
||||||
|
assertThat(response.getNodes(), hasSize(2));
|
||||||
|
assertThat(response.isSupported(), is(false));
|
||||||
|
|
||||||
|
// check we don't support a random invalid api
|
||||||
|
// TODO this is not working yet - see https://github.com/elastic/elasticsearch/issues/107425
|
||||||
|
/*response = clusterAdmin().nodesCapabilities(new NodesCapabilitiesRequest().path("_invalid"))
|
||||||
|
.actionGet();
|
||||||
|
assertThat(response.getNodes(), hasSize(2));
|
||||||
|
assertThat(response.isSupported(), is(false));*/
|
||||||
|
}
|
||||||
|
}
|
|
@ -65,6 +65,7 @@ module org.elasticsearch.server {
|
||||||
exports org.elasticsearch.action.admin.cluster.desirednodes;
|
exports org.elasticsearch.action.admin.cluster.desirednodes;
|
||||||
exports org.elasticsearch.action.admin.cluster.health;
|
exports org.elasticsearch.action.admin.cluster.health;
|
||||||
exports org.elasticsearch.action.admin.cluster.migration;
|
exports org.elasticsearch.action.admin.cluster.migration;
|
||||||
|
exports org.elasticsearch.action.admin.cluster.node.capabilities;
|
||||||
exports org.elasticsearch.action.admin.cluster.node.hotthreads;
|
exports org.elasticsearch.action.admin.cluster.node.hotthreads;
|
||||||
exports org.elasticsearch.action.admin.cluster.node.info;
|
exports org.elasticsearch.action.admin.cluster.node.info;
|
||||||
exports org.elasticsearch.action.admin.cluster.node.reload;
|
exports org.elasticsearch.action.admin.cluster.node.reload;
|
||||||
|
|
|
@ -29,6 +29,7 @@ import org.elasticsearch.action.admin.cluster.migration.GetFeatureUpgradeStatusA
|
||||||
import org.elasticsearch.action.admin.cluster.migration.PostFeatureUpgradeAction;
|
import org.elasticsearch.action.admin.cluster.migration.PostFeatureUpgradeAction;
|
||||||
import org.elasticsearch.action.admin.cluster.migration.TransportGetFeatureUpgradeStatusAction;
|
import org.elasticsearch.action.admin.cluster.migration.TransportGetFeatureUpgradeStatusAction;
|
||||||
import org.elasticsearch.action.admin.cluster.migration.TransportPostFeatureUpgradeAction;
|
import org.elasticsearch.action.admin.cluster.migration.TransportPostFeatureUpgradeAction;
|
||||||
|
import org.elasticsearch.action.admin.cluster.node.capabilities.TransportNodesCapabilitiesAction;
|
||||||
import org.elasticsearch.action.admin.cluster.node.hotthreads.TransportNodesHotThreadsAction;
|
import org.elasticsearch.action.admin.cluster.node.hotthreads.TransportNodesHotThreadsAction;
|
||||||
import org.elasticsearch.action.admin.cluster.node.info.TransportNodesInfoAction;
|
import org.elasticsearch.action.admin.cluster.node.info.TransportNodesInfoAction;
|
||||||
import org.elasticsearch.action.admin.cluster.node.reload.TransportNodesReloadSecureSettingsAction;
|
import org.elasticsearch.action.admin.cluster.node.reload.TransportNodesReloadSecureSettingsAction;
|
||||||
|
@ -284,6 +285,7 @@ import org.elasticsearch.rest.action.admin.cluster.RestGetSnapshotsAction;
|
||||||
import org.elasticsearch.rest.action.admin.cluster.RestGetStoredScriptAction;
|
import org.elasticsearch.rest.action.admin.cluster.RestGetStoredScriptAction;
|
||||||
import org.elasticsearch.rest.action.admin.cluster.RestGetTaskAction;
|
import org.elasticsearch.rest.action.admin.cluster.RestGetTaskAction;
|
||||||
import org.elasticsearch.rest.action.admin.cluster.RestListTasksAction;
|
import org.elasticsearch.rest.action.admin.cluster.RestListTasksAction;
|
||||||
|
import org.elasticsearch.rest.action.admin.cluster.RestNodesCapabilitiesAction;
|
||||||
import org.elasticsearch.rest.action.admin.cluster.RestNodesHotThreadsAction;
|
import org.elasticsearch.rest.action.admin.cluster.RestNodesHotThreadsAction;
|
||||||
import org.elasticsearch.rest.action.admin.cluster.RestNodesInfoAction;
|
import org.elasticsearch.rest.action.admin.cluster.RestNodesInfoAction;
|
||||||
import org.elasticsearch.rest.action.admin.cluster.RestNodesStatsAction;
|
import org.elasticsearch.rest.action.admin.cluster.RestNodesStatsAction;
|
||||||
|
@ -616,6 +618,7 @@ public class ActionModule extends AbstractModule {
|
||||||
|
|
||||||
actions.register(TransportNodesInfoAction.TYPE, TransportNodesInfoAction.class);
|
actions.register(TransportNodesInfoAction.TYPE, TransportNodesInfoAction.class);
|
||||||
actions.register(TransportRemoteInfoAction.TYPE, TransportRemoteInfoAction.class);
|
actions.register(TransportRemoteInfoAction.TYPE, TransportRemoteInfoAction.class);
|
||||||
|
actions.register(TransportNodesCapabilitiesAction.TYPE, TransportNodesCapabilitiesAction.class);
|
||||||
actions.register(RemoteClusterNodesAction.TYPE, RemoteClusterNodesAction.TransportAction.class);
|
actions.register(RemoteClusterNodesAction.TYPE, RemoteClusterNodesAction.TransportAction.class);
|
||||||
actions.register(TransportNodesStatsAction.TYPE, TransportNodesStatsAction.class);
|
actions.register(TransportNodesStatsAction.TYPE, TransportNodesStatsAction.class);
|
||||||
actions.register(TransportNodesUsageAction.TYPE, TransportNodesUsageAction.class);
|
actions.register(TransportNodesUsageAction.TYPE, TransportNodesUsageAction.class);
|
||||||
|
@ -833,6 +836,7 @@ public class ActionModule extends AbstractModule {
|
||||||
registerHandler.accept(new RestClearVotingConfigExclusionsAction());
|
registerHandler.accept(new RestClearVotingConfigExclusionsAction());
|
||||||
registerHandler.accept(new RestNodesInfoAction(settingsFilter));
|
registerHandler.accept(new RestNodesInfoAction(settingsFilter));
|
||||||
registerHandler.accept(new RestRemoteClusterInfoAction());
|
registerHandler.accept(new RestRemoteClusterInfoAction());
|
||||||
|
registerHandler.accept(new RestNodesCapabilitiesAction());
|
||||||
registerHandler.accept(new RestNodesStatsAction());
|
registerHandler.accept(new RestNodesStatsAction());
|
||||||
registerHandler.accept(new RestNodesUsageAction());
|
registerHandler.accept(new RestNodesUsageAction());
|
||||||
registerHandler.accept(new RestNodesHotThreadsAction());
|
registerHandler.accept(new RestNodesHotThreadsAction());
|
||||||
|
@ -1029,6 +1033,7 @@ public class ActionModule extends AbstractModule {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void configure() {
|
protected void configure() {
|
||||||
|
bind(RestController.class).toInstance(restController);
|
||||||
bind(ActionFilters.class).toInstance(actionFilters);
|
bind(ActionFilters.class).toInstance(actionFilters);
|
||||||
bind(DestructiveOperations.class).toInstance(destructiveOperations);
|
bind(DestructiveOperations.class).toInstance(destructiveOperations);
|
||||||
bind(new TypeLiteral<RequestValidators<PutMappingRequest>>() {
|
bind(new TypeLiteral<RequestValidators<PutMappingRequest>>() {
|
||||||
|
|
|
@ -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
|
||||||
|
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.elasticsearch.action.admin.cluster.node.capabilities;
|
||||||
|
|
||||||
|
import org.elasticsearch.action.support.nodes.BaseNodeResponse;
|
||||||
|
import org.elasticsearch.cluster.node.DiscoveryNode;
|
||||||
|
import org.elasticsearch.common.io.stream.StreamInput;
|
||||||
|
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class NodeCapability extends BaseNodeResponse {
|
||||||
|
|
||||||
|
private final boolean supported;
|
||||||
|
|
||||||
|
public NodeCapability(StreamInput in) throws IOException {
|
||||||
|
super(in);
|
||||||
|
|
||||||
|
supported = in.readBoolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
public NodeCapability(boolean supported, DiscoveryNode node) {
|
||||||
|
super(node);
|
||||||
|
this.supported = supported;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSupported() {
|
||||||
|
return supported;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeTo(StreamOutput out) throws IOException {
|
||||||
|
super.writeTo(out);
|
||||||
|
|
||||||
|
out.writeBoolean(supported);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.elasticsearch.action.admin.cluster.node.capabilities;
|
||||||
|
|
||||||
|
import org.elasticsearch.action.support.nodes.BaseNodesRequest;
|
||||||
|
import org.elasticsearch.common.Strings;
|
||||||
|
import org.elasticsearch.core.RestApiVersion;
|
||||||
|
import org.elasticsearch.rest.RestRequest;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class NodesCapabilitiesRequest extends BaseNodesRequest<NodesCapabilitiesRequest> {
|
||||||
|
|
||||||
|
private RestRequest.Method method = RestRequest.Method.GET;
|
||||||
|
private String path = "/";
|
||||||
|
private Set<String> parameters = Set.of();
|
||||||
|
private Set<String> capabilities = Set.of();
|
||||||
|
private RestApiVersion restApiVersion = RestApiVersion.current();
|
||||||
|
|
||||||
|
public NodesCapabilitiesRequest() {
|
||||||
|
// always send to all nodes
|
||||||
|
super(Strings.EMPTY_ARRAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NodesCapabilitiesRequest path(String path) {
|
||||||
|
this.path = path;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String path() {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NodesCapabilitiesRequest method(RestRequest.Method method) {
|
||||||
|
this.method = method;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RestRequest.Method method() {
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NodesCapabilitiesRequest parameters(String... parameters) {
|
||||||
|
this.parameters = Set.of(parameters);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> parameters() {
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NodesCapabilitiesRequest capabilities(String... capabilities) {
|
||||||
|
this.capabilities = Set.of(capabilities);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> capabilities() {
|
||||||
|
return capabilities;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NodesCapabilitiesRequest restApiVersion(RestApiVersion restApiVersion) {
|
||||||
|
this.restApiVersion = restApiVersion;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RestApiVersion restApiVersion() {
|
||||||
|
return restApiVersion;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.elasticsearch.action.admin.cluster.node.capabilities;
|
||||||
|
|
||||||
|
import org.elasticsearch.action.FailedNodeException;
|
||||||
|
import org.elasticsearch.action.support.TransportAction;
|
||||||
|
import org.elasticsearch.action.support.nodes.BaseNodesResponse;
|
||||||
|
import org.elasticsearch.cluster.ClusterName;
|
||||||
|
import org.elasticsearch.common.io.stream.StreamInput;
|
||||||
|
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||||
|
import org.elasticsearch.xcontent.ToXContentFragment;
|
||||||
|
import org.elasticsearch.xcontent.XContentBuilder;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class NodesCapabilitiesResponse extends BaseNodesResponse<NodeCapability> implements ToXContentFragment {
|
||||||
|
protected NodesCapabilitiesResponse(ClusterName clusterName, List<NodeCapability> nodes, List<FailedNodeException> failures) {
|
||||||
|
super(clusterName, nodes, failures);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<NodeCapability> readNodesFrom(StreamInput in) throws IOException {
|
||||||
|
return TransportAction.localOnly();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void writeNodesTo(StreamOutput out, List<NodeCapability> nodes) throws IOException {
|
||||||
|
TransportAction.localOnly();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSupported() {
|
||||||
|
return getNodes().isEmpty() == false && getNodes().stream().allMatch(NodeCapability::isSupported);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||||
|
return builder.field("supported", isSupported());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,140 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.elasticsearch.action.admin.cluster.node.capabilities;
|
||||||
|
|
||||||
|
import org.elasticsearch.action.ActionType;
|
||||||
|
import org.elasticsearch.action.FailedNodeException;
|
||||||
|
import org.elasticsearch.action.support.ActionFilters;
|
||||||
|
import org.elasticsearch.action.support.nodes.TransportNodesAction;
|
||||||
|
import org.elasticsearch.cluster.node.DiscoveryNode;
|
||||||
|
import org.elasticsearch.cluster.service.ClusterService;
|
||||||
|
import org.elasticsearch.common.inject.Inject;
|
||||||
|
import org.elasticsearch.common.io.stream.StreamInput;
|
||||||
|
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||||
|
import org.elasticsearch.core.RestApiVersion;
|
||||||
|
import org.elasticsearch.rest.RestController;
|
||||||
|
import org.elasticsearch.rest.RestRequest;
|
||||||
|
import org.elasticsearch.tasks.Task;
|
||||||
|
import org.elasticsearch.threadpool.ThreadPool;
|
||||||
|
import org.elasticsearch.transport.TransportRequest;
|
||||||
|
import org.elasticsearch.transport.TransportService;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class TransportNodesCapabilitiesAction extends TransportNodesAction<
|
||||||
|
NodesCapabilitiesRequest,
|
||||||
|
NodesCapabilitiesResponse,
|
||||||
|
TransportNodesCapabilitiesAction.NodeCapabilitiesRequest,
|
||||||
|
NodeCapability> {
|
||||||
|
|
||||||
|
public static final ActionType<NodesCapabilitiesResponse> TYPE = new ActionType<>("cluster:monitor/nodes/capabilities");
|
||||||
|
|
||||||
|
private final RestController restController;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public TransportNodesCapabilitiesAction(
|
||||||
|
ThreadPool threadPool,
|
||||||
|
ClusterService clusterService,
|
||||||
|
TransportService transportService,
|
||||||
|
ActionFilters actionFilters,
|
||||||
|
RestController restController
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
TYPE.name(),
|
||||||
|
clusterService,
|
||||||
|
transportService,
|
||||||
|
actionFilters,
|
||||||
|
NodeCapabilitiesRequest::new,
|
||||||
|
threadPool.executor(ThreadPool.Names.MANAGEMENT)
|
||||||
|
);
|
||||||
|
this.restController = restController;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected NodesCapabilitiesResponse newResponse(
|
||||||
|
NodesCapabilitiesRequest request,
|
||||||
|
List<NodeCapability> responses,
|
||||||
|
List<FailedNodeException> failures
|
||||||
|
) {
|
||||||
|
return new NodesCapabilitiesResponse(clusterService.getClusterName(), responses, failures);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected NodeCapabilitiesRequest newNodeRequest(NodesCapabilitiesRequest request) {
|
||||||
|
return new NodeCapabilitiesRequest(
|
||||||
|
request.method(),
|
||||||
|
request.path(),
|
||||||
|
request.parameters(),
|
||||||
|
request.capabilities(),
|
||||||
|
request.restApiVersion()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected NodeCapability newNodeResponse(StreamInput in, DiscoveryNode node) throws IOException {
|
||||||
|
return new NodeCapability(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected NodeCapability nodeOperation(NodeCapabilitiesRequest request, Task task) {
|
||||||
|
boolean supported = restController.checkSupported(
|
||||||
|
request.method,
|
||||||
|
request.path,
|
||||||
|
request.parameters,
|
||||||
|
request.capabilities,
|
||||||
|
request.restApiVersion
|
||||||
|
);
|
||||||
|
return new NodeCapability(supported, transportService.getLocalNode());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class NodeCapabilitiesRequest extends TransportRequest {
|
||||||
|
private final RestRequest.Method method;
|
||||||
|
private final String path;
|
||||||
|
private final Set<String> parameters;
|
||||||
|
private final Set<String> capabilities;
|
||||||
|
private final RestApiVersion restApiVersion;
|
||||||
|
|
||||||
|
public NodeCapabilitiesRequest(StreamInput in) throws IOException {
|
||||||
|
super(in);
|
||||||
|
|
||||||
|
method = in.readEnum(RestRequest.Method.class);
|
||||||
|
path = in.readString();
|
||||||
|
parameters = in.readCollectionAsImmutableSet(StreamInput::readString);
|
||||||
|
capabilities = in.readCollectionAsImmutableSet(StreamInput::readString);
|
||||||
|
restApiVersion = RestApiVersion.forMajor(in.readVInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
public NodeCapabilitiesRequest(
|
||||||
|
RestRequest.Method method,
|
||||||
|
String path,
|
||||||
|
Set<String> parameters,
|
||||||
|
Set<String> capabilities,
|
||||||
|
RestApiVersion restApiVersion
|
||||||
|
) {
|
||||||
|
this.method = method;
|
||||||
|
this.path = path;
|
||||||
|
this.parameters = Set.copyOf(parameters);
|
||||||
|
this.capabilities = Set.copyOf(capabilities);
|
||||||
|
this.restApiVersion = restApiVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeTo(StreamOutput out) throws IOException {
|
||||||
|
super.writeTo(out);
|
||||||
|
|
||||||
|
out.writeEnum(method);
|
||||||
|
out.writeString(path);
|
||||||
|
out.writeCollection(parameters, StreamOutput::writeString);
|
||||||
|
out.writeCollection(capabilities, StreamOutput::writeString);
|
||||||
|
out.writeVInt(restApiVersion.major);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,6 +21,9 @@ import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest;
|
||||||
import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequestBuilder;
|
import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequestBuilder;
|
||||||
import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
|
import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
|
||||||
import org.elasticsearch.action.admin.cluster.health.TransportClusterHealthAction;
|
import org.elasticsearch.action.admin.cluster.health.TransportClusterHealthAction;
|
||||||
|
import org.elasticsearch.action.admin.cluster.node.capabilities.NodesCapabilitiesRequest;
|
||||||
|
import org.elasticsearch.action.admin.cluster.node.capabilities.NodesCapabilitiesResponse;
|
||||||
|
import org.elasticsearch.action.admin.cluster.node.capabilities.TransportNodesCapabilitiesAction;
|
||||||
import org.elasticsearch.action.admin.cluster.node.info.NodesInfoRequest;
|
import org.elasticsearch.action.admin.cluster.node.info.NodesInfoRequest;
|
||||||
import org.elasticsearch.action.admin.cluster.node.info.NodesInfoRequestBuilder;
|
import org.elasticsearch.action.admin.cluster.node.info.NodesInfoRequestBuilder;
|
||||||
import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse;
|
import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse;
|
||||||
|
@ -248,6 +251,14 @@ public class ClusterAdminClient implements ElasticsearchClient {
|
||||||
return new NodesStatsRequestBuilder(this).setNodesIds(nodesIds);
|
return new NodesStatsRequestBuilder(this).setNodesIds(nodesIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ActionFuture<NodesCapabilitiesResponse> nodesCapabilities(final NodesCapabilitiesRequest request) {
|
||||||
|
return execute(TransportNodesCapabilitiesAction.TYPE, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void nodesCapabilities(final NodesCapabilitiesRequest request, final ActionListener<NodesCapabilitiesResponse> listener) {
|
||||||
|
execute(TransportNodesCapabilitiesAction.TYPE, request, listener);
|
||||||
|
}
|
||||||
|
|
||||||
public void nodesUsage(final NodesUsageRequest request, final ActionListener<NodesUsageResponse> listener) {
|
public void nodesUsage(final NodesUsageRequest request, final ActionListener<NodesUsageResponse> listener) {
|
||||||
execute(TransportNodesUsageAction.TYPE, request, listener);
|
execute(TransportNodesUsageAction.TYPE, request, listener);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import org.apache.lucene.search.spell.LevenshteinDistance;
|
||||||
import org.elasticsearch.client.internal.node.NodeClient;
|
import org.elasticsearch.client.internal.node.NodeClient;
|
||||||
import org.elasticsearch.common.settings.Setting;
|
import org.elasticsearch.common.settings.Setting;
|
||||||
import org.elasticsearch.common.settings.Setting.Property;
|
import org.elasticsearch.common.settings.Setting.Property;
|
||||||
|
import org.elasticsearch.common.util.set.Sets;
|
||||||
import org.elasticsearch.core.CheckedConsumer;
|
import org.elasticsearch.core.CheckedConsumer;
|
||||||
import org.elasticsearch.core.RefCounted;
|
import org.elasticsearch.core.RefCounted;
|
||||||
import org.elasticsearch.core.Releasable;
|
import org.elasticsearch.core.Releasable;
|
||||||
|
@ -77,6 +78,13 @@ public abstract class BaseRestHandler implements RestHandler {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void handleRequest(RestRequest request, RestChannel channel, NodeClient client) throws Exception {
|
public final void handleRequest(RestRequest request, RestChannel channel, NodeClient client) throws Exception {
|
||||||
|
// check if the query has any parameters that are not in the supported set (if declared)
|
||||||
|
Set<String> supported = supportedQueryParameters();
|
||||||
|
if (supported != null && supported.containsAll(request.params().keySet()) == false) {
|
||||||
|
Set<String> unsupported = Sets.difference(request.params().keySet(), supported);
|
||||||
|
throw new IllegalArgumentException(unrecognized(request, unsupported, supported, "parameter"));
|
||||||
|
}
|
||||||
|
|
||||||
// prepare the request for execution; has the side effect of touching the request parameters
|
// prepare the request for execution; has the side effect of touching the request parameters
|
||||||
try (var action = prepareRequest(request, client)) {
|
try (var action = prepareRequest(request, client)) {
|
||||||
|
|
||||||
|
|
|
@ -365,6 +365,32 @@ public class RestController implements HttpServerTransport.Dispatcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean checkSupported(
|
||||||
|
RestRequest.Method method,
|
||||||
|
String path,
|
||||||
|
Set<String> parameters,
|
||||||
|
Set<String> capabilities,
|
||||||
|
RestApiVersion restApiVersion
|
||||||
|
) {
|
||||||
|
Iterator<MethodHandlers> allHandlers = getAllHandlers(null, path);
|
||||||
|
while (allHandlers.hasNext()) {
|
||||||
|
RestHandler handler;
|
||||||
|
MethodHandlers handlers = allHandlers.next();
|
||||||
|
if (handlers == null) {
|
||||||
|
handler = null;
|
||||||
|
} else {
|
||||||
|
handler = handlers.getHandler(method, restApiVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handler != null) {
|
||||||
|
var supportedParams = handler.supportedQueryParameters();
|
||||||
|
return (supportedParams == null || supportedParams.containsAll(parameters))
|
||||||
|
&& handler.supportedCapabilities().containsAll(capabilities);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, HttpRouteStats> getStats() {
|
public Map<String, HttpRouteStats> getStats() {
|
||||||
final Iterator<MethodHandlers> methodHandlersIterator = handlers.allNodeValues();
|
final Iterator<MethodHandlers> methodHandlersIterator = handlers.allNodeValues();
|
||||||
|
|
|
@ -18,6 +18,7 @@ import org.elasticsearch.xcontent.XContent;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler for REST requests
|
* Handler for REST requests
|
||||||
|
@ -85,6 +86,22 @@ public interface RestHandler {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The set of query parameters accepted by this rest handler,
|
||||||
|
* {@code null} if query parameters should not be checked nor validated.
|
||||||
|
* TODO - make this not nullable when all handlers have been updated
|
||||||
|
*/
|
||||||
|
default @Nullable Set<String> supportedQueryParameters() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The set of capabilities this rest handler supports.
|
||||||
|
*/
|
||||||
|
default Set<String> supportedCapabilities() {
|
||||||
|
return Set.of();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Controls whether requests handled by this class are allowed to to access system indices by default.
|
* Controls whether requests handled by this class are allowed to to access system indices by default.
|
||||||
* @return {@code true} if requests handled by this class should be allowed to access system indices.
|
* @return {@code true} if requests handled by this class should be allowed to access system indices.
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.elasticsearch.rest.action.admin.cluster;
|
||||||
|
|
||||||
|
import org.elasticsearch.action.admin.cluster.node.capabilities.NodesCapabilitiesRequest;
|
||||||
|
import org.elasticsearch.client.internal.node.NodeClient;
|
||||||
|
import org.elasticsearch.common.Strings;
|
||||||
|
import org.elasticsearch.rest.BaseRestHandler;
|
||||||
|
import org.elasticsearch.rest.RestRequest;
|
||||||
|
import org.elasticsearch.rest.Scope;
|
||||||
|
import org.elasticsearch.rest.ServerlessScope;
|
||||||
|
import org.elasticsearch.rest.action.RestActions.NodesResponseRestListener;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@ServerlessScope(Scope.INTERNAL)
|
||||||
|
public class RestNodesCapabilitiesAction extends BaseRestHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Route> routes() {
|
||||||
|
return List.of(new Route(RestRequest.Method.GET, "/_capabilities"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> supportedQueryParameters() {
|
||||||
|
return Set.of("timeout", "method", "path", "parameters", "capabilities");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "nodes_capabilities_action";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException {
|
||||||
|
NodesCapabilitiesRequest r = new NodesCapabilitiesRequest().timeout(request.paramAsTime("timeout", null))
|
||||||
|
.method(RestRequest.Method.valueOf(request.param("method", "GET")))
|
||||||
|
.path(URLDecoder.decode(request.param("path"), StandardCharsets.UTF_8))
|
||||||
|
.parameters(request.paramAsStringArray("parameters", Strings.EMPTY_ARRAY))
|
||||||
|
.capabilities(request.paramAsStringArray("capabilities", Strings.EMPTY_ARRAY))
|
||||||
|
.restApiVersion(request.getRestApiVersion());
|
||||||
|
|
||||||
|
return channel -> client.admin().cluster().nodesCapabilities(r, new NodesResponseRestListener<>(channel));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canTripCircuitBreaker() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -341,6 +341,7 @@ public class Constants {
|
||||||
"cluster:monitor/update/health/info",
|
"cluster:monitor/update/health/info",
|
||||||
"cluster:monitor/ingest/geoip/stats",
|
"cluster:monitor/ingest/geoip/stats",
|
||||||
"cluster:monitor/main",
|
"cluster:monitor/main",
|
||||||
|
"cluster:monitor/nodes/capabilities",
|
||||||
"cluster:monitor/nodes/data_tier_usage",
|
"cluster:monitor/nodes/data_tier_usage",
|
||||||
"cluster:monitor/nodes/hot_threads",
|
"cluster:monitor/nodes/hot_threads",
|
||||||
"cluster:monitor/nodes/info",
|
"cluster:monitor/nodes/info",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue