Skip to content

Commit

Permalink
Add a capabilities API to check node and cluster capabilities (elasti…
Browse files Browse the repository at this point in the history
…c#106820)

This adds a /_capabilities rest endpoint for checking the capabilities of a cluster - what endpoints, parameters, and endpoint capabilities the cluster supports
  • Loading branch information
thecoop authored May 8, 2024
1 parent 8864058 commit e7350dc
Show file tree
Hide file tree
Showing 15 changed files with 504 additions and 0 deletions.
5 changes: 5 additions & 0 deletions docs/changelog/106820.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 106820
summary: Add a capabilities API to check node and cluster capabilities
area: Infra/REST API
type: feature
issues: []
11 changes: 11 additions & 0 deletions libs/core/src/main/java/org/elasticsearch/core/RestApiVersion.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,15 @@ public static Predicate<RestApiVersion> onOrAfter(RestApiVersion 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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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));*/
}
}
1 change: 1 addition & 0 deletions server/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
exports org.elasticsearch.action.admin.cluster.desirednodes;
exports org.elasticsearch.action.admin.cluster.health;
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.info;
exports org.elasticsearch.action.admin.cluster.node.reload;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.elasticsearch.action.admin.cluster.migration.PostFeatureUpgradeAction;
import org.elasticsearch.action.admin.cluster.migration.TransportGetFeatureUpgradeStatusAction;
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.info.TransportNodesInfoAction;
import org.elasticsearch.action.admin.cluster.node.reload.TransportNodesReloadSecureSettingsAction;
Expand Down Expand Up @@ -284,6 +285,7 @@
import org.elasticsearch.rest.action.admin.cluster.RestGetStoredScriptAction;
import org.elasticsearch.rest.action.admin.cluster.RestGetTaskAction;
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.RestNodesInfoAction;
import org.elasticsearch.rest.action.admin.cluster.RestNodesStatsAction;
Expand Down Expand Up @@ -616,6 +618,7 @@ public <Request extends ActionRequest, Response extends ActionResponse> void reg

actions.register(TransportNodesInfoAction.TYPE, TransportNodesInfoAction.class);
actions.register(TransportRemoteInfoAction.TYPE, TransportRemoteInfoAction.class);
actions.register(TransportNodesCapabilitiesAction.TYPE, TransportNodesCapabilitiesAction.class);
actions.register(RemoteClusterNodesAction.TYPE, RemoteClusterNodesAction.TransportAction.class);
actions.register(TransportNodesStatsAction.TYPE, TransportNodesStatsAction.class);
actions.register(TransportNodesUsageAction.TYPE, TransportNodesUsageAction.class);
Expand Down Expand Up @@ -833,6 +836,7 @@ public void initRestHandlers(Supplier<DiscoveryNodes> nodesInCluster, Predicate<
registerHandler.accept(new RestClearVotingConfigExclusionsAction());
registerHandler.accept(new RestNodesInfoAction(settingsFilter));
registerHandler.accept(new RestRemoteClusterInfoAction());
registerHandler.accept(new RestNodesCapabilitiesAction());
registerHandler.accept(new RestNodesStatsAction());
registerHandler.accept(new RestNodesUsageAction());
registerHandler.accept(new RestNodesHotThreadsAction());
Expand Down Expand Up @@ -1029,6 +1033,7 @@ public void initRestHandlers(Supplier<DiscoveryNodes> nodesInCluster, Predicate<

@Override
protected void configure() {
bind(RestController.class).toInstance(restController);
bind(ActionFilters.class).toInstance(actionFilters);
bind(DestructiveOperations.class).toInstance(destructiveOperations);
bind(new TypeLiteral<RequestValidators<PutMappingRequest>>() {
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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());
}
}
Loading

0 comments on commit e7350dc

Please sign in to comment.