-
Notifications
You must be signed in to change notification settings - Fork 1k
feat(sdk-core): add sync HTTP-client warm-up to SdkWarmUp.prime() for CRaC priming #7080
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feature/master/crac_auto_priming_support
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,91 @@ | ||||||||
| /* | ||||||||
| * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||||||||
| * | ||||||||
| * Licensed under the Apache License, Version 2.0 (the "License"). | ||||||||
| * You may not use this file except in compliance with the License. | ||||||||
| * A copy of the License is located at | ||||||||
| * | ||||||||
| * http://aws.amazon.com/apache2.0 | ||||||||
| * | ||||||||
| * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.core.internal.crac; | ||||||||
|
|
||||||||
| import java.net.URI; | ||||||||
| import java.util.Optional; | ||||||||
| import software.amazon.awssdk.annotations.SdkInternalApi; | ||||||||
| import software.amazon.awssdk.core.SdkSystemSetting; | ||||||||
| import software.amazon.awssdk.utils.OptionalUtils; | ||||||||
| import software.amazon.awssdk.utils.StringUtils; | ||||||||
| import software.amazon.awssdk.utils.SystemSetting; | ||||||||
| import software.amazon.awssdk.utils.http.SdkHttpUtils; | ||||||||
| import software.amazon.awssdk.utils.internal.SystemSettingUtils; | ||||||||
|
|
||||||||
| /** | ||||||||
| * Resolves the regional STS endpoint ({@code https://sts.<region>.amazonaws.com/}) used by the CRaC HTTP-client warm-up. | ||||||||
| * | ||||||||
| * <p>The region is taken from the first of: {@link SdkSystemSetting#AWS_REGION} (the {@code aws.region} system property or | ||||||||
| * {@code AWS_REGION} environment variable), the {@code AWS_DEFAULT_REGION} environment variable, or {@value #DEFAULT_REGION}. | ||||||||
| * | ||||||||
| * <p>Only system properties and environment variables are read. The full SDK region-resolution chain (IMDS, profile file) is | ||||||||
| * avoided during priming because those add network or filesystem calls that may fail or time out. The endpoint host always | ||||||||
| * uses the {@code amazonaws.com} suffix, which is incorrect for the China, GovCloud, and ISO partitions; in those partitions | ||||||||
| * the warm-up request simply fails and is ignored, since it is best-effort. | ||||||||
| */ | ||||||||
| @SdkInternalApi | ||||||||
| public final class RegionEndpointResolver { | ||||||||
|
|
||||||||
| static final String DEFAULT_REGION = "us-east-1"; | ||||||||
|
|
||||||||
| private RegionEndpointResolver() { | ||||||||
| } | ||||||||
|
|
||||||||
| public static RegionEndpointResolver create() { | ||||||||
| return new RegionEndpointResolver(); | ||||||||
| } | ||||||||
|
|
||||||||
| /** | ||||||||
| * @return the regional STS endpoint URI for the resolved region; never null. | ||||||||
| */ | ||||||||
| public URI stsEndpoint() { | ||||||||
| // URL-encode the region before putting it in the host, same as Region.of(String). | ||||||||
| return URI.create("https://sts." + SdkHttpUtils.urlEncode(resolveRegion()) + ".amazonaws.com/"); | ||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. might benefit from using aws-sdk-java-v2/utils/src/main/java/software/amazon/awssdk/utils/HostnameValidator.java Lines 31 to 33 in 8a6a76c
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. alternatively, build using the |
||||||||
| } | ||||||||
|
|
||||||||
| private String resolveRegion() { | ||||||||
| Optional<String> awsRegion = trimmed(SdkSystemSetting.AWS_REGION.getStringValue()); | ||||||||
| return OptionalUtils.firstPresent(awsRegion, RegionEndpointResolver::awsDefaultRegion) | ||||||||
| .orElse(DEFAULT_REGION); | ||||||||
| } | ||||||||
|
|
||||||||
| private static Optional<String> awsDefaultRegion() { | ||||||||
| return trimmed(SystemSettingUtils.resolveEnvironmentVariable(new AwsDefaultRegionEnvVar())); | ||||||||
| } | ||||||||
|
|
||||||||
| private static Optional<String> trimmed(Optional<String> value) { | ||||||||
| // trimToNull returns null for blank/empty input, so Optional.map collapses those to an empty Optional. | ||||||||
| return value.map(StringUtils::trimToNull); | ||||||||
| } | ||||||||
|
|
||||||||
| // AWS_DEFAULT_REGION is an environment-variable-only fallback with no system-property equivalent. | ||||||||
| private static final class AwsDefaultRegionEnvVar implements SystemSetting { | ||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. AFAIK, we don't use |
||||||||
| @Override | ||||||||
| public String property() { | ||||||||
| return null; | ||||||||
| } | ||||||||
|
|
||||||||
| @Override | ||||||||
| public String environmentVariable() { | ||||||||
| return "AWS_DEFAULT_REGION"; | ||||||||
| } | ||||||||
|
|
||||||||
| @Override | ||||||||
| public String defaultValue() { | ||||||||
| return null; | ||||||||
| } | ||||||||
| } | ||||||||
| } | ||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| /* | ||
| * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"). | ||
| * You may not use this file except in compliance with the License. | ||
| * A copy of the License is located at | ||
| * | ||
| * http://aws.amazon.com/apache2.0 | ||
| * | ||
| * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.core.internal.crac; | ||
|
|
||
| import java.util.Iterator; | ||
| import java.util.ServiceConfigurationError; | ||
| import java.util.function.Consumer; | ||
| import software.amazon.awssdk.annotations.SdkInternalApi; | ||
| import software.amazon.awssdk.utils.Logger; | ||
|
|
||
| /** | ||
| * Shared best-effort {@link java.util.ServiceLoader} iteration for the CRaC warm-up paths. | ||
| */ | ||
| @SdkInternalApi | ||
| public final class WarmUpDiscovery { | ||
|
|
||
| private static final Logger log = Logger.loggerFor(WarmUpDiscovery.class); | ||
|
|
||
| private WarmUpDiscovery() { | ||
| } | ||
|
|
||
| /** | ||
| * Applies {@code action} to every discovered element, skipping (and logging at warn) any element that fails to load or | ||
| * whose action throws, so the rest still run. Logs at debug when nothing is discovered. | ||
| */ | ||
| public static <T> void forEachDiscovered(Iterator<T> iterator, Consumer<T> action) { | ||
| boolean discoveredAny = false; | ||
| while (iterator.hasNext()) { | ||
| T element; | ||
| try { | ||
| element = iterator.next(); | ||
| } catch (ServiceConfigurationError e) { | ||
| // next() has already advanced past the bad element, so it is safe to continue to the next one. | ||
| log.warn(() -> "Skipping a warm-up task that could not be loaded.", e); | ||
| continue; | ||
| } | ||
|
|
||
| discoveredAny = true; | ||
| T discovered = element; | ||
| try { | ||
| action.accept(discovered); | ||
| } catch (RuntimeException | LinkageError e) { | ||
| // LinkageError because a discovered element can fail to link (missing deps/native lib, failed static init), | ||
| // which is an Error, not an Exception. Skip it to keep warm-up best-effort; fatal Errors still propagate. | ||
| log.warn(() -> "Warm-up failed for " + discovered.getClass().getName() + " and was skipped.", e); | ||
| } | ||
| } | ||
|
|
||
| if (!discoveredAny) { | ||
| log.debug(() -> "No warm-up tasks were discovered on the classpath."); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| /* | ||
| * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"). | ||
| * You may not use this file except in compliance with the License. | ||
| * A copy of the License is located at | ||
| * | ||
| * http://aws.amazon.com/apache2.0 | ||
| * | ||
| * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.core.internal.crac; | ||
|
|
||
| import java.net.URI; | ||
| import java.util.Optional; | ||
| import software.amazon.awssdk.annotations.SdkInternalApi; | ||
| import software.amazon.awssdk.http.ContentStreamProvider; | ||
| import software.amazon.awssdk.http.SdkHttpMethod; | ||
| import software.amazon.awssdk.http.SdkHttpRequest; | ||
|
|
||
| /** | ||
| * Describes the request the CRaC warm-up sends to the resolved endpoint, and converts it to an {@link SdkHttpRequest} bound to | ||
| * that endpoint. The request is never signed and needs no credentials; only its execution (DNS, TLS, request/response I/O) | ||
| * matters for JIT priming. | ||
| */ | ||
| @SdkInternalApi | ||
| public final class WarmUpRequest { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not really sure about the usefulness of this class. Does it make more sense to just have a utility class/factory just creates the |
||
|
|
||
| private final SdkHttpMethod method; | ||
| private final String body; | ||
| private final String contentType; | ||
|
Comment on lines
+34
to
+35
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The ctor is private; are these necessary? |
||
|
|
||
| private WarmUpRequest(SdkHttpMethod method, String body, String contentType) { | ||
| this.method = method; | ||
| this.body = body; | ||
| this.contentType = contentType; | ||
| } | ||
|
|
||
| /** | ||
| * A bare {@code GET} to the endpoint, with no body. Warms DNS, the TLS handshake, and certificate-chain validation, which | ||
| * are the dominant cold-start costs and are shared across all services and operations. | ||
| */ | ||
| public static WarmUpRequest get() { | ||
| return new WarmUpRequest(SdkHttpMethod.GET, null, null); | ||
| } | ||
|
|
||
| /** | ||
| * @retusrn this request as an {@link SdkHttpRequest} targeting {@code endpoint}. | ||
| */ | ||
| public SdkHttpRequest toHttpRequest(URI endpoint) { | ||
| SdkHttpRequest.Builder builder = SdkHttpRequest.builder() | ||
| .method(method) | ||
| .uri(endpoint); | ||
| if (body != null) { | ||
| builder.putHeader("Content-Type", contentType) | ||
| .putHeader("Content-Length", String.valueOf(body.getBytes(java.nio.charset.StandardCharsets.UTF_8).length)); | ||
| } | ||
| return builder.build(); | ||
| } | ||
|
|
||
| /** | ||
| * @return the request body stream provider, or empty if this request has no body. | ||
| */ | ||
| public Optional<ContentStreamProvider> contentStreamProvider() { | ||
| return body == null ? Optional.empty() : Optional.of(ContentStreamProvider.fromUtf8String(body)); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| /* | ||
| * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"). | ||
| * You may not use this file except in compliance with the License. | ||
| * A copy of the License is located at | ||
| * | ||
| * http://aws.amazon.com/apache2.0 | ||
| * | ||
| * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.core.internal.http.loader; | ||
|
|
||
| import java.util.Collections; | ||
| import java.util.List; | ||
| import software.amazon.awssdk.annotations.SdkInternalApi; | ||
| import software.amazon.awssdk.annotations.SdkTestInternalApi; | ||
|
|
||
| /** | ||
| * {@link HttpWarmupInvoker} that owns the set of {@link HttpClientWarmer}s and invokes each. | ||
| */ | ||
| @SdkInternalApi | ||
| public final class ClasspathHttpWarmupInvoker implements HttpWarmupInvoker { | ||
|
|
||
| private final List<HttpClientWarmer> warmers; | ||
|
|
||
| @SdkTestInternalApi | ||
| ClasspathHttpWarmupInvoker(List<HttpClientWarmer> warmers) { | ||
| this.warmers = warmers; | ||
| } | ||
|
|
||
| /** | ||
| * @return an invoker over the HTTP-client warmers on the classpath. | ||
| */ | ||
| public static HttpWarmupInvoker create() { | ||
| return new ClasspathHttpWarmupInvoker(Collections.singletonList(SyncHttpClientWarmer.create())); | ||
| } | ||
|
|
||
| @Override | ||
| public void invokeAll() { | ||
| for (HttpClientWarmer warmer : warmers) { | ||
| warmer.warmAll(); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| /* | ||
| * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"). | ||
| * You may not use this file except in compliance with the License. | ||
| * A copy of the License is located at | ||
| * | ||
| * http://aws.amazon.com/apache2.0 | ||
| * | ||
| * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.core.internal.http.loader; | ||
|
|
||
| import software.amazon.awssdk.annotations.SdkInternalApi; | ||
|
|
||
| /** | ||
| * Warms the sync or async HTTP clients on the classpath for CRaC priming. | ||
| */ | ||
| @SdkInternalApi | ||
| public interface HttpClientWarmer { | ||
|
|
||
| /** | ||
| * Warms every HTTP client found on the classpath. Best-effort; never throws. | ||
| */ | ||
| void warmAll(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this class intended to resolve other service endpoints in the future?