From c08050a3aa5790ed91a1acb6768103ecaed6b705 Mon Sep 17 00:00:00 2001 From: Ryan Bogan Date: Sun, 18 Dec 2022 22:29:56 +0000 Subject: [PATCH] Added getSettings() support, ActionListener onFailure(), and initial createComponents support for extensions Signed-off-by: Ryan Bogan --- .../common/settings/WriteableSetting.java | 279 ++++++++++ .../env/EnvironmentSettingsResponse.java | 86 ++++ .../AddSettingsUpdateConsumerRequest.java | 98 ++++ ...dSettingsUpdateConsumerRequestHandler.java | 91 ++++ .../EnvironmentSettingsRequest.java | 79 +++ .../EnvironmentSettingsRequestHandler.java | 43 ++ .../extensions/ExtensionActionListener.java | 50 ++ .../ExtensionActionListenerHandler.java | 43 ++ ...tensionActionListenerOnFailureRequest.java | 72 +++ .../extensions/ExtensionRequest.java | 2 - ...onse.java => ExtensionStringResponse.java} | 10 +- .../extensions/ExtensionsManager.java | 161 +++++- .../extensions/UpdateSettingsRequest.java | 93 ++++ .../UpdateSettingsResponseHandler.java | 47 ++ .../rest/RestActionsRequestHandler.java | 5 +- .../rest/RestSendToExtensionAction.java | 2 + .../CustomSettingsRequestHandler.java | 57 ++ .../RegisterCustomSettingsRequest.java | 83 +++ .../extensions/settings/package-info.java | 10 + .../main/java/org/opensearch/node/Node.java | 8 +- .../settings/WriteableSettingTests.java | 487 ++++++++++++++++++ .../extensions/ExtensionResponseTests.java | 51 ++ .../extensions/ExtensionsManagerTests.java | 353 ++++++++++--- .../rest/RegisterRestActionsTests.java | 17 - .../settings/RegisterCustomSettingsTests.java | 56 ++ 25 files changed, 2177 insertions(+), 106 deletions(-) create mode 100644 server/src/main/java/org/opensearch/common/settings/WriteableSetting.java create mode 100644 server/src/main/java/org/opensearch/env/EnvironmentSettingsResponse.java create mode 100644 server/src/main/java/org/opensearch/extensions/AddSettingsUpdateConsumerRequest.java create mode 100644 server/src/main/java/org/opensearch/extensions/AddSettingsUpdateConsumerRequestHandler.java create mode 100644 server/src/main/java/org/opensearch/extensions/EnvironmentSettingsRequest.java create mode 100644 server/src/main/java/org/opensearch/extensions/EnvironmentSettingsRequestHandler.java create mode 100644 server/src/main/java/org/opensearch/extensions/ExtensionActionListener.java create mode 100644 server/src/main/java/org/opensearch/extensions/ExtensionActionListenerHandler.java create mode 100644 server/src/main/java/org/opensearch/extensions/ExtensionActionListenerOnFailureRequest.java rename server/src/main/java/org/opensearch/extensions/{rest/RegisterRestActionsResponse.java => ExtensionStringResponse.java} (69%) create mode 100644 server/src/main/java/org/opensearch/extensions/UpdateSettingsRequest.java create mode 100644 server/src/main/java/org/opensearch/extensions/UpdateSettingsResponseHandler.java create mode 100644 server/src/main/java/org/opensearch/extensions/settings/CustomSettingsRequestHandler.java create mode 100644 server/src/main/java/org/opensearch/extensions/settings/RegisterCustomSettingsRequest.java create mode 100644 server/src/main/java/org/opensearch/extensions/settings/package-info.java create mode 100644 server/src/test/java/org/opensearch/common/settings/WriteableSettingTests.java create mode 100644 server/src/test/java/org/opensearch/extensions/ExtensionResponseTests.java create mode 100644 server/src/test/java/org/opensearch/extensions/settings/RegisterCustomSettingsTests.java diff --git a/server/src/main/java/org/opensearch/common/settings/WriteableSetting.java b/server/src/main/java/org/opensearch/common/settings/WriteableSetting.java new file mode 100644 index 0000000000000..0909224892c7a --- /dev/null +++ b/server/src/main/java/org/opensearch/common/settings/WriteableSetting.java @@ -0,0 +1,279 @@ +/* + * 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.common.settings; + +import org.opensearch.Version; +import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.common.io.stream.StreamOutput; +import org.opensearch.common.io.stream.Writeable; +import org.opensearch.common.settings.Setting.Property; +import org.opensearch.common.unit.ByteSizeValue; +import org.opensearch.common.unit.TimeValue; +import java.io.IOException; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.concurrent.TimeUnit; + +/** + * Wrapper for {@link Setting} with {@link #writeTo(StreamOutput)} implementation dependent on the setting type. + * + * @opensearch.internal + */ +public class WriteableSetting implements Writeable { + + /** + * The Generic Types which this class can serialize. + */ + public enum SettingType { + Boolean, + Integer, + Long, + Float, + Double, + String, + TimeValue, // long + TimeUnit + ByteSizeValue, // long + ByteSizeUnit + Version + } + + private Setting setting; + private SettingType type; + + /** + * Wrap a {@link Setting}. The generic type is determined from the type of the default value. + * + * @param setting The setting to wrap. The default value must not be null. + * @throws IllegalArgumentException if the setting has a null default value. + */ + public WriteableSetting(Setting setting) { + this(setting, getGenericTypeFromDefault(setting)); + } + + /** + * Wrap a {@link Setting} with a specified generic type. + * + * @param setting The setting to wrap. + * @param type The Generic type of the setting. + */ + public WriteableSetting(Setting setting, SettingType type) { + this.setting = setting; + this.type = type; + } + + /** + * Wrap a {@link Setting} read from a stream. + * + * @param in Input to read the value from. + * @throws IOException if there is an error reading the values + */ + public WriteableSetting(StreamInput in) throws IOException { + // Read the type + this.type = in.readEnum(SettingType.class); + // Read the key + String key = in.readString(); + // Read the default value + Object defaultValue = readDefaultValue(in); + // Read a boolean specifying whether the fallback settings is null + WriteableSetting fallback = null; + boolean hasFallback = in.readBoolean(); + if (hasFallback) { + fallback = new WriteableSetting(in); + } + // We are using known types so don't need the parser + // We are not using validator + // Read properties + EnumSet propSet = in.readEnumSet(Property.class); + // Put it all in a setting object + this.setting = createSetting(type, key, defaultValue, fallback, propSet.toArray(Property[]::new)); + } + + /** + * Due to type erasure, it is impossible to determine the generic type of a Setting at runtime. + * All settings have a non-null default, however, so the type of the default can be used to determine the setting type. + * + * @param setting The setting with a generic type. + * @return The corresponding {@link SettingType} for the default value. + */ + private static SettingType getGenericTypeFromDefault(Setting setting) { + String typeStr = null; + try { + // This throws NPE on null default + typeStr = setting.getDefault(Settings.EMPTY).getClass().getSimpleName(); + // This throws IAE if not in enum + return SettingType.valueOf(typeStr); + } catch (NullPointerException e) { + throw new IllegalArgumentException("Unable to determine the generic type of this setting with a null default value."); + } catch (IllegalArgumentException e) { + throw new UnsupportedOperationException( + "This class is not yet set up to handle the generic type: " + + typeStr + + ". Supported types are " + + Arrays.toString(SettingType.values()) + ); + } + } + + /** + * Gets the wrapped setting. Use {@link #getType()} to determine its generic type. + * + * @return The wrapped setting. + */ + public Setting getSetting() { + return this.setting; + } + + /** + * Gets the generic type of the wrapped setting. + * + * @return The wrapped setting's generic type. + */ + public SettingType getType() { + return this.type; + } + + @SuppressWarnings("unchecked") + private Setting createSetting( + SettingType type, + String key, + Object defaultValue, + WriteableSetting fallback, + Property[] propertyArray + ) { + switch (type) { + case Boolean: + return fallback == null + ? Setting.boolSetting(key, (boolean) defaultValue, propertyArray) + : Setting.boolSetting(key, (Setting) fallback.getSetting(), propertyArray); + case Integer: + return fallback == null + ? Setting.intSetting(key, (int) defaultValue, propertyArray) + : Setting.intSetting(key, (Setting) fallback.getSetting(), propertyArray); + case Long: + return fallback == null + ? Setting.longSetting(key, (long) defaultValue, propertyArray) + : Setting.longSetting(key, (Setting) fallback.getSetting(), propertyArray); + case Float: + return fallback == null + ? Setting.floatSetting(key, (float) defaultValue, propertyArray) + : Setting.floatSetting(key, (Setting) fallback.getSetting(), propertyArray); + case Double: + return fallback == null + ? Setting.doubleSetting(key, (double) defaultValue, propertyArray) + : Setting.doubleSetting(key, (Setting) fallback.getSetting(), propertyArray); + case String: + return fallback == null + ? Setting.simpleString(key, (String) defaultValue, propertyArray) + : Setting.simpleString(key, (Setting) fallback.getSetting(), propertyArray); + case TimeValue: + return fallback == null + ? Setting.timeSetting(key, (TimeValue) defaultValue, propertyArray) + : Setting.timeSetting(key, (Setting) fallback.getSetting(), propertyArray); + case ByteSizeValue: + return fallback == null + ? Setting.byteSizeSetting(key, (ByteSizeValue) defaultValue, propertyArray) + : Setting.byteSizeSetting(key, (Setting) fallback.getSetting(), propertyArray); + case Version: + // No fallback option on this method + return Setting.versionSetting(key, (Version) defaultValue, propertyArray); + default: + // This Should Never Happen (TM) + throw new UnsupportedOperationException("A SettingType has been added to the enum and not handled here."); + } + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + // Write the type + out.writeEnum(type); + // Write the key + out.writeString(setting.getKey()); + // Write the default value + writeDefaultValue(out, setting.getDefault(Settings.EMPTY)); + // Write a boolean specifying whether the fallback settings is null + boolean hasFallback = setting.fallbackSetting != null; + out.writeBoolean(hasFallback); + if (hasFallback) { + new WriteableSetting(setting.fallbackSetting, type).writeTo(out); + } + // We are using known types so don't need the parser + // We are not using validator + // Write properties + out.writeEnumSet(setting.getProperties()); + } + + private void writeDefaultValue(StreamOutput out, Object defaultValue) throws IOException { + switch (type) { + case Boolean: + out.writeBoolean((boolean) defaultValue); + break; + case Integer: + out.writeInt((int) defaultValue); + break; + case Long: + out.writeLong((long) defaultValue); + break; + case Float: + out.writeFloat((float) defaultValue); + break; + case Double: + out.writeDouble((double) defaultValue); + break; + case String: + out.writeString((String) defaultValue); + break; + case TimeValue: + TimeValue tv = (TimeValue) defaultValue; + out.writeLong(tv.duration()); + out.writeString(tv.timeUnit().name()); + break; + case ByteSizeValue: + ((ByteSizeValue) defaultValue).writeTo(out); + break; + case Version: + Version.writeVersion((Version) defaultValue, out); + break; + default: + // This Should Never Happen (TM) + throw new UnsupportedOperationException("A SettingType has been added to the enum and not handled here."); + } + } + + private Object readDefaultValue(StreamInput in) throws IOException { + switch (type) { + case Boolean: + return in.readBoolean(); + case Integer: + return in.readInt(); + case Long: + return in.readLong(); + case Float: + return in.readFloat(); + case Double: + return in.readDouble(); + case String: + return in.readString(); + case TimeValue: + long duration = in.readLong(); + TimeUnit unit = TimeUnit.valueOf(in.readString()); + return new TimeValue(duration, unit); + case ByteSizeValue: + return new ByteSizeValue(in); + case Version: + return Version.readVersion(in); + default: + // This Should Never Happen (TM) + throw new UnsupportedOperationException("A SettingType has been added to the enum and not handled here."); + } + } + + @Override + public String toString() { + return "WriteableSettings{type=Setting<" + type + ">, setting=" + setting + "}"; + } +} diff --git a/server/src/main/java/org/opensearch/env/EnvironmentSettingsResponse.java b/server/src/main/java/org/opensearch/env/EnvironmentSettingsResponse.java new file mode 100644 index 0000000000000..0f541ed8ce51b --- /dev/null +++ b/server/src/main/java/org/opensearch/env/EnvironmentSettingsResponse.java @@ -0,0 +1,86 @@ +/* + * 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.env; + +import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.common.io.stream.StreamOutput; +import org.opensearch.common.settings.Setting; +import org.opensearch.common.settings.Settings; +import org.opensearch.transport.TransportResponse; +import org.opensearch.common.settings.WriteableSetting; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * Environment Settings Response for Extensibility + * + * @opensearch.internal + */ +public class EnvironmentSettingsResponse extends TransportResponse { + private Map, Object> componentSettingValues; + + public EnvironmentSettingsResponse(Settings environmentSettings, List> componentSettings) { + Map, Object> componentSettingValues = new HashMap<>(); + for (Setting componentSetting : componentSettings) { + + // Retrieve component setting value from enviornment settings, or default value if it does not exist + Object componentSettingValue = componentSetting.get(environmentSettings); + componentSettingValues.put(componentSetting, componentSettingValue); + } + this.componentSettingValues = componentSettingValues; + } + + public EnvironmentSettingsResponse(StreamInput in) throws IOException { + super(in); + Map, Object> componentSettingValues = new HashMap<>(); + int componentSettingValuesCount = in.readVInt(); + for (int i = 0; i < componentSettingValuesCount; i++) { + Setting componentSetting = new WriteableSetting(in).getSetting(); + Object componentSettingValue = in.readGenericValue(); + componentSettingValues.put(componentSetting, componentSettingValue); + } + this.componentSettingValues = componentSettingValues; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeVInt(componentSettingValues.size()); + for (Map.Entry, Object> entry : componentSettingValues.entrySet()) { + new WriteableSetting(entry.getKey()).writeTo(out); + out.writeGenericValue(entry.getValue()); + } + } + + public Map, Object> getComponentSettingValues() { + return Collections.unmodifiableMap(this.componentSettingValues); + } + + @Override + public String toString() { + return "EnvironmentSettingsResponse{componentSettingValues=" + componentSettingValues.toString() + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + EnvironmentSettingsResponse that = (EnvironmentSettingsResponse) o; + return Objects.equals(componentSettingValues, that.componentSettingValues); + } + + @Override + public int hashCode() { + return Objects.hash(componentSettingValues); + } +} diff --git a/server/src/main/java/org/opensearch/extensions/AddSettingsUpdateConsumerRequest.java b/server/src/main/java/org/opensearch/extensions/AddSettingsUpdateConsumerRequest.java new file mode 100644 index 0000000000000..687e3a07e3108 --- /dev/null +++ b/server/src/main/java/org/opensearch/extensions/AddSettingsUpdateConsumerRequest.java @@ -0,0 +1,98 @@ +/* + * 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.extensions; + +import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.common.io.stream.StreamOutput; +import org.opensearch.transport.TransportRequest; +import org.opensearch.common.settings.Setting; +import org.opensearch.common.settings.WriteableSetting; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Add Settings Update Consumer Request for Extensibility + * + * @opensearch.internal + */ +public class AddSettingsUpdateConsumerRequest extends TransportRequest { + private final DiscoveryExtensionNode extensionNode; + private final List componentSettings; + + public AddSettingsUpdateConsumerRequest(DiscoveryExtensionNode extensionNode, List> componentSettings) { + this.extensionNode = extensionNode; + this.componentSettings = new ArrayList<>(componentSettings.size()); + for (Setting setting : componentSettings) { + this.componentSettings.add(new WriteableSetting(setting)); + } + } + + public AddSettingsUpdateConsumerRequest(StreamInput in) throws IOException { + super(in); + + // Set extension node to send update settings request to + this.extensionNode = new DiscoveryExtensionNode(in); + + // Read in component setting list + int componentSettingsCount = in.readVInt(); + List componentSettings = new ArrayList<>(componentSettingsCount); + for (int i = 0; i < componentSettingsCount; i++) { + componentSettings.add(new WriteableSetting(in)); + } + this.componentSettings = componentSettings; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + + // Write extension node to stream output + this.extensionNode.writeTo(out); + + // Write component setting list to stream output + out.writeVInt(this.componentSettings.size()); + for (WriteableSetting componentSetting : this.componentSettings) { + componentSetting.writeTo(out); + } + } + + public List getComponentSettings() { + return new ArrayList<>(this.componentSettings); + } + + public DiscoveryExtensionNode getExtensionNode() { + return this.extensionNode; + } + + @Override + public String toString() { + return "AddSettingsUpdateConsumerRequest{extensionNode=" + + this.extensionNode.toString() + + ", componentSettings=" + + this.componentSettings.toString() + + "}"; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + AddSettingsUpdateConsumerRequest that = (AddSettingsUpdateConsumerRequest) obj; + return Objects.equals(extensionNode, that.extensionNode) && Objects.equals(componentSettings, that.componentSettings); + } + + @Override + public int hashCode() { + return Objects.hash(extensionNode, componentSettings); + } + +} diff --git a/server/src/main/java/org/opensearch/extensions/AddSettingsUpdateConsumerRequestHandler.java b/server/src/main/java/org/opensearch/extensions/AddSettingsUpdateConsumerRequestHandler.java new file mode 100644 index 0000000000000..791482aad0432 --- /dev/null +++ b/server/src/main/java/org/opensearch/extensions/AddSettingsUpdateConsumerRequestHandler.java @@ -0,0 +1,91 @@ +/* + * 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.extensions; + +import java.util.List; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.settings.Setting; +import org.opensearch.common.settings.WriteableSetting; +import org.opensearch.transport.TransportResponse; +import org.opensearch.transport.TransportService; + +/** + * Handles requests to add setting update consumers + * + * @opensearch.internal + */ +public class AddSettingsUpdateConsumerRequestHandler { + + private static final Logger logger = LogManager.getLogger(AddSettingsUpdateConsumerRequestHandler.class); + + private final ClusterService clusterService; + private final TransportService transportService; + private final String updateSettingsRequestType; + + /** + * Instantiates a new Add Settings Update Consumer Request Handler with the {@link ClusterService} and {@link TransportService} + * + * @param clusterService the cluster service used to extract cluster settings + * @param transportService the node's transport service + * @param updateSettingsRequestType the update settings request type + */ + public AddSettingsUpdateConsumerRequestHandler( + ClusterService clusterService, + TransportService transportService, + String updateSettingsRequestType + ) { + this.clusterService = clusterService; + this.transportService = transportService; + this.updateSettingsRequestType = updateSettingsRequestType; + } + + /** + * Handles a {@link AddSettingsUpdateConsumerRequest}. + * + * @param addSettingsUpdateConsumerRequest The request to handle. + * @return A {@link AcknowledgedResponse} indicating success. + * @throws Exception if the request is not handled properly. + */ + TransportResponse handleAddSettingsUpdateConsumerRequest(AddSettingsUpdateConsumerRequest addSettingsUpdateConsumerRequest) + throws Exception { + + boolean status = true; + List extensionComponentSettings = addSettingsUpdateConsumerRequest.getComponentSettings(); + DiscoveryExtensionNode extensionNode = addSettingsUpdateConsumerRequest.getExtensionNode(); + + try { + for (WriteableSetting extensionComponentSetting : extensionComponentSettings) { + + // Extract setting and type from writeable setting + Setting setting = extensionComponentSetting.getSetting(); + WriteableSetting.SettingType settingType = extensionComponentSetting.getType(); + + // Register setting update consumer with callback method to extension + clusterService.getClusterSettings().addSettingsUpdateConsumer(setting, (data) -> { + logger.info("Sending extension request type: " + updateSettingsRequestType); + UpdateSettingsResponseHandler updateSettingsResponseHandler = new UpdateSettingsResponseHandler(); + transportService.sendRequest( + extensionNode, + updateSettingsRequestType, + new UpdateSettingsRequest(settingType, setting, data), + updateSettingsResponseHandler + ); + }); + } + } catch (IllegalArgumentException e) { + logger.error(e.toString()); + status = false; + } + + return new AcknowledgedResponse(status); + } +} diff --git a/server/src/main/java/org/opensearch/extensions/EnvironmentSettingsRequest.java b/server/src/main/java/org/opensearch/extensions/EnvironmentSettingsRequest.java new file mode 100644 index 0000000000000..ab470087f8ec9 --- /dev/null +++ b/server/src/main/java/org/opensearch/extensions/EnvironmentSettingsRequest.java @@ -0,0 +1,79 @@ +/* + * 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.extensions; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.common.io.stream.StreamOutput; +import org.opensearch.transport.TransportRequest; +import org.opensearch.common.settings.Setting; +import org.opensearch.common.settings.WriteableSetting; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Environment Settings Request for Extensibility + * + * @opensearch.internal + */ +public class EnvironmentSettingsRequest extends TransportRequest { + private static final Logger logger = LogManager.getLogger(EnvironmentSettingsRequest.class); + private List> componentSettings; + + public EnvironmentSettingsRequest(List> componentSettings) { + this.componentSettings = new ArrayList<>(componentSettings); + } + + public EnvironmentSettingsRequest(StreamInput in) throws IOException { + super(in); + int componentSettingsCount = in.readVInt(); + List> componentSettings = new ArrayList<>(componentSettingsCount); + for (int i = 0; i < componentSettingsCount; i++) { + WriteableSetting writeableSetting = new WriteableSetting(in); + componentSettings.add(writeableSetting.getSetting()); + } + this.componentSettings = componentSettings; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + out.writeVInt(this.componentSettings.size()); + for (Setting componentSetting : componentSettings) { + new WriteableSetting(componentSetting).writeTo(out); + } + } + + public List> getComponentSettings() { + return new ArrayList<>(componentSettings); + } + + @Override + public String toString() { + return "EnvironmentSettingsRequest{componentSettings=" + componentSettings.toString() + "}"; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + EnvironmentSettingsRequest that = (EnvironmentSettingsRequest) obj; + return Objects.equals(componentSettings, that.componentSettings); + } + + @Override + public int hashCode() { + return Objects.hash(componentSettings); + } + +} diff --git a/server/src/main/java/org/opensearch/extensions/EnvironmentSettingsRequestHandler.java b/server/src/main/java/org/opensearch/extensions/EnvironmentSettingsRequestHandler.java new file mode 100644 index 0000000000000..723eb482d7c44 --- /dev/null +++ b/server/src/main/java/org/opensearch/extensions/EnvironmentSettingsRequestHandler.java @@ -0,0 +1,43 @@ +/* + * 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.extensions; + +import org.opensearch.common.settings.Settings; +import org.opensearch.env.EnvironmentSettingsResponse; +import org.opensearch.transport.TransportResponse; + +/** + * Handles requests to retrieve environment settings. + * + * @opensearch.internal + */ +public class EnvironmentSettingsRequestHandler { + + private final Settings initialEnvironmentSettings; + + /** + * Instantiates a new Environment Settings Request Handler with the environment settings + * + * @param initialEnvironmentSettings the finalized view of environment {@link Settings} + */ + public EnvironmentSettingsRequestHandler(Settings initialEnvironmentSettings) { + this.initialEnvironmentSettings = initialEnvironmentSettings; + } + + /** + * Handles a {@link EnvironmentSettingsRequest}. + * + * @param environmentSettingsRequest The request to handle. + * @return A {@link EnvironmentSettingsResponse} + * @throws Exception if the request is not handled properly. + */ + TransportResponse handleEnvironmentSettingsRequest(EnvironmentSettingsRequest environmentSettingsRequest) throws Exception { + return new EnvironmentSettingsResponse(this.initialEnvironmentSettings, environmentSettingsRequest.getComponentSettings()); + } +} diff --git a/server/src/main/java/org/opensearch/extensions/ExtensionActionListener.java b/server/src/main/java/org/opensearch/extensions/ExtensionActionListener.java new file mode 100644 index 0000000000000..53d7e887e4814 --- /dev/null +++ b/server/src/main/java/org/opensearch/extensions/ExtensionActionListener.java @@ -0,0 +1,50 @@ +/* + * 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.extensions; + +import java.util.ArrayList; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.opensearch.action.ActionListener; +import org.opensearch.action.admin.indices.analyze.AnalyzeAction.Response; + +/** + * ActionListener for ExtensionsManager + * + * @opensearch.internal + */ +public class ExtensionActionListener implements ActionListener { + + private static final Logger logger = LogManager.getLogger(ExtensionActionListener.class); + private ArrayList exceptionList; + + public ExtensionActionListener() { + exceptionList = new ArrayList(); + } + + @Override + public void onResponse(Response response) { + logger.info("response {}", response); + } + + @Override + public void onFailure(Exception e) { + exceptionList.add(e); + logger.error(e.getMessage()); + } + + public static Logger getLogger() { + return logger; + } + + public ArrayList getExceptionList() { + return exceptionList; + } +} diff --git a/server/src/main/java/org/opensearch/extensions/ExtensionActionListenerHandler.java b/server/src/main/java/org/opensearch/extensions/ExtensionActionListenerHandler.java new file mode 100644 index 0000000000000..ceba1e1f65000 --- /dev/null +++ b/server/src/main/java/org/opensearch/extensions/ExtensionActionListenerHandler.java @@ -0,0 +1,43 @@ +/* + * 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.extensions; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.opensearch.OpenSearchException; + +/** + * Handles ActionListener requests from extensions + * + * @opensearch.internal + */ +public class ExtensionActionListenerHandler { + + private static final Logger logger = LogManager.getLogger(ExtensionActionListener.class); + private ExtensionActionListener listener; + + public ExtensionActionListenerHandler(ExtensionActionListener listener) { + this.listener = listener; + } + + /** + * Handles a {@link ExtensionActionListenerOnFailureRequest}. + * + * @param request The request to handle. + * @return A {@link AcknowledgedResponse} indicating success or failure. + */ + public AcknowledgedResponse handleExtensionActionListenerOnFailureRequest(ExtensionActionListenerOnFailureRequest request) { + try { + listener.onFailure(new OpenSearchException(request.getFailureException())); + return new AcknowledgedResponse(true); + } catch (Exception e) { + logger.error(e.getMessage()); + return new AcknowledgedResponse(false); + } + } +} diff --git a/server/src/main/java/org/opensearch/extensions/ExtensionActionListenerOnFailureRequest.java b/server/src/main/java/org/opensearch/extensions/ExtensionActionListenerOnFailureRequest.java new file mode 100644 index 0000000000000..22a12433f06d3 --- /dev/null +++ b/server/src/main/java/org/opensearch/extensions/ExtensionActionListenerOnFailureRequest.java @@ -0,0 +1,72 @@ +/* + * 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.extensions; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.common.io.stream.StreamOutput; +import org.opensearch.transport.TransportRequest; + +import java.io.IOException; +import java.util.Objects; + +/** + * ClusterService Request for Action Listener onFailure + * + * @opensearch.internal + */ +public class ExtensionActionListenerOnFailureRequest extends TransportRequest { + private static final Logger logger = LogManager.getLogger(ExtensionRequest.class); + private String failureExceptionMessage; + + /** + * Instantiates a request for ActionListener onFailure from an extension + * + * @param failureExceptionMessage A String that contains both the Exception type and message + */ + public ExtensionActionListenerOnFailureRequest(String failureExceptionMessage) { + super(); + this.failureExceptionMessage = failureExceptionMessage; + } + + public ExtensionActionListenerOnFailureRequest(StreamInput in) throws IOException { + super(in); + this.failureExceptionMessage = in.readString(); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + out.writeString(failureExceptionMessage); + } + + public String toString() { + return "ExtensionActionListenerOnFailureRequest{" + "failureExceptionMessage= " + failureExceptionMessage + " }"; + } + + @Override + public boolean equals(Object o) { + + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ExtensionActionListenerOnFailureRequest that = (ExtensionActionListenerOnFailureRequest) o; + return Objects.equals(failureExceptionMessage, that.failureExceptionMessage); + } + + @Override + public int hashCode() { + return Objects.hash(failureExceptionMessage); + } + + public String getFailureException() { + return failureExceptionMessage; + } + +} diff --git a/server/src/main/java/org/opensearch/extensions/ExtensionRequest.java b/server/src/main/java/org/opensearch/extensions/ExtensionRequest.java index 924fce49a5dc2..44d59f0815975 100644 --- a/server/src/main/java/org/opensearch/extensions/ExtensionRequest.java +++ b/server/src/main/java/org/opensearch/extensions/ExtensionRequest.java @@ -51,7 +51,6 @@ public String toString() { @Override public boolean equals(Object o) { - if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ExtensionRequest that = (ExtensionRequest) o; @@ -62,5 +61,4 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(requestType); } - } diff --git a/server/src/main/java/org/opensearch/extensions/rest/RegisterRestActionsResponse.java b/server/src/main/java/org/opensearch/extensions/ExtensionStringResponse.java similarity index 69% rename from server/src/main/java/org/opensearch/extensions/rest/RegisterRestActionsResponse.java rename to server/src/main/java/org/opensearch/extensions/ExtensionStringResponse.java index c0a79ad32ce89..5c9c4c3ef784b 100644 --- a/server/src/main/java/org/opensearch/extensions/rest/RegisterRestActionsResponse.java +++ b/server/src/main/java/org/opensearch/extensions/ExtensionStringResponse.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.extensions.rest; +package org.opensearch.extensions; import org.opensearch.common.io.stream.StreamInput; import org.opensearch.common.io.stream.StreamOutput; @@ -15,18 +15,18 @@ import java.io.IOException; /** - * Response to register REST Actions request. + * Generic string response indicating the status of some previous request sent to the SDK * * @opensearch.internal */ -public class RegisterRestActionsResponse extends TransportResponse { +public class ExtensionStringResponse extends TransportResponse { private String response; - public RegisterRestActionsResponse(String response) { + public ExtensionStringResponse(String response) { this.response = response; } - public RegisterRestActionsResponse(StreamInput in) throws IOException { + public ExtensionStringResponse(StreamInput in) throws IOException { response = in.readString(); } diff --git a/server/src/main/java/org/opensearch/extensions/ExtensionsManager.java b/server/src/main/java/org/opensearch/extensions/ExtensionsManager.java index be843fe35a5f9..cda8b40ef1b2d 100644 --- a/server/src/main/java/org/opensearch/extensions/ExtensionsManager.java +++ b/server/src/main/java/org/opensearch/extensions/ExtensionsManager.java @@ -33,6 +33,7 @@ import org.opensearch.common.io.FileSystemUtils; import org.opensearch.common.io.stream.StreamInput; import org.opensearch.common.settings.Settings; +import org.opensearch.common.settings.SettingsModule; import org.opensearch.common.transport.TransportAddress; import org.opensearch.discovery.InitializeExtensionRequest; @@ -40,6 +41,8 @@ import org.opensearch.extensions.ExtensionsSettings.Extension; import org.opensearch.extensions.rest.RegisterRestActionsRequest; import org.opensearch.extensions.rest.RestActionsRequestHandler; +import org.opensearch.extensions.settings.CustomSettingsRequestHandler; +import org.opensearch.extensions.settings.RegisterCustomSettingsRequest; import org.opensearch.index.IndexModule; import org.opensearch.index.IndexService; import org.opensearch.index.IndicesModuleRequest; @@ -69,11 +72,15 @@ public class ExtensionsManager { public static final String REQUEST_EXTENSION_CLUSTER_STATE = "internal:discovery/clusterstate"; public static final String REQUEST_EXTENSION_LOCAL_NODE = "internal:discovery/localnode"; public static final String REQUEST_EXTENSION_CLUSTER_SETTINGS = "internal:discovery/clustersettings"; + public static final String REQUEST_EXTENSION_ENVIRONMENT_SETTINGS = "internal:discovery/enviornmentsettings"; + public static final String REQUEST_EXTENSION_ADD_SETTINGS_UPDATE_CONSUMER = "internal:discovery/addsettingsupdateconsumer"; + public static final String REQUEST_EXTENSION_UPDATE_SETTINGS = "internal:discovery/updatesettings"; + public static final String REQUEST_EXTENSION_REGISTER_CUSTOM_SETTINGS = "internal:discovery/registercustomsettings"; public static final String REQUEST_EXTENSION_REGISTER_REST_ACTIONS = "internal:discovery/registerrestactions"; - public static final String REQUEST_OPENSEARCH_NAMED_WRITEABLE_REGISTRY = "internal:discovery/namedwriteableregistry"; + public static final String REQUEST_EXTENSION_REGISTER_TRANSPORT_ACTIONS = "internal:discovery/registertransportactions"; public static final String REQUEST_OPENSEARCH_PARSE_NAMED_WRITEABLE = "internal:discovery/parsenamedwriteable"; + public static final String REQUEST_EXTENSION_ACTION_LISTENER_ON_FAILURE = "internal:extensions/actionlisteneronfailure"; public static final String REQUEST_REST_EXECUTE_ON_EXTENSION_ACTION = "internal:extensions/restexecuteonextensiontaction"; - public static final String REQUEST_EXTENSION_REGISTER_TRANSPORT_ACTIONS = "internal:discovery/registertransportactions"; private static final Logger logger = LogManager.getLogger(ExtensionsManager.class); @@ -86,7 +93,9 @@ public static enum RequestType { REQUEST_EXTENSION_CLUSTER_STATE, REQUEST_EXTENSION_LOCAL_NODE, REQUEST_EXTENSION_CLUSTER_SETTINGS, + REQUEST_EXTENSION_ACTION_LISTENER_ON_FAILURE, REQUEST_EXTENSION_REGISTER_REST_ACTIONS, + REQUEST_EXTENSION_REGISTER_SETTINGS, CREATE_COMPONENT, ON_INDEX_MODULE, GET_SETTINGS @@ -106,8 +115,13 @@ public static enum OpenSearchRequestType { private List extensions; private Map extensionIdMap; private RestActionsRequestHandler restActionsRequestHandler; + private CustomSettingsRequestHandler customSettingsRequestHandler; private TransportService transportService; private ClusterService clusterService; + private ExtensionActionListener listener; + private ExtensionActionListenerHandler listenerHandler; + private EnvironmentSettingsRequestHandler environmentSettingsRequestHandler; + private AddSettingsUpdateConsumerRequestHandler addSettingsUpdateConsumerRequestHandler; public ExtensionsManager() { this.extensionsPath = Path.of(""); @@ -127,6 +141,7 @@ public ExtensionsManager(Settings settings, Path extensionsPath) throws IOExcept this.extensions = new ArrayList(); this.extensionIdMap = new HashMap(); this.clusterService = null; + this.listener = new ExtensionActionListener(); /* * Now Discover extensions @@ -136,21 +151,33 @@ public ExtensionsManager(Settings settings, Path extensionsPath) throws IOExcept } /** - * Initializes the {@link RestActionsRequestHandler}, {@link TransportService} and {@link ClusterService}. This is called during Node bootstrap. + * Initializes the {@link RestActionsRequestHandler}, {@link TransportService}, {@link ClusterService} and environment settings. This is called during Node bootstrap. * Lists/maps of extensions have already been initialized but not yet populated. * * @param restController The RestController on which to register Rest Actions. + * @param settingsModule The module that binds the provided settings to interface. * @param transportService The Node's transport service. * @param clusterService The Node's cluster service. + * @param initialEnvironmentSettings The finalized view of settings for the Environment */ public void initializeServicesAndRestHandler( RestController restController, + SettingsModule settingsModule, TransportService transportService, - ClusterService clusterService + ClusterService clusterService, + Settings initialEnvironmentSettings ) { this.restActionsRequestHandler = new RestActionsRequestHandler(restController, extensionIdMap, transportService); + this.listenerHandler = new ExtensionActionListenerHandler(listener); + this.customSettingsRequestHandler = new CustomSettingsRequestHandler(settingsModule); this.transportService = transportService; this.clusterService = clusterService; + this.environmentSettingsRequestHandler = new EnvironmentSettingsRequestHandler(initialEnvironmentSettings); + this.addSettingsUpdateConsumerRequestHandler = new AddSettingsUpdateConsumerRequestHandler( + clusterService, + transportService, + REQUEST_EXTENSION_UPDATE_SETTINGS + ); registerRequestHandler(); } @@ -163,6 +190,14 @@ private void registerRequestHandler() { RegisterRestActionsRequest::new, ((request, channel, task) -> channel.sendResponse(restActionsRequestHandler.handleRegisterRestActionsRequest(request))) ); + transportService.registerRequestHandler( + REQUEST_EXTENSION_REGISTER_CUSTOM_SETTINGS, + ThreadPool.Names.GENERIC, + false, + false, + RegisterCustomSettingsRequest::new, + ((request, channel, task) -> channel.sendResponse(customSettingsRequestHandler.handleRegisterCustomSettingsRequest(request))) + ); transportService.registerRequestHandler( REQUEST_EXTENSION_CLUSTER_STATE, ThreadPool.Names.GENERIC, @@ -187,6 +222,32 @@ private void registerRequestHandler() { ExtensionRequest::new, ((request, channel, task) -> channel.sendResponse(handleExtensionRequest(request))) ); + transportService.registerRequestHandler( + REQUEST_EXTENSION_ACTION_LISTENER_ON_FAILURE, + ThreadPool.Names.GENERIC, + false, + false, + ExtensionActionListenerOnFailureRequest::new, + ((request, channel, task) -> channel.sendResponse(listenerHandler.handleExtensionActionListenerOnFailureRequest(request))) + ); + transportService.registerRequestHandler( + REQUEST_EXTENSION_ENVIRONMENT_SETTINGS, + ThreadPool.Names.GENERIC, + false, + false, + EnvironmentSettingsRequest::new, + ((request, channel, task) -> channel.sendResponse(environmentSettingsRequestHandler.handleEnvironmentSettingsRequest(request))) + ); + transportService.registerRequestHandler( + REQUEST_EXTENSION_ADD_SETTINGS_UPDATE_CONSUMER, + ThreadPool.Names.GENERIC, + false, + false, + AddSettingsUpdateConsumerRequest::new, + ((request, channel, task) -> channel.sendResponse( + addSettingsUpdateConsumerRequestHandler.handleAddSettingsUpdateConsumerRequest(request) + )) + ); transportService.registerRequestHandler( REQUEST_EXTENSION_REGISTER_TRANSPORT_ACTIONS, ThreadPool.Names.GENERIC, @@ -357,7 +418,7 @@ TransportResponse handleExtensionRequest(ExtensionRequest extensionRequest) thro case REQUEST_EXTENSION_CLUSTER_SETTINGS: return new ClusterSettingsResponse(clusterService); default: - throw new IllegalStateException("Handler not present for the provided request"); + throw new IllegalArgumentException("Handler not present for the provided request"); } } @@ -528,10 +589,6 @@ public static String getRequestExtensionRegisterRestActions() { return REQUEST_EXTENSION_REGISTER_REST_ACTIONS; } - public static String getRequestOpensearchNamedWriteableRegistry() { - return REQUEST_OPENSEARCH_NAMED_WRITEABLE_REGISTRY; - } - public static String getRequestOpensearchParseNamedWriteable() { return REQUEST_OPENSEARCH_PARSE_NAMED_WRITEABLE; } @@ -548,4 +605,90 @@ public RestActionsRequestHandler getRestActionsRequestHandler() { return restActionsRequestHandler; } + public void setExtensions(List extensions) { + this.extensions = extensions; + } + + public void setExtensionIdMap(Map extensionIdMap) { + this.extensionIdMap = extensionIdMap; + } + + public void setRestActionsRequestHandler(RestActionsRequestHandler restActionsRequestHandler) { + this.restActionsRequestHandler = restActionsRequestHandler; + } + + public void setTransportService(TransportService transportService) { + this.transportService = transportService; + } + + public void setClusterService(ClusterService clusterService) { + this.clusterService = clusterService; + } + + public static String getRequestExtensionRegisterTransportActions() { + return REQUEST_EXTENSION_REGISTER_TRANSPORT_ACTIONS; + } + + public static String getRequestExtensionActionListenerOnFailure() { + return REQUEST_EXTENSION_ACTION_LISTENER_ON_FAILURE; + } + + public ExtensionActionListener getListener() { + return listener; + } + + public static String getRequestExtensionRegisterCustomSettings() { + return REQUEST_EXTENSION_REGISTER_CUSTOM_SETTINGS; + } + + public CustomSettingsRequestHandler getCustomSettingsRequestHandler() { + return customSettingsRequestHandler; + } + + public void setCustomSettingsRequestHandler(CustomSettingsRequestHandler customSettingsRequestHandler) { + this.customSettingsRequestHandler = customSettingsRequestHandler; + } + + public static String getRequestExtensionEnvironmentSettings() { + return REQUEST_EXTENSION_ENVIRONMENT_SETTINGS; + } + + public static String getRequestExtensionAddSettingsUpdateConsumer() { + return REQUEST_EXTENSION_ADD_SETTINGS_UPDATE_CONSUMER; + } + + public static String getRequestExtensionUpdateSettings() { + return REQUEST_EXTENSION_UPDATE_SETTINGS; + } + + public EnvironmentSettingsRequestHandler getEnvironmentSettingsRequestHandler() { + return environmentSettingsRequestHandler; + } + + public void setEnvironmentSettingsRequestHandler(EnvironmentSettingsRequestHandler environmentSettingsRequestHandler) { + this.environmentSettingsRequestHandler = environmentSettingsRequestHandler; + } + + public AddSettingsUpdateConsumerRequestHandler getAddSettingsUpdateConsumerRequestHandler() { + return addSettingsUpdateConsumerRequestHandler; + } + + public void setAddSettingsUpdateConsumerRequestHandler( + AddSettingsUpdateConsumerRequestHandler addSettingsUpdateConsumerRequestHandler + ) { + this.addSettingsUpdateConsumerRequestHandler = addSettingsUpdateConsumerRequestHandler; + } + + public void setListener(ExtensionActionListener listener) { + this.listener = listener; + } + + public ExtensionActionListenerHandler getListenerHandler() { + return listenerHandler; + } + + public void setListenerHandler(ExtensionActionListenerHandler listenerHandler) { + this.listenerHandler = listenerHandler; + } + } diff --git a/server/src/main/java/org/opensearch/extensions/UpdateSettingsRequest.java b/server/src/main/java/org/opensearch/extensions/UpdateSettingsRequest.java new file mode 100644 index 0000000000000..3191f189ac18b --- /dev/null +++ b/server/src/main/java/org/opensearch/extensions/UpdateSettingsRequest.java @@ -0,0 +1,93 @@ +/* + * 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.extensions; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.opensearch.common.settings.Setting; +import org.opensearch.common.settings.WriteableSetting; +import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.common.io.stream.StreamOutput; +import org.opensearch.transport.TransportRequest; + +import java.io.IOException; +import java.util.Objects; + +/** + * Update Settings Request for Extensibility + * + * @opensearch.internal + */ +public class UpdateSettingsRequest extends TransportRequest { + private static final Logger logger = LogManager.getLogger(EnvironmentSettingsRequest.class); + + private WriteableSetting.SettingType settingType; + private Setting componentSetting; + private Object data; + + public UpdateSettingsRequest(WriteableSetting.SettingType settingType, Setting componentSetting, Object data) { + this.settingType = settingType; + this.componentSetting = componentSetting; + this.data = data; + } + + public UpdateSettingsRequest(StreamInput in) throws IOException { + super(in); + this.settingType = in.readEnum(WriteableSetting.SettingType.class); + this.componentSetting = new WriteableSetting(in).getSetting(); + this.data = in.readGenericValue(); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + out.writeEnum(settingType); + new WriteableSetting(componentSetting).writeTo(out); + out.writeGenericValue(this.data); + } + + public WriteableSetting.SettingType getSettingType() { + return this.settingType; + } + + public Setting getComponentSetting() { + return this.componentSetting; + } + + public Object getData() { + return this.data; + } + + @Override + public String toString() { + return "UpdateSettingRequest{settingType=" + + this.settingType.toString() + + "componentSetting=" + + this.componentSetting.toString() + + ", data=" + + this.data.toString() + + "}"; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + UpdateSettingsRequest that = (UpdateSettingsRequest) obj; + return Objects.equals(settingType, that.settingType) + && Objects.equals(componentSetting, that.componentSetting) + && Objects.equals(data, that.data); + } + + @Override + public int hashCode() { + return Objects.hash(settingType, componentSetting, data); + } + +} diff --git a/server/src/main/java/org/opensearch/extensions/UpdateSettingsResponseHandler.java b/server/src/main/java/org/opensearch/extensions/UpdateSettingsResponseHandler.java new file mode 100644 index 0000000000000..be8f43b5cfce6 --- /dev/null +++ b/server/src/main/java/org/opensearch/extensions/UpdateSettingsResponseHandler.java @@ -0,0 +1,47 @@ +/* + * 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.extensions; + +import java.io.IOException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.message.ParameterizedMessage; +import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.threadpool.ThreadPool; +import org.opensearch.transport.TransportException; +import org.opensearch.transport.TransportResponseHandler; + +/** + * Response handler for {@link UpdateSettingsRequest} + * + * @opensearch.internal + */ +public class UpdateSettingsResponseHandler implements TransportResponseHandler { + private static final Logger logger = LogManager.getLogger(UpdateSettingsResponseHandler.class); + + @Override + public AcknowledgedResponse read(StreamInput in) throws IOException { + return new AcknowledgedResponse(in); + } + + @Override + public void handleResponse(AcknowledgedResponse response) { + logger.info("response {}", response.getStatus()); + } + + @Override + public void handleException(TransportException exp) { + logger.error(new ParameterizedMessage("UpdateSettingsRequest failed"), exp); + } + + @Override + public String executor() { + return ThreadPool.Names.GENERIC; + } +} diff --git a/server/src/main/java/org/opensearch/extensions/rest/RestActionsRequestHandler.java b/server/src/main/java/org/opensearch/extensions/rest/RestActionsRequestHandler.java index e24f5d519bf81..8c935b1dc87a6 100644 --- a/server/src/main/java/org/opensearch/extensions/rest/RestActionsRequestHandler.java +++ b/server/src/main/java/org/opensearch/extensions/rest/RestActionsRequestHandler.java @@ -9,6 +9,7 @@ package org.opensearch.extensions.rest; import org.opensearch.extensions.DiscoveryExtensionNode; +import org.opensearch.extensions.ExtensionStringResponse; import org.opensearch.rest.RestController; import org.opensearch.rest.RestHandler; import org.opensearch.transport.TransportResponse; @@ -48,14 +49,14 @@ public RestActionsRequestHandler( * Handles a {@link RegisterRestActionsRequest}. * * @param restActionsRequest The request to handle. - * @return A {@link RegisterRestActionsResponse} indicating success. + * @return A {@link ExtensionStringResponse} indicating success. * @throws Exception if the request is not handled properly. */ public TransportResponse handleRegisterRestActionsRequest(RegisterRestActionsRequest restActionsRequest) throws Exception { DiscoveryExtensionNode discoveryExtensionNode = extensionIdMap.get(restActionsRequest.getUniqueId()); RestHandler handler = new RestSendToExtensionAction(restActionsRequest, discoveryExtensionNode, transportService); restController.registerHandler(handler); - return new RegisterRestActionsResponse( + return new ExtensionStringResponse( "Registered extension " + restActionsRequest.getUniqueId() + " to handle REST Actions " + restActionsRequest.getRestActions() ); } diff --git a/server/src/main/java/org/opensearch/extensions/rest/RestSendToExtensionAction.java b/server/src/main/java/org/opensearch/extensions/rest/RestSendToExtensionAction.java index 8f5a2e6b1c8a5..d08a74c0ba314 100644 --- a/server/src/main/java/org/opensearch/extensions/rest/RestSendToExtensionAction.java +++ b/server/src/main/java/org/opensearch/extensions/rest/RestSendToExtensionAction.java @@ -156,6 +156,8 @@ public String executor() { transportService.sendRequest( discoveryExtensionNode, ExtensionsManager.REQUEST_REST_EXECUTE_ON_EXTENSION_ACTION, + // HERE BE DRAGONS - DO NOT INCLUDE HEADERS + // SEE https://github.com/opensearch-project/OpenSearch/issues/4429 new RestExecuteOnExtensionRequest(method, uri), restExecuteOnExtensionResponseHandler ); diff --git a/server/src/main/java/org/opensearch/extensions/settings/CustomSettingsRequestHandler.java b/server/src/main/java/org/opensearch/extensions/settings/CustomSettingsRequestHandler.java new file mode 100644 index 0000000000000..83d34facb35f6 --- /dev/null +++ b/server/src/main/java/org/opensearch/extensions/settings/CustomSettingsRequestHandler.java @@ -0,0 +1,57 @@ +/* + * 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.extensions.settings; + +import org.opensearch.common.settings.Setting; +import org.opensearch.common.settings.SettingsModule; +import org.opensearch.extensions.ExtensionStringResponse; +import org.opensearch.transport.TransportResponse; + +import java.util.ArrayList; +import java.util.List; + +/** + * Handles requests to register a list of custom extension settings. + * + * @opensearch.internal + */ +public class CustomSettingsRequestHandler { + + private final SettingsModule settingsModule; + + /** + * Instantiates a new Settings Request Handler using the Node's SettingsModule. + * + * @param settingsModule The Node's {@link SettingsModule}. + */ + public CustomSettingsRequestHandler(SettingsModule settingsModule) { + this.settingsModule = settingsModule; + } + + /** + * Handles a {@link RegisterCustomSettingsRequest}. + * + * @param customSettingsRequest The request to handle. + * @return A {@link ExtensionStringResponse} indicating success. + * @throws Exception if the request is not handled properly. + */ + public TransportResponse handleRegisterCustomSettingsRequest(RegisterCustomSettingsRequest customSettingsRequest) throws Exception { + // TODO: How do we prevent key collisions in settings registration? + // we have settingsRequest.getUniqueId() available or could enforce reverse DNS naming + // See https://github.com/opensearch-project/opensearch-sdk-java/issues/142 + List registeredCustomSettings = new ArrayList<>(); + for (Setting setting : customSettingsRequest.getSettings()) { + settingsModule.registerDynamicSetting(setting); + registeredCustomSettings.add(setting.getKey()); + } + return new ExtensionStringResponse( + "Registered settings from extension " + customSettingsRequest.getUniqueId() + ": " + String.join(", ", registeredCustomSettings) + ); + } +} diff --git a/server/src/main/java/org/opensearch/extensions/settings/RegisterCustomSettingsRequest.java b/server/src/main/java/org/opensearch/extensions/settings/RegisterCustomSettingsRequest.java new file mode 100644 index 0000000000000..b8217972767f9 --- /dev/null +++ b/server/src/main/java/org/opensearch/extensions/settings/RegisterCustomSettingsRequest.java @@ -0,0 +1,83 @@ +/* + * 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.extensions.settings; + +import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.common.io.stream.StreamOutput; +import org.opensearch.common.settings.Setting; +import org.opensearch.common.settings.WriteableSetting; +import org.opensearch.transport.TransportRequest; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Request to register a list of custom extension settings + * + * @opensearch.internal + */ +public class RegisterCustomSettingsRequest extends TransportRequest { + private String uniqueId; + private List> settings; + + public RegisterCustomSettingsRequest(String uniqueId, List> settings) { + this.uniqueId = uniqueId; + this.settings = new ArrayList<>(settings); + } + + public RegisterCustomSettingsRequest(StreamInput in) throws IOException { + super(in); + this.uniqueId = in.readString(); + int size = in.readVInt(); + List> settingsList = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + WriteableSetting ws = new WriteableSetting(in); + settingsList.add(ws.getSetting()); + } + this.settings = settingsList; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + out.writeString(uniqueId); + out.writeVInt(settings.size()); + for (Setting setting : settings) { + new WriteableSetting(setting).writeTo(out); + } + } + + public String getUniqueId() { + return uniqueId; + } + + public List> getSettings() { + return new ArrayList<>(settings); + } + + @Override + public String toString() { + return "RegisterSettingsRequest{uniqueId=" + uniqueId + ", settings=" + settings + "}"; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + RegisterCustomSettingsRequest that = (RegisterCustomSettingsRequest) obj; + return Objects.equals(uniqueId, that.uniqueId) && Objects.equals(settings, that.settings); + } + + @Override + public int hashCode() { + return Objects.hash(uniqueId, settings); + } +} diff --git a/server/src/main/java/org/opensearch/extensions/settings/package-info.java b/server/src/main/java/org/opensearch/extensions/settings/package-info.java new file mode 100644 index 0000000000000..4ae82baba9bbb --- /dev/null +++ b/server/src/main/java/org/opensearch/extensions/settings/package-info.java @@ -0,0 +1,10 @@ +/* + * 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. + */ + +/** Settings classes for the extensions package. OpenSearch extensions provide extensibility to OpenSearch.*/ +package org.opensearch.extensions.settings; diff --git a/server/src/main/java/org/opensearch/node/Node.java b/server/src/main/java/org/opensearch/node/Node.java index 25b821430ce4e..e127826921fe9 100644 --- a/server/src/main/java/org/opensearch/node/Node.java +++ b/server/src/main/java/org/opensearch/node/Node.java @@ -828,7 +828,13 @@ protected Node( taskHeaders ); if (FeatureFlags.isEnabled(FeatureFlags.EXTENSIONS)) { - this.extensionsManager.initializeServicesAndRestHandler(restController, transportService, clusterService); + this.extensionsManager.initializeServicesAndRestHandler( + restController, + settingsModule, + transportService, + clusterService, + environment.settings() + ); } final GatewayMetaState gatewayMetaState = new GatewayMetaState(); final ResponseCollectorService responseCollectorService = new ResponseCollectorService(clusterService); diff --git a/server/src/test/java/org/opensearch/common/settings/WriteableSettingTests.java b/server/src/test/java/org/opensearch/common/settings/WriteableSettingTests.java new file mode 100644 index 0000000000000..98eb81ef23957 --- /dev/null +++ b/server/src/test/java/org/opensearch/common/settings/WriteableSettingTests.java @@ -0,0 +1,487 @@ +/* + * 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.common.settings; + +import org.junit.Before; +import org.opensearch.Version; +import org.opensearch.common.SuppressForbidden; +import org.opensearch.common.bytes.BytesReference; +import org.opensearch.common.io.stream.BytesStreamInput; +import org.opensearch.common.io.stream.BytesStreamOutput; +import org.opensearch.common.unit.ByteSizeUnit; +import org.opensearch.common.unit.ByteSizeValue; +import org.opensearch.common.unit.TimeValue; +import org.opensearch.test.OpenSearchTestCase; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +import static org.opensearch.common.settings.Setting.Property; +import static org.opensearch.common.settings.WriteableSetting.SettingType; + +public class WriteableSettingTests extends OpenSearchTestCase { + + // These settings have a default value and null fallback + private final Map> settingMap = new EnumMap<>(SettingType.class); + // These settings have a fallback setting instead of a default + private final Map> settingWithFallbackMap = new EnumMap<>(SettingType.class); + + @SuppressWarnings("unchecked") + @Before + public void setup() throws Exception { + super.setUp(); + settingMap.put(SettingType.Boolean, Setting.boolSetting("boolSettingBase", false, Property.NodeScope, Property.Dynamic)); + settingMap.put(SettingType.Integer, Setting.intSetting("intSettingBase", 6, Property.NodeScope, Property.Dynamic)); + settingMap.put(SettingType.Long, Setting.longSetting("longSettingBase", 42L, Property.NodeScope, Property.Dynamic)); + settingMap.put(SettingType.Float, Setting.floatSetting("floatSettingBase", 6.2f, Property.NodeScope, Property.Dynamic)); + settingMap.put(SettingType.Double, Setting.doubleSetting("doubleSettingBase", 42.2d, Property.NodeScope, Property.Dynamic)); + settingMap.put(SettingType.String, Setting.simpleString("simpleStringBase", "foo", Property.NodeScope, Property.Dynamic)); + settingMap.put( + SettingType.TimeValue, + Setting.timeSetting("timeSettingBase", new TimeValue(5, TimeUnit.MILLISECONDS), Property.NodeScope, Property.Dynamic) + ); + settingMap.put( + SettingType.ByteSizeValue, + Setting.byteSizeSetting("byteSizeSettingBase", new ByteSizeValue(10, ByteSizeUnit.KB), Property.NodeScope, Property.Dynamic) + ); + settingMap.put( + SettingType.Version, + Setting.versionSetting("versionSettingBase", Version.CURRENT, Property.NodeScope, Property.Dynamic) + ); + + settingWithFallbackMap.put( + SettingType.Boolean, + Setting.boolSetting("boolSetting", (Setting) settingMap.get(SettingType.Boolean), Property.NodeScope, Property.Dynamic) + ); + settingWithFallbackMap.put( + SettingType.Integer, + Setting.intSetting("intSetting", (Setting) settingMap.get(SettingType.Integer), Property.NodeScope, Property.Dynamic) + ); + settingWithFallbackMap.put( + SettingType.Long, + Setting.longSetting("longSetting", (Setting) settingMap.get(SettingType.Long), Property.NodeScope, Property.Dynamic) + ); + settingWithFallbackMap.put( + SettingType.Float, + Setting.floatSetting("floatSetting", (Setting) settingMap.get(SettingType.Float), Property.NodeScope, Property.Dynamic) + ); + settingWithFallbackMap.put( + SettingType.Double, + Setting.doubleSetting( + "doubleSetting", + (Setting) settingMap.get(SettingType.Double), + Property.NodeScope, + Property.Dynamic + ) + ); + settingWithFallbackMap.put( + SettingType.String, + Setting.simpleString("simpleString", (Setting) settingMap.get(SettingType.String), Property.NodeScope, Property.Dynamic) + ); + settingWithFallbackMap.put( + SettingType.TimeValue, + Setting.timeSetting( + "timeSetting", + (Setting) settingMap.get(SettingType.TimeValue), + Property.NodeScope, + Property.Dynamic + ) + ); + settingWithFallbackMap.put( + SettingType.ByteSizeValue, + Setting.byteSizeSetting( + "byteSizeSetting", + (Setting) settingMap.get(SettingType.ByteSizeValue), + Property.NodeScope, + Property.Dynamic + ) + ); + // No fallback for versionSetting + + } + + @SuppressWarnings("unchecked") + public void testBooleanSetting() throws IOException { + WriteableSetting ws = new WriteableSetting(settingMap.get(SettingType.Boolean)); + assertEquals(SettingType.Boolean, ws.getType()); + Setting setting = (Setting) ws.getSetting(); + assertEquals("boolSettingBase", setting.getKey()); + assertFalse(setting.getDefault(Settings.EMPTY)); + EnumSet props = setting.getProperties(); + assertEquals(2, props.size()); + assertTrue(props.contains(Property.NodeScope)); + assertTrue(props.contains(Property.Dynamic)); + + WriteableSetting wsfb = new WriteableSetting(settingWithFallbackMap.get(SettingType.Boolean)); + assertEquals(SettingType.Boolean, wsfb.getType()); + setting = (Setting) wsfb.getSetting(); + assertEquals("boolSetting", setting.getKey()); + assertFalse(setting.getDefault(Settings.EMPTY)); + props = setting.getProperties(); + assertEquals(2, props.size()); + assertTrue(props.contains(Property.NodeScope)); + assertTrue(props.contains(Property.Dynamic)); + + try (BytesStreamOutput out = new BytesStreamOutput()) { + wsfb.writeTo(out); + out.flush(); + try (BytesStreamInput in = new BytesStreamInput(BytesReference.toBytes(out.bytes()))) { + WriteableSetting wsIn = new WriteableSetting(in); + + assertEquals(SettingType.Boolean, wsIn.getType()); + setting = (Setting) wsIn.getSetting(); + assertEquals("boolSetting", setting.getKey()); + assertFalse(setting.getDefault(Settings.EMPTY)); + props = setting.getProperties(); + assertEquals(2, props.size()); + assertTrue(props.contains(Property.NodeScope)); + assertTrue(props.contains(Property.Dynamic)); + } + } + + } + + @SuppressWarnings("unchecked") + public void testIntegerSetting() throws IOException { + WriteableSetting ws = new WriteableSetting(settingMap.get(SettingType.Integer)); + assertEquals(SettingType.Integer, ws.getType()); + Setting setting = (Setting) ws.getSetting(); + assertEquals("intSettingBase", setting.getKey()); + assertEquals(6, (int) setting.getDefault(Settings.EMPTY)); + EnumSet props = setting.getProperties(); + assertEquals(2, props.size()); + assertTrue(props.contains(Property.NodeScope)); + assertTrue(props.contains(Property.Dynamic)); + + WriteableSetting wsfb = new WriteableSetting(settingWithFallbackMap.get(SettingType.Integer)); + assertEquals(SettingType.Integer, wsfb.getType()); + setting = (Setting) wsfb.getSetting(); + assertEquals("intSetting", setting.getKey()); + assertEquals(6, (int) setting.getDefault(Settings.EMPTY)); + props = setting.getProperties(); + assertEquals(2, props.size()); + assertTrue(props.contains(Property.NodeScope)); + assertTrue(props.contains(Property.Dynamic)); + + try (BytesStreamOutput out = new BytesStreamOutput()) { + wsfb.writeTo(out); + out.flush(); + try (BytesStreamInput in = new BytesStreamInput(BytesReference.toBytes(out.bytes()))) { + WriteableSetting wsIn = new WriteableSetting(in); + + assertEquals(SettingType.Integer, wsIn.getType()); + setting = (Setting) wsIn.getSetting(); + assertEquals("intSetting", setting.getKey()); + assertEquals(6, (int) setting.getDefault(Settings.EMPTY)); + props = setting.getProperties(); + assertEquals(2, props.size()); + assertTrue(props.contains(Property.NodeScope)); + assertTrue(props.contains(Property.Dynamic)); + } + } + } + + @SuppressWarnings("unchecked") + public void testLongSetting() throws IOException { + WriteableSetting ws = new WriteableSetting(settingMap.get(SettingType.Long)); + assertEquals(SettingType.Long, ws.getType()); + Setting setting = (Setting) ws.getSetting(); + assertEquals("longSettingBase", setting.getKey()); + assertEquals(42L, (long) setting.getDefault(Settings.EMPTY)); + EnumSet props = setting.getProperties(); + assertEquals(2, props.size()); + assertTrue(props.contains(Property.NodeScope)); + assertTrue(props.contains(Property.Dynamic)); + + WriteableSetting wsfb = new WriteableSetting(settingWithFallbackMap.get(SettingType.Long)); + assertEquals(SettingType.Long, wsfb.getType()); + setting = (Setting) wsfb.getSetting(); + assertEquals("longSetting", setting.getKey()); + assertEquals(42L, (long) setting.getDefault(Settings.EMPTY)); + props = setting.getProperties(); + assertEquals(2, props.size()); + assertTrue(props.contains(Property.NodeScope)); + assertTrue(props.contains(Property.Dynamic)); + + try (BytesStreamOutput out = new BytesStreamOutput()) { + wsfb.writeTo(out); + out.flush(); + try (BytesStreamInput in = new BytesStreamInput(BytesReference.toBytes(out.bytes()))) { + WriteableSetting wsIn = new WriteableSetting(in); + + assertEquals(SettingType.Long, wsIn.getType()); + setting = (Setting) wsIn.getSetting(); + assertEquals("longSetting", setting.getKey()); + assertEquals(42L, (long) setting.getDefault(Settings.EMPTY)); + props = setting.getProperties(); + assertEquals(2, props.size()); + assertTrue(props.contains(Property.NodeScope)); + assertTrue(props.contains(Property.Dynamic)); + } + } + } + + @SuppressWarnings("unchecked") + public void testFloatSetting() throws IOException { + WriteableSetting ws = new WriteableSetting(settingMap.get(SettingType.Float)); + assertEquals(SettingType.Float, ws.getType()); + Setting setting = (Setting) ws.getSetting(); + assertEquals("floatSettingBase", setting.getKey()); + assertEquals(6.2f, (float) setting.getDefault(Settings.EMPTY), Float.MIN_NORMAL); + EnumSet props = setting.getProperties(); + assertEquals(2, props.size()); + assertTrue(props.contains(Property.NodeScope)); + assertTrue(props.contains(Property.Dynamic)); + + WriteableSetting wsfb = new WriteableSetting(settingWithFallbackMap.get(SettingType.Float)); + assertEquals(SettingType.Float, wsfb.getType()); + setting = (Setting) wsfb.getSetting(); + assertEquals("floatSetting", setting.getKey()); + assertEquals(6.2f, (float) setting.getDefault(Settings.EMPTY), Float.MIN_NORMAL); + props = setting.getProperties(); + assertEquals(2, props.size()); + assertTrue(props.contains(Property.NodeScope)); + assertTrue(props.contains(Property.Dynamic)); + + try (BytesStreamOutput out = new BytesStreamOutput()) { + wsfb.writeTo(out); + out.flush(); + try (BytesStreamInput in = new BytesStreamInput(BytesReference.toBytes(out.bytes()))) { + WriteableSetting wsIn = new WriteableSetting(in); + + assertEquals(SettingType.Float, wsIn.getType()); + setting = (Setting) wsIn.getSetting(); + assertEquals("floatSetting", setting.getKey()); + assertEquals(6.2f, (Float) setting.getDefault(Settings.EMPTY), Float.MIN_NORMAL); + props = setting.getProperties(); + assertEquals(2, props.size()); + assertTrue(props.contains(Property.NodeScope)); + assertTrue(props.contains(Property.Dynamic)); + } + } + } + + @SuppressWarnings("unchecked") + public void testDoubleSetting() throws IOException { + WriteableSetting ws = new WriteableSetting(settingMap.get(SettingType.Double)); + assertEquals(SettingType.Double, ws.getType()); + Setting setting = (Setting) ws.getSetting(); + assertEquals("doubleSettingBase", setting.getKey()); + assertEquals(42.2d, (double) setting.getDefault(Settings.EMPTY), Double.MIN_NORMAL); + EnumSet props = setting.getProperties(); + assertEquals(2, props.size()); + assertTrue(props.contains(Property.NodeScope)); + assertTrue(props.contains(Property.Dynamic)); + + WriteableSetting wsfb = new WriteableSetting(settingWithFallbackMap.get(SettingType.Double)); + assertEquals(SettingType.Double, wsfb.getType()); + setting = (Setting) wsfb.getSetting(); + assertEquals("doubleSetting", setting.getKey()); + assertEquals(42.2d, (double) setting.getDefault(Settings.EMPTY), Double.MIN_NORMAL); + props = setting.getProperties(); + assertEquals(2, props.size()); + assertTrue(props.contains(Property.NodeScope)); + assertTrue(props.contains(Property.Dynamic)); + + try (BytesStreamOutput out = new BytesStreamOutput()) { + wsfb.writeTo(out); + out.flush(); + try (BytesStreamInput in = new BytesStreamInput(BytesReference.toBytes(out.bytes()))) { + WriteableSetting wsIn = new WriteableSetting(in); + + assertEquals(SettingType.Double, wsIn.getType()); + setting = (Setting) wsIn.getSetting(); + assertEquals("doubleSetting", setting.getKey()); + assertEquals(42.2d, (double) setting.getDefault(Settings.EMPTY), Double.MIN_NORMAL); + props = setting.getProperties(); + assertEquals(2, props.size()); + assertTrue(props.contains(Property.NodeScope)); + assertTrue(props.contains(Property.Dynamic)); + } + } + } + + @SuppressWarnings("unchecked") + public void testStringSetting() throws IOException { + WriteableSetting ws = new WriteableSetting(settingMap.get(SettingType.String)); + assertEquals(SettingType.String, ws.getType()); + Setting setting = (Setting) ws.getSetting(); + assertEquals("simpleStringBase", setting.getKey()); + assertEquals("foo", (String) setting.getDefault(Settings.EMPTY)); + EnumSet props = setting.getProperties(); + assertEquals(2, props.size()); + assertTrue(props.contains(Property.NodeScope)); + assertTrue(props.contains(Property.Dynamic)); + + WriteableSetting wsfb = new WriteableSetting(settingWithFallbackMap.get(SettingType.String)); + assertEquals(SettingType.String, wsfb.getType()); + setting = (Setting) wsfb.getSetting(); + assertEquals("simpleString", setting.getKey()); + assertEquals("foo", (String) setting.getDefault(Settings.EMPTY)); + props = setting.getProperties(); + assertEquals(2, props.size()); + assertTrue(props.contains(Property.NodeScope)); + assertTrue(props.contains(Property.Dynamic)); + + try (BytesStreamOutput out = new BytesStreamOutput()) { + wsfb.writeTo(out); + out.flush(); + try (BytesStreamInput in = new BytesStreamInput(BytesReference.toBytes(out.bytes()))) { + WriteableSetting wsIn = new WriteableSetting(in); + + assertEquals(SettingType.String, wsIn.getType()); + setting = (Setting) wsIn.getSetting(); + assertEquals("simpleString", setting.getKey()); + assertEquals("foo", (String) setting.getDefault(Settings.EMPTY)); + props = setting.getProperties(); + assertEquals(2, props.size()); + assertTrue(props.contains(Property.NodeScope)); + assertTrue(props.contains(Property.Dynamic)); + } + } + } + + @SuppressWarnings("unchecked") + public void testTimeValueSetting() throws IOException { + WriteableSetting ws = new WriteableSetting(settingMap.get(SettingType.TimeValue)); + assertEquals(SettingType.TimeValue, ws.getType()); + Setting setting = (Setting) ws.getSetting(); + assertEquals("timeSettingBase", setting.getKey()); + assertEquals(new TimeValue(5, TimeUnit.MILLISECONDS), (TimeValue) setting.getDefault(Settings.EMPTY)); + EnumSet props = setting.getProperties(); + assertEquals(2, props.size()); + assertTrue(props.contains(Property.NodeScope)); + assertTrue(props.contains(Property.Dynamic)); + + WriteableSetting wsfb = new WriteableSetting(settingWithFallbackMap.get(SettingType.TimeValue)); + assertEquals(SettingType.TimeValue, wsfb.getType()); + setting = (Setting) wsfb.getSetting(); + assertEquals("timeSetting", setting.getKey()); + assertEquals(new TimeValue(5, TimeUnit.MILLISECONDS), (TimeValue) setting.getDefault(Settings.EMPTY)); + props = setting.getProperties(); + assertEquals(2, props.size()); + assertTrue(props.contains(Property.NodeScope)); + assertTrue(props.contains(Property.Dynamic)); + + try (BytesStreamOutput out = new BytesStreamOutput()) { + wsfb.writeTo(out); + out.flush(); + try (BytesStreamInput in = new BytesStreamInput(BytesReference.toBytes(out.bytes()))) { + WriteableSetting wsIn = new WriteableSetting(in); + + assertEquals(SettingType.TimeValue, wsIn.getType()); + setting = (Setting) wsIn.getSetting(); + assertEquals("timeSetting", setting.getKey()); + assertEquals(new TimeValue(5, TimeUnit.MILLISECONDS), (TimeValue) setting.getDefault(Settings.EMPTY)); + props = setting.getProperties(); + assertEquals(2, props.size()); + assertTrue(props.contains(Property.NodeScope)); + assertTrue(props.contains(Property.Dynamic)); + } + } + } + + @SuppressWarnings("unchecked") + public void testByteSizeValueSetting() throws IOException { + WriteableSetting ws = new WriteableSetting(settingMap.get(SettingType.ByteSizeValue)); + assertEquals(SettingType.ByteSizeValue, ws.getType()); + Setting setting = (Setting) ws.getSetting(); + assertEquals("byteSizeSettingBase", setting.getKey()); + assertEquals(new ByteSizeValue(10, ByteSizeUnit.KB), (ByteSizeValue) setting.getDefault(Settings.EMPTY)); + EnumSet props = setting.getProperties(); + assertEquals(2, props.size()); + assertTrue(props.contains(Property.NodeScope)); + assertTrue(props.contains(Property.Dynamic)); + + WriteableSetting wsfb = new WriteableSetting(settingWithFallbackMap.get(SettingType.ByteSizeValue)); + assertEquals(SettingType.ByteSizeValue, wsfb.getType()); + setting = (Setting) wsfb.getSetting(); + assertEquals("byteSizeSetting", setting.getKey()); + assertEquals(new ByteSizeValue(10, ByteSizeUnit.KB), (ByteSizeValue) setting.getDefault(Settings.EMPTY)); + props = setting.getProperties(); + assertEquals(2, props.size()); + assertTrue(props.contains(Property.NodeScope)); + assertTrue(props.contains(Property.Dynamic)); + + try (BytesStreamOutput out = new BytesStreamOutput()) { + wsfb.writeTo(out); + out.flush(); + try (BytesStreamInput in = new BytesStreamInput(BytesReference.toBytes(out.bytes()))) { + WriteableSetting wsIn = new WriteableSetting(in); + + assertEquals(SettingType.ByteSizeValue, wsIn.getType()); + setting = (Setting) wsIn.getSetting(); + assertEquals("byteSizeSetting", setting.getKey()); + assertEquals(new ByteSizeValue(10, ByteSizeUnit.KB), (ByteSizeValue) setting.getDefault(Settings.EMPTY)); + props = setting.getProperties(); + assertEquals(2, props.size()); + assertTrue(props.contains(Property.NodeScope)); + assertTrue(props.contains(Property.Dynamic)); + } + } + } + + @SuppressWarnings("unchecked") + public void testVersionSetting() throws IOException { + WriteableSetting ws = new WriteableSetting(settingMap.get(SettingType.Version)); + assertEquals(SettingType.Version, ws.getType()); + Setting setting = (Setting) ws.getSetting(); + assertEquals("versionSettingBase", setting.getKey()); + assertEquals(Version.CURRENT, (Version) setting.getDefault(Settings.EMPTY)); + EnumSet props = setting.getProperties(); + assertEquals(2, props.size()); + assertTrue(props.contains(Property.NodeScope)); + assertTrue(props.contains(Property.Dynamic)); + + try (BytesStreamOutput out = new BytesStreamOutput()) { + ws.writeTo(out); + out.flush(); + try (BytesStreamInput in = new BytesStreamInput(BytesReference.toBytes(out.bytes()))) { + WriteableSetting wsIn = new WriteableSetting(in); + + assertEquals(SettingType.Version, wsIn.getType()); + setting = (Setting) wsIn.getSetting(); + assertEquals("versionSettingBase", setting.getKey()); + assertEquals(Version.CURRENT, (Version) setting.getDefault(Settings.EMPTY)); + props = setting.getProperties(); + assertEquals(2, props.size()); + assertTrue(props.contains(Property.NodeScope)); + assertTrue(props.contains(Property.Dynamic)); + } + } + } + + @SuppressForbidden(reason = "The only way to test these is via reflection") + public void testExceptionHandling() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { + // abuse reflection to change default value, no way to do this with given Setting class + Setting setting = Setting.simpleString(""); + Field dv = setting.getClass().getDeclaredField("defaultValue"); + dv.setAccessible(true); + Field p = setting.getClass().getDeclaredField("parser"); + p.setAccessible(true); + + // test null default value + dv.set(setting, null); + IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () -> new WriteableSetting(setting)); + assertTrue(iae.getMessage().contains("null default value")); + + // test default value type not in enum + Function dvfi = s -> ""; + dv.set(setting, dvfi); + Function pfi = s -> new WriteableSettingTests(); + p.set(setting, pfi); + UnsupportedOperationException uoe = expectThrows(UnsupportedOperationException.class, () -> new WriteableSetting(setting)); + assertTrue(uoe.getMessage().contains("generic type: WriteableSettingTests")); + } +} diff --git a/server/src/test/java/org/opensearch/extensions/ExtensionResponseTests.java b/server/src/test/java/org/opensearch/extensions/ExtensionResponseTests.java new file mode 100644 index 0000000000000..3b2993cb164c0 --- /dev/null +++ b/server/src/test/java/org/opensearch/extensions/ExtensionResponseTests.java @@ -0,0 +1,51 @@ +/* + * 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.extensions; + +import org.opensearch.common.bytes.BytesReference; +import org.opensearch.common.io.stream.BytesStreamInput; +import org.opensearch.common.io.stream.BytesStreamOutput; +import org.opensearch.test.OpenSearchTestCase; + +public class ExtensionResponseTests extends OpenSearchTestCase { + + public void testAcknowledgedResponse() throws Exception { + boolean response = true; + AcknowledgedResponse booleanResponse = new AcknowledgedResponse(response); + + assertEquals(response, booleanResponse.getStatus()); + + try (BytesStreamOutput out = new BytesStreamOutput()) { + booleanResponse.writeTo(out); + out.flush(); + try (BytesStreamInput in = new BytesStreamInput(BytesReference.toBytes(out.bytes()))) { + booleanResponse = new AcknowledgedResponse(in); + + assertEquals(response, booleanResponse.getStatus()); + } + } + } + + public void testExtensionStringResponse() throws Exception { + String response = "This is a response"; + ExtensionStringResponse stringResponse = new ExtensionStringResponse(response); + + assertEquals(response, stringResponse.getResponse()); + + try (BytesStreamOutput out = new BytesStreamOutput()) { + stringResponse.writeTo(out); + out.flush(); + try (BytesStreamInput in = new BytesStreamInput(BytesReference.toBytes(out.bytes()))) { + stringResponse = new ExtensionStringResponse(in); + + assertEquals(response, stringResponse.getResponse()); + } + } + } +} diff --git a/server/src/test/java/org/opensearch/extensions/ExtensionsManagerTests.java b/server/src/test/java/org/opensearch/extensions/ExtensionsManagerTests.java index d45e51ea2bbc8..ae4f61ffbec99 100644 --- a/server/src/test/java/org/opensearch/extensions/ExtensionsManagerTests.java +++ b/server/src/test/java/org/opensearch/extensions/ExtensionsManagerTests.java @@ -8,6 +8,7 @@ package org.opensearch.extensions; +import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; import static java.util.Collections.emptySet; import static org.mockito.ArgumentMatchers.any; @@ -30,7 +31,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; -import java.util.Objects; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -43,17 +43,23 @@ import org.opensearch.client.node.NodeClient; import org.opensearch.cluster.ClusterSettingsResponse; import org.opensearch.cluster.LocalNodeResponse; +import org.opensearch.env.EnvironmentSettingsResponse; import org.opensearch.cluster.metadata.IndexMetadata; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.node.DiscoveryNode; import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.bytes.BytesReference; import org.opensearch.common.io.PathUtils; -import org.opensearch.common.io.stream.NamedWriteable; +import org.opensearch.common.io.stream.BytesStreamInput; +import org.opensearch.common.io.stream.BytesStreamOutput; import org.opensearch.common.io.stream.NamedWriteableRegistry; -import org.opensearch.common.io.stream.StreamInput; -import org.opensearch.common.io.stream.StreamOutput; import org.opensearch.common.network.NetworkService; +import org.opensearch.common.settings.Setting; import org.opensearch.common.settings.Settings; +import org.opensearch.common.settings.WriteableSetting; +import org.opensearch.common.settings.Setting.Property; +import org.opensearch.common.settings.WriteableSetting.SettingType; +import org.opensearch.common.settings.SettingsModule; import org.opensearch.common.transport.TransportAddress; import org.opensearch.common.util.FeatureFlagTests; import org.opensearch.common.util.PageCacheRecycler; @@ -61,7 +67,7 @@ import org.opensearch.env.Environment; import org.opensearch.env.TestEnvironment; import org.opensearch.extensions.rest.RegisterRestActionsRequest; -import org.opensearch.extensions.rest.RegisterRestActionsResponse; +import org.opensearch.extensions.settings.RegisterCustomSettingsRequest; import org.opensearch.index.IndexModule; import org.opensearch.index.IndexSettings; import org.opensearch.index.analysis.AnalysisRegistry; @@ -86,6 +92,7 @@ public class ExtensionsManagerTests extends OpenSearchTestCase { private TransportService transportService; private RestController restController; + private SettingsModule settingsModule; private ClusterService clusterService; private MockNioTransport transport; private Path extensionDir; @@ -122,6 +129,8 @@ public class ExtensionsManagerTests extends OpenSearchTestCase { " hasNativeController: true" ); + private DiscoveryExtensionNode extensionNode; + @Before public void setup() throws Exception { FeatureFlagTests.enableFeature(); @@ -158,9 +167,31 @@ public void setup() throws Exception { new NoneCircuitBreakerService(), new UsageService() ); + settingsModule = new SettingsModule(Settings.EMPTY, emptyList(), emptyList(), emptySet()); clusterService = createClusterService(threadPool); extensionDir = createTempDir(); + + extensionNode = new DiscoveryExtensionNode( + "firstExtension", + "uniqueid1", + "uniqueid1", + "myIndependentPluginHost1", + "127.0.0.0", + new TransportAddress(InetAddress.getByName("127.0.0.0"), 9300), + new HashMap(), + Version.fromString("3.0.0"), + new PluginInfo( + "firstExtension", + "Fake description 1", + "0.0.7", + Version.fromString("3.0.0"), + "14", + "fakeClass1", + new ArrayList(), + false + ) + ); } @Override @@ -172,8 +203,6 @@ public void tearDown() throws Exception { } public void testDiscover() throws Exception { - Path extensionDir = createTempDir(); - Files.write(extensionDir.resolve("extensions.yml"), extensionsYmlLines, StandardCharsets.UTF_8); ExtensionsManager extensionsManager = new ExtensionsManager(settings, extensionDir); @@ -231,14 +260,13 @@ public void testDiscover() throws Exception { } public void testNonUniqueExtensionsDiscovery() throws Exception { - Path extensionDir = createTempDir(); - + Path emptyExtensionDir = createTempDir(); List nonUniqueYmlLines = extensionsYmlLines.stream() .map(s -> s.replace("uniqueid2", "uniqueid1")) .collect(Collectors.toList()); - Files.write(extensionDir.resolve("extensions.yml"), nonUniqueYmlLines, StandardCharsets.UTF_8); + Files.write(emptyExtensionDir.resolve("extensions.yml"), nonUniqueYmlLines, StandardCharsets.UTF_8); - ExtensionsManager extensionsManager = new ExtensionsManager(settings, extensionDir); + ExtensionsManager extensionsManager = new ExtensionsManager(settings, emptyExtensionDir); List expectedUninitializedExtensions = new ArrayList(); @@ -299,26 +327,24 @@ public void testNoExtensionsFile() throws Exception { } public void testEmptyExtensionsFile() throws Exception { - Path extensionDir = createTempDir(); + Path emptyExtensionDir = createTempDir(); List emptyExtensionsYmlLines = Arrays.asList(); - Files.write(extensionDir.resolve("extensions.yml"), emptyExtensionsYmlLines, StandardCharsets.UTF_8); + Files.write(emptyExtensionDir.resolve("extensions.yml"), emptyExtensionsYmlLines, StandardCharsets.UTF_8); Settings settings = Settings.builder().build(); - expectThrows(IOException.class, () -> new ExtensionsManager(settings, extensionDir)); + expectThrows(IOException.class, () -> new ExtensionsManager(settings, emptyExtensionDir)); } public void testInitialize() throws Exception { - Path extensionDir = createTempDir(); - Files.write(extensionDir.resolve("extensions.yml"), extensionsYmlLines, StandardCharsets.UTF_8); ExtensionsManager extensionsManager = new ExtensionsManager(settings, extensionDir); transportService.start(); transportService.acceptIncomingRequests(); - extensionsManager.initializeServicesAndRestHandler(restController, transportService, clusterService); + extensionsManager.initializeServicesAndRestHandler(restController, settingsModule, transportService, clusterService, settings); try (MockLogAppender mockLogAppender = MockLogAppender.createForLoggers(LogManager.getLogger(ExtensionsManager.class))) { @@ -350,31 +376,45 @@ public void testInitialize() throws Exception { } public void testHandleRegisterRestActionsRequest() throws Exception { - - Path extensionDir = createTempDir(); - Files.write(extensionDir.resolve("extensions.yml"), extensionsYmlLines, StandardCharsets.UTF_8); ExtensionsManager extensionsManager = new ExtensionsManager(settings, extensionDir); - extensionsManager.initializeServicesAndRestHandler(restController, transportService, clusterService); + extensionsManager.initializeServicesAndRestHandler(restController, settingsModule, transportService, clusterService, settings); String uniqueIdStr = "uniqueid1"; List actionsList = List.of("GET /foo", "PUT /bar", "POST /baz"); RegisterRestActionsRequest registerActionsRequest = new RegisterRestActionsRequest(uniqueIdStr, actionsList); TransportResponse response = extensionsManager.getRestActionsRequestHandler() .handleRegisterRestActionsRequest(registerActionsRequest); - assertEquals(RegisterRestActionsResponse.class, response.getClass()); - assertTrue(((RegisterRestActionsResponse) response).getResponse().contains(uniqueIdStr)); - assertTrue(((RegisterRestActionsResponse) response).getResponse().contains(actionsList.toString())); + assertEquals(ExtensionStringResponse.class, response.getClass()); + assertTrue(((ExtensionStringResponse) response).getResponse().contains(uniqueIdStr)); + assertTrue(((ExtensionStringResponse) response).getResponse().contains(actionsList.toString())); } - public void testHandleRegisterRestActionsRequestWithInvalidMethod() throws Exception { + public void testHandleRegisterSettingsRequest() throws Exception { + Files.write(extensionDir.resolve("extensions.yml"), extensionsYmlLines, StandardCharsets.UTF_8); - Path extensionDir = createTempDir(); + ExtensionsManager extensionsManager = new ExtensionsManager(settings, extensionDir); + extensionsManager.initializeServicesAndRestHandler(restController, settingsModule, transportService, clusterService, settings); + String uniqueIdStr = "uniqueid1"; + List> settingsList = List.of( + Setting.boolSetting("index.falseSetting", false, Property.IndexScope, Property.Dynamic), + Setting.simpleString("fooSetting", "foo", Property.NodeScope, Property.Final) + ); + RegisterCustomSettingsRequest registerCustomSettingsRequest = new RegisterCustomSettingsRequest(uniqueIdStr, settingsList); + TransportResponse response = extensionsManager.getCustomSettingsRequestHandler() + .handleRegisterCustomSettingsRequest(registerCustomSettingsRequest); + assertEquals(ExtensionStringResponse.class, response.getClass()); + assertTrue(((ExtensionStringResponse) response).getResponse().contains(uniqueIdStr)); + assertTrue(((ExtensionStringResponse) response).getResponse().contains("falseSetting")); + assertTrue(((ExtensionStringResponse) response).getResponse().contains("fooSetting")); + } + + public void testHandleRegisterRestActionsRequestWithInvalidMethod() throws Exception { ExtensionsManager extensionsManager = new ExtensionsManager(settings, extensionDir); - extensionsManager.initializeServicesAndRestHandler(restController, transportService, clusterService); + extensionsManager.initializeServicesAndRestHandler(restController, settingsModule, transportService, clusterService, settings); String uniqueIdStr = "uniqueid1"; List actionsList = List.of("FOO /foo", "PUT /bar", "POST /baz"); RegisterRestActionsRequest registerActionsRequest = new RegisterRestActionsRequest(uniqueIdStr, actionsList); @@ -390,7 +430,7 @@ public void testHandleRegisterRestActionsRequestWithInvalidUri() throws Exceptio ExtensionsManager extensionsManager = new ExtensionsManager(settings, extensionDir); - extensionsManager.initializeServicesAndRestHandler(restController, transportService, clusterService); + extensionsManager.initializeServicesAndRestHandler(restController, settingsModule, transportService, clusterService, settings); String uniqueIdStr = "uniqueid1"; List actionsList = List.of("GET", "PUT /bar", "POST /baz"); RegisterRestActionsRequest registerActionsRequest = new RegisterRestActionsRequest(uniqueIdStr, actionsList); @@ -404,7 +444,7 @@ public void testHandleExtensionRequest() throws Exception { ExtensionsManager extensionsManager = new ExtensionsManager(settings, extensionDir); - extensionsManager.initializeServicesAndRestHandler(restController, transportService, clusterService); + extensionsManager.initializeServicesAndRestHandler(restController, settingsModule, transportService, clusterService, settings); ExtensionRequest clusterStateRequest = new ExtensionRequest(ExtensionsManager.RequestType.REQUEST_EXTENSION_CLUSTER_STATE); assertEquals(ClusterStateResponse.class, extensionsManager.handleExtensionRequest(clusterStateRequest).getClass()); @@ -415,67 +455,240 @@ public void testHandleExtensionRequest() throws Exception { assertEquals(LocalNodeResponse.class, extensionsManager.handleExtensionRequest(localNodeRequest).getClass()); ExtensionRequest exceptionRequest = new ExtensionRequest(ExtensionsManager.RequestType.GET_SETTINGS); - Exception exception = expectThrows(IllegalStateException.class, () -> extensionsManager.handleExtensionRequest(exceptionRequest)); + Exception exception = expectThrows( + IllegalArgumentException.class, + () -> extensionsManager.handleExtensionRequest(exceptionRequest) + ); assertEquals("Handler not present for the provided request", exception.getMessage()); } - public void testRegisterHandler() throws Exception { + public void testHandleActionListenerOnFailureRequest() throws Exception { + + Files.write(extensionDir.resolve("extensions.yml"), extensionsYmlLines, StandardCharsets.UTF_8); ExtensionsManager extensionsManager = new ExtensionsManager(settings, extensionDir); - TransportService mockTransportService = spy( - new TransportService( - Settings.EMPTY, - mock(Transport.class), - null, - TransportService.NOOP_TRANSPORT_INTERCEPTOR, - x -> null, - null, - Collections.emptySet() - ) - ); + extensionsManager.initializeServicesAndRestHandler(restController, settingsModule, transportService, clusterService, settings); - extensionsManager.initializeServicesAndRestHandler(restController, mockTransportService, clusterService); - verify(mockTransportService, times(5)).registerRequestHandler(anyString(), anyString(), anyBoolean(), anyBoolean(), any(), any()); + ExtensionActionListenerOnFailureRequest listenerFailureRequest = new ExtensionActionListenerOnFailureRequest("Test failure"); + assertEquals( + AcknowledgedResponse.class, + extensionsManager.getListenerHandler().handleExtensionActionListenerOnFailureRequest(listenerFailureRequest).getClass() + ); + assertEquals("Test failure", extensionsManager.getListener().getExceptionList().get(0).getMessage()); } - private static class Example implements NamedWriteable { - public static final String INVALID_NAME = "invalid_name"; - public static final String NAME = "example"; - private final String message; + public void testEnvironmentSettingsRequest() throws Exception { - Example(String message) { - this.message = message; - } + Path extensionDir = createTempDir(); + Files.write(extensionDir.resolve("extensions.yml"), extensionsYmlLines, StandardCharsets.UTF_8); + ExtensionsManager extensionsManager = new ExtensionsManager(settings, extensionDir); + extensionsManager.initializeServicesAndRestHandler(restController, settingsModule, transportService, clusterService, settings); - Example(StreamInput in) throws IOException { - this.message = in.readString(); - } + List> componentSettings = List.of( + Setting.boolSetting("falseSetting", false, Property.IndexScope, Property.NodeScope), + Setting.simpleString("fooSetting", "foo", Property.Dynamic) + ); - @Override - public void writeTo(StreamOutput out) throws IOException { - out.writeString(message); + // Test EnvironmentSettingsRequest arg constructor + EnvironmentSettingsRequest environmentSettingsRequest = new EnvironmentSettingsRequest(componentSettings); + List> requestComponentSettings = environmentSettingsRequest.getComponentSettings(); + assertEquals(componentSettings.size(), requestComponentSettings.size()); + assertTrue(requestComponentSettings.containsAll(componentSettings)); + assertTrue(componentSettings.containsAll(requestComponentSettings)); + + // Test EnvironmentSettingsRequest StreamInput constructor + try (BytesStreamOutput out = new BytesStreamOutput()) { + environmentSettingsRequest.writeTo(out); + out.flush(); + try (BytesStreamInput in = new BytesStreamInput(BytesReference.toBytes(out.bytes()))) { + environmentSettingsRequest = new EnvironmentSettingsRequest(in); + requestComponentSettings = environmentSettingsRequest.getComponentSettings(); + assertEquals(componentSettings.size(), requestComponentSettings.size()); + assertTrue(requestComponentSettings.containsAll(componentSettings)); + assertTrue(componentSettings.containsAll(requestComponentSettings)); + } } - @Override - public String getWriteableName() { - return NAME; + } + + public void testEnvironmentSettingsResponse() throws Exception { + + List> componentSettings = List.of( + Setting.boolSetting("falseSetting", false, Property.IndexScope, Property.NodeScope), + Setting.simpleString("fooSetting", "foo", Property.Dynamic) + ); + + // Test EnvironmentSettingsResponse arg constructor + EnvironmentSettingsResponse environmentSettingsResponse = new EnvironmentSettingsResponse(settings, componentSettings); + assertEquals(componentSettings.size(), environmentSettingsResponse.getComponentSettingValues().size()); + + List> responseSettings = new ArrayList<>(); + responseSettings.addAll(environmentSettingsResponse.getComponentSettingValues().keySet()); + assertTrue(responseSettings.containsAll(componentSettings)); + assertTrue(componentSettings.containsAll(responseSettings)); + + // Test EnvironmentSettingsResponse StreamInput constrcutor + try (BytesStreamOutput out = new BytesStreamOutput()) { + environmentSettingsResponse.writeTo(out); + out.flush(); + try (BytesStreamInput in = new BytesStreamInput(BytesReference.toBytes(out.bytes()))) { + + environmentSettingsResponse = new EnvironmentSettingsResponse(in); + assertEquals(componentSettings.size(), environmentSettingsResponse.getComponentSettingValues().size()); + + responseSettings = new ArrayList<>(); + responseSettings.addAll(environmentSettingsResponse.getComponentSettingValues().keySet()); + assertTrue(responseSettings.containsAll(componentSettings)); + assertTrue(componentSettings.containsAll(responseSettings)); + } } + } + + public void testHandleEnvironmentSettingsRequest() throws Exception { + + Path extensionDir = createTempDir(); + Files.write(extensionDir.resolve("extensions.yml"), extensionsYmlLines, StandardCharsets.UTF_8); + ExtensionsManager extensionsManager = new ExtensionsManager(settings, extensionDir); + extensionsManager.initializeServicesAndRestHandler(restController, settingsModule, transportService, clusterService, settings); - @Override - public boolean equals(Object o) { - if (o == null || getClass() != o.getClass()) { - return false; + List> componentSettings = List.of( + Setting.boolSetting("falseSetting", false, Property.Dynamic), + Setting.boolSetting("trueSetting", true, Property.Dynamic) + ); + + EnvironmentSettingsRequest environmentSettingsRequest = new EnvironmentSettingsRequest(componentSettings); + TransportResponse response = extensionsManager.getEnvironmentSettingsRequestHandler() + .handleEnvironmentSettingsRequest(environmentSettingsRequest); + + assertEquals(EnvironmentSettingsResponse.class, response.getClass()); + assertEquals(componentSettings.size(), ((EnvironmentSettingsResponse) response).getComponentSettingValues().size()); + + List> responseSettings = new ArrayList<>(); + responseSettings.addAll(((EnvironmentSettingsResponse) response).getComponentSettingValues().keySet()); + assertTrue(responseSettings.containsAll(componentSettings)); + assertTrue(componentSettings.containsAll(responseSettings)); + } + + public void testAddSettingsUpdateConsumerRequest() throws Exception { + Path extensionDir = createTempDir(); + Files.write(extensionDir.resolve("extensions.yml"), extensionsYmlLines, StandardCharsets.UTF_8); + ExtensionsManager extensionsManager = new ExtensionsManager(settings, extensionDir); + extensionsManager.initializeServicesAndRestHandler(restController, settingsModule, transportService, clusterService, settings); + + List> componentSettings = List.of( + Setting.boolSetting("falseSetting", false, Property.IndexScope, Property.NodeScope), + Setting.simpleString("fooSetting", "foo", Property.Dynamic) + ); + + // Test AddSettingsUpdateConsumerRequest arg constructor + AddSettingsUpdateConsumerRequest addSettingsUpdateConsumerRequest = new AddSettingsUpdateConsumerRequest( + extensionNode, + componentSettings + ); + assertEquals(extensionNode, addSettingsUpdateConsumerRequest.getExtensionNode()); + assertEquals(componentSettings.size(), addSettingsUpdateConsumerRequest.getComponentSettings().size()); + + List> requestComponentSettings = new ArrayList<>(); + for (WriteableSetting writeableSetting : addSettingsUpdateConsumerRequest.getComponentSettings()) { + requestComponentSettings.add(writeableSetting.getSetting()); + } + assertTrue(requestComponentSettings.containsAll(componentSettings)); + assertTrue(componentSettings.containsAll(requestComponentSettings)); + + // Test AddSettingsUpdateConsumerRequest StreamInput constructor + try (BytesStreamOutput out = new BytesStreamOutput()) { + addSettingsUpdateConsumerRequest.writeTo(out); + out.flush(); + try (BytesStreamInput in = new BytesStreamInput(BytesReference.toBytes(out.bytes()))) { + addSettingsUpdateConsumerRequest = new AddSettingsUpdateConsumerRequest(in); + assertEquals(extensionNode, addSettingsUpdateConsumerRequest.getExtensionNode()); + assertEquals(componentSettings.size(), addSettingsUpdateConsumerRequest.getComponentSettings().size()); + + requestComponentSettings = new ArrayList<>(); + for (WriteableSetting writeableSetting : addSettingsUpdateConsumerRequest.getComponentSettings()) { + requestComponentSettings.add(writeableSetting.getSetting()); + } + assertTrue(requestComponentSettings.containsAll(componentSettings)); + assertTrue(componentSettings.containsAll(requestComponentSettings)); } - Example that = (Example) o; - return Objects.equals(message, that.message); } - @Override - public int hashCode() { - return Objects.hash(message); + } + + public void testHandleAddSettingsUpdateConsumerRequest() throws Exception { + + Path extensionDir = createTempDir(); + Files.write(extensionDir.resolve("extensions.yml"), extensionsYmlLines, StandardCharsets.UTF_8); + ExtensionsManager extensionsManager = new ExtensionsManager(settings, extensionDir); + + extensionsManager.initializeServicesAndRestHandler(restController, settingsModule, transportService, clusterService, settings); + + List> componentSettings = List.of( + Setting.boolSetting("falseSetting", false, Property.Dynamic), + Setting.boolSetting("trueSetting", true, Property.Dynamic) + ); + + AddSettingsUpdateConsumerRequest addSettingsUpdateConsumerRequest = new AddSettingsUpdateConsumerRequest( + extensionNode, + componentSettings + ); + TransportResponse response = extensionsManager.getAddSettingsUpdateConsumerRequestHandler() + .handleAddSettingsUpdateConsumerRequest(addSettingsUpdateConsumerRequest); + assertEquals(AcknowledgedResponse.class, response.getClass()); + // Should fail as component settings are not registered within cluster settings + assertEquals(false, ((AcknowledgedResponse) response).getStatus()); + } + + public void testUpdateSettingsRequest() throws Exception { + Path extensionDir = createTempDir(); + Files.write(extensionDir.resolve("extensions.yml"), extensionsYmlLines, StandardCharsets.UTF_8); + ExtensionsManager extensionsManager = new ExtensionsManager(settings, extensionDir); + extensionsManager.initializeServicesAndRestHandler(restController, settingsModule, transportService, clusterService, settings); + + Setting componentSetting = Setting.boolSetting("falseSetting", false, Property.Dynamic); + SettingType settingType = SettingType.Boolean; + boolean data = true; + + // Test UpdateSettingRequest arg constructor + UpdateSettingsRequest updateSettingsRequest = new UpdateSettingsRequest(settingType, componentSetting, data); + assertEquals(componentSetting, updateSettingsRequest.getComponentSetting()); + assertEquals(settingType, updateSettingsRequest.getSettingType()); + assertEquals(data, updateSettingsRequest.getData()); + + // Test UpdateSettingRequest StreamInput constructor + try (BytesStreamOutput out = new BytesStreamOutput()) { + updateSettingsRequest.writeTo(out); + out.flush(); + try (BytesStreamInput in = new BytesStreamInput(BytesReference.toBytes(out.bytes()))) { + updateSettingsRequest = new UpdateSettingsRequest(in); + assertEquals(componentSetting, updateSettingsRequest.getComponentSetting()); + assertEquals(settingType, updateSettingsRequest.getSettingType()); + assertEquals(data, updateSettingsRequest.getData()); + } } + + } + + public void testRegisterHandler() throws Exception { + + ExtensionsManager extensionsManager = new ExtensionsManager(settings, extensionDir); + + TransportService mockTransportService = spy( + new TransportService( + Settings.EMPTY, + mock(Transport.class), + null, + TransportService.NOOP_TRANSPORT_INTERCEPTOR, + x -> null, + null, + Collections.emptySet() + ) + ); + extensionsManager.initializeServicesAndRestHandler(restController, settingsModule, mockTransportService, clusterService, settings); + verify(mockTransportService, times(9)).registerRequestHandler(anyString(), anyString(), anyBoolean(), anyBoolean(), any(), any()); + } public void testOnIndexModule() throws Exception { @@ -485,7 +698,7 @@ public void testOnIndexModule() throws Exception { transportService.start(); transportService.acceptIncomingRequests(); - extensionsManager.initializeServicesAndRestHandler(restController, transportService, clusterService); + extensionsManager.initializeServicesAndRestHandler(restController, settingsModule, transportService, clusterService, settings); Environment environment = TestEnvironment.newEnvironment(settings); AnalysisRegistry emptyAnalysisRegistry = new AnalysisRegistry( diff --git a/server/src/test/java/org/opensearch/extensions/rest/RegisterRestActionsTests.java b/server/src/test/java/org/opensearch/extensions/rest/RegisterRestActionsTests.java index a8f1739ce82f2..4929394fd18bb 100644 --- a/server/src/test/java/org/opensearch/extensions/rest/RegisterRestActionsTests.java +++ b/server/src/test/java/org/opensearch/extensions/rest/RegisterRestActionsTests.java @@ -42,21 +42,4 @@ public void testRegisterRestActionsRequest() throws Exception { } } } - - public void testRegisterRestActionsResponse() throws Exception { - String response = "This is a response"; - RegisterRestActionsResponse registerRestActionsResponse = new RegisterRestActionsResponse(response); - - assertEquals(response, registerRestActionsResponse.getResponse()); - - try (BytesStreamOutput out = new BytesStreamOutput()) { - registerRestActionsResponse.writeTo(out); - out.flush(); - try (BytesStreamInput in = new BytesStreamInput(BytesReference.toBytes(out.bytes()))) { - registerRestActionsResponse = new RegisterRestActionsResponse(in); - - assertEquals(response, registerRestActionsResponse.getResponse()); - } - } - } } diff --git a/server/src/test/java/org/opensearch/extensions/settings/RegisterCustomSettingsTests.java b/server/src/test/java/org/opensearch/extensions/settings/RegisterCustomSettingsTests.java new file mode 100644 index 0000000000000..68f32672871ad --- /dev/null +++ b/server/src/test/java/org/opensearch/extensions/settings/RegisterCustomSettingsTests.java @@ -0,0 +1,56 @@ +/* + * 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.extensions.settings; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.opensearch.common.bytes.BytesReference; +import org.opensearch.common.io.stream.BytesStreamInput; +import org.opensearch.common.io.stream.BytesStreamOutput; +import org.opensearch.common.settings.Setting; +import org.opensearch.common.settings.Setting.Property; +import org.opensearch.common.unit.ByteSizeUnit; +import org.opensearch.common.unit.ByteSizeValue; +import org.opensearch.common.unit.TimeValue; +import org.opensearch.test.OpenSearchTestCase; + +public class RegisterCustomSettingsTests extends OpenSearchTestCase { + + public void testRegisterCustomSettingsRequest() throws Exception { + String uniqueIdStr = "uniqueid1"; + List> expected = List.of( + Setting.boolSetting("falseSetting", false, Property.IndexScope, Property.NodeScope), + Setting.simpleString("fooSetting", "foo", Property.Dynamic), + Setting.timeSetting("timeSetting", new TimeValue(5, TimeUnit.MILLISECONDS), Property.Dynamic), + Setting.byteSizeSetting("byteSizeSetting", new ByteSizeValue(10, ByteSizeUnit.KB), Property.Dynamic) + ); + RegisterCustomSettingsRequest registerCustomSettingsRequest = new RegisterCustomSettingsRequest(uniqueIdStr, expected); + + assertEquals(uniqueIdStr, registerCustomSettingsRequest.getUniqueId()); + List> settings = registerCustomSettingsRequest.getSettings(); + assertEquals(expected.size(), settings.size()); + assertTrue(settings.containsAll(expected)); + assertTrue(expected.containsAll(settings)); + + try (BytesStreamOutput out = new BytesStreamOutput()) { + registerCustomSettingsRequest.writeTo(out); + out.flush(); + try (BytesStreamInput in = new BytesStreamInput(BytesReference.toBytes(out.bytes()))) { + registerCustomSettingsRequest = new RegisterCustomSettingsRequest(in); + + assertEquals(uniqueIdStr, registerCustomSettingsRequest.getUniqueId()); + settings = registerCustomSettingsRequest.getSettings(); + assertEquals(expected.size(), settings.size()); + assertTrue(settings.containsAll(expected)); + assertTrue(expected.containsAll(settings)); + } + } + } +}