Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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.apache.iceberg.aws;

import java.time.Duration;
import java.util.Map;
import org.apache.iceberg.relocated.com.google.common.annotations.VisibleForTesting;
import org.apache.iceberg.util.PropertyUtil;
import software.amazon.awssdk.awscore.client.builder.AwsSyncClientBuilder;
import software.amazon.awssdk.http.apache.ApacheHttpClient;

class ApacheHttpClientConfigurations {
private Long connectionTimeoutMs;
private Long socketTimeoutMs;
private Long acquisitionTimeoutMs;
private Long connectionMaxIdleTimeMs;
private Long connectionTimeToLiveMs;
private Boolean expectContinueEnabled;
private Integer maxConnections;
private Boolean tcpKeepAliveEnabled;
private Boolean useIdleConnectionReaperEnabled;

private ApacheHttpClientConfigurations() {}

public <T extends AwsSyncClientBuilder> void configureHttpClientBuilder(T awsClientBuilder) {
ApacheHttpClient.Builder apacheHttpClientBuilder = ApacheHttpClient.builder();
configureApacheHttpClientBuilder(apacheHttpClientBuilder);
awsClientBuilder.httpClientBuilder(apacheHttpClientBuilder);
}

private void initialize(Map<String, String> httpClientProperties) {
this.connectionTimeoutMs =
PropertyUtil.propertyAsNullableLong(
httpClientProperties, AwsProperties.HTTP_CLIENT_APACHE_CONNECTION_TIMEOUT_MS);
this.socketTimeoutMs =
PropertyUtil.propertyAsNullableLong(
httpClientProperties, AwsProperties.HTTP_CLIENT_APACHE_SOCKET_TIMEOUT_MS);
this.acquisitionTimeoutMs =
PropertyUtil.propertyAsNullableLong(
httpClientProperties,
AwsProperties.HTTP_CLIENT_APACHE_CONNECTION_ACQUISITION_TIMEOUT_MS);
this.connectionMaxIdleTimeMs =
PropertyUtil.propertyAsNullableLong(
httpClientProperties, AwsProperties.HTTP_CLIENT_APACHE_CONNECTION_MAX_IDLE_TIME_MS);
this.connectionTimeToLiveMs =
PropertyUtil.propertyAsNullableLong(
httpClientProperties, AwsProperties.HTTP_CLIENT_APACHE_CONNECTION_TIME_TO_LIVE_MS);
this.expectContinueEnabled =
PropertyUtil.propertyAsNullableBoolean(
httpClientProperties, AwsProperties.HTTP_CLIENT_APACHE_EXPECT_CONTINUE_ENABLED);
this.maxConnections =
PropertyUtil.propertyAsNullableInt(
httpClientProperties, AwsProperties.HTTP_CLIENT_APACHE_MAX_CONNECTIONS);
this.tcpKeepAliveEnabled =
PropertyUtil.propertyAsNullableBoolean(
httpClientProperties, AwsProperties.HTTP_CLIENT_APACHE_TCP_KEEP_ALIVE_ENABLED);
this.useIdleConnectionReaperEnabled =
PropertyUtil.propertyAsNullableBoolean(
httpClientProperties,
AwsProperties.HTTP_CLIENT_APACHE_USE_IDLE_CONNECTION_REAPER_ENABLED);
}

@VisibleForTesting
void configureApacheHttpClientBuilder(ApacheHttpClient.Builder apacheHttpClientBuilder) {
if (connectionTimeoutMs != null) {
apacheHttpClientBuilder.connectionTimeout(Duration.ofMillis(connectionTimeoutMs));
}
if (socketTimeoutMs != null) {
apacheHttpClientBuilder.socketTimeout(Duration.ofMillis(socketTimeoutMs));
}
if (acquisitionTimeoutMs != null) {
apacheHttpClientBuilder.connectionAcquisitionTimeout(Duration.ofMillis(acquisitionTimeoutMs));
}
if (connectionMaxIdleTimeMs != null) {
apacheHttpClientBuilder.connectionMaxIdleTime(Duration.ofMillis(connectionMaxIdleTimeMs));
}
if (connectionTimeToLiveMs != null) {
apacheHttpClientBuilder.connectionTimeToLive(Duration.ofMillis(connectionTimeToLiveMs));
}
if (expectContinueEnabled != null) {
apacheHttpClientBuilder.expectContinueEnabled(expectContinueEnabled);
}
if (maxConnections != null) {
apacheHttpClientBuilder.maxConnections(maxConnections);
}
if (tcpKeepAliveEnabled != null) {
apacheHttpClientBuilder.tcpKeepAlive(tcpKeepAliveEnabled);
}
if (useIdleConnectionReaperEnabled != null) {
apacheHttpClientBuilder.useIdleConnectionReaper(useIdleConnectionReaperEnabled);
}
}

public static ApacheHttpClientConfigurations create(Map<String, String> properties) {
ApacheHttpClientConfigurations configurations = new ApacheHttpClientConfigurations();
configurations.initialize(properties);
return configurations;
}
}
154 changes: 40 additions & 114 deletions aws/src/main/java/org/apache/iceberg/aws/AwsProperties.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@

