Remove plugin isolation feature for a future version

relates #5261
This commit is contained in:
Costin Leau 2014-03-17 15:17:49 +02:00
parent a9c8624c67
commit 960d353dbd
13 changed files with 123 additions and 745 deletions

View File

@ -40,7 +40,6 @@ plugins per node:
* `site`: `true` if the plugin is a site plugin
* `jvm`: `true` if the plugin is a plugin running in the JVM
* `url`: URL if the plugin is a site plugin
* `isolation`: whether the plugin is loaded in isolation (`true`) or not (`false`)
The result will look similar to:

View File

@ -142,20 +142,6 @@ bin/plugin --install mobz/elasticsearch-head --timeout 1m
bin/plugin --install mobz/elasticsearch-head --timeout 0
-----------------------------------
added[1.1.0]
[float]
==== Plugins isolation
Since Elasticsearch 1.1, by default, each plugin is loaded in _isolation_ (in its dedicated `ClassLoader`) to avoid class clashes between the various plugins and their associated libraries. The default can be changed through the `plugins.isolation` property in `elasticsearch.yml`, by setting it to `false`:
[source,js]
--------------------------------------------------
plugins.isolation: false
--------------------------------------------------
Do note that each plugin can specify its _mandatory_ isolation through the `isolation` property in its `es-plugin.properties` configuration. In this (rare) case, the plugin setting is used, overwriting whatever default used by Elasticsearch.
[float]
[[known-plugins]]
=== Known Plugins

View File

