/** * 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.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.io.OutputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.net.URLDecoder; import java.util.HashMap; import java.util.Iterator; import java.util.concurrent.ConcurrentLinkedQueue; import org.apache.hadoop.fs.FSDataOutputStream; import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.util.StringUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; public class TestNativeAzureFileSystemConcurrency { private AzureBlobStorageTestAccount testAccount; private FileSystem fs; private InMemoryBlockBlobStore backingStore; @Before public void setUp() throws Exception { testAccount = AzureBlobStorageTestAccount.createMock(); fs = testAccount.getFileSystem(); backingStore = testAccount.getMockStorage().getBackingStore(); } @After public void tearDown() throws Exception { testAccount.cleanup(); fs = null; backingStore = null; } @Test public void testLinkBlobs() throws Exception { Path filePath = new Path("/inProgress"); FSDataOutputStream outputStream = fs.create(filePath); // Since the stream is still open, we should see an empty link // blob in the backing store linking to the temporary file. HashMap<String, String> metadata = backingStore .getMetadata(AzureBlobStorageTestAccount.toMockUri(filePath)); assertNotNull(metadata); String linkValue = metadata.get(AzureNativeFileSystemStore.LINK_BACK_TO_UPLOAD_IN_PROGRESS_METADATA_KEY); linkValue = URLDecoder.decode(linkValue, "UTF-8"); assertNotNull(linkValue); assertTrue(backingStore.exists(AzureBlobStorageTestAccount .toMockUri(linkValue))); // Also, WASB should say the file exists now even before we close the // stream. assertTrue(fs.exists(filePath)); outputStream.close(); // Now there should be no link metadata on the final file. metadata = backingStore.getMetadata(AzureBlobStorageTestAccount .toMockUri(filePath)); assertNull(metadata .get(AzureNativeFileSystemStore.LINK_BACK_TO_UPLOAD_IN_PROGRESS_METADATA_KEY)); } private static String toString(FileStatus[] list) { String[] asStrings = new String[list.length]; for (int i = 0; i < list.length; i++) { asStrings[i] = list[i].getPath().toString(); } return StringUtils.join(",", asStrings); } /** * Test to make sure that we don't expose the temporary upload folder when * listing at the root. */ @Test public void testNoTempBlobsVisible() throws Exception { Path filePath = new Path("/inProgress"); FSDataOutputStream outputStream = fs.create(filePath); // Make sure I can't see the temporary blob if I ask for a listing FileStatus[] listOfRoot = fs.listStatus(new Path("/")); assertEquals("Expected one file listed, instead got: " + toString(listOfRoot), 1, listOfRoot.length); assertEquals(fs.makeQualified(filePath), listOfRoot[0].getPath()); outputStream.close(); } /** * Converts a collection of exceptions to a collection of strings by getting * the stack trace on every exception. */ private static Iterable<String> selectToString( final Iterable<Throwable> collection) { return new Iterable<String>() { @Override public Iterator<String> iterator() { final Iterator<Throwable> exceptionIterator = collection.iterator(); return new Iterator<String>() { @Override public boolean hasNext() { return exceptionIterator.hasNext(); } @Override public String next() { StringWriter stringWriter = new StringWriter(); PrintWriter printWriter = new PrintWriter(stringWriter); exceptionIterator.next().printStackTrace(printWriter); printWriter.close(); return stringWriter.toString(); } @Override public void remove() { exceptionIterator.remove(); } }; } }; } /** * Tests running starting multiple threads all doing various File system * operations against the same FS. */ @Test public void testMultiThreadedOperation() throws Exception { for (int iter = 0; iter < 10; iter++) { final int numThreads = 20; Thread[] threads = new Thread[numThreads]; final ConcurrentLinkedQueue<Throwable> exceptionsEncountered = new ConcurrentLinkedQueue<Throwable>(); for (int i = 0; i < numThreads; i++) { final Path threadLocalFile = new Path("/myFile" + i); threads[i] = new Thread(new Runnable() { @Override public void run() { try { assertTrue(!fs.exists(threadLocalFile)); OutputStream output = fs.create(threadLocalFile); output.write(5); output.close(); assertTrue(fs.exists(threadLocalFile)); assertTrue(fs.listStatus(new Path("/")).length > 0); } catch (Throwable ex) { exceptionsEncountered.add(ex); } } }); } for (Thread t : threads) { t.start(); } for (Thread t : threads) { t.join(); } assertTrue( "Encountered exceptions: " + StringUtils.join("\r\n", selectToString(exceptionsEncountered)), exceptionsEncountered.isEmpty()); tearDown(); setUp(); } } }