Browse Source

Replace jvm-example by two plugin examples (#28339)

This pull request replaces the jvm-example plugin (from the jvm/site plugins era) by two new plugins: a custom-settings that shows how to register and use custom settings (including secured settings) in a plugin, and rest-handler plugin that shows how to register a rest handler.

The two plugins now reside in the plugins/examples project. They can serve as sample plugins for users, a special attention has been put on documentation. The packaging tests have been adapted to use the custom-settings plugin.
Tanguy Leroux 7 years ago
parent
commit
be74f11517
29 changed files with 571 additions and 201 deletions
  1. 4 2
      docs/plugins/authors.asciidoc
  2. 1 1
      docs/reference/cat/plugins.asciidoc
  3. 31 0
      plugins/examples/custom-settings/build.gradle
  4. 6 0
      plugins/examples/custom-settings/src/main/bin/test
  5. 4 0
      plugins/examples/custom-settings/src/main/bin/test.bat
  6. 5 0
      plugins/examples/custom-settings/src/main/config/custom.yml
  7. 128 0
      plugins/examples/custom-settings/src/main/java/org/elasticsearch/example/customsettings/ExampleCustomSettingsConfig.java
  8. 68 0
      plugins/examples/custom-settings/src/main/java/org/elasticsearch/example/customsettings/ExampleCustomSettingsPlugin.java
  9. 51 0
      plugins/examples/custom-settings/src/test/java/org/elasticsearch/example/customsettings/ExampleCustomSettingsClientYamlTestSuiteIT.java
  10. 44 0
      plugins/examples/custom-settings/src/test/java/org/elasticsearch/example/customsettings/ExampleCustomSettingsConfigTests.java
  11. 10 0
      plugins/examples/custom-settings/src/test/resources/rest-api-spec/test/customsettings/10_plugin.yml
  12. 54 0
      plugins/examples/custom-settings/src/test/resources/rest-api-spec/test/customsettings/10_settings.yml
  13. 8 8
      plugins/examples/rest-handler/build.gradle
  14. 10 8
      plugins/examples/rest-handler/src/main/java/org/elasticsearch/example/resthandler/ExampleCatAction.java
  15. 11 21
      plugins/examples/rest-handler/src/main/java/org/elasticsearch/example/resthandler/ExampleRestHandlerPlugin.java
  16. 12 8
      plugins/examples/rest-handler/src/test/java/org/elasticsearch/example/resthandler/ExampleFixtureIT.java
  17. 18 6
      plugins/examples/rest-handler/src/test/java/org/elasticsearch/example/resthandler/ExampleRestHandlerClientYamlTestSuiteIT.java
  18. 8 3
      plugins/examples/rest-handler/src/test/resources/rest-api-spec/api/cat.example.json
  19. 10 0
      plugins/examples/rest-handler/src/test/resources/rest-api-spec/test/resthandler/10_basic.yml
  20. 26 0
      plugins/examples/rest-handler/src/test/resources/rest-api-spec/test/resthandler/20_cat_example.yml
  21. 0 3
      plugins/jvm-example/src/main/bin/test
  22. 0 1
      plugins/jvm-example/src/main/bin/test.bat
  23. 0 1
      plugins/jvm-example/src/main/config/example.yml
  24. 0 56
      plugins/jvm-example/src/main/java/org/elasticsearch/plugin/example/ExamplePluginConfiguration.java
  25. 0 13
      plugins/jvm-example/src/test/resources/rest-api-spec/test/jvm_example/10_basic.yml
  26. 0 22
      plugins/jvm-example/src/test/resources/rest-api-spec/test/jvm_example/20_configured_example.yml
  27. 1 1
      qa/vagrant/build.gradle
  28. 42 27
      qa/vagrant/src/test/resources/packaging/tests/module_and_plugin_test_cases.bash
  29. 19 20
      qa/vagrant/src/test/resources/packaging/utils/plugins.bash

+ 4 - 2
docs/plugins/authors.asciidoc

@@ -5,8 +5,10 @@
 
 The Elasticsearch repository contains examples of:
 
-* a https://github.com/elastic/elasticsearch/tree/master/plugins/jvm-example[Java plugin]
-  which contains Java code.
+* a https://github.com/elastic/elasticsearch/tree/master/plugins/custom-settings[Java plugin]
+  which contains a plugin with custom settings.
+* a https://github.com/elastic/elasticsearch/tree/master/plugins/rest-handler[Java plugin]
+  which contains a plugin that registers a Rest handler.
 * a https://github.com/elastic/elasticsearch/tree/master/plugins/examples/rescore[Java plugin]
   which contains a rescore plugin.
 * a https://github.com/elastic/elasticsearch/tree/master/plugins/examples/script-expert-scoring[Java plugin]

+ 1 - 1
docs/reference/cat/plugins.asciidoc

@@ -27,10 +27,10 @@ U7321H6 discovery-gce           {version} The Google Compute Engine (GCE) Discov
 U7321H6 ingest-attachment       {version} Ingest processor that uses Apache Tika to extract contents
 U7321H6 ingest-geoip            {version} Ingest processor that uses looksup geo data based on ip adresses using the Maxmind geo database
 U7321H6 ingest-user-agent       {version} Ingest processor that extracts information from a user agent
-U7321H6 jvm-example             {version} Demonstrates all the pluggable Java entry points in Elasticsearch
 U7321H6 mapper-murmur3          {version} The Mapper Murmur3 plugin allows to compute hashes of a field's values at index-time and to store them in the index.
 U7321H6 mapper-size             {version} The Mapper Size plugin allows document to record their uncompressed size at index time.
 U7321H6 store-smb               {version} The Store SMB plugin adds support for SMB stores.
+U7321H6 transport-nio           {version} The nio transport.
 ------------------------------------------------------------------------------
 // TESTRESPONSE[s/([.()])/\\$1/ s/U7321H6/.+/ _cat]
 

+ 31 - 0
plugins/examples/custom-settings/build.gradle

@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+apply plugin: 'elasticsearch.esplugin'
+
+esplugin {
+  name 'custom-settings'
+  description 'An example plugin showing how to register custom settings'
+  classname 'org.elasticsearch.example.customsettings.ExampleCustomSettingsPlugin'
+}
+
+integTestCluster {
+  // Adds a setting in the Elasticsearch keystore before running the integration tests
+  keystoreSetting 'custom.secured', 'password'
+}

+ 6 - 0
plugins/examples/custom-settings/src/main/bin/test

@@ -0,0 +1,6 @@
+#!/bin/bash
+
+# Plugin can contain executable files that are copied by the plugin manager
+# to a <plugin-name>/bin folder.
+
+echo test

+ 4 - 0
plugins/examples/custom-settings/src/main/bin/test.bat

@@ -0,0 +1,4 @@
+REM Plugin can contain executable files that are copied by the plugin manager
+REM to a <plugin-name>/bin folder.
+
+echo test

+ 5 - 0
plugins/examples/custom-settings/src/main/config/custom.yml

@@ -0,0 +1,5 @@
+# Custom configuration file for the custom-settings plugin
+custom:
+    simple: foo
+    list: [0, 1, 1, 2, 3, 5, 8, 13, 21]
+    filtered: secret

+ 128 - 0
plugins/examples/custom-settings/src/main/java/org/elasticsearch/example/customsettings/ExampleCustomSettingsConfig.java

@@ -0,0 +1,128 @@
+/*
+ * 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.example.customsettings;
+
+import org.elasticsearch.ElasticsearchException;
+import org.elasticsearch.common.settings.SecureSetting;
+import org.elasticsearch.common.settings.SecureString;
+import org.elasticsearch.common.settings.Setting;
+import org.elasticsearch.common.settings.Setting.Property;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.env.Environment;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * {@link ExampleCustomSettingsConfig} contains the custom settings values and their static declarations.
+ */
+public class ExampleCustomSettingsConfig {
+
+    /**
+     * A simple string setting
+     */
+    static final Setting<String> SIMPLE_SETTING = Setting.simpleString("custom.simple", Property.NodeScope);
+
+    /**
+     * A simple boolean setting that can be dynamically updated using the Cluster Settings API and that is {@code "false"} by default
+     */
+    static final Setting<Boolean> BOOLEAN_SETTING = Setting.boolSetting("custom.bool", false, Property.NodeScope, Property.Dynamic);
+
+    /**
+     * A string setting that can be dynamically updated and that is validated by some logic
+     */
+    static final Setting<String> VALIDATED_SETTING = Setting.simpleString("custom.validated", (value, settings) -> {
+        if (value != null && value.contains("forbidden")) {
+            throw new IllegalArgumentException("Setting must not contain [forbidden]");
+        }
+    }, Property.NodeScope, Property.Dynamic);
+
+    /**
+     * A setting that is filtered out when listing all the cluster's settings
+     */
+    static final Setting<String> FILTERED_SETTING = Setting.simpleString("custom.filtered", Property.NodeScope, Property.Filtered);
+
+    /**
+     * A setting which contains a sensitive string. This may be any sensitive string, e.g. a username, a password, an auth token, etc.
+     */
+    static final Setting<SecureString> SECURED_SETTING = SecureSetting.secureString("custom.secured", null);
+
+    /**
+     * A setting that consists of a list of integers
+     */
+    static final Setting<List<Integer>> LIST_SETTING =
+        Setting.listSetting("custom.list", Collections.emptyList(), Integer::valueOf, Property.NodeScope);
+
+
+    private final String simple;
+    private final String validated;
+    private final Boolean bool;
+    private final List<Integer> list;
+    private final String filtered;
+
+    public ExampleCustomSettingsConfig(final Environment environment) {
+        // Elasticsearch config directory
+        final Path configDir = environment.configFile();
+
+        // Resolve the plugin's custom settings file
+        final Path customSettingsYamlFile = configDir.resolve("custom-settings/custom.yml");
+
+        // Load the settings from the plugin's custom settings file
+        final Settings customSettings;
+        try {
+            customSettings = Settings.builder().loadFromPath(customSettingsYamlFile).build();
+            assert customSettings != null;
+        } catch (IOException e) {
+            throw new ElasticsearchException("Failed to load settings", e);
+        }
+
+        this.simple = SIMPLE_SETTING.get(customSettings);
+        this.bool = BOOLEAN_SETTING.get(customSettings);
+        this.validated = VALIDATED_SETTING.get(customSettings);
+        this.filtered = FILTERED_SETTING.get(customSettings);
+        this.list = LIST_SETTING.get(customSettings);
+
+        // Loads the secured setting from the keystore
+        final SecureString secured = SECURED_SETTING.get(environment.settings());
+        assert secured != null;
+    }
+
+    public String getSimple() {
+        return simple;
+    }
+
+    public Boolean getBool() {
+        return bool;
+    }
+
+    public String getValidated() {
+        return validated;
+    }
+
+    public String getFiltered() {
+        return filtered;
+    }
+
+    public List<Integer> getList() {
+        return list;
+    }
+
+}

+ 68 - 0
plugins/examples/custom-settings/src/main/java/org/elasticsearch/example/customsettings/ExampleCustomSettingsPlugin.java

@@ -0,0 +1,68 @@
+/*
+ * 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.example.customsettings;
+
+import org.elasticsearch.common.settings.Setting;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.env.Environment;
+import org.elasticsearch.plugins.Plugin;
+
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+
+import static java.util.stream.Collectors.toList;
+
+public class ExampleCustomSettingsPlugin extends Plugin {
+
+    private final ExampleCustomSettingsConfig config;
+
+    public ExampleCustomSettingsPlugin(final Settings settings, final Path configPath) {
+        this.config = new ExampleCustomSettingsConfig(new Environment(settings, configPath));
+
+        // asserts that the setting has been correctly loaded from the custom setting file
+        assert "secret".equals(config.getFiltered());
+    }
+
+    /**
+     * @return the plugin's custom settings
+     */
+    @Override
+    public List<Setting<?>> getSettings() {
+        return Arrays.asList(ExampleCustomSettingsConfig.SIMPLE_SETTING,
+                             ExampleCustomSettingsConfig.BOOLEAN_SETTING,
+                             ExampleCustomSettingsConfig.VALIDATED_SETTING,
+                             ExampleCustomSettingsConfig.FILTERED_SETTING,
+                             ExampleCustomSettingsConfig.SECURED_SETTING,
+                             ExampleCustomSettingsConfig.LIST_SETTING);
+    }
+
+    @Override
+    public Settings additionalSettings() {
+        final Settings.Builder builder = Settings.builder();
+
+        // Exposes SIMPLE_SETTING and LIST_SETTING as a node settings
+        builder.put(ExampleCustomSettingsConfig.SIMPLE_SETTING.getKey(), config.getSimple());
+
+        final List<String> values = config.getList().stream().map(integer -> Integer.toString(integer)).collect(toList());
+        builder.putList(ExampleCustomSettingsConfig.LIST_SETTING.getKey(), values);
+
+        return builder.build();
+    }
+}

