/** * 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.table; import com.microsoft.azure.storage.LocationMode; import com.microsoft.azure.storage.OperationContext; import com.microsoft.azure.storage.RequestResult; import com.microsoft.azure.storage.ResultSegment; import com.microsoft.azure.storage.RetryContext; import com.microsoft.azure.storage.RetryExponentialRetry; import com.microsoft.azure.storage.RetryInfo; import com.microsoft.azure.storage.RetryLinearRetry; import com.microsoft.azure.storage.RetryPolicy; import com.microsoft.azure.storage.StorageCredentialsSharedAccessSignature; import com.microsoft.azure.storage.StorageException; import com.microsoft.azure.storage.StorageLocation; 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 com.microsoft.azure.storage.core.PathUtility; import com.microsoft.azure.storage.core.SR; import com.microsoft.azure.storage.table.TableTestHelper.Class1; import com.microsoft.azure.storage.table.TableTestHelper.Class2; import org.junit.Test; import org.junit.experimental.categories.Category; import java.net.HttpURLConnection; import java.net.URISyntaxException; import java.security.InvalidKeyException; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.EnumSet; import java.util.GregorianCalendar; import java.util.UUID; import static org.junit.Assert.*; /** * Table Client Tests */ public class TableClientTests { @Test @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class }) public void testListTablesSegmented() throws URISyntaxException, StorageException { TableRequestOptions options = new TableRequestOptions(); TablePayloadFormat[] formats = {TablePayloadFormat.JsonFullMetadata, TablePayloadFormat.Json, TablePayloadFormat.JsonNoMetadata}; for (TablePayloadFormat format : formats) { options.setTablePayloadFormat(format); testListTablesSegmented(options); } } private void testListTablesSegmented(TableRequestOptions options) throws URISyntaxException, StorageException { final CloudTableClient tClient = TableTestHelper.createCloudTableClient(); String tableBaseName = TableTestHelper.generateRandomTableName(); ArrayList<String> tables = new ArrayList<String>(); for (int m = 0; m < 20; m++) { String name = String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(m)); CloudTable table = tClient.getTableReference(name); table.create(); tables.add(name); } try { int currTable = 0; ResultSegment<String> segment1 = tClient.listTablesSegmented(tableBaseName, 5, null, options, null); assertEquals(5, segment1.getLength()); for (String s : segment1.getResults()) { assertEquals(s, String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(currTable))); currTable++; } ResultSegment<String> segment2 = tClient.listTablesSegmented(tableBaseName, 5, segment1.getContinuationToken(), options, null); assertEquals(5, segment2.getLength()); for (String s : segment2.getResults()) { assertEquals(s, String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(currTable))); currTable++; } ResultSegment<String> segment3 = tClient.listTablesSegmented(tableBaseName, 5, segment2.getContinuationToken(), options, null); assertEquals(5, segment3.getLength()); for (String s : segment3.getResults()) { assertEquals(s, String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(currTable))); currTable++; } } finally { for (String s : tables) { CloudTable table = tClient.getTableReference(s); table.delete(); } } } @Test @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class }) public void testListTablesSegmentedMaxResultsValidation() throws URISyntaxException, StorageException { final CloudTableClient tClient = TableTestHelper.createCloudTableClient(); // Validation should cause each of these to fail. for (int i = 0; i >= -2; i--) { try { tClient.listTablesSegmented(null, i, null, null, null); fail(); } catch (IllegalArgumentException e) { assertTrue(String.format(SR.PARAMETER_SHOULD_BE_GREATER_OR_EQUAL, "maxResults", 1) .equals(e.getMessage())); } } assertNotNull(tClient.listTablesSegmented("thereshouldntbeanytableswiththisprefix")); } @Test @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class }) public void testListTablesSegmentedNoPrefix() throws URISyntaxException, StorageException { TableRequestOptions options = new TableRequestOptions(); options.setTablePayloadFormat(TablePayloadFormat.Json); final CloudTableClient tClient = TableTestHelper.createCloudTableClient(); String tableBaseName = TableTestHelper.generateRandomTableName(); ArrayList<String> tables = new ArrayList<String>(); for (int m = 0; m < 20; m++) { String name = String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(m)); CloudTable table = tClient.getTableReference(name); table.create(); tables.add(name); } try { int currTable = 0; ResultSegment<String> segment1 = tClient.listTablesSegmented(null, 5, null, options, null); assertEquals(5, segment1.getLength()); for (String s : segment1.getResults()) { if (s.startsWith(tableBaseName)) { assertEquals(s, String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(currTable))); currTable++; } } ResultSegment<String> segment2 = tClient.listTablesSegmented(null, 5, segment1.getContinuationToken(), options, null); assertEquals(5, segment2.getLength()); for (String s : segment2.getResults()) { if (s.startsWith(tableBaseName)) { assertEquals(s, String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(currTable))); currTable++; } } ResultSegment<String> segment3 = tClient.listTablesSegmented(null, 5, segment2.getContinuationToken(), options, null); assertEquals(5, segment3.getLength()); for (String s : segment3.getResults()) { if (s.startsWith(tableBaseName)) { assertEquals(s, String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(currTable))); currTable++; } } } finally { for (String s : tables) { CloudTable table = tClient.getTableReference(s); table.delete(); } } } @Test @Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class }) public void testListTablesWithIterator() throws URISyntaxException, StorageException { TableRequestOptions options = new TableRequestOptions(); options.setTablePayloadFormat(TablePayloadFormat.Json); final CloudTableClient tClient = TableTestHelper.createCloudTableClient(); String tableBaseName = TableTestHelper.generateRandomTableName(); ArrayList<String> tables = new ArrayList<String>(); for (int m = 0; m < 20; m++) { String name = String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(m)); CloudTable table = tClient.getTableReference(name); table.create(); tables.add(name); } try { // With prefix int currTable = 0; Iterable<String> listTables = tClient.listTables(tableBaseName, options, null); for (String s : listTables) { assertEquals(s, String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(currTable))); currTable++; } assertEquals(20, currTable); // Second Iteration currTable = 0; for (String s : listTables) { assertEquals(s, String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(currTable))); currTable++; } assertEquals(20, currTable); // Without prefix currTable = 0; Iterable<String> listTablesNoPrefix = tClient.listTables(); for (String s : listTablesNoPrefix) { if (s.startsWith(tableBaseName)) { currTable++; } } assertEquals(20, currTable); currTable = 0; for (String s : listTablesNoPrefix) { if (s.startsWith(tableBaseName)) { currTable++; } } assertEquals(20, currTable); } finally { for (String s : tables) { CloudTable table = tClient.getTableReference(s); table.delete(); } } } @Test @Category({SlowTests.class, DevFabricTests.class, DevStoreTests.class, CloudTests.class }) public void testTableSASFromIdentifier() throws StorageException, URISyntaxException, InvalidKeyException, InterruptedException { CloudTable table = TableTestHelper.getRandomTableReference(); try { table.create(); TablePermissions expectedPermissions = new TablePermissions(); String identifier = UUID.randomUUID().toString(); // Add a policy, check setting and getting. SharedAccessTablePolicy policy1 = new SharedAccessTablePolicy(); Calendar now = GregorianCalendar.getInstance(); policy1.setSharedAccessStartTime(now.getTime()); now.add(Calendar.MINUTE, 10); policy1.setSharedAccessExpiryTime(now.getTime()); policy1.setPermissions(EnumSet.of(SharedAccessTablePermissions.ADD, SharedAccessTablePermissions.QUERY, SharedAccessTablePermissions.UPDATE, SharedAccessTablePermissions.DELETE)); expectedPermissions.getSharedAccessPolicies().put(identifier, policy1); table.uploadPermissions(expectedPermissions); Thread.sleep(30000); // Insert 500 entities in Batches to query for (int i = 0; i < 5; i++) { TableBatchOperation batch = new TableBatchOperation(); for (int j = 0; j < 100; j++) { Class1 ent = TableTestHelper.generateRandomEntity("javatables_batch_" + Integer.toString(i)); ent.setRowKey(String.format("%06d", j)); batch.insert(ent); } table.execute(batch); } String sasString = table.generateSharedAccessSignature(null, identifier, null, null, null, null); CloudTable tableFromIdentifierSAS = new CloudTable(PathUtility.addToQuery(table.getUri(), sasString)); { Class1 randEnt = TableTestHelper.generateRandomEntity(null); TableQuery<Class1> query = TableQuery.from(Class1.class).where( String.format("(PartitionKey eq '%s') and (RowKey ge '%s')", "javatables_batch_1", "000050")); int count = 0; for (Class1 ent : tableFromIdentifierSAS.execute(query)) { assertEquals(ent.getA(), randEnt.getA()); assertEquals(ent.getB(), randEnt.getB()); assertEquals(ent.getC(), randEnt.getC()); assertEquals(ent.getPartitionKey(), "javatables_batch_1"); assertEquals(ent.getRowKey(), String.format("%06d", count + 50)); count++; } assertEquals(count, 50); } { Class1 baseEntity = new Class1(); baseEntity.setA("foo_A"); baseEntity.setB("foo_B"); baseEntity.setC("foo_C"); baseEntity.setD(new byte[] { 0, 1, 2 }); baseEntity.setPartitionKey("jxscl_odata"); baseEntity.setRowKey(UUID.randomUUID().toString()); Class2 secondEntity = new Class2(); secondEntity.setL("foo_L"); secondEntity.setM("foo_M"); secondEntity.setN("foo_N"); secondEntity.setO("foo_O"); secondEntity.setPartitionKey(baseEntity.getPartitionKey()); secondEntity.setRowKey(baseEntity.getRowKey()); secondEntity.setEtag(baseEntity.getEtag()); // Insert or merge Entity - ENTITY DOES NOT EXIST NOW. TableResult insertResult = tableFromIdentifierSAS.execute(TableOperation.insertOrMerge(baseEntity)); assertEquals(insertResult.getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); // Insert or replace Entity - ENTITY EXISTS -> WILL REPLACE tableFromIdentifierSAS.execute(TableOperation.insertOrMerge(secondEntity)); // Retrieve entity TableResult queryResult = tableFromIdentifierSAS.execute(TableOperation.retrieve( baseEntity.getPartitionKey(), baseEntity.getRowKey(), DynamicTableEntity.class)); DynamicTableEntity retrievedEntity = queryResult.<DynamicTableEntity> getResultAsType(); assertNotNull("Property A", retrievedEntity.getProperties().get("A")); assertEquals(baseEntity.getA(), retrievedEntity.getProperties().get("A").getValueAsString()); assertNotNull("Property B", retrievedEntity.getProperties().get("B")); assertEquals(baseEntity.getB(), retrievedEntity.getProperties().get("B").getValueAsString()); assertNotNull("Property C", retrievedEntity.getProperties().get("C")); assertEquals(baseEntity.getC(), retrievedEntity.getProperties().get("C").getValueAsString()); assertNotNull("Property D", retrievedEntity.getProperties().get("D")); assertTrue(Arrays.equals(baseEntity.getD(), retrievedEntity.getProperties().get("D") .getValueAsByteArray())); // Validate New properties exist assertNotNull("Property L", retrievedEntity.getProperties().get("L")); assertEquals(secondEntity.getL(), retrievedEntity.getProperties().get("L").getValueAsString()); assertNotNull("Property M", retrievedEntity.getProperties().get("M")); assertEquals(secondEntity.getM(), retrievedEntity.getProperties().get("M").getValueAsString()); assertNotNull("Property N", retrievedEntity.getProperties().get("N")); assertEquals(secondEntity.getN(), retrievedEntity.getProperties().get("N").getValueAsString()); assertNotNull("Property O", retrievedEntity.getProperties().get("O")); assertEquals(secondEntity.getO(), retrievedEntity.getProperties().get("O").getValueAsString()); } } finally { // cleanup table.deleteIfExists(); } } @Test @Category({ SlowTests.class, DevFabricTests.class, DevStoreTests.class, CloudTests.class }) public void testTableSASFromPermission() throws StorageException, URISyntaxException, InvalidKeyException { CloudTable table = TableTestHelper.getRandomTableReference(); try { table.create(); // Add a policy, check setting and getting. SharedAccessTablePolicy policy1 = new SharedAccessTablePolicy(); Calendar now = GregorianCalendar.getInstance(); now.add(Calendar.MINUTE, -10); policy1.setSharedAccessStartTime(now.getTime()); now.add(Calendar.MINUTE, 30); policy1.setSharedAccessExpiryTime(now.getTime()); policy1.setPermissions(EnumSet.of(SharedAccessTablePermissions.ADD, SharedAccessTablePermissions.QUERY, SharedAccessTablePermissions.UPDATE, SharedAccessTablePermissions.DELETE)); // Insert 500 entities in Batches to query for (int i = 0; i < 5; i++) { TableBatchOperation batch = new TableBatchOperation(); for (int j = 0; j < 100; j++) { Class1 ent = TableTestHelper.generateRandomEntity("javatables_batch_" + Integer.toString(i)); ent.setRowKey(String.format("%06d", j)); batch.insert(ent); } table.execute(batch); } String sasString = table.generateSharedAccessSignature(policy1, null, "javatables_batch_0", "0", "javatables_batch_9", "9"); CloudTable tableFromPermission = new CloudTable(PathUtility.addToQuery(table.getUri(), sasString)); sasString = table.generateSharedAccessSignature(policy1, null, "javatables_batch_0", null, "javatables_batch_9", null); CloudTable tableFromPermissionJustPks = new CloudTable(PathUtility.addToQuery(table.getUri(), sasString)); { TableBatchOperation batchFromSAS = new TableBatchOperation(); for (int j = 1000; j < 1010; j++) { Class1 ent = TableTestHelper.generateRandomEntity("javatables_batch_" + Integer.toString(0)); ent.setRowKey(String.format("%06d", j)); batchFromSAS.insert(ent); } tableFromPermission.execute(batchFromSAS); Class1 randEnt = TableTestHelper.generateRandomEntity(null); TableQuery<Class1> query = TableQuery.from(Class1.class).where( String.format("(PartitionKey eq '%s') and (RowKey ge '%s')", "javatables_batch_1", "000050")); int count = 0; for (Class1 ent : tableFromPermission.execute(query)) { assertEquals(ent.getA(), randEnt.getA()); assertEquals(ent.getB(), randEnt.getB()); assertEquals(ent.getC(), randEnt.getC()); assertEquals(ent.getPartitionKey(), "javatables_batch_1"); assertEquals(ent.getRowKey(), String.format("%06d", count + 50)); count++; } assertEquals(count, 50); count = 0; for (Class1 ent : tableFromPermissionJustPks.execute(query)) { assertEquals(ent.getA(), randEnt.getA()); assertEquals(ent.getB(), randEnt.getB()); assertEquals(ent.getC(), randEnt.getC()); assertEquals(ent.getPartitionKey(), "javatables_batch_1"); assertEquals(ent.getRowKey(), String.format("%06d", count + 50)); count++; } assertEquals(count, 50); } { Class1 baseEntity = new Class1(); baseEntity.setA("foo_A"); baseEntity.setB("foo_B"); baseEntity.setC("foo_C"); baseEntity.setD(new byte[] { 0, 1, 2 }); baseEntity.setPartitionKey("javatables_batch_0" + UUID.randomUUID().toString()); baseEntity.setRowKey("0" + UUID.randomUUID().toString()); Class2 secondEntity = new Class2(); secondEntity.setL("foo_L"); secondEntity.setM("foo_M"); secondEntity.setN("foo_N"); secondEntity.setO("foo_O"); secondEntity.setPartitionKey(baseEntity.getPartitionKey()); secondEntity.setRowKey(baseEntity.getRowKey()); secondEntity.setEtag(baseEntity.getEtag()); TableResult insertResult = tableFromPermission.execute(TableOperation.insertOrMerge(baseEntity)); // Insert or merge Entity - ENTITY DOES NOT EXIST NOW. assertEquals(insertResult.getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); // Insert or replace Entity - ENTITY EXISTS -> WILL REPLACE tableFromPermission.execute(TableOperation.insertOrMerge(secondEntity)); // Retrieve entity TableResult queryResult = tableFromPermission.execute(TableOperation.retrieve( baseEntity.getPartitionKey(), baseEntity.getRowKey(), DynamicTableEntity.class)); DynamicTableEntity retrievedEntity = queryResult.<DynamicTableEntity> getResultAsType(); assertNotNull("Property A", retrievedEntity.getProperties().get("A")); assertEquals(baseEntity.getA(), retrievedEntity.getProperties().get("A").getValueAsString()); assertNotNull("Property B", retrievedEntity.getProperties().get("B")); assertEquals(baseEntity.getB(), retrievedEntity.getProperties().get("B").getValueAsString()); assertNotNull("Property C", retrievedEntity.getProperties().get("C")); assertEquals(baseEntity.getC(), retrievedEntity.getProperties().get("C").getValueAsString()); assertNotNull("Property D", retrievedEntity.getProperties().get("D")); assertTrue(Arrays.equals(baseEntity.getD(), retrievedEntity.getProperties().get("D") .getValueAsByteArray())); // Validate New properties exist assertNotNull("Property L", retrievedEntity.getProperties().get("L")); assertEquals(secondEntity.getL(), retrievedEntity.getProperties().get("L").getValueAsString()); assertNotNull("Property M", retrievedEntity.getProperties().get("M")); assertEquals(secondEntity.getM(), retrievedEntity.getProperties().get("M").getValueAsString()); assertNotNull("Property N", retrievedEntity.getProperties().get("N")); assertEquals(secondEntity.getN(), retrievedEntity.getProperties().get("N").getValueAsString()); assertNotNull("Property O", retrievedEntity.getProperties().get("O")); assertEquals(secondEntity.getO(), retrievedEntity.getProperties().get("O").getValueAsString()); } } finally { // cleanup table.deleteIfExists(); } } @Test @Category({ SlowTests.class, DevFabricTests.class, DevStoreTests.class, CloudTests.class }) public void testTableSASPkRk() throws StorageException, URISyntaxException, InvalidKeyException { CloudTable table = TableTestHelper.getRandomTableReference(); try { table.create(); // Add a policy, check setting and getting. SharedAccessTablePolicy policy1 = new SharedAccessTablePolicy(); Calendar now = GregorianCalendar.getInstance(); now.add(Calendar.MINUTE, -10); policy1.setSharedAccessStartTime(now.getTime()); now.add(Calendar.MINUTE, 30); policy1.setSharedAccessExpiryTime(now.getTime()); policy1.setPermissions(EnumSet.of(SharedAccessTablePermissions.ADD, SharedAccessTablePermissions.QUERY, SharedAccessTablePermissions.UPDATE, SharedAccessTablePermissions.DELETE)); String sasString = table.generateSharedAccessSignature(policy1, null, "javatables_batch_0", "00", "javatables_batch_1", "04"); StorageCredentialsSharedAccessSignature sasCreds = new StorageCredentialsSharedAccessSignature(sasString); CloudTable directTable = new CloudTable(PathUtility.addToQuery(table.getUri(), sasString)); CloudTable transformedTable = new CloudTable(sasCreds.transformUri(table.getUri())); Class1 ent = new Class1("javatables_batch_0", "00"); directTable.execute(TableOperation.insert(ent)); ent = new Class1("javatables_batch_0", "01"); transformedTable.execute(TableOperation.insert(ent)); ent = new Class1("javatables_batch_2", "01"); try { directTable.execute(TableOperation.insert(ent)); transformedTable.execute(TableOperation.insert(ent)); } catch (StorageException e) { assertEquals(HttpURLConnection.HTTP_FORBIDDEN, e.getHttpStatusCode()); assertEquals("AuthorizationFailure", e.getExtendedErrorInformation().getErrorCode()); } ent = new Class1("javatables_batch_1", "05"); try { directTable.execute(TableOperation.insert(ent)); transformedTable.execute(TableOperation.insert(ent)); } catch (StorageException e) { assertEquals(HttpURLConnection.HTTP_FORBIDDEN, e.getHttpStatusCode()); assertEquals("AuthorizationFailure", e.getExtendedErrorInformation().getErrorCode()); } } finally { // cleanup table.deleteIfExists(); } } @Test @Category({ SlowTests.class, DevFabricTests.class, DevStoreTests.class, CloudTests.class }) public void testBackoffTimeOverflow() { RetryExponentialRetry exponentialRetry = new RetryExponentialRetry(4000, 100000); testBackoffTimeOverflow(exponentialRetry, 100000); RetryLinearRetry linearRetry = new RetryLinearRetry(4000, 100000); testBackoffTimeOverflow(linearRetry, 100000); } private void testBackoffTimeOverflow(RetryPolicy retryPolicy, int maxAttempts) { Exception e = new Exception(); OperationContext context = new OperationContext(); RequestResult requestResult = new RequestResult(); requestResult.setException(e); requestResult.setStatusCode(HttpURLConnection.HTTP_INTERNAL_ERROR); requestResult.setStopDate(new Date()); requestResult.setTargetLocation(StorageLocation.PRIMARY); for (int i = 0; i < maxAttempts; i++) { RetryContext retryContext = new RetryContext(i, requestResult, StorageLocation.PRIMARY, LocationMode.PRIMARY_ONLY); RetryInfo result = retryPolicy.evaluate(retryContext, context); assertNotNull(result); } RetryContext retryContext = new RetryContext(maxAttempts, requestResult, StorageLocation.PRIMARY, LocationMode.PRIMARY_ONLY); assertNull(retryPolicy.evaluate(retryContext, context)); } @Test @Category( {CloudTests.class }) public void testGetServiceStats() throws StorageException { CloudTableClient tClient = TableTestHelper.createCloudTableClient(); tClient.getDefaultRequestOptions().setLocationMode(LocationMode.SECONDARY_ONLY); TableTestHelper.verifyServiceStats(tClient.getServiceStats()); } }