Skip to content

Commit e03f5e5

Browse files
committed
Use DefaultAwsRegionProviderChain to obtain default region
1 parent d7a4882 commit e03f5e5

File tree

8 files changed

+108
-24
lines changed

8 files changed

+108
-24
lines changed

modules/repository-s3/src/javaRestTest/java/org/elasticsearch/repositories/s3/RepositoryS3ImdsV2CredentialsRestIT.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public class RepositoryS3ImdsV2CredentialsRestIT extends AbstractRepositoryS3Res
4141

4242
private static final Ec2ImdsHttpFixture ec2ImdsHttpFixture = new Ec2ImdsHttpFixture(
4343
new Ec2ImdsServiceBuilder(Ec2ImdsVersion.V2).newCredentialsConsumer(dynamicCredentials::addValidCredentials)
44-
.instanceIdentityDocument((b, p) -> b.field("region", regionSupplier.get())) // TODO NOMERGE this region is not used
44+
.instanceIdentityDocument((b, p) -> b.field("region", regionSupplier.get()))
4545
);
4646

4747
private static final S3HttpFixture s3Fixture = new S3HttpFixture(true, BUCKET, BASE_PATH, dynamicCredentials::isAuthorized);
@@ -50,7 +50,6 @@ public class RepositoryS3ImdsV2CredentialsRestIT extends AbstractRepositoryS3Res
5050
.module("repository-s3")
5151
.setting("s3.client." + CLIENT + ".endpoint", s3Fixture::getAddress)
5252
.systemProperty(Ec2ImdsHttpFixture.ENDPOINT_OVERRIDE_SYSPROP_NAME_SDK2, ec2ImdsHttpFixture::getAddress)
53-
.systemProperty("aws.region", regionSupplier)
5453
.build();
5554

5655
@ClassRule

modules/repository-s3/src/main/java/org/elasticsearch/repositories/s3/S3RepositoryPlugin.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
package org.elasticsearch.repositories.s3;
1111

1212
import software.amazon.awssdk.regions.Region;
13+
import software.amazon.awssdk.regions.providers.DefaultAwsRegionProviderChain;
1314