+ 51 - 0
plugins/examples/custom-settings/src/test/java/org/elasticsearch/example/customsettings/ExampleCustomSettingsClientYamlTestSuiteIT.java

@@ -0,0 +1,51 @@
+/*
+ * 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.example.customsettings;
+
+import com.carrotsearch.randomizedtesting.annotations.Name;
+import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
+import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate;
+import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase;
+
+/**
+ * {@link ExampleCustomSettingsClientYamlTestSuiteIT} executes the plugin's REST API integration tests.
+ * <p>
+ * The tests can be executed using the command: ./gradlew :example-plugins:custom-settings:check
+ * <p>
+ * This class extends {@link ESClientYamlSuiteTestCase}, which takes care of parsing the YAML files
+ * located in the src/test/resources/rest-api-spec/test/ directory and validates them against the
+ * custom REST API definition files located in src/test/resources/rest-api-spec/api/.
+ * <p>
+ * Once validated, {@link ESClientYamlSuiteTestCase} executes the REST tests against a single node
+ * integration cluster which has the plugin already installed by the Gradle build script.
+ * </p>
+ */
+public class ExampleCustomSettingsClientYamlTestSuiteIT extends ESClientYamlSuiteTestCase {
+
+    public ExampleCustomSettingsClientYamlTestSuiteIT(@Name("yaml") ClientYamlTestCandidate testCandidate) {
+        super(testCandidate);
+    }
+
+    @ParametersFactory
+    public static Iterable<Object[]> parameters() throws Exception {
+        // The test executes all the test candidates by default
+        // see ESClientYamlSuiteTestCase.REST_TESTS_SUITE
+        return ESClientYamlSuiteTestCase.createParameters();
+    }
+}

