diff --git a/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/AwsEc2Service.java b/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/AwsEc2Service.java index 9765ce6e1bdfc..c001e35ad1ee6 100644 --- a/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/AwsEc2Service.java +++ b/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/AwsEc2Service.java @@ -22,12 +22,14 @@ import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Setting.Property; import org.elasticsearch.common.unit.TimeValue; + +import java.io.Closeable; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.function.Function; -interface AwsEc2Service { +interface AwsEc2Service extends Closeable { Setting AUTO_ATTRIBUTE_SETTING = Setting.boolSetting("cloud.node.auto_attributes", false, Property.NodeScope); class HostType { @@ -79,25 +81,20 @@ class HostType { key -> Setting.listSetting(key, Collections.emptyList(), Function.identity(), Property.NodeScope)); /** - * Creates then caches an {@code AmazonEC2} client using the current client - * settings. + * Builds then caches an {@code AmazonEC2} client using the current client + * settings. Returns an {@code AmazonEc2Reference} wrapper which should be + * released as soon as it is not required anymore. */ AmazonEc2Reference client(); /** - * Updates settings for building the client. Future client requests will use the - * new settings. Implementations SHOULD drop the client cache to prevent reusing - * the client with old settings from cache. + * Updates the settings for building the client and releases the cached one. + * Future client requests will use the new settings to lazily built the new + * client. * - * @param clientSettings - * the new settings - * @return the old settings + * @param clientSettings the new refreshed settings + * @return the old stale settings */ - Ec2ClientSettings updateClientSettings(Ec2ClientSettings clientSettings); + Ec2ClientSettings refreshAndClearCache(Ec2ClientSettings clientSettings); - /** - * Releases the cached client. Subsequent client requests will recreate the - * client instance. Does not touch the client settings. - */ - void releaseCachedClient(); } diff --git a/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/AwsEc2ServiceImpl.java b/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/AwsEc2ServiceImpl.java index 8d31ac213534e..d3aaa153711e4 100644 --- a/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/AwsEc2ServiceImpl.java +++ b/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/AwsEc2ServiceImpl.java @@ -19,6 +19,7 @@ package org.elasticsearch.discovery.ec2; +import java.io.IOException; import java.util.Random; import com.amazonaws.ClientConfiguration; @@ -127,12 +128,12 @@ public AmazonEc2Reference client() { /** - * Reloads the settings for the AmazonEC2 client. New clients will be build - * using these. Old client is usable until released. On release it will be - * destroyed instead of being returned to the cache. + * Refreshes the settings for the AmazonEC2 client. New clients will be build + * using these new settings. Old client is usable until released. On release it + * will be destroyed instead of being returned to the cache. */ @Override - public synchronized Ec2ClientSettings updateClientSettings(Ec2ClientSettings clientSettings) { + public synchronized Ec2ClientSettings refreshAndClearCache(Ec2ClientSettings clientSettings) { // shutdown all unused clients // others will shutdown on their respective release releaseCachedClient(); @@ -142,7 +143,11 @@ public synchronized Ec2ClientSettings updateClientSettings(Ec2ClientSettings cli } @Override - public synchronized void releaseCachedClient() { + public void close() { + releaseCachedClient(); + } + + private synchronized void releaseCachedClient() { if (this.clientReference == null) { return; } @@ -154,4 +159,5 @@ public synchronized void releaseCachedClient() { // it will be restarted on new client usage IdleConnectionReaper.shutdown(); } + } diff --git a/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/Ec2DiscoveryPlugin.java b/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/Ec2DiscoveryPlugin.java index 4b86263b9ad55..9fc32ea306c0e 100644 --- a/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/Ec2DiscoveryPlugin.java +++ b/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/Ec2DiscoveryPlugin.java @@ -31,7 +31,7 @@ import org.elasticsearch.node.Node; import org.elasticsearch.plugins.DiscoveryPlugin; import org.elasticsearch.plugins.Plugin; -import org.elasticsearch.plugins.ReInitializablePlugin; +import org.elasticsearch.plugins.ReloadablePlugin; import org.elasticsearch.transport.TransportService; import java.io.BufferedReader; @@ -50,7 +50,7 @@ import java.util.Map; import java.util.function.Supplier; -public class Ec2DiscoveryPlugin extends Plugin implements DiscoveryPlugin, ReInitializablePlugin { +public class Ec2DiscoveryPlugin extends Plugin implements DiscoveryPlugin, ReloadablePlugin { private static Logger logger = Loggers.getLogger(Ec2DiscoveryPlugin.class); public static final String EC2 = "ec2"; @@ -85,7 +85,7 @@ protected Ec2DiscoveryPlugin(Settings settings, AwsEc2ServiceImpl ec2Service) { this.settings = settings; this.ec2Service = ec2Service; // eagerly load client settings when secure settings are accessible - reinit(settings); + reload(settings); } @Override @@ -172,14 +172,13 @@ static Settings getAvailabilityZoneNodeAttributes(Settings settings, String azMe @Override public void close() throws IOException { - ec2Service.releaseCachedClient(); + ec2Service.close(); } @Override - public boolean reinit(Settings settings) { + public void reload(Settings settings) { // secure settings should be readable final Ec2ClientSettings clientSettings = Ec2ClientSettings.getClientSettings(settings); - ec2Service.updateClientSettings(clientSettings); - return true; + ec2Service.refreshAndClearCache(clientSettings); } } diff --git a/plugins/discovery-ec2/src/test/java/org/elasticsearch/discovery/ec2/Ec2DiscoveryPluginTests.java b/plugins/discovery-ec2/src/test/java/org/elasticsearch/discovery/ec2/Ec2DiscoveryPluginTests.java index 87754cc8f9af6..5db5bf84ab936 100644 --- a/plugins/discovery-ec2/src/test/java/org/elasticsearch/discovery/ec2/Ec2DiscoveryPluginTests.java +++ b/plugins/discovery-ec2/src/test/java/org/elasticsearch/discovery/ec2/Ec2DiscoveryPluginTests.java @@ -135,7 +135,7 @@ public void testClientSettingsReInit() throws IOException { assertThat(((AmazonEc2Mock) clientReference.client()).configuration.getProxyPort(), is(881)); assertThat(((AmazonEc2Mock) clientReference.client()).endpoint, is("ec2_endpoint_1")); // reload secure settings2 - plugin.reinit(settings2); + plugin.reload(settings2); // client is not released, it is still using the old settings assertThat(((AmazonEc2Mock) clientReference.client()).credentials.getCredentials().getAWSAccessKeyId(), is("ec2_access_1")); assertThat(((AmazonEc2Mock) clientReference.client()).credentials.getCredentials().getAWSSecretKey(), is("ec2_secret_1")); diff --git a/plugins/repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureBlobStore.java b/plugins/repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureBlobStore.java index 56ae5b6af31ba..8384ff5943f5d 100644 --- a/plugins/repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureBlobStore.java +++ b/plugins/repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureBlobStore.java @@ -54,9 +54,9 @@ public AzureBlobStore(RepositoryMetaData metadata, Settings settings, AzureStora this.service = service; // locationMode is set per repository, not per client this.locationMode = Repository.LOCATION_MODE_SETTING.get(metadata.settings()); - final Map prevSettings = this.service.updateClientsSettings(emptyMap()); + final Map prevSettings = this.service.refreshAndClearCache(emptyMap()); final Map newSettings = AzureStorageSettings.overrideLocationMode(prevSettings, this.locationMode); - this.service.updateClientsSettings(newSettings); + this.service.refreshAndClearCache(newSettings); } @Override diff --git a/plugins/repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureRepositoryPlugin.java b/plugins/repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureRepositoryPlugin.java index eb92fd198c570..1c53422e1902b 100644 --- a/plugins/repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureRepositoryPlugin.java +++ b/plugins/repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureRepositoryPlugin.java @@ -24,7 +24,7 @@ import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.env.Environment; import org.elasticsearch.plugins.Plugin; -import org.elasticsearch.plugins.ReInitializablePlugin; +import org.elasticsearch.plugins.ReloadablePlugin; import org.elasticsearch.plugins.RepositoryPlugin; import org.elasticsearch.repositories.Repository; import java.util.Arrays; @@ -35,7 +35,7 @@ /** * A plugin to add a repository type that writes to and from the Azure cloud storage service. */ -public class AzureRepositoryPlugin extends Plugin implements RepositoryPlugin, ReInitializablePlugin { +public class AzureRepositoryPlugin extends Plugin implements RepositoryPlugin, ReloadablePlugin { // protected for testing final AzureStorageService azureStoreService; @@ -65,10 +65,9 @@ public List> getSettings() { } @Override - public boolean reinit(Settings settings) { + public void reload(Settings settings) { // secure settings should be readable final Map clientsSettings = AzureStorageSettings.load(settings); - azureStoreService.updateClientsSettings(clientsSettings); - return true; + azureStoreService.refreshAndClearCache(clientsSettings); } } diff --git a/plugins/repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureStorageService.java b/plugins/repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureStorageService.java index dd832bcb80de1..d0a167993f868 100644 --- a/plugins/repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureStorageService.java +++ b/plugins/repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureStorageService.java @@ -50,14 +50,13 @@ public interface AzureStorageService { Tuple> client(String clientName); /** - * Updates settings for building clients. Future client requests will use the - * new settings. + * Updates settings for building clients. Any client cache is cleared. Future + * client requests will use the new refreshed settings. * - * @param clientsSettings - * the new settings + * @param clientsSettings the settings for new clients * @return the old settings */ - Map updateClientsSettings(Map clientsSettings); + Map refreshAndClearCache(Map clientsSettings); ByteSizeValue MIN_CHUNK_SIZE = new ByteSizeValue(1, ByteSizeUnit.BYTES); ByteSizeValue MAX_CHUNK_SIZE = new ByteSizeValue(64, ByteSizeUnit.MB); diff --git a/plugins/repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureStorageServiceImpl.java b/plugins/repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureStorageServiceImpl.java index 734ba4f7fa1af..dbd2fab64da86 100644 --- a/plugins/repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureStorageServiceImpl.java +++ b/plugins/repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureStorageServiceImpl.java @@ -61,7 +61,7 @@ public AzureStorageServiceImpl(Settings settings) { super(settings); // eagerly load client settings so that secure settings are read final Map clientsSettings = AzureStorageSettings.load(settings); - updateClientsSettings(clientsSettings); + refreshAndClearCache(clientsSettings); } @Override @@ -107,7 +107,7 @@ protected OperationContext buildOperationContext(AzureStorageSettings azureStora } @Override - public Map updateClientsSettings(Map clientsSettings) { + public Map refreshAndClearCache(Map clientsSettings) { final Map prevSettings = this.storageSettings; this.storageSettings = MapBuilder.newMapBuilder(clientsSettings).immutableMap(); // clients are built lazily by {@link client(String)} diff --git a/plugins/repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureStorageSettings.java b/plugins/repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureStorageSettings.java index 6423fc1ce3c17..42676f56bf70c 100644 --- a/plugins/repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureStorageSettings.java +++ b/plugins/repository-azure/src/main/java/org/elasticsearch/repositories/azure/AzureStorageSettings.java @@ -185,7 +185,7 @@ public String toString() { } /** - * Parses settings and read all settings available under azure.client.* + * Parse and read all settings available under the azure.client.* namespace * @param settings settings to parse * @return All the named configurations */ diff --git a/plugins/repository-azure/src/test/java/org/elasticsearch/repositories/azure/AzureStorageServiceMock.java b/plugins/repository-azure/src/test/java/org/elasticsearch/repositories/azure/AzureStorageServiceMock.java index 2de20c1babb82..880b921afe55a 100644 --- a/plugins/repository-azure/src/test/java/org/elasticsearch/repositories/azure/AzureStorageServiceMock.java +++ b/plugins/repository-azure/src/test/java/org/elasticsearch/repositories/azure/AzureStorageServiceMock.java @@ -212,7 +212,7 @@ public Tuple> client(String clientNa } @Override - public Map updateClientsSettings(Map clientsSettings) { + public Map refreshAndClearCache(Map clientsSettings) { return emptyMap(); } } diff --git a/plugins/repository-azure/src/test/java/org/elasticsearch/repositories/azure/AzureStorageServiceTests.java b/plugins/repository-azure/src/test/java/org/elasticsearch/repositories/azure/AzureStorageServiceTests.java index 391f9295751e2..3dc943df1c2db 100644 --- a/plugins/repository-azure/src/test/java/org/elasticsearch/repositories/azure/AzureStorageServiceTests.java +++ b/plugins/repository-azure/src/test/java/org/elasticsearch/repositories/azure/AzureStorageServiceTests.java @@ -95,7 +95,7 @@ public void testReinitClientSettings() throws IOException { final SettingsException e1 = expectThrows(SettingsException.class, () -> azureStorageService.client("azure3")); assertThat(e1.getMessage(), is("Unable to find client with name [azure3]")); // update client settings - plugin.reinit(settings2); + plugin.reload(settings2); // old client 1 not changed assertThat(client11.getEndpoint().toString(), equalTo("https://myaccount11.blob.core.windows.net")); // new client 1 is changed @@ -122,7 +122,7 @@ public void testReinitClientEmptySettings() throws IOException { final CloudBlobClient client11 = azureStorageService.client("azure1").v1(); assertThat(client11.getEndpoint().toString(), equalTo("https://myaccount1.blob.core.windows.net")); // reinit with empty settings - final SettingsException e = expectThrows(SettingsException.class, () -> plugin.reinit(Settings.EMPTY)); + final SettingsException e = expectThrows(SettingsException.class, () -> plugin.reload(Settings.EMPTY)); assertThat(e.getMessage(), is("If you want to use an azure repository, you need to define a client configuration.")); // existing client untouched assertThat(client11.getEndpoint().toString(), equalTo("https://myaccount1.blob.core.windows.net")); @@ -145,7 +145,7 @@ public void testReinitClientWrongSettings() throws IOException { final AzureStorageServiceImpl azureStorageService = (AzureStorageServiceImpl) plugin.azureStoreService; final CloudBlobClient client11 = azureStorageService.client("azure1").v1(); assertThat(client11.getEndpoint().toString(), equalTo("https://myaccount1.blob.core.windows.net")); - plugin.reinit(settings2); + plugin.reload(settings2); // existing client untouched assertThat(client11.getEndpoint().toString(), equalTo("https://myaccount1.blob.core.windows.net")); final SettingsException e = expectThrows(SettingsException.class, () -> azureStorageService.client("azure1")); diff --git a/plugins/repository-gcs/src/main/java/org/elasticsearch/repositories/gcs/GoogleCloudStoragePlugin.java b/plugins/repository-gcs/src/main/java/org/elasticsearch/repositories/gcs/GoogleCloudStoragePlugin.java index bea572006a6d7..12e7fd26ff565 100644 --- a/plugins/repository-gcs/src/main/java/org/elasticsearch/repositories/gcs/GoogleCloudStoragePlugin.java +++ b/plugins/repository-gcs/src/main/java/org/elasticsearch/repositories/gcs/GoogleCloudStoragePlugin.java @@ -24,7 +24,7 @@ import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.env.Environment; import org.elasticsearch.plugins.Plugin; -import org.elasticsearch.plugins.ReInitializablePlugin; +import org.elasticsearch.plugins.ReloadablePlugin; import org.elasticsearch.plugins.RepositoryPlugin; import org.elasticsearch.repositories.Repository; import java.util.Arrays; @@ -32,7 +32,7 @@ import java.util.List; import java.util.Map; -public class GoogleCloudStoragePlugin extends Plugin implements RepositoryPlugin, ReInitializablePlugin { +public class GoogleCloudStoragePlugin extends Plugin implements RepositoryPlugin, ReloadablePlugin { // package-private for tests final GoogleCloudStorageService storageService; @@ -40,7 +40,7 @@ public class GoogleCloudStoragePlugin extends Plugin implements RepositoryPlugin public GoogleCloudStoragePlugin(final Settings settings) { this.storageService = createStorageService(settings); // eagerly load client settings so that secure settings are readable (not closed) - reinit(settings); + reload(settings); } // overridable for tests @@ -67,14 +67,13 @@ public List> getSettings() { } @Override - public boolean reinit(Settings settings) { + public void reload(Settings settings) { // Secure settings should be readable inside this method. Duplicate client // settings in a format (`GoogleCloudStorageClientSettings`) that does not // require for the `SecureSettings` to be open. Pass that around (the // `GoogleCloudStorageClientSettings` instance) instead of the `Settings` // instance. final Map clientsSettings = GoogleCloudStorageClientSettings.load(settings); - this.storageService.updateClientsSettings(clientsSettings); - return true; + this.storageService.refreshAndClearCache(clientsSettings); } } diff --git a/plugins/repository-gcs/src/main/java/org/elasticsearch/repositories/gcs/GoogleCloudStorageService.java b/plugins/repository-gcs/src/main/java/org/elasticsearch/repositories/gcs/GoogleCloudStorageService.java index 6aea5d20364bf..9fe78dfb9970b 100644 --- a/plugins/repository-gcs/src/main/java/org/elasticsearch/repositories/gcs/GoogleCloudStorageService.java +++ b/plugins/repository-gcs/src/main/java/org/elasticsearch/repositories/gcs/GoogleCloudStorageService.java @@ -55,7 +55,7 @@ public GoogleCloudStorageService(final Settings settings) { } /** - * Updates the client settings and clears the client cache. Subsequent calls to + * Refreshes the client settings and clears the client cache. Subsequent calls to * {@code GoogleCloudStorageService#client} will return new clients constructed * using these passed settings. * @@ -63,7 +63,7 @@ public GoogleCloudStorageService(final Settings settings) { * @return previous settings which have been substituted */ public synchronized Map - updateClientsSettings(Map clientsSettings) { + refreshAndClearCache(Map clientsSettings) { final Map prevSettings = this.clientsSettings; this.clientsSettings = MapBuilder.newMapBuilder(clientsSettings).immutableMap(); this.clientsCache = emptyMap(); diff --git a/plugins/repository-gcs/src/test/java/org/elasticsearch/repositories/gcs/GoogleCloudStorageServiceTests.java b/plugins/repository-gcs/src/test/java/org/elasticsearch/repositories/gcs/GoogleCloudStorageServiceTests.java index a85cd118175ad..0130d2c576cd5 100644 --- a/plugins/repository-gcs/src/test/java/org/elasticsearch/repositories/gcs/GoogleCloudStorageServiceTests.java +++ b/plugins/repository-gcs/src/test/java/org/elasticsearch/repositories/gcs/GoogleCloudStorageServiceTests.java @@ -64,7 +64,7 @@ public void testClientInitializer() throws Exception { .put(GoogleCloudStorageClientSettings.PROJECT_ID_SETTING.getConcreteSettingForNamespace(clientName).getKey(), projectIdName) .build(); final GoogleCloudStorageService service = new GoogleCloudStorageService(settings); - service.updateClientsSettings(GoogleCloudStorageClientSettings.load(settings)); + service.refreshAndClearCache(GoogleCloudStorageClientSettings.load(settings)); final IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> service.client("another_client")); assertThat(e.getMessage(), Matchers.startsWith("Unknown client name")); assertSettingDeprecationsAndWarnings( @@ -100,7 +100,7 @@ public void testReinitClientSettings() throws Exception { final IllegalArgumentException e1 = expectThrows(IllegalArgumentException.class, () -> storageService.client("gcs3")); assertThat(e1.getMessage(), containsString("Unknown client name [gcs3].")); // update client settings - plugin.reinit(settings2); + plugin.reload(settings2); // old client 1 not changed assertThat(client11.getOptions().getProjectId(), equalTo("project_gcs11")); // new client 1 is changed diff --git a/plugins/repository-s3/src/main/java/org/elasticsearch/repositories/s3/AwsS3Service.java b/plugins/repository-s3/src/main/java/org/elasticsearch/repositories/s3/AwsS3Service.java index 38e39747de7fa..03b06c5b1bd34 100644 --- a/plugins/repository-s3/src/main/java/org/elasticsearch/repositories/s3/AwsS3Service.java +++ b/plugins/repository-s3/src/main/java/org/elasticsearch/repositories/s3/AwsS3Service.java @@ -19,30 +19,25 @@ package org.elasticsearch.repositories.s3; +import java.io.Closeable; import java.util.Map; -interface AwsS3Service { +interface AwsS3Service extends Closeable { /** * Creates then caches an {@code AmazonS3} client using the current client - * settings. + * settings. Returns an {@code AmazonS3Reference} wrapper which has to be + * released as soon as it is not needed anymore. */ AmazonS3Reference client(String clientName); /** - * Updates settings for building clients. Future client requests will use the - * new settings. Implementations SHOULD drop the client cache to prevent reusing - * clients with old settings from cache. + * Updates settings for building clients and clears the client cache. Future + * client requests will use the new settings to lazily build new clients. * - * @param clientsSettings - * the new settings - * @return the old settings + * @param clientsSettings the new refreshed settings + * @return the old stale settings */ - Map updateClientsSettings(Map clientsSettings); + Map refreshAndClearCache(Map clientsSettings); - /** - * Releases cached clients. Subsequent client requests will recreate client - * instances. Does not touch the client settings. - */ - void releaseCachedClients(); } diff --git a/plugins/repository-s3/src/main/java/org/elasticsearch/repositories/s3/InternalAwsS3Service.java b/plugins/repository-s3/src/main/java/org/elasticsearch/repositories/s3/InternalAwsS3Service.java index 381b72bdf950c..a54320f1fbd19 100644 --- a/plugins/repository-s3/src/main/java/org/elasticsearch/repositories/s3/InternalAwsS3Service.java +++ b/plugins/repository-s3/src/main/java/org/elasticsearch/repositories/s3/InternalAwsS3Service.java @@ -34,6 +34,8 @@ import org.elasticsearch.common.collect.MapBuilder; import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.settings.Settings; + +import java.io.IOException; import java.util.Map; import static java.util.Collections.emptyMap; @@ -48,12 +50,13 @@ class InternalAwsS3Service extends AbstractComponent implements AwsS3Service { } /** - * Reloads the settings for the AmazonS3 client. New clients will be build using - * these. Old clients are usable until released. On release they will be - * destroyed contrary to being returned to the cache. + * Refreshes the settings for the AmazonS3 clients and clears the cache of + * existing clients. New clients will be build using these new settings. Old + * clients are usable until released. On release they will be destroyed instead + * to being returned to the cache. */ @Override - public synchronized Map updateClientsSettings(Map clientsSettings) { + public synchronized Map refreshAndClearCache(Map clientsSettings) { // shutdown all unused clients // others will shutdown on their respective release releaseCachedClients(); @@ -142,8 +145,7 @@ static AWSCredentialsProvider buildCredentials(Logger logger, S3ClientSettings c } } - @Override - public synchronized void releaseCachedClients() { + protected synchronized void releaseCachedClients() { // the clients will shutdown when they will not be used anymore for (final AmazonS3Reference clientReference : clientsCache.values()) { clientReference.decRef(); @@ -173,4 +175,9 @@ public void refresh() { } } + @Override + public void close() throws IOException { + releaseCachedClients(); + } + } diff --git a/plugins/repository-s3/src/main/java/org/elasticsearch/repositories/s3/S3BlobStore.java b/plugins/repository-s3/src/main/java/org/elasticsearch/repositories/s3/S3BlobStore.java index c0d89c0f8fd01..c0f61e4d07828 100644 --- a/plugins/repository-s3/src/main/java/org/elasticsearch/repositories/s3/S3BlobStore.java +++ b/plugins/repository-s3/src/main/java/org/elasticsearch/repositories/s3/S3BlobStore.java @@ -34,6 +34,7 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.ByteSizeValue; +import java.io.IOException; import java.util.ArrayList; import java.util.Locale; @@ -150,8 +151,8 @@ public void delete(BlobPath path) { } @Override - public void close() { - this.service.releaseCachedClients(); + public void close() throws IOException { + this.service.close(); } public CannedAccessControlList getCannedACL() { diff --git a/plugins/repository-s3/src/main/java/org/elasticsearch/repositories/s3/S3Repository.java b/plugins/repository-s3/src/main/java/org/elasticsearch/repositories/s3/S3Repository.java index c021ed063d8a5..9f984c4b5e362 100644 --- a/plugins/repository-s3/src/main/java/org/elasticsearch/repositories/s3/S3Repository.java +++ b/plugins/repository-s3/src/main/java/org/elasticsearch/repositories/s3/S3Repository.java @@ -192,9 +192,9 @@ class S3Repository extends BlobStoreRepository { + "store these in named clients and the elasticsearch keystore for secure settings."); final BasicAWSCredentials insecureCredentials = S3ClientSettings.loadDeprecatedCredentials(metadata.settings()); // hack, but that's ok because the whole if branch should be axed - final Map prevSettings = awsService.updateClientsSettings(S3ClientSettings.load(Settings.EMPTY)); + final Map prevSettings = awsService.refreshAndClearCache(S3ClientSettings.load(Settings.EMPTY)); final Map newSettings = S3ClientSettings.overrideCredentials(prevSettings, insecureCredentials); - awsService.updateClientsSettings(newSettings); + awsService.refreshAndClearCache(newSettings); } blobStore = new S3BlobStore(settings, awsService, clientName, bucket, serverSideEncryption, bufferSize, cannedACL, storageClass); diff --git a/plugins/repository-s3/src/main/java/org/elasticsearch/repositories/s3/S3RepositoryPlugin.java b/plugins/repository-s3/src/main/java/org/elasticsearch/repositories/s3/S3RepositoryPlugin.java index 91b4a7863f805..93561c94d2b9a 100644 --- a/plugins/repository-s3/src/main/java/org/elasticsearch/repositories/s3/S3RepositoryPlugin.java +++ b/plugins/repository-s3/src/main/java/org/elasticsearch/repositories/s3/S3RepositoryPlugin.java @@ -35,14 +35,14 @@ import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.env.Environment; import org.elasticsearch.plugins.Plugin; -import org.elasticsearch.plugins.ReInitializablePlugin; +import org.elasticsearch.plugins.ReloadablePlugin; import org.elasticsearch.plugins.RepositoryPlugin; import org.elasticsearch.repositories.Repository; /** * A plugin to add a repository type that writes to and from the AWS S3. */ -public class S3RepositoryPlugin extends Plugin implements RepositoryPlugin, ReInitializablePlugin { +public class S3RepositoryPlugin extends Plugin implements RepositoryPlugin, ReloadablePlugin { static { SpecialPermission.check(); @@ -66,7 +66,7 @@ public S3RepositoryPlugin(Settings settings) { this.awsS3Service = getAwsS3Service(settings); // eagerly load client settings so that secure settings are read final Map clientsSettings = S3ClientSettings.load(settings); - this.awsS3Service.updateClientsSettings(clientsSettings); + this.awsS3Service.refreshAndClearCache(clientsSettings); } protected S3RepositoryPlugin(AwsS3Service awsS3Service) { @@ -109,15 +109,14 @@ public List> getSettings() { } @Override - public boolean reinit(Settings settings) { + public void reload(Settings settings) { // secure settings should be readable final Map clientsSettings = S3ClientSettings.load(settings); - awsS3Service.updateClientsSettings(clientsSettings); - return true; + awsS3Service.refreshAndClearCache(clientsSettings); } @Override - public void close() { - awsS3Service.releaseCachedClients(); + public void close() throws IOException { + awsS3Service.close(); } } diff --git a/plugins/repository-s3/src/test/java/org/elasticsearch/repositories/s3/RepositoryCredentialsTests.java b/plugins/repository-s3/src/test/java/org/elasticsearch/repositories/s3/RepositoryCredentialsTests.java index c42403503e0c8..f3bd894977999 100644 --- a/plugins/repository-s3/src/test/java/org/elasticsearch/repositories/s3/RepositoryCredentialsTests.java +++ b/plugins/repository-s3/src/test/java/org/elasticsearch/repositories/s3/RepositoryCredentialsTests.java @@ -179,7 +179,7 @@ public void testReinitSecureCredentials() throws IOException { newSecureSettings.setString("s3.client." + clientName + ".secret_key", "new_secret_aws_secret"); final Settings newSettings = Settings.builder().setSecureSettings(newSecureSettings).build(); // reload S3 plugin settings - s3Plugin.reinit(newSettings); + s3Plugin.reload(newSettings); // check the not-yet-closed client reference still has the same credentials if (repositorySettings) { assertThat(credentials.getAWSAccessKeyId(), is("insecure_aws_key")); diff --git a/plugins/repository-s3/src/test/java/org/elasticsearch/repositories/s3/S3RepositoryTests.java b/plugins/repository-s3/src/test/java/org/elasticsearch/repositories/s3/S3RepositoryTests.java index a70088f83ea44..5c0aada66585c 100644 --- a/plugins/repository-s3/src/test/java/org/elasticsearch/repositories/s3/S3RepositoryTests.java +++ b/plugins/repository-s3/src/test/java/org/elasticsearch/repositories/s3/S3RepositoryTests.java @@ -67,12 +67,12 @@ public AmazonS3Reference client(String clientName) { } @Override - public Map updateClientsSettings(Map clientsSettings) { + public Map refreshAndClearCache(Map clientsSettings) { return Collections.emptyMap(); } @Override - public void releaseCachedClients() { + public void close() { } } diff --git a/plugins/repository-s3/src/test/java/org/elasticsearch/repositories/s3/TestAwsS3Service.java b/plugins/repository-s3/src/test/java/org/elasticsearch/repositories/s3/TestAwsS3Service.java index 85a11d722cbe7..f376f73820624 100644 --- a/plugins/repository-s3/src/test/java/org/elasticsearch/repositories/s3/TestAwsS3Service.java +++ b/plugins/repository-s3/src/test/java/org/elasticsearch/repositories/s3/TestAwsS3Service.java @@ -52,7 +52,7 @@ private AmazonS3 cachedWrapper(AmazonS3Reference clientReference) { } @Override - public synchronized void releaseCachedClients() { + protected synchronized void releaseCachedClients() { super.releaseCachedClients(); clients.clear(); } diff --git a/server/src/main/java/org/elasticsearch/action/ActionModule.java b/server/src/main/java/org/elasticsearch/action/ActionModule.java index fa3a3ff612bf6..652a58196e271 100644 --- a/server/src/main/java/org/elasticsearch/action/ActionModule.java +++ b/server/src/main/java/org/elasticsearch/action/ActionModule.java @@ -29,6 +29,8 @@ import org.elasticsearch.action.admin.cluster.node.info.NodesInfoAction; import org.elasticsearch.action.admin.cluster.node.info.TransportNodesInfoAction; import org.elasticsearch.action.admin.cluster.node.liveness.TransportLivenessAction; +import org.elasticsearch.action.admin.cluster.node.reload.NodesReloadSecureSettingsAction; +import org.elasticsearch.action.admin.cluster.node.reload.TransportNodesReloadSecureSettingsAction; import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsAction; import org.elasticsearch.action.admin.cluster.node.stats.TransportNodesStatsAction; import org.elasticsearch.action.admin.cluster.node.tasks.cancel.CancelTasksAction; @@ -39,8 +41,6 @@ import org.elasticsearch.action.admin.cluster.node.tasks.list.TransportListTasksAction; import org.elasticsearch.action.admin.cluster.node.usage.NodesUsageAction; import org.elasticsearch.action.admin.cluster.node.usage.TransportNodesUsageAction; -import org.elasticsearch.action.admin.cluster.reinit.NodesReInitAction; -import org.elasticsearch.action.admin.cluster.reinit.TransportNodesReInitAction; import org.elasticsearch.action.admin.cluster.remote.RemoteInfoAction; import org.elasticsearch.action.admin.cluster.remote.TransportRemoteInfoAction; import org.elasticsearch.action.admin.cluster.repositories.delete.DeleteRepositoryAction; @@ -239,7 +239,7 @@ import org.elasticsearch.rest.action.admin.cluster.RestPendingClusterTasksAction; import org.elasticsearch.rest.action.admin.cluster.RestPutRepositoryAction; import org.elasticsearch.rest.action.admin.cluster.RestPutStoredScriptAction; -import org.elasticsearch.rest.action.admin.cluster.RestReInitAction; +import org.elasticsearch.rest.action.admin.cluster.RestReloadSecureSettingsAction; import org.elasticsearch.rest.action.admin.cluster.RestRemoteClusterInfoAction; import org.elasticsearch.rest.action.admin.cluster.RestRestoreSnapshotAction; import org.elasticsearch.rest.action.admin.cluster.RestSnapshotsStatusAction; @@ -496,7 +496,7 @@ public void reg actions.register(ExplainAction.INSTANCE, TransportExplainAction.class); actions.register(ClearScrollAction.INSTANCE, TransportClearScrollAction.class); actions.register(RecoveryAction.INSTANCE, TransportRecoveryAction.class); - actions.register(NodesReInitAction.INSTANCE, TransportNodesReInitAction.class); + actions.register(NodesReloadSecureSettingsAction.INSTANCE, TransportNodesReloadSecureSettingsAction.class); //Indexed scripts actions.register(PutStoredScriptAction.INSTANCE, TransportPutStoredScriptAction.class); @@ -619,7 +619,7 @@ public void initRestHandlers(Supplier nodesInCluster) { registerHandler.accept(new RestRecoveryAction(settings, restController)); - registerHandler.accept(new RestReInitAction(settings, restController)); + registerHandler.accept(new RestReloadSecureSettingsAction(settings, restController)); // Scripts API registerHandler.accept(new RestGetStoredScriptAction(settings, restController)); diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/reinit/NodesReInitAction.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/node/reload/NodesReloadSecureSettingsAction.java similarity index 55% rename from server/src/main/java/org/elasticsearch/action/admin/cluster/reinit/NodesReInitAction.java rename to server/src/main/java/org/elasticsearch/action/admin/cluster/node/reload/NodesReloadSecureSettingsAction.java index 0bfe3d08604af..705756e6a628b 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/reinit/NodesReInitAction.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/node/reload/NodesReloadSecureSettingsAction.java @@ -17,27 +17,28 @@ * under the License. */ -package org.elasticsearch.action.admin.cluster.reinit; +package org.elasticsearch.action.admin.cluster.node.reload; import org.elasticsearch.action.Action; import org.elasticsearch.client.ElasticsearchClient; -public class NodesReInitAction extends Action { +public class NodesReloadSecureSettingsAction + extends Action { - public static final NodesReInitAction INSTANCE = new NodesReInitAction(); - public static final String NAME = "cluster:admin/reinit"; + public static final NodesReloadSecureSettingsAction INSTANCE = new NodesReloadSecureSettingsAction(); + public static final String NAME = "cluster:admin/nodes/reload_secure_settings"; - private NodesReInitAction() { + private NodesReloadSecureSettingsAction() { super(NAME); } @Override - public NodesReInitResponse newResponse() { - return new NodesReInitResponse(); + public NodesReloadSecureSettingsResponse newResponse() { + return new NodesReloadSecureSettingsResponse(); } @Override - public NodesReInitRequestBuilder newRequestBuilder(ElasticsearchClient client) { - return new NodesReInitRequestBuilder(client, this); + public NodesReloadSecureSettingsRequestBuilder newRequestBuilder(ElasticsearchClient client) { + return new NodesReloadSecureSettingsRequestBuilder(client, this); } } diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/reinit/NodesReInitRequest.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/node/reload/NodesReloadSecureSettingsRequest.java similarity index 54% rename from server/src/main/java/org/elasticsearch/action/admin/cluster/reinit/NodesReInitRequest.java rename to server/src/main/java/org/elasticsearch/action/admin/cluster/node/reload/NodesReloadSecureSettingsRequest.java index 18b2bc6792017..e3a9229893ed2 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/reinit/NodesReInitRequest.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/node/reload/NodesReloadSecureSettingsRequest.java @@ -17,62 +17,69 @@ * under the License. */ -package org.elasticsearch.action.admin.cluster.reinit; +package org.elasticsearch.action.admin.cluster.node.reload; import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.action.support.nodes.BaseNodesRequest; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; - import java.io.IOException; import static org.elasticsearch.action.ValidateActions.addValidationError; /** - * Request for an update cluster settings action + * Request for a reload secure settings action */ -public class NodesReInitRequest extends BaseNodesRequest { +public class NodesReloadSecureSettingsRequest extends BaseNodesRequest { - private String secureStorePassword; + /** + * The password which is broadcasted to all nodes, but is never stored on + * persistent storage. The password is used to reread and decrypt the contents + * of the node's keystore (backing the implementation of + * {@code SecureSettings}). + */ + private String secureSettingsPassword; - public NodesReInitRequest() { + public NodesReloadSecureSettingsRequest() { } /** - * Get usage from nodes based on the nodes ids specified. If none are - * passed, usage for all nodes will be returned. + * Reload secure settings only on certain nodes, based on the nodes ids + * specified. If none are passed, secure settings will be reloaded on all the + * nodes. */ - public NodesReInitRequest(String... nodesIds) { + public NodesReloadSecureSettingsRequest(String... nodesIds) { super(nodesIds); } @Override public ActionRequestValidationException validate() { ActionRequestValidationException validationException = null; - if (secureStorePassword == null) { - validationException = addValidationError("secure store password cannot be null (use empty string).", validationException); + if (secureSettingsPassword == null) { + validationException = addValidationError("secure settings password cannot be null (use empty string instead)", + validationException); } return validationException; } - public String secureStorePassword() { - return secureStorePassword; + public String secureSettingsPassword() { + return secureSettingsPassword; } - public NodesReInitRequest secureStorePassword(String secureStorePassword) { - this.secureStorePassword = secureStorePassword; + public NodesReloadSecureSettingsRequest secureStorePassword(String secureStorePassword) { + this.secureSettingsPassword = secureStorePassword; return this; } @Override public void readFrom(StreamInput in) throws IOException { super.readFrom(in); - secureStorePassword = in.readString(); + secureSettingsPassword = in.readString(); } @Override public void writeTo(StreamOutput out) throws IOException { super.writeTo(out); - out.writeString(secureStorePassword); + out.writeString(secureSettingsPassword); } } diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/reinit/NodesReInitRequestBuilder.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/node/reload/NodesReloadSecureSettingsRequestBuilder.java similarity index 62% rename from server/src/main/java/org/elasticsearch/action/admin/cluster/reinit/NodesReInitRequestBuilder.java rename to server/src/main/java/org/elasticsearch/action/admin/cluster/node/reload/NodesReloadSecureSettingsRequestBuilder.java index 95c5eef90abc8..fbf0d86d7c52c 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/reinit/NodesReInitRequestBuilder.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/node/reload/NodesReloadSecureSettingsRequestBuilder.java @@ -17,26 +17,22 @@ * under the License. */ -package org.elasticsearch.action.admin.cluster.reinit; +package org.elasticsearch.action.admin.cluster.node.reload; import org.elasticsearch.action.support.nodes.NodesOperationRequestBuilder; import org.elasticsearch.client.ElasticsearchClient; - /** - * Builder for a cluster update settings request + * Builder for the reload secure settings nodes request */ -public class NodesReInitRequestBuilder - extends NodesOperationRequestBuilder { +public class NodesReloadSecureSettingsRequestBuilder extends NodesOperationRequestBuilder { - public NodesReInitRequestBuilder(ElasticsearchClient client, NodesReInitAction action) { - super(client, action, new NodesReInitRequest()); + public NodesReloadSecureSettingsRequestBuilder(ElasticsearchClient client, NodesReloadSecureSettingsAction action) { + super(client, action, new NodesReloadSecureSettingsRequest()); } - /** - * Sets the transient settings to be updated. They will not survive a full cluster restart - */ - public NodesReInitRequestBuilder setSecureStorePassword(String secureStorePassword) { + public NodesReloadSecureSettingsRequestBuilder setSecureStorePassword(String secureStorePassword) { request.secureStorePassword(secureStorePassword); return this; } diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/reinit/NodesReInitResponse.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/node/reload/NodesReloadSecureSettingsResponse.java similarity index 52% rename from server/src/main/java/org/elasticsearch/action/admin/cluster/reinit/NodesReInitResponse.java rename to server/src/main/java/org/elasticsearch/action/admin/cluster/node/reload/NodesReloadSecureSettingsResponse.java index 3386906f83333..394b1f10dc2d9 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/reinit/NodesReInitResponse.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/node/reload/NodesReloadSecureSettingsResponse.java @@ -17,8 +17,9 @@ * under the License. */ -package org.elasticsearch.action.admin.cluster.reinit; +package org.elasticsearch.action.admin.cluster.node.reload; +import org.elasticsearch.ElasticsearchException; import org.elasticsearch.action.FailedNodeException; import org.elasticsearch.action.support.nodes.BaseNodeResponse; import org.elasticsearch.action.support.nodes.BaseNodesResponse; @@ -30,42 +31,47 @@ import org.elasticsearch.common.xcontent.ToXContentFragment; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; - import java.io.IOException; import java.util.List; /** - * A response for a cluster update settings action. + * The response for the reload secure settings action */ -public class NodesReInitResponse extends BaseNodesResponse implements ToXContentFragment { +public class NodesReloadSecureSettingsResponse extends BaseNodesResponse + implements ToXContentFragment { - public NodesReInitResponse() { + public NodesReloadSecureSettingsResponse() { } - public NodesReInitResponse(ClusterName clusterName, List nodes, List failures) { + public NodesReloadSecureSettingsResponse(ClusterName clusterName, List nodes, List failures) { super(clusterName, nodes, failures); } @Override - protected List readNodesFrom(StreamInput in) throws IOException { + protected List readNodesFrom(StreamInput in) throws IOException { return in.readList(NodeResponse::readNodeResponse); } @Override - protected void writeNodesTo(StreamOutput out, List nodes) throws IOException { + protected void writeNodesTo(StreamOutput out, List nodes) throws IOException { out.writeStreamableList(nodes); } @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject("nodes"); - for (final NodesReInitResponse.NodeResponse node : getNodes()) { + for (final NodesReloadSecureSettingsResponse.NodeResponse node : getNodes()) { builder.startObject(node.getNode().getId()); builder.field("name", node.getNode().getName()); + final Exception e = node.reloadException(); + if (e != null) { + builder.startObject("reload_exception"); + ElasticsearchException.generateThrowableXContent(builder, params, e); + builder.endObject(); + } builder.endObject(); } builder.endObject(); - return builder; } @@ -84,11 +90,54 @@ public String toString() { public static class NodeResponse extends BaseNodeResponse { + private Exception reloadException = null; + public NodeResponse() { } - public NodeResponse(DiscoveryNode node) { + public NodeResponse(DiscoveryNode node, Exception reloadException) { super(node); + this.reloadException = reloadException; + } + + public Exception reloadException() { + return this.reloadException; + } + + @Override + public void readFrom(StreamInput in) throws IOException { + super.readFrom(in); + if (in.readBoolean()) { + reloadException = in.readException(); + } + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + if (reloadException != null) { + out.writeBoolean(true); + out.writeException(reloadException); + } else { + out.writeBoolean(false); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final NodesReloadSecureSettingsResponse.NodeResponse that = (NodesReloadSecureSettingsResponse.NodeResponse) o; + return reloadException != null ? reloadException.equals(that.reloadException) : that.reloadException == null; + } + + @Override + public int hashCode() { + return reloadException != null ? reloadException.hashCode() : 0; } public static NodeResponse readNodeResponse(StreamInput in) throws IOException { diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/node/reload/TransportNodesReloadSecureSettingsAction.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/node/reload/TransportNodesReloadSecureSettingsAction.java new file mode 100644 index 0000000000000..5e8cb306d497d --- /dev/null +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/node/reload/TransportNodesReloadSecureSettingsAction.java @@ -0,0 +1,144 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.action.admin.cluster.node.reload; + +import org.elasticsearch.ExceptionsHelper; +import org.elasticsearch.action.FailedNodeException; +import org.elasticsearch.action.support.ActionFilters; +import org.elasticsearch.action.support.nodes.BaseNodeRequest; +import org.elasticsearch.action.support.nodes.TransportNodesAction; +import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; +import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.settings.KeyStoreWrapper; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.env.Environment; +import org.elasticsearch.plugins.PluginsService; +import org.elasticsearch.plugins.ReloadablePlugin; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.transport.TransportService; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class TransportNodesReloadSecureSettingsAction extends TransportNodesAction { + + private final Environment environment; + private final PluginsService pluginsService; + + @Inject + public TransportNodesReloadSecureSettingsAction(Settings settings, ThreadPool threadPool, ClusterService clusterService, + TransportService transportService, ActionFilters actionFilters, + IndexNameExpressionResolver indexNameExpressionResolver, Environment environment, + PluginsService pluginService) { + super(settings, NodesReloadSecureSettingsAction.NAME, threadPool, clusterService, transportService, actionFilters, + indexNameExpressionResolver, NodesReloadSecureSettingsRequest::new, NodeRequest::new, ThreadPool.Names.GENERIC, + NodesReloadSecureSettingsResponse.NodeResponse.class); + this.environment = environment; + this.pluginsService = pluginService; + } + + @Override + protected NodesReloadSecureSettingsResponse newResponse(NodesReloadSecureSettingsRequest request, + List responses, + List failures) { + return new NodesReloadSecureSettingsResponse(clusterService.getClusterName(), responses, failures); + } + + @Override + protected NodeRequest newNodeRequest(String nodeId, NodesReloadSecureSettingsRequest request) { + return new NodeRequest(nodeId, request); + } + + @Override + protected NodesReloadSecureSettingsResponse.NodeResponse newNodeResponse() { + return new NodesReloadSecureSettingsResponse.NodeResponse(); + } + + @Override + protected NodesReloadSecureSettingsResponse.NodeResponse nodeOperation(NodeRequest nodeReloadRequest) { + final NodesReloadSecureSettingsRequest request = nodeReloadRequest.request; + KeyStoreWrapper keystore = null; + try { + // reread keystore from config file + keystore = KeyStoreWrapper.load(environment.configFile()); + if (keystore == null) { + return new NodesReloadSecureSettingsResponse.NodeResponse(clusterService.localNode(), + new IllegalStateException("Keystore is missing")); + } + // decrypt the keystore using the password from the request + keystore.decrypt(request.secureSettingsPassword().toCharArray()); + // add the keystore to the original node settings object + final Settings settingsWithKeystore = Settings.builder() + .put(environment.settings(), false) + .setSecureSettings(keystore) + .build(); + final List exceptions = new ArrayList<>(); + // broadcast the new settings object (with the open embedded keystore) to all reloadable plugins + pluginsService.filterPlugins(ReloadablePlugin.class).stream().forEach(p -> { + try { + p.reload(settingsWithKeystore); + } catch (final Exception e) { + exceptions.add(e); + } + }); + ExceptionsHelper.rethrowAndSuppress(exceptions); + return new NodesReloadSecureSettingsResponse.NodeResponse(clusterService.localNode(), null); + } catch (final Exception e) { + return new NodesReloadSecureSettingsResponse.NodeResponse(clusterService.localNode(), e); + } finally { + if (keystore != null) { + keystore.close(); + } + } + } + + public static class NodeRequest extends BaseNodeRequest { + + NodesReloadSecureSettingsRequest request; + + public NodeRequest() { + } + + NodeRequest(String nodeId, NodesReloadSecureSettingsRequest request) { + super(nodeId); + this.request = request; + } + + @Override + public void readFrom(StreamInput in) throws IOException { + super.readFrom(in); + request = new NodesReloadSecureSettingsRequest(); + request.readFrom(in); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + request.writeTo(out); + } + } +} diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/reinit/TransportNodesReInitAction.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/reinit/TransportNodesReInitAction.java deleted file mode 100644 index 1ae53f0da1e2f..0000000000000 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/reinit/TransportNodesReInitAction.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Licensed to Elasticsearch under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.elasticsearch.action.admin.cluster.reinit; - -import org.elasticsearch.action.FailedNodeException; -import org.elasticsearch.action.support.ActionFilters; -import org.elasticsearch.action.support.nodes.BaseNodeRequest; -import org.elasticsearch.action.support.nodes.TransportNodesAction; -import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; -import org.elasticsearch.cluster.service.ClusterService; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.common.settings.KeyStoreWrapper; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.env.Environment; -import org.elasticsearch.plugins.PluginsService; -import org.elasticsearch.plugins.ReInitializablePlugin; -import org.elasticsearch.threadpool.ThreadPool; -import org.elasticsearch.transport.TransportService; - -import java.io.IOException; -import java.security.GeneralSecurityException; -import java.util.List; - -public class TransportNodesReInitAction extends TransportNodesAction { - - private final Environment environment; - private final PluginsService pluginsService; - - @Inject - public TransportNodesReInitAction(Settings settings, ThreadPool threadPool, ClusterService clusterService, - TransportService transportService, ActionFilters actionFilters, - IndexNameExpressionResolver indexNameExpressionResolver, Environment environment, - PluginsService pluginService) { - super(settings, NodesReInitAction.NAME, threadPool, clusterService, transportService, actionFilters, indexNameExpressionResolver, - NodesReInitRequest::new, NodeRequest::new, ThreadPool.Names.MANAGEMENT, NodesReInitResponse.NodeResponse.class); - this.environment = environment; - this.pluginsService = pluginService; - } - - @Override - protected NodesReInitResponse newResponse(NodesReInitRequest request, List responses, - List failures) { - return new NodesReInitResponse(clusterService.getClusterName(), responses, failures); - } - - @Override - protected TransportNodesReInitAction.NodeRequest newNodeRequest(String nodeId, NodesReInitRequest request) { - return new NodeRequest(nodeId, request); - } - - @Override - protected NodesReInitResponse.NodeResponse newNodeResponse() { - return new NodesReInitResponse.NodeResponse(); - } - - @Override - protected NodesReInitResponse.NodeResponse nodeOperation(TransportNodesReInitAction.NodeRequest nodeStatsRequest) { - final NodesReInitRequest request = nodeStatsRequest.request; - // open keystore - KeyStoreWrapper keystore = null; - try { - keystore = KeyStoreWrapper.load(environment.configFile()); - keystore.decrypt(new char[0] /* use password from request */); - } catch (GeneralSecurityException | IOException e) { - throw new RuntimeException(e); - } finally { - if (keystore != null) { - keystore.close(); - } - } - - final Settings.Builder builder = Settings.builder().put(environment.settings(), false); - builder.setSecureSettings(keystore); - - final boolean success = pluginsService.filterPlugins(ReInitializablePlugin.class).stream() - .map(p -> p.reinit(builder.build())).allMatch(e -> e == true); - - return new NodesReInitResponse.NodeResponse(clusterService.localNode()); - } - - public static class NodeRequest extends BaseNodeRequest { - - NodesReInitRequest request; - - public NodeRequest() { - } - - NodeRequest(String nodeId, NodesReInitRequest request) { - super(nodeId); - this.request = request; - } - - @Override - public void readFrom(StreamInput in) throws IOException { - super.readFrom(in); - request = new NodesReInitRequest(); - request.readFrom(in); - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - request.writeTo(out); - } - } -} diff --git a/server/src/main/java/org/elasticsearch/client/ClusterAdminClient.java b/server/src/main/java/org/elasticsearch/client/ClusterAdminClient.java index 147a24976babb..949b0110fff20 100644 --- a/server/src/main/java/org/elasticsearch/client/ClusterAdminClient.java +++ b/server/src/main/java/org/elasticsearch/client/ClusterAdminClient.java @@ -33,6 +33,7 @@ import org.elasticsearch.action.admin.cluster.node.info.NodesInfoRequest; import org.elasticsearch.action.admin.cluster.node.info.NodesInfoRequestBuilder; import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse; +import org.elasticsearch.action.admin.cluster.node.reload.NodesReloadSecureSettingsRequestBuilder; import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsRequest; import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsRequestBuilder; import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsResponse; @@ -48,7 +49,6 @@ import org.elasticsearch.action.admin.cluster.node.usage.NodesUsageRequest; import org.elasticsearch.action.admin.cluster.node.usage.NodesUsageRequestBuilder; import org.elasticsearch.action.admin.cluster.node.usage.NodesUsageResponse; -import org.elasticsearch.action.admin.cluster.reinit.NodesReInitRequestBuilder; import org.elasticsearch.action.admin.cluster.repositories.delete.DeleteRepositoryRequest; import org.elasticsearch.action.admin.cluster.repositories.delete.DeleteRepositoryRequestBuilder; import org.elasticsearch.action.admin.cluster.repositories.delete.DeleteRepositoryResponse; @@ -189,7 +189,7 @@ public interface ClusterAdminClient extends ElasticsearchClient { /** * Re initialize each cluster node and pass them the secret store password. */ - NodesReInitRequestBuilder prepareReInit(); + NodesReloadSecureSettingsRequestBuilder prepareReloadSecureSettings(); /** * Reroutes allocation of shards. Advance API. diff --git a/server/src/main/java/org/elasticsearch/client/support/AbstractClient.java b/server/src/main/java/org/elasticsearch/client/support/AbstractClient.java index 0c140580a0126..dc70da4e61f7e 100644 --- a/server/src/main/java/org/elasticsearch/client/support/AbstractClient.java +++ b/server/src/main/java/org/elasticsearch/client/support/AbstractClient.java @@ -41,6 +41,8 @@ import org.elasticsearch.action.admin.cluster.node.info.NodesInfoRequest; import org.elasticsearch.action.admin.cluster.node.info.NodesInfoRequestBuilder; import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse; +import org.elasticsearch.action.admin.cluster.node.reload.NodesReloadSecureSettingsAction; +import org.elasticsearch.action.admin.cluster.node.reload.NodesReloadSecureSettingsRequestBuilder; import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsAction; import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsRequest; import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsRequestBuilder; @@ -61,8 +63,6 @@ import org.elasticsearch.action.admin.cluster.node.usage.NodesUsageRequest; import org.elasticsearch.action.admin.cluster.node.usage.NodesUsageRequestBuilder; import org.elasticsearch.action.admin.cluster.node.usage.NodesUsageResponse; -import org.elasticsearch.action.admin.cluster.reinit.NodesReInitAction; -import org.elasticsearch.action.admin.cluster.reinit.NodesReInitRequestBuilder; import org.elasticsearch.action.admin.cluster.repositories.delete.DeleteRepositoryAction; import org.elasticsearch.action.admin.cluster.repositories.delete.DeleteRepositoryRequest; import org.elasticsearch.action.admin.cluster.repositories.delete.DeleteRepositoryRequestBuilder; @@ -774,8 +774,8 @@ public ClusterUpdateSettingsRequestBuilder prepareUpdateSettings() { } @Override - public NodesReInitRequestBuilder prepareReInit() { - return new NodesReInitRequestBuilder(this, NodesReInitAction.INSTANCE); + public NodesReloadSecureSettingsRequestBuilder prepareReloadSecureSettings() { + return new NodesReloadSecureSettingsRequestBuilder(this, NodesReloadSecureSettingsAction.INSTANCE); } @Override diff --git a/server/src/main/java/org/elasticsearch/plugins/Plugin.java b/server/src/main/java/org/elasticsearch/plugins/Plugin.java index 0ef703448b799..65d47682a95c0 100644 --- a/server/src/main/java/org/elasticsearch/plugins/Plugin.java +++ b/server/src/main/java/org/elasticsearch/plugins/Plugin.java @@ -74,6 +74,7 @@ *
  • {@link RepositoryPlugin} *
  • {@link ScriptPlugin} *
  • {@link SearchPlugin} + *
  • {@link ReloadablePlugin} * *

    In addition to extension points this class also declares some {@code @Deprecated} {@code public final void onModule} methods. These * methods should cause any extensions of {@linkplain Plugin} that used the pre-5.x style extension syntax to fail to build and point the diff --git a/server/src/main/java/org/elasticsearch/plugins/ReInitializablePlugin.java b/server/src/main/java/org/elasticsearch/plugins/ReInitializablePlugin.java deleted file mode 100644 index 8295305a97110..0000000000000 --- a/server/src/main/java/org/elasticsearch/plugins/ReInitializablePlugin.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Licensed to Elasticsearch under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.elasticsearch.plugins; - -import org.elasticsearch.common.settings.Settings; - -public interface ReInitializablePlugin { - boolean reinit(Settings settings); -} diff --git a/server/src/main/java/org/elasticsearch/plugins/ReloadablePlugin.java b/server/src/main/java/org/elasticsearch/plugins/ReloadablePlugin.java new file mode 100644 index 0000000000000..86d7759185e69 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/plugins/ReloadablePlugin.java @@ -0,0 +1,54 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.plugins; + +import org.elasticsearch.common.settings.Settings; + +/** + * An extension point for {@link Plugin}s that can be reloaded. There is no + * clear definition about what reloading a plugin actually means. When a plugin + * is reloaded it might rebuild any internal members. Plugins usually implement + * this interface in order to reread the values of {@code SecureSetting}s and + * then rebuild any dependent internal members. + */ +public interface ReloadablePlugin { + /** + * Called to trigger the rebuilt of the plugin's internal members. The reload + * operation is required to have been completed when the method returns. + * Strictly speaking, the settings argument should not be accessed + * outside of this method's call stack, as any values stored in the node's + * keystore (see {@code SecureSetting}) will not otherwise be retrievable. The + * setting values do not follow dynamic updates, i.e. the values are identical + * to the ones during the initial plugin loading, barring the keystore file on + * disk changes. Any failure during the operation should be signaled by raising + * an exception, but the plugin should otherwise continue to function + * unperturbed. + * + * @param settings + * Settings used while reloading the plugin. All values are + * retrievable, including the values stored in the node's keystore. + * The setting values are the initial ones, from when the node has be + * started, i.e. they don't follow dynamic updates. + * @throws Exception + * if the operation failed. The plugin should continue to operate as + * if the offending call didn't happen. + */ + void reload(Settings settings) throws Exception; +} \ No newline at end of file diff --git a/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestReInitAction.java b/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestReloadSecureSettingsAction.java similarity index 68% rename from server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestReInitAction.java rename to server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestReloadSecureSettingsAction.java index c7a5d5d809f47..4533f36dd6cfc 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestReInitAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestReloadSecureSettingsAction.java @@ -19,9 +19,7 @@ package org.elasticsearch.rest.action.admin.cluster; -import org.elasticsearch.action.admin.cluster.reinit.NodesReInitAction; -import org.elasticsearch.action.admin.cluster.reinit.NodesReInitRequest; -import org.elasticsearch.action.admin.cluster.reinit.NodesReInitResponse; +import org.elasticsearch.action.admin.cluster.node.reload.NodesReloadSecureSettingsResponse; import org.elasticsearch.client.node.NodeClient; import org.elasticsearch.common.Strings; import org.elasticsearch.common.settings.Settings; @@ -39,31 +37,32 @@ import static org.elasticsearch.rest.RestRequest.Method.POST; -public final class RestReInitAction extends BaseRestHandler { +public final class RestReloadSecureSettingsAction extends BaseRestHandler { - public RestReInitAction(Settings settings, RestController controller) { + public RestReloadSecureSettingsAction(Settings settings, RestController controller) { super(settings); - controller.registerHandler(POST, "/_nodes/reinit", this); - controller.registerHandler(POST, "/_nodes/{nodeId}/reinit", this); + controller.registerHandler(POST, "/_nodes/reload_secure_settings", this); + controller.registerHandler(POST, "/_nodes/{nodeId}/reload_secure_settings", this); } @Override public String getName() { - return "nodes_reinit_action"; + return "nodes_reload_action"; } @Override public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { final String[] nodesIds = Strings.splitStringByCommaToArray(request.param("nodeId")); - final NodesReInitRequest nodesReInitRequest = new NodesReInitRequest(nodesIds); - nodesReInitRequest.timeout(request.param("timeout")); - nodesReInitRequest.secureStorePassword(request.param("secureStorePassword", "")); - - return channel -> client.admin().cluster().execute(NodesReInitAction.INSTANCE, nodesReInitRequest, - new RestBuilderListener(channel) { - + return channel -> client.admin() + .cluster() + .prepareReloadSecureSettings() + .setTimeout(request.param("timeout")) + .setNodesIds(nodesIds) + .setSecureStorePassword(request.param("secure_settings_password", "")) + .execute(new RestBuilderListener(channel) { @Override - public RestResponse buildResponse(NodesReInitResponse response, XContentBuilder builder) throws Exception { + public RestResponse buildResponse(NodesReloadSecureSettingsResponse response, XContentBuilder builder) + throws Exception { builder.startObject(); RestActions.buildNodesHeader(builder, channel.request(), response); builder.field("cluster_name", response.getClusterName().value()); @@ -73,7 +72,6 @@ public RestResponse buildResponse(NodesReInitResponse response, XContentBuilder return new BytesRestResponse(RestStatus.OK, builder); } }); - } @Override