1415
import org.apache.lucene.util.SetOnce;
1516
import org.elasticsearch.SpecialPermission;
@@ -20,6 +21,8 @@
2021
import org.elasticsearch.common.util.BigArrays;
2122
import org.elasticsearch.env.Environment;
2223
import org.elasticsearch.indices.recovery.RecoverySettings;
24+
import org.elasticsearch.logging.LogManager;
25+
import org.elasticsearch.logging.Logger;
2326
import org.elasticsearch.plugins.Plugin;
2427
import org.elasticsearch.plugins.ReloadablePlugin;
2528
import org.elasticsearch.plugins.RepositoryPlugin;
@@ -43,6 +46,8 @@
4346
*/
4447
public class S3RepositoryPlugin extends Plugin implements RepositoryPlugin, ReloadablePlugin {
4548

49+
private static final Logger logger = LogManager.getLogger(S3RepositoryPlugin.class);
50+
4651
static {
4752
SpecialPermission.check();
4853
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
@@ -87,11 +92,22 @@ protected S3Repository createRepository(
8792
public Collection<?> createComponents(PluginServices services) {
8893
service.set(s3Service(services.environment(), services.clusterService().getSettings(), services.resourceWatcherService()));
8994
this.service.get().refreshAndClearCache(S3ClientSettings.load(settings));
90-
return List.of(service);
95+
return List.of(service.get());
9196
}
9297

9398
S3Service s3Service(Environment environment, Settings nodeSettings, ResourceWatcherService resourceWatcherService) {
94-
return new S3Service(environment, nodeSettings, resourceWatcherService);
99+
return new S3Service(environment, nodeSettings, resourceWatcherService, S3RepositoryPlugin::getDefaultRegion);
100+
}
101+
102+
private static Region getDefaultRegion() {
103+
return AccessController.doPrivileged((PrivilegedAction<Region>) () -> {
104+
try {
105+
return DefaultAwsRegionProviderChain.builder().build().getRegion();
106+
} catch (Exception e) {
107+
logger.debug("failed to obtain region from default provider chain", e);
108+
return null;
109+
}
110+
});
95111
}
96112

97113
@Override

modules/repository-s3/src/main/java/org/elasticsearch/repositories/s3/S3Service.java

Lines changed: 63 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,11 @@
4242
import org.elasticsearch.cluster.metadata.RepositoryMetadata;
4343
import org.elasticsearch.cluster.node.DiscoveryNode;
4444
import org.elasticsearch.common.Strings;
45+
import org.elasticsearch.common.component.AbstractLifecycleComponent;
4546
import org.elasticsearch.common.settings.Setting;
4647
import org.elasticsearch.common.settings.Settings;
4748
import org.elasticsearch.common.util.Maps;
49+
import org.elasticsearch.common.util.concurrent.RunOnce;
4850
import org.elasticsearch.core.Nullable;
4951
import org.elasticsearch.core.Releasable;
5052
import org.elasticsearch.core.Releasables;
@@ -55,7 +57,6 @@
5557
import org.elasticsearch.watcher.FileWatcher;
5658
import org.elasticsearch.watcher.ResourceWatcherService;
5759

58-
import java.io.Closeable;
5960
import java.io.IOException;
6061
import java.net.URI;
6162
import java.net.URISyntaxException;
@@ -69,14 +70,14 @@
6970
import java.util.Optional;
7071
import java.util.concurrent.CompletableFuture;
7172
import java.util.function.Consumer;
73+
import java.util.function.Supplier;
7274

7375
import static java.util.Collections.emptyMap;
74-
import static software.amazon.awssdk.core.SdkSystemSetting.AWS_REGION;
7576
import static software.amazon.awssdk.core.SdkSystemSetting.AWS_ROLE_ARN;
7677
import static software.amazon.awssdk.core.SdkSystemSetting.AWS_ROLE_SESSION_NAME;
7778
import static software.amazon.awssdk.core.SdkSystemSetting.AWS_WEB_IDENTITY_TOKEN_FILE;
7879

79-
class S3Service implements Closeable {
80+
class S3Service extends AbstractLifecycleComponent {
8081
private static final Logger LOGGER = LogManager.getLogger(S3Service.class);
8182

8283
static final Setting<TimeValue> REPOSITORY_S3_CAS_TTL_SETTING = Setting.timeSetting(
@@ -108,13 +109,21 @@ class S3Service implements Closeable {
108109
*/
109110
private volatile Map<Settings, S3ClientSettings> derivedClientSettings = emptyMap();
110111

112+
private final Runnable defaultRegionSetter;
113+
private volatile Region defaultRegion;
114+
111115
final CustomWebIdentityTokenCredentialsProvider webIdentityTokenCredentialsProvider;
112116

113117
final TimeValue compareAndExchangeTimeToLive;
114118
final TimeValue compareAndExchangeAntiContentionDelay;
115119
final boolean isStateless;
116120

117-
S3Service(Environment environment, Settings nodeSettings, ResourceWatcherService resourceWatcherService) {
121+
S3Service(
122+
Environment environment,
123+
Settings nodeSettings,
124+
ResourceWatcherService resourceWatcherService,
125+
Supplier<Region> defaultRegionSupplier
126+
) {
118127
webIdentityTokenCredentialsProvider = new CustomWebIdentityTokenCredentialsProvider(
119128
environment,
120129
System::getenv,
@@ -125,6 +134,7 @@ class S3Service implements Closeable {
125134
compareAndExchangeTimeToLive = REPOSITORY_S3_CAS_TTL_SETTING.get(nodeSettings);
126135
compareAndExchangeAntiContentionDelay = REPOSITORY_S3_CAS_ANTI_CONTENTION_DELAY_SETTING.get(nodeSettings);
127136
isStateless = DiscoveryNode.isStateless(nodeSettings);
137+
defaultRegionSetter = new RunOnce(() -> defaultRegion = defaultRegionSupplier.get());
128138
}
129139

130140
/**
@@ -230,20 +240,53 @@ protected S3ClientBuilder buildClientBuilder(S3ClientSettings clientSettings, Sd
230240
if (clientSettings.pathStyleAccess) {
231241
s3clientBuilder.forcePathStyle(true);
232242
}
233-
if (Strings.hasLength(clientSettings.region)) {
234-
s3clientBuilder.region(Region.of(clientSettings.region));
235-
} else if (AWS_REGION.getStringValue().isPresent() == false) {
236-
// TODO NOMERGE: how we handle regions TBD, this allows testing to pass
237-
// TODO NOMERGE: specifically we don't pick up the region from IMDS
238-
s3clientBuilder.region(Region.of("us-east-1"));
239-
}
243+
244+
s3clientBuilder.region(getClientRegion(clientSettings));
245+
240246
if (Strings.hasLength(clientSettings.endpoint)) {
241247
s3clientBuilder.endpointOverride(URI.create(clientSettings.endpoint)); // TODO NOMERGE what if URI.create fails?
242248
}
243249

244250
return s3clientBuilder;
245251
}
246252

253+
// TODO NOMERGE test this logic
254+
private Region getClientRegion(S3ClientSettings clientSettings) {
255+
assert lifecycle.initialized() == false : lifecycle;
256+
257+
if (Strings.hasLength(clientSettings.region)) {
258+
return Region.of(clientSettings.region);
259+
}
260+
if (Strings.hasLength(clientSettings.endpoint)) {
261+
final var guessedRegion = RegionFromEndpointGuesser.guessRegion(clientSettings.endpoint);
262+
if (guessedRegion != null) {
263+
LOGGER.warn(
264+
"""
265+
found S3 client with endpoint [{}] but no configured region, guessing it should use [{}]; \
266+
to suppress this warning, configure the [{}] setting on this node""",
267+
clientSettings.endpoint,
268+
guessedRegion,
269+
S3ClientSettings.REGION.getConcreteSettingForNamespace("CLIENT_NAME").getKey()
270+
);
271+
return Region.of(guessedRegion);
272+
}
273+
}
274+
final var defaultRegion = this.defaultRegion;
275+
if (defaultRegion != null) {
276+
LOGGER.debug("""
277+
found S3 client with no configured region, using region [{}] from SDK""", defaultRegion);
278+
return defaultRegion;
279+
}
280+
LOGGER.warn(
281+
"""
282+
found S3 client with no configured region, falling back to [{}]; \
283+
to suppress this warning, configure the [{}] setting on this node""",
284+
Region.US_EAST_1,
285+
S3ClientSettings.REGION.getConcreteSettingForNamespace("CLIENT_NAME").getKey()
286+
);
287+
return Region.US_EAST_1;
288+
}
289+
247290
@Nullable // in production, but exposed for tests to override
248291
DnsResolver getCustomDnsResolver() {
249292
return null;
@@ -373,7 +416,15 @@ public void onBlobStoreClose() {
373416
}
374417

375418
@Override
376-
public void close() throws IOException {
419+
protected void doStart() {
420+
defaultRegionSetter.run();
421+
}
422+
423+
@Override
424+
protected void doStop() {}
425+
426+
@Override
427+
public void doClose() throws IOException {
377428
releaseCachedClients();
378429
webIdentityTokenCredentialsProvider.close();
379430
}

modules/repository-s3/src/test/java/org/elasticsearch/repositories/s3/RepositoryCredentialsTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ public static final class ProxyS3Service extends S3Service {
299299
private static final Logger logger = LogManager.getLogger(ProxyS3Service.class);
300300

301301
ProxyS3Service(Environment environment, Settings nodeSettings, ResourceWatcherService resourceWatcherService) {
302-
super(environment, nodeSettings, resourceWatcherService);
302+
super(environment, nodeSettings, resourceWatcherService, () -> null);
303303
}
304304

305305
@Override

modules/repository-s3/src/test/java/org/elasticsearch/repositories/s3/S3BlobContainerRetriesTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ public class S3BlobContainerRetriesTests extends AbstractBlobContainerRetriesTes
119119
@Before
120120
public void setUp() throws Exception {
121121
shouldErrorOnDns = false;
122-
service = new S3Service(Mockito.mock(Environment.class), Settings.EMPTY, Mockito.mock(ResourceWatcherService.class)) {
122+
service = new S3Service(Mockito.mock(Environment.class), Settings.EMPTY, Mockito.mock(ResourceWatcherService.class), () -> null) {
123123
private InetAddress[] resolveHost(String host) throws UnknownHostException {
124124
assertEquals("127.0.0.1", host);
125125
if (shouldErrorOnDns && randomBoolean() && randomBoolean()) {
@@ -133,6 +133,7 @@ DnsResolver getCustomDnsResolver() {
133133
return this::resolveHost;
134134
}
135135
};
136+
service.start();
136137
recordingMeterRegistry = new RecordingMeterRegistry();
137138
super.setUp();
138139
}

modules/repository-s3/src/test/java/org/elasticsearch/repositories/s3/S3ClientSettingsTests.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,14 @@ public void testRegionCanBeSet() throws IOException {
184184
assertThat(settings.get("default").region, is(""));
185185
assertThat(settings.get("other").region, is(region));
186186

187-
try (var s3Service = new S3Service(Mockito.mock(Environment.class), Settings.EMPTY, Mockito.mock(ResourceWatcherService.class))) {
187+
try (
188+
var s3Service = new S3Service(
189+
Mockito.mock(Environment.class),
190+
Settings.EMPTY,
191+
Mockito.mock(ResourceWatcherService.class),
192+
() -> null
193+
)
194+
) {
188195
// TODO NOMERGE: S3Client#getBucketLocation is supposed to be a work around to access the region: the response object includes
189196
// the region. However, we can't build a S3Client in this file, so we need to migrate it elsewhere.
190197
// "Unable to load credentials from any of the providers in the chain AwsCredentialsProviderChain"

modules/repository-s3/src/test/java/org/elasticsearch/repositories/s3/S3RepositoryTests.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public String serviceName() {
5555
private static class DummyS3Service extends S3Service {
5656

5757
DummyS3Service(Environment environment, ResourceWatcherService resourceWatcherService) {
58-
super(environment, Settings.EMPTY, resourceWatcherService);
58+
super(environment, Settings.EMPTY, resourceWatcherService, () -> null);
5959
}
6060

6161
@Override
@@ -67,7 +67,9 @@ public AmazonS3Reference client(RepositoryMetadata repositoryMetadata) {
6767
public void refreshAndClearCache(Map<String, S3ClientSettings> clientsSettings) {}
6868

6969
@Override
70-
public void close() {}
70+
public void doClose() {
71+
// nothing to clean up
72+
}
7173
}
7274

7375
public void testInvalidChunkBufferSizeSettings() {

modules/repository-s3/src/test/java/org/elasticsearch/repositories/s3/S3ServiceTests.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import software.amazon.awssdk.awscore.exception.AwsServiceException;
1313
import software.amazon.awssdk.core.retry.RetryPolicyContext;
1414
import software.amazon.awssdk.core.retry.conditions.RetryCondition;
15+
import software.amazon.awssdk.regions.Region;
1516
import software.amazon.awssdk.services.s3.model.S3Exception;
1617

1718
import org.elasticsearch.cluster.metadata.RepositoryMetadata;
@@ -29,7 +30,13 @@
2930
public class S3ServiceTests extends ESTestCase {
3031

3132
public void testCachedClientsAreReleased() throws IOException {
32-
final S3Service s3Service = new S3Service(mock(Environment.class), Settings.EMPTY, mock(ResourceWatcherService.class));
33+
final S3Service s3Service = new S3Service(
34+
mock(Environment.class),
35+
Settings.EMPTY,
36+
mock(ResourceWatcherService.class),
37+
() -> Region.of("es-test-region")
38+
);
39+
s3Service.start();
3340
final String endpointOverride = "http://first";
3441
final Settings settings = Settings.builder().put("endpoint", endpointOverride).build();
3542
final RepositoryMetadata metadata1 = new RepositoryMetadata("first", "s3", settings);
@@ -44,13 +51,14 @@ public void testCachedClientsAreReleased() throws IOException {
4451
assertEquals("es-test-region", reference.client().serviceClientConfiguration().region().toString());
4552

4653
reference.close();
47-
s3Service.close();
54+
s3Service.doClose();
4855
final AmazonS3Reference referenceReloaded = s3Service.client(metadata1);
4956
assertNotSame(referenceReloaded, reference);
5057
referenceReloaded.close();
51-
s3Service.close();
58+
s3Service.doClose();
5259
final S3ClientSettings clientSettingsReloaded = s3Service.settings(metadata1);
5360
assertNotSame(clientSettings, clientSettingsReloaded);
61+
s3Service.close();
5462
}
5563

5664
public void testRetryOn403RetryPolicy() {

0 commit comments

Comments
 (0)