+ 44 - 0
plugins/examples/custom-settings/src/test/java/org/elasticsearch/example/customsettings/ExampleCustomSettingsConfigTests.java

@@ -0,0 +1,44 @@
+/*
+ * 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.example.customsettings;
+
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.test.ESTestCase;
+
+import static org.elasticsearch.example.customsettings.ExampleCustomSettingsConfig.VALIDATED_SETTING;
+
+/**
+ * {@link ExampleCustomSettingsConfigTests} is a unit test class for {@link ExampleCustomSettingsConfig}.
+ * <p>
+ * It's a JUnit test class that extends {@link ESTestCase} which provides useful methods for testing.
+ * <p>
+ * The tests can be executed in the IDE or using the command: ./gradlew :example-plugins:custom-settings:test
+ */
+public class ExampleCustomSettingsConfigTests extends ESTestCase {
+
+    public void testValidatedSetting() {
+        final String expected = randomAlphaOfLengthBetween(1, 5);
+        final String actual = VALIDATED_SETTING.get(Settings.builder().put(VALIDATED_SETTING.getKey(), expected).build());
+        assertEquals(expected, actual);
+
+        final IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () ->
+            VALIDATED_SETTING.get(Settings.builder().put("custom.validated", "it's forbidden").build()));
+        assertEquals("Setting must not contain [forbidden]", exception.getMessage());
+    }
+}

+ 10 - 0
plugins/examples/custom-settings/src/test/resources/rest-api-spec/test/customsettings/10_plugin.yml

@@ -0,0 +1,10 @@
+"Test that the custom-settings plugin is loaded in Elasticsearch":
+
+  # Use the Cat Plugins API to retrieve the list of plugins
+  - do:
+      cat.plugins:
+        local: true
+        h: component
+
+  - match:
+      $body: /^custom-settings\n$/

