From 36b92be212e884daff251cc2f3f85fb982e5c85d Mon Sep 17 00:00:00 2001 From: David Pilato Date: Fri, 15 Mar 2013 19:28:01 +0100 Subject: [PATCH] List of existing plugins with Node Info API We want to display information about loaded plugins in Node Info API using plugin option: ```sh curl http://localhost:9200/_nodes?plugin=true ``` For example, on a 4 nodes cluster, it could provide the following output: ```javascript { "ok" : true, "cluster_name" : "test-cluster-MacBook-Air-de-David.local", "nodes" : { "lodYfbFTRnmwE6rjWGGyQQ" : { "name" : "node1", "transport_address" : "inet[/172.18.58.139:9300]", "hostname" : "MacBook-Air-de-David.local", "version" : "0.90.0.Beta2-SNAPSHOT", "http_address" : "inet[/172.18.58.139:9200]", "plugins" : [ ] }, "hJLXmY_NTrCytiIMbX4_1g" : { "name" : "node4", "transport_address" : "inet[/172.18.58.139:9303]", "hostname" : "MacBook-Air-de-David.local", "version" : "0.90.0.Beta2-SNAPSHOT", "http_address" : "inet[/172.18.58.139:9203]", "plugins" : [ { "name" : "test-plugin", "description" : "test-plugin description", "site" : true, "jvm" : false }, { "name" : "test-no-version-plugin", "description" : "test-no-version-plugin description", "site" : true, "jvm" : false }, { "name" : "dummy", "description" : "No description found for dummy.", "url" : "/_plugin/dummy/", "site" : false, "jvm" : true } ] }, "bnoySsBfTrSzbDRZ0BFHvg" : { "name" : "node2", "transport_address" : "inet[/172.18.58.139:9301]", "hostname" : "MacBook-Air-de-David.local", "version" : "0.90.0.Beta2-SNAPSHOT", "http_address" : "inet[/172.18.58.139:9201]", "plugins" : [ { "name" : "dummy", "description" : "This is a description for a dummy test site plugin.", "url" : "/_plugin/dummy/", "site" : false, "jvm" : true } ] }, "0Vwil01LSfK9YgRrMce3Ug" : { "name" : "node3", "transport_address" : "inet[/172.18.58.139:9302]", "hostname" : "MacBook-Air-de-David.local", "version" : "0.90.0.Beta2-SNAPSHOT", "http_address" : "inet[/172.18.58.139:9202]", "plugins" : [ { "name" : "test-plugin", "description" : "test-plugin description", "site" : true, "jvm" : false } ] } } } ``` Information are cached for 10 seconds by default. Modify `plugins.info_refresh_interval` property if needed. Setting `plugins.info_refresh_interval` to `-1` will cause infinite caching. Setting `plugins.info_refresh_interval` to `0` will disable caching. Closes #2668. --- .../admin/cluster/node/info/NodeInfo.java | 21 ++- .../cluster/node/info/NodesInfoRequest.java | 22 +++ .../node/info/NodesInfoRequestBuilder.java | 4 + .../cluster/node/info/NodesInfoResponse.java | 17 +++ .../admin/cluster/node/info/PluginInfo.java | 120 ++++++++++++++++ .../admin/cluster/node/info/PluginsInfo.java | 70 ++++++++++ .../node/info/TransportNodesInfoAction.java | 3 +- .../node/service/NodeService.java | 17 ++- .../elasticsearch/plugins/PluginsService.java | 106 ++++++++++++++- .../node/info/RestNodesInfoAction.java | 13 ++ .../nodesinfo/SimpleNodesInfoTests.java | 128 +++++++++++++++++- .../nodesinfo/plugin/dummy1/TestPlugin.java | 40 ++++++ .../plugin/dummy2/TestNoVersionPlugin.java | 40 ++++++ .../node2/dummy/_site/es-plugin.properties | 20 +++ .../nodesinfo/node2/dummy/_site/index.html | 9 ++ .../node3/test-plugin/es-plugin.properties | 20 +++ .../nodesinfo/node4/dummy/_site/index.html | 9 ++ .../test-no-version-plugin/_site/index.html | 9 ++ .../es-plugin.properties | 19 +++ 19 files changed, 674 insertions(+), 13 deletions(-) create mode 100644 src/main/java/org/elasticsearch/action/admin/cluster/node/info/PluginInfo.java create mode 100644 src/main/java/org/elasticsearch/action/admin/cluster/node/info/PluginsInfo.java create mode 100644 src/test/java/org/elasticsearch/test/integration/nodesinfo/plugin/dummy1/TestPlugin.java create mode 100644 src/test/java/org/elasticsearch/test/integration/nodesinfo/plugin/dummy2/TestNoVersionPlugin.java create mode 100644 src/test/resources/org/elasticsearch/test/integration/nodesinfo/node2/dummy/_site/es-plugin.properties create mode 100644 src/test/resources/org/elasticsearch/test/integration/nodesinfo/node2/dummy/_site/index.html create mode 100644 src/test/resources/org/elasticsearch/test/integration/nodesinfo/node3/test-plugin/es-plugin.properties create mode 100644 src/test/resources/org/elasticsearch/test/integration/nodesinfo/node4/dummy/_site/index.html create mode 100644 src/test/resources/org/elasticsearch/test/integration/nodesinfo/node4/test-no-version-plugin/_site/index.html create mode 100644 src/test/resources/org/elasticsearch/test/integration/nodesinfo/node4/test-no-version-plugin/es-plugin.properties diff --git a/src/main/java/org/elasticsearch/action/admin/cluster/node/info/NodeInfo.java b/src/main/java/org/elasticsearch/action/admin/cluster/node/info/NodeInfo.java index 95071b22278..2fe5676234d 100644 --- a/src/main/java/org/elasticsearch/action/admin/cluster/node/info/NodeInfo.java +++ b/src/main/java/org/elasticsearch/action/admin/cluster/node/info/NodeInfo.java @@ -76,12 +76,15 @@ public class NodeInfo extends NodeOperationResponse { @Nullable private HttpInfo http; + @Nullable + private PluginsInfo plugins; + NodeInfo() { } public NodeInfo(@Nullable String hostname, Version version, DiscoveryNode node, @Nullable ImmutableMap serviceAttributes, @Nullable Settings settings, @Nullable OsInfo os, @Nullable ProcessInfo process, @Nullable JvmInfo jvm, @Nullable ThreadPoolInfo threadPool, @Nullable NetworkInfo network, - @Nullable TransportInfo transport, @Nullable HttpInfo http) { + @Nullable TransportInfo transport, @Nullable HttpInfo http, @Nullable PluginsInfo plugins) { super(node); this.hostname = hostname; this.version = version; @@ -94,6 +97,7 @@ public class NodeInfo extends NodeOperationResponse { this.network = network; this.transport = transport; this.http = http; + this.plugins = plugins; } /** @@ -174,6 +178,11 @@ public class NodeInfo extends NodeOperationResponse { return http; } + @Nullable + public PluginsInfo getPlugins() { + return this.plugins; + } + public static NodeInfo readNodeInfo(StreamInput in) throws IOException { NodeInfo nodeInfo = new NodeInfo(); nodeInfo.readFrom(in); @@ -219,6 +228,9 @@ public class NodeInfo extends NodeOperationResponse { if (in.readBoolean()) { http = HttpInfo.readHttpInfo(in); } + if (in.readBoolean()) { + plugins = PluginsInfo.readPluginsInfo(in); + } } @Override @@ -289,5 +301,12 @@ public class NodeInfo extends NodeOperationResponse { out.writeBoolean(true); http.writeTo(out); } + if (plugins == null) { + out.writeBoolean(false); + } else { + out.writeBoolean(true); + plugins.writeTo(out); + } } + } diff --git a/src/main/java/org/elasticsearch/action/admin/cluster/node/info/NodesInfoRequest.java b/src/main/java/org/elasticsearch/action/admin/cluster/node/info/NodesInfoRequest.java index 561aacb9a1a..36de439b35d 100644 --- a/src/main/java/org/elasticsearch/action/admin/cluster/node/info/NodesInfoRequest.java +++ b/src/main/java/org/elasticsearch/action/admin/cluster/node/info/NodesInfoRequest.java @@ -38,6 +38,7 @@ public class NodesInfoRequest extends NodesOperationRequest { private boolean network = false; private boolean transport = false; private boolean http = false; + private boolean plugin = false; public NodesInfoRequest() { } @@ -62,6 +63,7 @@ public class NodesInfoRequest extends NodesOperationRequest { network = false; transport = false; http = false; + plugin = false; return this; } @@ -77,6 +79,7 @@ public class NodesInfoRequest extends NodesOperationRequest { network = true; transport = true; http = true; + plugin = true; return this; } @@ -200,6 +203,23 @@ public class NodesInfoRequest extends NodesOperationRequest { return this; } + /** + * Should information about plugins be returned + * @param plugin true if you want info + * @return The request + */ + public NodesInfoRequest plugin(boolean plugin) { + this.plugin = plugin; + return this; + } + + /** + * @return true if information about plugins is requested + */ + public boolean plugin() { + return plugin; + } + @Override public void readFrom(StreamInput in) throws IOException { super.readFrom(in); @@ -211,6 +231,7 @@ public class NodesInfoRequest extends NodesOperationRequest { network = in.readBoolean(); transport = in.readBoolean(); http = in.readBoolean(); + plugin = in.readBoolean(); } @Override @@ -224,5 +245,6 @@ public class NodesInfoRequest extends NodesOperationRequest { out.writeBoolean(network); out.writeBoolean(transport); out.writeBoolean(http); + out.writeBoolean(plugin); } } diff --git a/src/main/java/org/elasticsearch/action/admin/cluster/node/info/NodesInfoRequestBuilder.java b/src/main/java/org/elasticsearch/action/admin/cluster/node/info/NodesInfoRequestBuilder.java index a6acaf8ddf6..6408e4a7ff4 100644 --- a/src/main/java/org/elasticsearch/action/admin/cluster/node/info/NodesInfoRequestBuilder.java +++ b/src/main/java/org/elasticsearch/action/admin/cluster/node/info/NodesInfoRequestBuilder.java @@ -113,6 +113,10 @@ public class NodesInfoRequestBuilder extends NodesOperationRequestBuilder listener) { diff --git a/src/main/java/org/elasticsearch/action/admin/cluster/node/info/NodesInfoResponse.java b/src/main/java/org/elasticsearch/action/admin/cluster/node/info/NodesInfoResponse.java index c27eac82e21..d44a6a96534 100644 --- a/src/main/java/org/elasticsearch/action/admin/cluster/node/info/NodesInfoResponse.java +++ b/src/main/java/org/elasticsearch/action/admin/cluster/node/info/NodesInfoResponse.java @@ -27,6 +27,7 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.SettingsFilter; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentFactory; import java.io.IOException; import java.util.Map; @@ -132,10 +133,26 @@ public class NodesInfoResponse extends NodesOperationResponse implemen if (nodeInfo.getHttp() != null) { nodeInfo.getHttp().toXContent(builder, params); } + if (nodeInfo.getPlugins() != null) { + nodeInfo.getPlugins().toXContent(builder, params); + } builder.endObject(); } builder.endObject(); return builder; } + + @Override + public String toString() { + try { + XContentBuilder builder = XContentFactory.jsonBuilder().prettyPrint(); + builder.startObject(); + toXContent(builder, EMPTY_PARAMS); + builder.endObject(); + return builder.string(); + } catch (IOException e) { + return "{ \"error\" : \"" + e.getMessage() + "\"}"; + } + } } diff --git a/src/main/java/org/elasticsearch/action/admin/cluster/node/info/PluginInfo.java b/src/main/java/org/elasticsearch/action/admin/cluster/node/info/PluginInfo.java new file mode 100644 index 00000000000..2159bb989fe --- /dev/null +++ b/src/main/java/org/elasticsearch/action/admin/cluster/node/info/PluginInfo.java @@ -0,0 +1,120 @@ +package org.elasticsearch.action.admin.cluster.node.info; + +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.io.stream.Streamable; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentBuilderString; + +import java.io.IOException; +import java.io.Serializable; + +public class PluginInfo implements Streamable, Serializable, ToXContent { + static final class Fields { + static final XContentBuilderString NAME = new XContentBuilderString("name"); + static final XContentBuilderString DESCRIPTION = new XContentBuilderString("description"); + static final XContentBuilderString URL = new XContentBuilderString("url"); + static final XContentBuilderString JVM = new XContentBuilderString("jvm"); + static final XContentBuilderString SITE = new XContentBuilderString("site"); + } + + private String name; + private String description; + private boolean site; + private boolean jvm; + + public PluginInfo() { + } + + /** + * Information about plugins + * @param name Its name + * @param description Its description + * @param site true if it's a site plugin + * @param jvm true if it's a jvm plugin + */ + public PluginInfo(String name, String description, boolean site, boolean jvm) { + this.name = name; + this.description = description; + this.site = site; + this.jvm = jvm; + } + + /** + * @return Plugin's name + */ + public String getName() { + return name; + } + + /** + * @return Plugin's description if any + */ + public String getDescription() { + return description; + } + + /** + * @return true is it's a site plugin + */ + public boolean isSite() { + return site; + } + + /** + * @return true if it's a plugin running in the jvm + */ + public boolean isJvm() { + return jvm; + } + + /** + * We compute the URL for sites: "/_plugin/" + name + "/" + * @return + */ + public String getUrl() { + if (site) { + return ("/_plugin/" + name + "/"); + } else { + return null; + } + } + + public static PluginInfo readPluginInfo(StreamInput in) throws IOException { + PluginInfo info = new PluginInfo(); + info.readFrom(in); + return info; + } + + @Override + public void readFrom(StreamInput in) throws IOException { + this.name = in.readString(); + this.description = in.readString(); + this.site = in.readBoolean(); + this.jvm = in.readBoolean(); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(name); + out.writeString(description); + out.writeBoolean(site); + out.writeBoolean(jvm); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field(Fields.NAME, name); + builder.field(Fields.DESCRIPTION, description); + if (site) { + builder.field(Fields.URL, getUrl()); + } + builder.field(Fields.JVM, jvm); + builder.field(Fields.SITE, site); + builder.endObject(); + + return builder; + } +} diff --git a/src/main/java/org/elasticsearch/action/admin/cluster/node/info/PluginsInfo.java b/src/main/java/org/elasticsearch/action/admin/cluster/node/info/PluginsInfo.java new file mode 100644 index 00000000000..13163f08cc7 --- /dev/null +++ b/src/main/java/org/elasticsearch/action/admin/cluster/node/info/PluginsInfo.java @@ -0,0 +1,70 @@ +package org.elasticsearch.action.admin.cluster.node.info; + +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.io.stream.Streamable; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentBuilderString; + +import java.io.IOException; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +public class PluginsInfo implements Streamable, Serializable, ToXContent { + static final class Fields { + static final XContentBuilderString PLUGINS = new XContentBuilderString("plugins"); + } + + private List infos; + + public PluginsInfo() { + infos = new ArrayList(); + } + + public PluginsInfo(int size) { + infos = new ArrayList(size); + } + + public List getInfos() { + return infos; + } + + public void add(PluginInfo info) { + infos.add(info); + } + + public static PluginsInfo readPluginsInfo(StreamInput in) throws IOException { + PluginsInfo infos = new PluginsInfo(); + infos.readFrom(in); + return infos; + } + + @Override + public void readFrom(StreamInput in) throws IOException { + int plugins_size = in.readInt(); + for (int i = 0; i < plugins_size; i++) { + infos.add(PluginInfo.readPluginInfo(in)); + } + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeInt(infos.size()); + for (PluginInfo plugin : infos) { + plugin.writeTo(out); + } + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startArray(Fields.PLUGINS); + for (PluginInfo pluginInfo : infos) { + pluginInfo.toXContent(builder, params); + } + builder.endArray(); + + return builder; + } +} diff --git a/src/main/java/org/elasticsearch/action/admin/cluster/node/info/TransportNodesInfoAction.java b/src/main/java/org/elasticsearch/action/admin/cluster/node/info/TransportNodesInfoAction.java index d608785d938..84017b07b57 100644 --- a/src/main/java/org/elasticsearch/action/admin/cluster/node/info/TransportNodesInfoAction.java +++ b/src/main/java/org/elasticsearch/action/admin/cluster/node/info/TransportNodesInfoAction.java @@ -97,7 +97,8 @@ public class TransportNodesInfoAction extends TransportNodesOperationAction> onModuleReferences; + private PluginsInfo cachedPluginsInfo; + private final TimeValue refreshInterval; + private long lastRefresh; + static class OnModuleReference { public final Class moduleClass; public final Method onModuleMethod; @@ -128,6 +134,9 @@ public class PluginsService extends AbstractComponent { } } this.onModuleReferences = onModuleReferences.immutableMap(); + + this.refreshInterval = componentSettings.getAsTime("info_refresh_interval", TimeValue.timeValueSeconds(10)); + } public ImmutableMap plugins() { @@ -240,6 +249,93 @@ public class PluginsService extends AbstractComponent { return services; } + /** + * Get information about plugins (jvm and site plugins). + * Information are cached for 10 seconds by default. Modify `plugins.info_refresh_interval` property if needed. + * Setting `plugins.info_refresh_interval` to `-1` will cause infinite caching. + * Setting `plugins.info_refresh_interval` to `0` will disable caching. + * @return List of plugins information + */ + synchronized public PluginsInfo info() { + if (refreshInterval.millis() != 0) { + if (cachedPluginsInfo != null && + (refreshInterval.millis() < 0 || (System.currentTimeMillis() - lastRefresh) < refreshInterval.millis())) { + if (logger.isTraceEnabled()) logger.trace("using cache to retrieve plugins info"); + return cachedPluginsInfo; + } + lastRefresh = System.currentTimeMillis(); + } + + if (logger.isTraceEnabled()) logger.trace("starting to fetch info on plugins"); + cachedPluginsInfo = new PluginsInfo(); + + // We create a map to have only unique values + Set plugins = new HashSet(); + + for (Plugin plugin : plugins().values()) { + // We should detect if the plugin has also an embedded _site structure + File siteFile = new File(new File(environment.pluginsFile(), plugin.name()), "_site"); + boolean isSite = siteFile.exists() && siteFile.isDirectory(); + if (logger.isTraceEnabled()) logger.trace("found a jvm plugin [{}], [{}]{}", + plugin.name(), plugin.description(), isSite ? ": with _site structure" : ""); + cachedPluginsInfo.add(new PluginInfo(plugin.name(), plugin.description(), isSite, true)); + plugins.add(plugin.name()); + } + + File pluginsFile = environment.pluginsFile(); + if (!pluginsFile.exists()) { + return cachedPluginsInfo; + } + if (!pluginsFile.isDirectory()) { + return cachedPluginsInfo; + } + + File[] pluginsFiles = pluginsFile.listFiles(); + if (pluginsFiles != null) { + for (File plugin : pluginsFiles) { + // We skip already known jvm plugins + if (!plugins.contains(plugin.getName())) { + File sitePluginDir = new File(plugin, "_site"); + if (sitePluginDir.exists()) { + String name = plugin.getName(); + String description = "No description found for " + name + "."; + + // We check if es-plugin.properties exists in plugin/_site dir + File pluginPropFile = new File(sitePluginDir, ES_PLUGIN_PROPERTIES); + if (pluginPropFile.exists()) { + + Properties pluginProps = new Properties(); + InputStream is = null; + try { + is = new FileInputStream(pluginPropFile.getAbsolutePath()); + pluginProps.load(is); + description = pluginProps.getProperty("description"); + } catch (Exception e) { + logger.warn("failed to load plugin description from [" + + pluginPropFile.getAbsolutePath() + "]", e); + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + // ignore + } + } + } + } + + if (logger.isTraceEnabled()) logger.trace("found a site plugin [{}], [{}]", + name, description); + cachedPluginsInfo.add(new PluginInfo(name, description, true, false)); + } + } + } + } + + + return cachedPluginsInfo; + } + private void loadPluginsIntoClassLoader() { File pluginsFile = environment.pluginsFile(); if (!pluginsFile.exists()) { @@ -302,7 +398,7 @@ public class PluginsService extends AbstractComponent { Map plugins = newHashMap(); Enumeration pluginUrls = null; try { - pluginUrls = settings.getClassLoader().getResources("es-plugin.properties"); + pluginUrls = settings.getClassLoader().getResources(ES_PLUGIN_PROPERTIES); } catch (IOException e) { logger.warn("failed to find plugins from classpath", e); return ImmutableMap.of(); diff --git a/src/main/java/org/elasticsearch/rest/action/admin/cluster/node/info/RestNodesInfoAction.java b/src/main/java/org/elasticsearch/rest/action/admin/cluster/node/info/RestNodesInfoAction.java index 5107783dd26..20057d11d33 100644 --- a/src/main/java/org/elasticsearch/rest/action/admin/cluster/node/info/RestNodesInfoAction.java +++ b/src/main/java/org/elasticsearch/rest/action/admin/cluster/node/info/RestNodesInfoAction.java @@ -73,6 +73,9 @@ public class RestNodesInfoAction extends BaseRestHandler { controller.registerHandler(RestRequest.Method.GET, "/_nodes/http", new RestHttpHandler()); controller.registerHandler(RestRequest.Method.GET, "/_nodes/{nodeId}/http", new RestHttpHandler()); + controller.registerHandler(RestRequest.Method.GET, "/_nodes/plugin", new RestPluginHandler()); + controller.registerHandler(RestRequest.Method.GET, "/_nodes/{nodeId}/plugin", new RestPluginHandler()); + this.settingsFilter = settingsFilter; } @@ -97,6 +100,7 @@ public class RestNodesInfoAction extends BaseRestHandler { nodesInfoRequest.network(request.paramAsBoolean("network", nodesInfoRequest.network())); nodesInfoRequest.transport(request.paramAsBoolean("transport", nodesInfoRequest.transport())); nodesInfoRequest.http(request.paramAsBoolean("http", nodesInfoRequest.http())); + nodesInfoRequest.plugin(request.paramAsBoolean("plugin", nodesInfoRequest.plugin())); executeNodeRequest(request, channel, nodesInfoRequest); } @@ -201,4 +205,13 @@ public class RestNodesInfoAction extends BaseRestHandler { executeNodeRequest(request, channel, nodesInfoRequest); } } + + class RestPluginHandler implements RestHandler { + @Override + public void handleRequest(final RestRequest request, final RestChannel channel) { + NodesInfoRequest nodesInfoRequest = new NodesInfoRequest(RestActions.splitNodes(request.param("nodeId"))); + nodesInfoRequest.clear().plugin(true); + executeNodeRequest(request, channel, nodesInfoRequest); + } + } } diff --git a/src/test/java/org/elasticsearch/test/integration/nodesinfo/SimpleNodesInfoTests.java b/src/test/java/org/elasticsearch/test/integration/nodesinfo/SimpleNodesInfoTests.java index cd1b43e3d65..c289f02c5b4 100644 --- a/src/test/java/org/elasticsearch/test/integration/nodesinfo/SimpleNodesInfoTests.java +++ b/src/test/java/org/elasticsearch/test/integration/nodesinfo/SimpleNodesInfoTests.java @@ -21,23 +21,39 @@ package org.elasticsearch.test.integration.nodesinfo; import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse; +import org.elasticsearch.action.admin.cluster.node.info.PluginInfo; +import org.elasticsearch.action.admin.cluster.node.info.PluginsInfo; import org.elasticsearch.cluster.ClusterService; +import org.elasticsearch.common.settings.ImmutableSettings; import org.elasticsearch.node.internal.InternalNode; import org.elasticsearch.test.integration.AbstractNodesTests; +import org.elasticsearch.test.integration.nodesinfo.plugin.dummy1.TestPlugin; +import org.elasticsearch.test.integration.nodesinfo.plugin.dummy2.TestNoVersionPlugin; import org.testng.annotations.AfterMethod; import org.testng.annotations.Test; +import java.io.File; +import java.net.URISyntaxException; +import java.net.URL; + import static org.elasticsearch.client.Requests.clusterHealthRequest; import static org.elasticsearch.client.Requests.nodesInfoRequest; +import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.*; /** * */ public class SimpleNodesInfoTests extends AbstractNodesTests { + static final class Fields { + static final String SITE_PLUGIN = "dummy"; + static final String SITE_PLUGIN_DESCRIPTION = "This is a description for a dummy test site plugin."; + static final String SITE_PLUGIN_NO_DESCRIPTION = "No description found for dummy."; + static final String JVM_PLUGIN_NO_DESCRIPTION = "No description found for test-no-version-plugin."; + } + @AfterMethod public void closeNodes() { closeAllNodes(); @@ -81,4 +97,112 @@ public class SimpleNodesInfoTests extends AbstractNodesTests { assertThat(response.getNodes().length, equalTo(1)); assertThat(response.getNodesMap().get(server2NodeId), notNullValue()); } + + /** + * Use case is to start 4 nodes: + *
    + *
  • 1 : no plugin
  • + *
  • 2 : one site plugin (with a es-plugin.properties file)
  • + *
  • 3 : one java plugin
  • + *
  • 4 : one site plugin and 2 java plugins (included the previous one)
  • + *
