diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e193c9f86e12..07f70f9f4a7aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Add feature flag for extensions ([#5211](https://github.com/opensearch-project/OpenSearch/pull/5211)) - Added jackson dependency to server ([#5366] (https://github.com/opensearch-project/OpenSearch/pull/5366)) - Adding support to register settings dynamically ([#5495](https://github.com/opensearch-project/OpenSearch/pull/5495)) -- Added experimental support for extensions ([#5347](https://github.com/opensearch-project/OpenSearch/pull/5347)), ([#5518](https://github.com/opensearch-project/OpenSearch/pull/5518)) +- Added experimental support for extensions ([#5347](https://github.com/opensearch-project/OpenSearch/pull/5347)), ([#5518](https://github.com/opensearch-project/OpenSearch/pull/5518), ([#5597](https://github.com/opensearch-project/OpenSearch/pull/5597))) - Add CI bundle pattern to distribution download ([#5348](https://github.com/opensearch-project/OpenSearch/pull/5348)) - Add support for ppc64le architecture ([#5459](https://github.com/opensearch-project/OpenSearch/pull/5459)) 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..2dd0d85ab0cd7 --- /dev/null +++ b/server/src/main/java/org/opensearch/common/settings/WriteableSetting.java @@ -0,0 +1,275 @@ +/* + * 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 = setting.getDefault(Settings.EMPTY).getClass().getSimpleName(); + try { + // This throws IAE if not in enum + return SettingType.valueOf(typeStr); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException( + "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 IllegalArgumentException("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 IllegalArgumentException("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 IllegalArgumentException("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..3fbe636803e66 --- /dev/null +++ b/server/src/main/java/org/opensearch/env/EnvironmentSettingsResponse.java @@ -0,0 +1,61 @@ +/* + * 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.Settings; +import org.opensearch.transport.TransportResponse; + +import java.io.IOException; +import java.util.Objects; + +/** + * Environment Settings Response for Extensibility + * + * @opensearch.internal + */ +public class EnvironmentSettingsResponse extends TransportResponse { + private final Settings environmentSettings; + + public EnvironmentSettingsResponse(Settings environmentSettings) { + this.environmentSettings = environmentSettings; + } + + public EnvironmentSettingsResponse(StreamInput in) throws IOException { + this.environmentSettings = Settings.readSettingsFromStream(in); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + Settings.writeSettingsToStream(this.environmentSettings, out); + } + + public Settings getEnvironmentSettings() { + return environmentSettings; + } + + @Override + public String toString() { + return "EnvironmentSettingsResponse{environmentSettings=" + environmentSettings.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(environmentSettings, that.environmentSettings); + } + + @Override + public int hashCode() { + return Objects.hash(environmentSettings); + } +} 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..0ecf841e49b9e --- /dev/null +++ b/server/src/main/java/org/opensearch/extensions/AddSettingsUpdateConsumerRequest.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.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(); + this.componentSettings = new ArrayList<>(componentSettingsCount); + for (int i = 0; i < componentSettingsCount; i++) { + this.componentSettings.add(new WriteableSetting(in)); + } + } + + @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() + "}"; + } + + @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..cb05a4210b483 --- /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.debug("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/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/ExtensionsManager.java b/server/src/main/java/org/opensearch/extensions/ExtensionsManager.java index be843fe35a5f9..e42b9d1e755a5 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; @@ -53,6 +56,7 @@ import org.opensearch.transport.TransportResponse; import org.opensearch.transport.TransportResponseHandler; import org.opensearch.transport.TransportService; +import org.opensearch.env.EnvironmentSettingsResponse; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; @@ -69,11 +73,14 @@ 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_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); @@ -87,6 +94,8 @@ public static enum RequestType { REQUEST_EXTENSION_LOCAL_NODE, REQUEST_EXTENSION_CLUSTER_SETTINGS, REQUEST_EXTENSION_REGISTER_REST_ACTIONS, + REQUEST_EXTENSION_REGISTER_SETTINGS, + REQUEST_EXTENSION_ENVIRONMENT_SETTINGS, CREATE_COMPONENT, ON_INDEX_MODULE, GET_SETTINGS @@ -106,8 +115,11 @@ public static enum OpenSearchRequestType { private List extensions; private Map extensionIdMap; private RestActionsRequestHandler restActionsRequestHandler; + private CustomSettingsRequestHandler customSettingsRequestHandler; private TransportService transportService; private ClusterService clusterService; + private Settings environmentSettings; + private AddSettingsUpdateConsumerRequestHandler addSettingsUpdateConsumerRequestHandler; public ExtensionsManager() { this.extensionsPath = Path.of(""); @@ -136,21 +148,32 @@ 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.customSettingsRequestHandler = new CustomSettingsRequestHandler(settingsModule); this.transportService = transportService; this.clusterService = clusterService; + this.environmentSettings = initialEnvironmentSettings; + this.addSettingsUpdateConsumerRequestHandler = new AddSettingsUpdateConsumerRequestHandler( + clusterService, + transportService, + REQUEST_EXTENSION_UPDATE_SETTINGS + ); registerRequestHandler(); } @@ -163,6 +186,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 +218,24 @@ private void registerRequestHandler() { ExtensionRequest::new, ((request, channel, task) -> channel.sendResponse(handleExtensionRequest(request))) ); + transportService.registerRequestHandler( + REQUEST_EXTENSION_ENVIRONMENT_SETTINGS, + ThreadPool.Names.GENERIC, + false, + false, + ExtensionRequest::new, + ((request, channel, task) -> channel.sendResponse(handleExtensionRequest(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, @@ -356,8 +405,10 @@ TransportResponse handleExtensionRequest(ExtensionRequest extensionRequest) thro return new LocalNodeResponse(clusterService); case REQUEST_EXTENSION_CLUSTER_SETTINGS: return new ClusterSettingsResponse(clusterService); + case REQUEST_EXTENSION_ENVIRONMENT_SETTINGS: + return new EnvironmentSettingsResponse(this.environmentSettings); default: - throw new IllegalStateException("Handler not present for the provided request"); + throw new IllegalArgumentException("Handler not present for the provided request"); } } @@ -528,10 +579,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 +595,70 @@ 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 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 AddSettingsUpdateConsumerRequestHandler getAddSettingsUpdateConsumerRequestHandler() { + return addSettingsUpdateConsumerRequestHandler; + } + + public void setAddSettingsUpdateConsumerRequestHandler( + AddSettingsUpdateConsumerRequestHandler addSettingsUpdateConsumerRequestHandler + ) { + this.addSettingsUpdateConsumerRequestHandler = addSettingsUpdateConsumerRequestHandler; + } + + public Settings getEnvironmentSettings() { + return environmentSettings; + } + + public void setEnvironmentSettings(Settings environmentSettings) { + this.environmentSettings = environmentSettings; + } + } 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..6ed7b9a5a6d36 --- /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(UpdateSettingsRequest.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..9bf53812c223e --- /dev/null +++ b/server/src/main/java/org/opensearch/extensions/UpdateSettingsResponseHandler.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.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()); + if (!response.getStatus()) { + handleException(new TransportException("Request was not completed successfully")); + } + } + + @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/RegisterRestActionsResponse.java b/server/src/main/java/org/opensearch/extensions/rest/RegisterRestActionsResponse.java deleted file mode 100644 index c0a79ad32ce89..0000000000000 --- a/server/src/main/java/org/opensearch/extensions/rest/RegisterRestActionsResponse.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -package org.opensearch.extensions.rest; - -import org.opensearch.common.io.stream.StreamInput; -import org.opensearch.common.io.stream.StreamOutput; -import org.opensearch.transport.TransportResponse; - -import java.io.IOException; - -/** - * Response to register REST Actions request. - * - * @opensearch.internal - */ -public class RegisterRestActionsResponse extends TransportResponse { - private String response; - - public RegisterRestActionsResponse(String response) { - this.response = response; - } - - public RegisterRestActionsResponse(StreamInput in) throws IOException { - response = in.readString(); - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - out.writeString(response); - } - - public String getResponse() { - return response; - } -} 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..790beaef0a969 100644 --- a/server/src/main/java/org/opensearch/extensions/rest/RestActionsRequestHandler.java +++ b/server/src/main/java/org/opensearch/extensions/rest/RestActionsRequestHandler.java @@ -8,6 +8,7 @@ package org.opensearch.extensions.rest; +import org.opensearch.extensions.AcknowledgedResponse; import org.opensearch.extensions.DiscoveryExtensionNode; import org.opensearch.rest.RestController; import org.opensearch.rest.RestHandler; @@ -48,15 +49,13 @@ public RestActionsRequestHandler( * Handles a {@link RegisterRestActionsRequest}. * * @param restActionsRequest The request to handle. - * @return A {@link RegisterRestActionsResponse} indicating success. + * @return A {@link AcknowledgedResponse} 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( - "Registered extension " + restActionsRequest.getUniqueId() + " to handle REST Actions " + restActionsRequest.getRestActions() - ); + return new AcknowledgedResponse(true); } } 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..980dcf67c3128 --- /dev/null +++ b/server/src/main/java/org/opensearch/extensions/settings/CustomSettingsRequestHandler.java @@ -0,0 +1,55 @@ +/* + * 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.AcknowledgedResponse; +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 AcknowledgedResponse} 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 AcknowledgedResponse(true); + } +} 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..1f7e23544ebdf --- /dev/null +++ b/server/src/main/java/org/opensearch/extensions/settings/RegisterCustomSettingsRequest.java @@ -0,0 +1,82 @@ +/* + * 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(); + this.settings = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + WriteableSetting ws = new WriteableSetting(in); + this.settings.add(ws.getSetting()); + } + } + + @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..5e34f68539798 --- /dev/null +++ b/server/src/test/java/org/opensearch/common/settings/WriteableSettingTests.java @@ -0,0 +1,482 @@ +/* + * 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 default value type not in enum + Function dvfi = s -> ""; + dv.set(setting, dvfi); + Function pfi = s -> new WriteableSettingTests(); + p.set(setting, pfi); + IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () -> new WriteableSetting(setting)); + assertTrue(iae.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..de8c677e5bcc6 --- /dev/null +++ b/server/src/test/java/org/opensearch/extensions/ExtensionResponseTests.java @@ -0,0 +1,34 @@ +/* + * 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()); + } + } + } +} diff --git a/server/src/test/java/org/opensearch/extensions/ExtensionsManagerTests.java b/server/src/test/java/org/opensearch/extensions/ExtensionsManagerTests.java index d45e51ea2bbc8..472899db7dad6 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,42 @@ 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(AcknowledgedResponse.class, response.getClass()); + assertTrue(((AcknowledgedResponse) response).getStatus()); } - 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(AcknowledgedResponse.class, response.getClass()); + assertTrue(((AcknowledgedResponse) response).getStatus()); + } + 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 +427,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 +441,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()); @@ -414,68 +451,198 @@ public void testHandleExtensionRequest() throws Exception { ExtensionRequest localNodeRequest = new ExtensionRequest(ExtensionsManager.RequestType.REQUEST_EXTENSION_LOCAL_NODE); assertEquals(LocalNodeResponse.class, extensionsManager.handleExtensionRequest(localNodeRequest).getClass()); + ExtensionRequest environmentSettingsRequest = new ExtensionRequest( + ExtensionsManager.RequestType.REQUEST_EXTENSION_ENVIRONMENT_SETTINGS + ); + assertEquals(EnvironmentSettingsResponse.class, extensionsManager.handleExtensionRequest(environmentSettingsRequest).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 testEnvironmentSettingsResponse() throws Exception { - ExtensionsManager extensionsManager = new ExtensionsManager(settings, extensionDir); + // Test EnvironmentSettingsResponse arg constructor + EnvironmentSettingsResponse environmentSettingsResponse = new EnvironmentSettingsResponse(settings); + assertEquals(settings, environmentSettingsResponse.getEnvironmentSettings()); - TransportService mockTransportService = spy( - new TransportService( - Settings.EMPTY, - mock(Transport.class), - null, - TransportService.NOOP_TRANSPORT_INTERCEPTOR, - x -> null, - null, - Collections.emptySet() - ) - ); - - extensionsManager.initializeServicesAndRestHandler(restController, mockTransportService, clusterService); - verify(mockTransportService, times(5)).registerRequestHandler(anyString(), anyString(), anyBoolean(), anyBoolean(), any(), any()); + // Test EnvironmentSettingsResponse StreamInput constructor + try (BytesStreamOutput out = new BytesStreamOutput()) { + environmentSettingsResponse.writeTo(out); + out.flush(); + try (BytesStreamInput in = new BytesStreamInput(BytesReference.toBytes(out.bytes()))) { + environmentSettingsResponse = new EnvironmentSettingsResponse(in); + assertEquals(settings, environmentSettingsResponse.getEnvironmentSettings()); + } + } } - 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 testEnvironmentSettingsRegisteredValue() throws Exception { + // Create setting with value false + Setting boolSetting = Setting.boolSetting("boolSetting", false, Property.Dynamic); - Example(String message) { - this.message = message; - } + // Create Settings with registered bool setting with value true + Settings environmentSettings = Settings.builder().put("boolSetting", "true").build(); - Example(StreamInput in) throws IOException { - this.message = in.readString(); - } + EnvironmentSettingsResponse environmentSettingsResponse = new EnvironmentSettingsResponse(environmentSettings); + try (BytesStreamOutput out = new BytesStreamOutput()) { + environmentSettingsResponse.writeTo(out); + out.flush(); + try (BytesStreamInput in = new BytesStreamInput(BytesReference.toBytes(out.bytes()))) { - @Override - public void writeTo(StreamOutput out) throws IOException { - out.writeString(message); + environmentSettingsResponse = new EnvironmentSettingsResponse(in); + assertEquals(environmentSettings, environmentSettingsResponse.getEnvironmentSettings()); + + // bool setting is registered in Settings object, thus the expected return value is the registered setting value + assertEquals(true, boolSetting.get(environmentSettingsResponse.getEnvironmentSettings())); + } } + } - @Override - public String getWriteableName() { - return NAME; + public void testEnvironmentSettingsDefaultValue() throws Exception { + // Create setting with value false + Setting boolSetting = Setting.boolSetting("boolSetting", false, Property.Dynamic); + + // Create settings object without registered bool setting + Settings environmentSettings = Settings.builder().put("testSetting", "true").build(); + + EnvironmentSettingsResponse environmentSettingsResponse = new EnvironmentSettingsResponse(environmentSettings); + try (BytesStreamOutput out = new BytesStreamOutput()) { + environmentSettingsResponse.writeTo(out); + out.flush(); + try (BytesStreamInput in = new BytesStreamInput(BytesReference.toBytes(out.bytes()))) { + + environmentSettingsResponse = new EnvironmentSettingsResponse(in); + assertEquals(environmentSettings, environmentSettingsResponse.getEnvironmentSettings()); + // bool setting is not registered in Settings object, thus the expected return value is the default setting value + assertEquals(false, boolSetting.get(environmentSettingsResponse.getEnvironmentSettings())); + } } + } + + 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()); - @Override - public boolean equals(Object o) { - if (o == null || getClass() != o.getClass()) { - return false; + 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(8)).registerRequestHandler(anyString(), anyString(), anyBoolean(), anyBoolean(), any(), any()); + } public void testOnIndexModule() throws Exception { @@ -485,7 +652,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)); + } + } + } +}