+ 54 - 0
plugins/examples/custom-settings/src/test/resources/rest-api-spec/test/customsettings/10_settings.yml

@@ -0,0 +1,54 @@
+"Test custom settings":
+
+  # Use the Get Cluster Settings API to list the settings including the default ones
+  - do:
+      cluster.get_settings:
+        include_defaults: true
+
+  - is_false: defaults.custom.bool
+  - match: { defaults.custom.list.0: "0" }
+  - match: { defaults.custom.list.1: "1" }
+  - match: { defaults.custom.list.2: "1" }
+  - match: { defaults.custom.list.3: "2" }
+  - match: { defaults.custom.list.4: "3" }
+  - match: { defaults.custom.list.5: "5" }
+  - match: { defaults.custom.list.6: "8" }
+  - match: { defaults.custom.list.7: "13" }
+  - match: { defaults.custom.list.8: "21" }
+
+  # This setting is filtered: it does not appear in the response
+  - is_false: defaults.custom.filtered
+
+  # Use the Cluster Update Settings API to update some custom settings
+  - do:
+      cluster.put_settings:
+        body:
+          transient:
+            custom:
+              bool: true
+              validated: "updated"
+
+  # Use the Get Cluster Settings API to list the settings again
+  - do:
+      cluster.get_settings: {}
+
+  - is_true: transient.custom.bool
+  - match: { transient.custom.validated: "updated" }
+
+  # Try to update the "validated" setting with a forbidden value
+  - do:
+      catch: bad_request
+      cluster.put_settings:
+        body:
+          transient:
+            custom:
+              validated: "forbidden"
+
+  # Reset the settings to their default values
+  - do:
+      cluster.put_settings:
+        body:
+          transient:
+            custom:
+              bool: null
+              validated: null

+ 8 - 8
plugins/jvm-example/build.gradle → plugins/examples/rest-handler/build.gradle

@@ -17,15 +17,15 @@
  * under the License.
  */
 
+apply plugin: 'elasticsearch.esplugin'
+
 esplugin {
-  description 'Demonstrates all the pluggable Java entry points in Elasticsearch'
-  classname 'org.elasticsearch.plugin.example.JvmExamplePlugin'
+  name 'rest-handler'
+  description 'An example plugin showing how to register a REST handler'
+  classname 'org.elasticsearch.example.resthandler.ExampleRestHandlerPlugin'
 }
-// Not published so no need to assemble
-tasks.remove(assemble)
-build.dependsOn.remove('assemble')
 
