HADOOP-14461 Azure: handle failure gracefully in case of missing account access key.

Contributed by Mingliang Liu.
This commit is contained in:
Steve Loughran 2017-06-26 13:39:47 +01:00
parent 379f19a2c7
commit 48f4a229a9
No known key found for this signature in database
GPG Key ID: 950CC3E032B79CA2
4 changed files with 59 additions and 46 deletions

View File

@ -303,6 +303,14 @@ public class AzureNativeFileSystemStore implements NativeFileSystemStore {
private boolean useLocalSasKeyMode = false; private boolean useLocalSasKeyMode = false;
private String delegationToken; private String delegationToken;
/** The error message template when container is not accessible. */
static final String NO_ACCESS_TO_CONTAINER_MSG = "No credentials found for "
+ "account %s in the configuration, and its container %s is not "
+ "accessible using anonymous credentials. Please check if the container "
+ "exists first. If it is not publicly available, you have to provide "
+ "account credentials.";
/** /**
* A test hook interface that can modify the operation context we use for * A test hook interface that can modify the operation context we use for
* Azure Storage operations, e.g. to inject errors. * Azure Storage operations, e.g. to inject errors.
@ -778,18 +786,17 @@ public class AzureNativeFileSystemStore implements NativeFileSystemStore {
rootDirectory = container.getDirectoryReference(""); rootDirectory = container.getDirectoryReference("");
// Check for container existence, and our ability to access it. // Check for container existence, and our ability to access it.
boolean canAccess;
try { try {
if (!container.exists(getInstrumentedContext())) { canAccess = container.exists(getInstrumentedContext());
throw new AzureException("Container " + containerName + " in account "
+ accountName + " not found, and we can't create"
+ " it using anoynomous credentials, and no credentials found for them"
+ " in the configuration.");
}
} catch (StorageException ex) { } catch (StorageException ex) {
throw new AzureException("Unable to access container " + containerName LOG.error("Service returned StorageException when checking existence "
+ " in account " + accountName + "of container {} in account {}", containerName, accountName, ex);
+ " using anonymous credentials, and no credentials found for them " canAccess = false;
+ " in the configuration.", ex); }
if (!canAccess) {
throw new AzureException(String.format(NO_ACCESS_TO_CONTAINER_MSG,
accountName, containerName));
} }
// Accessing the storage server unauthenticated using // Accessing the storage server unauthenticated using
@ -999,22 +1006,17 @@ public class AzureNativeFileSystemStore implements NativeFileSystemStore {
// Check whether the account is configured with an account key. // Check whether the account is configured with an account key.
propertyValue = getAccountKeyFromConfiguration(accountName, propertyValue = getAccountKeyFromConfiguration(accountName,
sessionConfiguration); sessionConfiguration);
if (propertyValue != null) { if (StringUtils.isNotEmpty(propertyValue)) {
// Account key was found. // Account key was found.
// Create the Azure storage session using the account key and container. // Create the Azure storage session using the account key and container.
connectUsingConnectionStringCredentials( connectUsingConnectionStringCredentials(
getAccountFromAuthority(sessionUri), getAccountFromAuthority(sessionUri),
getContainerFromAuthority(sessionUri), propertyValue); getContainerFromAuthority(sessionUri), propertyValue);
} else {
// Return to caller LOG.debug("The account access key is not configured for {}. "
return; + "Now try anonymous access.", sessionUri);
connectUsingAnonymousCredentials(sessionUri);
} }
// The account access is not configured for this cluster. Try anonymous
// access.
connectUsingAnonymousCredentials(sessionUri);
} catch (Exception e) { } catch (Exception e) {
// Caught exception while attempting to initialize the Azure File // Caught exception while attempting to initialize the Azure File
// System store, re-throw the exception. // System store, re-throw the exception.

View File

@ -22,6 +22,9 @@ import com.microsoft.azure.storage.*;
import com.microsoft.azure.storage.blob.*; import com.microsoft.azure.storage.blob.*;
import com.microsoft.azure.storage.core.Base64; import com.microsoft.azure.storage.core.Base64;
import org.apache.commons.configuration2.SubsetConfiguration; import org.apache.commons.configuration2.SubsetConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.azure.metrics.AzureFileSystemInstrumentation; import org.apache.hadoop.fs.azure.metrics.AzureFileSystemInstrumentation;
@ -46,6 +49,8 @@ import static org.apache.hadoop.fs.azure.AzureNativeFileSystemStore.KEY_USE_LOCA
* for instructions on how to connect to a real Azure Storage account. * for instructions on how to connect to a real Azure Storage account.
*/ */
public final class AzureBlobStorageTestAccount { public final class AzureBlobStorageTestAccount {
private static final Logger LOG = LoggerFactory.getLogger(
AzureBlobStorageTestAccount.class);
private static final String ACCOUNT_KEY_PROPERTY_NAME = "fs.azure.account.key."; private static final String ACCOUNT_KEY_PROPERTY_NAME = "fs.azure.account.key.";
private static final String SAS_PROPERTY_NAME = "fs.azure.sas."; private static final String SAS_PROPERTY_NAME = "fs.azure.sas.";
@ -299,10 +304,9 @@ public final class AzureBlobStorageTestAccount {
Configuration conf = createTestConfiguration(); Configuration conf = createTestConfiguration();
if (!conf.getBoolean(USE_EMULATOR_PROPERTY_NAME, false)) { if (!conf.getBoolean(USE_EMULATOR_PROPERTY_NAME, false)) {
// Not configured to test against the storage emulator. // Not configured to test against the storage emulator.
System.out LOG.warn("Skipping emulator Azure test because configuration doesn't "
.println("Skipping emulator Azure test because configuration " + + "indicate that it's running. Please see RunningLiveWasbTests.txt "
"doesn't indicate that it's running." + + "for guidance.");
" Please see RunningLiveWasbTests.txt for guidance.");
return null; return null;
} }
CloudStorageAccount account = CloudStorageAccount account =
@ -456,18 +460,22 @@ public final class AzureBlobStorageTestAccount {
KeyProviderException { KeyProviderException {
String accountKey = AzureNativeFileSystemStore String accountKey = AzureNativeFileSystemStore
.getAccountKeyFromConfiguration(accountName, conf); .getAccountKeyFromConfiguration(accountName, conf);
StorageCredentials credentials; final StorageCredentials credentials;
if (accountKey == null && allowAnonymous) { if (accountKey == null) {
credentials = StorageCredentialsAnonymous.ANONYMOUS; if (allowAnonymous) {
credentials = StorageCredentialsAnonymous.ANONYMOUS;
} else {
LOG.warn("Skipping live Azure test because of missing key for"
+ " account '" + accountName + "'. "
+ "Please see RunningLiveWasbTests.txt for guidance.");
return null;
}
} else { } else {
credentials = new StorageCredentialsAccountAndKey( credentials = new StorageCredentialsAccountAndKey(
accountName.split("\\.")[0], accountKey); accountName.split("\\.")[0], accountKey);
} }
if (credentials == null) {
return null; return new CloudStorageAccount(credentials);
} else {
return new CloudStorageAccount(credentials);
}
} }
public static Configuration createTestConfiguration() { public static Configuration createTestConfiguration() {
@ -493,9 +501,8 @@ public final class AzureBlobStorageTestAccount {
throws URISyntaxException, KeyProviderException { throws URISyntaxException, KeyProviderException {
String testAccountName = conf.get(TEST_ACCOUNT_NAME_PROPERTY_NAME); String testAccountName = conf.get(TEST_ACCOUNT_NAME_PROPERTY_NAME);
if (testAccountName == null) { if (testAccountName == null) {
System.out LOG.warn("Skipping live Azure test because of missing test account. "
.println("Skipping live Azure test because of missing test account." + + "Please see RunningLiveWasbTests.txt for guidance.");
" Please see RunningLiveWasbTests.txt for guidance.");
return null; return null;
} }
return createStorageAccount(testAccountName, conf, false); return createStorageAccount(testAccountName, conf, false);

View File

@ -18,6 +18,7 @@
package org.apache.hadoop.fs.azure; package org.apache.hadoop.fs.azure;
import static org.apache.hadoop.fs.azure.AzureNativeFileSystemStore.NO_ACCESS_TO_CONTAINER_MSG;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@ -35,6 +36,8 @@ import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.azure.AzureNativeFileSystemStore.TestHookOperationContext; import org.apache.hadoop.fs.azure.AzureNativeFileSystemStore.TestHookOperationContext;
import org.apache.hadoop.test.GenericTestUtils;
import org.junit.Test; import org.junit.Test;
import com.microsoft.azure.storage.OperationContext; import com.microsoft.azure.storage.OperationContext;
@ -64,18 +67,18 @@ public class TestAzureFileSystemErrorConditions {
*/ */
@Test @Test
public void testAccessUnauthorizedPublicContainer() throws Exception { public void testAccessUnauthorizedPublicContainer() throws Exception {
final String container = "nonExistentContainer";
final String account = "hopefullyNonExistentAccount";
Path noAccessPath = new Path( Path noAccessPath = new Path(
"wasb://nonExistentContainer@hopefullyNonExistentAccount/someFile"); "wasb://" + container + "@" + account + "/someFile");
NativeAzureFileSystem.suppressRetryPolicy(); NativeAzureFileSystem.suppressRetryPolicy();
try { try {
FileSystem.get(noAccessPath.toUri(), new Configuration()) FileSystem.get(noAccessPath.toUri(), new Configuration())
.open(noAccessPath); .open(noAccessPath);
assertTrue("Should've thrown.", false); assertTrue("Should've thrown.", false);
} catch (AzureException ex) { } catch (AzureException ex) {
assertTrue("Unexpected message in exception " + ex, GenericTestUtils.assertExceptionContains(
ex.getMessage().contains( String.format(NO_ACCESS_TO_CONTAINER_MSG, account, container), ex);
"Unable to access container nonExistentContainer in account" +
" hopefullyNonExistentAccount"));
} finally { } finally {
NativeAzureFileSystem.resumeRetryPolicy(); NativeAzureFileSystem.resumeRetryPolicy();
} }

View File

@ -21,9 +21,13 @@ import java.net.URI;
import java.util.UUID; import java.util.UUID;
import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.test.GenericTestUtils;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import static org.apache.hadoop.fs.azure.AzureNativeFileSystemStore.NO_ACCESS_TO_CONTAINER_MSG;
public class TestFileSystemOperationExceptionMessage extends public class TestFileSystemOperationExceptionMessage extends
NativeAzureFileSystemBaseTest { NativeAzureFileSystemBaseTest {
@ -41,11 +45,6 @@ public class TestFileSystemOperationExceptionMessage extends
String wasbUri = String.format("wasb://%s@%s", String wasbUri = String.format("wasb://%s@%s",
testContainer, testStorageAccount); testContainer, testStorageAccount);
String expectedErrorMessage =
String.format("Container %s in account %s not found, and we can't create it "
+ "using anoynomous credentials, and no credentials found for "
+ "them in the configuration.", testContainer, testStorageAccount);
fs = new NativeAzureFileSystem(); fs = new NativeAzureFileSystem();
try { try {
fs.initialize(new URI(wasbUri), conf); fs.initialize(new URI(wasbUri), conf);
@ -63,7 +62,9 @@ public class TestFileSystemOperationExceptionMessage extends
|| exceptionMessage.length() == 0) { || exceptionMessage.length() == 0) {
Assert.fail();} Assert.fail();}
else { else {
Assert.assertTrue(exceptionMessage.equals(expectedErrorMessage)); GenericTestUtils.assertExceptionContains(String.format(
NO_ACCESS_TO_CONTAINER_MSG, testStorageAccount, testContainer),
ex);
} }
} else { } else {
Assert.fail(); Assert.fail();