/** * Copyright Microsoft Corporation * * Licensed 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 com.microsoft.azure.storage.blob; import com.microsoft.azure.storage.AccessCondition; import com.microsoft.azure.storage.Constants; import com.microsoft.azure.storage.OperationContext; import com.microsoft.azure.storage.ResponseReceivedEvent; import com.microsoft.azure.storage.StorageEvent; import com.microsoft.azure.storage.StorageException; import com.microsoft.azure.storage.TestRunners.CloudTests; import com.microsoft.azure.storage.core.SR; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.experimental.categories.Category; import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Date; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorCompletionService; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import static org.junit.Assert.*; /** * Blob Output Stream Tests */ @Category({ CloudTests.class }) public class BlobOutputStreamTests { protected CloudBlobContainer container; @Before public void blobOutputStreamTestMethodSetUp() throws URISyntaxException, StorageException { this.container = BlobTestHelper.getRandomContainerReference(); this.container.create(); } @After public void blobOutputStreamTestMethodTearDown() throws StorageException { this.container.deleteIfExists(); } @Test public void testEmpty() throws URISyntaxException, StorageException, IOException { String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testblob"); CloudBlockBlob blockBlob = this.container.getBlockBlobReference(blobName); BlobOutputStream str = blockBlob.openOutputStream(); str.close(); CloudBlockBlob blockBlob2 = this.container.getBlockBlobReference(blobName); blockBlob2.downloadAttributes(); assertEquals(0, blockBlob2.getProperties().getLength()); ArrayList<BlockEntry> blocks = blockBlob2.downloadBlockList(BlockListingFilter.ALL, null, null, null); assertEquals(0, blocks.size()); } @Test public void testClose() throws URISyntaxException, StorageException, IOException { String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testblob"); CloudBlockBlob blockBlob = this.container.getBlockBlobReference(blobName); BlobOutputStream str = blockBlob.openOutputStream(); str.close(); try { str.close(); fail("Can't close twice."); } catch(IOException e) { assertEquals(SR.STREAM_CLOSED, e.getMessage()); } str = blockBlob.openOutputStream(); str.write(8); ArrayList<BlockEntry> blocks = blockBlob.downloadBlockList(BlockListingFilter.ALL, null, null, null); assertEquals(0, blocks.size()); str.close(); blocks = blockBlob.downloadBlockList(BlockListingFilter.COMMITTED, null, null, null); assertEquals(1, blocks.size()); } @Test public void testWithAccessCondition() throws URISyntaxException, StorageException, IOException { int blobLengthToUse = 8 * 512; byte[] buffer = BlobTestHelper.getRandomBuffer(blobLengthToUse); String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testblob"); AccessCondition accessCondition = AccessCondition.generateIfNotModifiedSinceCondition(new Date()); CloudBlockBlob blockBlob = this.container.getBlockBlobReference(blobName); BlobOutputStream blobOutputStream = blockBlob.openOutputStream(accessCondition, null, null); ByteArrayInputStream inputStream = new ByteArrayInputStream(buffer); blobOutputStream.write(inputStream, 512); inputStream = new ByteArrayInputStream(buffer, 512, 3 * 512); blobOutputStream.write(inputStream, 3 * 512); blobOutputStream.close(); byte[] result = new byte[blobLengthToUse]; blockBlob.downloadToByteArray(result, 0); int i = 0; for (; i < 4 * 512; i++) { assertEquals(buffer[i], result[i]); } for (; i < 8 * 512; i++) { assertEquals(0, result[i]); } } @Test public void testWriteStream() throws URISyntaxException, StorageException, IOException { int blobLengthToUse = 8 * 512; byte[] buffer = BlobTestHelper.getRandomBuffer(blobLengthToUse); String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testblob"); CloudBlockBlob blockBlob = this.container.getBlockBlobReference(blobName); BlobOutputStream blobOutputStream = blockBlob.openOutputStream(); ByteArrayInputStream inputStream = new ByteArrayInputStream(buffer); blobOutputStream.write(inputStream, 512); inputStream = new ByteArrayInputStream(buffer, 512, 3 * 512); blobOutputStream.write(inputStream, 3 * 512); blobOutputStream.close(); byte[] result = new byte[blobLengthToUse]; blockBlob.downloadToByteArray(result, 0); int i = 0; for (; i < 4 * 512; i++) { assertEquals(buffer[i], result[i]); } for (; i < 8 * 512; i++) { assertEquals(0, result[i]); } } @Test public void testFlush() throws Exception { CloudBlockBlob blockBlob = this.container.getBlockBlobReference( BlobTestHelper.generateRandomBlobNameWithPrefix("flush")); OperationContext ctx = new OperationContext(); ctx.getResponseReceivedEventHandler().addListener(new StorageEvent<ResponseReceivedEvent>() { @Override public void eventOccurred(ResponseReceivedEvent eventArg) { try { HttpURLConnection con = (HttpURLConnection) eventArg.getConnectionObject(); if ("511".equals(con.getRequestProperty(Constants.HeaderConstants.CONTENT_LENGTH))) { Thread.sleep(3000); } } catch (InterruptedException e) { // do nothing } } }); BlobOutputStream blobOutputStream = blockBlob.openOutputStream(null, null, ctx); ExecutorService threadExecutor = Executors.newFixedThreadPool(1); byte[] buffer = BlobTestHelper.getRandomBuffer(511); blobOutputStream.write(buffer); Future<Void> future = threadExecutor.submit(new FlushTask(blobOutputStream)); Thread.sleep(1000); buffer = BlobTestHelper.getRandomBuffer(513); blobOutputStream.write(buffer); // Writes complete when the upload is dispatched (not when the upload completes and flush must // wait for upload1 to complete. So, flush should finish last and writes should finish in order. while(!future.isDone()) { Thread.sleep(500); } // After flush we should see the first upload ArrayList<BlockEntry> blocks = blockBlob.downloadBlockList(BlockListingFilter.UNCOMMITTED, null, null, null); assertEquals(1, blocks.size()); assertEquals(511, blocks.get(0).getSize()); // After close we should see the second upload blobOutputStream.close(); blocks = blockBlob.downloadBlockList(BlockListingFilter.COMMITTED, null, null, null); assertEquals(2, blocks.size()); assertEquals(513, blocks.get(1).getSize()); } @Test public void testWritesDoubleConcurrency() throws URISyntaxException, StorageException, IOException, InterruptedException { String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("concurrency"); CloudBlockBlob blockBlob = this.container.getBlockBlobReference(blobName); // setup the blob output stream with a concurrency of 5 BlobRequestOptions options = new BlobRequestOptions(); options.setConcurrentRequestCount(5); BlobOutputStream blobOutputStream = blockBlob.openOutputStream(null, options, null); // set up the execution completion service ExecutorService threadExecutor = Executors.newFixedThreadPool(5); ExecutorCompletionService<Void> completion = new ExecutorCompletionService<Void>(threadExecutor); int tasks = 10; int writes = 10; int length = 512; // submit tasks to write and flush many blocks for (int i = 0; i < tasks; i++) { completion.submit(new WriteTask(blobOutputStream, length, writes, 4 /*flush period*/)); } // wait for all tasks to complete for (int i = 0; i < tasks; i++) { completion.take(); } // shut down the thread executor for this method threadExecutor.shutdown(); // check that blocks were committed ArrayList<BlockEntry> blocks = blockBlob.downloadBlockList(BlockListingFilter.UNCOMMITTED, null, null, null); assertTrue(blocks.size() != 0); // close the stream and check that the blob is the expected length blobOutputStream.close(); blockBlob.downloadAttributes(); assertTrue(blockBlob.getProperties().getLength() == length*writes*tasks); } @Test public void testWritesNoConcurrency() throws URISyntaxException, StorageException, IOException { int writes = 10; this.smallPutThresholdHelper(Constants.MB, writes, null); this.writeFlushHelper(512, writes, null, 1); this.writeFlushHelper(512, writes, null, 4); this.writeFlushHelper(512, writes, null, writes+1); } public void testWritesConcurrency() throws URISyntaxException, StorageException, IOException { int writes = 10; BlobRequestOptions options = new BlobRequestOptions(); options.setConcurrentRequestCount(5); this.smallPutThresholdHelper(Constants.MB, writes, options); this.writeFlushHelper(512, writes, options, 1); this.writeFlushHelper(512, writes, options, 4); this.writeFlushHelper(512, writes, options, writes+1); } private void smallPutThresholdHelper(int length, int writes, BlobRequestOptions options) throws URISyntaxException, StorageException, IOException { byte[] buffer = BlobTestHelper.getRandomBuffer(length*writes); String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("concurrency"); CloudBlockBlob blockBlob = this.container.getBlockBlobReference(blobName); blockBlob.setStreamWriteSizeInBytes(length); BlobOutputStream blobOutputStream = blockBlob.openOutputStream(null, options, null); for (int i = 0; i < writes; i ++) { blobOutputStream.write(buffer, i*length, length); } blobOutputStream.flush(); ArrayList<BlockEntry> blocks = blockBlob.downloadBlockList(BlockListingFilter.UNCOMMITTED, null, null, null); assertEquals(writes, blocks.size()); blobOutputStream.close(); blocks = blockBlob.downloadBlockList(BlockListingFilter.COMMITTED, null, null, null); assertEquals(writes, blocks.size()); byte[] outBuffer = new byte[writes*length]; blockBlob.downloadToByteArray(outBuffer, 0); for (int i = 0; i < length*writes; i ++) { assertEquals(buffer[i], outBuffer[i]); } } private void writeFlushHelper(int length, int writes, BlobRequestOptions options, int flushPeriod) throws URISyntaxException, StorageException, IOException { byte[] buffer = BlobTestHelper.getRandomBuffer(length*writes); String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("concurrency"); CloudBlockBlob blockBlob = this.container.getBlockBlobReference(blobName); ArrayList<BlockEntry> blocks; BlobOutputStream blobOutputStream = blockBlob.openOutputStream(null, options, null); for (int i = 0; i < writes; i ++) { blobOutputStream.write(buffer, i*length, length); if ((i+1)%flushPeriod == 0) { blobOutputStream.flush(); blocks = blockBlob.downloadBlockList(BlockListingFilter.UNCOMMITTED, null, null, null); assertEquals((int)Math.ceil((i+1)/flushPeriod), blocks.size()); } } blobOutputStream.close(); blocks = blockBlob.downloadBlockList(BlockListingFilter.COMMITTED, null, null, null); int flushRequired = writes-flushPeriod < 0 || writes%flushPeriod == 0 ? 0 : 1; double expected = Math.ceil(((double)writes+flushRequired)/flushPeriod); assertEquals((long)expected, blocks.size()); byte[] outBuffer = new byte[writes*length]; blockBlob.downloadToByteArray(outBuffer, 0); for (int i = 0; i < length*writes; i ++) { assertEquals(buffer[i], outBuffer[i]); } } private static class FlushTask implements Callable<Void> { final BlobOutputStream stream; public FlushTask(BlobOutputStream stream) { this.stream = stream; } @Override public Void call() { try { stream.flush(); } catch (IOException e) { fail("The flush should succeed."); } return null; } } private class WriteTask implements Callable<Void> { final int length; final int writes; final int flushPeriod; final BlobOutputStream blobOutputStream; public WriteTask(BlobOutputStream blobOutputStream, int length, int writes, int flushPeriod) { this.length = length; this.writes = writes; this.flushPeriod = flushPeriod; this.blobOutputStream = blobOutputStream; } @Override public Void call() { try { byte[] buffer = BlobTestHelper.getRandomBuffer(this.length*this.writes); for (int i = 0; i < writes; i ++) { this.blobOutputStream.write(buffer, i*this.length, this.length); if ((i+1)%flushPeriod == 0) { this.blobOutputStream.flush(); } } } catch (Exception e) { fail("flushHelper should succeed."); } return null; } } }