/** * 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; import com.microsoft.azure.storage.blob.BlobOutputStream; import com.microsoft.azure.storage.blob.BlobRequestOptions; import com.microsoft.azure.storage.blob.BlobTestHelper; import com.microsoft.azure.storage.blob.CloudBlobClient; import com.microsoft.azure.storage.blob.CloudBlobContainer; import com.microsoft.azure.storage.blob.CloudBlockBlob; import com.microsoft.azure.storage.core.SR; import com.microsoft.azure.storage.core.Utility; import com.microsoft.azure.storage.queue.CloudQueue; import com.microsoft.azure.storage.queue.CloudQueueClient; import com.microsoft.azure.storage.table.CloudTable; import com.microsoft.azure.storage.table.CloudTableClient; import com.microsoft.azure.storage.TestRunners.CloudTests; import com.microsoft.azure.storage.TestRunners.DevFabricTests; import com.microsoft.azure.storage.TestRunners.DevStoreTests; import com.microsoft.azure.storage.TestRunners.SlowTests; import org.junit.After; import org.junit.Assert; 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.ConnectException; import java.net.HttpURLConnection; import java.net.InetSocketAddress; import java.net.Proxy; import java.net.SocketTimeoutException; import java.net.URISyntaxException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.UUID; import static org.junit.Assert.*; @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class }) public class GenericTests { @Before public void genericTestMethodSetUp() { OperationContext.setDefaultProxy(Proxy.NO_PROXY); } @After public void genericTestMethodTearDown() { OperationContext.setDefaultProxy(Proxy.NO_PROXY); } /** * ReadTimeout must always be explicitly set on HttpUrlConnection to avoid a bug in JDK 6. In certain cases this * bug causes an immediate SocketException to be thrown indicating that the read timed out even if ReadTimeout was * not set. The SocketException is retried and can cause server-side errors to be returned (in this case, an * InvalidBlockList error). This tests to make sure we are setting read timeout so that this issue does not occur. * * @see {@link BaseRequest} * @throws URISyntaxException * @throws StorageException * @throws IOException */ @Test @Category({ DevFabricTests.class, DevStoreTests.class, SlowTests.class }) public void testReadTimeoutIssue() throws URISyntaxException, StorageException, IOException { // part 1 byte[] buffer = BlobTestHelper.getRandomBuffer(1 * 1024 * 1024); // set the maximum execution time BlobRequestOptions options = new BlobRequestOptions(); options.setMaximumExecutionTimeInMs(5000); CloudBlobClient blobClient = TestHelper.createCloudBlobClient(); CloudBlobContainer container = blobClient.getContainerReference(generateRandomContainerName()); String blobName = "testBlob"; final CloudBlockBlob blockBlobRef = container.getBlockBlobReference(blobName); blockBlobRef.setStreamWriteSizeInBytes(1 * 1024 * 1024); ByteArrayInputStream inputStream = new ByteArrayInputStream(buffer); BlobOutputStream blobOutputStream = null; try { container.createIfNotExists(); blobOutputStream = blockBlobRef.openOutputStream(null, options, null); try { blobOutputStream.write(inputStream, buffer.length); } finally { blobOutputStream.close(); } assertTrue(blockBlobRef.exists()); } finally { inputStream.close(); container.deleteIfExists(); } // part 2 int length2 = 10 * 1024 * 1024; byte[] uploadBuffer2 = BlobTestHelper.getRandomBuffer(length2); CloudBlobClient blobClient2 = TestHelper.createCloudBlobClient(); CloudBlobContainer container2 = blobClient2.getContainerReference(generateRandomContainerName()); String blobName2 = "testBlob"; final CloudBlockBlob blockBlobRef2 = container2.getBlockBlobReference(blobName2); ByteArrayInputStream inputStream2 = new ByteArrayInputStream(uploadBuffer2); try { container2.createIfNotExists(); blockBlobRef2.upload(inputStream2, length2); } finally { inputStream2.close(); container2.deleteIfExists(); } } @Test public void testProxy() throws URISyntaxException, StorageException { CloudBlobClient blobClient = TestHelper.createCloudBlobClient(); CloudBlobContainer container = blobClient.getContainerReference("container1"); // Use a request-level proxy OperationContext opContext = new OperationContext(); opContext.setProxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("10.1.1.1", 8888))); // Turn of retries to make the failure happen faster BlobRequestOptions opt = new BlobRequestOptions(); opt.setRetryPolicyFactory(new RetryNoRetry()); // Unfortunately HttpURLConnection doesn't expose a getter and the usingProxy method it does have doesn't // work as one would expect and will always for us return false. So, we validate by making sure the request // fails when we set a bad proxy rather than check the proxy setting itself. try { container.exists(null, opt, opContext); fail("Bad proxy should throw an exception."); } catch (StorageException e) { if (e.getCause().getClass() != ConnectException.class && e.getCause().getClass() != SocketTimeoutException.class) { Assert.fail("Unepected exception for bad proxy"); } } } @Test public void testDefaultProxy() throws URISyntaxException, StorageException { CloudBlobClient blobClient = TestHelper.createCloudBlobClient(); CloudBlobContainer container = blobClient.getContainerReference("container1"); // Use a default proxy OperationContext.setDefaultProxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("10.1.1.1", 8888))); // Turn of retries to make the failure happen faster BlobRequestOptions opt = new BlobRequestOptions(); opt.setRetryPolicyFactory(new RetryNoRetry()); // Unfortunately HttpURLConnection doesn't expose a getter and the usingProxy method it does have doesn't // work as one would expect and will always for us return false. So, we validate by making sure the request // fails when we set a bad proxy rather than check the proxy setting itself succeeding. try { container.exists(null, opt, null); fail("Bad proxy should throw an exception."); } catch (StorageException e) { if (e.getCause().getClass() != ConnectException.class && e.getCause().getClass() != SocketTimeoutException.class) { Assert.fail("Unepected exception for bad proxy"); } } } @Test public void testProxyOverridesDefault() throws URISyntaxException, StorageException { CloudBlobClient blobClient = TestHelper.createCloudBlobClient(); CloudBlobContainer container = blobClient.getContainerReference("container1"); // Set a default proxy OperationContext.setDefaultProxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("10.1.1.1", 8888))); // Turn off retries to make the failure happen faster BlobRequestOptions opt = new BlobRequestOptions(); opt.setRetryPolicyFactory(new RetryNoRetry()); // Unfortunately HttpURLConnection doesn't expose a getter and the usingProxy method it does have doesn't // work as one would expect and will always for us return false. So, we validate by making sure the request // fails when we set a bad proxy rather than check the proxy setting itself succeeding. try { container.exists(null, opt, null); fail("Bad proxy should throw an exception."); } catch (StorageException e) { if (e.getCause().getClass() != ConnectException.class && e.getCause().getClass() != SocketTimeoutException.class) { Assert.fail("Unepected exception for bad proxy"); } } // Override it with no proxy OperationContext opContext = new OperationContext(); opContext.setProxy(Proxy.NO_PROXY); // Should succeed as request-level proxy should override the bad default proxy container.exists(null, null, opContext); } /** * Make sure that if a request throws an error when it is being built that the request is not sent. * * @throws URISyntaxException * @throws StorageException */ @Test public void testExecutionEngineErrorHandling() throws URISyntaxException, StorageException { CloudBlobContainer container = BlobTestHelper.getRandomContainerReference(); try { final ArrayList<Boolean> callList = new ArrayList<Boolean>(); OperationContext opContext = new OperationContext(); opContext.getSendingRequestEventHandler().addListener(new StorageEvent<SendingRequestEvent>() { // insert a metadata element with an empty value @Override public void eventOccurred(SendingRequestEvent eventArg) { callList.add(true); } }); container.getMetadata().put("key", " "); // invalid value try { container.uploadMetadata(null, null, opContext); fail(SR.METADATA_KEY_INVALID); } catch (StorageException e) { // make sure a request was not sent assertEquals(0, callList.size()); assertEquals(SR.METADATA_VALUE_INVALID, e.getMessage()); } } finally { container.deleteIfExists(); } } @Test public void testUserAgentString() throws URISyntaxException, StorageException { // Test with a blob request CloudBlobClient blobClient = TestHelper.createCloudBlobClient(); CloudBlobContainer container = blobClient.getContainerReference("container1"); OperationContext sendingRequestEventContext = new OperationContext(); sendingRequestEventContext.getSendingRequestEventHandler().addListener(new StorageEvent<SendingRequestEvent>() { @Override public void eventOccurred(SendingRequestEvent eventArg) { assertEquals( Constants.HeaderConstants.USER_AGENT_PREFIX + "/" + Constants.HeaderConstants.USER_AGENT_VERSION + " " + String.format(Utility.LOCALE_US, "(JavaJRE %s; %s %s)", System.getProperty("java.version"), System.getProperty("os.name").replaceAll(" ", ""), System.getProperty("os.version")), ((HttpURLConnection) eventArg .getConnectionObject()).getRequestProperty(Constants.HeaderConstants.USER_AGENT)); } }); container.exists(null, null, sendingRequestEventContext); // Test with a queue request CloudQueueClient queueClient = TestHelper.createCloudQueueClient(); CloudQueue queue = queueClient.getQueueReference("queue1"); queue.exists(null, sendingRequestEventContext); // Test with a table request CloudTableClient tableClient = TestHelper.createCloudTableClient(); CloudTable table = tableClient.getTableReference("table1"); table.exists(null, sendingRequestEventContext); } @Test public void testUserHeaders() throws URISyntaxException, StorageException { CloudBlobClient blobClient = TestHelper.createCloudBlobClient(); CloudBlobContainer container = blobClient.getContainerReference("container1"); OperationContext context = new OperationContext(); // no user headers container.exists(null, null, context); // add user headers HashMap<String, String> userHeaders = new HashMap<String, String>(); userHeaders.put("x-ms-foo", "bar"); userHeaders.put("x-ms-hello", "value"); context.setUserHeaders(userHeaders); StorageEvent<SendingRequestEvent> event = new StorageEvent<SendingRequestEvent>() { @Override public void eventOccurred(SendingRequestEvent eventArg) { HttpURLConnection connection = (HttpURLConnection) eventArg.getConnectionObject(); assertNotNull(connection.getRequestProperty("x-ms-foo")); assertNotNull(connection.getRequestProperty("x-ms-hello")); } }; context.getSendingRequestEventHandler().addListener(event); container.exists(null, null, context); // clear user headers userHeaders.clear(); context.getSendingRequestEventHandler().removeListener(event); context.setUserHeaders(userHeaders); context.getSendingRequestEventHandler().addListener(new StorageEvent<SendingRequestEvent>() { @Override public void eventOccurred(SendingRequestEvent eventArg) { HttpURLConnection connection = (HttpURLConnection) eventArg.getConnectionObject(); assertNull(connection.getRequestProperty("x-ms-foo")); assertNull(connection.getRequestProperty("x-ms-hello")); } }); container.exists(null, null, context); } @Test public void testNullRetryPolicy() throws URISyntaxException, StorageException { CloudBlobClient blobClient = TestHelper.createCloudBlobClient(); CloudBlobContainer container = blobClient.getContainerReference("container1"); blobClient.getDefaultRequestOptions().setRetryPolicyFactory(null); container.exists(); } @Test public void testDateStringParsingWithRounding() throws ParseException { String fullDateString = "1999-12-31T23:59:45.1234567Z"; SimpleDateFormat testFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS Z"); Date milliDate = testFormat.parse("1999-12-31T23:59:45.123 -0000"); assertEquals(milliDate, Utility.parseDate(fullDateString)); fullDateString = "1999-04-30T23:59:55.9876Z"; long millisSinceEpoch = 925516795987L; Date deciDate = Utility.parseDate(fullDateString.replace("876Z", "Z")); assertEquals(deciDate.getTime(), (millisSinceEpoch / 100) * 100); Date centiDate = Utility.parseDate(fullDateString.replace("76Z", "Z")); assertEquals(centiDate.getTime(), (millisSinceEpoch / 10) * 10); milliDate = Utility.parseDate(fullDateString); assertEquals(milliDate.getTime(), millisSinceEpoch); } @Test public void testDateStringParsing() throws ParseException { // 2014-12-07T09:15:12.123Z from Java testDate("2014-12-07T09:15:12.123Z", 1417943712123L, 0, false, false); // 2015-01-14T14:53:32.800Z from Java testDate("2015-01-14T14:53:32.800Z", 1421247212800L, 0, false, false); // 2014-11-29T22:55:21.9876543Z from .Net testDate("2014-11-29T22:55:21.9876543Z", 1417301721987L, 6543, false, false); // 2015-02-14T03:11:13.0000229Z from .Net testDate("2015-02-14T03:11:13.0000229Z", 1423883473000L, 229, false, false); } @Test public void testDateStringParsingCrossVersion() throws ParseException { // 2014-12-07T09:15:12.123Z from Java, milliseconds are incorrectly left-padded testDate("2014-12-07T09:15:12.0000123Z", 1417943712123L, 0, true, false); // 2015-01-14T14:53:32.800Z from Java, milliseconds are incorrectly left-padded testDate("2015-01-14T14:53:32.0000800Z", 1421247212800L, 0, true, false); // 2014-11-29T22:55:21.9876543Z from .Net testDate("2014-11-29T22:55:21.9876543Z", 1417301721987L, 6543, true, false); // 2015-02-14T03:11:13.0000229Z from .Net testDate("2015-02-14T03:11:13.0000229Z", 1423883473000L, 229, true, false); } @Test public void testDateStringParsingWithBackwardCompatibility() throws ParseException { // 2014-12-07T09:15:12.123Z from Java testDate("2014-12-07T09:15:12.123Z", 1417943712123L, 0, false, true); // 2015-01-14T14:53:32.800Z from Java testDate("2015-01-14T14:53:32.800Z", 1421247212800L, 0, false, true); // 2014-11-29T22:55:21.9876543Z from .Net testDate("2014-11-29T22:55:21.9876543Z", 1417301721987L, 6543, false, true); // 2015-02-14T03:11:13.0000229Z from .Net testDate("2015-02-14T03:11:13.0000229Z", 1423883473000L, 229, false, true); } @Test public void testDateStringParsingCrossVersionWithBackwardCompatibility() throws ParseException { // 2014-12-07T09:15:12.123Z from Java, milliseconds are incorrectly left-padded testDate("2014-12-07T09:15:12.0000123Z", 1417943712123L, 0, true, true); // 2015-01-14T14:53:32.800Z from Java, milliseconds are incorrectly left-padded testDate("2015-01-14T14:53:32.0000800Z", 1421247212800L, 0, true, true); // 2014-11-29T22:55:21.9876543Z from .Net testDate("2014-11-29T22:55:21.9876543Z", 1417301721987L, 6543, true, true); // 2015-02-14T03:11:13.0000229Z from .Net testDate("2015-02-14T03:11:13.0000229Z", 1423883473000L, 229, true, true); } private static void testDate(final String dateString, final long intendedMilliseconds, final int ticks, final boolean writtenPre2, final boolean dateBackwardCompatibility) { assertTrue(ticks >= 0); // ticks is non-negative assertTrue(ticks <= 9999); // ticks do not overflow into milliseconds long expectedMilliseconds = intendedMilliseconds; if (dateBackwardCompatibility && (intendedMilliseconds % 1000 == 0) && (ticks < 1000)) { // when no milliseconds are present dateBackwardCompatibility causes up to 3 digits of ticks // to be read as milliseconds expectedMilliseconds += ticks; } else if (writtenPre2 && !dateBackwardCompatibility && (ticks == 0)) { // without DateBackwardCompatibility, milliseconds stored by Java prior to 2.0.0 are lost expectedMilliseconds -= expectedMilliseconds % 1000; } assertEquals(expectedMilliseconds, Utility.parseDate(dateString, dateBackwardCompatibility).getTime()); } @Test public void testDateStringFormatting() { String fullDateString = "2014-12-07T09:15:12.123Z"; String outDateString = Utility.getJavaISO8601Time(Utility.parseDate(fullDateString)); assertEquals(fullDateString, outDateString); fullDateString = "2015-01-14T14:53:32.800Z"; outDateString = Utility.getJavaISO8601Time(Utility.parseDate(fullDateString)); assertEquals(fullDateString, outDateString); // Ensure that trimming of trailing zeroes by the service does not affect this fullDateString = "2015-01-14T14:53:32.8Z"; outDateString = Utility.getJavaISO8601Time(Utility.parseDate(fullDateString)); fullDateString = fullDateString.replace("Z", "00Z"); assertEquals(fullDateString, outDateString); // Ensure that trimming of trailing zeroes by the service does not affect this // even with dateBackwardCompatibility fullDateString = "2015-01-14T14:53:32.0000800Z"; outDateString = Utility.getJavaISO8601Time(Utility.parseDate(fullDateString, true)); fullDateString = "2015-01-14T14:53:32.800Z"; assertEquals(fullDateString, outDateString); } private static String generateRandomContainerName() { String containerName = "container" + UUID.randomUUID().toString(); return containerName.replace("-", ""); } }