Skip to content

Commit 1fdd52f

Browse files
steveloughrandeepakdamri
authored andcommitted
HADOOP-14556. S3A to support Delegation Tokens.
Contributed by Steve Loughran and Daryn Sharp.
1 parent 50725e2 commit 1fdd52f

File tree

90 files changed

+10542
-912
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

90 files changed

+10542
-912
lines changed

hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/StorageStatistics.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package org.apache.hadoop.fs;
1919

2020
import org.apache.hadoop.classification.InterfaceAudience;
21+
import org.apache.hadoop.classification.InterfaceStability;
2122

2223
import java.util.Iterator;
2324

@@ -37,9 +38,13 @@ public abstract class StorageStatistics {
3738
*
3839
* When adding new common statistic name constants, please make them unique.
3940
* By convention, they are implicitly unique:
40-
* - the name of the constants are uppercase, words separated by underscores.
41-
* - the value of the constants are lowercase of the constant names.
41+
* <ul>
42+
* <li>the name of the constants are uppercase, words separated by
43+
* underscores.</li>
44+
* <li>the value of the constants are lowercase of the constant names.</li>
45+
* </ul>
4246
*/
47+
@InterfaceStability.Evolving
4348
public interface CommonStatisticNames {
4449
// The following names are for file system operation invocations
4550
String OP_APPEND = "op_append";
@@ -49,6 +54,7 @@ public interface CommonStatisticNames {
4954
String OP_DELETE = "op_delete";
5055
String OP_EXISTS = "op_exists";
5156
String OP_GET_CONTENT_SUMMARY = "op_get_content_summary";
57+
String OP_GET_DELEGATION_TOKEN = "op_get_delegation_token";
5258
String OP_GET_FILE_CHECKSUM = "op_get_file_checksum";
5359
String OP_GET_FILE_STATUS = "op_get_file_status";
5460
String OP_GET_STATUS = "op_get_status";

hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/Job.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242
import org.apache.hadoop.mapreduce.util.ConfigUtil;
4343
import org.apache.hadoop.util.StringUtils;
4444
import org.apache.hadoop.yarn.api.records.ReservationId;
45+
46+
import com.google.common.annotations.VisibleForTesting;
4547
import org.slf4j.Logger;
4648
import org.slf4j.LoggerFactory;
4749

@@ -1524,7 +1526,10 @@ public static Map<String, Boolean> getArchiveSharedCacheUploadPolicies(
15241526
return getSharedCacheUploadPolicies(conf, false);
15251527
}
15261528

1527-
private synchronized void connect()
1529+
/** Only for mocking via unit tests. */
1530+
@Private
1531+
@VisibleForTesting
1532+
synchronized void connect()
15281533
throws IOException, InterruptedException, ClassNotFoundException {
15291534
if (cluster == null) {
15301535
cluster =
@@ -1544,7 +1549,8 @@ boolean isConnected() {
15441549

15451550
/** Only for mocking via unit tests. */
15461551
@Private
1547-
public JobSubmitter getJobSubmitter(FileSystem fs,
1552+
@VisibleForTesting
1553+
JobSubmitter getJobSubmitter(FileSystem fs,
15481554
ClientProtocol submitClient) throws IOException {
15491555
return new JobSubmitter(fs, submitClient);
15501556
}

hadoop-project/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1732,6 +1732,11 @@
17321732
<artifactId>snakeyaml</artifactId>
17331733
<version>${snakeyaml.version}</version>
17341734
</dependency>
1735+
<dependency>
1736+
<groupId>org.hamcrest</groupId>
1737+
<artifactId>hamcrest-library</artifactId>
1738+
<version>1.3</version>
1739+
</dependency>
17351740
<dependency>
17361741
<groupId>org.assertj</groupId>
17371742
<artifactId>assertj-core</artifactId>

hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/AWSCredentialProviderList.java

Lines changed: 74 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.io.Closeable;
2222
import java.util.ArrayList;
2323
import java.util.Collection;
24+
import java.util.Collections;
2425
import java.util.List;
2526
import java.util.concurrent.atomic.AtomicBoolean;
2627
import java.util.concurrent.atomic.AtomicInteger;
@@ -39,6 +40,7 @@
3940
import org.apache.hadoop.classification.InterfaceAudience;
4041
import org.apache.hadoop.classification.InterfaceStability;
4142
import org.apache.hadoop.fs.s3a.auth.NoAuthWithAWSException;
43+
import org.apache.hadoop.fs.s3a.auth.NoAwsCredentialsException;
4244
import org.apache.hadoop.io.IOUtils;
4345

4446
/**
@@ -52,7 +54,8 @@
5254
* an {@link AmazonClientException}, that is rethrown, rather than
5355
* swallowed.</li>
5456
* <li>Has some more diagnostics.</li>
55-
* <li>On failure, the last AmazonClientException raised is rethrown.</li>
57+
* <li>On failure, the last "relevant" AmazonClientException raised is
58+
* rethrown; exceptions other than 'no credentials' have priority.</li>
5659
* <li>Special handling of {@link AnonymousAWSCredentials}.</li>
5760
* </ol>
5861
*/
@@ -78,6 +81,12 @@ public class AWSCredentialProviderList implements AWSCredentialsProvider,
7881

7982
private final AtomicBoolean closed = new AtomicBoolean(false);
8083

84+
/**
85+
* The name, which is empty by default.
86+
* Uses in the code assume if non empty there's a trailing space.
87+
*/
88+
private String name = "";
89+
8190
/**
8291
* Empty instance. This is not ready to be used.
8392
*/
@@ -93,6 +102,29 @@ public AWSCredentialProviderList(
93102
this.providers.addAll(providers);
94103
}
95104

105+
/**
106+
* Create with an initial list of providers.
107+
* @param name name for error messages, may be ""
108+
* @param providerArgs provider list.
109+
*/
110+
public AWSCredentialProviderList(final String name,
111+
final AWSCredentialsProvider... providerArgs) {
112+
setName(name);
113+
Collections.addAll(providers, providerArgs);
114+
}
115+
116+
/**
117+
* Set the name; adds a ": " if needed.
118+
* @param name name to add, or "" for no name.
119+
*/
120+
public void setName(final String name) {
121+
if (!name.isEmpty() && !name.endsWith(": ")) {
122+
this.name = name + ": ";
123+
} else {
124+
this.name = name;
125+
}
126+
}
127+
96128
/**
97129
* Add a new provider.
98130
* @param p provider
@@ -101,6 +133,14 @@ public void add(AWSCredentialsProvider p) {
101133
providers.add(p);
102134
}
103135

136+
/**
137+
* Add all providers from another list to this one.
138+
* @param other the other list.
139+
*/
140+
public void addAll(AWSCredentialProviderList other) {
141+
providers.addAll(other.providers);
142+
}
143+
104144
/**
105145
* Refresh all child entries.
106146
*/
@@ -123,7 +163,7 @@ public void refresh() {
123163
public AWSCredentials getCredentials() {
124164
if (isClosed()) {
125165
LOG.warn(CREDENTIALS_REQUESTED_WHEN_CLOSED);
126-
throw new NoAuthWithAWSException(
166+
throw new NoAuthWithAWSException(name +
127167
CREDENTIALS_REQUESTED_WHEN_CLOSED);
128168
}
129169
checkNotEmpty();
@@ -135,13 +175,27 @@ public AWSCredentials getCredentials() {
135175
for (AWSCredentialsProvider provider : providers) {
136176
try {
137177
AWSCredentials credentials = provider.getCredentials();
178+
Preconditions.checkNotNull(credentials,
179+
"Null credentials returned by %s", provider);
138180
if ((credentials.getAWSAccessKeyId() != null &&
139181
credentials.getAWSSecretKey() != null)
140182
|| (credentials instanceof AnonymousAWSCredentials)) {
141183
lastProvider = provider;
142184
LOG.debug("Using credentials from {}", provider);
143185
return credentials;
144186
}
187+
} catch (NoAwsCredentialsException e) {
188+
// don't bother with the stack trace here as it is usually a
189+
// minor detail.
190+
191+
// only update the last exception if it isn't set.
192+
// Why so? Stops delegation token issues being lost on the fallback
193+
// values.
194+
if (lastException == null) {
195+
lastException = e;
196+
}
197+
LOG.debug("No credentials from {}: {}",
198+
provider, e.toString());
145199
} catch (AmazonClientException e) {
146200
lastException = e;
147201
LOG.debug("No credentials provided by {}: {}",
@@ -151,12 +205,16 @@ public AWSCredentials getCredentials() {
151205

152206
// no providers had any credentials. Rethrow the last exception
153207
// or create a new one.
154-
String message = "No AWS Credentials provided by "
208+
String message = name + "No AWS Credentials provided by "
155209
+ listProviderNames();
156210
if (lastException != null) {
157211
message += ": " + lastException;
158212
}
159-
throw new NoAuthWithAWSException(message, lastException);
213+
if (lastException instanceof CredentialInitializationException) {
214+
throw lastException;
215+
} else {
216+
throw new NoAuthWithAWSException(message, lastException);
217+
}
160218
}
161219

162220
/**
@@ -175,7 +233,7 @@ List<AWSCredentialsProvider> getProviders() {
175233
*/
176234
public void checkNotEmpty() {
177235
if (providers.isEmpty()) {
178-
throw new NoAuthWithAWSException(NO_AWS_CREDENTIAL_PROVIDERS);
236+
throw new NoAuthWithAWSException(name + NO_AWS_CREDENTIAL_PROVIDERS);
179237
}
180238
}
181239

@@ -198,8 +256,10 @@ public String listProviderNames() {
198256
@Override
199257
public String toString() {
200258
return "AWSCredentialProviderList[" +
259+
name +
201260
"refcount= " + refCount.get() + ": [" +
202-
StringUtils.join(providers, ", ") + ']';
261+
StringUtils.join(providers, ", ") + ']'
262+
+ (lastProvider != null ? (" last provider: " + lastProvider) : "");
203263
}
204264

205265
/**
@@ -265,4 +325,12 @@ public void close() {
265325
}
266326
}
267327
}
328+
329+
/**
330+
* Get the size of this list.
331+
* @return the number of providers in the list.
332+
*/
333+
public int size() {
334+
return providers.size();
335+
}
268336
}

hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/Constants.java

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ private Constants() {
4949
// s3 secret key
5050
public static final String SECRET_KEY = "fs.s3a.secret.key";
5151

52-
// aws credentials provider
52+
// aws credentials providers
5353
public static final String AWS_CREDENTIALS_PROVIDER =
5454
"fs.s3a.aws.credentials.provider";
5555

@@ -61,18 +61,20 @@ private Constants() {
6161
public static final String S3A_SECURITY_CREDENTIAL_PROVIDER_PATH =
6262
"fs.s3a.security.credential.provider.path";
6363

64-
// session token for when using TemporaryAWSCredentialsProvider
64+
/**
65+
* session token for when using TemporaryAWSCredentialsProvider: : {@value}.
66+
*/
6567
public static final String SESSION_TOKEN = "fs.s3a.session.token";
6668

6769
/**
68-
* AWS Role to request.
70+
* ARN of AWS Role to request: {@value}.
6971
*/
7072
public static final String ASSUMED_ROLE_ARN =
7173
"fs.s3a.assumed.role.arn";
7274

7375
/**
7476
* Session name for the assumed role, must be valid characters according
75-
* to the AWS APIs.
77+
* to the AWS APIs: {@value}.
7678
* If not set, one is generated from the current Hadoop/Kerberos username.
7779
*/
7880
public static final String ASSUMED_ROLE_SESSION_NAME =
@@ -84,34 +86,50 @@ private Constants() {
8486
public static final String ASSUMED_ROLE_SESSION_DURATION =
8587
"fs.s3a.assumed.role.session.duration";
8688

87-
/** Security Token Service Endpoint. If unset, uses the default endpoint. */
89+
/**
90+
* Security Token Service Endpoint: {@value}.
91+
* If unset, uses the default endpoint.
92+
*/
8893
public static final String ASSUMED_ROLE_STS_ENDPOINT =
8994
"fs.s3a.assumed.role.sts.endpoint";
9095

9196
/**
92-
* Region for the STS endpoint; only relevant if the endpoint
93-
* is set.
97+
* Default endpoint for session tokens: {@value}.
98+
* This is the central STS endpoint which, for v3 signing, can
99+
* issue STS tokens for any region.
100+
*/
101+
public static final String DEFAULT_ASSUMED_ROLE_STS_ENDPOINT = "";
102+
103+
/**
104+
* Region for the STS endpoint; needed if the endpoint
105+
* is set to anything other then the central one.: {@value}.
94106
*/
95107
public static final String ASSUMED_ROLE_STS_ENDPOINT_REGION =
96108
"fs.s3a.assumed.role.sts.endpoint.region";
97109

98110
/**
99111
* Default value for the STS endpoint region; needed for
100-
* v4 signing.
112+
* v4 signing: {@value}.
101113
*/
102-
public static final String ASSUMED_ROLE_STS_ENDPOINT_REGION_DEFAULT =
103-
"us-west-1";
114+
public static final String ASSUMED_ROLE_STS_ENDPOINT_REGION_DEFAULT = "";
104115

105116
/**
106-
* Default duration of an assumed role.
117+
* Default duration of an assumed role: {@value}.
107118
*/
108-
public static final String ASSUMED_ROLE_SESSION_DURATION_DEFAULT = "30m";
119+
public static final String ASSUMED_ROLE_SESSION_DURATION_DEFAULT = "1h";
109120

110-
/** list of providers to authenticate for the assumed role. */
121+
/**
122+
* List of providers to authenticate for the assumed role: {@value}.
123+
*/
111124
public static final String ASSUMED_ROLE_CREDENTIALS_PROVIDER =
112125
"fs.s3a.assumed.role.credentials.provider";
113126

114-
/** JSON policy containing the policy to apply to the role. */
127+
/**
128+
* JSON policy containing the policy to apply to the role: {@value}.
129+
* This is not used for delegation tokens, which generate the policy
130+
* automatically, and restrict it to the S3, KMS and S3Guard services
131+
* needed.
132+
*/
115133
public static final String ASSUMED_ROLE_POLICY =
116134
"fs.s3a.assumed.role.policy";
117135

@@ -318,7 +336,10 @@ private Constants() {
318336
/** Prefix for S3A bucket-specific properties: {@value}. */
319337
public static final String FS_S3A_BUCKET_PREFIX = "fs.s3a.bucket.";
320338

321-
public static final int S3A_DEFAULT_PORT = -1;
339+
/**
340+
* Default port for this is 443: HTTPS.
341+
*/
342+
public static final int S3A_DEFAULT_PORT = 443;
322343

323344
public static final String USER_AGENT_PREFIX = "fs.s3a.user.agent.prefix";
324345

hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/DefaultS3ClientFactory.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
import com.amazonaws.services.s3.S3ClientOptions;
2929
import org.slf4j.Logger;
3030

31+
import org.apache.commons.lang3.StringUtils;
32+
import org.apache.hadoop.classification.InterfaceAudience;
33+
import org.apache.hadoop.classification.InterfaceStability;
3134
import org.apache.hadoop.conf.Configuration;
3235
import org.apache.hadoop.conf.Configured;
3336

@@ -39,6 +42,8 @@
3942
* This which calls the AWS SDK to configure and create an
4043
* {@link AmazonS3Client} that communicates with the S3 service.
4144
*/
45+
@InterfaceAudience.Private
46+
@InterfaceStability.Unstable
4247
public class DefaultS3ClientFactory extends Configured
4348
implements S3ClientFactory {
4449

@@ -47,9 +52,13 @@ public class DefaultS3ClientFactory extends Configured
4752
@Override
4853
public AmazonS3 createS3Client(URI name,
4954
final String bucket,
50-
final AWSCredentialsProvider credentials) throws IOException {
55+
final AWSCredentialsProvider credentials,
56+
final String userAgentSuffix) throws IOException {
5157
Configuration conf = getConf();
5258
final ClientConfiguration awsConf = S3AUtils.createAwsConf(getConf(), bucket);
59+
if (!StringUtils.isEmpty(userAgentSuffix)) {
60+
awsConf.setUserAgentSuffix(userAgentSuffix);
61+
}
5362
return configureAmazonS3Client(
5463
newAmazonS3Client(credentials, awsConf), conf);
5564
}

hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/Invoker.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,7 @@ public void onFailure(String text,
476476
};
477477

478478
/**
479-
* Log summary at info, full stack at debug.
479+
* Log retries at debug.
480480
*/
481481
public static final Retried LOG_EVENT = new Retried() {
482482
@Override

0 commit comments

Comments
 (0)