import java.io.Serializable;
import java.net.URI;
import java.time.Duration;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
Expand All @@ -32,7 +31,6 @@
import org.apache.iceberg.common.DynConstructors;
import org.apache.iceberg.common.DynMethods;
import org.apache.iceberg.exceptions.ValidationException;
import org.apache.iceberg.relocated.com.google.common.annotations.VisibleForTesting;
import org.apache.iceberg.relocated.com.google.common.base.Preconditions;
import org.apache.iceberg.relocated.com.google.common.base.Strings;
import org.apache.iceberg.relocated.com.google.common.collect.Maps;
Expand All @@ -50,8 +48,6 @@
import software.amazon.awssdk.core.client.builder.SdkClientBuilder;
import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption;
import software.amazon.awssdk.core.signer.Signer;
import software.amazon.awssdk.http.apache.ApacheHttpClient;
import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient;
import software.amazon.awssdk.services.dynamodb.DynamoDbClientBuilder;
import software.amazon.awssdk.services.glue.GlueClientBuilder;
import software.amazon.awssdk.services.s3.S3ClientBuilder;
Expand Down Expand Up @@ -377,8 +373,8 @@ public class AwsProperties implements Serializable {

/**
* Used to configure the connection timeout in milliseconds for {@link
* UrlConnectionHttpClient.Builder}. This flag only works when {@link #HTTP_CLIENT_TYPE} is set to
* {@link #HTTP_CLIENT_TYPE_URLCONNECTION}
* software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient.Builder}. This flag only
* works when {@link #HTTP_CLIENT_TYPE} is set to {@link #HTTP_CLIENT_TYPE_URLCONNECTION}
*
* <p>For more details, see
* https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/http/urlconnection/UrlConnectionHttpClient.Builder.html
Expand All @@ -388,8 +384,8 @@ public class AwsProperties implements Serializable {

/**
* Used to configure the socket timeout in milliseconds for {@link
* UrlConnectionHttpClient.Builder}. This flag only works when {@link #HTTP_CLIENT_TYPE} is set to
* {@link #HTTP_CLIENT_TYPE_URLCONNECTION}
* software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient.Builder}. This flag only
* works when {@link #HTTP_CLIENT_TYPE} is set to {@link #HTTP_CLIENT_TYPE_URLCONNECTION}
*
* <p>For more details, see
* https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/http/urlconnection/UrlConnectionHttpClient.Builder.html
Expand Down Expand Up @@ -632,18 +628,9 @@ public class AwsProperties implements Serializable {
*/
public static final String LAKE_FORMATION_DB_NAME = "lakeformation.db-name";

private static final String HTTP_CLIENT_PREFIX = "http-client.";
private String httpClientType;
private Long httpClientUrlConnectionConnectionTimeoutMs;
private Long httpClientUrlConnectionSocketTimeoutMs;
private Long httpClientApacheConnectionAcquisitionTimeoutMs;
private Long httpClientApacheConnectionMaxIdleTimeMs;
private Long httpClientApacheConnectionTimeToLiveMs;
private Long httpClientApacheConnectionTimeoutMs;
private Boolean httpClientApacheExpectContinueEnabled;
private Integer httpClientApacheMaxConnections;
private Long httpClientApacheSocketTimeoutMs;
private Boolean httpClientApacheTcpKeepAliveEnabled;
private Boolean httpClientApacheUseIdleConnectionReaperEnabled;
private final Map<String, String> httpClientProperties;
private final Set<software.amazon.awssdk.services.sts.model.Tag> stsClientAssumeRoleTags;

private String clientAssumeRoleArn;
Expand Down Expand Up @@ -693,17 +680,7 @@ public class AwsProperties implements Serializable {

public AwsProperties() {
this.httpClientType = HTTP_CLIENT_TYPE_DEFAULT;
this.httpClientUrlConnectionConnectionTimeoutMs = null;
this.httpClientUrlConnectionSocketTimeoutMs = null;
this.httpClientApacheConnectionAcquisitionTimeoutMs = null;
this.httpClientApacheConnectionMaxIdleTimeMs = null;
this.httpClientApacheConnectionTimeToLiveMs = null;
this.httpClientApacheConnectionTimeoutMs = null;
this.httpClientApacheExpectContinueEnabled = null;
this.httpClientApacheMaxConnections = null;
this.httpClientApacheSocketTimeoutMs = null;
this.httpClientApacheTcpKeepAliveEnabled = null;
this.httpClientApacheUseIdleConnectionReaperEnabled = null;
this.httpClientProperties = Collections.emptyMap();
this.stsClientAssumeRoleTags = Sets.newHashSet();

this.clientAssumeRoleArn = null;
Expand Down Expand Up @@ -760,36 +737,8 @@ public AwsProperties() {
public AwsProperties(Map<String, String> properties) {
this.httpClientType =
PropertyUtil.propertyAsString(properties, HTTP_CLIENT_TYPE, HTTP_CLIENT_TYPE_DEFAULT);
this.httpClientUrlConnectionConnectionTimeoutMs =
PropertyUtil.propertyAsNullableLong(
properties, HTTP_CLIENT_URLCONNECTION_CONNECTION_TIMEOUT_MS);
this.httpClientUrlConnectionSocketTimeoutMs =
PropertyUtil.propertyAsNullableLong(
properties, HTTP_CLIENT_URLCONNECTION_SOCKET_TIMEOUT_MS);
this.httpClientApacheConnectionAcquisitionTimeoutMs =
PropertyUtil.propertyAsNullableLong(
properties, HTTP_CLIENT_APACHE_CONNECTION_ACQUISITION_TIMEOUT_MS);
this.httpClientApacheConnectionMaxIdleTimeMs =
PropertyUtil.propertyAsNullableLong(
properties, HTTP_CLIENT_APACHE_CONNECTION_MAX_IDLE_TIME_MS);
this.httpClientApacheConnectionTimeToLiveMs =
PropertyUtil.propertyAsNullableLong(
properties, HTTP_CLIENT_APACHE_CONNECTION_TIME_TO_LIVE_MS);
this.httpClientApacheConnectionTimeoutMs =
PropertyUtil.propertyAsNullableLong(properties, HTTP_CLIENT_APACHE_CONNECTION_TIMEOUT_MS);
this.httpClientApacheExpectContinueEnabled =
PropertyUtil.propertyAsNullableBoolean(
properties, HTTP_CLIENT_APACHE_EXPECT_CONTINUE_ENABLED);
this.httpClientApacheMaxConnections =
PropertyUtil.propertyAsNullableInt(properties, HTTP_CLIENT_APACHE_MAX_CONNECTIONS);
this.httpClientApacheSocketTimeoutMs =
PropertyUtil.propertyAsNullableLong(properties, HTTP_CLIENT_APACHE_SOCKET_TIMEOUT_MS);
this.httpClientApacheTcpKeepAliveEnabled =
PropertyUtil.propertyAsNullableBoolean(
properties, HTTP_CLIENT_APACHE_TCP_KEEP_ALIVE_ENABLED);
this.httpClientApacheUseIdleConnectionReaperEnabled =
PropertyUtil.propertyAsNullableBoolean(
properties, HTTP_CLIENT_APACHE_USE_IDLE_CONNECTION_REAPER_ENABLED);
this.httpClientProperties =
PropertyUtil.filterProperties(properties, key -> key.startsWith(HTTP_CLIENT_PREFIX));
this.stsClientAssumeRoleTags = toStsTags(properties, CLIENT_ASSUME_ROLE_TAGS_PREFIX);
this.clientAssumeRoleArn = properties.get(CLIENT_ASSUME_ROLE_ARN);
this.clientAssumeRoleTimeoutSec =
Expand Down Expand Up @@ -1108,6 +1057,10 @@ public Map<String, String> s3BucketToAccessPointMapping() {
return s3BucketToAccessPointMapping;
}

public Map<String, String> httpClientProperties() {
return httpClientProperties;
}

/**
* Configure the credentials for an S3 client.
*
Expand Down Expand Up @@ -1219,13 +1172,16 @@ public <T extends AwsSyncClientBuilder> void applyHttpClientConfigurations(T bui
}
switch (httpClientType) {
case HTTP_CLIENT_TYPE_URLCONNECTION:
builder.httpClientBuilder(
UrlConnectionHttpClient.builder()
.applyMutation(this::configureUrlConnectionHttpClientBuilder));
UrlConnectionHttpClientConfigurations urlConnectionHttpClientConfigurations =
(UrlConnectionHttpClientConfigurations)
loadHttpClientConfigurations(UrlConnectionHttpClientConfigurations.class.getName());
urlConnectionHttpClientConfigurations.configureHttpClientBuilder(builder);
break;
case HTTP_CLIENT_TYPE_APACHE:
builder.httpClientBuilder(
ApacheHttpClient.builder().applyMutation(this::configureApacheHttpClientBuilder));
ApacheHttpClientConfigurations apacheHttpClientConfigurations =
(ApacheHttpClientConfigurations)
loadHttpClientConfigurations(ApacheHttpClientConfigurations.class.getName());
apacheHttpClientConfigurations.configureHttpClientBuilder(builder);
break;
default:
throw new IllegalArgumentException("Unrecognized HTTP client type " + httpClientType);
Expand Down Expand Up @@ -1314,55 +1270,25 @@ private <T extends SdkClientBuilder> void configureEndpoint(T builder, String en
}
}

@VisibleForTesting
<T extends UrlConnectionHttpClient.Builder> void configureUrlConnectionHttpClientBuilder(
T builder) {
if (httpClientUrlConnectionConnectionTimeoutMs != null) {
builder.connectionTimeout(Duration.ofMillis(httpClientUrlConnectionConnectionTimeoutMs));
}

if (httpClientUrlConnectionSocketTimeoutMs != null) {
builder.socketTimeout(Duration.ofMillis(httpClientUrlConnectionSocketTimeoutMs));
}
}

@VisibleForTesting
<T extends ApacheHttpClient.Builder> void configureApacheHttpClientBuilder(T builder) {
if (httpClientApacheConnectionTimeoutMs != null) {
builder.connectionTimeout(Duration.ofMillis(httpClientApacheConnectionTimeoutMs));
}

if (httpClientApacheSocketTimeoutMs != null) {
builder.socketTimeout(Duration.ofMillis(httpClientApacheSocketTimeoutMs));
}

if (httpClientApacheConnectionAcquisitionTimeoutMs != null) {
builder.connectionAcquisitionTimeout(
Duration.ofMillis(httpClientApacheConnectionAcquisitionTimeoutMs));
}

if (httpClientApacheConnectionMaxIdleTimeMs != null) {
builder.connectionMaxIdleTime(Duration.ofMillis(httpClientApacheConnectionMaxIdleTimeMs));
}

if (httpClientApacheConnectionTimeToLiveMs != null) {
builder.connectionTimeToLive(Duration.ofMillis(httpClientApacheConnectionTimeToLiveMs));
}

if (httpClientApacheExpectContinueEnabled != null) {
builder.expectContinueEnabled(httpClientApacheExpectContinueEnabled);
}

if (httpClientApacheMaxConnections != null) {
builder.maxConnections(httpClientApacheMaxConnections);
}

if (httpClientApacheTcpKeepAliveEnabled != null) {
builder.tcpKeepAlive(httpClientApacheTcpKeepAliveEnabled);
}

if (httpClientApacheUseIdleConnectionReaperEnabled != null) {
builder.useIdleConnectionReaper(httpClientApacheUseIdleConnectionReaperEnabled);
/**
* Dynamically load the http client builder to avoid runtime deps requirements of both {@link
* software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient} and {@link
* software.amazon.awssdk.http.apache.ApacheHttpClient}, since including both will cause error
* described in <a href="https://github.com/apache/iceberg/issues/6715">issue#6715</a>
*/
private Object loadHttpClientConfigurations(String impl) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe add a brief Javadoc to explain why we are doing the reflection here and link to the github issue for more context.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Object httpClientConfigurations;
try {
httpClientConfigurations =
DynMethods.builder("create")
.hiddenImpl(impl, Map.class)
.buildStaticChecked()
.invoke(httpClientProperties);
return httpClientConfigurations;
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException(
String.format("Cannot create %s to generate and configure the http client builder", impl),
e);
}
}
}
Loading