1818
1919package org .apache .hadoop .fs .s3a .impl ;
2020
21+ import java .io .IOException ;
2122import java .net .URI ;
2223import java .net .URISyntaxException ;
23- import java .util .Locale ;
2424import java .util .Optional ;
2525import java .util .regex .Matcher ;
2626import java .util .regex .Pattern ;
3131import org .slf4j .LoggerFactory ;
3232import software .amazon .awssdk .awscore .util .AwsHostNameUtils ;
3333import software .amazon .awssdk .regions .Region ;
34+ import software .amazon .awssdk .regions .providers .InstanceProfileRegionProvider ;
3435
3536import org .apache .hadoop .classification .VisibleForTesting ;
3637import org .apache .hadoop .conf .Configuration ;
38+ import org .apache .hadoop .fs .s3a .Invoker ;
39+ import org .apache .hadoop .fs .s3a .Retries ;
3740import org .apache .hadoop .fs .s3a .S3ClientFactory ;
3841
3942import static java .util .Objects .requireNonNull ;
@@ -98,6 +101,7 @@ public enum RegionResolutionMechanism {
98101 CalculatedFromEndpoint ("Calculated from endpoint" ),
99102 FallbackToCentral ("Fallback to central endpoint" ),
100103 ParseVpceEndpoint ("Parse VPCE Endpoint" ),
104+ Ec2Metadata ("EC2 Metadata" ),
101105 Sdk ("SDK resolution chain" ),
102106 Specified ("region specified" );
103107
@@ -141,7 +145,7 @@ public static final class Resolution {
141145 * How was the region resolved?
142146 * Null means unresolved.
143147 */
144- private RegionResolutionMechanism resolution ;
148+ private RegionResolutionMechanism mechanism ;
145149
146150 /**
147151 * Should FIPS be enabled?
@@ -168,17 +172,31 @@ public static final class Resolution {
168172 */
169173 private boolean useCentralEndpoint ;
170174
175+ public Resolution () {
176+ }
177+
178+ /**
179+ * Instantiate with a region and resolution mechanism.
180+ * @param region region
181+ * @param mechanism resolution mechanism.
182+ */
183+ public Resolution (final Region region , final RegionResolutionMechanism mechanism ) {
184+ this .region = region ;
185+ this .mechanism = mechanism ;
186+ }
187+
171188 /**
172189 * Set the region.
173190 * Declares the region as resolved even when the value is null (i.e. resolve to SDK).
174- * @param region new value
191+ * @param region region
192+ * @param resolutionMechanism resolution mechanism.
175193 * @return the builder
176194 */
177195 public Resolution withRegion (
178196 @ Nullable final Region region ,
179197 final RegionResolutionMechanism resolutionMechanism ) {
180198 this .region = region ;
181- this .resolution = requireNonNull (resolutionMechanism );
199+ this .mechanism = requireNonNull (resolutionMechanism );
182200 return this ;
183201 }
184202
@@ -238,16 +256,16 @@ public boolean isCrossRegionAccessEnabled() {
238256 return crossRegionAccessEnabled ;
239257 }
240258
241- public RegionResolutionMechanism getResolution () {
242- return resolution ;
259+ public RegionResolutionMechanism getMechanism () {
260+ return mechanism ;
243261 }
244262
245263 public String getEndpointStr () {
246264 return endpointStr ;
247265 }
248266
249267 public boolean isRegionResolved () {
250- return resolution != null ;
268+ return mechanism != null ;
251269 }
252270
253271 public boolean isUseCentralEndpoint () {
@@ -268,7 +286,7 @@ public Resolution withUseCentralEndpoint(final boolean value) {
268286 public String toString () {
269287 final StringBuilder sb = new StringBuilder ("Resolution{" );
270288 sb .append ("region=" ).append (region );
271- sb .append (", resolution=" ).append (resolution );
289+ sb .append (", resolution=" ).append (mechanism );
272290 sb .append (", useFips=" ).append (useFips );
273291 sb .append (", crossRegionAccessEnabled=" ).append (crossRegionAccessEnabled );
274292 sb .append (", endpointUri=" ).append (endpointUri );
@@ -324,34 +342,55 @@ public static Optional<Resolution> getS3RegionFromEndpoint(
324342 if (matcher .find ()) {
325343 LOG .debug ("Mapping to VPCE" );
326344 LOG .debug ("Endpoint {} is vpc endpoint; parsing region as {}" , endpoint , matcher .group (1 ));
327- return Optional .of (new Resolution ()
328- . withRegion ( Region .of (matcher .group (1 )),
329- RegionResolutionMechanism .ParseVpceEndpoint ));
345+ return Optional .of (new Resolution (
346+ Region .of (matcher .group (1 )),
347+ RegionResolutionMechanism .ParseVpceEndpoint ));
330348 }
331349
332350 LOG .debug ("Endpoint {} is not the default; parsing" , endpoint );
333351 return AwsHostNameUtils .parseSigningRegion (endpoint , S3_SERVICE_NAME )
334352 .map (r ->
335- new Resolution ().withRegion (r ,
336- RegionResolutionMechanism .CalculatedFromEndpoint ));
353+ new Resolution (r , RegionResolutionMechanism .CalculatedFromEndpoint ));
337354 }
338355
339356 // No resolution.
340357 return Optional .empty ();
341358 }
342359
360+ /**
361+ * Does the region name refer to an SDK region?
362+ * @param configuredRegion region in the configuration
363+ * @return true if this is considered to refer to an SDK region.
364+ */
365+ public static boolean isSdkRegion (String configuredRegion ) {
366+ return SDK_REGION .equalsIgnoreCase (configuredRegion )
367+ || EMPTY_REGION .equalsIgnoreCase (configuredRegion );
368+ }
369+
370+ /**
371+ * Does the region name refer to {@code "ec2"} in which case special handling
372+ * is required.
373+ * @param configuredRegion region in the configuration
374+ * @return true if this is considered to refer to an SDK region.
375+ */
376+ public static boolean isEc2Region (String configuredRegion ) {
377+ return EC2_REGION .equalsIgnoreCase (configuredRegion );
378+ }
379+
343380 /**
344381 * Calculate the region and the final endpoint.
345382 * @param parameters creation parameters
346383 * @param conf configuration with other options.
347384 * @return the resolved region and endpoint.
385+ * @throws IOException if the client failed to communicate with the IAM service.
348386 * @throws IllegalArgumentException failure to parse endpoint, or FIPS settings.
349387 */
388+ @ Retries .OnceTranslated
350389 public static Resolution calculateRegion (
351390 final S3ClientFactory .S3ClientCreationParameters parameters ,
352- final Configuration conf ) {
391+ final Configuration conf ) throws IOException {
353392
354- final Resolution resolution = new Resolution ();
393+ Resolution resolution = new Resolution ();
355394
356395 // endpoint; may be null
357396 final String endpointStr = parameters .getEndpoint ();
@@ -364,42 +403,26 @@ public static Resolution calculateRegion(
364403 // If the region was configured, set it.
365404 // this includes special handling of the sdk, ec2 and "" regions.
366405 if (configuredRegion != null ) {
367- switch (configuredRegion .toLowerCase (Locale .ROOT )) {
368- case EC2_REGION :
369- case SDK_REGION :
370- case EMPTY_REGION :
406+ checkArgument (!"null" .equals (configuredRegion ),
407+ "null is region name" );
408+ if (isSdkRegion (configuredRegion )) {
371409 resolution .withRegion (null , RegionResolutionMechanism .Sdk );
372- break ;
373-
374- default :
410+ } else if (isEc2Region (configuredRegion )) {
411+ // special EC2 handling
412+ final Resolution r = getS3RegionFromEc2IAM ();
413+ resolution .withRegion (r .getRegion (), r .getMechanism ());
414+ } else {
375415 resolution .withRegion (Region .of (configuredRegion ),
376416 RegionResolutionMechanism .Specified );
377417 }
378418 }
379419
380-
381- // cross region setting.
382- resolution .withCrossRegionAccessEnabled (
383- conf .getBoolean (AWS_S3_CROSS_REGION_ACCESS_ENABLED ,
384- AWS_S3_CROSS_REGION_ACCESS_ENABLED_DEFAULT ));
385-
386420 // central endpoint if no endpoint has been set, or it is explicitly
387421 // requested
388422 boolean endpointEndsWithCentral = endpointStr == null
389423 || endpointStr .isEmpty ()
390424 || endpointStr .endsWith (CENTRAL_ENDPOINT );
391425
392- // fips settings.
393- final boolean fipsEnabled = parameters .isFipsEnabled ();
394- resolution .withUseFips (fipsEnabled );
395- if (fipsEnabled ) {
396- // validate the FIPS settings
397- checkArgument (endpoint == null || endpointEndsWithCentral ,
398- "%s : %s" , ERROR_ENDPOINT_WITH_FIPS , endpoint );
399- checkArgument (!parameters .isPathStyleAccess (),
400- FIPS_PATH_ACCESS_INCOMPATIBLE );
401- }
402-
403426 if (!resolution .isRegionResolved ()) {
404427 // parse from the endpoint and set if calculated
405428 LOG .debug ("Falling back to parsing region endpoint {}; endpointEndsWithCentral={}" ,
@@ -409,9 +432,26 @@ public static Resolution calculateRegion(
409432 if (regionFromEndpoint .isPresent ()) {
410433 regionFromEndpoint
411434 .map (r ->
412- resolution .withRegion (r .getRegion (), r .getResolution ()));
435+ resolution .withRegion (r .getRegion (), r .getMechanism ()));
413436 }
414437 }
438+
439+ // cross region setting.
440+ resolution .withCrossRegionAccessEnabled (
441+ conf .getBoolean (AWS_S3_CROSS_REGION_ACCESS_ENABLED ,
442+ AWS_S3_CROSS_REGION_ACCESS_ENABLED_DEFAULT ));
443+
444+ // fips settings.
445+ final boolean fipsEnabled = parameters .isFipsEnabled ();
446+ resolution .withUseFips (fipsEnabled );
447+ if (fipsEnabled ) {
448+ // validate the FIPS settings
449+ checkArgument (endpoint == null || endpointEndsWithCentral ,
450+ "%s : %s" , ERROR_ENDPOINT_WITH_FIPS , endpoint );
451+ checkArgument (!parameters .isPathStyleAccess (),
452+ FIPS_PATH_ACCESS_INCOMPATIBLE );
453+ }
454+
415455 if (!resolution .isRegionResolved ()) {
416456 // still failing to resolve the region
417457 // fall back to central
@@ -442,4 +482,21 @@ public static Resolution calculateRegion(
442482 return resolution ;
443483 }
444484
485+ /**
486+ * Probes EC2 Metadata for the region.
487+ * This uses a class {@code InstanceProfileRegionProvider} which AWS
488+ * declare as for internal use only.
489+ * Linking/invocation should be caught and downgraded to returning an empty() option.
490+ * @return the region from EC2 IAM.
491+ * @throws IOException if the client failed to communicate with the IAM service.
492+ */
493+ @ VisibleForTesting
494+ @ Retries .OnceTranslated
495+ static Resolution getS3RegionFromEc2IAM () throws IOException {
496+ return Invoker .once ("Resolve EC2 Metadata" , "/" , () -> {
497+ LOG .debug ("Resolving region through EC2 Metadata" );
498+ final Region region = new InstanceProfileRegionProvider ().getRegion ();
499+ return new Resolution (region , RegionResolutionMechanism .Ec2Metadata );
500+ });
501+ }
445502}
0 commit comments