diff --git a/ChangeLog.txt b/ChangeLog.txt index 7fe17ea69bbf5..b16680c08803d 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,3 +1,6 @@ +2017.XX.XX Version X.X.X + * For Standard Storage Accounts only, added the ability to set the tier of individual block blobs. The tier can currently only be set through uploadTier() + 2017.07.12 Version 5.4.0 * Added ErrorReceivingResponseEvent which fires when a network error occurs before the responseReceivedEvent fires. If the responseReceivedEvent fires sucessfully, this new event will not fire. * For Premium Accounts only, added support for getting and setting the tier on a page blob. The tier can also be set when creating or copying from an existing page blob. diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlockBlobTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlockBlobTests.java index c62f29da77a4d..579d46b8e6293 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlockBlobTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudBlockBlobTests.java @@ -48,15 +48,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.security.InvalidKeyException; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Date; -import java.util.EnumSet; -import java.util.GregorianCalendar; -import java.util.List; -import java.util.Map; -import java.util.Random; -import java.util.TimeZone; +import java.util.*; import com.microsoft.azure.storage.StorageErrorCodeStrings; import com.microsoft.azure.storage.TestRunners.CloudTests; @@ -1942,4 +1934,82 @@ private void doCloudBlockBlobCopy(boolean sourceIsSas, boolean destinationIsSas) destination.delete(); source.delete(); } + + @Test + @Category({ DevFabricTests.class, DevStoreTests.class }) + public void testCloudBlockBlobUploadStandardTier() throws StorageException, IOException, URISyntaxException { + for (StandardBlobTier standardBlobTier : StandardBlobTier.values()) { + if (standardBlobTier == StandardBlobTier.UNKNOWN) { + continue; + } + + final String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testBlob"); + final CloudBlockBlob blob = this.container.getBlockBlobReference(blobName); + blob.uploadText("text"); + + blob.uploadStandardBlobTier(standardBlobTier); + assertEquals(standardBlobTier, blob.getProperties().getStandardBlobTier()); + assertNull(blob.getProperties().getPremiumPageBlobTier()); + assertNull(blob.getProperties().getRehydrationStatus()); + + CloudBlockBlob blob2 = this.container.getBlockBlobReference(blobName); + blob2.downloadAttributes(); + assertEquals(standardBlobTier, blob2.getProperties().getStandardBlobTier()); + assertNull(blob2.getProperties().getPremiumPageBlobTier()); + assertNull(blob2.getProperties().getRehydrationStatus()); + + CloudBlockBlob blob3 = (CloudBlockBlob)this.container.listBlobs().iterator().next(); + assertEquals(standardBlobTier, blob3.getProperties().getStandardBlobTier()); + assertNull(blob3.getProperties().getPremiumPageBlobTier()); + assertNull(blob3.getProperties().getRehydrationStatus()); + + blob.deleteIfExists(); + } + } + + @Test + @Category({ DevFabricTests.class, DevStoreTests.class }) + public void testCloudBlockBlobRehydrateBlob() throws StorageException, IOException, URISyntaxException { + final String blobName1 = BlobTestHelper.generateRandomBlobNameWithPrefix("testBlob1"); + final String blobName2 = BlobTestHelper.generateRandomBlobNameWithPrefix("testBlob2"); + final CloudBlockBlob blob = this.container.getBlockBlobReference(blobName1); + blob.uploadText("text"); + blob.uploadStandardBlobTier(StandardBlobTier.ARCHIVE); + final CloudBlockBlob blob2 = this.container.getBlockBlobReference(blobName2); + blob2.uploadText("text"); + blob2.uploadStandardBlobTier(StandardBlobTier.ARCHIVE); + + CloudBlockBlob blobRef1 = this.container.getBlockBlobReference(blobName1); + blobRef1.uploadStandardBlobTier(StandardBlobTier.COOL); + assertNull(blobRef1.getProperties().getRehydrationStatus()); + assertEquals(StandardBlobTier.ARCHIVE, blobRef1.getProperties().getStandardBlobTier()); + assertNull(blobRef1.getProperties().getPremiumPageBlobTier()); + + blob.downloadAttributes(); + assertEquals(RehydrationStatus.PENDING_TO_COOL, blob.getProperties().getRehydrationStatus()); + assertEquals(StandardBlobTier.ARCHIVE, blob.getProperties().getStandardBlobTier()); + assertNull(blob.getProperties().getPremiumPageBlobTier()); + + CloudBlockBlob blobRef2 = this.container.getBlockBlobReference(blobName2); + blobRef2.uploadStandardBlobTier(StandardBlobTier.HOT); + assertNull(blobRef2.getProperties().getRehydrationStatus()); + assertEquals(StandardBlobTier.ARCHIVE, blobRef2.getProperties().getStandardBlobTier()); + assertNull(blobRef2.getProperties().getPremiumPageBlobTier()); + + blob2.downloadAttributes(); + assertEquals(RehydrationStatus.PENDING_TO_HOT, blob2.getProperties().getRehydrationStatus()); + assertEquals(StandardBlobTier.ARCHIVE, blob2.getProperties().getStandardBlobTier()); + assertNull(blob2.getProperties().getPremiumPageBlobTier()); + + Iterator it = this.container.listBlobs().iterator(); + CloudBlockBlob listBlob = (CloudBlockBlob)it.next(); + assertEquals(RehydrationStatus.PENDING_TO_COOL, listBlob.getProperties().getRehydrationStatus()); + assertEquals(StandardBlobTier.ARCHIVE, listBlob.getProperties().getStandardBlobTier()); + assertNull(listBlob.getProperties().getPremiumPageBlobTier()); + + CloudBlockBlob listBlob2 = (CloudBlockBlob)it.next(); + assertEquals(RehydrationStatus.PENDING_TO_HOT, listBlob2.getProperties().getRehydrationStatus()); + assertEquals(StandardBlobTier.ARCHIVE, listBlob2.getProperties().getStandardBlobTier()); + assertNull(listBlob2.getProperties().getPremiumPageBlobTier()); + } } diff --git a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudPageBlobTests.java b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudPageBlobTests.java index c8657699d40a5..6ade1eb60b60e 100644 --- a/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudPageBlobTests.java +++ b/microsoft-azure-storage-test/src/com/microsoft/azure/storage/blob/CloudPageBlobTests.java @@ -1242,39 +1242,47 @@ public void testCloudPageBlobSetPremiumBlobTierOnCreate() throws URISyntaxExcept // Test create API CloudPageBlob blob = container.getPageBlobReference(blobName); - assertNull(blob.getProperties().getInferredBlobTier()); + assertNull(blob.getProperties().isBlobTierInferred()); blob.create(1024, PremiumPageBlobTier.P4, null, null, null); assertEquals(PremiumPageBlobTier.P4, blob.getProperties().getPremiumPageBlobTier()); - assertFalse(blob.getProperties().getInferredBlobTier()); + assertFalse(blob.getProperties().isBlobTierInferred()); + assertNull(blob.getProperties().getStandardBlobTier()); + assertNull(blob.getProperties().getRehydrationStatus()); CloudPageBlob blob2 = container.getPageBlobReference(blobName); blob2.downloadAttributes(); assertEquals(PremiumPageBlobTier.P4, blob2.getProperties().getPremiumPageBlobTier()); - assertNull(blob2.getProperties().getInferredBlobTier()); + assertNull(blob2.getProperties().isBlobTierInferred()); + assertNull(blob2.getProperties().getStandardBlobTier()); + assertNull(blob2.getProperties().getRehydrationStatus()); // Test upload from byte array API byte[] buffer = BlobTestHelper.getRandomBuffer(1024); CloudPageBlob blob3 = container.getPageBlobReference("blob3"); blob3.uploadFromByteArray(buffer, 0, 1024, PremiumPageBlobTier.P6, null, null, null); assertEquals(PremiumPageBlobTier.P6, blob3.getProperties().getPremiumPageBlobTier()); - assertFalse(blob3.getProperties().getInferredBlobTier()); + assertFalse(blob3.getProperties().isBlobTierInferred()); + assertNull(blob3.getProperties().getStandardBlobTier()); + assertNull(blob3.getProperties().getRehydrationStatus()); CloudPageBlob blob3Ref = container.getPageBlobReference("blob3"); blob3Ref.downloadAttributes(); assertEquals(PremiumPageBlobTier.P6, blob3Ref.getProperties().getPremiumPageBlobTier()); - assertNull(blob3Ref.getProperties().getInferredBlobTier()); + assertNull(blob3Ref.getProperties().isBlobTierInferred()); // Test upload from stream API ByteArrayInputStream srcStream = new ByteArrayInputStream(buffer); CloudPageBlob blob4 = container.getPageBlobReference("blob4"); blob4.upload(srcStream, 1024, PremiumPageBlobTier.P10, null, null, null); assertEquals(PremiumPageBlobTier.P10, blob4.getProperties().getPremiumPageBlobTier()); - assertFalse(blob4.getProperties().getInferredBlobTier()); + assertFalse(blob4.getProperties().isBlobTierInferred()); + assertNull(blob4.getProperties().getStandardBlobTier()); + assertNull(blob4.getProperties().getRehydrationStatus()); CloudPageBlob blob4Ref = container.getPageBlobReference("blob4"); blob4Ref.downloadAttributes(); assertEquals(PremiumPageBlobTier.P10, blob4Ref.getProperties().getPremiumPageBlobTier()); - assertNull(blob4Ref.getProperties().getInferredBlobTier()); + assertNull(blob4Ref.getProperties().isBlobTierInferred()); // Test upload from file API File sourceFile = File.createTempFile("sourceFile", ".tmp"); @@ -1286,12 +1294,14 @@ public void testCloudPageBlobSetPremiumBlobTierOnCreate() throws URISyntaxExcept CloudPageBlob blob5 = container.getPageBlobReference("blob5"); blob5.uploadFromFile(sourceFile.getAbsolutePath(), PremiumPageBlobTier.P20, null, null, null); assertEquals(PremiumPageBlobTier.P20, blob5.getProperties().getPremiumPageBlobTier()); - assertFalse(blob5.getProperties().getInferredBlobTier()); + assertFalse(blob5.getProperties().isBlobTierInferred()); + assertNull(blob5.getProperties().getStandardBlobTier()); + assertNull(blob5.getProperties().getRehydrationStatus()); CloudPageBlob blob5Ref = container.getPageBlobReference("blob5"); blob5Ref.downloadAttributes(); assertEquals(PremiumPageBlobTier.P20, blob5Ref.getProperties().getPremiumPageBlobTier()); - assertNull(blob5Ref.getProperties().getInferredBlobTier()); + assertNull(blob5Ref.getProperties().isBlobTierInferred()); } finally { container.deleteIfExists(); @@ -1307,19 +1317,21 @@ public void testCloudPageBlobSetBlobTier() throws URISyntaxException, StorageExc String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testblob"); CloudPageBlob blob = container.getPageBlobReference(blobName); blob.create(1024); - assertNull(blob.getProperties().getInferredBlobTier()); + assertNull(blob.getProperties().isBlobTierInferred()); blob.downloadAttributes(); - assertTrue(blob.getProperties().getInferredBlobTier()); + assertTrue(blob.getProperties().isBlobTierInferred()); assertEquals(PremiumPageBlobTier.P10, blob.getProperties().getPremiumPageBlobTier()); blob.uploadPremiumPageBlobTier(PremiumPageBlobTier.P40); assertEquals(PremiumPageBlobTier.P40, blob.properties.getPremiumPageBlobTier()); - assertFalse(blob.getProperties().getInferredBlobTier()); + assertFalse(blob.getProperties().isBlobTierInferred()); + assertNull(blob.getProperties().getStandardBlobTier()); + assertNull(blob.getProperties().getRehydrationStatus()); CloudPageBlob blob2 = container.getPageBlobReference(blobName); blob2.downloadAttributes(); assertEquals(PremiumPageBlobTier.P40, blob2.properties.getPremiumPageBlobTier()); - assertNull(blob2.getProperties().getInferredBlobTier()); + assertNull(blob2.getProperties().isBlobTierInferred()); boolean pageBlobWithTierFound = false; for (ListBlobItem blobItem : container.listBlobs()) { @@ -1328,7 +1340,9 @@ public void testCloudPageBlobSetBlobTier() throws URISyntaxException, StorageExc if (blob.getName().equals(blobName) && !pageBlobWithTierFound) { // Check that the blob is found exactly once assertEquals(PremiumPageBlobTier.P40, blob3.properties.getPremiumPageBlobTier()); - assertFalse(blob3.getProperties().getInferredBlobTier()); + assertNull(blob3.getProperties().isBlobTierInferred()); + assertNull(blob3.getProperties().getStandardBlobTier()); + assertNull(blob3.getProperties().getRehydrationStatus()); pageBlobWithTierFound = true; } else if (blob.getName().equals(blobName)) { fail("Page blob found twice"); @@ -1379,14 +1393,18 @@ public void testCloudPageBlobSetBlobTierOnCopy() throws URISyntaxException, Stor assertEquals(BlobType.PAGE_BLOB, copy.getProperties().getBlobType()); assertEquals(PremiumPageBlobTier.P30, copy.getProperties().getPremiumPageBlobTier()); assertEquals(PremiumPageBlobTier.P10, source.getProperties().getPremiumPageBlobTier()); - assertFalse(source.getProperties().getInferredBlobTier()); - assertFalse(copy.getProperties().getInferredBlobTier()); + assertFalse(source.getProperties().isBlobTierInferred()); + assertFalse(copy.getProperties().isBlobTierInferred()); + assertNull(source.getProperties().getStandardBlobTier()); + assertNull(source.getProperties().getRehydrationStatus()); + assertNull(copy.getProperties().getStandardBlobTier()); + assertNull(copy.getProperties().getRehydrationStatus()); BlobTestHelper.waitForCopy(copy); CloudPageBlob copyRef = container.getPageBlobReference("copy"); copyRef.downloadAttributes(); assertEquals(PremiumPageBlobTier.P30, copyRef.getProperties().getPremiumPageBlobTier()); - assertNull(copyRef.getProperties().getInferredBlobTier()); + assertNull(copyRef.getProperties().isBlobTierInferred()); // copy where source does not have a tier CloudPageBlob source2 = container.getPageBlobReference("source2"); @@ -1397,8 +1415,12 @@ public void testCloudPageBlobSetBlobTierOnCopy() throws URISyntaxException, Stor assertEquals(BlobType.PAGE_BLOB, copy3.getProperties().getBlobType()); assertEquals(PremiumPageBlobTier.P60, copy3.getProperties().getPremiumPageBlobTier()); assertNull(source2.getProperties().getPremiumPageBlobTier()); - assertNull(source2.getProperties().getInferredBlobTier()); - assertFalse(copy3.getProperties().getInferredBlobTier()); + assertNull(source2.getProperties().isBlobTierInferred()); + assertFalse(copy3.getProperties().isBlobTierInferred()); + assertNull(source2.getProperties().getStandardBlobTier()); + assertNull(source2.getProperties().getRehydrationStatus()); + assertNull(copy3.getProperties().getStandardBlobTier()); + assertNull(copy3.getProperties().getRehydrationStatus()); } finally { container.deleteIfExists(); diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/Constants.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/Constants.java index 278150e9e9f65..68ba8b0a7967f 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/Constants.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/Constants.java @@ -899,6 +899,11 @@ public static class QueryConstants { */ public static final String ACCESS_TIER = "AccessTier"; + /** + * XML element for the archive status. + */ + public static final String ARCHIVE_STATUS = "ArchiveStatus"; + /** * Buffer width used to copy data to output streams. */ diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobConstants.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobConstants.java index 603e513644cb3..102bbd4bdc974 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobConstants.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobConstants.java @@ -30,11 +30,17 @@ final class BlobConstants { * The header that specifies if the access tier is inferred. */ public static final String ACCESS_TIER_INFERRED_HEADER = Constants.PREFIX_FOR_STORAGE_HEADER + "access-tier-inferred"; + /** * Specifies the append blob type. */ public static final String APPEND_BLOB = "AppendBlob"; + /** + * The header that specifies the archive status. + */ + public static final String ARCHIVE_STATUS_HEADER = Constants.PREFIX_FOR_STORAGE_HEADER + "archive-status"; + /** * XML element for authentication error details. */ diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobListHandler.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobListHandler.java index 1898af48aad70..5fe51e3f866ae 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobListHandler.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobListHandler.java @@ -325,9 +325,31 @@ else if (Constants.COPY_DESTINATION_SNAPSHOT_ID_ELEMENT.equals(currentNode)) { this.copyState.setCopyDestinationSnapshotID(value); } else if (Constants.ACCESS_TIER.equals(currentNode)) { - PremiumPageBlobTier premiumPageBlobTier = PremiumPageBlobTier.parse(value); - this.properties.setPremiumPageBlobTier(premiumPageBlobTier); - this.properties.setBlobTierInferredTier(false); + if (properties.getBlobType().equals(BlobType.PAGE_BLOB)) { + PremiumPageBlobTier premiumPageBlobTier = PremiumPageBlobTier.parse(value); + this.properties.setPremiumPageBlobTier(premiumPageBlobTier); + } + else if (properties.getBlobType().equals(BlobType.BLOCK_BLOB)) { + StandardBlobTier standardBlobTier = StandardBlobTier.parse(value); + this.properties.setStandardBlobTier(standardBlobTier); + } + else if (properties.getBlobType().equals(BlobType.UNSPECIFIED)) { + PremiumPageBlobTier premiumPageBlobTier = PremiumPageBlobTier.parse(value); + StandardBlobTier standardBlobTier = StandardBlobTier.parse(value); + if (!premiumPageBlobTier.equals(PremiumPageBlobTier.UNKNOWN)) { + properties.setPremiumPageBlobTier(premiumPageBlobTier); + } + else if (!standardBlobTier.equals(StandardBlobTier.UNKNOWN)) { + properties.setStandardBlobTier(standardBlobTier); + } + else { + properties.setPremiumPageBlobTier(PremiumPageBlobTier.UNKNOWN); + properties.setStandardBlobTier(StandardBlobTier.UNKNOWN); + } + } + } + else if (Constants.ARCHIVE_STATUS.equals(currentNode)) { + this.properties.setRehydrationStatus(RehydrationStatus.parse(value)); } } } \ No newline at end of file diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobProperties.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobProperties.java index 0c227c48945bb..3c645e3a4ce87 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobProperties.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobProperties.java @@ -122,11 +122,21 @@ public final class BlobProperties { */ private PremiumPageBlobTier premiumPageBlobTier; + /** + * Represents the tier on a blob on a standard storage account. + */ + private StandardBlobTier standardBlobTier; + /** * Represents whether or not the blob tier is inferred. */ private Boolean isBlobTierInferredTier; - + + /** + * Represents the rehydration status if the blob is being rehydrated. + */ + private RehydrationStatus rehydrationStatus; + /** * Creates an instance of the BlobProperties class. */ @@ -152,16 +162,18 @@ public BlobProperties(final BlobProperties other) { this.contentType = other.contentType; this.copyState = other.copyState; this.etag = other.etag; + this.isBlobTierInferredTier = other.isBlobTierInferredTier; + this.isIncrementalCopy = other.isIncrementalCopy; this.leaseStatus = other.leaseStatus; this.leaseState = other.leaseState; this.leaseDuration = other.leaseDuration; this.length = other.length; this.lastModified = other.lastModified; this.pageBlobSequenceNumber = other.pageBlobSequenceNumber; - this.serverEncrypted = other.serverEncrypted; - this.isIncrementalCopy = other.isIncrementalCopy; this.premiumPageBlobTier = other.premiumPageBlobTier; - this.isBlobTierInferredTier = other.isBlobTierInferredTier; + this.serverEncrypted = other.serverEncrypted; + this.standardBlobTier = other.standardBlobTier; + this.rehydrationStatus = other.rehydrationStatus; } /** @@ -288,7 +300,7 @@ public Date getLastModified() { * * @return A {@Link java.lang.Boolean} object which represents if the blob tier was inferred. */ - public Boolean getInferredBlobTier() { return this.isBlobTierInferredTier; } + public Boolean isBlobTierInferred() { return this.isBlobTierInferredTier; } /** * Gets the lease status for the blob. @@ -344,6 +356,22 @@ public PremiumPageBlobTier getPremiumPageBlobTier() { return this.premiumPageBlobTier; } + /** + * If using a standard account and the blob is a block blob, gets the tier of the blob. + * @return A {@link StandardBlobTier} object which represents the tier of the blob + * or null if the tier has not been set. + */ + public StandardBlobTier getStandardBlobTier() { + return this.standardBlobTier; + } + + /** + * The rehydration status if the blob is being rehydrated + * and the tier of the blob once the rehydration from archive has completed. + * @return + */ + public RehydrationStatus getRehydrationStatus() { return this.rehydrationStatus; } + /** * Gets the blob's server-side encryption status; * @@ -550,12 +578,30 @@ protected void setPremiumPageBlobTier(PremiumPageBlobTier premiumPageBlobTier) { this.premiumPageBlobTier = premiumPageBlobTier; } + /** + * Sets the tier of the block blob. This is only supported for standard storage accounts. + * @param standardBlobTier + * A {@link StandardBlobTier} object which represents the tier of the blob. + */ + protected void setStandardBlobTier(StandardBlobTier standardBlobTier) { + this.standardBlobTier = standardBlobTier; + } + /** * Sets whether the blob tier is inferred. * @param isBlobTierInferredTier * A {@Link java.lang.Boolean} which specifies if the blob tier is inferred. */ - protected void setBlobTierInferredTier(Boolean isBlobTierInferredTier) { + protected void setBlobTierInferred(Boolean isBlobTierInferredTier) { this.isBlobTierInferredTier = isBlobTierInferredTier; } + + /** + * Sets the rehydration status of the blob. + * @param rehydrationStatus + * A {@Link RehydrationStatus} which specifies the rehydration status of the blob. + */ + protected void setRehydrationStatus(RehydrationStatus rehydrationStatus) { + this.rehydrationStatus = rehydrationStatus; + } } \ No newline at end of file diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobResponse.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobResponse.java index 0e26efa41847d..70b38e691c5fe 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobResponse.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobResponse.java @@ -125,23 +125,43 @@ else if (!Utility.isNullOrEmpty(xContentLengthHeader)) { } // Get the tier of the blob - final String premiumBlobTierString = request.getHeaderField(BlobConstants.ACCESS_TIER_HEADER); + final String blobTierString = request.getHeaderField(BlobConstants.ACCESS_TIER_HEADER); - if (properties.getBlobType().equals(BlobType.PAGE_BLOB)) - { - PremiumPageBlobTier premiumPageBlobTier = PremiumPageBlobTier.parse(premiumBlobTierString); + if (!Utility.isNullOrEmpty(blobTierString) && properties.getBlobType().equals(BlobType.PAGE_BLOB)) { + PremiumPageBlobTier premiumPageBlobTier = PremiumPageBlobTier.parse(blobTierString); properties.setPremiumPageBlobTier(premiumPageBlobTier); } - else if (properties.getBlobType().equals(BlobType.UNSPECIFIED)) { - PremiumPageBlobTier premiumPageBlobTier = PremiumPageBlobTier.parse(premiumBlobTierString); + else if (!Utility.isNullOrEmpty(blobTierString) && properties.getBlobType().equals(BlobType.BLOCK_BLOB)) { + StandardBlobTier standardBlobTier = StandardBlobTier.parse(blobTierString); + properties.setStandardBlobTier(standardBlobTier); + } + else if (!Utility.isNullOrEmpty(blobTierString) && properties.getBlobType().equals(BlobType.UNSPECIFIED)) { + PremiumPageBlobTier premiumPageBlobTier = PremiumPageBlobTier.parse(blobTierString); + StandardBlobTier standardBlobTier = StandardBlobTier.parse(blobTierString); if (!premiumPageBlobTier.equals(PremiumPageBlobTier.UNKNOWN)) { properties.setPremiumPageBlobTier(premiumPageBlobTier); } + else if (!standardBlobTier.equals(StandardBlobTier.UNKNOWN)) { + properties.setStandardBlobTier(standardBlobTier); + } + else { + properties.setPremiumPageBlobTier(PremiumPageBlobTier.UNKNOWN); + properties.setStandardBlobTier(StandardBlobTier.UNKNOWN); + } } final String tierInferredString = request.getHeaderField(BlobConstants.ACCESS_TIER_INFERRED_HEADER); if (!Utility.isNullOrEmpty(tierInferredString)) { - properties.setBlobTierInferredTier(Boolean.parseBoolean(tierInferredString)); + properties.setBlobTierInferred(Boolean.parseBoolean(tierInferredString)); + } + + final String rehydrationStatusString = request.getHeaderField(BlobConstants.ARCHIVE_STATUS_HEADER); + if (!Utility.isNullOrEmpty(rehydrationStatusString)) { + RehydrationStatus rehydrationStatus = RehydrationStatus.parse(rehydrationStatusString); + properties.setRehydrationStatus(rehydrationStatus); + } + else { + properties.setRehydrationStatus(null); } final String incrementalCopyHeaderString = diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlob.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlob.java index ad64655b3a56b..4e5926ede8ae6 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlob.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlob.java @@ -772,7 +772,7 @@ public String preProcessResponse(CloudBlob blob, CloudBlobClient client, Operati blob.properties.setCopyState(BlobResponse.getCopyState(this.getConnection())); blob.properties.setPremiumPageBlobTier(premiumPageBlobTier); if (premiumPageBlobTier != null) { - blob.properties.setBlobTierInferredTier(false); + blob.properties.setBlobTierInferred(false); } return blob.properties.getCopyState().getCopyId(); @@ -2600,6 +2600,56 @@ public Void preProcessResponse(CloudBlob blob, CloudBlobClient client, Operation return putRequest; } + protected StorageRequest uploadBlobTierImpl(final String blobTierString, final BlobRequestOptions options) { + final StorageRequest setTierRequest = new StorageRequest( + options, this.getStorageUri()) { + + @Override + public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, OperationContext context) + throws Exception { + return BlobRequest.setBlobTier(blob.getTransformedAddress(context).getUri(this.getCurrentLocation()), options, context, blobTierString); + } + + @Override + public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) + throws Exception { + StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context); + } + + @Override + public Void preProcessResponse(CloudBlob blob, CloudBlobClient client, OperationContext context) + throws Exception { + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK && this.getResult().getStatusCode() != HttpURLConnection.HTTP_ACCEPTED) { + this.setNonExceptionedRetryableFailure(true); + return null; + } + + blob.updateEtagAndLastModifiedFromResponse(this.getConnection()); + this.getResult().setRequestServiceEncrypted(BaseResponse.isServerRequestEncrypted(this.getConnection())); + blob.getProperties().setBlobTierInferred(false); + if (blob.getProperties().getBlobType() == BlobType.BLOCK_BLOB) { + // For standard accounts when rehydrating a blob from archive, the status code will be 202 instead of 200. + StandardBlobTier standardBlobTier = StandardBlobTier.parse(blobTierString); + blob.getProperties().setRehydrationStatus(null); + if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_OK) { + blob.getProperties().setStandardBlobTier(standardBlobTier); + } + else if (standardBlobTier.equals(StandardBlobTier.COOL)) { + blob.getProperties().setStandardBlobTier(StandardBlobTier.ARCHIVE); + } + else if (standardBlobTier.equals(StandardBlobTier.HOT)) { + blob.getProperties().setStandardBlobTier(StandardBlobTier.ARCHIVE); + } + } + + return null; + } + + }; + + return setTierRequest; + } + /** * Sets the container for the blob. * diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlockBlob.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlockBlob.java index 411752dcd7459..94e70ebab9599 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlockBlob.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudBlockBlob.java @@ -1240,6 +1240,49 @@ public void setStreamWriteSizeInBytes(final int streamWriteSizeInBytes) this.isStreamWriteSizeModified = true; } + /** + * Sets the blob tier on a block blob on a standard storage account. + * @param standardBlobTier + * A {@link StandardBlobTier} object which represents the tier of the blob. + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public void uploadStandardBlobTier(final StandardBlobTier standardBlobTier) throws StorageException { + this.uploadStandardBlobTier(standardBlobTier, null /* options */, null /* opContext */); + } + + /** + * Sets the tier on a block blob on a standard storage account. + * @param standardBlobTier + * A {@link StandardBlobTier} object which represents the tier of the blob. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying + * null will use the default request options from the associated service client ( + * {@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object which represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public void uploadStandardBlobTier(final StandardBlobTier standardBlobTier, BlobRequestOptions options, + OperationContext opContext) throws StorageException { + assertNoWriteOperationForSnapshot(); + Utility.assertNotNull("standardBlobTier", standardBlobTier); + + if (opContext == null) { + opContext = new OperationContext(); + } + + options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.BLOCK_BLOB, this.blobServiceClient); + + ExecutionEngine.executeWithRetry(this.blobServiceClient, this, + this.uploadBlobTierImpl(standardBlobTier.toString(), options), options.getRetryPolicyFactory(), opContext); + } + /** * Gets the flag that indicates whether the default streamWriteSize was modified. */ diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudPageBlob.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudPageBlob.java index 8553b05198379..6296b9a089876 100644 --- a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudPageBlob.java +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/CloudPageBlob.java @@ -580,7 +580,7 @@ public Void preProcessResponse(CloudBlob blob, CloudBlobClient client, Operation blob.getProperties().setLength(length); blob.getProperties().setPremiumPageBlobTier(premiumBlobTier); if (premiumBlobTier != null) { - blob.getProperties().setBlobTierInferredTier(false); + blob.getProperties().setBlobTierInferred(false); } return null; @@ -1611,43 +1611,8 @@ public void uploadPremiumPageBlobTier(final PremiumPageBlobTier premiumBlobTier, options = BlobRequestOptions.populateAndApplyDefaults(options, BlobType.PAGE_BLOB, this.blobServiceClient); ExecutionEngine.executeWithRetry(this.blobServiceClient, this, - this.uploadPremiumPageBlobTierImpl(premiumBlobTier, options), options.getRetryPolicyFactory(), opContext); - } - - private StorageRequest uploadPremiumPageBlobTierImpl(final PremiumPageBlobTier premiumBlobTier, - final BlobRequestOptions options) { - final StorageRequest setTierRequest = new StorageRequest( - options, this.getStorageUri()) { - - @Override - public HttpURLConnection buildRequest(CloudBlobClient client, CloudBlob blob, OperationContext context) - throws Exception { - return BlobRequest.setBlobTier(blob.getTransformedAddress(context).getUri(this.getCurrentLocation()), options, context, premiumBlobTier.toString()); - } - - @Override - public void signRequest(HttpURLConnection connection, CloudBlobClient client, OperationContext context) - throws Exception { - StorageRequest.signBlobQueueAndFileRequest(connection, client, 0L, context); - } - - @Override - public Void preProcessResponse(CloudBlob blob, CloudBlobClient client, OperationContext context) - throws Exception { - if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) { - this.setNonExceptionedRetryableFailure(true); - return null; - } - - blob.updateEtagAndLastModifiedFromResponse(this.getConnection()); - blob.properties.setPremiumPageBlobTier(premiumBlobTier); - blob.properties.setBlobTierInferredTier(false); - - return null; - } - - }; - - return setTierRequest; + this.uploadBlobTierImpl(premiumBlobTier.toString(), options), options.getRetryPolicyFactory(), opContext); + this.properties.setPremiumPageBlobTier(premiumBlobTier); + this.properties.setBlobTierInferred(false); } } \ No newline at end of file diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/RehydrationStatus.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/RehydrationStatus.java new file mode 100644 index 0000000000000..34f176d73caca --- /dev/null +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/RehydrationStatus.java @@ -0,0 +1,49 @@ +package com.microsoft.azure.storage.blob; + +import com.microsoft.azure.storage.core.Utility; + +import java.util.Locale; + +/** + * The rehydration status for the blob that is currently archived. + * Only applicable for block blobs on standard storage accounts for this version. + */ +public enum RehydrationStatus { + /** + * The rehydration status is not recognized by this version of the library. + */ + UNKNOWN, + + /** + * The blob is being rehydrated to hot storage. + */ + PENDING_TO_HOT, + + /** + * The blob is being rehydrated to cool storage. + **/ + PENDING_TO_COOL; + + /** + * Parses a rehydration status from the given string. + * + * @param rehydrationStatusString + * A String which represents the rehydration status to string. + * + * @return A RehydrationStatus value that represents the rehydration status of the blob. + */ + protected static RehydrationStatus parse(final String rehydrationStatusString) { + if (Utility.isNullOrEmpty(rehydrationStatusString)) { + return UNKNOWN; + } + else if ("rehydrate-pending-to-hot".equals(rehydrationStatusString.toLowerCase(Locale.US))) { + return PENDING_TO_HOT; + } + else if ("rehydrate-pending-to-cool".equals(rehydrationStatusString.toLowerCase(Locale.US))) { + return PENDING_TO_COOL; + } + else { + return UNKNOWN; + } + } +} \ No newline at end of file diff --git a/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/StandardBlobTier.java b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/StandardBlobTier.java new file mode 100644 index 0000000000000..251c859871c50 --- /dev/null +++ b/microsoft-azure-storage/src/com/microsoft/azure/storage/blob/StandardBlobTier.java @@ -0,0 +1,70 @@ +/** + * 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.core.Utility; + +import java.util.Locale; + +/** + * The tier of the block blob on a standard storage account. + */ +public enum StandardBlobTier { + /** + * The tier is not recognized by this version of the library. + */ + UNKNOWN, + + /** + * The tier is hot storage. + */ + HOT, + + /** + * The tier is cool storage. + */ + COOL, + + /** + * The tier is archive storage. + */ + ARCHIVE; + + /** + * Parses a standard blob tier from the given string. + * + * @param standardBlobTierString + * A String which represents the tier of the blob tier on a standard storage account. + * + * @return A StandardBlobTier value that represents the standard blob tier. + */ + protected static StandardBlobTier parse(final String standardBlobTierString) { + if (Utility.isNullOrEmpty(standardBlobTierString)) { + return UNKNOWN; + } + else if ("hot".equals(standardBlobTierString.toLowerCase(Locale.US))) { + return HOT; + } + else if ("cool".equals(standardBlobTierString.toLowerCase(Locale.US))) { + return COOL; + } + else if ("archive".equals(standardBlobTierString.toLowerCase(Locale.US))) { + return ARCHIVE; + } + else { + return UNKNOWN; + } + } +}