From c2ae17a3e7996f1bed7a2ccd0c731ab7edee8235 Mon Sep 17 00:00:00 2001 From: Ryan Bogan Date: Fri, 16 Sep 2022 23:43:20 +0000 Subject: [PATCH 1/8] Added createComponent to Extension interface and created BaseExtension abstract class Signed-off-by: Ryan Bogan --- build.gradle | 3 +- .../org/opensearch/sdk/BaseExtension.java | 169 ++++++++++++++++++ .../java/org/opensearch/sdk/Extension.java | 90 ++++++++++ .../helloworld/HelloWorldExtension.java | 6 +- .../helloworld/TestHelloWorldExtension.java | 59 ++++++ 5 files changed, 324 insertions(+), 3 deletions(-) create mode 100644 src/main/java/org/opensearch/sdk/BaseExtension.java diff --git a/build.gradle b/build.gradle index 75a67fec..bcdbd788 100644 --- a/build.gradle +++ b/build.gradle @@ -46,7 +46,7 @@ publishing { repositories { mavenLocal() // Remove the commented code below once TransportService is published to maven - //maven { url "https://aws.oss.sonatype.org/content/repositories/snapshots" } + maven { url "https://aws.oss.sonatype.org/content/repositories/snapshots" } maven { url "https://d1nvenhzbhpy0q.cloudfront.net/snapshots/lucene/"} mavenCentral() } @@ -63,6 +63,7 @@ dependencies { implementation 'org.opensearch.client:opensearch-rest-client:2.0.0' implementation 'org.opensearch.client:opensearch-java:2.0.0' implementation "io.netty:netty-all:4.1.73.Final" + implementation "org.apache.lucene:lucene-core:9.4.0-snapshot-ddf0d0a" testCompileOnly ("junit:junit:4.13.2") { exclude module : 'hamcrest' exclude module : 'hamcrest-core' diff --git a/src/main/java/org/opensearch/sdk/BaseExtension.java b/src/main/java/org/opensearch/sdk/BaseExtension.java new file mode 100644 index 00000000..799cda3a --- /dev/null +++ b/src/main/java/org/opensearch/sdk/BaseExtension.java @@ -0,0 +1,169 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.sdk; + +import java.io.IOException; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; + +import org.apache.lucene.util.SetOnce; +import org.opensearch.client.Client; +import org.opensearch.client.node.NodeClient; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.io.stream.NamedWriteableRegistry; +import org.opensearch.common.io.stream.NamedWriteableRegistry.Entry; +import org.opensearch.common.settings.ClusterSettings; +import org.opensearch.common.settings.Setting; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.settings.Setting.Property; +import org.opensearch.common.xcontent.NamedXContentRegistry; +import org.opensearch.env.Environment; +import org.opensearch.env.NodeEnvironment; +import org.opensearch.node.Node; +import org.opensearch.repositories.RepositoriesService; +import org.opensearch.script.ScriptContext; +import org.opensearch.script.ScriptEngine; +import org.opensearch.script.ScriptService; +import org.opensearch.threadpool.ExecutorBuilder; +import org.opensearch.threadpool.ThreadPool; +import org.opensearch.watcher.ResourceWatcherService; + +public abstract class BaseExtension implements Extension { + private Client client; + private ClusterService clusterService; + private ThreadPool threadPool; + private ResourceWatcherService resourceWatcherService; + private ScriptService scriptService; + private NamedXContentRegistry xContentRegistry; + private Environment environment; + private NodeEnvironment nodeEnvironment; + private NamedWriteableRegistry namedWriteableRegistry; + private IndexNameExpressionResolver indexNameExpressionResolver; + private Supplier repositoriesServiceSupplier; + + /** + * + */ + public BaseExtension() throws IOException { + createComponents(); + } + + private void createComponents() throws IOException { + //Settings.EMPTY will eventually be replaced with a getSettings method from the Extension interface + Settings settings = Settings.builder().put("node.name", Property.NodeScope).build(); + this.threadPool = new ThreadPool(settings, new AtomicReference<>(), Collections.emptyList().toArray(new ExecutorBuilder[0])); + this.client = new NodeClient(settings, threadPool); + this.clusterService = new ClusterService(settings, new ClusterSettings(settings, new HashSet>()), threadPool); + this.resourceWatcherService = new ResourceWatcherService(settings, threadPool); + this.scriptService = new ScriptService(settings, new HashMap(), new HashMap>()); + //TODO replace NamedXContentRegistry.EMPTY with the actual xContentRegistry from OpenSearch. + //This should not be used if {@link XContentParser#namedObject(Class, String, Object)} is being called, as calling the method will cause a failure. + this.xContentRegistry = new NamedXContentRegistry(new ArrayList()); + this.environment = new Environment(settings, Paths.get("")); + this.nodeEnvironment = new NodeEnvironment(settings, environment); + this.namedWriteableRegistry = new NamedWriteableRegistryAPI().getRegistry(); + this.indexNameExpressionResolver = new IndexNameExpressionResolver(threadPool.getThreadContext()); + final SetOnce repositoriesServiceReference = new SetOnce<>(); + this.repositoriesServiceSupplier = repositoriesServiceReference::get; + } + + public Client getClient() { + return client; + } + + protected void setClient(Client client) { + this.client = client; + } + + public ClusterService getClusterService() { + return clusterService; + } + + protected void setClusterService(ClusterService clusterService) { + this.clusterService = clusterService; + } + + public ThreadPool getThreadPool() { + return threadPool; + } + + protected void setThreadPool(ThreadPool threadPool) { + this.threadPool = threadPool; + } + + public ResourceWatcherService getResourceWatcherService() { + return resourceWatcherService; + } + + protected void setResourceWatcherService(ResourceWatcherService resourceWatcherService) { + this.resourceWatcherService = resourceWatcherService; + } + + public ScriptService getScriptService() { + return scriptService; + } + + protected void setScriptService(ScriptService scriptService) { + this.scriptService = scriptService; + } + + public NamedXContentRegistry getNamedXContentRegistry() { + return xContentRegistry; + } + + protected void setNamedXContentRegistry(NamedXContentRegistry xContentRegistry) { + this.xContentRegistry = xContentRegistry; + } + + public Environment getEnvironment() { + return environment; + } + + protected void setEnvironment(Environment environment) { + this.environment = environment; + } + + public NodeEnvironment getNodeEnvironment() { + return nodeEnvironment; + } + + protected void setNodeEnvironment(NodeEnvironment nodeEnvironment) { + this.nodeEnvironment = nodeEnvironment; + } + + public NamedWriteableRegistry getNamedWriteableRegistry() { + return namedWriteableRegistry; + } + + protected void setNamedWriteableRegistry(NamedWriteableRegistry namedWriteableRegistry) { + this.namedWriteableRegistry = namedWriteableRegistry; + } + + public IndexNameExpressionResolver getIndexNameExpressionResolver() { + return indexNameExpressionResolver; + } + + protected void setIndexNameExpressionResolver(IndexNameExpressionResolver indexNameExpressionResolver) { + this.indexNameExpressionResolver = indexNameExpressionResolver; + } + + public Supplier getRepositoriesServiceSupplier() { + return repositoriesServiceSupplier; + } + + protected void setRepositoriesServiceSupplier(Supplier repositoriesServiceSupplier) { + this.repositoriesServiceSupplier = repositoriesServiceSupplier; + } +} diff --git a/src/main/java/org/opensearch/sdk/Extension.java b/src/main/java/org/opensearch/sdk/Extension.java index eea1ca93..bd926287 100644 --- a/src/main/java/org/opensearch/sdk/Extension.java +++ b/src/main/java/org/opensearch/sdk/Extension.java @@ -11,6 +11,19 @@ import java.io.IOException; import java.net.URL; import java.util.List; +import java.util.function.Supplier; + +import org.opensearch.client.Client; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.io.stream.NamedWriteableRegistry; +import org.opensearch.common.xcontent.NamedXContentRegistry; +import org.opensearch.env.Environment; +import org.opensearch.env.NodeEnvironment; +import org.opensearch.repositories.RepositoriesService; +import org.opensearch.script.ScriptService; +import org.opensearch.threadpool.ThreadPool; +import org.opensearch.watcher.ResourceWatcherService; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; @@ -36,6 +49,83 @@ public interface Extension { */ List getExtensionRestHandlers(); + /** + * Gets the {@link Client} of this extension + * + * @return the client + */ + Client getClient(); + + /** + * Gets the {@link ClusterService} of this extension + * + * @return the cluster service + */ + ClusterService getClusterService(); + + /** + * Gets the {@link ThreadPool} of this extension + * + * @return the thread pool + */ + ThreadPool getThreadPool(); + + /** + * Gets the {@link ResourceWatcherService} of this extension + * + * @return the resource watcher service + */ + ResourceWatcherService getResourceWatcherService(); + + /** + * Gets the {@link ScriptService} of this extension + * + * @return the script service + */ + ScriptService getScriptService(); + + /** + * Gets the {@link NamedXContentRegistry} of this extension + * + * @return the NamedXContentRegistry + */ + NamedXContentRegistry getNamedXContentRegistry(); + + /** + * Gets the {@link Environment} of this extension + * + * @return the environment + */ + Environment getEnvironment(); + + /** + * Gets the {@link NodeEnvironment} of this extension + * + * @return the node environment + */ + NodeEnvironment getNodeEnvironment(); + + /** + * Gets the {@link NamedWritableRegistry} of this extension + * + * @return the NamedWritableRegistry + */ + NamedWriteableRegistry getNamedWriteableRegistry(); + + /** + * Gets the {@link IndexNameExpressionResolver} of this extension + * + * @return the IndexNameExpressionResolver + */ + IndexNameExpressionResolver getIndexNameExpressionResolver(); + + /** + * Gets the {@link Supplier} of {@link RepositoriesService} of this extension + * + * @return the repositories service supplier + */ + Supplier getRepositoriesServiceSupplier(); + /** * Helper method to read extension settings from a YAML file. * diff --git a/src/main/java/org/opensearch/sdk/sample/helloworld/HelloWorldExtension.java b/src/main/java/org/opensearch/sdk/sample/helloworld/HelloWorldExtension.java index a3c24b71..43616334 100644 --- a/src/main/java/org/opensearch/sdk/sample/helloworld/HelloWorldExtension.java +++ b/src/main/java/org/opensearch/sdk/sample/helloworld/HelloWorldExtension.java @@ -10,6 +10,7 @@ import java.io.IOException; import java.util.List; +import org.opensearch.sdk.BaseExtension; import org.opensearch.sdk.Extension; import org.opensearch.sdk.ExtensionRestHandler; import org.opensearch.sdk.ExtensionSettings; @@ -25,7 +26,7 @@ *

* To execute, pass an instatiated object of this class to {@link ExtensionsRunner#run(Extension)}. */ -public class HelloWorldExtension implements Extension { +public class HelloWorldExtension extends BaseExtension { /** * Optional classpath-relative path to a yml file containing extension settings. @@ -40,7 +41,8 @@ public class HelloWorldExtension implements Extension { /** * Instantiate this extension, initializing the connection settings and REST actions. */ - public HelloWorldExtension() { + public HelloWorldExtension() throws IOException { + super(); try { this.settings = initializeSettings(); } catch (IOException e) { diff --git a/src/test/java/org/opensearch/sdk/sample/helloworld/TestHelloWorldExtension.java b/src/test/java/org/opensearch/sdk/sample/helloworld/TestHelloWorldExtension.java index bac31b4d..272d152d 100644 --- a/src/test/java/org/opensearch/sdk/sample/helloworld/TestHelloWorldExtension.java +++ b/src/test/java/org/opensearch/sdk/sample/helloworld/TestHelloWorldExtension.java @@ -7,15 +7,44 @@ */ package org.opensearch.sdk.sample.helloworld; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; +import org.apache.lucene.util.SetOnce; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.opensearch.client.Client; +import org.opensearch.client.node.NodeClient; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.io.stream.NamedWriteableRegistry; +import org.opensearch.common.settings.ClusterSettings; +import org.opensearch.common.settings.Setting; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.settings.Setting.Property; +import org.opensearch.common.xcontent.NamedXContentRegistry; +import org.opensearch.env.Environment; +import org.opensearch.env.NodeEnvironment; +import org.opensearch.repositories.RepositoriesService; import org.opensearch.rest.RestHandler.Route; +import org.opensearch.script.ScriptContext; +import org.opensearch.script.ScriptEngine; +import org.opensearch.script.ScriptService; import org.opensearch.sdk.Extension; import org.opensearch.sdk.ExtensionRestHandler; import org.opensearch.sdk.ExtensionSettings; +import org.opensearch.sdk.NamedWriteableRegistryAPI; import org.opensearch.test.OpenSearchTestCase; +import org.opensearch.threadpool.ExecutorBuilder; +import org.opensearch.threadpool.ThreadPool; +import org.opensearch.watcher.ResourceWatcherService; public class TestHelloWorldExtension extends OpenSearchTestCase { @@ -46,4 +75,34 @@ public void testExtensionRestHandlers() { assertEquals(2, routes.size()); } + /** + @Test + public void testCreateComponents() throws IOException { + Settings settings = Settings.builder().put("node.name", Property.NodeScope).build(); + ThreadPool threadPool = new ThreadPool(settings, new AtomicReference<>(), Collections.emptyList().toArray(new ExecutorBuilder[0])); + Client client = new NodeClient(settings, threadPool); + ClusterService clusterService = new ClusterService(settings, new ClusterSettings(settings, new HashSet>()), threadPool); + ResourceWatcherService resourceWatcherService = new ResourceWatcherService(settings, threadPool); + ScriptService scriptService = new ScriptService(settings, new HashMap(), new HashMap>()); + NamedXContentRegistry xContentRegistry = new NamedXContentRegistry(new ArrayList()); + Environment environment = new Environment(settings, Paths.get("")); + NodeEnvironment nodeEnvironment = new NodeEnvironment(settings, environment); + NamedWriteableRegistry namedWriteableRegistry = new NamedWriteableRegistryAPI().getRegistry(); + IndexNameExpressionResolver indexNameExpressionResolver = new IndexNameExpressionResolver(threadPool.getThreadContext()); + final SetOnce repositoriesServiceReference = new SetOnce<>(); + Supplier repositoriesServiceSupplier = repositoriesServiceReference::get; + assertEquals(threadPool, extension.getThreadPool()); + assertEquals(client, extension.getClient()); + assertEquals(clusterService, extension.getClusterService()); + assertEquals(resourceWatcherService, extension.getResourceWatcherService()); + assertEquals(scriptService, extension.getScriptService()); + assertEquals(xContentRegistry, extension.getNamedXContentRegistry()); + assertEquals(environment, extension.getEnvironment()); + assertEquals(nodeEnvironment, extension.getNodeEnvironment()); + assertEquals(namedWriteableRegistry, extension.getNamedWriteableRegistry()); + assertEquals(indexNameExpressionResolver, extension.getIndexNameExpressionResolver()); + assertEquals(repositoriesServiceSupplier, extension.getRepositoriesServiceSupplier()); + } + */ + } From 152676c75424bbd4e9a6b5cae91c83ff5b99d7fc Mon Sep 17 00:00:00 2001 From: Ryan Bogan Date: Thu, 29 Sep 2022 18:20:24 +0000 Subject: [PATCH 2/8] Fixed minor error Signed-off-by: Ryan Bogan --- src/main/java/org/opensearch/sdk/Extension.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/opensearch/sdk/Extension.java b/src/main/java/org/opensearch/sdk/Extension.java index 8aefeb07..04677c27 100644 --- a/src/main/java/org/opensearch/sdk/Extension.java +++ b/src/main/java/org/opensearch/sdk/Extension.java @@ -13,11 +13,19 @@ import java.util.Collections; import java.util.Collection; import java.util.List; +import java.util.function.Supplier; +import org.opensearch.client.Client; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.io.stream.NamedWriteableRegistry; import org.opensearch.common.xcontent.NamedXContentRegistry; import org.opensearch.env.Environment; +import org.opensearch.env.NodeEnvironment; +import org.opensearch.repositories.RepositoriesService; +import org.opensearch.script.ScriptService; import org.opensearch.threadpool.ThreadPool; +import org.opensearch.watcher.ResourceWatcherService; import org.opensearch.common.settings.Setting; @@ -46,7 +54,6 @@ public interface Extension { List getExtensionRestHandlers(); /** - <<<<<<< Updated upstream * Gets an optional list of custom {@link Setting} for the extension to register with OpenSearch. * * @return a list of custom settings this extension uses. @@ -56,8 +63,6 @@ default List> getSettings() { } /** - ======= - >>>>>>> Stashed changes * Returns components added by this plugin. * * Any components returned that implement {@link LifecycleComponent} will have their lifecycle managed. From c8a8bc08ba00297ecd2fd9b3400be967f63dd0b4 Mon Sep 17 00:00:00 2001 From: Ryan Bogan Date: Tue, 4 Oct 2022 20:09:41 +0000 Subject: [PATCH 3/8] Changed create component arguments Signed-off-by: Ryan Bogan --- .../org/opensearch/sdk/BaseExtension.java | 7 +---- .../java/org/opensearch/sdk/Extension.java | 10 ------- .../org/opensearch/sdk/ExtensionsRunner.java | 12 ++++---- .../helloworld/TestHelloWorldExtension.java | 30 ------------------- 4 files changed, 8 insertions(+), 51 deletions(-) diff --git a/src/main/java/org/opensearch/sdk/BaseExtension.java b/src/main/java/org/opensearch/sdk/BaseExtension.java index 81090cad..db004dac 100644 --- a/src/main/java/org/opensearch/sdk/BaseExtension.java +++ b/src/main/java/org/opensearch/sdk/BaseExtension.java @@ -11,7 +11,6 @@ import java.util.Collections; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.common.xcontent.NamedXContentRegistry; import org.opensearch.env.Environment; import org.opensearch.threadpool.ThreadPool; @@ -19,28 +18,24 @@ public abstract class BaseExtension implements Extension { protected SDKClient client; protected ClusterService clusterService; protected ThreadPool threadPool; - protected NamedXContentRegistry xContentRegistry; protected Environment environment; /** * Empty constructor to fulfill abstract class requirements */ - public BaseExtension() { + protected BaseExtension() { } - @Override public Collection createComponents( SDKClient client, ClusterService clusterService, ThreadPool threadPool, - NamedXContentRegistry xContentRegistry, Environment environment ) { this.client = client; this.clusterService = clusterService; this.threadPool = threadPool; - this.xContentRegistry = xContentRegistry; this.environment = environment; return Collections.emptyList(); diff --git a/src/main/java/org/opensearch/sdk/Extension.java b/src/main/java/org/opensearch/sdk/Extension.java index 04677c27..db52b1f2 100644 --- a/src/main/java/org/opensearch/sdk/Extension.java +++ b/src/main/java/org/opensearch/sdk/Extension.java @@ -13,19 +13,10 @@ import java.util.Collections; import java.util.Collection; import java.util.List; -import java.util.function.Supplier; -import org.opensearch.client.Client; -import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.common.io.stream.NamedWriteableRegistry; -import org.opensearch.common.xcontent.NamedXContentRegistry; import org.opensearch.env.Environment; -import org.opensearch.env.NodeEnvironment; -import org.opensearch.repositories.RepositoriesService; -import org.opensearch.script.ScriptService; import org.opensearch.threadpool.ThreadPool; -import org.opensearch.watcher.ResourceWatcherService; import org.opensearch.common.settings.Setting; @@ -79,7 +70,6 @@ public Collection createComponents( SDKClient client, ClusterService clusterService, ThreadPool threadPool, - NamedXContentRegistry xContentRegistry, Environment environment ); diff --git a/src/main/java/org/opensearch/sdk/ExtensionsRunner.java b/src/main/java/org/opensearch/sdk/ExtensionsRunner.java index 14296b1b..60b6ac49 100644 --- a/src/main/java/org/opensearch/sdk/ExtensionsRunner.java +++ b/src/main/java/org/opensearch/sdk/ExtensionsRunner.java @@ -149,7 +149,10 @@ private ExtensionsRunner(Extension extension) throws IOException { // save custom settings this.customSettings = extension.getSettings(); // initialize the transport service - this.initializeExtensionTransportService(this.getSettings()); + ThreadPool threadPool = new ThreadPool(this.getSettings()); + this.initializeExtensionTransportService(this.getSettings(), threadPool); + // Create components + extension.createComponents(new SDKClient(), null, threadPool, null); // start listening on configured port and wait for connection from OpenSearch this.startActionListener(0); } @@ -330,9 +333,7 @@ public Netty4Transport getNetty4Transport(Settings settings, ThreadPool threadPo * @param settings The transport settings to configure. * @return The initialized TransportService object. */ - public TransportService initializeExtensionTransportService(Settings settings) { - - ThreadPool threadPool = new ThreadPool(settings); + public TransportService initializeExtensionTransportService(Settings settings, ThreadPool threadPool) { Netty4Transport transport = getNetty4Transport(settings, threadPool); @@ -659,7 +660,8 @@ public static void main(String[] args) throws IOException { ExtensionsRunner extensionsRunner = new ExtensionsRunner(); // initialize the transport service - extensionsRunner.initializeExtensionTransportService(extensionsRunner.getSettings()); + ThreadPool threadPool = new ThreadPool(extensionsRunner.getSettings()); + extensionsRunner.initializeExtensionTransportService(extensionsRunner.getSettings(), threadPool); // start listening on configured port and wait for connection from OpenSearch extensionsRunner.startActionListener(0); } diff --git a/src/test/java/org/opensearch/sdk/sample/helloworld/TestHelloWorldExtension.java b/src/test/java/org/opensearch/sdk/sample/helloworld/TestHelloWorldExtension.java index c99112ac..bac31b4d 100644 --- a/src/test/java/org/opensearch/sdk/sample/helloworld/TestHelloWorldExtension.java +++ b/src/test/java/org/opensearch/sdk/sample/helloworld/TestHelloWorldExtension.java @@ -46,34 +46,4 @@ public void testExtensionRestHandlers() { assertEquals(2, routes.size()); } - /** - @Test - public void testCreateComponents() throws IOException { - Settings settings = Settings.builder().put("node.name", Property.NodeScope).build(); - ThreadPool threadPool = new ThreadPool(settings, new AtomicReference<>(), Collections.emptyList().toArray(new ExecutorBuilder[0])); - Client client = new NodeClient(settings, threadPool); - ClusterService clusterService = new ClusterService(settings, new ClusterSettings(settings, new HashSet>()), threadPool); - ResourceWatcherService resourceWatcherService = new ResourceWatcherService(settings, threadPool); - ScriptService scriptService = new ScriptService(settings, new HashMap(), new HashMap>()); - NamedXContentRegistry xContentRegistry = new NamedXContentRegistry(new ArrayList()); - Environment environment = new Environment(settings, Paths.get("")); - NodeEnvironment nodeEnvironment = new NodeEnvironment(settings, environment); - NamedWriteableRegistry namedWriteableRegistry = new NamedWriteableRegistryAPI().getRegistry(); - IndexNameExpressionResolver indexNameExpressionResolver = new IndexNameExpressionResolver(threadPool.getThreadContext()); - final SetOnce repositoriesServiceReference = new SetOnce<>(); - Supplier repositoriesServiceSupplier = repositoriesServiceReference::get; - assertEquals(threadPool, extension.getThreadPool()); - assertEquals(client, extension.getClient()); - assertEquals(clusterService, extension.getClusterService()); - assertEquals(resourceWatcherService, extension.getResourceWatcherService()); - assertEquals(scriptService, extension.getScriptService()); - assertEquals(xContentRegistry, extension.getNamedXContentRegistry()); - assertEquals(environment, extension.getEnvironment()); - assertEquals(nodeEnvironment, extension.getNodeEnvironment()); - assertEquals(namedWriteableRegistry, extension.getNamedWriteableRegistry()); - assertEquals(indexNameExpressionResolver, extension.getIndexNameExpressionResolver()); - assertEquals(repositoriesServiceSupplier, extension.getRepositoriesServiceSupplier()); - } - */ - } From 7dfb491e185a6b6a32e56defd228ba98be0916ae Mon Sep 17 00:00:00 2001 From: Ryan Bogan Date: Wed, 5 Oct 2022 18:31:10 +0000 Subject: [PATCH 4/8] Addressed PR Comments Signed-off-by: Ryan Bogan --- src/main/java/org/opensearch/sdk/Extension.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/opensearch/sdk/Extension.java b/src/main/java/org/opensearch/sdk/Extension.java index db52b1f2..e42d937d 100644 --- a/src/main/java/org/opensearch/sdk/Extension.java +++ b/src/main/java/org/opensearch/sdk/Extension.java @@ -14,6 +14,7 @@ import java.util.Collection; import java.util.List; +import org.opensearch.client.OpenSearchClient; import org.opensearch.cluster.service.ClusterService; import org.opensearch.env.Environment; import org.opensearch.threadpool.ThreadPool; @@ -54,20 +55,15 @@ default List> getSettings() { } /** - * Returns components added by this plugin. - * - * Any components returned that implement {@link LifecycleComponent} will have their lifecycle managed. - * Note: To aid in the migration away from guice, all objects returned as components will be bound in guice - * to themselves. + * Returns components added by this extension. * * @param client A client to make requests to the system * @param clusterService A service to allow watching and updating cluster state * @param threadPool A service to allow retrieving an executor to run an async action - * @param xContentRegistry the registry for extensible xContent parsing * @param environment the environment for path and setting configurations */ public Collection createComponents( - SDKClient client, + OpenSearchClient client, ClusterService clusterService, ThreadPool threadPool, Environment environment From 253025313d5db5b50b6cc185127993917706d57d Mon Sep 17 00:00:00 2001 From: Ryan Bogan Date: Mon, 10 Oct 2022 15:54:28 +0000 Subject: [PATCH 5/8] Fixed minor errors Signed-off-by: Ryan Bogan --- src/main/java/org/opensearch/sdk/ExtensionsRunner.java | 6 +++++- src/main/java/org/opensearch/sdk/NettyTransport.java | 9 ++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/opensearch/sdk/ExtensionsRunner.java b/src/main/java/org/opensearch/sdk/ExtensionsRunner.java index 4d07f6be..71b03d50 100644 --- a/src/main/java/org/opensearch/sdk/ExtensionsRunner.java +++ b/src/main/java/org/opensearch/sdk/ExtensionsRunner.java @@ -494,7 +494,11 @@ public static void main(String[] args) throws IOException { ExtensionsRunner extensionsRunner = new ExtensionsRunner(); // initialize the transport service - extensionsRunner.nettyTransport.initializeExtensionTransportService(extensionsRunner.getSettings(), extensionsRunner); + extensionsRunner.nettyTransport.initializeExtensionTransportService( + extensionsRunner.getSettings(), + new ThreadPool(extensionsRunner.getSettings()), + extensionsRunner + ); // start listening on configured port and wait for connection from OpenSearch extensionsRunner.startActionListener(0); } diff --git a/src/main/java/org/opensearch/sdk/NettyTransport.java b/src/main/java/org/opensearch/sdk/NettyTransport.java index 634dc5e7..5dcfb068 100644 --- a/src/main/java/org/opensearch/sdk/NettyTransport.java +++ b/src/main/java/org/opensearch/sdk/NettyTransport.java @@ -85,12 +85,15 @@ public Netty4Transport getNetty4Transport(Settings settings, ThreadPool threadPo * Initializes the TransportService object for this extension. This object will control communication between the extension and OpenSearch. * * @param settings The transport settings to configure. + * @param threadPool The thread pool to use to start transport service. * @param extensionsRunner method to call * @return The initialized TransportService object. */ - public TransportService initializeExtensionTransportService(Settings settings, ExtensionsRunner extensionsRunner) { - - ThreadPool threadPool = new ThreadPool(settings); + public TransportService initializeExtensionTransportService( + Settings settings, + ThreadPool threadPool, + ExtensionsRunner extensionsRunner + ) { Netty4Transport transport = getNetty4Transport(settings, threadPool); From 43d9cab40e8fbd7f6cbe7fc40a7e8b6bba39a9e4 Mon Sep 17 00:00:00 2001 From: Daniel Widdis Date: Mon, 10 Oct 2022 15:19:36 -0700 Subject: [PATCH 6/8] Return consumed params and content from extensions (#169) * Remove duplicate copies of registries in handlers Signed-off-by: Daniel Widdis * Move ExtensionRestResponse to OpenSearch Signed-off-by: Daniel Widdis * Add a POST request to parse content Signed-off-by: Daniel Widdis * Add a DELETE request corresponding to the POST Signed-off-by: Daniel Widdis * Add consumed params and content to Extension Responses Signed-off-by: Daniel Widdis * Update tests and OpenAPI spec Signed-off-by: Daniel Widdis Signed-off-by: Daniel Widdis --- .../opensearch/sdk/ExtensionRestHandler.java | 1 + .../opensearch/sdk/ExtensionRestResponse.java | 99 -------------- .../org/opensearch/sdk/ExtensionsRunner.java | 8 +- .../ExtensionsRestRequestHandler.java | 32 ++++- .../handlers/OpensearchRequestHandler.java | 13 +- .../helloworld/rest/RestHelloAction.java | 84 +++++++++++- .../sdk/sample/helloworld/spec/openapi.json | 51 ++++++- .../sdk/sample/helloworld/spec/openapi.yaml | 40 +++++- .../sdk/TestExtensionRestPathRegistry.java | 1 + .../sdk/TestExtensionRestResponse.java | 125 ------------------ .../opensearch/sdk/TestExtensionsRunner.java | 4 +- .../helloworld/TestHelloWorldExtension.java | 2 +- .../helloworld/rest/TestRestHelloAction.java | 73 ++++++++-- 13 files changed, 273 insertions(+), 260 deletions(-) delete mode 100644 src/main/java/org/opensearch/sdk/ExtensionRestResponse.java delete mode 100644 src/test/java/org/opensearch/sdk/TestExtensionRestResponse.java diff --git a/src/main/java/org/opensearch/sdk/ExtensionRestHandler.java b/src/main/java/org/opensearch/sdk/ExtensionRestHandler.java index 4f96b980..cbb86015 100644 --- a/src/main/java/org/opensearch/sdk/ExtensionRestHandler.java +++ b/src/main/java/org/opensearch/sdk/ExtensionRestHandler.java @@ -10,6 +10,7 @@ import java.util.List; import org.opensearch.extensions.rest.ExtensionRestRequest; +import org.opensearch.extensions.rest.ExtensionRestResponse; import org.opensearch.rest.BaseRestHandler; import org.opensearch.rest.RestHandler; import org.opensearch.rest.RestHandler.Route; diff --git a/src/main/java/org/opensearch/sdk/ExtensionRestResponse.java b/src/main/java/org/opensearch/sdk/ExtensionRestResponse.java deleted file mode 100644 index 2b915cf3..00000000 --- a/src/main/java/org/opensearch/sdk/ExtensionRestResponse.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ -package org.opensearch.sdk; - -import java.util.List; - -import org.opensearch.common.bytes.BytesReference; -import org.opensearch.common.xcontent.XContentBuilder; -import org.opensearch.extensions.rest.ExtensionRestRequest; -import org.opensearch.rest.BytesRestResponse; -import org.opensearch.rest.RestStatus; - -/** - * A subclass of {@link BytesRestResponse} which processes the consumed parameters into a custom header. - */ -public class ExtensionRestResponse extends BytesRestResponse { - - /** - * Key passed in {@link BytesRestResponse} headers to identify parameters consumed by the handler. For internal use. - */ - static final String CONSUMED_PARAMS_KEY = "extension.consumed.parameters"; - /** - * Key passed in {@link BytesRestResponse} headers to identify content consumed by the handler. For internal use. - */ - static final String CONSUMED_CONTENT_KEY = "extension.consumed.content"; - - /** - * Creates a new response based on {@link XContentBuilder}. - * - * @param request the REST request being responded to. - * @param status The REST status. - * @param builder The builder for the response. - */ - public ExtensionRestResponse(ExtensionRestRequest request, RestStatus status, XContentBuilder builder) { - super(status, builder); - addConsumedHeaders(request.consumedParams(), request.isContentConsumed()); - } - - /** - * Creates a new plain text response. - * - * @param request the REST request being responded to. - * @param status The REST status. - * @param content A plain text response string. - */ - public ExtensionRestResponse(ExtensionRestRequest request, RestStatus status, String content) { - super(status, content); - addConsumedHeaders(request.consumedParams(), request.isContentConsumed()); - } - - /** - * Creates a new plain text response. - * - * @param request the REST request being responded to. - * @param status The REST status. - * @param contentType The content type of the response string. - * @param content A response string. - */ - public ExtensionRestResponse(ExtensionRestRequest request, RestStatus status, String contentType, String content) { - super(status, contentType, content); - addConsumedHeaders(request.consumedParams(), request.isContentConsumed()); - } - - /** - * Creates a binary response. - * - * @param request the REST request being responded to. - * @param status The REST status. - * @param contentType The content type of the response bytes. - * @param content Response bytes. - */ - public ExtensionRestResponse(ExtensionRestRequest request, RestStatus status, String contentType, byte[] content) { - super(status, contentType, content); - addConsumedHeaders(request.consumedParams(), request.isContentConsumed()); - } - - /** - * Creates a binary response. - * - * @param request the REST request being responded to. - * @param status The REST status. - * @param contentType The content type of the response bytes. - * @param content Response bytes. - */ - public ExtensionRestResponse(ExtensionRestRequest request, RestStatus status, String contentType, BytesReference content) { - super(status, contentType, content); - addConsumedHeaders(request.consumedParams(), request.isContentConsumed()); - } - - private void addConsumedHeaders(List consumedParams, boolean contentConusmed) { - consumedParams.stream().forEach(p -> addHeader(CONSUMED_PARAMS_KEY, p)); - addHeader(CONSUMED_CONTENT_KEY, Boolean.toString(contentConusmed)); - } -} diff --git a/src/main/java/org/opensearch/sdk/ExtensionsRunner.java b/src/main/java/org/opensearch/sdk/ExtensionsRunner.java index e7cfe6bf..6fee89fb 100644 --- a/src/main/java/org/opensearch/sdk/ExtensionsRunner.java +++ b/src/main/java/org/opensearch/sdk/ExtensionsRunner.java @@ -85,13 +85,13 @@ public class ExtensionsRunner { * This field is initialized by a call from {@link ExtensionsInitRequestHandler}. */ public final Settings settings; - private ExtensionNamedWriteableRegistry namedWriteableRegistryApi = new ExtensionNamedWriteableRegistry(); + private ExtensionNamedWriteableRegistry namedWriteableRegistry = new ExtensionNamedWriteableRegistry(); private ExtensionsInitRequestHandler extensionsInitRequestHandler = new ExtensionsInitRequestHandler(); - private OpensearchRequestHandler opensearchRequestHandler = new OpensearchRequestHandler(); + private OpensearchRequestHandler opensearchRequestHandler = new OpensearchRequestHandler(namedWriteableRegistry); private ExtensionsIndicesModuleRequestHandler extensionsIndicesModuleRequestHandler = new ExtensionsIndicesModuleRequestHandler(); private ExtensionsIndicesModuleNameRequestHandler extensionsIndicesModuleNameRequestHandler = new ExtensionsIndicesModuleNameRequestHandler(); - private ExtensionsRestRequestHandler extensionsRestRequestHandler = new ExtensionsRestRequestHandler(); + private ExtensionsRestRequestHandler extensionsRestRequestHandler = new ExtensionsRestRequestHandler(extensionRestPathRegistry); private NettyTransport nettyTransport = new NettyTransport(); /* @@ -203,7 +203,7 @@ public void startTransportService(TransportService transportService) { false, false, NamedWriteableRegistryParseRequest::new, - (request, channel, task) -> channel.sendResponse(namedWriteableRegistryApi.handleNamedWriteableRegistryParseRequest(request)) + (request, channel, task) -> channel.sendResponse(namedWriteableRegistry.handleNamedWriteableRegistryParseRequest(request)) ); transportService.registerRequestHandler( diff --git a/src/main/java/org/opensearch/sdk/handlers/ExtensionsRestRequestHandler.java b/src/main/java/org/opensearch/sdk/handlers/ExtensionsRestRequestHandler.java index f777347c..4e11ebb6 100644 --- a/src/main/java/org/opensearch/sdk/handlers/ExtensionsRestRequestHandler.java +++ b/src/main/java/org/opensearch/sdk/handlers/ExtensionsRestRequestHandler.java @@ -11,12 +11,17 @@ import org.apache.logging.log4j.Logger; import org.opensearch.common.bytes.BytesReference; import org.opensearch.extensions.rest.ExtensionRestRequest; +import org.opensearch.extensions.rest.ExtensionRestResponse; import org.opensearch.extensions.rest.RestExecuteOnExtensionResponse; -import org.opensearch.rest.RestStatus; import org.opensearch.sdk.ExtensionRestHandler; import org.opensearch.sdk.ExtensionsRunner; import org.opensearch.sdk.ExtensionRestPathRegistry; -import org.opensearch.sdk.ExtensionRestResponse; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Collections.emptyMap; +import static java.util.Collections.emptyList; +import static org.opensearch.rest.BytesRestResponse.TEXT_CONTENT_TYPE; +import static org.opensearch.rest.RestStatus.NOT_FOUND; /** * This class handles the request from OpenSearch to a {@link ExtensionsRunner#startTransportService(TransportService transportService)} call. @@ -24,7 +29,16 @@ public class ExtensionsRestRequestHandler { private static final Logger logger = LogManager.getLogger(ExtensionsRestRequestHandler.class); - private ExtensionRestPathRegistry extensionRestPathRegistry = new ExtensionRestPathRegistry(); + private final ExtensionRestPathRegistry extensionRestPathRegistry; + + /** + * Instantiate this class with an existing registry + * + * @param restPathRegistry The ExtensionsRunnerer's REST path registry + */ + public ExtensionsRestRequestHandler(ExtensionRestPathRegistry restPathRegistry) { + this.extensionRestPathRegistry = restPathRegistry; + } /** * Handles a request from OpenSearch to execute a REST request on the extension. @@ -37,8 +51,12 @@ public RestExecuteOnExtensionResponse handleRestExecuteOnExtensionRequest(Extens ExtensionRestHandler restHandler = extensionRestPathRegistry.getHandler(request.method(), request.path()); if (restHandler == null) { return new RestExecuteOnExtensionResponse( - RestStatus.NOT_FOUND, - "No handler for " + ExtensionRestPathRegistry.restPathToString(request.method(), request.path()) + NOT_FOUND, + TEXT_CONTENT_TYPE, + String.join(" ", "No handler for", request.method().name(), request.path()).getBytes(UTF_8), + emptyMap(), + emptyList(), + false ); } @@ -49,7 +67,9 @@ public RestExecuteOnExtensionResponse handleRestExecuteOnExtensionRequest(Extens response.status(), response.contentType(), BytesReference.toBytes(response.content()), - response.getHeaders() + response.getHeaders(), + response.getConsumedParams(), + response.isContentConsumed() ); } diff --git a/src/main/java/org/opensearch/sdk/handlers/OpensearchRequestHandler.java b/src/main/java/org/opensearch/sdk/handlers/OpensearchRequestHandler.java index 49cec0b4..b203065d 100644 --- a/src/main/java/org/opensearch/sdk/handlers/OpensearchRequestHandler.java +++ b/src/main/java/org/opensearch/sdk/handlers/OpensearchRequestHandler.java @@ -17,7 +17,16 @@ */ public class OpensearchRequestHandler { - private ExtensionNamedWriteableRegistry namedWriteableRegistryApi = new ExtensionNamedWriteableRegistry(); + private final ExtensionNamedWriteableRegistry extensionNamedWriteableRegistry; + + /** + * Instantiate this object with a namedWriteableRegistry + * + * @param namedWriteableRegistry The registry passed from ExtensionsRunner + */ + public OpensearchRequestHandler(ExtensionNamedWriteableRegistry namedWriteableRegistry) { + this.extensionNamedWriteableRegistry = namedWriteableRegistry; + } /** * Handles a request from OpenSearch and invokes the extension point API corresponding with the request type @@ -30,7 +39,7 @@ public TransportResponse handleOpenSearchRequest(OpenSearchRequest request) thro // Read enum switch (request.getRequestType()) { case REQUEST_OPENSEARCH_NAMED_WRITEABLE_REGISTRY: - return namedWriteableRegistryApi.handleNamedWriteableRegistryRequest(request); + return extensionNamedWriteableRegistry.handleNamedWriteableRegistryRequest(request); // Add additional request handlers here default: throw new IllegalArgumentException("Handler not present for the provided request"); diff --git a/src/main/java/org/opensearch/sdk/sample/helloworld/rest/RestHelloAction.java b/src/main/java/org/opensearch/sdk/sample/helloworld/rest/RestHelloAction.java index 4e5ce476..cc1930fa 100644 --- a/src/main/java/org/opensearch/sdk/sample/helloworld/rest/RestHelloAction.java +++ b/src/main/java/org/opensearch/sdk/sample/helloworld/rest/RestHelloAction.java @@ -7,20 +7,26 @@ */ package org.opensearch.sdk.sample.helloworld.rest; +import org.opensearch.common.xcontent.XContentType; import org.opensearch.extensions.rest.ExtensionRestRequest; +import org.opensearch.extensions.rest.ExtensionRestResponse; import org.opensearch.rest.RestHandler.Route; import org.opensearch.rest.RestRequest.Method; import org.opensearch.sdk.ExtensionRestHandler; -import org.opensearch.sdk.ExtensionRestResponse; - import java.net.URLDecoder; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.List; +import java.util.Random; +import static org.opensearch.rest.RestRequest.Method.DELETE; import static org.opensearch.rest.RestRequest.Method.GET; +import static org.opensearch.rest.RestRequest.Method.POST; import static org.opensearch.rest.RestRequest.Method.PUT; import static org.opensearch.rest.RestStatus.BAD_REQUEST; +import static org.opensearch.rest.RestStatus.NOT_ACCEPTABLE; import static org.opensearch.rest.RestStatus.NOT_FOUND; +import static org.opensearch.rest.RestStatus.NOT_MODIFIED; import static org.opensearch.rest.RestStatus.OK; /** @@ -30,10 +36,12 @@ public class RestHelloAction implements ExtensionRestHandler { private static final String GREETING = "Hello, %s!"; private String worldName = "World"; + private List worldAdjectives = new ArrayList<>(); + private Random rand = new Random(); @Override public List routes() { - return List.of(new Route(GET, "/hello"), new Route(PUT, "/hello/{name}")); + return List.of(new Route(GET, "/hello"), new Route(POST, "/hello"), new Route(DELETE, "/hello"), new Route(PUT, "/hello/{name}")); } @Override @@ -42,6 +50,10 @@ public ExtensionRestResponse handleRequest(ExtensionRestRequest request) { if (Method.GET.equals(method)) { return handleGetRequest(request); + } else if (Method.POST.equals(method)) { + return handlePostRequest(request); + } else if (Method.DELETE.equals(method)) { + return handleDeleteRequest(request); } else if (Method.PUT.equals(method)) { return handlePutRequest(request); } @@ -49,7 +61,54 @@ public ExtensionRestResponse handleRequest(ExtensionRestRequest request) { } private ExtensionRestResponse handleGetRequest(ExtensionRestRequest request) { - return new ExtensionRestResponse(request, OK, String.format(GREETING, worldName)); + String worldNameWithRandomAdjective = worldAdjectives.isEmpty() + ? worldName + : String.join(" ", worldAdjectives.get(rand.nextInt(worldAdjectives.size())), worldName); + return new ExtensionRestResponse(request, OK, String.format(GREETING, worldNameWithRandomAdjective)); + } + + private ExtensionRestResponse handlePostRequest(ExtensionRestRequest request) { + if (request.hasContent()) { + String adjective = ""; + XContentType contentType = request.getXContentType(); + if (contentType == null) { + // Plain text + adjective = request.content().utf8ToString(); + } else if (contentType.equals(XContentType.JSON)) { + adjective = parseJsonAdjective(request.content().utf8ToString()); + } else { + return new ExtensionRestResponse(request, NOT_ACCEPTABLE, "Only text and JSON content types are supported"); + } + if (!adjective.isBlank()) { + worldAdjectives.add(adjective); + return new ExtensionRestResponse(request, OK, "Added " + adjective + " to words that describe the world!"); + } + return new ExtensionRestResponse(request, BAD_REQUEST, "No adjective included with POST request"); + } + return new ExtensionRestResponse(request, BAD_REQUEST, "No content included with POST request"); + } + + private ExtensionRestResponse handleDeleteRequest(ExtensionRestRequest request) { + if (request.hasContent()) { + String adjective = ""; + XContentType contentType = request.getXContentType(); + if (contentType == null) { + // Plain text + adjective = request.content().utf8ToString(); + } else if (contentType.equals(XContentType.JSON)) { + adjective = parseJsonAdjective(request.content().utf8ToString()); + } else { + return new ExtensionRestResponse(request, NOT_ACCEPTABLE, "Only text and JSON content types are supported"); + } + if (!adjective.isBlank()) { + if (worldAdjectives.remove(adjective)) { + return new ExtensionRestResponse(request, OK, "Goodbye, " + adjective + " world!"); + } + return new ExtensionRestResponse(request, NOT_MODIFIED, ""); + } + return new ExtensionRestResponse(request, BAD_REQUEST, "No adjective included with DELETE request"); + } + return new ExtensionRestResponse(request, BAD_REQUEST, "No content included with DELETE request"); } private ExtensionRestResponse handlePutRequest(ExtensionRestRequest request) { @@ -66,4 +125,21 @@ private ExtensionRestResponse handleBadRequest(ExtensionRestRequest request) { return new ExtensionRestResponse(request, NOT_FOUND, "Extension REST action improperly configured to handle " + request.toString()); } + private String parseJsonAdjective(String json) { + // TODO: Once CreateComponents has an XContentRegistry available we can parse from there + // For now we just hack our way into the result. + boolean foundLabel = false; + boolean foundColon = false; + for (String s : json.split("\"")) { + if (!foundLabel) { + foundLabel = "adjective".equals(s); + } else if (!foundColon) { + foundColon = s.contains(":"); + } else { + // This is the adjective! + return s; + } + } + return ""; + } } diff --git a/src/main/java/org/opensearch/sdk/sample/helloworld/spec/openapi.json b/src/main/java/org/opensearch/sdk/sample/helloworld/spec/openapi.json index 03cb9c58..15a43e76 100644 --- a/src/main/java/org/opensearch/sdk/sample/helloworld/spec/openapi.json +++ b/src/main/java/org/opensearch/sdk/sample/helloworld/spec/openapi.json @@ -1,5 +1,5 @@ { - "openapi": "3.0.3", + "openapi": "3.1.0", "info": { "title": "Hello World", "description": "This is a sample Hello World extension.", @@ -43,6 +43,53 @@ "description": "Improper REST action configuration" } } + }, + "post": { + "tags": [ + "hello" + ], + "summary": "Adds an adjective to a list", + "description": "Adds an adjective to a list from which a random element will be prepended to the world name", + "operationId": "", + "responses": { + "200": { + "description": "Successful operation" + }, + "400": { + "description": "Syntax Error in request" + }, + "404": { + "description": "Improper REST action configuration" + }, + "406": { + "description": "Content format not text or JSON" + } + } + }, + "delete": { + "tags": [ + "hello" + ], + "summary": "Removes an adjective from the list", + "description": "Removes an adjective from the list from which a random element will be prepended to the world name", + "operationId": "", + "responses": { + "200": { + "description": "Successful operation" + }, + "304": { + "description": "Adjective not in the list, no action taken" + }, + "400": { + "description": "Syntax Error in request" + }, + "404": { + "description": "Improper REST action configuration" + }, + "406": { + "description": "Content format not text or JSON" + } + } } }, "/hello/{name}": { @@ -56,7 +103,7 @@ { "name": "name", "in": "path", - "description": "a new name for the world", + "description": "A new name for the world", "required": true, "schema": { "type": "string" diff --git a/src/main/java/org/opensearch/sdk/sample/helloworld/spec/openapi.yaml b/src/main/java/org/opensearch/sdk/sample/helloworld/spec/openapi.yaml index 4a35fe38..4b8dc046 100644 --- a/src/main/java/org/opensearch/sdk/sample/helloworld/spec/openapi.yaml +++ b/src/main/java/org/opensearch/sdk/sample/helloworld/spec/openapi.yaml @@ -1,4 +1,4 @@ -openapi: 3.0.3 +openapi: 3.1.0 info: title: Hello World description: This is a sample Hello World extension. @@ -28,6 +28,42 @@ paths: description: Syntax Error in URI '404': description: Improper REST action configuration + post: + tags: + - hello + summary: Adds an adjective to a list + description: >- + Adds an adjective to a list from which a random element will be + prepended to the world name + operationId: '' + responses: + '200': + description: Successful operation + '400': + description: Syntax Error in request + '404': + description: Improper REST action configuration + '406': + description: Content format not text or JSON + delete: + tags: + - hello + summary: Removes an adjective from the list + description: >- + Removes an adjective from the list from which a random element + will be prepended to the world name + operationId: '' + responses: + '200': + description: Successful operation + '304': + description: Adjective not in the list, no action taken + '400': + description: Syntax Error in request + '404': + description: Improper REST action configuration + '406': + description: Content format not text or JSON /hello/{name}: put: tags: @@ -37,7 +73,7 @@ paths: parameters: - name: name in: path - description: a new name for the world + description: A new name for the world required: true schema: type: string diff --git a/src/test/java/org/opensearch/sdk/TestExtensionRestPathRegistry.java b/src/test/java/org/opensearch/sdk/TestExtensionRestPathRegistry.java index d6f2ae38..0510ca8c 100644 --- a/src/test/java/org/opensearch/sdk/TestExtensionRestPathRegistry.java +++ b/src/test/java/org/opensearch/sdk/TestExtensionRestPathRegistry.java @@ -5,6 +5,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.opensearch.extensions.rest.ExtensionRestRequest; +import org.opensearch.extensions.rest.ExtensionRestResponse; import org.opensearch.rest.RestHandler.Route; import org.opensearch.rest.RestRequest.Method; import org.opensearch.test.OpenSearchTestCase; diff --git a/src/test/java/org/opensearch/sdk/TestExtensionRestResponse.java b/src/test/java/org/opensearch/sdk/TestExtensionRestResponse.java deleted file mode 100644 index 4bd982da..00000000 --- a/src/test/java/org/opensearch/sdk/TestExtensionRestResponse.java +++ /dev/null @@ -1,125 +0,0 @@ -package org.opensearch.sdk; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.Collections; -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.opensearch.common.bytes.BytesArray; -import org.opensearch.common.bytes.BytesReference; -import org.opensearch.common.xcontent.XContentBuilder; -import org.opensearch.common.xcontent.XContentType; -import org.opensearch.extensions.rest.ExtensionRestRequest; -import org.opensearch.rest.RestRequest.Method; -import org.opensearch.test.OpenSearchTestCase; - -import static org.opensearch.rest.BytesRestResponse.TEXT_CONTENT_TYPE; -import static org.opensearch.rest.RestStatus.ACCEPTED; -import static org.opensearch.rest.RestStatus.OK; -import static org.opensearch.sdk.ExtensionRestResponse.CONSUMED_PARAMS_KEY; - -public class TestExtensionRestResponse extends OpenSearchTestCase { - - private static final String OCTET_CONTENT_TYPE = "application/octet-stream"; - private static final String JSON_CONTENT_TYPE = "application/json; charset=UTF-8"; - - private String testText; - private byte[] testBytes; - private ExtensionRestRequest request; - - @Override - @BeforeEach - public void setUp() throws Exception { - super.setUp(); - testText = "plain text"; - testBytes = new byte[] { 1, 2 }; - request = new ExtensionRestRequest(Method.GET, "/foo", Collections.emptyMap(), null, new BytesArray("Text Content"), null); - // consume params "foo" and "bar" - request.param("foo"); - request.param("bar"); - // consume content - request.content(); - } - - @Test - public void testConstructorWithBuilder() throws IOException { - XContentBuilder builder = XContentBuilder.builder(XContentType.JSON.xContent()); - builder.startObject(); - builder.field("status", ACCEPTED); - builder.endObject(); - ExtensionRestResponse response = new ExtensionRestResponse(request, OK, builder); - - assertEquals(OK, response.status()); - assertEquals(JSON_CONTENT_TYPE, response.contentType()); - assertEquals("{\"status\":\"ACCEPTED\"}", response.content().utf8ToString()); - List consumedParams = response.getHeaders().get(CONSUMED_PARAMS_KEY); - for (String param : consumedParams) { - assertTrue(request.consumedParams().contains(param)); - } - assertTrue(request.isContentConsumed()); - } - - @Test - public void testConstructorWithPlainText() { - ExtensionRestResponse response = new ExtensionRestResponse(request, OK, testText); - - assertEquals(OK, response.status()); - assertEquals(TEXT_CONTENT_TYPE, response.contentType()); - assertEquals(testText, response.content().utf8ToString()); - List consumedParams = response.getHeaders().get(CONSUMED_PARAMS_KEY); - for (String param : consumedParams) { - assertTrue(request.consumedParams().contains(param)); - } - assertTrue(request.isContentConsumed()); - } - - @Test - public void testConstructorWithText() { - ExtensionRestResponse response = new ExtensionRestResponse(request, OK, TEXT_CONTENT_TYPE, testText); - - assertEquals(OK, response.status()); - assertEquals(TEXT_CONTENT_TYPE, response.contentType()); - assertEquals(testText, response.content().utf8ToString()); - - List consumedParams = response.getHeaders().get(CONSUMED_PARAMS_KEY); - for (String param : consumedParams) { - assertTrue(request.consumedParams().contains(param)); - } - assertTrue(request.isContentConsumed()); - } - - @Test - public void testConstructorWithByteArray() { - ExtensionRestResponse response = new ExtensionRestResponse(request, OK, OCTET_CONTENT_TYPE, testBytes); - - assertEquals(OK, response.status()); - assertEquals(OCTET_CONTENT_TYPE, response.contentType()); - assertArrayEquals(testBytes, BytesReference.toBytes(response.content())); - List consumedParams = response.getHeaders().get(CONSUMED_PARAMS_KEY); - for (String param : consumedParams) { - assertTrue(request.consumedParams().contains(param)); - } - assertTrue(request.isContentConsumed()); - } - - @Test - public void testConstructorWithBytesReference() { - ExtensionRestResponse response = new ExtensionRestResponse( - request, - OK, - OCTET_CONTENT_TYPE, - BytesReference.fromByteBuffer(ByteBuffer.wrap(testBytes, 0, 2)) - ); - - assertEquals(OK, response.status()); - assertEquals(OCTET_CONTENT_TYPE, response.contentType()); - assertArrayEquals(testBytes, BytesReference.toBytes(response.content())); - List consumedParams = response.getHeaders().get(CONSUMED_PARAMS_KEY); - for (String param : consumedParams) { - assertTrue(request.consumedParams().contains(param)); - } - assertTrue(request.isContentConsumed()); - } -} diff --git a/src/test/java/org/opensearch/sdk/TestExtensionsRunner.java b/src/test/java/org/opensearch/sdk/TestExtensionsRunner.java index dc2fef61..ebcb12f8 100644 --- a/src/test/java/org/opensearch/sdk/TestExtensionsRunner.java +++ b/src/test/java/org/opensearch/sdk/TestExtensionsRunner.java @@ -73,8 +73,8 @@ public class TestExtensionsRunner extends OpenSearchTestCase { private static final String EXTENSION_NAME = "sample-extension"; private ExtensionsInitRequestHandler extensionsInitRequestHandler = new ExtensionsInitRequestHandler(); - private OpensearchRequestHandler opensearchRequestHandler = new OpensearchRequestHandler(); - private ExtensionsRestRequestHandler extensionsRestRequestHandler = new ExtensionsRestRequestHandler(); + private OpensearchRequestHandler opensearchRequestHandler = new OpensearchRequestHandler(new ExtensionNamedWriteableRegistry()); + private ExtensionsRestRequestHandler extensionsRestRequestHandler = new ExtensionsRestRequestHandler(new ExtensionRestPathRegistry()); private ExtensionsRunner extensionsRunner; private TransportService transportService; diff --git a/src/test/java/org/opensearch/sdk/sample/helloworld/TestHelloWorldExtension.java b/src/test/java/org/opensearch/sdk/sample/helloworld/TestHelloWorldExtension.java index bac31b4d..0abbab4e 100644 --- a/src/test/java/org/opensearch/sdk/sample/helloworld/TestHelloWorldExtension.java +++ b/src/test/java/org/opensearch/sdk/sample/helloworld/TestHelloWorldExtension.java @@ -43,7 +43,7 @@ public void testExtensionRestHandlers() { List extensionRestHandlers = extension.getExtensionRestHandlers(); assertEquals(1, extensionRestHandlers.size()); List routes = extensionRestHandlers.get(0).routes(); - assertEquals(2, routes.size()); + assertEquals(4, routes.size()); } } diff --git a/src/test/java/org/opensearch/sdk/sample/helloworld/rest/TestRestHelloAction.java b/src/test/java/org/opensearch/sdk/sample/helloworld/rest/TestRestHelloAction.java index cfe04a18..3d8a8c71 100644 --- a/src/test/java/org/opensearch/sdk/sample/helloworld/rest/TestRestHelloAction.java +++ b/src/test/java/org/opensearch/sdk/sample/helloworld/rest/TestRestHelloAction.java @@ -21,6 +21,7 @@ import org.opensearch.rest.RestRequest.Method; import org.opensearch.common.bytes.BytesArray; import org.opensearch.common.bytes.BytesReference; +import org.opensearch.common.xcontent.XContentType; import org.opensearch.extensions.rest.ExtensionRestRequest; import org.opensearch.rest.BytesRestResponse; import org.opensearch.rest.RestResponse; @@ -43,11 +44,15 @@ public void setUp() throws Exception { @Test public void testRoutes() { List routes = restHelloAction.routes(); - assertEquals(2, routes.size()); + assertEquals(4, routes.size()); assertEquals(Method.GET, routes.get(0).getMethod()); assertEquals("/hello", routes.get(0).getPath()); - assertEquals(Method.PUT, routes.get(1).getMethod()); - assertEquals("/hello/{name}", routes.get(1).getPath()); + assertEquals(Method.POST, routes.get(1).getMethod()); + assertEquals("/hello", routes.get(1).getPath()); + assertEquals(Method.DELETE, routes.get(2).getMethod()); + assertEquals("/hello", routes.get(2).getPath()); + assertEquals(Method.PUT, routes.get(3).getMethod()); + assertEquals("/hello/{name}", routes.get(3).getPath()); } @Test @@ -66,6 +71,22 @@ public void testHandleRequest() { new BytesArray(""), token ); + ExtensionRestRequest postRequest = new ExtensionRestRequest( + Method.POST, + "/hello", + params, + XContentType.JSON, + new BytesArray("{\"adjective\":\"testable\"}"), + token + ); + ExtensionRestRequest deleteRequest = new ExtensionRestRequest( + Method.DELETE, + "/hello", + params, + null, + new BytesArray("testable"), + token + ); ExtensionRestRequest badRequest = new ExtensionRestRequest( Method.PUT, "/hello/Bad%Request", @@ -74,21 +95,16 @@ public void testHandleRequest() { new BytesArray(""), token ); - ExtensionRestRequest unsuccessfulRequest = new ExtensionRestRequest( - Method.POST, - "/goodbye", - params, - null, - new BytesArray(""), - token - ); + ExtensionRestRequest unhandledRequest = new ExtensionRestRequest(Method.HEAD, "/goodbye", params, null, new BytesArray(""), token); + // Initial default response RestResponse response = restHelloAction.handleRequest(getRequest); assertEquals(RestStatus.OK, response.status()); assertEquals(BytesRestResponse.TEXT_CONTENT_TYPE, response.contentType()); String responseStr = new String(BytesReference.toBytes(response.content()), StandardCharsets.UTF_8); assertEquals("Hello, World!", responseStr); + // Change world's name response = restHelloAction.handleRequest(putRequest); assertEquals(RestStatus.OK, response.status()); assertEquals(BytesRestResponse.TEXT_CONTENT_TYPE, response.contentType()); @@ -101,17 +117,48 @@ public void testHandleRequest() { responseStr = new String(BytesReference.toBytes(response.content()), StandardCharsets.UTF_8); assertEquals("Hello, Passing Test!", responseStr); + // Add an adjective + response = restHelloAction.handleRequest(postRequest); + assertEquals(RestStatus.OK, response.status()); + assertEquals(BytesRestResponse.TEXT_CONTENT_TYPE, response.contentType()); + responseStr = new String(BytesReference.toBytes(response.content()), StandardCharsets.UTF_8); + assertTrue(responseStr.contains("testable")); + + response = restHelloAction.handleRequest(getRequest); + assertEquals(RestStatus.OK, response.status()); + assertEquals(BytesRestResponse.TEXT_CONTENT_TYPE, response.contentType()); + responseStr = new String(BytesReference.toBytes(response.content()), StandardCharsets.UTF_8); + assertEquals("Hello, testable Passing Test!", responseStr); + + // Remove the adjective + response = restHelloAction.handleRequest(deleteRequest); + assertEquals(RestStatus.OK, response.status()); + assertEquals(BytesRestResponse.TEXT_CONTENT_TYPE, response.contentType()); + responseStr = new String(BytesReference.toBytes(response.content()), StandardCharsets.UTF_8); + assertTrue(responseStr.contains("testable")); + + response = restHelloAction.handleRequest(getRequest); + assertEquals(RestStatus.OK, response.status()); + assertEquals(BytesRestResponse.TEXT_CONTENT_TYPE, response.contentType()); + responseStr = new String(BytesReference.toBytes(response.content()), StandardCharsets.UTF_8); + assertEquals("Hello, Passing Test!", responseStr); + + // Try to remove nonexistent adjective + response = restHelloAction.handleRequest(deleteRequest); + assertEquals(RestStatus.NOT_MODIFIED, response.status()); + + // Unparseable response = restHelloAction.handleRequest(badRequest); assertEquals(RestStatus.BAD_REQUEST, response.status()); assertEquals(BytesRestResponse.TEXT_CONTENT_TYPE, response.contentType()); responseStr = new String(BytesReference.toBytes(response.content()), StandardCharsets.UTF_8); assertTrue(responseStr.contains("Illegal hex characters in escape (%) pattern")); - response = restHelloAction.handleRequest(unsuccessfulRequest); + // Not registered + response = restHelloAction.handleRequest(unhandledRequest); assertEquals(RestStatus.NOT_FOUND, response.status()); assertEquals(BytesRestResponse.TEXT_CONTENT_TYPE, response.contentType()); responseStr = new String(BytesReference.toBytes(response.content()), StandardCharsets.UTF_8); assertTrue(responseStr.contains("/goodbye")); } - } From fed8cb1a6d65ebd451dbe98df7077fc2f36ce99b Mon Sep 17 00:00:00 2001 From: Ryan Bogan Date: Mon, 10 Oct 2022 23:53:41 +0000 Subject: [PATCH 7/8] Fixed minor errors Signed-off-by: Ryan Bogan --- .../org/opensearch/sdk/BaseExtension.java | 22 +++++++++++++++++++ .../java/org/opensearch/sdk/Extension.java | 1 + .../helloworld/HelloWorldExtension.java | 2 +- .../sdk/ExtensionsRunnerForTest.java | 2 +- .../sdk/TransportCommunicationIT.java | 3 ++- 5 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/opensearch/sdk/BaseExtension.java b/src/main/java/org/opensearch/sdk/BaseExtension.java index f9c1c157..408e18b8 100644 --- a/src/main/java/org/opensearch/sdk/BaseExtension.java +++ b/src/main/java/org/opensearch/sdk/BaseExtension.java @@ -13,9 +13,23 @@ import org.opensearch.cluster.service.ClusterService; import org.opensearch.threadpool.ThreadPool; +/** + * An abstract class that provides sample methods required by extensions + */ public abstract class BaseExtension implements Extension { + /** + * A client to make requests to the system + */ protected SDKClient client; + + /** + * A service to allow watching and updating cluster state + */ protected ClusterService clusterService; + + /** + * A service to allow retrieving an executor to run an async action + */ protected ThreadPool threadPool; /** @@ -25,6 +39,14 @@ protected BaseExtension() { } + /** + * Returns components added by this extension. + * + * @param client A client to make requests to the system + * @param clusterService A service to allow watching and updating cluster state + * @param threadPool A service to allow retrieving an executor to run an async action + * @return A collection of objects + */ public Collection createComponents(SDKClient client, ClusterService clusterService, ThreadPool threadPool) { this.client = client; this.clusterService = clusterService; diff --git a/src/main/java/org/opensearch/sdk/Extension.java b/src/main/java/org/opensearch/sdk/Extension.java index 322e7a45..3d8cae42 100644 --- a/src/main/java/org/opensearch/sdk/Extension.java +++ b/src/main/java/org/opensearch/sdk/Extension.java @@ -62,6 +62,7 @@ default List> getSettings() { * @param client A client to make requests to the system * @param clusterService A service to allow watching and updating cluster state * @param threadPool A service to allow retrieving an executor to run an async action + * @return A collection of objects */ public Collection createComponents(SDKClient client, ClusterService clusterService, ThreadPool threadPool); diff --git a/src/main/java/org/opensearch/sdk/sample/helloworld/HelloWorldExtension.java b/src/main/java/org/opensearch/sdk/sample/helloworld/HelloWorldExtension.java index 43616334..1725f055 100644 --- a/src/main/java/org/opensearch/sdk/sample/helloworld/HelloWorldExtension.java +++ b/src/main/java/org/opensearch/sdk/sample/helloworld/HelloWorldExtension.java @@ -41,7 +41,7 @@ public class HelloWorldExtension extends BaseExtension { /** * Instantiate this extension, initializing the connection settings and REST actions. */ - public HelloWorldExtension() throws IOException { + public HelloWorldExtension() { super(); try { this.settings = initializeSettings(); diff --git a/src/test/java/org/opensearch/sdk/ExtensionsRunnerForTest.java b/src/test/java/org/opensearch/sdk/ExtensionsRunnerForTest.java index e3c53f88..4e366996 100644 --- a/src/test/java/org/opensearch/sdk/ExtensionsRunnerForTest.java +++ b/src/test/java/org/opensearch/sdk/ExtensionsRunnerForTest.java @@ -15,7 +15,7 @@ public class ExtensionsRunnerForTest extends ExtensionsRunner { * @throws IOException if the runner failed to read settings or API. */ public ExtensionsRunnerForTest() throws IOException { - super(new Extension() { + super(new BaseExtension() { @Override public ExtensionSettings getExtensionSettings() { return new ExtensionSettings("sample-extension", "127.0.0.1", "4532"); diff --git a/src/test/java/org/opensearch/sdk/TransportCommunicationIT.java b/src/test/java/org/opensearch/sdk/TransportCommunicationIT.java index 10c31105..03a7323f 100644 --- a/src/test/java/org/opensearch/sdk/TransportCommunicationIT.java +++ b/src/test/java/org/opensearch/sdk/TransportCommunicationIT.java @@ -147,7 +147,8 @@ private void startTransportandClient(Settings settings, Thread client) throws IO // retrieve transport service ExtensionsRunner extensionsRunner = new ExtensionsRunnerForTest(); // start transport service - TransportService transportService = nettyTransport.initializeExtensionTransportService(settings, extensionsRunner); + ThreadPool threadPool = new ThreadPool(settings); + TransportService transportService = nettyTransport.initializeExtensionTransportService(settings, threadPool, extensionsRunner); assertEquals(Lifecycle.State.STARTED, transportService.lifecycleState()); From aae98439b9f23d11e4eb7ff183a6ece80aef47ae Mon Sep 17 00:00:00 2001 From: Ryan Bogan Date: Tue, 11 Oct 2022 17:39:37 +0000 Subject: [PATCH 8/8] Addressed PR Comments Signed-off-by: Ryan Bogan --- build.gradle | 1 - src/main/java/org/opensearch/sdk/ExtensionsRunner.java | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 4e4a7df2..285cbf1a 100644 --- a/build.gradle +++ b/build.gradle @@ -51,7 +51,6 @@ publishing { repositories { mavenLocal() - // Remove the commented code below once TransportService is published to maven maven { url "https://aws.oss.sonatype.org/content/repositories/snapshots" } maven { url "https://d1nvenhzbhpy0q.cloudfront.net/snapshots/lucene/"} mavenCentral() diff --git a/src/main/java/org/opensearch/sdk/ExtensionsRunner.java b/src/main/java/org/opensearch/sdk/ExtensionsRunner.java index c64e0408..77c5bb88 100644 --- a/src/main/java/org/opensearch/sdk/ExtensionsRunner.java +++ b/src/main/java/org/opensearch/sdk/ExtensionsRunner.java @@ -93,6 +93,7 @@ public class ExtensionsRunner { new ExtensionsIndicesModuleNameRequestHandler(); private ExtensionsRestRequestHandler extensionsRestRequestHandler = new ExtensionsRestRequestHandler(extensionRestPathRegistry); private NettyTransport nettyTransport = new NettyTransport(); + private SDKClient client = new SDKClient(); /* * TODO: expose an interface for extension to register actions @@ -135,7 +136,7 @@ protected ExtensionsRunner(Extension extension) throws IOException { ThreadPool threadPool = new ThreadPool(this.getSettings()); nettyTransport.initializeExtensionTransportService(this.getSettings(), threadPool, this); // create components - extension.createComponents(new SDKClient(), null, threadPool); + extension.createComponents(client, null, threadPool); } /**