+ * We test here that NodeInfo API with plugin option give us the right results. + * @throws URISyntaxException + */ + @Test + public void testNodeInfoPlugin() throws URISyntaxException { + // We start four nodes + // The first has no plugin + String server1NodeId = startNodeWithPlugins("node1"); + // The second has one site plugin with a es-plugin.properties file (description and version) + String server2NodeId = startNodeWithPlugins("node2"); + // The third has one java plugin + String server3NodeId = startNodeWithPlugins("node3"); + // The fourth has one java plugin and one site plugin + String server4NodeId = startNodeWithPlugins("node4"); + + ClusterHealthResponse clusterHealth = client("node4").admin().cluster().health(clusterHealthRequest().waitForGreenStatus()).actionGet(); + logger.info("--> done cluster_health, status " + clusterHealth.getStatus()); + + NodesInfoResponse response = client("node1").admin().cluster().prepareNodesInfo().setPlugin(true).execute().actionGet(); + logger.info("--> full json answer, status " + response.toString()); + + checkPlugin(response, server1NodeId, 0, 0); + checkPlugin(response, server2NodeId, 1, 0); + checkPlugin(response, server3NodeId, 0, 1); + checkPlugin(response, server4NodeId, 1, 2); // Note that we have now 2 JVM plugins as we have already loaded one with node3 + } + + /** + * We check infos + * @param response Response + * @param nodeId NodeId we want to check + * @param expectedSitePlugins Number of site plugins expected + * @param expectedJvmPlugins Number of jvm plugins expected + */ + private void checkPlugin(NodesInfoResponse response, String nodeId, + int expectedSitePlugins, int expectedJvmPlugins) { + assertThat(response.getNodesMap().get(nodeId), notNullValue()); + + PluginsInfo plugins = response.getNodesMap().get(nodeId).getPlugins(); + assertThat(plugins, notNullValue()); + + int num_site_plugins = 0; + int num_jvm_plugins = 0; + + for (PluginInfo pluginInfo : plugins.getInfos()) { + // It should be a site or a jvm plugin + assertThat(pluginInfo.isJvm() || pluginInfo.isSite(), is(true)); + + if (pluginInfo.isSite() && !pluginInfo.isJvm()) { + // Let's do some tests for site plugins + assertThat(pluginInfo.getName(), isOneOf(Fields.SITE_PLUGIN, + TestNoVersionPlugin.Fields.NAME)); + assertThat(pluginInfo.getDescription(), + isOneOf(Fields.SITE_PLUGIN_DESCRIPTION, + Fields.SITE_PLUGIN_NO_DESCRIPTION, + Fields.JVM_PLUGIN_NO_DESCRIPTION)); + assertThat(pluginInfo.getUrl(), notNullValue()); + num_site_plugins++; + } + + if (pluginInfo.isJvm() && !pluginInfo.isSite()) { + // Let's do some tests for site plugins + assertThat(pluginInfo.getName(), + isOneOf(TestPlugin.Fields.NAME, TestNoVersionPlugin.Fields.NAME)); + assertThat(pluginInfo.getDescription(), + isOneOf(TestPlugin.Fields.DESCRIPTION, TestNoVersionPlugin.Fields.DESCRIPTION)); + assertThat(pluginInfo.getUrl(), nullValue()); + num_jvm_plugins++; + } + + // On node4, test-no-version-plugin has an embedded _site structure + if (pluginInfo.isJvm() && pluginInfo.isSite()) { + assertThat(pluginInfo.getName(), + is(TestNoVersionPlugin.Fields.NAME)); + assertThat(pluginInfo.getDescription(), + is(TestNoVersionPlugin.Fields.DESCRIPTION)); + assertThat(pluginInfo.getUrl(), notNullValue()); + num_jvm_plugins++; + } + } + + assertThat(num_site_plugins, is(expectedSitePlugins)); + assertThat(num_jvm_plugins, is(expectedJvmPlugins)); + } + + private String startNodeWithPlugins(String name) throws URISyntaxException { + URL resource = SimpleNodesInfoTests.class.getResource("/org/elasticsearch/test/integration/nodesinfo/" + name + "/"); + ImmutableSettings.Builder settings = settingsBuilder(); + if (resource != null) { + settings.put("path.plugins", new File(resource.toURI()).getAbsolutePath()); + } + + startNode(name, settings); + String serverNodeId = ((InternalNode) node(name)).injector() + .getInstance(ClusterService.class).state().nodes().localNodeId(); + logger.debug("--> server {} started" + serverNodeId); + return serverNodeId; + } + } diff --git a/src/test/java/org/elasticsearch/test/integration/nodesinfo/plugin/dummy1/TestPlugin.java b/src/test/java/org/elasticsearch/test/integration/nodesinfo/plugin/dummy1/TestPlugin.java new file mode 100644 index 00000000000..81679c2d0b6 --- /dev/null +++ b/src/test/java/org/elasticsearch/test/integration/nodesinfo/plugin/dummy1/TestPlugin.java @@ -0,0 +1,40 @@ +/* + * Licensed to ElasticSearch and Shay Banon 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.test.integration.nodesinfo.plugin.dummy1; + +import org.elasticsearch.plugins.AbstractPlugin; + +public class TestPlugin extends AbstractPlugin { + + static final public class Fields { + static public final String NAME = "test-plugin"; + static public final String DESCRIPTION = NAME + " description"; + } + + @Override + public String name() { + return Fields.NAME; + } + + @Override + public String description() { + return Fields.DESCRIPTION; + } +} diff --git a/src/test/java/org/elasticsearch/test/integration/nodesinfo/plugin/dummy2/TestNoVersionPlugin.java b/src/test/java/org/elasticsearch/test/integration/nodesinfo/plugin/dummy2/TestNoVersionPlugin.java new file mode 100644 index 00000000000..056958cb862 --- /dev/null +++ b/src/test/java/org/elasticsearch/test/integration/nodesinfo/plugin/dummy2/TestNoVersionPlugin.java @@ -0,0 +1,40 @@ +/* + * Licensed to ElasticSearch and Shay Banon 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.test.integration.nodesinfo.plugin.dummy2; + +import org.elasticsearch.plugins.AbstractPlugin; + +public class TestNoVersionPlugin extends AbstractPlugin { + + static final public class Fields { + static public final String NAME = "test-no-version-plugin"; + static public final String DESCRIPTION = NAME + " description"; + } + + @Override + public String name() { + return Fields.NAME; + } + + @Override + public String description() { + return Fields.DESCRIPTION; + } +} diff --git a/src/test/resources/org/elasticsearch/test/integration/nodesinfo/node2/dummy/_site/es-plugin.properties b/src/test/resources/org/elasticsearch/test/integration/nodesinfo/node2/dummy/_site/es-plugin.properties new file mode 100644 index 00000000000..3b346f612a8 --- /dev/null +++ b/src/test/resources/org/elasticsearch/test/integration/nodesinfo/node2/dummy/_site/es-plugin.properties @@ -0,0 +1,20 @@ +################################################################ +# Licensed to ElasticSearch and Shay Banon 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. +################################################################ +description=This is a description for a dummy test site plugin. + diff --git a/src/test/resources/org/elasticsearch/test/integration/nodesinfo/node2/dummy/_site/index.html b/src/test/resources/org/elasticsearch/test/integration/nodesinfo/node2/dummy/_site/index.html new file mode 100644 index 00000000000..ceb8774542e --- /dev/null +++ b/src/test/resources/org/elasticsearch/test/integration/nodesinfo/node2/dummy/_site/index.html @@ -0,0 +1,9 @@ + + + + Dummy Site Plugin on Node 3 + + +

Welcome to this dummy elasticsearch plugin

+ + diff --git a/src/test/resources/org/elasticsearch/test/integration/nodesinfo/node3/test-plugin/es-plugin.properties b/src/test/resources/org/elasticsearch/test/integration/nodesinfo/node3/test-plugin/es-plugin.properties new file mode 100644 index 00000000000..b09a8d0178a --- /dev/null +++ b/src/test/resources/org/elasticsearch/test/integration/nodesinfo/node3/test-plugin/es-plugin.properties @@ -0,0 +1,20 @@ +################################################################ +# Licensed to ElasticSearch and Shay Banon 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. +################################################################ +plugin=org.elasticsearch.test.integration.nodesinfo.plugin.dummy1.TestPlugin + diff --git a/src/test/resources/org/elasticsearch/test/integration/nodesinfo/node4/dummy/_site/index.html b/src/test/resources/org/elasticsearch/test/integration/nodesinfo/node4/dummy/_site/index.html new file mode 100644 index 00000000000..ceb8774542e --- /dev/null +++ b/src/test/resources/org/elasticsearch/test/integration/nodesinfo/node4/dummy/_site/index.html @@ -0,0 +1,9 @@ + + + + Dummy Site Plugin on Node 3 + + +

Welcome to this dummy elasticsearch plugin

+ + diff --git a/src/test/resources/org/elasticsearch/test/integration/nodesinfo/node4/test-no-version-plugin/_site/index.html b/src/test/resources/org/elasticsearch/test/integration/nodesinfo/node4/test-no-version-plugin/_site/index.html new file mode 100644 index 00000000000..ceb8774542e --- /dev/null +++ b/src/test/resources/org/elasticsearch/test/integration/nodesinfo/node4/test-no-version-plugin/_site/index.html @@ -0,0 +1,9 @@ + + + + Dummy Site Plugin on Node 3 + + +

Welcome to this dummy elasticsearch plugin

+ + diff --git a/src/test/resources/org/elasticsearch/test/integration/nodesinfo/node4/test-no-version-plugin/es-plugin.properties b/src/test/resources/org/elasticsearch/test/integration/nodesinfo/node4/test-no-version-plugin/es-plugin.properties new file mode 100644 index 00000000000..91b16e83ed6 --- /dev/null +++ b/src/test/resources/org/elasticsearch/test/integration/nodesinfo/node4/test-no-version-plugin/es-plugin.properties @@ -0,0 +1,19 @@ +################################################################ +# Licensed to ElasticSearch and Shay Banon 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. +################################################################ +plugin=org.elasticsearch.test.integration.nodesinfo.plugin.dummy2.TestNoVersionPlugin