-// no unit tests
+// No unit tests in this example
 test.enabled = false
 
 configurations {
@@ -40,8 +40,8 @@ task exampleFixture(type: org.elasticsearch.gradle.test.AntFixture) {
   dependsOn project.configurations.exampleFixture
   executable = new File(project.runtimeJavaHome, 'bin/java')
   args '-cp', "${ -> project.configurations.exampleFixture.asPath }",
-       'example.ExampleTestFixture',
-       baseDir
+          'example.ExampleTestFixture',
+          baseDir
 }
 
 integTestCluster {

+ 10 - 8
plugins/jvm-example/src/main/java/org/elasticsearch/plugin/example/ExampleCatAction.java → plugins/examples/rest-handler/src/main/java/org/elasticsearch/example/resthandler/ExampleCatAction.java

@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.elasticsearch.plugin.example;
+package org.elasticsearch.example.resthandler;
 
 import org.elasticsearch.client.node.NodeClient;
 import org.elasticsearch.common.Table;
@@ -28,29 +28,31 @@ import org.elasticsearch.rest.action.cat.AbstractCatAction;
 import org.elasticsearch.rest.action.cat.RestTable;
 
 import static org.elasticsearch.rest.RestRequest.Method.GET;
+import static org.elasticsearch.rest.RestRequest.Method.POST;
 
 /**
  * Example of adding a cat action with a plugin.
  */
 public class ExampleCatAction extends AbstractCatAction {
-    private final ExamplePluginConfiguration config;
 
-    public ExampleCatAction(Settings settings, RestController controller, ExamplePluginConfiguration config) {
+    ExampleCatAction(final Settings settings, final RestController controller) {
         super(settings);
-        this.config = config;
-        controller.registerHandler(GET, "/_cat/configured_example", this);
+        controller.registerHandler(GET, "/_cat/example", this);
+        controller.registerHandler(POST, "/_cat/example", this);
     }
 
     @Override
     public String getName() {
-        return "example_cat_action";
+        return "rest_handler_cat_example";
     }
 
     @Override
     protected RestChannelConsumer doCatRequest(final RestRequest request, final NodeClient client) {
+        final String message = request.param("message", "Hello from Cat Example action");
+
         Table table = getTableWithHeader(request);
         table.startRow();
-        table.addCell(config.getTestConfig());
+        table.addCell(message);
         table.endRow();
         return channel -> {
             try {
@@ -67,7 +69,7 @@ public class ExampleCatAction extends AbstractCatAction {
     }
 
     public static String documentation() {
-        return "/_cat/configured_example\n";
+        return "/_cat/example\n";
     }
 
     @Override

+ 11 - 21
plugins/jvm-example/src/main/java/org/elasticsearch/plugin/example/JvmExamplePlugin.java → plugins/examples/rest-handler/src/main/java/org/elasticsearch/example/resthandler/ExampleRestHandlerPlugin.java

@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.elasticsearch.plugin.example;
+package org.elasticsearch.example.resthandler;
 
 import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
 import org.elasticsearch.cluster.node.DiscoveryNodes;
@@ -25,37 +25,27 @@ import org.elasticsearch.common.settings.ClusterSettings;
 import org.elasticsearch.common.settings.IndexScopedSettings;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.settings.SettingsFilter;
-import org.elasticsearch.env.Environment;
 import org.elasticsearch.plugins.ActionPlugin;
 import org.elasticsearch.plugins.Plugin;
 import org.elasticsearch.rest.RestController;
 import org.elasticsearch.rest.RestHandler;
 
-import java.nio.file.Path;
 import java.util.List;
 import java.util.function.Supplier;
 
 import static java.util.Collections.singletonList;
 
-/**
- * Example of a plugin.
- */
-public class JvmExamplePlugin extends Plugin implements ActionPlugin {
-    private final ExamplePluginConfiguration config;
-
-    public JvmExamplePlugin(Settings settings, Path configPath) {
-        config = new ExamplePluginConfiguration(new Environment(settings, configPath));
-    }
-
-    @Override
-    public Settings additionalSettings() {
-        return Settings.EMPTY;
-    }
+public class ExampleRestHandlerPlugin extends Plugin implements ActionPlugin {
 
     @Override
-    public List<RestHandler> getRestHandlers(Settings settings, RestController restController, ClusterSettings clusterSettings,
-            IndexScopedSettings indexScopedSettings, SettingsFilter settingsFilter, IndexNameExpressionResolver indexNameExpressionResolver,
-            Supplier<DiscoveryNodes> nodesInCluster) {
-        return singletonList(new ExampleCatAction(settings, restController, config));
+    public List<RestHandler> getRestHandlers(final Settings settings,
+                                             final RestController restController,
+                                             final ClusterSettings clusterSettings,
+                                             final IndexScopedSettings indexScopedSettings,
+                                             final SettingsFilter settingsFilter,
+                                             final IndexNameExpressionResolver indexNameExpressionResolver,
+                                             final Supplier<DiscoveryNodes> nodesInCluster) {
+
+        return singletonList(new ExampleCatAction(settings, restController));
     }
 }

+ 12 - 8
plugins/jvm-example/src/test/java/org/elasticsearch/plugin/example/ExampleExternalIT.java → plugins/examples/rest-handler/src/test/java/org/elasticsearch/example/resthandler/ExampleFixtureIT.java

@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package org.elasticsearch.plugin.example;
+package org.elasticsearch.example.resthandler;
 
 import org.elasticsearch.mocksocket.MockSocket;
 import org.elasticsearch.test.ESTestCase;
@@ -30,14 +30,18 @@ import java.net.URL;
 import java.nio.charset.StandardCharsets;
 import java.util.Objects;
 
-public class ExampleExternalIT extends ESTestCase {
+public class ExampleFixtureIT extends ESTestCase {
+
     public void testExample() throws Exception {
-        String stringAddress = Objects.requireNonNull(System.getProperty("external.address"));
-        URL url = new URL("http://" + stringAddress);
-        InetAddress address = InetAddress.getByName(url.getHost());
-        try (Socket socket = new MockSocket(address, url.getPort());
-             BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8))) {
-           assertEquals("TEST", reader.readLine());
+        final String stringAddress = Objects.requireNonNull(System.getProperty("external.address"));
+        final URL url = new URL("http://" + stringAddress);
+
+        final InetAddress address = InetAddress.getByName(url.getHost());
+        try (
+            Socket socket = new MockSocket(address, url.getPort());
+            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8))
+        ) {
+            assertEquals("TEST", reader.readLine());
         }
     }
 }

+ 18 - 6
plugins/jvm-example/src/test/java/org/elasticsearch/plugin/example/JvmExampleClientYamlTestSuiteIT.java → plugins/examples/rest-handler/src/test/java/org/elasticsearch/example/resthandler/ExampleRestHandlerClientYamlTestSuiteIT.java

@@ -16,24 +16,36 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-
-package org.elasticsearch.plugin.example;
+package org.elasticsearch.example.resthandler;
 
 import com.carrotsearch.randomizedtesting.annotations.Name;
 import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
-
 import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate;
 import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase;
 
-public class JvmExampleClientYamlTestSuiteIT extends ESClientYamlSuiteTestCase {
+/**
+ * {@link ExampleRestHandlerClientYamlTestSuiteIT} executes the plugin's REST API integration tests.
+ * <p>
+ * The tests can be executed using the command: ./gradlew :example-plugins:rest-handler:check
+ * <p>
+ * This class extends {@link ESClientYamlSuiteTestCase}, which takes care of parsing the YAML files
+ * located in the src/test/resources/rest-api-spec/test/ directory and validates them against the
+ * custom REST API definition files located in src/test/resources/rest-api-spec/api/.
+ * <p>
+ * Once validated, {@link ESClientYamlSuiteTestCase} executes the REST tests against a single node
+ * integration cluster which has the plugin already installed by the Gradle build script.
+ * </p>
+ */
+public class ExampleRestHandlerClientYamlTestSuiteIT extends ESClientYamlSuiteTestCase {
 
-    public JvmExampleClientYamlTestSuiteIT(@Name("yaml") ClientYamlTestCandidate testCandidate) {
+    public ExampleRestHandlerClientYamlTestSuiteIT(@Name("yaml") ClientYamlTestCandidate testCandidate) {
         super(testCandidate);
     }
 
     @ParametersFactory
     public static Iterable<Object[]> parameters() throws Exception {
+        // The test executes all the test candidates by default
+        // see ESClientYamlSuiteTestCase.REST_TESTS_SUITE
         return ESClientYamlSuiteTestCase.createParameters();
     }
 }
-

+ 8 - 3
plugins/jvm-example/src/test/resources/rest-api-spec/api/cat.configured_example.json → plugins/examples/rest-handler/src/test/resources/rest-api-spec/api/cat.example.json

@@ -1,10 +1,10 @@
 {
-  "cat.configured_example": {
+  "cat.example": {
     "documentation": "",
     "methods": ["GET"],
     "url": {
-      "path": "/_cat/configured_example",
-      "paths": ["/_cat/configured_example"],
+      "path": "/_cat/example",
+      "paths": ["/_cat/example"],
       "parts": {},
       "params": {
         "help": {
@@ -16,6 +16,11 @@
           "type": "boolean",
           "description": "Verbose mode. Display column headers",
           "default": true
+        },
+        "message": {
+          "type": "string",
+          "description": "A simple message that will be printed out in the response",
+          "default": "Hello from Cat Example action"
         }
       }
     },

+ 10 - 0
plugins/examples/rest-handler/src/test/resources/rest-api-spec/test/resthandler/10_basic.yml

@@ -0,0 +1,10 @@
+"Test that the rest-handler plugin is loaded in Elasticsearch":
+
+  # Use the Cat Plugins API to retrieve the list of plugins
+  - do:
+      cat.plugins:
+        local: true
+        h: component
+
+  - match:
+      $body: /^rest-handler\n$/

+ 26 - 0
plugins/examples/rest-handler/src/test/resources/rest-api-spec/test/resthandler/20_cat_example.yml

@@ -0,0 +1,26 @@
+---
+"Help":
+  - do:
+      cat.example:
+        help: true
+
+  - match:
+      $body: |
+               /^  test    .+   \n
+               $/
+
+---
+"Default message":
+  - do:
+      cat.example:
+        v: false
+
+  - match: {$body: "Hello from Cat Example action\n" }
+
+---
+"Custom message":
+  - do:
+      cat.example:
+        message: Hello from REST API test
+
+  - match: {$body: "Hello from REST API test\n" }

+ 0 - 3
plugins/jvm-example/src/main/bin/test

@@ -1,3 +0,0 @@
-#!/bin/bash
-
-echo test

+ 0 - 1
plugins/jvm-example/src/main/bin/test.bat

@@ -1 +0,0 @@
-echo test

+ 0 - 1
plugins/jvm-example/src/main/config/example.yml

@@ -1 +0,0 @@
-test: foo

+ 0 - 56
plugins/jvm-example/src/main/java/org/elasticsearch/plugin/example/ExamplePluginConfiguration.java

@@ -1,56 +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.plugin.example;
-
-import org.elasticsearch.common.settings.Setting;
-import org.elasticsearch.common.settings.Settings;
-import org.elasticsearch.env.Environment;
-
-import java.io.IOException;
-import java.nio.file.Path;
-
-/**
- * Example configuration.
- */
-public class ExamplePluginConfiguration {
-    private final Settings customSettings;
-
-    public static final Setting<String> TEST_SETTING =
-      new Setting<String>("test", "default_value",
-      (value) -> value, Setting.Property.Dynamic);
-
-    public ExamplePluginConfiguration(Environment env) {
-        // The directory part of the location matches the artifactId of this plugin
-        Path path = env.configFile().resolve("jvm-example/example.yml");
-        try {
-            customSettings = Settings.builder().loadFromPath(path).build();
-        } catch (IOException e) {
-            throw new RuntimeException("Failed to load settings, giving up", e);
-        }
-
-        // asserts for tests
-        assert customSettings != null;
-        assert TEST_SETTING.get(customSettings) != null;
-    }
-
-    public String getTestConfig() {
-        return TEST_SETTING.get(customSettings);
-    }
-}

+ 0 - 13
plugins/jvm-example/src/test/resources/rest-api-spec/test/jvm_example/10_basic.yml

@@ -1,13 +0,0 @@
-# Integration tests for JVM Example Plugin
-#
-"JVM Example loaded":
-    - do:
-        cluster.state: {}
-
-    # Get master node id
-    - set: { master_node: master }
-
-    - do:
-        nodes.info: {}
-
-    - match:  { nodes.$master.plugins.0.name: jvm-example  }

+ 0 - 22
plugins/jvm-example/src/test/resources/rest-api-spec/test/jvm_example/20_configured_example.yml

@@ -1,22 +0,0 @@
----
-"Help":
-  - do:
-      cat.configured_example:
-        help: true
-
-  - match:
-      $body: |
-               /^  test    .+   \n
-               $/
-
----
-"Data":
-  - do:
-      cat.configured_example:
-        v: false
-
-  - match:
-      $body: |
-            /^
-               foo       \s+
-            $/

+ 1 - 1
qa/vagrant/build.gradle

@@ -22,7 +22,7 @@ apply plugin: 'elasticsearch.vagrant'
 
 List<String> plugins = []
 for (Project subproj : project.rootProject.subprojects) {
-  if (subproj.path.startsWith(':plugins:')) {
+  if (subproj.path.startsWith(':plugins:') || subproj.path.equals(':example-plugins:custom-settings')) {
     // add plugin as a dep
     dependencies {
       bats project(path: "${subproj.path}", configuration: 'zip')

+ 42 - 27
qa/vagrant/src/test/resources/packaging/tests/module_and_plugin_test_cases.bash

@@ -57,7 +57,7 @@ setup() {
     # other tests. Commenting out lots of test cases seems like a reasonably
     # common workflow.
     if [ $BATS_TEST_NUMBER == 1 ] ||
-            [[ $BATS_TEST_NAME =~ install_jvm.*example ]] ||
+            [[ $BATS_TEST_NAME =~ install_a_sample_plugin ]] ||
             [ ! -d "$ESHOME" ]; then
         clean_before_test
         install
@@ -89,7 +89,7 @@ else
     }
 fi
 
-@test "[$GROUP] install jvm-example plugin with a symlinked plugins path" {
+@test "[$GROUP] install a sample plugin with a symlinked plugins path" {
     # Clean up after the last time this test was run
     rm -rf /tmp/plugins.*
     rm -rf /tmp/old_plugins.*
@@ -99,48 +99,63 @@ fi
     chown -R elasticsearch:elasticsearch "$es_plugins"
     ln -s "$es_plugins" "$ESPLUGINS"
 
-    install_jvm_example
+    install_plugin_example
     start_elasticsearch_service
+
     # check that symlinked plugin was actually picked up
-    curl -s localhost:9200/_cat/configured_example | sed 's/ *$//' > /tmp/installed
-    echo "foo" > /tmp/expected
+    curl -XGET -H 'Content-Type: application/json' 'http://localhost:9200/_cat/plugins?h=component' | sed 's/ *$//' > /tmp/installed
+    echo "custom-settings" > /tmp/expected
+    diff /tmp/installed /tmp/expected
+
+    curl -XGET -H 'Content-Type: application/json' 'http://localhost:9200/_cluster/settings?include_defaults&filter_path=defaults.custom.simple' > /tmp/installed
+    echo -n '{"defaults":{"custom":{"simple":"foo"}}}' > /tmp/expected
     diff /tmp/installed /tmp/expected
+
     stop_elasticsearch_service
-    remove_jvm_example
+    remove_plugin_example
 
     unlink "$ESPLUGINS"
 }
 
-@test "[$GROUP] install jvm-example plugin with a custom CONFIG_DIR" {
+@test "[$GROUP] install a sample plugin with a custom CONFIG_DIR" {
     # Clean up after the last time we ran this test
     rm -rf /tmp/config.*
 
     move_config
 
-    ES_PATH_CONF="$ESCONFIG" install_jvm_example
+    ES_PATH_CONF="$ESCONFIG" install_plugin_example
     ES_PATH_CONF="$ESCONFIG" start_elasticsearch_service
-    diff  <(curl -s localhost:9200/_cat/configured_example | sed 's/ //g') <(echo "foo")
+
+    # check that symlinked plugin was actually picked up
+    curl -XGET -H 'Content-Type: application/json' 'http://localhost:9200/_cat/plugins?h=component' | sed 's/ *$//' > /tmp/installed
+    echo "custom-settings" > /tmp/expected
+    diff /tmp/installed /tmp/expected
+
+    curl -XGET -H 'Content-Type: application/json' 'http://localhost:9200/_cluster/settings?include_defaults&filter_path=defaults.custom.simple' > /tmp/installed
+    echo -n '{"defaults":{"custom":{"simple":"foo"}}}' > /tmp/expected
+    diff /tmp/installed /tmp/expected
+
     stop_elasticsearch_service
-    ES_PATH_CONF="$ESCONFIG" remove_jvm_example
+    ES_PATH_CONF="$ESCONFIG" remove_plugin_example
 }
 
-@test "[$GROUP] install jvm-example plugin from a directory with a space" {
+@test "[$GROUP] install a sample plugin from a directory with a space" {
     rm -rf "/tmp/plugins with space"
     mkdir -p "/tmp/plugins with space"
-    local zip=$(ls jvm-example-*.zip)
+    local zip=$(ls custom-settings-*.zip)
     cp $zip "/tmp/plugins with space"
 
-    install_jvm_example "/tmp/plugins with space/$zip"
-    remove_jvm_example
+    install_plugin_example "/tmp/plugins with space/$zip"
+    remove_plugin_example
 }
 
-@test "[$GROUP] install jvm-example plugin to elasticsearch directory with a space" {
+@test "[$GROUP] install a sample plugin to elasticsearch directory with a space" {
     [ "$GROUP" == "TAR PLUGINS" ] || skip "Test case only supported by TAR PLUGINS"
 
     move_elasticsearch "/tmp/elastic search"
 
-    install_jvm_example
-    remove_jvm_example
+    install_plugin_example
+    remove_plugin_example
 }
 
 @test "[$GROUP] fail if java executable is not found" {
@@ -161,8 +176,8 @@ fi
 
 # Note that all of the tests from here to the end of the file expect to be run
 # in sequence and don't take well to being run one at a time.
-@test "[$GROUP] install jvm-example plugin" {
-    install_jvm_example
+@test "[$GROUP] install a sample plugin" {
+    install_plugin_example
 }
 
 @test "[$GROUP] install icu plugin" {
@@ -293,8 +308,8 @@ fi
     stop_elasticsearch_service
 }
 
-@test "[$GROUP] remove jvm-example plugin" {
-    remove_jvm_example
+@test "[$GROUP] remove a sample plugin" {
+    remove_plugin_example
 }
 
 @test "[$GROUP] remove icu plugin" {
@@ -399,8 +414,8 @@ fi
     stop_elasticsearch_service
 }
 
-@test "[$GROUP] install jvm-example with different logging modes and check output" {
-    local relativePath=${1:-$(readlink -m jvm-example-*.zip)}
+@test "[$GROUP] install a sample plugin with different logging modes and check output" {
+    local relativePath=${1:-$(readlink -m custom-settings-*.zip)}
     sudo -E -u $ESPLUGIN_COMMAND_USER "$ESHOME/bin/elasticsearch-plugin" install "file://$relativePath" > /tmp/plugin-cli-output
     # exclude progress line
     local loglines=$(cat /tmp/plugin-cli-output | grep -v "^[[:cntrl:]]" | wc -l)
@@ -409,9 +424,9 @@ fi
         cat /tmp/plugin-cli-output
         false
     }
-    remove_jvm_example
+    remove_plugin_example
 
-    local relativePath=${1:-$(readlink -m jvm-example-*.zip)}
+    local relativePath=${1:-$(readlink -m custom-settings-*.zip)}
     sudo -E -u $ESPLUGIN_COMMAND_USER ES_JAVA_OPTS="-Des.logger.level=DEBUG" "$ESHOME/bin/elasticsearch-plugin" install "file://$relativePath" > /tmp/plugin-cli-output
     local loglines=$(cat /tmp/plugin-cli-output | grep -v "^[[:cntrl:]]" | wc -l)
     [ "$loglines" -gt "2" ] || {
@@ -419,7 +434,7 @@ fi
         cat /tmp/plugin-cli-output
         false
     }
-    remove_jvm_example
+    remove_plugin_example
 }
 
 @test "[$GROUP] test java home with space" {
@@ -456,7 +471,7 @@ fi
 }
 
 @test "[$GROUP] test umask" {
-    install_jvm_example $(readlink -m jvm-example-*.zip) 0077
+    install_plugin_example $(readlink -m custom-settings-*.zip) 0077
 }
 
 @test "[$GROUP] hostname" {

+ 19 - 20
qa/vagrant/src/test/resources/packaging/utils/plugins.bash

@@ -82,50 +82,49 @@ remove_plugin() {
     fi
 }
 
-# Install the jvm-example plugin which fully exercises the special case file
-# placements for non-site plugins.
-install_jvm_example() {
-    local relativePath=${1:-$(readlink -m jvm-example-*.zip)}
-    install_plugin jvm-example "$relativePath" $2
+# Install a sample plugin which fully exercises the special case file placements.
+install_plugin_example() {
+    local relativePath=${1:-$(readlink -m custom-settings-*.zip)}
+    install_plugin custom-settings "$relativePath" $2
 
     bin_user=$(find "$ESHOME/bin" -maxdepth 0 -printf "%u")
     bin_owner=$(find "$ESHOME/bin" -maxdepth 0 -printf "%g")
 
-    assert_file "$ESHOME/plugins/jvm-example" d $bin_user $bin_owner 755
-    assert_file "$ESHOME/plugins/jvm-example/jvm-example-$(cat version).jar" f $bin_user $bin_owner 644
+    assert_file "$ESHOME/plugins/custom-settings" d $bin_user $bin_owner 755
+    assert_file "$ESHOME/plugins/custom-settings/custom-settings-$(cat version).jar" f $bin_user $bin_owner 644
 
     #owner group and permissions vary depending on how es was installed
     #just make sure that everything is the same as the parent bin dir, which was properly set up during install
-    assert_file "$ESHOME/bin/jvm-example" d $bin_user $bin_owner 755
-    assert_file "$ESHOME/bin/jvm-example/test" f $bin_user $bin_owner 755
+    assert_file "$ESHOME/bin/custom-settings" d $bin_user $bin_owner 755
+    assert_file "$ESHOME/bin/custom-settings/test" f $bin_user $bin_owner 755
 
     #owner group and permissions vary depending on how es was installed
     #just make sure that everything is the same as $CONFIG_DIR, which was properly set up during install
     config_user=$(find "$ESCONFIG" -maxdepth 0 -printf "%u")
     config_owner=$(find "$ESCONFIG" -maxdepth 0 -printf "%g")
     # directories should user the user file-creation mask
-    assert_file "$ESCONFIG/jvm-example" d $config_user $config_owner 750
-    assert_file "$ESCONFIG/jvm-example/example.yml" f $config_user $config_owner 660
+    assert_file "$ESCONFIG/custom-settings" d $config_user $config_owner 750
+    assert_file "$ESCONFIG/custom-settings/custom.yml" f $config_user $config_owner 660
 
-    run sudo -E -u vagrant LANG="en_US.UTF-8" cat "$ESCONFIG/jvm-example/example.yml"
+    run sudo -E -u vagrant LANG="en_US.UTF-8" cat "$ESCONFIG/custom-settings/custom.yml"
     [ $status = 1 ]
     [[ "$output" == *"Permission denied"* ]] || {
         echo "Expected permission denied but found $output:"
         false
     }
 
-    echo "Running jvm-example's bin script...."
-    "$ESHOME/bin/jvm-example/test" | grep test
+    echo "Running sample plugin bin script...."
+    "$ESHOME/bin/custom-settings/test" | grep test
 }
 
-# Remove the jvm-example plugin which fully exercises the special cases of
+# Remove the sample plugin which fully exercises the special cases of
 # removing bin and not removing config.
-remove_jvm_example() {
-    remove_plugin jvm-example
+remove_plugin_example() {
+    remove_plugin custom-settings
 
-    assert_file_not_exist "$ESHOME/bin/jvm-example"
-    assert_file_exist "$ESCONFIG/jvm-example"
-    assert_file_exist "$ESCONFIG/jvm-example/example.yml"
+    assert_file_not_exist "$ESHOME/bin/custom-settings"
+    assert_file_exist "$ESCONFIG/custom-settings"
+    assert_file_exist "$ESCONFIG/custom-settings/custom.yml"
 }
 
 # Install a plugin with a special prefix. For the most part prefixes are just