Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Backport 1.x] Register new routes via SDK as named routes (#827) and Makes route prefix setting routeNamePrefix optional #868 #866

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions CREATE_YOUR_FIRST_EXTENSION.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,28 +164,28 @@ import org.opensearch.sdk.rest.BaseExtensionRestHandler;
public class CrudAction extends BaseExtensionRestHandler {

@Override
protected List<RouteHandler> routeHandlers() {
public List<NamedRoute> routes() {
return List.of(
new RouteHandler(Method.PUT, "/sample", createHandler),
new RouteHandler(Method.GET, "/sample/{id}", readHandler),
new RouteHandler(Method.POST, "/sample/{id}", updateHandler),
new RouteHandler(Method.DELETE, "/sample/{id}", deleteHandler)
new NamedRoute.Builder().method(Method.PUT).path("/sample").uniqueName("extension1:sample/create").handler(createHandler).build(),
new NamedRoute.Builder().method(Method.GET).path("/sample/{id}").uniqueName("extension1:sample/get").handler(readHandler).build(),
new NamedRoute.Builder().method(Method.POST).path("/sample/{id}").uniqueName("extension1:sample/post").handler(updateHandler).build(),
new NamedRoute.Builder().method(Method.DELETE).path("/sample/{id}").uniqueName("extension1:sample/delete").handler(deleteHandler).build()
);
}

Function<RestRequest, ExtensionRestResponse> createHandler = (request) -> {
Function<RestRequest, RestResponse> createHandler = (request) -> {
return new ExtensionRestResponse(request, RestStatus.OK, "To be implemented");
};

Function<RestRequest, ExtensionRestResponse> readHandler = (request) -> {
Function<RestRequest, RestResponse> readHandler = (request) -> {
return new ExtensionRestResponse(request, RestStatus.OK, "To be implemented");
};

Function<RestRequest, ExtensionRestResponse> updateHandler = (request) -> {
Function<RestRequest, RestResponse> updateHandler = (request) -> {
return new ExtensionRestResponse(request, RestStatus.OK, "To be implemented");
};

Function<RestRequest, ExtensionRestResponse> deleteHandler = (request) -> {
Function<RestRequest, RestResponse> deleteHandler = (request) -> {
return new ExtensionRestResponse(request, RestStatus.OK, "To be implemented");
};
}
Expand Down Expand Up @@ -248,7 +248,7 @@ return createJsonResponse(request, RestStatus.OK, "_id", response.id());
Finally, you have the following code:

```java
Function<RestRequest, ExtensionRestResponse> createHandler = (request) -> {
Function<RestRequest, RestResponse> createHandler = (request) -> {
IndexResponse response;
try {
BooleanResponse exists = client.indices().exists(new ExistsRequest.Builder().index("crudsample").build());
Expand Down
39 changes: 22 additions & 17 deletions DEVELOPER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,14 +146,14 @@ To **run OpenSearch from a compiled binary**, follow these steps:
- Start OpenSearch using `./bin/opensearch`.
- Send the below sample REST API to initialize an extension
```bash
curl -XPOST "localhost:9200/_extensions/initialize" -H "Content-Type:application/json" --data '{ \
"name":"hello-world", \
"uniqueId":"hello-world", \
"hostAddress":"127.0.0.1", \
"port":"4532", \
"version":"1.0", \
"opensearchVersion":"3.0.0", \
"minimumCompatibleVersion":"3.0.0", \
curl -XPOST "localhost:9200/_extensions/initialize" -H "Content-Type:application/json" --data '{
"name":"hello-world",
"uniqueId":"hello-world",
"hostAddress":"127.0.0.1",
"port":"4532",
"version":"1.0",
"opensearchVersion":"3.0.0",
"minimumCompatibleVersion":"3.0.0",
"dependencies":[{"uniqueId":"test1","version":"2.0.0"},{"uniqueId":"test2","version":"3.0.0"}] \
}'
```
Expand All @@ -162,18 +162,20 @@ To **run OpenSearch from Gradle**, follow these steps:
- Run `./gradlew run` to start OpenSearch.
- Send the below sample REST API to initialize an extension
```bash
curl -XPOST "localhost:9200/_extensions/initialize" -H "Content-Type:application/json" --data '{ \
"name":"hello-world", \
"uniqueId":"hello-world", \
"hostAddress":"127.0.0.1", \
"port":"4532", \
"version":"1.0", \
"opensearchVersion":"3.0.0", \
"minimumCompatibleVersion":"3.0.0", \
"dependencies":[{"uniqueId":"test1","version":"2.0.0"},{"uniqueId":"test2","version":"3.0.0"}] \
curl -XPOST "localhost:9200/_extensions/initialize" -H "Content-Type:application/json" --data '{
"name":"hello-world",
"uniqueId":"hello-world",
"hostAddress":"127.0.0.1",
"port":"4532",
"version":"1.0",
"opensearchVersion":"3.0.0",
"minimumCompatibleVersion":"3.0.0",
"dependencies":[{"uniqueId":"test1","version":"2.0.0"},{"uniqueId":"test2","version":"3.0.0"}]
}'
```

Note: If the Security plugin is initialized in OpenSearch, use admin credentials to send extension initialization request.

In response to the REST `/initialize` request, `ExtensionsManager` discovers the extension listening on a predefined port and executes the TCP handshake protocol to establish a data transfer connection. Then OpenSearch sends a request to the OpenSearch SDK for Java and, upon acknowledgment, the extension responds with its name. This name is logged in the terminal where OpenSearch is running:

```bash
Expand Down Expand Up @@ -301,6 +303,9 @@ The artifact will include extension settings for the sample Hello World extensio
opensearchPort: 9200
```

You can optionally add `routeNamePrefix:` as a value to the yml. This setting allows you to prefix all your registered NamedRoute names.
The value must be alphanumeric and can contain `_` in the name.

Start the sample extension with `./bin/opensearch-sdk-java`

### Submitting changes
Expand Down
28 changes: 26 additions & 2 deletions PLUGIN_MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,38 @@ XContentParser parser = XContentType.JSON
Other potential initialization values are:
```java
this.environmentSettings = extensionsRunner.getEnvironmentSettings();
this.transportService = extensionsRunner.getExtensionTransportService();
this.transportService = extensionsRunner.getSdkTransportService().getTransportService();
this.restClient = anomalyDetectorExtension.getRestClient();
this.sdkClusterService = new SDKClusterService(extensionsRunner);
```

Many of these components are also available via Guice injection.

Optionally, change the `routes()` to `routeHandlers()`. Change `prepareRequest()` to `handleRequest()`.
### Replace `Route` with `NamedRoute`
Change `routes()` to be NamedRoutes. Here is a sample of an existing route converted to a named route:
Before:
```
public List<Route> routes() {
return ImmutableList.of(
new Route(GET, "/uri")
);
}
```
With new scheme:
```
private Function<RestRequest, RestResponse> uriHandler = () -> {};
public List<NamedRoute> routes() {
return ImmutableList.of(
new NamedRoute.Builder().method(GET).path("/uri").uniqueName("extension:uri").handler(uriHandler).build()
);
}
```

You can optionally also add `actionNames()` to this route. These should correspond to any current actions defined as permissions in roles.
`actionNames()` serve as a valuable tool for converting plugins into extensions while maintaining compatibility with pre-defined reserved roles.
Ensure that these name-to-route mappings are easily accessible to the cluster admins to allow granting access to these APIs.

Change `prepareRequest()` to `handleRequest()`.

### Replace `BytesRestResponse` with `ExtensionRestResponse`

Expand Down
26 changes: 15 additions & 11 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ dependencies {
def javaxVersion = "1"
def guavaFailureAccessVersion = "1.0.1"
def aopallianceVersion = "1.0"
def slf4jVersion = "1.7.36"

api("org.opensearch:opensearch:${opensearchVersion}")
implementation("org.apache.logging.log4j:log4j-api:${log4jVersion}")
Expand Down Expand Up @@ -187,16 +188,19 @@ dependencies {
testRuntimeOnly("org.junit.platform:junit-platform-launcher:${junitPlatform}")

configurations.all {
resolutionStrategy.force("jakarta.json:jakarta.json-api:${jakartaVersion}")
resolutionStrategy.force("com.fasterxml.jackson.core:jackson-databind:${jacksonDatabindVersion}")
resolutionStrategy.force("com.fasterxml.jackson.core:jackson-core:${jacksonDatabindVersion}")
resolutionStrategy.force("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:${jacksonDatabindVersion}")
resolutionStrategy.force("com.fasterxml.jackson.dataformat:jackson-dataformat-smile:${jacksonDatabindVersion}")
resolutionStrategy.force("com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:${jacksonDatabindVersion}")
resolutionStrategy.force("org.apache.logging.log4j:log4j-api:${log4jVersion}")
resolutionStrategy.force("org.apache.logging.log4j:log4j-core:${log4jVersion}")
resolutionStrategy.force("org.apache.logging.log4j:log4j-jul:${log4jVersion}")
resolutionStrategy.force("org.opensearch.client:opensearch-rest-client:${opensearchVersion}")
resolutionStrategy {
force("jakarta.json:jakarta.json-api:${jakartaVersion}")
force("com.fasterxml.jackson.core:jackson-databind:${jacksonDatabindVersion}")
force("com.fasterxml.jackson.core:jackson-core:${jacksonDatabindVersion}")
force("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:${jacksonDatabindVersion}")
force("com.fasterxml.jackson.dataformat:jackson-dataformat-smile:${jacksonDatabindVersion}")
force("com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:${jacksonDatabindVersion}")
force("org.apache.logging.log4j:log4j-api:${log4jVersion}")
force("org.apache.logging.log4j:log4j-core:${log4jVersion}")
force("org.apache.logging.log4j:log4j-jul:${log4jVersion}")
force("org.opensearch.client:opensearch-rest-client:${opensearchVersion}")
force("org.slf4j:slf4j-api:${slf4jVersion}")
}
}
}

Expand Down Expand Up @@ -320,7 +324,7 @@ task closeTestExtension (type: Exec) {
tasks.named("integTest").configure { finalizedBy(closeTestExtension) }

testClusters.integTest {
extension(new ExtensionsProperties("${testExtensionYml.name}", "${testExtensionYml.uniqueId}", "${testExtensionYml.hostAddress}", "${testExtensionYml.port}", "${testExtensionYml.version}", "${testExtensionYml.opensearchVersion}", "${testExtensionYml.minimumCompatibleVersion}"))
extension(true)
testDistribution = "ARCHIVE"
// Cluster shrink exception thrown if we try to set numberOfNodes to 1, so only apply if > 1
if (_numNodes > 1) numberOfNodes = _numNodes
Expand Down
34 changes: 34 additions & 0 deletions config/certs/cert-gen.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#! /bin/bash

openssl genrsa -out root-ca-key.pem 2048
openssl req -new -x509 -sha256 -key root-ca-key.pem -subj "/C=US/ST=NEW YORK/L=BROOKLYN/O=OPENSEARCH/OU=SECURITY/CN=ROOT" -out root-ca.pem -days 730

openssl genrsa -out extension-01-key-temp.pem 2048
openssl pkcs8 -inform PEM -outform PEM -in extension-01-key-temp.pem -topk8 -nocrypt -v1 PBE-SHA1-3DES -out extension-01-key.pem
openssl req -new -key extension-01-key.pem -subj "/C=US/ST=NEW YORK/L=BROOKLYN/O=OPENSEARCH/OU=SECURITY/CN=extension-01" -out extension-01.csr
echo 'subjectAltName=DNS:extension-01' | tee -a extension-01.ext
echo 'subjectAltName=IP:172.20.0.11' | tee -a extension-01.ext
openssl x509 -req -in extension-01.csr -CA root-ca.pem -CAkey root-ca-key.pem -CAcreateserial -sha256 -out extension-01.pem -days 730 -extfile extension-01.ext

rm extension-01-key-temp.pem
rm extension-01.csr
rm extension-01.ext
rm root-ca.srl

openssl genrsa -out admin-key-temp.pem 2048
openssl pkcs8 -inform PEM -outform PEM -in admin-key-temp.pem -topk8 -nocrypt -v1 PBE-SHA1-3DES -out admin-key.pem
openssl req -new -key admin-key.pem -subj "/C=US/ST=NEW YORK/L=BROOKLYN/O=OPENSEARCH/OU=SECURITY/CN=A" -out admin.csr
openssl x509 -req -in admin.csr -CA root-ca.pem -CAkey root-ca-key.pem -CAcreateserial -sha256 -out admin.pem -days 730
openssl genrsa -out os-node-01-key-temp.pem 2048
openssl pkcs8 -inform PEM -outform PEM -in os-node-01-key-temp.pem -topk8 -nocrypt -v1 PBE-SHA1-3DES -out os-node-01-key.pem
openssl req -new -key os-node-01-key.pem -subj "/C=US/ST=NEW YORK/L=BROOKLYN/O=OPENSEARCH/OU=SECURITY/CN=os-node-01" -out os-node-01.csr
echo 'subjectAltName=DNS:os-node-01' | tee -a os-node-01.ext
echo 'subjectAltName=IP:172.20.0.11' | tee -a os-node-01.ext
openssl x509 -req -in os-node-01.csr -CA root-ca.pem -CAkey root-ca-key.pem -CAcreateserial -sha256 -out os-node-01.pem -days 730 -extfile os-node-01.ext

rm admin-key-temp.pem
rm admin.csr
rm os-node-01-key-temp.pem
rm os-node-01.csr
rm os-node-01.ext
rm root-ca.srl
59 changes: 57 additions & 2 deletions src/main/java/org/opensearch/sdk/ExtensionSettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,13 @@ public class ExtensionSettings {
private String hostPort;
private String opensearchAddress;
private String opensearchPort;
private String routeNamePrefix;
private Map<String, String> securitySettings;

/**
* A set of keys for security settings related to SSL transport, keystore and truststore files, and hostname verification.
* These settings are used in OpenSearch to secure network communication and ensure data privacy.
*/
public static final Set<String> SECURITY_SETTINGS_KEYS = Set.of(
"path.home", // TODO Find the right place to put this setting
SSL_TRANSPORT_CLIENT_PEMCERT_FILEPATH,
Expand All @@ -79,8 +85,6 @@ public class ExtensionSettings {
SSL_TRANSPORT_TRUSTSTORE_TYPE
);

private Map<String, String> securitySettings;

/**
* Jackson requires a no-arg constructor.
*/
Expand Down Expand Up @@ -116,6 +120,7 @@ public ExtensionSettings(String extensionName, String hostAddress, String hostPo
* @param hostPort The port to bind this extension to.
* @param opensearchAddress The IP Address on which OpenSearch is running.
* @param opensearchPort The port on which OpenSearch is running.
* @param routeNamePrefix The prefix to be pre-pended to a NamedRoute being registered
* @param securitySettings A generic map of any settings set in the config file that are not default setting keys
*/
public ExtensionSettings(
Expand All @@ -124,40 +129,83 @@ public ExtensionSettings(
String hostPort,
String opensearchAddress,
String opensearchPort,
String routeNamePrefix,
Map<String, String> securitySettings
) {
this(extensionName, hostAddress, hostPort, opensearchAddress, opensearchPort);
this.routeNamePrefix = routeNamePrefix;
this.securitySettings = securitySettings;
}

/**
* Returns the name of the extension.
* @return A string representing the name of the extension.
*/
public String getExtensionName() {
return extensionName;
}

/**
* Returns the host address associated with this object.
* @return The host address as a string.
*/
public String getHostAddress() {
return hostAddress;
}

/**
* Returns the host and port number of the server.
* @return A string representation of the host and port number of the server.
*/
public String getHostPort() {
return hostPort;
}

/**
* Sets the OpenSearch server address to use for connecting to OpenSearch.
* @param opensearchAddress the URL or IP address of the OpenSearch server.
*/
public void setOpensearchAddress(String opensearchAddress) {
this.opensearchAddress = opensearchAddress;
}

/**
* Returns the address of the OpenSearch instance being used by the application.
* @return The address of the OpenSearch instance.
*/
public String getOpensearchAddress() {
return opensearchAddress;
}

/**
* Sets the OpenSearch port number to be used for communication.
* @param opensearchPort The port number to set.
*/
public void setOpensearchPort(String opensearchPort) {
this.opensearchPort = opensearchPort;
}

/**
* Returns the OpenSearch port number.
* @return The OpenSearch port number as a String.
*/
public String getOpensearchPort() {
return opensearchPort;
}

/**
* Returns the route Prefix for all routes registered by this extension
* @return A string representing the route prefix of this extension
*/
public String getRoutePrefix() {
return routeNamePrefix;
}

/**
* Returns the security settings as a map of key-value pairs.
* The keys represent the different security settings available, and the values represent the values set for each key.
* @return A map of security settings and their values.
*/
public Map<String, String> getSecuritySettings() {
return securitySettings;
}
Expand Down Expand Up @@ -203,12 +251,19 @@ public static ExtensionSettings readSettingsFromYaml(String extensionSettingsPat
securitySettings.put(settingKey, extensionMap.get(settingKey).toString());
}
}

// Making routeNamePrefix an optional setting
String routeNamePrefix = null;
if (extensionMap.containsKey("routeNamePrefix")) {
routeNamePrefix = extensionMap.get("routeNamePrefix").toString();
}
return new ExtensionSettings(
extensionMap.get("extensionName").toString(),
extensionMap.get("hostAddress").toString(),
extensionMap.get("hostPort").toString(),
extensionMap.get("opensearchAddress").toString(),
extensionMap.get("opensearchPort").toString(),
routeNamePrefix,
securitySettings
);
} catch (URISyntaxException e) {
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/org/opensearch/sdk/ExtensionsRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.opensearch.sdk.handlers.ExtensionsInitRequestHandler;
import org.opensearch.sdk.handlers.ExtensionsRestRequestHandler;
import org.opensearch.sdk.handlers.UpdateSettingsRequestHandler;
import org.opensearch.sdk.rest.BaseExtensionRestHandler;
import org.opensearch.sdk.rest.ExtensionRestHandler;
import org.opensearch.sdk.rest.ExtensionRestPathRegistry;
import org.opensearch.tasks.TaskManager;
Expand Down Expand Up @@ -233,6 +234,9 @@ protected ExtensionsRunner(Extension extension) throws IOException {
if (extension instanceof ActionExtension) {
// store REST handlers in the registry
for (ExtensionRestHandler extensionRestHandler : ((ActionExtension) extension).getExtensionRestHandlers()) {
if (extensionRestHandler instanceof BaseExtensionRestHandler) {
((BaseExtensionRestHandler) extensionRestHandler).setRouteNamePrefix(extensionSettings.getRoutePrefix());
}
extensionRestPathRegistry.registerHandler(extensionRestHandler);
}
}
Expand Down
Loading