blob: 6030005d10fb3a0f98369f53acdf462750e011a2 [file] [log] [blame]
/**
* 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.hadoop.fs.s3a;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.URI;
import java.nio.file.AccessDeniedException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.EnvironmentVariableCredentialsProvider;
import com.amazonaws.auth.InstanceProfileCredentialsProvider;
import org.apache.hadoop.util.Sets;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.s3a.auth.AssumedRoleCredentialProvider;
import org.apache.hadoop.fs.s3a.auth.NoAuthWithAWSException;
import org.apache.hadoop.io.retry.RetryPolicy;
import static org.apache.hadoop.fs.s3a.Constants.*;
import static org.apache.hadoop.fs.s3a.S3ATestConstants.*;
import static org.apache.hadoop.fs.s3a.S3ATestUtils.*;
import static org.apache.hadoop.fs.s3a.S3AUtils.*;
import static org.apache.hadoop.test.LambdaTestUtils.intercept;
import static org.junit.Assert.*;
/**
* Unit tests for {@link Constants#AWS_CREDENTIALS_PROVIDER} logic.
*/
public class TestS3AAWSCredentialsProvider {
/**
* URI of the landsat images.
*/
private static final URI TESTFILE_URI = new Path(
DEFAULT_CSVTEST_FILE).toUri();
@Rule
public ExpectedException exception = ExpectedException.none();
@Test
public void testProviderWrongClass() throws Exception {
expectProviderInstantiationFailure(this.getClass(),
NOT_AWS_PROVIDER);
}
@Test
public void testProviderAbstractClass() throws Exception {
expectProviderInstantiationFailure(AbstractProvider.class,
ABSTRACT_PROVIDER);
}
@Test
public void testProviderNotAClass() throws Exception {
expectProviderInstantiationFailure("NoSuchClass",
"ClassNotFoundException");
}
@Test
public void testProviderConstructorError() throws Exception {
expectProviderInstantiationFailure(
ConstructorSignatureErrorProvider.class,
CONSTRUCTOR_EXCEPTION);
}
@Test
public void testProviderFailureError() throws Exception {
expectProviderInstantiationFailure(
ConstructorFailureProvider.class,
INSTANTIATION_EXCEPTION);
}
@Test
@SuppressWarnings("deprecation")
public void testInstantiationChain() throws Throwable {
Configuration conf = new Configuration(false);
conf.set(AWS_CREDENTIALS_PROVIDER,
TemporaryAWSCredentialsProvider.NAME
+ ", \t" + SimpleAWSCredentialsProvider.NAME
+ " ,\n " + AnonymousAWSCredentialsProvider.NAME);
Path testFile = getCSVTestPath(conf);
AWSCredentialProviderList list = createAWSCredentialProviderSet(
testFile.toUri(), conf);
List<Class<?>> expectedClasses =
Arrays.asList(
TemporaryAWSCredentialsProvider.class,
SimpleAWSCredentialsProvider.class,
AnonymousAWSCredentialsProvider.class);
assertCredentialProviders(expectedClasses, list);
}
@Test
@SuppressWarnings("deprecation")
public void testDefaultChain() throws Exception {
URI uri1 = new URI("s3a://bucket1"), uri2 = new URI("s3a://bucket2");
Configuration conf = new Configuration(false);
// use the default credential provider chain
conf.unset(AWS_CREDENTIALS_PROVIDER);
AWSCredentialProviderList list1 = createAWSCredentialProviderSet(
uri1, conf);
AWSCredentialProviderList list2 = createAWSCredentialProviderSet(
uri2, conf);
List<Class<?>> expectedClasses = STANDARD_AWS_PROVIDERS;
assertCredentialProviders(expectedClasses, list1);
assertCredentialProviders(expectedClasses, list2);
}
@Test
public void testDefaultChainNoURI() throws Exception {
Configuration conf = new Configuration(false);
// use the default credential provider chain
conf.unset(AWS_CREDENTIALS_PROVIDER);
assertCredentialProviders(STANDARD_AWS_PROVIDERS,
createAWSCredentialProviderSet(null, conf));
}
@Test
@SuppressWarnings("deprecation")
public void testConfiguredChain() throws Exception {
URI uri1 = new URI("s3a://bucket1"), uri2 = new URI("s3a://bucket2");
List<Class<?>> expectedClasses =
Arrays.asList(
EnvironmentVariableCredentialsProvider.class,
InstanceProfileCredentialsProvider.class,
AnonymousAWSCredentialsProvider.class);
Configuration conf =
createProviderConfiguration(buildClassListString(expectedClasses));
AWSCredentialProviderList list1 = createAWSCredentialProviderSet(
uri1, conf);
AWSCredentialProviderList list2 = createAWSCredentialProviderSet(
uri2, conf);
assertCredentialProviders(expectedClasses, list1);
assertCredentialProviders(expectedClasses, list2);
}
@Test
@SuppressWarnings("deprecation")
public void testConfiguredChainUsesSharedInstanceProfile() throws Exception {
URI uri1 = new URI("s3a://bucket1"), uri2 = new URI("s3a://bucket2");
Configuration conf = new Configuration(false);
List<Class<?>> expectedClasses =
Arrays.asList(
InstanceProfileCredentialsProvider.class);
conf.set(AWS_CREDENTIALS_PROVIDER, buildClassListString(expectedClasses));
AWSCredentialProviderList list1 = createAWSCredentialProviderSet(
uri1, conf);
AWSCredentialProviderList list2 = createAWSCredentialProviderSet(
uri2, conf);
assertCredentialProviders(expectedClasses, list1);
assertCredentialProviders(expectedClasses, list2);
}
@Test
public void testFallbackToDefaults() throws Throwable {
// build up the base provider
final AWSCredentialProviderList credentials = buildAWSProviderList(
new URI("s3a://bucket1"),
createProviderConfiguration(" "),
ASSUMED_ROLE_CREDENTIALS_PROVIDER,
Arrays.asList(
EnvironmentVariableCredentialsProvider.class),
Sets.newHashSet());
assertTrue("empty credentials", credentials.size() > 0);
}
/**
* A credential provider declared as abstract, so it cannot be instantiated.
*/
static abstract class AbstractProvider implements AWSCredentialsProvider {
}
/**
* A credential provider whose constructor signature doesn't match.
*/
static class ConstructorSignatureErrorProvider
implements AWSCredentialsProvider {
@SuppressWarnings("unused")
public ConstructorSignatureErrorProvider(String str) {
}
@Override
public AWSCredentials getCredentials() {
return null;
}
@Override
public void refresh() {
}
}
/**
* A credential provider whose constructor raises an NPE.
*/
static class ConstructorFailureProvider
implements AWSCredentialsProvider {
@SuppressWarnings("unused")
public ConstructorFailureProvider() {
throw new NullPointerException("oops");
}
@Override
public AWSCredentials getCredentials() {
return null;
}
@Override
public void refresh() {
}
}
@Test
public void testAWSExceptionTranslation() throws Throwable {
IOException ex = expectProviderInstantiationFailure(
AWSExceptionRaisingFactory.class,
AWSExceptionRaisingFactory.NO_AUTH);
if (!(ex instanceof AccessDeniedException)) {
throw ex;
}
}
static class AWSExceptionRaisingFactory implements AWSCredentialsProvider {
public static final String NO_AUTH = "No auth";
public static AWSCredentialsProvider getInstance() {
throw new NoAuthWithAWSException(NO_AUTH);
}
@Override
public AWSCredentials getCredentials() {
return null;
}
@Override
public void refresh() {
}
}
@Test
public void testFactoryWrongType() throws Throwable {
expectProviderInstantiationFailure(
FactoryOfWrongType.class,
CONSTRUCTOR_EXCEPTION);
}
static class FactoryOfWrongType implements AWSCredentialsProvider {
public static final String NO_AUTH = "No auth";
public static String getInstance() {
return "oops";
}
@Override
public AWSCredentials getCredentials() {
return null;
}
@Override
public void refresh() {
}
}
/**
* Expect a provider to raise an exception on failure.
* @param option aws provider option string.
* @param expectedErrorText error text to expect
* @return the exception raised
* @throws Exception any unexpected exception thrown.
*/
private IOException expectProviderInstantiationFailure(String option,
String expectedErrorText) throws Exception {
return intercept(IOException.class, expectedErrorText,
() -> createAWSCredentialProviderSet(
TESTFILE_URI,
createProviderConfiguration(option)));
}
/**
* Expect a provider to raise an exception on failure.
* @param aClass class to use
* @param expectedErrorText error text to expect
* @return the exception raised
* @throws Exception any unexpected exception thrown.
*/
private IOException expectProviderInstantiationFailure(Class aClass,
String expectedErrorText) throws Exception {
return expectProviderInstantiationFailure(
buildClassListString(Collections.singletonList(aClass)),
expectedErrorText);
}
/**
* Create a configuration with a specific provider.
* @param providerOption option for the aws credential provider option.
* @return a configuration to use in test cases
*/
private Configuration createProviderConfiguration(
final String providerOption) {
Configuration conf = new Configuration(false);
conf.set(AWS_CREDENTIALS_PROVIDER, providerOption);
return conf;
}
/**
* Create a configuration with a specific class.
* @param aClass class to use
* @return a configuration to use in test cases
*/
public Configuration createProviderConfiguration(final Class<?> aClass) {
return createProviderConfiguration(buildClassListString(
Collections.singletonList(aClass)));
}
/**
* Asserts expected provider classes in list.
* @param expectedClasses expected provider classes
* @param list providers to check
*/
private static void assertCredentialProviders(
List<Class<?>> expectedClasses,
AWSCredentialProviderList list) {
assertNotNull(list);
List<AWSCredentialsProvider> providers = list.getProviders();
assertEquals(expectedClasses.size(), providers.size());
for (int i = 0; i < expectedClasses.size(); ++i) {
Class<?> expectedClass =
expectedClasses.get(i);
AWSCredentialsProvider provider = providers.get(i);
assertNotNull(
String.format("At position %d, expected class is %s, but found null.",
i, expectedClass), provider);
assertTrue(
String.format("At position %d, expected class is %s, but found %s.",
i, expectedClass, provider.getClass()),
expectedClass.isAssignableFrom(provider.getClass()));
}
}
/**
* This is here to check up on the S3ATestUtils probes themselves.
* @see S3ATestUtils#authenticationContains(Configuration, String).
*/
@Test
@SuppressWarnings("deprecation")
public void testAuthenticationContainsProbes() {
Configuration conf = new Configuration(false);
assertFalse("found AssumedRoleCredentialProvider",
authenticationContains(conf, AssumedRoleCredentialProvider.NAME));
conf.set(AWS_CREDENTIALS_PROVIDER, AssumedRoleCredentialProvider.NAME);
assertTrue("didn't find AssumedRoleCredentialProvider",
authenticationContains(conf, AssumedRoleCredentialProvider.NAME));
}
@Test
public void testExceptionLogic() throws Throwable {
AWSCredentialProviderList providers
= new AWSCredentialProviderList();
// verify you can't get credentials from it
NoAuthWithAWSException noAuth = intercept(NoAuthWithAWSException.class,
AWSCredentialProviderList.NO_AWS_CREDENTIAL_PROVIDERS,
() -> providers.getCredentials());
// but that it closes safely
providers.close();
S3ARetryPolicy retryPolicy = new S3ARetryPolicy(new Configuration(false));
assertEquals("Expected no retry on auth failure",
RetryPolicy.RetryAction.FAIL.action,
retryPolicy.shouldRetry(noAuth, 0, 0, true).action);
try {
throw S3AUtils.translateException("login", "", noAuth);
} catch (AccessDeniedException expected) {
// this is what we want; other exceptions will be passed up
assertEquals("Expected no retry on AccessDeniedException",
RetryPolicy.RetryAction.FAIL.action,
retryPolicy.shouldRetry(expected, 0, 0, true).action);
}
}
@Test
public void testRefCounting() throws Throwable {
AWSCredentialProviderList providers
= new AWSCredentialProviderList();
assertEquals("Ref count for " + providers,
1, providers.getRefCount());
AWSCredentialProviderList replicate = providers.share();
assertEquals(providers, replicate);
assertEquals("Ref count after replication for " + providers,
2, providers.getRefCount());
assertFalse("Was closed " + providers, providers.isClosed());
providers.close();
assertFalse("Was closed " + providers, providers.isClosed());
assertEquals("Ref count after close() for " + providers,
1, providers.getRefCount());
// this should now close it
providers.close();
assertTrue("Was not closed " + providers, providers.isClosed());
assertEquals("Ref count after close() for " + providers,
0, providers.getRefCount());
assertEquals("Ref count after second close() for " + providers,
0, providers.getRefCount());
intercept(IllegalStateException.class, "closed",
() -> providers.share());
// final call harmless
providers.close();
assertEquals("Ref count after close() for " + providers,
0, providers.getRefCount());
providers.refresh();
intercept(NoAuthWithAWSException.class,
AWSCredentialProviderList.CREDENTIALS_REQUESTED_WHEN_CLOSED,
() -> providers.getCredentials());
}
/**
* Verify that IOEs are passed up without being wrapped.
*/
@Test
public void testIOEInConstructorPropagation() throws Throwable {
IOException expected = expectProviderInstantiationFailure(
IOERaisingProvider.class.getName(),
"expected");
if (!(expected instanceof InterruptedIOException)) {
throw expected;
}
}
/**
* Credential provider which raises an IOE when constructed.
*/
private static class IOERaisingProvider implements AWSCredentialsProvider {
public IOERaisingProvider(URI uri, Configuration conf)
throws IOException {
throw new InterruptedIOException("expected");
}
@Override
public AWSCredentials getCredentials() {
return null;
}
@Override
public void refresh() {
}
}
}