/** * 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.azure; import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.FS_DEFAULT_NAME_KEY; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.junit.Assume.assumeNotNull; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import java.util.Date; import java.util.EnumSet; import java.io.File; import org.apache.hadoop.security.ProviderUtils; import org.apache.hadoop.security.alias.CredentialProvider; import org.apache.hadoop.security.alias.CredentialProviderFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.AbstractFileSystem; import org.apache.hadoop.fs.FileContext; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.UnsupportedFileSystemException; import org.apache.hadoop.fs.azure.AzureBlobStorageTestAccount.CreateOptions; import org.junit.After; import org.junit.Assert; import org.junit.Assume; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import com.microsoft.azure.storage.blob.CloudBlobContainer; import com.microsoft.azure.storage.blob.CloudBlockBlob; public class TestWasbUriAndConfiguration { private static final int FILE_SIZE = 4096; private static final String PATH_DELIMITER = "/"; protected String accountName; protected String accountKey; protected static Configuration conf = null; private boolean runningInSASMode = false; @Rule public final TemporaryFolder tempDir = new TemporaryFolder(); private AzureBlobStorageTestAccount testAccount; @After public void tearDown() throws Exception { if (testAccount != null) { testAccount.cleanup(); testAccount = null; } } @Before public void setMode() { runningInSASMode = AzureBlobStorageTestAccount.createTestConfiguration(). getBoolean(AzureNativeFileSystemStore.KEY_USE_SECURE_MODE, false); } private boolean validateIOStreams(Path filePath) throws IOException { // Capture the file system from the test account. FileSystem fs = testAccount.getFileSystem(); return validateIOStreams(fs, filePath); } private boolean validateIOStreams(FileSystem fs, Path filePath) throws IOException { // Create and write a file OutputStream outputStream = fs.create(filePath); outputStream.write(new byte[FILE_SIZE]); outputStream.close(); // Return true if the the count is equivalent to the file size. return (FILE_SIZE == readInputStream(fs, filePath)); } private int readInputStream(Path filePath) throws IOException { // Capture the file system from the test account. FileSystem fs = testAccount.getFileSystem(); return readInputStream(fs, filePath); } private int readInputStream(FileSystem fs, Path filePath) throws IOException { // Read the file InputStream inputStream = fs.open(filePath); int count = 0; while (inputStream.read() >= 0) { count++; } inputStream.close(); // Return true if the the count is equivalent to the file size. return count; } // Positive tests to exercise making a connection with to Azure account using // account key. @Test public void testConnectUsingKey() throws Exception { testAccount = AzureBlobStorageTestAccount.create(); assumeNotNull(testAccount); // Validate input and output on the connection. assertTrue(validateIOStreams(new Path("/wasb_scheme"))); } @Test public void testConnectUsingSAS() throws Exception { Assume.assumeFalse(runningInSASMode); // Create the test account with SAS credentials. testAccount = AzureBlobStorageTestAccount.create("", EnumSet.of(CreateOptions.UseSas, CreateOptions.CreateContainer)); assumeNotNull(testAccount); // Validate input and output on the connection. // NOTE: As of 4/15/2013, Azure Storage has a deficiency that prevents the // full scenario from working (CopyFromBlob doesn't work with SAS), so // just do a minor check until that is corrected. assertFalse(testAccount.getFileSystem().exists(new Path("/IDontExist"))); //assertTrue(validateIOStreams(new Path("/sastest.txt"))); } @Test public void testConnectUsingSASReadonly() throws Exception { Assume.assumeFalse(runningInSASMode); // Create the test account with SAS credentials. testAccount = AzureBlobStorageTestAccount.create("", EnumSet.of( CreateOptions.UseSas, CreateOptions.CreateContainer, CreateOptions.Readonly)); assumeNotNull(testAccount); // Create a blob in there final String blobKey = "blobForReadonly"; CloudBlobContainer container = testAccount.getRealContainer(); CloudBlockBlob blob = container.getBlockBlobReference(blobKey); ByteArrayInputStream inputStream = new ByteArrayInputStream(new byte[] { 1, 2, 3 }); blob.upload(inputStream, 3); inputStream.close(); // Make sure we can read it from the file system Path filePath = new Path("/" + blobKey); FileSystem fs = testAccount.getFileSystem(); assertTrue(fs.exists(filePath)); byte[] obtained = new byte[3]; DataInputStream obtainedInputStream = fs.open(filePath); obtainedInputStream.readFully(obtained); obtainedInputStream.close(); assertEquals(3, obtained[2]); } @Test public void testConnectUsingAnonymous() throws Exception { // Create test account with anonymous credentials testAccount = AzureBlobStorageTestAccount.createAnonymous("testWasb.txt", FILE_SIZE); assumeNotNull(testAccount); // Read the file from the public folder using anonymous credentials. assertEquals(FILE_SIZE, readInputStream(new Path("/testWasb.txt"))); } @Test public void testConnectToEmulator() throws Exception { testAccount = AzureBlobStorageTestAccount.createForEmulator(); assumeNotNull(testAccount); assertTrue(validateIOStreams(new Path("/testFile"))); } /** * Tests that we can connect to fully qualified accounts outside of * blob.core.windows.net */ @Test public void testConnectToFullyQualifiedAccountMock() throws Exception { Configuration conf = new Configuration(); AzureBlobStorageTestAccount.setMockAccountKey(conf, "mockAccount.mock.authority.net"); AzureNativeFileSystemStore store = new AzureNativeFileSystemStore(); MockStorageInterface mockStorage = new MockStorageInterface(); store.setAzureStorageInteractionLayer(mockStorage); NativeAzureFileSystem fs = new NativeAzureFileSystem(store); fs.initialize( new URI("wasb://mockContainer@mockAccount.mock.authority.net"), conf); fs.createNewFile(new Path("/x")); assertTrue(mockStorage.getBackingStore().exists( "http://mockAccount.mock.authority.net/mockContainer/x")); fs.close(); } public void testConnectToRoot() throws Exception { // Set up blob names. final String blobPrefix = String.format("wasbtests-%s-%tQ-blob", System.getProperty("user.name"), new Date()); final String inblobName = blobPrefix + "_In" + ".txt"; final String outblobName = blobPrefix + "_Out" + ".txt"; // Create test account with default root access. testAccount = AzureBlobStorageTestAccount.createRoot(inblobName, FILE_SIZE); assumeNotNull(testAccount); // Read the file from the default container. assertEquals(FILE_SIZE, readInputStream(new Path(PATH_DELIMITER + inblobName))); try { // Capture file system. FileSystem fs = testAccount.getFileSystem(); // Create output path and open an output stream to the root folder. Path outputPath = new Path(PATH_DELIMITER + outblobName); OutputStream outputStream = fs.create(outputPath); fail("Expected an AzureException when writing to root folder."); outputStream.write(new byte[FILE_SIZE]); outputStream.close(); } catch (AzureException e) { assertTrue(true); } catch (Exception e) { String errMsg = String.format( "Expected AzureException but got %s instead.", e); assertTrue(errMsg, false); } } // Positive tests to exercise throttling I/O path. Connections are made to an // Azure account using account key. // public void testConnectWithThrottling() throws Exception { testAccount = AzureBlobStorageTestAccount.createThrottled(); // Validate input and output on the connection. assertTrue(validateIOStreams(new Path("/wasb_scheme"))); } /** * Creates a file and writes a single byte with the given value in it. */ private static void writeSingleByte(FileSystem fs, Path testFile, int toWrite) throws Exception { OutputStream outputStream = fs.create(testFile); outputStream.write(toWrite); outputStream.close(); } /** * Reads the file given and makes sure that it's a single-byte file with the * given value in it. */ private static void assertSingleByteValue(FileSystem fs, Path testFile, int expectedValue) throws Exception { InputStream inputStream = fs.open(testFile); int byteRead = inputStream.read(); assertTrue("File unexpectedly empty: " + testFile, byteRead >= 0); assertTrue("File has more than a single byte: " + testFile, inputStream.read() < 0); inputStream.close(); assertEquals("Unxpected content in: " + testFile, expectedValue, byteRead); } @Test public void testMultipleContainers() throws Exception { AzureBlobStorageTestAccount firstAccount = AzureBlobStorageTestAccount .create("first"), secondAccount = AzureBlobStorageTestAccount .create("second"); assumeNotNull(firstAccount); assumeNotNull(secondAccount); try { FileSystem firstFs = firstAccount.getFileSystem(), secondFs = secondAccount.getFileSystem(); Path testFile = new Path("/testWasb"); assertTrue(validateIOStreams(firstFs, testFile)); assertTrue(validateIOStreams(secondFs, testFile)); // Make sure that we're really dealing with two file systems here. writeSingleByte(firstFs, testFile, 5); writeSingleByte(secondFs, testFile, 7); assertSingleByteValue(firstFs, testFile, 5); assertSingleByteValue(secondFs, testFile, 7); } finally { firstAccount.cleanup(); secondAccount.cleanup(); } } @Test public void testDefaultKeyProvider() throws Exception { Configuration conf = new Configuration(); String account = "testacct"; String key = "testkey"; conf.set(SimpleKeyProvider.KEY_ACCOUNT_KEY_PREFIX + account, key); String result = AzureNativeFileSystemStore.getAccountKeyFromConfiguration( account, conf); assertEquals(key, result); } @Test public void testCredsFromCredentialProvider() throws Exception { Assume.assumeFalse(runningInSASMode); String account = "testacct"; String key = "testkey"; // set up conf to have a cred provider final Configuration conf = new Configuration(); final File file = tempDir.newFile("test.jks"); final URI jks = ProviderUtils.nestURIForLocalJavaKeyStoreProvider( file.toURI()); conf.set(CredentialProviderFactory.CREDENTIAL_PROVIDER_PATH, jks.toString()); provisionAccountKey(conf, account, key); // also add to configuration as clear text that should be overridden conf.set(SimpleKeyProvider.KEY_ACCOUNT_KEY_PREFIX + account, key + "cleartext"); String result = AzureNativeFileSystemStore.getAccountKeyFromConfiguration( account, conf); // result should contain the credential provider key not the config key assertEquals("AccountKey incorrect.", key, result); } void provisionAccountKey( final Configuration conf, String account, String key) throws Exception { // add our creds to the provider final CredentialProvider provider = CredentialProviderFactory.getProviders(conf).get(0); provider.createCredentialEntry( SimpleKeyProvider.KEY_ACCOUNT_KEY_PREFIX + account, key.toCharArray()); provider.flush(); } @Test public void testValidKeyProvider() throws Exception { Configuration conf = new Configuration(); String account = "testacct"; String key = "testkey"; conf.set(SimpleKeyProvider.KEY_ACCOUNT_KEY_PREFIX + account, key); conf.setClass("fs.azure.account.keyprovider." + account, SimpleKeyProvider.class, KeyProvider.class); String result = AzureNativeFileSystemStore.getAccountKeyFromConfiguration( account, conf); assertEquals(key, result); } @Test public void testInvalidKeyProviderNonexistantClass() throws Exception { Configuration conf = new Configuration(); String account = "testacct"; conf.set("fs.azure.account.keyprovider." + account, "org.apache.Nonexistant.Class"); try { AzureNativeFileSystemStore.getAccountKeyFromConfiguration(account, conf); Assert.fail("Nonexistant key provider class should have thrown a " + "KeyProviderException"); } catch (KeyProviderException e) { } } @Test public void testInvalidKeyProviderWrongClass() throws Exception { Configuration conf = new Configuration(); String account = "testacct"; conf.set("fs.azure.account.keyprovider." + account, "java.lang.String"); try { AzureNativeFileSystemStore.getAccountKeyFromConfiguration(account, conf); Assert.fail("Key provider class that doesn't implement KeyProvider " + "should have thrown a KeyProviderException"); } catch (KeyProviderException e) { } } /** * Tests the cases when the URI is specified with no authority, i.e. * wasb:///path/to/file. */ @Test public void testNoUriAuthority() throws Exception { // For any combination of default FS being asv(s)/wasb(s)://c@a/ and // the actual URI being asv(s)/wasb(s):///, it should work. String[] wasbAliases = new String[] { "wasb", "wasbs" }; for (String defaultScheme : wasbAliases) { for (String wantedScheme : wasbAliases) { testAccount = AzureBlobStorageTestAccount.createMock(); Configuration conf = testAccount.getFileSystem().getConf(); String authority = testAccount.getFileSystem().getUri().getAuthority(); URI defaultUri = new URI(defaultScheme, authority, null, null, null); conf.set(FS_DEFAULT_NAME_KEY, defaultUri.toString()); // Add references to file system implementations for wasb and wasbs. conf.addResource("azure-test.xml"); URI wantedUri = new URI(wantedScheme + ":///random/path"); NativeAzureFileSystem obtained = (NativeAzureFileSystem) FileSystem .get(wantedUri, conf); assertNotNull(obtained); assertEquals(new URI(wantedScheme, authority, null, null, null), obtained.getUri()); // Make sure makeQualified works as expected Path qualified = obtained.makeQualified(new Path(wantedUri)); assertEquals(new URI(wantedScheme, authority, wantedUri.getPath(), null, null), qualified.toUri()); // Cleanup for the next iteration to not cache anything in FS testAccount.cleanup(); FileSystem.closeAll(); } } // If the default FS is not a WASB FS, then specifying a URI without // authority for the Azure file system should throw. testAccount = AzureBlobStorageTestAccount.createMock(); Configuration conf = testAccount.getFileSystem().getConf(); conf.set(FS_DEFAULT_NAME_KEY, "file:///"); try { FileSystem.get(new URI("wasb:///random/path"), conf); fail("Should've thrown."); } catch (IllegalArgumentException e) { } } @Test public void testWasbAsDefaultFileSystemHasNoPort() throws Exception { try { testAccount = AzureBlobStorageTestAccount.createMock(); Configuration conf = testAccount.getFileSystem().getConf(); String authority = testAccount.getFileSystem().getUri().getAuthority(); URI defaultUri = new URI("wasb", authority, null, null, null); conf.set(FS_DEFAULT_NAME_KEY, defaultUri.toString()); conf.addResource("azure-test.xml"); FileSystem fs = FileSystem.get(conf); assertTrue(fs instanceof NativeAzureFileSystem); assertEquals(-1, fs.getUri().getPort()); AbstractFileSystem afs = FileContext.getFileContext(conf) .getDefaultFileSystem(); assertTrue(afs instanceof Wasb); assertEquals(-1, afs.getUri().getPort()); } finally { testAccount.cleanup(); FileSystem.closeAll(); } } /** * Tests the cases when the scheme specified is 'wasbs'. */ @Test public void testAbstractFileSystemImplementationForWasbsScheme() throws Exception { try { testAccount = AzureBlobStorageTestAccount.createMock(); Configuration conf = testAccount.getFileSystem().getConf(); String authority = testAccount.getFileSystem().getUri().getAuthority(); URI defaultUri = new URI("wasbs", authority, null, null, null); conf.set(FS_DEFAULT_NAME_KEY, defaultUri.toString()); conf.set("fs.AbstractFileSystem.wasbs.impl", "org.apache.hadoop.fs.azure.Wasbs"); conf.addResource("azure-test.xml"); FileSystem fs = FileSystem.get(conf); assertTrue(fs instanceof NativeAzureFileSystem); assertEquals("wasbs", fs.getScheme()); AbstractFileSystem afs = FileContext.getFileContext(conf) .getDefaultFileSystem(); assertTrue(afs instanceof Wasbs); assertEquals(-1, afs.getUri().getPort()); assertEquals("wasbs", afs.getUri().getScheme()); } finally { testAccount.cleanup(); FileSystem.closeAll(); } } @Test public void testNoAbstractFileSystemImplementationSpecifiedForWasbsScheme() throws Exception { try { testAccount = AzureBlobStorageTestAccount.createMock(); Configuration conf = testAccount.getFileSystem().getConf(); String authority = testAccount.getFileSystem().getUri().getAuthority(); URI defaultUri = new URI("wasbs", authority, null, null, null); conf.set(FS_DEFAULT_NAME_KEY, defaultUri.toString()); FileSystem fs = FileSystem.get(conf); assertTrue(fs instanceof NativeAzureFileSystem); assertEquals("wasbs", fs.getScheme()); // should throw if 'fs.AbstractFileSystem.wasbs.impl'' is not specified try{ FileContext.getFileContext(conf).getDefaultFileSystem(); fail("Should've thrown."); }catch(UnsupportedFileSystemException e){ } } finally { testAccount.cleanup(); FileSystem.closeAll(); } } @Test public void testCredentialProviderPathExclusions() throws Exception { String providerPath = "user:///,jceks://wasb/user/hrt_qa/sqoopdbpasswd.jceks," + "jceks://hdfs@nn1.example.com/my/path/test.jceks"; Configuration config = new Configuration(); config.set(CredentialProviderFactory.CREDENTIAL_PROVIDER_PATH, providerPath); String newPath = "user:///,jceks://hdfs@nn1.example.com/my/path/test.jceks"; excludeAndTestExpectations(config, newPath); } @Test public void testExcludeAllProviderTypesFromConfig() throws Exception { String providerPath = "jceks://wasb/tmp/test.jceks," + "jceks://wasb@/my/path/test.jceks"; Configuration config = new Configuration(); config.set(CredentialProviderFactory.CREDENTIAL_PROVIDER_PATH, providerPath); String newPath = null; excludeAndTestExpectations(config, newPath); } void excludeAndTestExpectations(Configuration config, String newPath) throws Exception { Configuration conf = ProviderUtils.excludeIncompatibleCredentialProviders( config, NativeAzureFileSystem.class); String effectivePath = conf.get( CredentialProviderFactory.CREDENTIAL_PROVIDER_PATH, null); assertEquals(newPath, effectivePath); } }