diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index 5e26d96c4ca17..3e45f715395cd 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -3607,6 +3607,11 @@
+
+
+
+
+
diff --git a/server/src/main/java/org/elasticsearch/action/ActionModule.java b/server/src/main/java/org/elasticsearch/action/ActionModule.java
index b550755ce7bdd..1f2a52835b503 100644
--- a/server/src/main/java/org/elasticsearch/action/ActionModule.java
+++ b/server/src/main/java/org/elasticsearch/action/ActionModule.java
@@ -445,6 +445,7 @@ public class ActionModule extends AbstractModule {
private final SettingsFilter settingsFilter;
private final List actionPlugins;
private final Map> actions;
+ private static Map> debugActions; //testonly
private final ActionFilters actionFilters;
private final AutoCreateIndex autoCreateIndex;
private final DestructiveOperations destructiveOperations;
@@ -485,6 +486,9 @@ public ActionModule(
this.actionPlugins = actionPlugins;
this.threadPool = threadPool;
actions = setupActions(actionPlugins);
+ // if("true".equalsIgnoreCase(System.getProperty("testonly"))) {
+ debugActions = actions;
+ // }
actionFilters = setupActionFilters(actionPlugins);
autoCreateIndex = new AutoCreateIndex(settings, clusterSettings, indexNameExpressionResolver, systemIndices);
destructiveOperations = new DestructiveOperations(settings, clusterSettings);
diff --git a/server/src/main/java/org/elasticsearch/action/IndicesRequest.java b/server/src/main/java/org/elasticsearch/action/IndicesRequest.java
index 20913148fd9b3..687384cae128e 100644
--- a/server/src/main/java/org/elasticsearch/action/IndicesRequest.java
+++ b/server/src/main/java/org/elasticsearch/action/IndicesRequest.java
@@ -90,4 +90,6 @@ interface RemoteClusterShardRequest extends IndicesRequest {
*/
Collection shards();
}
+
+
}
diff --git a/x-pack/plugin/security/qa/consistency-checks/build.gradle b/x-pack/plugin/security/qa/consistency-checks/build.gradle
index 6fa3deb773e4c..fc5f919e23c19 100644
--- a/x-pack/plugin/security/qa/consistency-checks/build.gradle
+++ b/x-pack/plugin/security/qa/consistency-checks/build.gradle
@@ -1,27 +1,43 @@
apply plugin: 'elasticsearch.standalone-test'
-dependencies {
+//since we are placing everything on the classpath for this test we need to force versions to converge or simply ignore them
+configurations.all {
+ resolutionStrategy {
+ force "org.slf4j:slf4j-api:2.0.10"
+ force "commons-codec:commons-codec:1.16.1"
+ force "org.slf4j:slf4j-nop:2.0.10"
+ force "org.apache.commons:commons-lang3:3.14.0"
+ force "com.fasterxml.jackson.core:jackson-databind:2.15.0"
+ force "com.fasterxml.jackson.core:jackson-core:2.15.4"
+ force "com.fasterxml.jackson.core:jackson-annotations:2.15.4"
+ force "joda-time:joda-time:2.10.14"
+ force "org.ow2.asm:asm:8.0.1"
+ force "com.sun.mail:jakarta.mail:1.6.4"
+
+ exclude group: 'commons-logging', module: 'commons-logging'
+ exclude group: 'jakarta.xml.bind', module: 'jakarta.xml.bind-api'
+ exclude group: 'jakarta.activation', module: 'jakarta.activation-api'
+ }
+}
+
+dependencies {
+ testImplementation "org.reflections:reflections:0.10.2"
+ testImplementation "org.javassist:javassist:3.30.2-GA"
testImplementation(testArtifact(project(xpackModule('core'))))
- testImplementation project(path: ':modules:ingest-common')
- testImplementation project(path: ':modules:data-streams')
- testImplementation project(path: ':modules:lang-mustache')
- testImplementation project(path: ':modules:rank-eval')
- testImplementation project(path: ':modules:reindex')
- testImplementation project(path: xpackModule('analytics'))
- testImplementation project(path: xpackModule('async-search'))
- testImplementation project(path: xpackModule('autoscaling'))
- testImplementation project(path: xpackModule('ccr'))
- testImplementation project(path: xpackModule('downsample'))
- testImplementation project(path: xpackModule('eql'))
- testImplementation project(path: xpackModule('esql'))
- testImplementation project(path: xpackModule('esql-core'))
- testImplementation project(path: xpackModule('frozen-indices'))
- testImplementation project(path: xpackModule('graph'))
- testImplementation project(path: xpackModule('ilm'))
- testImplementation project(path: xpackModule('inference'))
- testImplementation project(path: xpackModule('profiling'))
- testImplementation project(path: xpackModule('rollup'))
- testImplementation project(path: xpackModule('slm'))
- testImplementation project(path: xpackModule('sql'))
+
+ project.rootProject.subprojects.findAll { it.parent.path == ':modules' }.each { Project module ->
+ testImplementation module
+ }
+
+ Project xpack = project(':x-pack:plugin')
+ xpack.subprojects.findAll { it.parent == xpack }.each { Project xpackModule ->
+ testImplementation xpackModule
+ }
+
+}
+
+tasks.named("test").configure {
+ systemProperty 'tests.security.manager', 'false'
+ //TODO: add system property for registry debug
}
diff --git a/x-pack/plugin/security/qa/consistency-checks/src/test/java/org/elasticsearch/xpack/security/CrossClusterShardTests.java b/x-pack/plugin/security/qa/consistency-checks/src/test/java/org/elasticsearch/xpack/security/CrossClusterShardTests.java
index d9e870b031877..72f85134d331d 100644
--- a/x-pack/plugin/security/qa/consistency-checks/src/test/java/org/elasticsearch/xpack/security/CrossClusterShardTests.java
+++ b/x-pack/plugin/security/qa/consistency-checks/src/test/java/org/elasticsearch/xpack/security/CrossClusterShardTests.java
@@ -7,18 +7,25 @@
package org.elasticsearch.xpack.security;
+import org.elasticsearch.action.ActionModule;
+import org.elasticsearch.action.ActionType;
+import org.elasticsearch.action.IndicesRequest;
+import org.elasticsearch.action.RemoteClusterActionType;
import org.elasticsearch.action.admin.cluster.shards.TransportClusterSearchShardsAction;
import org.elasticsearch.action.search.TransportSearchShardsAction;
import org.elasticsearch.action.support.TransportAction;
import org.elasticsearch.action.support.single.shard.TransportSingleShardAction;
-import org.elasticsearch.common.inject.Binding;
import org.elasticsearch.common.inject.TypeLiteral;
import org.elasticsearch.datastreams.DataStreamsPlugin;
import org.elasticsearch.index.rankeval.RankEvalPlugin;
import org.elasticsearch.ingest.IngestTestPlugin;
import org.elasticsearch.ingest.common.IngestCommonPlugin;
import org.elasticsearch.node.Node;
+import org.elasticsearch.painless.PainlessPlugin;
+import org.elasticsearch.plugins.ActionPlugin;
import org.elasticsearch.plugins.Plugin;
+import org.elasticsearch.plugins.SystemIndexPlugin;
+import org.elasticsearch.plugins.interceptor.RestServerActionPlugin;
import org.elasticsearch.reindex.ReindexPlugin;
import org.elasticsearch.script.mustache.MustachePlugin;
import org.elasticsearch.test.ESSingleNodeTestCase;
@@ -26,6 +33,8 @@
import org.elasticsearch.xpack.autoscaling.Autoscaling;
import org.elasticsearch.xpack.ccr.Ccr;
import org.elasticsearch.xpack.core.LocalStateCompositeXPackPlugin;
+import org.elasticsearch.xpack.core.XPackClientPlugin;
+import org.elasticsearch.xpack.core.XPackPlugin;
import org.elasticsearch.xpack.core.security.action.apikey.CrossClusterApiKeyRoleDescriptorBuilder;
import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege;
import org.elasticsearch.xpack.downsample.Downsample;
@@ -41,14 +50,21 @@
import org.elasticsearch.xpack.search.AsyncSearch;
import org.elasticsearch.xpack.slm.SnapshotLifecycle;
import org.elasticsearch.xpack.sql.plugin.SqlPlugin;
+import org.reflections.Reflections;
+import org.reflections.scanners.Scanners;
+import org.reflections.util.ConfigurationBuilder;
+import java.lang.reflect.Field;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
+import java.lang.reflect.Constructor;
import java.util.HashSet;
import java.util.List;
-import java.util.Locale;
+import java.util.Map;
import java.util.Set;
-import java.util.function.Predicate;
+import java.util.stream.Collectors;
public class CrossClusterShardTests extends ESSingleNodeTestCase {
@@ -69,97 +85,349 @@ public class CrossClusterShardTests extends ESSingleNodeTestCase {
TransportSingleShardAction.class
);
+ private static final Set> ignoredPlugins = Set.of(
+// RestServerActionPlugin.class,
+ PainlessPlugin.class //has extendtino that causes issues,
+// XPackPlugin.class, //using xpacklocal plugin instead
+// XPackClientPlugin.class
+ );
+
@Override
+ @SuppressWarnings("unchecked")
protected Collection> getPlugins() {
final ArrayList> plugins = new ArrayList<>(super.getPlugins());
- plugins.addAll(
- List.of(
- LocalStateCompositeXPackPlugin.class,
- AnalyticsPlugin.class,
- AsyncSearch.class,
- Autoscaling.class,
- Ccr.class,
- DataStreamsPlugin.class,
- Downsample.class,
- EqlPlugin.class,
- EsqlPlugin.class,
- FrozenIndices.class,
- Graph.class,
- IndexLifecycle.class,
- InferencePlugin.class,
- IngestCommonPlugin.class,
- IngestTestPlugin.class,
- MustachePlugin.class,
- ProfilingPlugin.class,
- RankEvalPlugin.class,
- ReindexPlugin.class,
- Rollup.class,
- SnapshotLifecycle.class,
- SqlPlugin.class
- )
+
+ Reflections reflections = new Reflections(
+ new ConfigurationBuilder().forPackages("org.elasticsearch").addScanners(Scanners.SubTypes)
);
+
+
+ Set> actionPlugins = reflections.getSubTypesOf(ActionPlugin.class);
+ //actionPlugins.stream().filter(plugin -> ignoredPlugins.contains(RestServerActionPlugin.class) == false).forEach(plugin -> {
+ actionPlugins.stream().forEach(plugin -> {
+
+ if(ignoredPlugins.contains(plugin)){
+ System.out.println("** ignoring: " + plugin);
+ }else {
+
+ boolean hasDefaultConstructor = false;
+ Constructor>[] constructors = plugin.getDeclaredConstructors();
+ for (Constructor> constructor : constructors) {
+ if (constructor.getParameterCount() == 0) {
+ hasDefaultConstructor =true;
+ }
+ }
+
+ if(hasDefaultConstructor) {
+
+ if(plugin.getCanonicalName().contains("xpack")){
+ System.out.println("** ignoring (handled by xpack local): " + plugin);
+ }else {
+
+ System.out.println("** adding: " + plugin);
+ plugins.add((Class extends Plugin>) plugin);
+ }
+ } else {
+ System.out.println("** ignoring (no default constructor): " + plugin);
+ }
+ }
+
+ });
+
+ plugins.add(LocalStateCompositeXPackPlugin.class);
+
+
+ // plugins.addAll(actionPlugins);
+// List.of(
+// LocalStateCompositeXPackPlugin.class,
+// AnalyticsPlugin.class,
+// AsyncSearch.class,
+// Autoscaling.class,
+// Ccr.class,
+// DataStreamsPlugin.class,
+// Downsample.class,
+// EqlPlugin.class,
+// EsqlPlugin.class,
+// FrozenIndices.class,
+// Graph.class,
+// IndexLifecycle.class,
+// InferencePlugin.class,
+// IngestCommonPlugin.class,
+// IngestTestPlugin.class,
+// MustachePlugin.class,
+// ProfilingPlugin.class,
+// RankEvalPlugin.class,
+// ReindexPlugin.class,
+// Rollup.class,
+// SnapshotLifecycle.class,
+// SqlPlugin.class
+// )
+ // );
return plugins;
}
- @SuppressWarnings("rawtypes")
+
+
+
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
public void testCheckForNewShardLevelTransportActions() throws Exception {
Node node = node();
- List> transportActionBindings = node.injector().findBindingsByType(TypeLiteral.get(TransportAction.class));
+ Reflections reflections = new Reflections(
+ new ConfigurationBuilder().forPackages("org.elasticsearch").addScanners(Scanners.SubTypes)
+ );
+
+ // Find all subclasses of IndicesRequest
+ Set> indicesRequest = reflections.getSubTypesOf(IndicesRequest.class);
+
+ //Find all transport actions instances from Guice (depends on the set of plugins this test is configured to use)
+ List allTransportActions = node.injector()
+ .findBindingsByType(TypeLiteral.get(TransportAction.class)).stream()
+ .map(binding -> binding.getProvider().get())
+ .toList();
+
+
+ for (TransportAction transportAction : allTransportActions) {
+ System.out.println("transportAction: " + transportAction.getClass());
+ }
+
+
+ // Find the ActionType -> TransportAction mapping, both share the same action name
+ Field field = ActionModule.class.getDeclaredField("debugActions");
+ field.setAccessible(true);
+ Map> actionRegistry = (Map>) field.get(null);
+
+ // Find the TransportActions that can go across clusters based on the ActionType having a RemoteClusterActionType
+ Set crossClusterActions = new HashSet<>();
+ for (TransportAction transportAction : allTransportActions) {
+ //read the parameters from the transport action superclass
+ ActionType actionType = actionRegistry.get(transportAction.actionName).getAction();
+ crossClusterActions.add(actionType);
+ if(hasMemberWithType(actionType.getClass(), RemoteClusterActionType.class)){
+ }
+ }
+
+
+
+ for (ActionType actionType : crossClusterActions) {
+ System.out.println("actionType: " + actionType.getClass());
+ }
+ // allowedRemoteClusterRequestTypes.add(getRequestTypeName(transportAction.getClass().getGenericSuperclass()));
+
+ // Find the permissions allowed for RCS 2.0
Set crossClusterPrivilegeNames = new HashSet<>();
crossClusterPrivilegeNames.addAll(List.of(CrossClusterApiKeyRoleDescriptorBuilder.CCS_INDICES_PRIVILEGE_NAMES));
crossClusterPrivilegeNames.addAll(List.of(CrossClusterApiKeyRoleDescriptorBuilder.CCR_INDICES_PRIVILEGE_NAMES));
- List shardActions = transportActionBindings.stream()
+ // Find the allowed cluster action names based on the permissions for RCS 2.0
+ Set allowedRemoteClusterActionNames = actionRegistry.keySet().stream()
+ .filter(actionName -> IndexPrivilege.get(crossClusterPrivilegeNames).predicate().test(actionName)).collect(Collectors.toSet());
+
+
+
+
+ // Get a reference to all the TransportAction instances that are allowed by RCS 2.0
+ List allowedRemoteClusterActions = node.injector()
+ .findBindingsByType(TypeLiteral.get(TransportAction.class)).stream()
.map(binding -> binding.getProvider().get())
- .filter(action -> IndexPrivilege.get(crossClusterPrivilegeNames).predicate().test(action.actionName))
- .filter(this::actionIsLikelyShardAction)
- .map(action -> action.actionName)
+ .filter(action -> allowedRemoteClusterActionNames.contains(action.actionName))
.toList();
- List actionsNotOnAllowlist = shardActions.stream().filter(Predicate.not(MANUALLY_CHECKED_SHARD_ACTIONS::contains)).toList();
- if (actionsNotOnAllowlist.isEmpty() == false) {
- fail("""
- If this test fails, you likely just added a transport action, probably with `shard` in the name. Transport actions which
- operate on shards directly and can be used across clusters must meet some additional requirements in order to be
- handled correctly by all Elasticsearch infrastructure, so please make sure you have read the javadoc on the
- IndicesRequest.RemoteClusterShardRequest interface and implemented it if appropriate and not already appropriately
- implemented by a supertype, then add the name (as in "indices:data/read/get") of your new transport action to
- MANUALLY_CHECKED_SHARD_ACTIONS above. Found actions not in allowlist:
- """ + actionsNotOnAllowlist);
+
+ // reverse the actionRegistry map to key by ActionHandler
+
+
+
+ //Ignore any indices requests that are already marked with the RemoteClusterShardRequest interface
+ indicesRequest.removeAll(reflections.getSubTypesOf(IndicesRequest.RemoteClusterShardRequest.class));
+
+
+
+ //Type erasure makes the relationship between the transport action and the request type difficult to determine
+
+ // List>
+// List allTransportActionNames = transportActionBindings.stream()
+// .map(binding -> binding.getProvider().get())
+// .map(action -> action.actionName)
+// .toList();
+
+
+
+// List> transportActionBindings = node.injector().findBindingsByType(TypeLiteral.get(TransportAction.class));
+// Set crossClusterPrivilegeNames = new HashSet<>();
+// crossClusterPrivilegeNames.addAll(List.of(CrossClusterApiKeyRoleDescriptorBuilder.CCS_INDICES_PRIVILEGE_NAMES));
+// crossClusterPrivilegeNames.addAll(List.of(CrossClusterApiKeyRoleDescriptorBuilder.CCR_INDICES_PRIVILEGE_NAMES));
+// List allTransportActionNames = transportActionBindings.stream()
+// .map(binding -> binding.getProvider().get())
+// .map(action -> action.actionName)
+// .toList();
+//
+
+
+// // Find subclasses of RemoteClusterActionType
+// Set> remoteClusterActionType = reflections.getSubTypesOf(RemoteClusterActionType.class);
+// for (Class extends RemoteClusterActionType> clazz : remoteClusterActionType) {
+// System.out.println("**************** " + clazz.getName());
+// }
+
+
+
+
+// for (Class extends IndicesRequest> clazz : candidatesToAddMarkerInterface) {
+//
+// System.out.println("**************** " + clazz.getName());
+//
+//
+// }
+
+
+
+
+
+// // Filter down that are likely to be missing the RemoteClusterShardRequest interface
+// Class remoteClusterShardRequestClass = IndicesRequest.RemoteClusterShardRequest.class;
+// Class indicesRequest = IndicesRequest.class;
+// Set< Class extends TransportRequest>> indicesRequestsWithoutShardInterface = new HashSet<>();
+//
+//
+// for (Class extends TransportRequest> subclassWithShard : subclassesWithShards) {
+// if (indicesRequest.isAssignableFrom(subclassWithShard)) {
+// if (remoteClusterShardRequestClass.isAssignableFrom(subclassWithShard) == false) {
+// // indices request does not have shard level interface - candidates to add the interface
+// System.out.println(subclassWithShard.getCanonicalName() + ": does not have shard level interface");
+// indicesRequestsWithoutShardInterface.add(subclassWithShard);
+// }
+// }
+// }
+//
+// // Find all subclasses of TransportRequest
+// Class transportRequestClass = TransportRequest.class;
+// Reflections reflections = new Reflections(
+// new ConfigurationBuilder().forPackages("org.elasticsearch").addScanners(Scanners.SubTypes)
+// );
+// Set> subclasses = reflections.getSubTypesOf(transportRequestClass);
+//
+//
+//
+//
+//
+// // Get a reference to all transport actions (on classpath)
+// List> transportActionBindings = node.injector().findBindingsByType(TypeLiteral.get(TransportAction.class));
+// Set crossClusterPrivilegeNames = new HashSet<>();
+// crossClusterPrivilegeNames.addAll(List.of(CrossClusterApiKeyRoleDescriptorBuilder.CCS_INDICES_PRIVILEGE_NAMES));
+// crossClusterPrivilegeNames.addAll(List.of(CrossClusterApiKeyRoleDescriptorBuilder.CCR_INDICES_PRIVILEGE_NAMES));
+// List allTransportActionNames = transportActionBindings.stream()
+// .map(binding -> binding.getProvider().get())
+// .map(action -> action.actionName)
+// .toList();
+//
+// // Find the transport actions names that can go cross cluters
+// RemoteClusterActionType
+
+
+// List shardActions = transportActionBindings.stream()
+// .map(binding -> binding.getProvider().get())
+// .filter(action -> IndexPrivilege.get(crossClusterPrivilegeNames).predicate().test(action.actionName))
+// .filter(this::actionIsLikelyShardAction)
+// .map(action -> action.actionName)
+// .toList();
+//
+// List actionsNotOnAllowlist = shardActions.stream().filter(Predicate.not(MANUALLY_CHECKED_SHARD_ACTIONS::contains)).toList();
+// if (actionsNotOnAllowlist.isEmpty() == false) {
+// fail("""
+// If this test fails, you likely just added a transport action, probably with `shard` in the name. Transport actions which
+// operate on shards directly and can be used across clusters must meet some additional requirements in order to be
+// handled correctly by all Elasticsearch infrastructure, so please make sure you have read the javadoc on the
+// IndicesRequest.RemoteClusterShardRequest interface and implemented it if appropriate and not already appropriately
+// implemented by a supertype, then add the name (as in "indices:data/read/get") of your new transport action to
+// MANUALLY_CHECKED_SHARD_ACTIONS above. Found actions not in allowlist:
+// """ + actionsNotOnAllowlist);
+// }
+//
+// // Also make sure the allowlist stays up to date and doesn't have any unnecessary entries.
+// List actionsOnAllowlistNotFound = MANUALLY_CHECKED_SHARD_ACTIONS.stream()
+// .filter(Predicate.not(shardActions::contains))
+// .toList();
+// if (actionsOnAllowlistNotFound.isEmpty() == false) {
+// fail(
+// "Some actions were on the allowlist but not found in the list of cross-cluster capable transport actions, please remove "
+// + "these from MANUALLY_CHECKED_SHARD_ACTIONS if they have been removed from Elasticsearch: "
+// + actionsOnAllowlistNotFound
+// );
+// }
+
+
+
+ // // Find any IndicesRequest that have methods related to shards, these are the candidate requests for the marker interface
+// Set> candidatesToAddMarkerInterface = new HashSet<>();
+// Set methodNames = Set.of("shard", "shards", "getShard", "getShards", "shardId", "shardIds", "getShardId", "getShardIds");
+// for (Class extends IndicesRequest> clazz : indicesRequest) {
+// for (Method method : clazz.getDeclaredMethods()) {
+// for (String methodName : methodNames) {
+// if (method.getName().equals(methodName)) {
+// candidatesToAddMarkerInterface.add((Class extends IndicesRequest>) method.getDeclaringClass());
+// }
+// }
+// }
+// }
+
+
+ }
+//
+// /**
+// * Getting to the actual request classes themselves is made difficult by the design of Elasticsearch's transport
+// * protocol infrastructure combined with JVM type erasure. Therefore, we resort to a crude heuristic here.
+// * @param transportAction The transportport action to be checked.
+// * @return True if the action is suspected of being an action which may operate on shards directly.
+// */
+// private boolean actionIsLikelyShardAction(TransportAction, ?> transportAction) {
+// Class> clazz = transportAction.getClass();
+// Set> classHeirarchy = new HashSet<>();
+// while (clazz != TransportAction.class) {
+// classHeirarchy.add(clazz);
+// clazz = clazz.getSuperclass();
+// }
+// boolean hasCheckedSuperclass = classHeirarchy.stream().anyMatch(clz -> CHECKED_ABSTRACT_CLASSES.contains(clz));
+// boolean shardInClassName = classHeirarchy.stream().anyMatch(clz -> clz.getName().toLowerCase(Locale.ROOT).contains("shard"));
+// return hasCheckedSuperclass == false
+// && (shardInClassName
+// || transportAction.actionName.toLowerCase(Locale.ROOT).contains("shard")
+// || transportAction.actionName.toLowerCase(Locale.ROOT).contains("[s]"));
+// }
+
+ private static String getRequestTypeName(Type type) {
+ if (type instanceof ParameterizedType parameterizedType) {
+ Type[] typeArguments = parameterizedType.getActualTypeArguments();
+ return getRequestTypeName(typeArguments[0]);
+ } else if (type instanceof Class) {
+ return ((Class>) type).getName();
}
+ throw new RuntimeException("Unknown type: " + type);
+ }
- // Also make sure the allowlist stays up to date and doesn't have any unnecessary entries.
- List actionsOnAllowlistNotFound = MANUALLY_CHECKED_SHARD_ACTIONS.stream()
- .filter(Predicate.not(shardActions::contains))
- .toList();
- if (actionsOnAllowlistNotFound.isEmpty() == false) {
- fail(
- "Some actions were on the allowlist but not found in the list of cross-cluster capable transport actions, please remove "
- + "these from MANUALLY_CHECKED_SHARD_ACTIONS if they have been removed from Elasticsearch: "
- + actionsOnAllowlistNotFound
- );
+ private static boolean hasMemberWithType(Class> clazz, Class> targetType) {
+ Field[] fields = clazz.getDeclaredFields();
+ for (Field field : fields) {
+ if (isTypeMatching(field.getGenericType(), targetType)) {
+ return true;
+ }
+ }
+ // Check superclass recursively
+ Class> superclass = clazz.getSuperclass();
+ if (superclass != null && superclass != Object.class) {
+ hasMemberWithType(superclass, targetType);
}
+ return false;
}
- /**
- * Getting to the actual request classes themselves is made difficult by the design of Elasticsearch's transport
- * protocol infrastructure combined with JVM type erasure. Therefore, we resort to a crude heuristic here.
- * @param transportAction The transportport action to be checked.
- * @return True if the action is suspected of being an action which may operate on shards directly.
- */
- private boolean actionIsLikelyShardAction(TransportAction, ?> transportAction) {
- Class> clazz = transportAction.getClass();
- Set> classHeirarchy = new HashSet<>();
- while (clazz != TransportAction.class) {
- classHeirarchy.add(clazz);
- clazz = clazz.getSuperclass();
+ private static boolean isTypeMatching(Type type, Class> targetType) {
+ if (type instanceof ParameterizedType) {
+ ParameterizedType parameterizedType = (ParameterizedType) type;
+ return parameterizedType.getRawType().equals(targetType);
+ } else if (type instanceof Class) {
+ return type.equals(targetType);
}
- boolean hasCheckedSuperclass = classHeirarchy.stream().anyMatch(clz -> CHECKED_ABSTRACT_CLASSES.contains(clz));
- boolean shardInClassName = classHeirarchy.stream().anyMatch(clz -> clz.getName().toLowerCase(Locale.ROOT).contains("shard"));
- return hasCheckedSuperclass == false
- && (shardInClassName
- || transportAction.actionName.toLowerCase(Locale.ROOT).contains("shard")
- || transportAction.actionName.toLowerCase(Locale.ROOT).contains("[s]"));
+ return false;
}
}