1
15
16 package software.amazon.awssdk.awscore.interceptor;
17
18 import java.net.UnknownHostException;
19 import java.util.List;
20 import java.util.Optional;
21 import java.util.stream.Collectors;
22 import software.amazon.awssdk.annotations.SdkInternalApi;
23 import software.amazon.awssdk.awscore.AwsExecutionAttribute;
24 import software.amazon.awssdk.core.exception.SdkClientException;
25 import software.amazon.awssdk.core.interceptor.Context;
26 import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
27 import software.amazon.awssdk.core.interceptor.ExecutionInterceptor;
28 import software.amazon.awssdk.regions.PartitionMetadata;
29 import software.amazon.awssdk.regions.Region;
30 import software.amazon.awssdk.regions.RegionMetadata;
31 import software.amazon.awssdk.regions.ServiceMetadata;
32 import software.amazon.awssdk.regions.ServicePartitionMetadata;
33
34
38 @SdkInternalApi
39 public final class HelpfulUnknownHostExceptionInterceptor implements ExecutionInterceptor {
40 @Override
41 public Throwable modifyException(Context.FailedExecution context, ExecutionAttributes executionAttributes) {
42 if (!hasCause(context.exception(), UnknownHostException.class)) {
43 return context.exception();
44 }
45
46 StringBuilder error = new StringBuilder();
47 error.append("Received an UnknownHostException when attempting to interact with a service. See cause for the "
48 + "exact endpoint that is failing to resolve. ");
49
50 Optional<String> globalRegionErrorDetails = getGlobalRegionErrorDetails(executionAttributes);
51
52 if (globalRegionErrorDetails.isPresent()) {
53 error.append(globalRegionErrorDetails.get());
54 } else {
55 error.append("If this is happening on an endpoint that previously worked, there may be a network connectivity "
56 + "issue or your DNS cache could be storing endpoints for too long.");
57 }
58
59 return SdkClientException.builder().message(error.toString()).cause(context.exception()).build();
60 }
61
62
66 private Optional<String> getGlobalRegionErrorDetails(ExecutionAttributes executionAttributes) {
67 Region clientRegion = clientRegion(executionAttributes);
68 if (clientRegion.isGlobalRegion()) {
69 return Optional.empty();
70 }
71
72 List<ServicePartitionMetadata> globalPartitionsForService = globalPartitionsForService(executionAttributes);
73 if (globalPartitionsForService.isEmpty()) {
74 return Optional.empty();
75 }
76
77 String clientPartition = Optional.ofNullable(clientRegion.metadata())
78 .map(RegionMetadata::partition)
79 .map(PartitionMetadata::id)
80 .orElse(null);
81
82 Optional<Region> globalRegionForClientRegion =
83 globalPartitionsForService.stream()
84 .filter(p -> p.partition().id().equals(clientPartition))
85 .findAny()
86 .flatMap(ServicePartitionMetadata::globalRegion);
87
88 if (!globalRegionForClientRegion.isPresent()) {
89 String globalRegionsForThisService = globalPartitionsForService.stream()
90 .map(ServicePartitionMetadata::globalRegion)
91 .filter(Optional::isPresent)
92 .map(Optional::get)
93 .filter(Region::isGlobalRegion)
94 .map(Region::id)
95 .collect(Collectors.joining("/"));
96
97 return Optional.of("This specific service may be a global service, in which case you should configure a global "
98 + "region like " + globalRegionsForThisService + " on the client.");
99 }
100
101 Region globalRegion = globalRegionForClientRegion.get();
102
103 return Optional.of("This specific service is global in the same partition as the region configured on this client ("
104 + clientRegion + "). If this is the first time you're trying to talk to this service in this region, "
105 + "you should try configuring the global region on your client, instead: " + globalRegion);
106 }
107
108
111 private Region clientRegion(ExecutionAttributes executionAttributes) {
112 return executionAttributes.getAttribute(AwsExecutionAttribute.AWS_REGION);
113 }
114
115
118 private List<ServicePartitionMetadata> globalPartitionsForService(ExecutionAttributes executionAttributes) {
119 return ServiceMetadata.of(executionAttributes.getAttribute(AwsExecutionAttribute.ENDPOINT_PREFIX))
120 .servicePartitions()
121 .stream()
122 .filter(sp -> sp.globalRegion().isPresent())
123 .collect(Collectors.toList());
124 }
125
126 private boolean hasCause(Throwable thrown, Class<? extends Throwable> cause) {
127 if (thrown == null) {
128 return false;
129 }
130
131 if (cause.isAssignableFrom(thrown.getClass())) {
132 return true;
133 }
134
135 return hasCause(thrown.getCause(), cause);
136 }
137 }
138