Skip to content

Commit

Permalink
Add extension scoped settings and add area for additional settings
Browse files Browse the repository at this point in the history
Signed-off-by: Craig Perkins <cwperx@amazon.com>
  • Loading branch information
cwperks committed May 11, 2023
1 parent 0acc450 commit 9717fd6
Show file tree
Hide file tree
Showing 4 changed files with 257 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,13 @@
*
* @opensearch.internal
*/
public final class ExtensionSecuritySettings extends AbstractScopedSettings {
public final class ExtensionAdditionalSettings extends AbstractScopedSettings {

public ExtensionSecuritySettings(final Set<Setting<?>> settingsSet) {
public ExtensionAdditionalSettings(final Set<Setting<?>> settingsSet) {
this(settingsSet, Collections.emptySet());
}

public ExtensionSecuritySettings(final Set<Setting<?>> settingsSet, final Set<SettingUpgrader<?>> settingUpgraders) {
public ExtensionAdditionalSettings(final Set<Setting<?>> settingsSet, final Set<SettingUpgrader<?>> settingUpgraders) {
super(Settings.EMPTY, settingsSet, settingUpgraders, Property.ExtensionScope);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand All @@ -40,7 +39,6 @@
import org.opensearch.cluster.ClusterSettingsResponse;
import org.opensearch.cluster.node.DiscoveryNode;
import org.opensearch.cluster.service.ClusterService;
import org.opensearch.common.settings.Setting;
import org.opensearch.core.util.FileSystemUtils;
import org.opensearch.common.io.stream.StreamInput;
import org.opensearch.common.settings.Settings;
Expand Down Expand Up @@ -660,13 +658,19 @@ private ExtensionsSettings readFromExtensionsYml(Path filePath) throws IOExcepti
}
}

Set<String> securitySettingsKeys = identityService.getExtensionSettings().stream().map(s -> s.getKey()).collect(Collectors.toSet());
Map<String, ?> securitySettings = extensionMap.entrySet()
.stream()
.filter(kv -> securitySettingsKeys.contains(kv.getKey()))
.collect(Collectors.toMap(map -> map.getKey(), map -> map.getValue()));
Settings.Builder output = Settings.builder();
output.loadFromMap(securitySettings);
ExtensionAdditionalSettings additionalSettings = new ExtensionAdditionalSettings(Set.of());

if (identityService != null) {
additionalSettings = new ExtensionAdditionalSettings(identityService.getExtensionSettings().stream().collect(Collectors.toSet()));
Set<String> securitySettingsKeys = identityService.getExtensionSettings().stream().map(s -> s.getKey()).collect(Collectors.toSet());
Map<String, ?> securitySettings = extensionMap.entrySet()
.stream()
.filter(kv -> securitySettingsKeys.contains(kv.getKey()))
.collect(Collectors.toMap(map -> map.getKey(), map -> map.getValue()));
output.loadFromMap(securitySettings);
additionalSettings.applySettings(output.build());
}

// Create extension read from yml config
readExtensions.add(
Expand All @@ -679,7 +683,7 @@ private ExtensionsSettings readFromExtensionsYml(Path filePath) throws IOExcepti
extensionMap.get("opensearchVersion").toString(),
extensionMap.get("minimumCompatibleVersion").toString(),
extensionDependencyList,
output.build()
additionalSettings
)
);
} catch (IOException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,11 @@

package org.opensearch.extensions;

import org.opensearch.common.settings.Setting;
import org.opensearch.common.settings.Settings;
import org.opensearch.identity.IdentityService;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* List of extension configurations from extension.yml
Expand Down Expand Up @@ -50,7 +46,7 @@ public static class Extension {
private String opensearchVersion;
private String minimumCompatibleVersion;
private List<ExtensionDependency> dependencies = Collections.emptyList();
private Settings securitySettings = Settings.EMPTY;
private ExtensionAdditionalSettings additionalSettings;

public Extension(
String name,
Expand All @@ -61,7 +57,7 @@ public Extension(
String opensearchVersion,
String minimumCompatibleVersion,
List<ExtensionDependency> dependencies,
Settings securitySettings
ExtensionAdditionalSettings additionalSettings
) {
this.name = name;
this.uniqueId = uniqueId;
Expand All @@ -71,7 +67,7 @@ public Extension(
this.opensearchVersion = opensearchVersion;
this.minimumCompatibleVersion = minimumCompatibleVersion;
this.dependencies = dependencies;
this.securitySettings = securitySettings;
this.additionalSettings = additionalSettings;
}

public Extension() {
Expand Down Expand Up @@ -136,8 +132,8 @@ public List<ExtensionDependency> getDependencies() {
return dependencies;
}

public Settings getSecuritySettings() {
return securitySettings;
public ExtensionAdditionalSettings getAdditionalSettings() {
return additionalSettings;
}

public String getMinimumCompatibleVersion() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
/*
* 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.junit.After;
import org.junit.Before;
import org.opensearch.Version;
import org.opensearch.action.ActionModule;
import org.opensearch.client.node.NodeClient;
import org.opensearch.cluster.metadata.IndexMetadata;
import org.opensearch.cluster.node.DiscoveryNode;
import org.opensearch.cluster.service.ClusterService;
import org.opensearch.common.io.stream.NamedWriteableRegistry;
import org.opensearch.common.network.NetworkService;
import org.opensearch.common.settings.Setting;
import org.opensearch.common.settings.Setting.Property;
import org.opensearch.common.settings.Settings;
import org.opensearch.common.settings.SettingsModule;
import org.opensearch.common.transport.TransportAddress;
import org.opensearch.common.util.FeatureFlags;
import org.opensearch.common.util.PageCacheRecycler;
import org.opensearch.env.Environment;
import org.opensearch.identity.IdentityService;
import org.opensearch.identity.Subject;
import org.opensearch.identity.noop.NoopSubject;
import org.opensearch.indices.breaker.NoneCircuitBreakerService;
import org.opensearch.plugins.IdentityPlugin;
import org.opensearch.rest.RestController;
import org.opensearch.test.FeatureFlagSetter;
import org.opensearch.test.OpenSearchTestCase;
import org.opensearch.test.client.NoOpNodeClient;
import org.opensearch.test.transport.MockTransportService;
import org.opensearch.threadpool.TestThreadPool;
import org.opensearch.threadpool.ThreadPool;
import org.opensearch.transport.TransportService;
import org.opensearch.transport.nio.MockNioTransport;
import org.opensearch.usage.UsageService;

import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;

import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static java.util.Collections.emptySet;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.opensearch.test.ClusterServiceUtils.createClusterService;

public class ExtensionsManagerWithIdentityTests extends OpenSearchTestCase {

private FeatureFlagSetter featureFlagSetter;
private TransportService transportService;
private ActionModule actionModule;
private RestController restController;
private SettingsModule settingsModule;
private ClusterService clusterService;
private IdentityService identityService;

private Setting customSetting = Setting.simpleString("custom_extension_setting", "none", Property.ExtensionScope);
private NodeClient client;
private MockNioTransport transport;
private Path extensionDir;
private final ThreadPool threadPool = new TestThreadPool(ExtensionsManagerWithIdentityTests.class.getSimpleName());
private final Settings settings = Settings.builder()
.put(IndexMetadata.SETTING_VERSION_CREATED, Version.CURRENT)
.put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toString())
.build();
private final List<String> extensionsYmlLines = Arrays.asList(
"extensions:",
" - name: firstExtension",
" uniqueId: uniqueid1",
" hostAddress: '127.0.0.0'",
" port: '9300'",
" version: '0.0.7'",
" opensearchVersion: '3.0.0'",
" minimumCompatibleVersion: '3.0.0'",
" custom_extension_setting: 'custom_setting'",
" - name: secondExtension",
" uniqueId: 'uniqueid2'",
" hostAddress: '127.0.0.1'",
" port: '9301'",
" version: '3.14.16'",
" opensearchVersion: '2.0.0'",
" minimumCompatibleVersion: '2.0.0'"
);

private DiscoveryExtensionNode extensionNode;

@Before
public void setup() throws Exception {
featureFlagSetter = FeatureFlagSetter.set(FeatureFlags.EXTENSIONS);
Settings settings = Settings.builder().put("cluster.name", "test").build();
transport = new MockNioTransport(
settings,
Version.CURRENT,
threadPool,
new NetworkService(Collections.emptyList()),
PageCacheRecycler.NON_RECYCLING_INSTANCE,
new NamedWriteableRegistry(Collections.emptyList()),
new NoneCircuitBreakerService()
);
transportService = new MockTransportService(
settings,
transport,
threadPool,
TransportService.NOOP_TRANSPORT_INTERCEPTOR,
(boundAddress) -> new DiscoveryNode(
"test_node",
"test_node",
boundAddress.publishAddress(),
emptyMap(),
emptySet(),
Version.CURRENT
),
null,
Collections.emptySet()
);
actionModule = mock(ActionModule.class);
IdentityPlugin identityPlugin = new IdentityPlugin() {
@Override
public Subject getSubject() {
return new NoopSubject();
}

@Override
public List<Setting<?>> getExtensionSettings() {
List<Setting<?>> settings = new ArrayList<Setting<?>>();
settings.add(customSetting);
return settings;
}
};
identityService = new IdentityService(Settings.EMPTY, List.of(identityPlugin));
restController = new RestController(
emptySet(),
null,
new NodeClient(Settings.EMPTY, threadPool),
new NoneCircuitBreakerService(),
new UsageService(),
identityService
);
when(actionModule.getRestController()).thenReturn(restController);
settingsModule = new SettingsModule(Settings.EMPTY, emptyList(), emptyList(), emptySet());
clusterService = createClusterService(threadPool);

extensionDir = createTempDir();

extensionNode = new DiscoveryExtensionNode(
"firstExtension",
"uniqueid1",
new TransportAddress(InetAddress.getByName("127.0.0.0"), 9300),
new HashMap<String, String>(),
Version.fromString("3.0.0"),
Version.fromString("3.0.0"),
Collections.emptyList()
);
client = new NoOpNodeClient(this.getTestName());
}

@Override
@After
public void tearDown() throws Exception {
super.tearDown();
transportService.close();
client.close();
ThreadPool.terminate(threadPool, 30, TimeUnit.SECONDS);
featureFlagSetter.close();
}

public void testAdditionalExtensionSettings() throws Exception {
Files.write(extensionDir.resolve("extensions.yml"), extensionsYmlLines, StandardCharsets.UTF_8);

ExtensionsManager extensionsManager = new ExtensionsManager(extensionDir, identityService);

List<DiscoveryExtensionNode> expectedExtensions = new ArrayList<DiscoveryExtensionNode>();

String expectedUniqueId = "uniqueid0";

expectedExtensions.add(
new DiscoveryExtensionNode(
"firstExtension",
"uniqueid1",
new TransportAddress(InetAddress.getByName("127.0.0.0"), 9300),
new HashMap<String, String>(),
Version.fromString("3.0.0"),
Version.fromString("3.0.0"),
Collections.emptyList()
)
);

expectedExtensions.add(
new DiscoveryExtensionNode(
"secondExtension",
"uniqueid2",
new TransportAddress(InetAddress.getByName("127.0.0.1"), 9301),
new HashMap<String, String>(),
Version.fromString("2.0.0"),
Version.fromString("2.0.0"),
List.of()
)
);
assertEquals(expectedExtensions.size(), extensionsManager.getExtensionIdMap().values().size());
for (DiscoveryExtensionNode extension : expectedExtensions) {
DiscoveryExtensionNode initializedExtension = extensionsManager.getExtensionIdMap().get(extension.getId());
assertEquals(extension.getName(), initializedExtension.getName());
assertEquals(extension.getId(), initializedExtension.getId());
assertEquals(extension.getAddress(), initializedExtension.getAddress());
assertEquals(extension.getAttributes(), initializedExtension.getAttributes());
assertEquals(extension.getVersion(), initializedExtension.getVersion());
assertEquals(extension.getMinimumCompatibleVersion(), initializedExtension.getMinimumCompatibleVersion());
assertEquals(extension.getDependencies(), initializedExtension.getDependencies());
assertTrue(extensionsManager.lookupExtensionSettingsById(extension.getId()).isPresent());
if ("firstExtension".equals(extension.getName())) {
assertEquals("custom_setting", extensionsManager.lookupExtensionSettingsById(extension.getId()).get().getAdditionalSettings().get(customSetting));
} else if ("secondExtension".equals(extension.getName())) {
assertEquals("none", extensionsManager.lookupExtensionSettingsById(extension.getId()).get().getAdditionalSettings().get(customSetting));
}
}
}
}

0 comments on commit 9717fd6

Please sign in to comment.