From 04d0e2979fec4d7d260bf6ddebe2382d6e60a051 Mon Sep 17 00:00:00 2001
From: Nikola Grcevski
Date: Thu, 16 Jun 2022 17:28:54 -0400
Subject: [PATCH 1/8] Add OperatorHandler and SPI
---
server/src/main/java/module-info.java | 1 +
.../operator/OperatorHandler.java | 118 ++++++++++++++++++
.../operator/OperatorHandlerProvider.java | 25 ++++
.../operator/TransformState.java | 21 ++++
.../elasticsearch/plugins/PluginsService.java | 28 ++++-
.../elasticsearch/bootstrap/security.policy | 3 +
.../plugins/PluginsServiceTests.java | 88 +++++++++++++
.../plugins/MockPluginsService.java | 2 +-
8 files changed, 284 insertions(+), 2 deletions(-)
create mode 100644 server/src/main/java/org/elasticsearch/operator/OperatorHandler.java
create mode 100644 server/src/main/java/org/elasticsearch/operator/OperatorHandlerProvider.java
create mode 100644 server/src/main/java/org/elasticsearch/operator/TransformState.java
diff --git a/server/src/main/java/module-info.java b/server/src/main/java/module-info.java
index f0b4ccd3ff049..0ae63370a22d4 100644
--- a/server/src/main/java/module-info.java
+++ b/server/src/main/java/module-info.java
@@ -272,6 +272,7 @@
exports org.elasticsearch.monitor.os;
exports org.elasticsearch.monitor.process;
exports org.elasticsearch.node;
+ exports org.elasticsearch.operator;
exports org.elasticsearch.persistent;
exports org.elasticsearch.persistent.decider;
exports org.elasticsearch.plugins;
diff --git a/server/src/main/java/org/elasticsearch/operator/OperatorHandler.java b/server/src/main/java/org/elasticsearch/operator/OperatorHandler.java
new file mode 100644
index 0000000000000..29ed71c3c29f4
--- /dev/null
+++ b/server/src/main/java/org/elasticsearch/operator/OperatorHandler.java
@@ -0,0 +1,118 @@
+/*
+ * 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.operator;
+
+import org.elasticsearch.ElasticsearchGenerationException;
+import org.elasticsearch.action.ActionRequestValidationException;
+import org.elasticsearch.action.support.master.MasterNodeRequest;
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.xcontent.XContentBuilder;
+import org.elasticsearch.xcontent.XContentFactory;
+import org.elasticsearch.xcontent.XContentParser;
+import org.elasticsearch.xcontent.XContentParserConfiguration;
+import org.elasticsearch.xcontent.XContentType;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * Updating cluster state in operator mode, for file based settings and modules/plugins, requires
+ * that we have a separate update handler interface that is different than REST handlers. This interface declares
+ * the basic contract for implementing cluster state update handlers in operator mode.
+ */
+public interface OperatorHandler {
+ String CONTENT = "content";
+
+ /**
+ * The operator handler name is a unique identifier that is matched to a section in a
+ * cluster state update content. The operator cluster state updates are done as a single
+ * cluster state update and the cluster state is typically supplied as a combined content,
+ * unlike the REST handlers. This name must match a desired content key name in the combined
+ * cluster state update, e.g. "ilm" or "cluster_settings" (for persistent cluster settings update).
+ *
+ * @return a String with the operator key name, e.g "ilm".
+ */
+ String name();
+
+ /**
+ * The transform method of the operator handler should apply the necessary changes to
+ * the cluster state as it normally would in a REST handler. One difference is that the
+ * transform method in an operator handler must perform all CRUD operations of the cluster
+ * state in one go. For that reason, we supply a wrapper class to the cluster state called
+ * {@link TransformState}, which contains the current cluster state as well as any previous keys
+ * set by this handler on prior invocation.
+ *
+ * @param source The parsed information specific to this handler from the combined cluster state content
+ * @param prevState The previous cluster state and keys set by this handler (if any)
+ * @return The modified state and the current keys set by this handler
+ * @throws Exception
+ */
+ TransformState transform(Object source, TransformState prevState) throws Exception;
+
+ /**
+ * Sometimes certain parts of the cluster state cannot be created/updated without previously
+ * setting other cluster state components, e.g. composable templates. Since the cluster state handlers
+ * are processed in random order by the OperatorClusterStateController, this method gives an opportunity
+ * to any operator handler to declare other operator handlers it depends on. Given dependencies exist,
+ * the OperatorClusterStateController will order those handlers such that the handlers that are dependent
+ * on are processed first.
+ *
+ * @return a collection of operator handler names
+ */
+ default Collection dependencies() {
+ return Collections.emptyList();
+ }
+
+ /**
+ * All implementations of OperatorHandler should call the request validate method, by calling this default
+ * implementation. To aid in any special validation logic that may need to be implemented by the operator handler
+ * we provide this convenience method.
+ *
+ * @param request the master node request that we base this operator handler on
+ */
+ default void validate(MasterNodeRequest> request) {
+ ActionRequestValidationException exception = request.validate();
+ if (exception != null) {
+ throw new IllegalStateException("Validation error", exception);
+ }
+ }
+
+ /**
+ * Convenience method to convert the incoming passed in input to the transform method into a map.
+ *
+ * @param input the input passed into the operator handler after parsing the content
+ * @return
+ */
+ @SuppressWarnings("unchecked")
+ default Map asMap(Object input) {
+ if (input instanceof Map, ?> source) {
+ return (Map) source;
+ }
+ throw new IllegalStateException("Unsupported " + name() + " request format");
+ }
+
+ /**
+ * Convenience method that creates a {@link XContentParser} from a content map so that it can be passed to
+ * existing REST based code for input parsing.
+ *
+ * @param config XContentParserConfiguration for this mapper
+ * @param source the operator content as a map
+ * @return
+ */
+ default XContentParser mapToXContentParser(XContentParserConfiguration config, Map source) {
+ try (XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON)) {
+ builder.map(source);
+ return XContentFactory.xContent(builder.contentType()).createParser(config, Strings.toString(builder));
+ } catch (IOException e) {
+ throw new ElasticsearchGenerationException("Failed to generate [" + source + "]", e);
+ }
+ }
+}
diff --git a/server/src/main/java/org/elasticsearch/operator/OperatorHandlerProvider.java b/server/src/main/java/org/elasticsearch/operator/OperatorHandlerProvider.java
new file mode 100644
index 0000000000000..0f168ea6e4d61
--- /dev/null
+++ b/server/src/main/java/org/elasticsearch/operator/OperatorHandlerProvider.java
@@ -0,0 +1,25 @@
+/*
+ * 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.operator;
+
+import java.util.Collection;
+
+/**
+ * SPI service interface for supplying OperatorHandler implementations to Elasticsearch
+ * from plugins/modules.
+ */
+public interface OperatorHandlerProvider {
+ /**
+ * Returns a list of OperatorHandler implementations that a module/plugin supplies.
+ * @see OperatorHandler
+ *
+ * @return a list of ${@link OperatorHandler}s
+ */
+ Collection> handlers();
+}
diff --git a/server/src/main/java/org/elasticsearch/operator/TransformState.java b/server/src/main/java/org/elasticsearch/operator/TransformState.java
new file mode 100644
index 0000000000000..a68ce7e1f2290
--- /dev/null
+++ b/server/src/main/java/org/elasticsearch/operator/TransformState.java
@@ -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
+ * 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.operator;
+
+import org.elasticsearch.cluster.ClusterState;
+
+import java.util.Set;
+
+/**
+ * A {@link ClusterState} wrapper used by the OperatorClusterStateController to pass the
+ * current state as well as previous keys set by an {@link OperatorHandler} to each transform
+ * step of the cluster state update.
+ *
+ */
+public record TransformState(ClusterState state, Set keys) {}
diff --git a/server/src/main/java/org/elasticsearch/plugins/PluginsService.java b/server/src/main/java/org/elasticsearch/plugins/PluginsService.java
index d5af93af4a2c4..4d72a2731492d 100644
--- a/server/src/main/java/org/elasticsearch/plugins/PluginsService.java
+++ b/server/src/main/java/org/elasticsearch/plugins/PluginsService.java
@@ -47,11 +47,13 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
+import java.util.ServiceLoader;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -281,7 +283,6 @@ private Map loadBundles(Set bundles) {
// package-private for test visibility
static void loadExtensions(Collection plugins) {
-
Map> extendingPluginsByName = plugins.stream()
.flatMap(t -> t.descriptor().getExtendedPlugins().stream().map(extendedPlugin -> Tuple.tuple(extendedPlugin, t.instance())))
.collect(Collectors.groupingBy(Tuple::v1, Collectors.mapping(Tuple::v2, Collectors.toList())));
@@ -295,6 +296,31 @@ static void loadExtensions(Collection plugins) {
}
}
+ // package private for testing
+ Iterator providersIterator(Class service, ClassLoader classLoader) {
+ return ServiceLoader.load(service, classLoader).iterator();
+ }
+
+ /**
+ * SPI convenience method that uses the {@link ServiceLoader} JDK class to load various SPI providers
+ * from plugins/modules. For example:
+ *
+ * var pluginHandlers = pluginsService.loadServiceProviders(OperatorHandlerProvider.class);
+ *
+ * @param service A templated service class to look for providers in plugins
+ * @return an immutable {@link List} of discovered providers in the plugins/modules
+ */
+ public List extends T> loadServiceProviders(Class service) {
+ List result = new ArrayList<>();
+ getClass().getModule().addUses(service);
+
+ for (LoadedPlugin pluginTuple : plugins()) {
+ providersIterator(service, pluginTuple.loader()).forEachRemaining(c -> result.add(c));
+ }
+
+ return Collections.unmodifiableList(result);
+ }
+
private static void loadExtensionsForPlugin(ExtensiblePlugin extensiblePlugin, List extendingPlugins) {
ExtensiblePlugin.ExtensionLoader extensionLoader = new ExtensiblePlugin.ExtensionLoader() {
@Override
diff --git a/server/src/main/resources/org/elasticsearch/bootstrap/security.policy b/server/src/main/resources/org/elasticsearch/bootstrap/security.policy
index f9b37f65538f2..b4c4500f62daa 100644
--- a/server/src/main/resources/org/elasticsearch/bootstrap/security.policy
+++ b/server/src/main/resources/org/elasticsearch/bootstrap/security.policy
@@ -170,4 +170,7 @@ grant {
// system memory on Linux systems affected by JDK bug (#66629)
permission java.io.FilePermission "/proc/meminfo", "read";
+
+ // needed for PluginsServiceTests
+ permission java.lang.RuntimePermission "createClassLoader";
};
diff --git a/server/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java b/server/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java
index 78f2d39c1eddc..b4b9c4bba3cf0 100644
--- a/server/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java
+++ b/server/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java
@@ -12,9 +12,14 @@
import org.apache.lucene.util.Constants;
import org.elasticsearch.Version;
import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.core.PathUtils;
+import org.elasticsearch.core.Strings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.env.TestEnvironment;
import org.elasticsearch.index.IndexModule;
+import org.elasticsearch.operator.OperatorHandler;
+import org.elasticsearch.operator.OperatorHandlerProvider;
+import org.elasticsearch.operator.TransformState;
import org.elasticsearch.test.ESTestCase;
import java.io.IOException;
@@ -27,6 +32,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Set;
@@ -38,6 +44,9 @@
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.sameInstance;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
@LuceneTestCase.SuppressFileSystems(value = "ExtrasFS")
public class PluginsServiceTests extends ESTestCase {
@@ -652,6 +661,66 @@ public void testThrowingConstructor() {
assertThat(e.getCause().getCause(), hasToString(containsString("test constructor failure")));
}
+ public void testLoadServiceProviders() throws ClassNotFoundException {
+ FakeClassLoader fakeClassLoader = new FakeClassLoader();
+ @SuppressWarnings("unchecked")
+ Class extends Plugin> fakePluginClass = (Class extends Plugin>) fakeClassLoader.findClass(FakePlugin.class.getName());
+
+ FakeClassLoader fakeClassLoader1 = new FakeClassLoader();
+ @SuppressWarnings("unchecked")
+ Class extends Plugin> fakePluginClass1 = (Class extends Plugin>) fakeClassLoader1.findClass(FakePlugin.class.getName());
+
+ assertFalse(fakePluginClass.getClassLoader().equals(fakePluginClass1.getClassLoader()));
+
+ OperatorHandlerProvider provider1 = () -> List.of(new OperatorHandler() {
+ @Override
+ public String name() {
+ return "integer";
+ }
+
+ @Override
+ public TransformState transform(Object source, TransformState prevState) {
+ return prevState;
+ }
+ });
+
+ OperatorHandlerProvider provider2 = () -> List.of(new OperatorHandler() {
+ @Override
+ public String name() {
+ return "string";
+ }
+
+ @Override
+ public TransformState transform(Object source, TransformState prevState) {
+ return prevState;
+ }
+ });
+ PluginsService service = spy(newMockPluginsService(List.of(fakePluginClass, fakePluginClass1)));
+
+ doReturn(List.of(provider1).iterator()).when(service).providersIterator(eq(OperatorHandlerProvider.class), eq(fakeClassLoader));
+ doReturn(List.of(provider2).iterator()).when(service).providersIterator(eq(OperatorHandlerProvider.class), eq(fakeClassLoader1));
+
+ List extends OperatorHandlerProvider> providers = service.loadServiceProviders(OperatorHandlerProvider.class);
+ assertEquals(2, providers.size());
+ List handlers = providers.stream().map(p -> p.handlers()).flatMap(l -> l.stream()).map(h -> h.name()).toList();
+
+ assertThat(handlers, containsInAnyOrder("string", "integer"));
+
+ doReturn(Collections.emptyList().iterator()).when(service)
+ .providersIterator(eq(OperatorHandlerProvider.class), eq(fakeClassLoader));
+ doReturn(List.of(provider2).iterator()).when(service).providersIterator(eq(OperatorHandlerProvider.class), eq(fakeClassLoader1));
+
+ assertEquals(1, service.loadServiceProviders(OperatorHandlerProvider.class).size());
+
+ doReturn(Collections.emptyList().iterator()).when(service)
+ .providersIterator(eq(OperatorHandlerProvider.class), eq(fakeClassLoader));
+ doReturn(Collections.emptyList().iterator()).when(service)
+ .providersIterator(eq(OperatorHandlerProvider.class), eq(fakeClassLoader1));
+
+ assertEquals(0, service.loadServiceProviders(OperatorHandlerProvider.class).size());
+
+ }
+
private static class TestExtensiblePlugin extends Plugin implements ExtensiblePlugin {
private List extensions;
@@ -691,4 +760,23 @@ public ThrowingConstructorExtension() {
throw new IllegalArgumentException("test constructor failure");
}
}
+
+ static class FakeClassLoader extends ClassLoader {
+ @Override
+ public Class> findClass(String name) throws ClassNotFoundException {
+ byte[] classBytes = fromFile(getClass().getClassLoader(), name);
+ return defineClass(name, classBytes, 0, classBytes.length);
+ }
+
+ private static byte[] fromFile(ClassLoader realLoader, String fileName) {
+ try {
+ InputStream input = realLoader.getResourceAsStream(
+ Strings.format("%s.class", fileName.replace(".", PathUtils.get(".").getFileSystem().getSeparator()))
+ );
+ return input.readAllBytes();
+ } catch (Exception x) {
+ throw new IllegalStateException("Error loading class", x);
+ }
+ }
+ }
}
diff --git a/test/framework/src/main/java/org/elasticsearch/plugins/MockPluginsService.java b/test/framework/src/main/java/org/elasticsearch/plugins/MockPluginsService.java
index 4f44e0be17249..f824201a93f4e 100644
--- a/test/framework/src/main/java/org/elasticsearch/plugins/MockPluginsService.java
+++ b/test/framework/src/main/java/org/elasticsearch/plugins/MockPluginsService.java
@@ -60,7 +60,7 @@ public MockPluginsService(Settings settings, Environment environment, Collection
if (logger.isTraceEnabled()) {
logger.trace("plugin loaded from classpath [{}]", pluginInfo);
}
- pluginsLoaded.add(new LoadedPlugin(pluginInfo, plugin));
+ pluginsLoaded.add(new LoadedPlugin(pluginInfo, plugin, pluginClass.getClassLoader(), ModuleLayer.boot()));
}
this.classpathPlugins = List.copyOf(pluginsLoaded);
From 4a67eded2f1deea8670e74e5e0659e52458534c2 Mon Sep 17 00:00:00 2001
From: Nikola Grcevski
Date: Mon, 27 Jun 2022 10:23:52 -0400
Subject: [PATCH 2/8] Better docs
---
.../java/org/elasticsearch/plugins/PluginsService.java | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/server/src/main/java/org/elasticsearch/plugins/PluginsService.java b/server/src/main/java/org/elasticsearch/plugins/PluginsService.java
index 125def8d79770..f72ad2c876979 100644
--- a/server/src/main/java/org/elasticsearch/plugins/PluginsService.java
+++ b/server/src/main/java/org/elasticsearch/plugins/PluginsService.java
@@ -301,10 +301,13 @@ Iterator providersIterator(Class service, ClassLoader classLoader) {
/**
* SPI convenience method that uses the {@link ServiceLoader} JDK class to load various SPI providers
- * from plugins/modules. For example:
+ * from plugins/modules.
*
- * var pluginHandlers = pluginsService.loadServiceProviders(OperatorHandlerProvider.class);
+ *
+ * For example:
*
+ * var pluginHandlers = pluginsService.loadServiceProviders(OperatorHandlerProvider.class);
+ *
* @param service A templated service class to look for providers in plugins
* @return an immutable {@link List} of discovered providers in the plugins/modules
*/
From 4517be80b0d5ab1a0961372a278fe73d278a6b98 Mon Sep 17 00:00:00 2001
From: Nikola Grcevski
Date: Mon, 27 Jun 2022 10:33:24 -0400
Subject: [PATCH 3/8] Fix wrong security policy file
---
.../resources/org/elasticsearch/bootstrap/security.policy | 4 +---
.../org/elasticsearch/bootstrap/test-framework.policy | 2 ++
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/server/src/main/resources/org/elasticsearch/bootstrap/security.policy b/server/src/main/resources/org/elasticsearch/bootstrap/security.policy
index b4c4500f62daa..24b4483964fc9 100644
--- a/server/src/main/resources/org/elasticsearch/bootstrap/security.policy
+++ b/server/src/main/resources/org/elasticsearch/bootstrap/security.policy
@@ -170,7 +170,5 @@ grant {
// system memory on Linux systems affected by JDK bug (#66629)
permission java.io.FilePermission "/proc/meminfo", "read";
-
- // needed for PluginsServiceTests
- permission java.lang.RuntimePermission "createClassLoader";
+ permission java.io.FilePermission "/proc/meminfo", "read";
};
diff --git a/server/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy b/server/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy
index 8907c4d463d3a..caa014c0c5059 100644
--- a/server/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy
+++ b/server/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy
@@ -125,4 +125,6 @@ grant codeBase "${codebase.netty-transport}" {
grant {
permission java.net.SocketPermission "127.0.0.1", "connect, resolve";
permission java.nio.file.LinkPermission "symbolic";
+ // needed for PluginsServiceTests
+ permission java.lang.RuntimePermission "createClassLoader";
};
From 6d6b0bf7f27340b5dad87dec9aa05b6ee3fe8694 Mon Sep 17 00:00:00 2001
From: Nikola Grcevski
Date: Mon, 27 Jun 2022 10:34:25 -0400
Subject: [PATCH 4/8] Fix line dup
---
.../main/resources/org/elasticsearch/bootstrap/security.policy | 1 -
1 file changed, 1 deletion(-)
diff --git a/server/src/main/resources/org/elasticsearch/bootstrap/security.policy b/server/src/main/resources/org/elasticsearch/bootstrap/security.policy
index 24b4483964fc9..f9b37f65538f2 100644
--- a/server/src/main/resources/org/elasticsearch/bootstrap/security.policy
+++ b/server/src/main/resources/org/elasticsearch/bootstrap/security.policy
@@ -170,5 +170,4 @@ grant {
// system memory on Linux systems affected by JDK bug (#66629)
permission java.io.FilePermission "/proc/meminfo", "read";
- permission java.io.FilePermission "/proc/meminfo", "read";
};
From ec62beab4b30069cf7396250389c6d391f70eb9e Mon Sep 17 00:00:00 2001
From: Nikola Grcevski
Date: Mon, 27 Jun 2022 13:57:15 -0400
Subject: [PATCH 5/8] Fake the classloaders instead of using real
---
.../bootstrap/test-framework.policy | 2 -
.../plugins/PluginsServiceTests.java | 54 ++++++++-----------
.../plugins/MockPluginsService.java | 8 ++-
3 files changed, 28 insertions(+), 36 deletions(-)
diff --git a/server/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy b/server/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy
index caa014c0c5059..8907c4d463d3a 100644
--- a/server/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy
+++ b/server/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy
@@ -125,6 +125,4 @@ grant codeBase "${codebase.netty-transport}" {
grant {
permission java.net.SocketPermission "127.0.0.1", "connect, resolve";
permission java.nio.file.LinkPermission "symbolic";
- // needed for PluginsServiceTests
- permission java.lang.RuntimePermission "createClassLoader";
};
diff --git a/server/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java b/server/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java
index c82983d83b1f2..0054fdc569df3 100644
--- a/server/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java
+++ b/server/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java
@@ -12,8 +12,6 @@
import org.apache.lucene.util.Constants;
import org.elasticsearch.Version;
import org.elasticsearch.common.settings.Settings;
-import org.elasticsearch.core.PathUtils;
-import org.elasticsearch.core.Strings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.env.TestEnvironment;
import org.elasticsearch.index.IndexModule;
@@ -46,6 +44,7 @@
import static org.hamcrest.Matchers.sameInstance;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
@LuceneTestCase.SuppressFileSystems(value = "ExtrasFS")
@@ -623,15 +622,10 @@ public void testThrowingConstructor() {
}
public void testLoadServiceProviders() throws ClassNotFoundException {
- FakeClassLoader fakeClassLoader = new FakeClassLoader();
- @SuppressWarnings("unchecked")
- Class extends Plugin> fakePluginClass = (Class extends Plugin>) fakeClassLoader.findClass(FakePlugin.class.getName());
-
- FakeClassLoader fakeClassLoader1 = new FakeClassLoader();
- @SuppressWarnings("unchecked")
- Class extends Plugin> fakePluginClass1 = (Class extends Plugin>) fakeClassLoader1.findClass(FakePlugin.class.getName());
-
- assertFalse(fakePluginClass.getClassLoader().equals(fakePluginClass1.getClassLoader()));
+ ClassLoader fakeClassLoader = mock(ClassLoader.class);
+ ClassLoader fakeClassLoader1 = mock(ClassLoader.class);
+ FakePlugin fakePlugin = new FakePlugin();
+ FakePlugin fakePlugin1 = new FakePlugin();
OperatorHandlerProvider provider1 = () -> List.of(new OperatorHandler() {
@Override
@@ -656,7 +650,23 @@ public TransformState transform(Object source, TransformState prevState) {
return prevState;
}
});
- PluginsService service = spy(newMockPluginsService(List.of(fakePluginClass, fakePluginClass1)));
+ // Make the service load without plugins, we mock and force set them after
+ PluginsService service = newMockPluginsService(new ArrayList<>());
+ PluginsService.LoadedPlugin plugin1 = new PluginsService.LoadedPlugin(
+ mock(PluginDescriptor.class),
+ fakePlugin,
+ fakeClassLoader,
+ ModuleLayer.boot()
+ );
+ PluginsService.LoadedPlugin plugin2 = new PluginsService.LoadedPlugin(
+ mock(PluginDescriptor.class),
+ fakePlugin1,
+ fakeClassLoader1,
+ ModuleLayer.boot()
+ );
+ ((MockPluginsService) service).setLoadedPlugins(List.of(plugin1, plugin2));
+
+ service = spy(service);
doReturn(List.of(provider1).iterator()).when(service).providersIterator(eq(OperatorHandlerProvider.class), eq(fakeClassLoader));
doReturn(List.of(provider2).iterator()).when(service).providersIterator(eq(OperatorHandlerProvider.class), eq(fakeClassLoader1));
@@ -679,7 +689,6 @@ public TransformState transform(Object source, TransformState prevState) {
.providersIterator(eq(OperatorHandlerProvider.class), eq(fakeClassLoader1));
assertEquals(0, service.loadServiceProviders(OperatorHandlerProvider.class).size());
-
}
private static class TestExtensiblePlugin extends Plugin implements ExtensiblePlugin {
@@ -721,23 +730,4 @@ public ThrowingConstructorExtension() {
throw new IllegalArgumentException("test constructor failure");
}
}
-
- static class FakeClassLoader extends ClassLoader {
- @Override
- public Class> findClass(String name) throws ClassNotFoundException {
- byte[] classBytes = fromFile(getClass().getClassLoader(), name);
- return defineClass(name, classBytes, 0, classBytes.length);
- }
-
- private static byte[] fromFile(ClassLoader realLoader, String fileName) {
- try {
- InputStream input = realLoader.getResourceAsStream(
- Strings.format("%s.class", fileName.replace(".", PathUtils.get(".").getFileSystem().getSeparator()))
- );
- return input.readAllBytes();
- } catch (Exception x) {
- throw new IllegalStateException("Error loading class", x);
- }
- }
- }
}
diff --git a/test/framework/src/main/java/org/elasticsearch/plugins/MockPluginsService.java b/test/framework/src/main/java/org/elasticsearch/plugins/MockPluginsService.java
index 60c10f430c911..21a5d051d97b6 100644
--- a/test/framework/src/main/java/org/elasticsearch/plugins/MockPluginsService.java
+++ b/test/framework/src/main/java/org/elasticsearch/plugins/MockPluginsService.java
@@ -25,7 +25,7 @@ public class MockPluginsService extends PluginsService {
private static final Logger logger = LogManager.getLogger(MockPluginsService.class);
- private final List classpathPlugins;
+ private List classpathPlugins;
/**
* Constructs a new PluginService
@@ -58,7 +58,7 @@ public MockPluginsService(Settings settings, Environment environment, Collection
if (logger.isTraceEnabled()) {
logger.trace("plugin loaded from classpath [{}]", pluginInfo);
}
- pluginsLoaded.add(new LoadedPlugin(pluginInfo, plugin, pluginClass.getClassLoader(), ModuleLayer.boot()));
+ pluginsLoaded.add(new LoadedPlugin(pluginInfo, plugin));
}
this.classpathPlugins = List.copyOf(pluginsLoaded);
@@ -69,6 +69,10 @@ protected final List plugins() {
return this.classpathPlugins;
}
+ void setLoadedPlugins(List plugins) {
+ this.classpathPlugins = List.copyOf(plugins);
+ }
+
@Override
public PluginsAndModules info() {
return new PluginsAndModules(
From 20cf2e7d07ceb14386cef85764b66f499206303a Mon Sep 17 00:00:00 2001
From: Nikola Grcevski
Date: Mon, 27 Jun 2022 15:02:20 -0400
Subject: [PATCH 6/8] Bring back real class loaders in tests
---
.../elasticsearch/plugins/PluginsService.java | 5 +-
.../bootstrap/test-framework.policy | 2 +
.../plugins/PluginsServiceTests.java | 54 +++++++++++--------
.../plugins/MockPluginsService.java | 8 +--
4 files changed, 38 insertions(+), 31 deletions(-)
diff --git a/server/src/main/java/org/elasticsearch/plugins/PluginsService.java b/server/src/main/java/org/elasticsearch/plugins/PluginsService.java
index f72ad2c876979..50f950023f8a4 100644
--- a/server/src/main/java/org/elasticsearch/plugins/PluginsService.java
+++ b/server/src/main/java/org/elasticsearch/plugins/PluginsService.java
@@ -303,17 +303,16 @@ Iterator providersIterator(Class service, ClassLoader classLoader) {
* SPI convenience method that uses the {@link ServiceLoader} JDK class to load various SPI providers
* from plugins/modules.
*
- *
+ *
* For example:
*
* var pluginHandlers = pluginsService.loadServiceProviders(OperatorHandlerProvider.class);
- *
+ *
* @param service A templated service class to look for providers in plugins
* @return an immutable {@link List} of discovered providers in the plugins/modules
*/
public List extends T> loadServiceProviders(Class service) {
List result = new ArrayList<>();
- getClass().getModule().addUses(service);
for (LoadedPlugin pluginTuple : plugins()) {
providersIterator(service, pluginTuple.loader()).forEachRemaining(c -> result.add(c));
diff --git a/server/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy b/server/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy
index 8907c4d463d3a..caa014c0c5059 100644
--- a/server/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy
+++ b/server/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy
@@ -125,4 +125,6 @@ grant codeBase "${codebase.netty-transport}" {
grant {
permission java.net.SocketPermission "127.0.0.1", "connect, resolve";
permission java.nio.file.LinkPermission "symbolic";
+ // needed for PluginsServiceTests
+ permission java.lang.RuntimePermission "createClassLoader";
};
diff --git a/server/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java b/server/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java
index 0054fdc569df3..c82983d83b1f2 100644
--- a/server/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java
+++ b/server/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java
@@ -12,6 +12,8 @@
import org.apache.lucene.util.Constants;
import org.elasticsearch.Version;
import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.core.PathUtils;
+import org.elasticsearch.core.Strings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.env.TestEnvironment;
import org.elasticsearch.index.IndexModule;
@@ -44,7 +46,6 @@
import static org.hamcrest.Matchers.sameInstance;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
@LuceneTestCase.SuppressFileSystems(value = "ExtrasFS")
@@ -622,10 +623,15 @@ public void testThrowingConstructor() {
}
public void testLoadServiceProviders() throws ClassNotFoundException {
- ClassLoader fakeClassLoader = mock(ClassLoader.class);
- ClassLoader fakeClassLoader1 = mock(ClassLoader.class);
- FakePlugin fakePlugin = new FakePlugin();
- FakePlugin fakePlugin1 = new FakePlugin();
+ FakeClassLoader fakeClassLoader = new FakeClassLoader();
+ @SuppressWarnings("unchecked")
+ Class extends Plugin> fakePluginClass = (Class extends Plugin>) fakeClassLoader.findClass(FakePlugin.class.getName());
+
+ FakeClassLoader fakeClassLoader1 = new FakeClassLoader();
+ @SuppressWarnings("unchecked")
+ Class extends Plugin> fakePluginClass1 = (Class extends Plugin>) fakeClassLoader1.findClass(FakePlugin.class.getName());
+
+ assertFalse(fakePluginClass.getClassLoader().equals(fakePluginClass1.getClassLoader()));
OperatorHandlerProvider provider1 = () -> List.of(new OperatorHandler() {
@Override
@@ -650,23 +656,7 @@ public TransformState transform(Object source, TransformState prevState) {
return prevState;
}
});
- // Make the service load without plugins, we mock and force set them after
- PluginsService service = newMockPluginsService(new ArrayList<>());
- PluginsService.LoadedPlugin plugin1 = new PluginsService.LoadedPlugin(
- mock(PluginDescriptor.class),
- fakePlugin,
- fakeClassLoader,
- ModuleLayer.boot()
- );
- PluginsService.LoadedPlugin plugin2 = new PluginsService.LoadedPlugin(
- mock(PluginDescriptor.class),
- fakePlugin1,
- fakeClassLoader1,
- ModuleLayer.boot()
- );
- ((MockPluginsService) service).setLoadedPlugins(List.of(plugin1, plugin2));
-
- service = spy(service);
+ PluginsService service = spy(newMockPluginsService(List.of(fakePluginClass, fakePluginClass1)));
doReturn(List.of(provider1).iterator()).when(service).providersIterator(eq(OperatorHandlerProvider.class), eq(fakeClassLoader));
doReturn(List.of(provider2).iterator()).when(service).providersIterator(eq(OperatorHandlerProvider.class), eq(fakeClassLoader1));
@@ -689,6 +679,7 @@ public TransformState transform(Object source, TransformState prevState) {
.providersIterator(eq(OperatorHandlerProvider.class), eq(fakeClassLoader1));
assertEquals(0, service.loadServiceProviders(OperatorHandlerProvider.class).size());
+
}
private static class TestExtensiblePlugin extends Plugin implements ExtensiblePlugin {
@@ -730,4 +721,23 @@ public ThrowingConstructorExtension() {
throw new IllegalArgumentException("test constructor failure");
}
}
+
+ static class FakeClassLoader extends ClassLoader {
+ @Override
+ public Class> findClass(String name) throws ClassNotFoundException {
+ byte[] classBytes = fromFile(getClass().getClassLoader(), name);
+ return defineClass(name, classBytes, 0, classBytes.length);
+ }
+
+ private static byte[] fromFile(ClassLoader realLoader, String fileName) {
+ try {
+ InputStream input = realLoader.getResourceAsStream(
+ Strings.format("%s.class", fileName.replace(".", PathUtils.get(".").getFileSystem().getSeparator()))
+ );
+ return input.readAllBytes();
+ } catch (Exception x) {
+ throw new IllegalStateException("Error loading class", x);
+ }
+ }
+ }
}
diff --git a/test/framework/src/main/java/org/elasticsearch/plugins/MockPluginsService.java b/test/framework/src/main/java/org/elasticsearch/plugins/MockPluginsService.java
index 21a5d051d97b6..60c10f430c911 100644
--- a/test/framework/src/main/java/org/elasticsearch/plugins/MockPluginsService.java
+++ b/test/framework/src/main/java/org/elasticsearch/plugins/MockPluginsService.java
@@ -25,7 +25,7 @@ public class MockPluginsService extends PluginsService {
private static final Logger logger = LogManager.getLogger(MockPluginsService.class);
- private List classpathPlugins;
+ private final List classpathPlugins;
/**
* Constructs a new PluginService
@@ -58,7 +58,7 @@ public MockPluginsService(Settings settings, Environment environment, Collection
if (logger.isTraceEnabled()) {
logger.trace("plugin loaded from classpath [{}]", pluginInfo);
}
- pluginsLoaded.add(new LoadedPlugin(pluginInfo, plugin));
+ pluginsLoaded.add(new LoadedPlugin(pluginInfo, plugin, pluginClass.getClassLoader(), ModuleLayer.boot()));
}
this.classpathPlugins = List.copyOf(pluginsLoaded);
@@ -69,10 +69,6 @@ protected final List plugins() {
return this.classpathPlugins;
}
- void setLoadedPlugins(List plugins) {
- this.classpathPlugins = List.copyOf(plugins);
- }
-
@Override
public PluginsAndModules info() {
return new PluginsAndModules(
From 3fe50077ad2840130ecb986812add6819b8fd109 Mon Sep 17 00:00:00 2001
From: Nikola Grcevski
Date: Wed, 29 Jun 2022 17:02:46 -0400
Subject: [PATCH 7/8] Apply review feedback
---
.../elasticsearch/plugins/PluginsService.java | 4 +-
.../bootstrap/test-framework.policy | 2 -
.../plugins/PluginsServiceTests.java | 123 ++++++++----------
.../plugins/spi/TestService.java | 13 ++
4 files changed, 67 insertions(+), 75 deletions(-)
create mode 100644 server/src/test/java/org/elasticsearch/plugins/spi/TestService.java
diff --git a/server/src/main/java/org/elasticsearch/plugins/PluginsService.java b/server/src/main/java/org/elasticsearch/plugins/PluginsService.java
index 50f950023f8a4..eaaa7810f1ce3 100644
--- a/server/src/main/java/org/elasticsearch/plugins/PluginsService.java
+++ b/server/src/main/java/org/elasticsearch/plugins/PluginsService.java
@@ -302,10 +302,10 @@ Iterator providersIterator(Class service, ClassLoader classLoader) {
/**
* SPI convenience method that uses the {@link ServiceLoader} JDK class to load various SPI providers
* from plugins/modules.
- *
- *
+ *
* For example:
*
+ *
* var pluginHandlers = pluginsService.loadServiceProviders(OperatorHandlerProvider.class);
*
* @param service A templated service class to look for providers in plugins
diff --git a/server/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy b/server/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy
index caa014c0c5059..8907c4d463d3a 100644
--- a/server/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy
+++ b/server/src/main/resources/org/elasticsearch/bootstrap/test-framework.policy
@@ -125,6 +125,4 @@ grant codeBase "${codebase.netty-transport}" {
grant {
permission java.net.SocketPermission "127.0.0.1", "connect, resolve";
permission java.nio.file.LinkPermission "symbolic";
- // needed for PluginsServiceTests
- permission java.lang.RuntimePermission "createClassLoader";
};
diff --git a/server/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java b/server/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java
index c82983d83b1f2..b440923b5f723 100644
--- a/server/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java
+++ b/server/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java
@@ -12,19 +12,21 @@
import org.apache.lucene.util.Constants;
import org.elasticsearch.Version;
import org.elasticsearch.common.settings.Settings;
-import org.elasticsearch.core.PathUtils;
import org.elasticsearch.core.Strings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.env.TestEnvironment;
import org.elasticsearch.index.IndexModule;
-import org.elasticsearch.operator.OperatorHandler;
-import org.elasticsearch.operator.OperatorHandlerProvider;
-import org.elasticsearch.operator.TransformState;
+import org.elasticsearch.plugins.spi.TestService;
import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.test.compiler.InMemoryJavaCompiler;
+import org.elasticsearch.test.jar.JarUtils;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystemException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
@@ -32,9 +34,10 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
-import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
import java.util.Set;
import static org.hamcrest.Matchers.containsInAnyOrder;
@@ -44,9 +47,6 @@
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.sameInstance;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
@LuceneTestCase.SuppressFileSystems(value = "ExtrasFS")
public class PluginsServiceTests extends ESTestCase {
@@ -622,64 +622,64 @@ public void testThrowingConstructor() {
assertThat(e.getCause().getCause(), hasToString(containsString("test constructor failure")));
}
- public void testLoadServiceProviders() throws ClassNotFoundException {
- FakeClassLoader fakeClassLoader = new FakeClassLoader();
- @SuppressWarnings("unchecked")
- Class extends Plugin> fakePluginClass = (Class extends Plugin>) fakeClassLoader.findClass(FakePlugin.class.getName());
+ private ClassLoader buildTestProviderPlugin(String name) throws Exception {
+ String pluginClass = Strings.format("""
+ package r;
+ import org.elasticsearch.plugins.spi.TestService;
+ import org.elasticsearch.plugins.ActionPlugin;
+ import org.elasticsearch.plugins.Plugin;
+ public final class FooPlugin extends Plugin implements ActionPlugin, TestService {
+ @Override
+ public String name() {
+ return "%s";
+ }
+ }
+ """, name);
- FakeClassLoader fakeClassLoader1 = new FakeClassLoader();
- @SuppressWarnings("unchecked")
- Class extends Plugin> fakePluginClass1 = (Class extends Plugin>) fakeClassLoader1.findClass(FakePlugin.class.getName());
+ var classToBytes = InMemoryJavaCompiler.compile("r.FooPlugin", pluginClass);
- assertFalse(fakePluginClass.getClassLoader().equals(fakePluginClass1.getClassLoader()));
+ Map jarEntries = new HashMap<>();
+ jarEntries.put("r/FooPlugin.class", classToBytes);
+ jarEntries.put("META-INF/services/org.elasticsearch.plugins.spi.TestService", "r.FooPlugin".getBytes(StandardCharsets.UTF_8));
- OperatorHandlerProvider provider1 = () -> List.of(new OperatorHandler() {
- @Override
- public String name() {
- return "integer";
- }
+ Path topLevelDir = createTempDir(getTestName());
+ Path jar = topLevelDir.resolve(Strings.format("custom_plugin_%s.jar", name));
+ JarUtils.createJarWithEntries(jar, jarEntries);
+ URL[] urls = new URL[] { jar.toUri().toURL() };
- @Override
- public TransformState transform(Object source, TransformState prevState) {
- return prevState;
- }
- });
+ URLClassLoader loader = URLClassLoader.newInstance(urls, this.getClass().getClassLoader());
+ return loader;
+ }
- OperatorHandlerProvider provider2 = () -> List.of(new OperatorHandler() {
- @Override
- public String name() {
- return "string";
- }
+ public void testLoadServiceProviders() throws Exception {
+ ClassLoader fakeClassLoader = buildTestProviderPlugin("integer");
+ @SuppressWarnings("unchecked")
+ Class extends Plugin> fakePluginClass = (Class extends Plugin>) fakeClassLoader.loadClass("r.FooPlugin");
- @Override
- public TransformState transform(Object source, TransformState prevState) {
- return prevState;
- }
- });
- PluginsService service = spy(newMockPluginsService(List.of(fakePluginClass, fakePluginClass1)));
+ ClassLoader fakeClassLoader1 = buildTestProviderPlugin("string");
+ @SuppressWarnings("unchecked")
+ Class extends Plugin> fakePluginClass1 = (Class extends Plugin>) fakeClassLoader1.loadClass("r.FooPlugin");
- doReturn(List.of(provider1).iterator()).when(service).providersIterator(eq(OperatorHandlerProvider.class), eq(fakeClassLoader));
- doReturn(List.of(provider2).iterator()).when(service).providersIterator(eq(OperatorHandlerProvider.class), eq(fakeClassLoader1));
+ assertFalse(fakePluginClass.getClassLoader().equals(fakePluginClass1.getClassLoader()));
- List extends OperatorHandlerProvider> providers = service.loadServiceProviders(OperatorHandlerProvider.class);
- assertEquals(2, providers.size());
- List handlers = providers.stream().map(p -> p.handlers()).flatMap(l -> l.stream()).map(h -> h.name()).toList();
+ getClass().getModule().addUses(TestService.class);
- assertThat(handlers, containsInAnyOrder("string", "integer"));
+ PluginsService service = newMockPluginsService(List.of(fakePluginClass, fakePluginClass1));
- doReturn(Collections.emptyList().iterator()).when(service)
- .providersIterator(eq(OperatorHandlerProvider.class), eq(fakeClassLoader));
- doReturn(List.of(provider2).iterator()).when(service).providersIterator(eq(OperatorHandlerProvider.class), eq(fakeClassLoader1));
+ List extends TestService> providers = service.loadServiceProviders(TestService.class);
+ assertEquals(2, providers.size());
+ assertThat(providers.stream().map(p -> p.name()).toList(), containsInAnyOrder("string", "integer"));
- assertEquals(1, service.loadServiceProviders(OperatorHandlerProvider.class).size());
+ service = newMockPluginsService(List.of(fakePluginClass));
+ providers = service.loadServiceProviders(TestService.class);
- doReturn(Collections.emptyList().iterator()).when(service)
- .providersIterator(eq(OperatorHandlerProvider.class), eq(fakeClassLoader));
- doReturn(Collections.emptyList().iterator()).when(service)
- .providersIterator(eq(OperatorHandlerProvider.class), eq(fakeClassLoader1));
+ assertEquals(1, providers.size());
+ assertThat(providers.stream().map(p -> p.name()).toList(), containsInAnyOrder("integer"));
- assertEquals(0, service.loadServiceProviders(OperatorHandlerProvider.class).size());
+ service = newMockPluginsService(new ArrayList<>());
+ providers = service.loadServiceProviders(TestService.class);
+ assertEquals(0, providers.size());
}
private static class TestExtensiblePlugin extends Plugin implements ExtensiblePlugin {
@@ -721,23 +721,4 @@ public ThrowingConstructorExtension() {
throw new IllegalArgumentException("test constructor failure");
}
}
-
- static class FakeClassLoader extends ClassLoader {
- @Override
- public Class> findClass(String name) throws ClassNotFoundException {
- byte[] classBytes = fromFile(getClass().getClassLoader(), name);
- return defineClass(name, classBytes, 0, classBytes.length);
- }
-
- private static byte[] fromFile(ClassLoader realLoader, String fileName) {
- try {
- InputStream input = realLoader.getResourceAsStream(
- Strings.format("%s.class", fileName.replace(".", PathUtils.get(".").getFileSystem().getSeparator()))
- );
- return input.readAllBytes();
- } catch (Exception x) {
- throw new IllegalStateException("Error loading class", x);
- }
- }
- }
}
diff --git a/server/src/test/java/org/elasticsearch/plugins/spi/TestService.java b/server/src/test/java/org/elasticsearch/plugins/spi/TestService.java
new file mode 100644
index 0000000000000..1156ebc1f809a
--- /dev/null
+++ b/server/src/test/java/org/elasticsearch/plugins/spi/TestService.java
@@ -0,0 +1,13 @@
+/*
+ * 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.plugins.spi;
+
+public interface TestService {
+ String name();
+}
From e38c2fc395eb38ba7c2051ab585fe03012541db5 Mon Sep 17 00:00:00 2001
From: Nikola Grcevski
Date: Wed, 29 Jun 2022 19:30:02 -0400
Subject: [PATCH 8/8] Split the provider class in the test
---
.../elasticsearch/plugins/PluginsService.java | 8 +-------
.../plugins/PluginsServiceTests.java | 18 +++++++++++-------
2 files changed, 12 insertions(+), 14 deletions(-)
diff --git a/server/src/main/java/org/elasticsearch/plugins/PluginsService.java b/server/src/main/java/org/elasticsearch/plugins/PluginsService.java
index eaaa7810f1ce3..058885de79f81 100644
--- a/server/src/main/java/org/elasticsearch/plugins/PluginsService.java
+++ b/server/src/main/java/org/elasticsearch/plugins/PluginsService.java
@@ -47,7 +47,6 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
@@ -294,11 +293,6 @@ static void loadExtensions(Collection plugins) {
}
}
- // package private for testing
- Iterator providersIterator(Class service, ClassLoader classLoader) {
- return ServiceLoader.load(service, classLoader).iterator();
- }
-
/**
* SPI convenience method that uses the {@link ServiceLoader} JDK class to load various SPI providers
* from plugins/modules.
@@ -315,7 +309,7 @@ public List extends T> loadServiceProviders(Class service) {
List result = new ArrayList<>();
for (LoadedPlugin pluginTuple : plugins()) {
- providersIterator(service, pluginTuple.loader()).forEachRemaining(c -> result.add(c));
+ ServiceLoader.load(service, pluginTuple.loader()).iterator().forEachRemaining(c -> result.add(c));
}
return Collections.unmodifiableList(result);
diff --git a/server/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java b/server/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java
index b440923b5f723..e9960f22ff3d1 100644
--- a/server/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java
+++ b/server/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java
@@ -623,24 +623,28 @@ public void testThrowingConstructor() {
}
private ClassLoader buildTestProviderPlugin(String name) throws Exception {
- String pluginClass = Strings.format("""
+ Map sources = Map.of("r.FooPlugin", """
package r;
- import org.elasticsearch.plugins.spi.TestService;
import org.elasticsearch.plugins.ActionPlugin;
import org.elasticsearch.plugins.Plugin;
- public final class FooPlugin extends Plugin implements ActionPlugin, TestService {
+ public final class FooPlugin extends Plugin implements ActionPlugin { }
+ """, "r.FooTestService", Strings.format("""
+ package r;
+ import org.elasticsearch.plugins.spi.TestService;
+ public final class FooTestService implements TestService {
@Override
public String name() {
return "%s";
}
}
- """, name);
+ """, name));
- var classToBytes = InMemoryJavaCompiler.compile("r.FooPlugin", pluginClass);
+ var classToBytes = InMemoryJavaCompiler.compile(sources);
Map jarEntries = new HashMap<>();
- jarEntries.put("r/FooPlugin.class", classToBytes);
- jarEntries.put("META-INF/services/org.elasticsearch.plugins.spi.TestService", "r.FooPlugin".getBytes(StandardCharsets.UTF_8));
+ jarEntries.put("r/FooPlugin.class", classToBytes.get("r.FooPlugin"));
+ jarEntries.put("r/FooTestService.class", classToBytes.get("r.FooTestService"));
+ jarEntries.put("META-INF/services/org.elasticsearch.plugins.spi.TestService", "r.FooTestService".getBytes(StandardCharsets.UTF_8));
Path topLevelDir = createTempDir(getTestName());
Path jar = topLevelDir.resolve(Strings.format("custom_plugin_%s.jar", name));