@ -41,7 +41,6 @@ public class PluginInfo implements Streamable, Serializable, ToXContent {
static final XContentBuilderString JVM = new XContentBuilderString("jvm");
static final XContentBuilderString SITE = new XContentBuilderString("site");
static final XContentBuilderString VERSION = new XContentBuilderString("version");
static final XContentBuilderString ISOLATION = new XContentBuilderString("isolation");
}
private String name;
@ -49,7 +48,6 @@ public class PluginInfo implements Streamable, Serializable, ToXContent {
private boolean site;
private boolean jvm;
private String version;
private boolean isolation;
public PluginInfo() {
}
@ -62,9 +60,8 @@ public class PluginInfo implements Streamable, Serializable, ToXContent {
* @param site true if it's a site plugin
* @param jvm true if it's a jvm plugin
* @param version Version number is applicable (NA otherwise)
* @param isolation true if it's an isolated plugin
*/
public PluginInfo(String name, String description, boolean site, boolean jvm, String version, boolean isolation) {
public PluginInfo(String name, String description, boolean site, boolean jvm, String version) {
this.name = name;
this.description = description;
this.site = site;
@ -74,7 +71,6 @@ public class PluginInfo implements Streamable, Serializable, ToXContent {
} else {
this.version = VERSION_NOT_AVAILABLE;
}
this.isolation = isolation;
}
/**
@ -125,13 +121,6 @@ public class PluginInfo implements Streamable, Serializable, ToXContent {
return version;
}
/**
* @return Plugin isolation
*/
public boolean isIsolation() {
return isolation;
}
public static PluginInfo readPluginInfo(StreamInput in) throws IOException {
PluginInfo info = new PluginInfo();
info.readFrom(in);
@ -149,11 +138,6 @@ public class PluginInfo implements Streamable, Serializable, ToXContent {
} else {
this.version = VERSION_NOT_AVAILABLE;
}
if (in.getVersion().onOrAfter(Version.V_1_1_0)) {
this.isolation = in.readBoolean();
} else {
this.isolation = false;
}
}
@Override
@ -165,9 +149,6 @@ public class PluginInfo implements Streamable, Serializable, ToXContent {
if (out.getVersion().onOrAfter(Version.V_1_0_0_RC2)) {
out.writeString(version);
}
if (out.getVersion().onOrAfter(Version.V_1_1_0)) {
out.writeBoolean(isolation);
}
}
@Override
@ -181,7 +162,6 @@ public class PluginInfo implements Streamable, Serializable, ToXContent {
}
builder.field(Fields.JVM, jvm);
builder.field(Fields.SITE, site);
builder.field(Fields.ISOLATION, isolation);
builder.endObject();
return builder;
@ -207,13 +187,12 @@ public class PluginInfo implements Streamable, Serializable, ToXContent {
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("PluginInfo{");
final StringBuffer sb = new StringBuffer("PluginInfo{");
sb.append("name='").append(name).append('\'');
sb.append(", description='").append(description).append('\'');
sb.append(", site=").append(site);
sb.append(", jvm=").append(jvm);
sb.append(", version='").append(version).append('\'');
sb.append(", isolation=").append(isolation);
sb.append('}');
return sb.toString();
}

View File

@ -1,110 +0,0 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.plugins;
import com.google.common.collect.Lists;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
public class PluginClassLoader extends URLClassLoader {
private final ClassLoader system;
private final URL url;
PluginClassLoader(URL[] urls, ClassLoader parent) throws IOException {
super(urls, parent);
url = (urls != null && urls.length > 0 ? urls[0] : null);
ClassLoader sys = getSystemClassLoader();
while (sys.getParent() != null) {
sys = sys.getParent();
}
system = sys;
}
// load first from system class loader then fall back to this one
@Override
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class<?> c = findLoadedClass(name);
if (c == null) {
// check system class loader (jvm & bootclasspath)
if (system != null) {
try {
c = system.loadClass(name);
} catch (ClassNotFoundException ignored) {
}
}
if (c == null) {
try {
// check plugin classloader
c = findClass(name);
} catch (ClassNotFoundException e) {
// fall back to parent
c = super.loadClass(name, resolve);
}
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
@Override
public URL getResource(String name) {
// apply same rules frmo loadClass
URL url = null;
if (system != null) {
url = system.getResource(name);
}
if (url == null) {
url = findResource(name);
if (url == null) {
url = super.getResource(name);
}
}
return url;
}
@Override
public Enumeration<URL> getResources(String name) throws IOException {
List<URL> urls = Lists.newArrayList();
if (system != null) {
urls.addAll(Collections.list(system.getResources(name)));
}
urls.addAll(Collections.list(findResources(name)));
ClassLoader parent = getParent();
if (parent != null) {
urls.addAll(Collections.list(parent.getResources(name)));
}
return Collections.enumeration(urls);
}
@Override
public String toString() {
return String.format(Locale.US, "PluginClassLoader for url [%s], classpath [%s], systemCL [%s]", url, Arrays.toString(getURLs()), system);
}
}

View File

@ -1,142 +0,0 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.plugins;
import com.google.common.collect.Lists;
import org.apache.lucene.util.IOUtils;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.Booleans;
import org.elasticsearch.common.settings.Settings;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
public final class PluginUtils {
private PluginUtils() {}
@SuppressWarnings("unchecked")
static Plugin loadPlugin(String className, Settings settings, ClassLoader classLoader) {
try {
Class<? extends Plugin> pluginClass = (Class<? extends Plugin>) classLoader.loadClass(className);
try {
return pluginClass.getConstructor(Settings.class).newInstance(settings);
} catch (NoSuchMethodException e) {
try {
return pluginClass.newInstance();
} catch (Exception e1) {
throw new ElasticsearchException("No constructor for [" + pluginClass + "]. A plugin class must " +
"have either an empty default constructor or a single argument constructor accepting a " +
"Settings instance");
}
}
} catch (Exception e) {
throw new ElasticsearchException("Failed to load plugin class [" + className + "]", e);
}
}
static List<File> pluginClassPathAsFiles(File pluginFolder) throws IOException {
List<File> cpFiles = Lists.newArrayList();
cpFiles.add(pluginFolder);
List<File> libFiles = Lists.newArrayList();
File[] files = pluginFolder.listFiles();
if (files != null) {
Collections.addAll(libFiles, files);
}
File libLocation = new File(pluginFolder, "lib");
if (libLocation.exists() && libLocation.isDirectory()) {
files = libLocation.listFiles();
if (files != null) {
Collections.addAll(libFiles, files);
}
}
// if there are jars in it, add it as well
for (File libFile : libFiles) {
if (libFile.getName().endsWith(".jar") || libFile.getName().endsWith(".zip")) {
cpFiles.add(libFile);
}
}
return cpFiles;
}
static boolean lookupIsolation(List<URL> pluginProperties, boolean defaultIsolation) throws IOException {
Properties props = new Properties();
InputStream is = null;
for (URL prop : pluginProperties) {
try {
props.load(prop.openStream());
return Booleans.parseBoolean(props.getProperty("isolation"), defaultIsolation);
} finally {
IOUtils.closeWhileHandlingException(is);
}
}
return defaultIsolation;
}
static List<URL> lookupPluginProperties(List<File> pluginClassPath) throws Exception {
if (pluginClassPath.isEmpty()) {
return Collections.emptyList();
}
List<URL> found = Lists.newArrayList();
for (File file : pluginClassPath) {
String toString = file.getName();
if (toString.endsWith(".jar") || toString.endsWith(".zip")) {
JarFile jar = new JarFile(file);
try {
JarEntry jarEntry = jar.getJarEntry("es-plugin.properties");
if (jarEntry != null) {
found.add(new URL("jar:" + file.toURI().toString() + "!/es-plugin.properties"));
}
} finally {
IOUtils.closeWhileHandlingException(jar);
}
}
else {
File props = new File(file, "es-plugin.properties");
if (props.exists() && props.canRead()) {
found.add(props.toURI().toURL());
}
}
}
return found;
}
static URL[] convertFileToUrl(List<File> pluginClassPath) throws IOException {
URL[] urls = new URL[pluginClassPath.size()];
for (int i = 0; i < urls.length; i++) {
urls[i] = pluginClassPath.get(i).toURI().toURL();
}
return urls;
}
}

View File

@ -38,6 +38,7 @@ import org.elasticsearch.index.CloseableIndexComponent;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.URL;
@ -88,8 +89,8 @@ public class PluginsService extends AbstractComponent {
// first we load all the default plugins from the settings
String[] defaultPluginsClasses = settings.getAsArray("plugin.types");
for (String pluginClass : defaultPluginsClasses) {
Plugin plugin = PluginUtils.loadPlugin(pluginClass, settings, settings.getClassLoader());
PluginInfo pluginInfo = new PluginInfo(plugin.name(), plugin.description(), hasSite(plugin.name()), true, PluginInfo.VERSION_NOT_AVAILABLE, false);
Plugin plugin = loadPlugin(pluginClass, settings);
PluginInfo pluginInfo = new PluginInfo(plugin.name(), plugin.description(), hasSite(plugin.name()), true, PluginInfo.VERSION_NOT_AVAILABLE);
if (logger.isTraceEnabled()) {
logger.trace("plugin loaded from settings [{}]", pluginInfo);
}
@ -97,7 +98,8 @@ public class PluginsService extends AbstractComponent {
}
// now, find all the ones that are in the classpath
tupleBuilder.addAll(loadPlugins());
loadPluginsIntoClassLoader();
tupleBuilder.addAll(loadPluginsFromClasspath(settings));
this.plugins = tupleBuilder.build();
// We need to build a List of jvm and site plugins for checking mandatory plugins
@ -315,110 +317,103 @@ public class PluginsService extends AbstractComponent {
return cachedPluginsInfo;
}
private List<Tuple<PluginInfo,Plugin>> loadPlugins() {
File pluginsFile = environment.pluginsFile();
if (!isAccessibleDirectory(pluginsFile, logger)) {
return Collections.emptyList();
private void loadPluginsIntoClassLoader() {
File pluginsDirectory = environment.pluginsFile();
if (!isAccessibleDirectory(pluginsDirectory, logger)) {
return;
}
List<Tuple<PluginInfo, Plugin>> pluginData = Lists.newArrayList();
boolean defaultIsolation = settings.getAsBoolean("plugins.isolation", Boolean.FALSE);
ClassLoader esClassLoader = settings.getClassLoader();
ClassLoader classLoader = settings.getClassLoader();
Class classLoaderClass = classLoader.getClass();
Method addURL = null;
boolean discoveredAddUrl = false;
File[] pluginsFiles = pluginsFile.listFiles();
if (pluginsFiles != null) {
for (File pluginRoot : pluginsFiles) {
if (isAccessibleDirectory(pluginRoot, logger)) {
try {
logger.trace("--- adding plugin [" + pluginRoot.getAbsolutePath() + "]");
// check isolation
List<File> pluginClassPath = PluginUtils.pluginClassPathAsFiles(pluginRoot);
List<URL> pluginProperties = PluginUtils.lookupPluginProperties(pluginClassPath);
boolean isolated = PluginUtils.lookupIsolation(pluginProperties, defaultIsolation);
if (isolated) {
logger.trace("--- creating isolated space for plugin [" + pluginRoot.getAbsolutePath() + "]");
PluginClassLoader pcl = new PluginClassLoader(PluginUtils.convertFileToUrl(pluginClassPath), esClassLoader);
pluginData.addAll(loadPlugin(pluginClassPath, pluginProperties, pcl, true));
} else {
if (!discoveredAddUrl) {
discoveredAddUrl = true;
Class<?> esClassLoaderClass = esClassLoader.getClass();
while (!esClassLoaderClass.equals(Object.class)) {
try {
addURL = esClassLoaderClass.getDeclaredMethod("addURL", URL.class);
addURL.setAccessible(true);
break;
} catch (NoSuchMethodException e) {
// no method, try the parent
esClassLoaderClass = esClassLoaderClass.getSuperclass();
}
}
}
if (addURL == null) {
logger.debug("failed to find addURL method on classLoader [" + esClassLoader + "] to add methods");
}
else {
for (File file : pluginClassPath) {
addURL.invoke(esClassLoader, file.toURI().toURL());
}
pluginData.addAll(loadPlugin(pluginClassPath, pluginProperties, esClassLoader, false));
}
}
} catch (Throwable e) {
logger.warn("failed to add plugin [" + pluginRoot.getAbsolutePath() + "]", e);
}
}
while (!classLoaderClass.equals(Object.class)) {
try {
addURL = classLoaderClass.getDeclaredMethod("addURL", URL.class);
addURL.setAccessible(true);
break;
} catch (NoSuchMethodException e) {
// no method, try the parent
classLoaderClass = classLoaderClass.getSuperclass();
}
} else {
logger.debug("failed to list plugins from {}. Check your right access.", pluginsFile.getAbsolutePath());
}
if (addURL == null) {
logger.debug("failed to find addURL method on classLoader [" + classLoader + "] to add methods");
return;
}
return pluginData;
for (File plugin : pluginsDirectory.listFiles()) {
// We check that subdirs are directories and readable
if (!isAccessibleDirectory(plugin, logger)) {
continue;
}
logger.trace("--- adding plugin [{}]", plugin.getAbsolutePath());
try {
// add the root
addURL.invoke(classLoader, plugin.toURI().toURL());
// gather files to add
List<File> libFiles = Lists.newArrayList();
if (plugin.listFiles() != null) {
libFiles.addAll(Arrays.asList(plugin.listFiles()));
}
File libLocation = new File(plugin, "lib");
if (libLocation.exists() && libLocation.isDirectory() && libLocation.listFiles() != null) {
libFiles.addAll(Arrays.asList(libLocation.listFiles()));
}
// if there are jars in it, add it as well
for (File libFile : libFiles) {
if (!(libFile.getName().endsWith(".jar") || libFile.getName().endsWith(".zip"))) {
continue;
}
addURL.invoke(classLoader, libFile.toURI().toURL());
}
} catch (Throwable e) {
logger.warn("failed to add plugin [" + plugin + "]", e);
}
}
}
private Collection<? extends Tuple<PluginInfo, Plugin>> loadPlugin(List<File> pluginClassPath, List<URL> properties, ClassLoader classLoader, boolean isolation) throws Exception {
List<Tuple<PluginInfo, Plugin>> plugins = Lists.newArrayList();
private ImmutableList<Tuple<PluginInfo,Plugin>> loadPluginsFromClasspath(Settings settings) {
ImmutableList.Builder<Tuple<PluginInfo, Plugin>> plugins = ImmutableList.builder();
Enumeration<URL> entries = Collections.enumeration(properties);
while (entries.hasMoreElements()) {
URL pluginUrl = entries.nextElement();
Properties pluginProps = new Properties();
InputStream is = null;
try {
is = pluginUrl.openStream();
pluginProps.load(is);
String pluginClassName = pluginProps.getProperty("plugin");
if (pluginClassName == null) {
throw new IllegalArgumentException("No plugin class specified");
// Trying JVM plugins: looking for es-plugin.properties files
try {
Enumeration<URL> pluginUrls = settings.getClassLoader().getResources(ES_PLUGIN_PROPERTIES);
while (pluginUrls.hasMoreElements()) {
URL pluginUrl = pluginUrls.nextElement();
Properties pluginProps = new Properties();
InputStream is = null;
try {
is = pluginUrl.openStream();
pluginProps.load(is);
String pluginClassName = pluginProps.getProperty("plugin");
String pluginVersion = pluginProps.getProperty("version", PluginInfo.VERSION_NOT_AVAILABLE);
Plugin plugin = loadPlugin(pluginClassName, settings);
// Is it a site plugin as well? Does it have also an embedded _site structure
File siteFile = new File(new File(environment.pluginsFile(), plugin.name()), "_site");
boolean isSite = isAccessibleDirectory(siteFile, logger);
if (logger.isTraceEnabled()) {
logger.trace("found a jvm plugin [{}], [{}]{}",
plugin.name(), plugin.description(), isSite ? ": with _site structure" : "");
}
PluginInfo pluginInfo = new PluginInfo(plugin.name(), plugin.description(), isSite, true, pluginVersion);
plugins.add(new Tuple<>(pluginInfo, plugin));
} catch (Throwable e) {
logger.warn("failed to load plugin from [" + pluginUrl + "]", e);
} finally {
IOUtils.closeWhileHandlingException(is);
}
String pluginVersion = pluginProps.getProperty("version", PluginInfo.VERSION_NOT_AVAILABLE);
Plugin plugin = PluginUtils.loadPlugin(pluginClassName, settings, classLoader);
// Is it a site plugin as well? Does it have also an embedded _site structure
File siteFile = new File(new File(environment.pluginsFile(), plugin.name()), "_site");
boolean isSite = isAccessibleDirectory(siteFile, logger);
if (logger.isTraceEnabled()) {
logger.trace("found a jvm plugin [{}], [{}]{}",
plugin.name(), plugin.description(), isSite ? ": with _site structure" : "");
}
PluginInfo pluginInfo = new PluginInfo(plugin.name(), plugin.description(), isSite, true, pluginVersion, isolation);
plugins.add(new Tuple<>(pluginInfo, plugin));
} catch (Throwable e) {
logger.warn("failed to load plugin from [" + pluginUrl + "]", e);
} finally {
IOUtils.closeWhileHandlingException(is);
}
} catch (IOException e) {
logger.warn("failed to find jvm plugins from classpath", e);
}
return plugins;
return plugins.build();
}
private ImmutableList<Tuple<PluginInfo,Plugin>> loadSitePlugins() {
@ -471,7 +466,7 @@ public class PluginsService extends AbstractComponent {
logger.trace("found a site plugin name [{}], version [{}], description [{}]",
name, version, description);
}
sitePlugins.add(new Tuple<PluginInfo, Plugin>(new PluginInfo(name, description, true, false, version, false), null));
sitePlugins.add(new Tuple<PluginInfo, Plugin>(new PluginInfo(name, description, true, false, version), null));
}
}
}
@ -494,4 +489,27 @@ public class PluginsService extends AbstractComponent {
File sitePluginDir = new File(pluginsFile, name + "/_site");
return isAccessibleDirectory(sitePluginDir, logger);
}
}
private Plugin loadPlugin(String className, Settings settings) {
try {
Class<? extends Plugin> pluginClass = (Class<? extends Plugin>) settings.getClassLoader().loadClass(className);
Plugin plugin;
try {
plugin = pluginClass.getConstructor(Settings.class).newInstance(settings);
} catch (NoSuchMethodException e) {
try {
plugin = pluginClass.getConstructor().newInstance();
} catch (NoSuchMethodException e1) {
throw new ElasticsearchException("No constructor for [" + pluginClass + "]. A plugin class must " +
"have either an empty default constructor or a single argument constructor accepting a " +
"Settings instance");
}
}
return plugin;
} catch (Throwable e) {
throw new ElasticsearchException("Failed to load plugin class [" + className + "]", e);
}
}
}

View File

@ -84,7 +84,6 @@ public class RestPluginsAction extends AbstractCatAction {
table.addCell("component", "alias:c;desc:component");
table.addCell("version", "alias:v;desc:component version");
table.addCell("type", "alias:t;desc:type (j for JVM, s for Site)");
table.addCell("isolation", "alias:i;desc:isolation");
table.addCell("url", "alias:u;desc:url for site plugins");
table.addCell("description", "alias:d;default:false;desc:plugin details");
table.endHeaders();
@ -118,7 +117,6 @@ public class RestPluginsAction extends AbstractCatAction {
}
}
table.addCell(type);
table.addCell(pluginInfo.isIsolation() ? "x" : "");
table.addCell(pluginInfo.getUrl());
table.addCell(pluginInfo.getDescription());
table.endRow();

View File

@ -132,43 +132,37 @@ public class SimpleNodesInfoTests extends ElasticsearchIntegrationTest {
logger.info("--> full json answer, status " + response.toString());
assertNodeContainsPlugins(response, server1NodeId,
Collections.EMPTY_LIST, Collections.EMPTY_LIST, Collections.EMPTY_LIST, Collections.EMPTY_LIST,// No JVM Plugin
Collections.EMPTY_LIST, Collections.EMPTY_LIST, Collections.EMPTY_LIST, Collections.EMPTY_LIST);// No Site Plugin
Collections.EMPTY_LIST, Collections.EMPTY_LIST, Collections.EMPTY_LIST, // No JVM Plugin
Collections.EMPTY_LIST, Collections.EMPTY_LIST, Collections.EMPTY_LIST);// No Site Plugin
assertNodeContainsPlugins(response, server2NodeId,
Collections.EMPTY_LIST, Collections.EMPTY_LIST, Collections.EMPTY_LIST, Collections.EMPTY_LIST, // No JVM Plugin
Collections.EMPTY_LIST, Collections.EMPTY_LIST, Collections.EMPTY_LIST, // No JVM Plugin
Lists.newArrayList(Fields.SITE_PLUGIN), // Site Plugin
Lists.newArrayList(Fields.SITE_PLUGIN_DESCRIPTION),
Lists.newArrayList(Fields.SITE_PLUGIN_VERSION),
Lists.newArrayList(Boolean.FALSE));
Lists.newArrayList(Fields.SITE_PLUGIN_VERSION));
assertNodeContainsPlugins(response, server3NodeId,
Lists.newArrayList(TestPlugin.Fields.NAME), // JVM Plugin
Lists.newArrayList(TestPlugin.Fields.DESCRIPTION),
Lists.newArrayList(PluginInfo.VERSION_NOT_AVAILABLE),
Lists.newArrayList(Boolean.FALSE),
Collections.EMPTY_LIST, Collections.EMPTY_LIST, Collections.EMPTY_LIST, Collections.EMPTY_LIST);// No site Plugin
Collections.EMPTY_LIST, Collections.EMPTY_LIST, Collections.EMPTY_LIST);// No site Plugin
assertNodeContainsPlugins(response, server4NodeId,
Lists.newArrayList(TestNoVersionPlugin.Fields.NAME), // JVM Plugin
Lists.newArrayList(TestNoVersionPlugin.Fields.DESCRIPTION),
Lists.newArrayList(PluginInfo.VERSION_NOT_AVAILABLE),
Lists.newArrayList(Boolean.FALSE),
Lists.newArrayList(Fields.SITE_PLUGIN, TestNoVersionPlugin.Fields.NAME),// Site Plugin
Lists.newArrayList(PluginInfo.DESCRIPTION_NOT_AVAILABLE),
Lists.newArrayList(PluginInfo.VERSION_NOT_AVAILABLE),
Lists.newArrayList(Boolean.FALSE));
Lists.newArrayList(PluginInfo.VERSION_NOT_AVAILABLE));
}
private void assertNodeContainsPlugins(NodesInfoResponse response, String nodeId,
List<String> expectedJvmPluginNames,
List<String> expectedJvmPluginDescriptions,
List<String> expectedJvmVersions,
List<Boolean> expectedJvmIsolations,
List<String> expectedSitePluginNames,
List<String> expectedSitePluginDescriptions,
List<String> expectedSiteVersions,
List<Boolean> expectedSiteIsolations) {
List<String> expectedSiteVersions) {
assertThat(response.getNodesMap().get(nodeId), notNullValue());
@ -190,11 +184,6 @@ public class SimpleNodesInfoTests extends ElasticsearchIntegrationTest {
assertThat(jvmPluginVersions, hasItem(pluginVersion));
}
List<Boolean> jvmPluginIsolations = FluentIterable.from(plugins.getInfos()).filter(jvmPluginPredicate).transform(isolationFunction).toList();
for (Boolean pluginIsolation : expectedJvmIsolations) {
assertThat(jvmPluginIsolations, hasItem(pluginIsolation));
}
FluentIterable<String> jvmUrls = FluentIterable.from(plugins.getInfos())
.filter(and(jvmPluginPredicate, Predicates.not(sitePluginPredicate)))
.filter(isNull())
@ -219,11 +208,6 @@ public class SimpleNodesInfoTests extends ElasticsearchIntegrationTest {
for (String pluginVersion : expectedSiteVersions) {
assertThat(sitePluginVersions, hasItem(pluginVersion));
}
List<Boolean> sitePluginIsolations = FluentIterable.from(plugins.getInfos()).filter(sitePluginPredicate).transform(isolationFunction).toList();
for (Boolean pluginIsolation : expectedSiteIsolations) {
assertThat(sitePluginIsolations, hasItem(pluginIsolation));
}
}
private String startNodeWithPlugins(int nodeId, String ... pluginClassNames) throws URISyntaxException {
@ -283,10 +267,4 @@ public class SimpleNodesInfoTests extends ElasticsearchIntegrationTest {
return pluginInfo.getVersion();
}
};
private Function<PluginInfo, Boolean> isolationFunction = new Function<PluginInfo, Boolean>() {
public Boolean apply(PluginInfo pluginInfo) {
return pluginInfo.isIsolation();
}
};
}

View File

@ -1,133 +0,0 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.plugins;
import com.google.common.io.Files;
import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.FileSystemUtils;
import org.elasticsearch.common.io.Streams;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.test.ElasticsearchIntegrationTest;
import org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Properties;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
// NB: the tests uses System Properties to pass the information from different plugins (loaded in separate CLs) to the test.
// hence the use of try/finally blocks to clean these up after the test has been executed as otherwise the test framework will trigger a failure
@ClusterScope(scope = ElasticsearchIntegrationTest.Scope.TEST, numNodes = 0)
public class IsolatedPluginTests extends ElasticsearchIntegrationTest {
private static final Settings SETTINGS;
private static final File PLUGIN_DIR;
private Properties props;
static {
PLUGIN_DIR = Files.createTempDir();
SETTINGS = ImmutableSettings.settingsBuilder()
.put("discovery.zen.ping.multicast.enabled", false)
.put("force.http.enabled", true)
.put("plugins.isolation", true)
.put("path.plugins", PLUGIN_DIR.getAbsolutePath())
.build();
}
@Before
public void beforeTest() throws Exception {
props = new Properties();
props.putAll(System.getProperties());
logger.info("Installing plugins into folder {}", PLUGIN_DIR);
FileSystemUtils.mkdirs(PLUGIN_DIR);
// copy plugin
copyPlugin(new File(PLUGIN_DIR, "plugin-v1"));
copyPlugin(new File(PLUGIN_DIR, "plugin-v2"));
}
private void copyPlugin(File p1) throws IOException {
FileSystemUtils.mkdirs(p1);
// copy plugin
File dir = new File(p1, "org/elasticsearch/plugins/isolation/");
FileSystemUtils.mkdirs(dir);
copyFile(getClass().getResourceAsStream("isolation/DummyClass.class"), new File(dir, "DummyClass.class"));
copyFile(getClass().getResourceAsStream("isolation/IsolatedPlugin.class"), new File(dir, "IsolatedPlugin.class"));
copyFile(getClass().getResourceAsStream("isolation/es-plugin.properties"), new File(p1, "es-plugin.properties"));
}
private void copyFile(InputStream source, File out) throws IOException {
out.createNewFile();
Streams.copy(source, new FileOutputStream(out));
}
@After
public void afterTest() throws Exception {
FileSystemUtils.deleteRecursively(PLUGIN_DIR);
}
protected Settings nodeSettings(int nodeOrdinal) {
return SETTINGS;
}
@Test
public void testPluginNumberOfIsolatedInstances() throws Exception {
try {
NodesInfoResponse nodesInfoResponse = client().admin().cluster().prepareNodesInfo().clear().setPlugins(true).get();
assertThat(nodesInfoResponse.getNodes().length, equalTo(1));
assertThat(nodesInfoResponse.getNodes()[0].getPlugins().getInfos(), notNullValue());
assertThat(nodesInfoResponse.getNodes()[0].getPlugins().getInfos().size(), equalTo(2));
} finally {
System.setProperties(props);
}
}
@Test
public void testIsolatedPluginProperties() throws Exception {
try {
String prop = "es.test.isolated.plugin.count";
int count = Integer.getInteger(prop, 0);
client();
// do a >= comparison in case there are multiple tests running in the same JVM (build server)
assertThat(Integer.getInteger(prop), greaterThanOrEqualTo(count + 2));
Properties p = System.getProperties();
prop = p.getProperty("es.test.isolated.plugin.instantiated.hashes");
String[] hashes = Strings.delimitedListToStringArray(prop, " ");
// 2 plugins plus trailing space
assertThat(hashes.length, greaterThanOrEqualTo(count + 2));
Arrays.sort(hashes);
assertThat(Arrays.binarySearch(hashes, p.getProperty("es.test.isolated.plugin.instantiated")), greaterThanOrEqualTo(0));
} finally {
System.setProperties(props);
}
}
}

View File

@ -1,103 +0,0 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.plugins;
import org.junit.Before;
import org.junit.Test;
import java.net.URL;
import java.util.Properties;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.*;
public class PluginClassLoaderTest {
private static final String NAME = "es.test.isolated.plugin.name";
private static final String INSTANCE = "es.test.isolated.plugin.instantiated";
private static final String READ = "es.test.isolated.plugin.read.name";
private URL root;
private String clazz = getClass().getName().replace('.', '/').concat(".class");
private ClassLoader parent;
@Before
public void before() throws Exception {
System.getProperties().setProperty(READ, "");
System.getProperties().setProperty(NAME, "");
System.getProperties().setProperty(INSTANCE, "");
parent = getClass().getClassLoader();
URL url = parent.getResource(clazz);
root = new URL(url.toString().substring(0, url.toString().indexOf(clazz)));
}
@Test
public void testClassSpaceIsolationEvenWhenPointingToSameClass() throws Exception {
PluginClassLoader space1 = new PluginClassLoader(new URL[] { root }, null);
Properties props = System.getProperties();
assertThat(props.getProperty(READ), is(""));
props.setProperty(NAME, "one");
Class<?> class1 = Class.forName("org.elasticsearch.plugins.isolation.DummyClass", true, space1);
assertThat(props.getProperty(READ), is("one"));
String instance1 = props.getProperty(INSTANCE);
PluginClassLoader space2 = new PluginClassLoader(new URL[] { root }, null);
props.setProperty(NAME, "two");
Class<?> class2 = Class.forName("org.elasticsearch.plugins.isolation.DummyClass", true, space2);
assertThat(props.getProperty(READ), is("two"));
String instance2 = props.getProperty(INSTANCE);
assertThat(instance1, not(instance2));
assertNotSame(class1, class2);
}
@Test
public void testDelegateToParentIfResourcesNotFoundLocally() throws Exception {
PluginClassLoader space1 = new PluginClassLoader(new URL[] {}, null);
assertNull(space1.getResource(clazz));
PluginClassLoader space2 = new PluginClassLoader(new URL[] {}, parent);
assertNotNull(parent.getResource(clazz));
assertNotNull(space2.getResource(clazz));
}
@Test
public void testIgnoreClassesInParentIfFoundLocally() throws Exception {
ClassLoader parent = getClass().getClassLoader();
PluginClassLoader space1 = new PluginClassLoader(new URL[] { root }, parent);
Properties props = System.getProperties();
assertThat(props.getProperty(READ), is(""));
props.setProperty(NAME, "one");
String before = props.getProperty(NAME);
Class parentClass = Class.forName("org.elasticsearch.plugins.isolation.DummyClass", true, parent);
props.setProperty(NAME, "one");
Class class1 = Class.forName("org.elasticsearch.plugins.isolation.DummyClass", true, space1);
assertThat(props.getProperty(READ), is(before));
assertThat(class1, is(not(parentClass)));
}
}

View File

@ -1,51 +0,0 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.plugins.isolation;
import java.util.Properties;
public class DummyClass {
static final String name;
static {
Properties sysProps = System.getProperties();
// make sure to get a string even when dealing with null
name = "" + sysProps.getProperty("es.test.isolated.plugin.name");
sysProps.setProperty("es.test.isolated.plugin.instantiated", "" + DummyClass.class.hashCode());
Integer count = Integer.getInteger("es.test.isolated.plugin.count");
if (count == null) {
count = Integer.valueOf(0);
}
count = count + 1;
sysProps.setProperty("es.test.isolated.plugin.count", count.toString());
String prop = sysProps.getProperty("es.test.isolated.plugin.instantiated.hashes");
if (prop == null) {
prop = "";
}
prop = prop + DummyClass.class.hashCode() + " ";
sysProps.setProperty("es.test.isolated.plugin.instantiated.hashes", prop);
sysProps.setProperty("es.test.isolated.plugin.read.name", name);
}
}

View File

@ -1,40 +0,0 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.plugins.isolation;
import org.elasticsearch.plugins.AbstractPlugin;
public class IsolatedPlugin extends AbstractPlugin {
private final DummyClass dummy;
public IsolatedPlugin() {
dummy = new DummyClass();
}
@Override
public String name() {
return dummy.name;
}
@Override
public String description() {
return "IsolatedPlugin " + hashCode();
}
}

View File

@ -1 +0,0 @@
plugin=org.elasticsearch.plugins.isolation.IsolatedPlugin