Skip to content

Commit 8839a65

Browse files
authored
[Storage] [DataMovement] [Blobs] Setting AccessTier to null will disable preserving (Azure#49231)
* [Storage] [DataMovement] [Blobs] Setting AccessTier to null will disable preserving from source to destination * Cleanup * Forgot to push assets.json * Updated checkpoint 4 bin file * Update tests * WIP * Added fix for preserve page blob premium tier * Removed unnecessary access tier calculation line
1 parent 6d246a4 commit 8839a65

15 files changed

+265
-38
lines changed

sdk/storage/Azure.Storage.DataMovement.Blobs/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
## 12.2.0-beta.1 (Unreleased)
44

55
### Features Added
6+
- Explicitly setting `BlobStorageResourceOptions.AccessTier` to `null` will disable preserving the access tier value from the source blob to the destination blob.
67

78
### Breaking Changes
89

910
### Bugs Fixed
11+
- Fixed bug where AccessTier for Premium Page Blobs were not being preserved.
1012

1113
### Other Changes
1214

sdk/storage/Azure.Storage.DataMovement.Blobs/assets.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
"AssetsRepo": "Azure/azure-sdk-assets",
33
"AssetsRepoPrefixPath": "net",
44
"TagPrefix": "net/storage/Azure.Storage.DataMovement.Blobs",
5-
"Tag": "net/storage/Azure.Storage.DataMovement.Blobs_476246f258"
5+
"Tag": "net/storage/Azure.Storage.DataMovement.Blobs_385f623849"
66
}

sdk/storage/Azure.Storage.DataMovement.Blobs/src/Azure.Storage.DataMovement.Blobs.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
</PropertyGroup>
2222
<ItemGroup>
2323
<PackageReference Include="Azure.Core" />
24-
<PackageReference Include="Azure.Storage.Blobs" />
25-
<!-- <ProjectReference Include="$(MSBuildThisFileDirectory)..\..\Azure.Storage.Blobs\src\Azure.Storage.Blobs.csproj" /> -->
24+
<!-- <PackageReference Include="Azure.Storage.Blobs" />-->
25+
<ProjectReference Include="$(MSBuildThisFileDirectory)..\..\Azure.Storage.Blobs\src\Azure.Storage.Blobs.csproj" />
2626
<ProjectReference Include="$(MSBuildThisFileDirectory)..\..\Azure.Storage.DataMovement\src\Azure.Storage.DataMovement.csproj" />
2727
<PackageReference Include="System.Threading.Channels" />
2828
</ItemGroup>

sdk/storage/Azure.Storage.DataMovement.Blobs/src/BlobDestinationCheckpointDetails.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ internal class BlobDestinationCheckpointDetails : StorageResourceCheckpointDetai
4848
/// The access tier of the destination blob.
4949
/// </summary>
5050
public AccessTier? AccessTierValue;
51+
public bool IsAccessTierSet;
5152

5253
/// <summary>
5354
/// The metadata for the destination blob.
@@ -85,6 +86,7 @@ public BlobDestinationCheckpointDetails(bool isBlobTypeSet, BlobType? blobType,
8586
contentDisposition: blobOptions?.ContentDisposition,
8687
isCacheControlSet: blobOptions?._isCacheControlSet ?? false,
8788
cacheControl: blobOptions?.CacheControl,
89+
isAccessTierSet: blobOptions?._isAccessTierSet ?? false,
8890
accessTier: blobOptions?.AccessTier,
8991
isMetadataSet: blobOptions?._isMetadataSet ?? false,
9092
metadata: blobOptions?.Metadata,
@@ -105,6 +107,7 @@ public BlobDestinationCheckpointDetails(
105107
string contentDisposition,
106108
bool isCacheControlSet,
107109
string cacheControl,
110+
bool isAccessTierSet,
108111
AccessTier? accessTier,
109112
bool isMetadataSet,
110113
Metadata metadata,
@@ -115,6 +118,7 @@ public BlobDestinationCheckpointDetails(
115118
BlobType = blobType;
116119
IsBlobTypeSet = isBlobTypeSet;
117120

121+
IsAccessTierSet = isAccessTierSet;
118122
AccessTierValue = accessTier;
119123

120124
CacheControl = cacheControl;
@@ -233,6 +237,7 @@ protected override void Serialize(Stream stream)
233237
}
234238

235239
// AccessTier
240+
writer.Write(IsAccessTierSet);
236241
writer.Write((byte)AccessTierValue.ToJobPlanAccessTier());
237242

238243
// Preserve Metadata
@@ -335,9 +340,10 @@ internal static BlobDestinationCheckpointDetails Deserialize(Stream stream)
335340
int cacheControlLength = reader.ReadInt32();
336341

337342
// AccessTier
343+
bool isAccessTierSet = reader.ReadBoolean();
338344
AccessTier? accessTier = default;
339345
JobPlanAccessTier jobPlanAccessTier = (JobPlanAccessTier)reader.ReadByte();
340-
if (!jobPlanAccessTier.Equals(JobPlanAccessTier.None))
346+
if (isAccessTierSet)
341347
{
342348
accessTier = new AccessTier(jobPlanAccessTier.ToString());
343349
}
@@ -422,6 +428,7 @@ internal static BlobDestinationCheckpointDetails Deserialize(Stream stream)
422428
contentDisposition: contentDisposition,
423429
isCacheControlSet: isCacheControlSet,
424430
cacheControl: cacheControl,
431+
isAccessTierSet: isAccessTierSet,
425432
accessTier: accessTier,
426433
isMetadataSet: isMetadataSet,
427434
metadata: metadataString.ToDictionary(nameof(metadataString)),

sdk/storage/Azure.Storage.DataMovement.Blobs/src/BlobStorageResourceOptions.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ public class BlobStorageResourceOptions
3030
private string _contentType = default;
3131
internal bool _isContentTypeSet = false;
3232

33+
private AccessTier? _accessTier = default;
34+
internal bool _isAccessTierSet = false;
35+
3336
/// <summary>
3437
/// Default constructor.
3538
/// </summary>
@@ -52,6 +55,7 @@ internal BlobStorageResourceOptions(BlobStorageResourceOptions other)
5255
ContentType = other?.ContentType;
5356
_isContentTypeSet = other?._isContentTypeSet ?? false;
5457
AccessTier = other?.AccessTier;
58+
_isAccessTierSet = other?._isAccessTierSet ?? false;
5559
}
5660

5761
internal BlobStorageResourceOptions(BlobDestinationCheckpointDetails checkpointDetails)
@@ -69,6 +73,7 @@ internal BlobStorageResourceOptions(BlobDestinationCheckpointDetails checkpointD
6973
ContentType = checkpointDetails.ContentType;
7074
_isContentTypeSet = checkpointDetails.IsContentTypeSet;
7175
AccessTier = checkpointDetails.AccessTierValue;
76+
_isAccessTierSet = checkpointDetails.IsAccessTierSet;
7277
}
7378

7479
/// <summary>
@@ -189,11 +194,19 @@ public string ContentType
189194
/// Optional. See <see cref="Storage.Blobs.Models.AccessTier"/>.
190195
/// Indicates the access tier to be set on the destination blob.
191196
///
192-
/// Access Tier is automatically preserved during blob to blob copies.
197+
/// Access Tier is automatically preserved during blob to blob copies. If explicitly set to null, the Access Tier will not be preserved from source to destination.
193198
///
194199
/// Applies to upload and copy transfers.
195200
/// Also respective Tier Values applies only to Block or Page Blobs.
196201
/// </summary>
197-
public AccessTier? AccessTier { get; set; }
202+
public AccessTier? AccessTier
203+
{
204+
get => _accessTier;
205+
set
206+
{
207+
_accessTier = value;
208+
_isAccessTierSet = true;
209+
}
210+
}
198211
}
199212
}

sdk/storage/Azure.Storage.DataMovement.Blobs/src/DataMovementBlobConstants.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@ internal class SourceCheckpointDetails
2121

2222
internal class DestinationCheckpointDetails
2323
{
24+
// Blob Schema Versions 1 and 2 were the beta version of the schema and do not need to be serialized and deserialized backwards compatible.
25+
// Only Blob Schema Versions 3 and beyond need to be backwards compatible.
2426
internal const int SchemaVersion_3 = 3;
25-
internal const int SchemaVersion = SchemaVersion_3;
27+
internal const int SchemaVersion_4 = 4;
28+
internal const int SchemaVersion = SchemaVersion_4;
2629

2730
internal const int VersionIndex = 0;
2831
internal const int PreserveBlobTypeIndex = VersionIndex + IntSizeInBytes;
@@ -47,7 +50,8 @@ internal class DestinationCheckpointDetails
4750
internal const int CacheControlOffsetIndex = PreserveCacheControlIndex + OneByte;
4851
internal const int CacheControlLengthIndex = CacheControlOffsetIndex + IntSizeInBytes;
4952

50-
internal const int AccessTierValueIndex = CacheControlLengthIndex + IntSizeInBytes;
53+
internal const int PreserveAccessTierIndex = CacheControlLengthIndex + IntSizeInBytes;
54+
internal const int AccessTierValueIndex = PreserveAccessTierIndex + OneByte;
5155

5256
internal const int PreserveMetadataIndex = AccessTierValueIndex + OneByte;
5357
internal const int MetadataOffsetIndex = PreserveMetadataIndex + OneByte;

sdk/storage/Azure.Storage.DataMovement.Blobs/src/DataMovementBlobsExtensions.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,7 @@ internal static PageBlobCreateOptions GetCreateOptions(
482482
TagConditions = options?.DestinationConditions?.TagConditions,
483483
LeaseId = options?.DestinationConditions?.LeaseId,
484484
},
485+
PremiumPageBlobAccessTier = GetAccessTier(options, sourceProperties?.RawProperties).ToPremiumPageBlobAccessTier(),
485486
};
486487
}
487488

@@ -667,7 +668,7 @@ private static BlobHttpHeaders GetHttpHeaders(
667668
private static AccessTier? GetAccessTier(
668669
BlobStorageResourceOptions options,
669670
IDictionary<string, object> properties)
670-
=> options?.AccessTier != default
671+
=> options?._isAccessTierSet ?? false
671672
? options?.AccessTier
672673
: properties?.TryGetValue(DataMovementConstants.ResourceProperties.AccessTier, out object accessTierObject) == true
673674
? (AccessTier?)accessTierObject
@@ -682,5 +683,20 @@ private static Metadata GetMetadata(
682683
: properties?.TryGetValue(DataMovementConstants.ResourceProperties.Metadata, out object metadataObject) == true
683684
? (Metadata)metadataObject
684685
: default;
686+
687+
// Convert AccessTier to PremiumPageBlobAccessTier
688+
// As long as it works. Do not set if the AccessTier is a BlockBlob tier
689+
private static PremiumPageBlobAccessTier? ToPremiumPageBlobAccessTier(this AccessTier? accessTier)
690+
{
691+
if (accessTier != default &&
692+
accessTier != AccessTier.Hot &&
693+
accessTier != AccessTier.Cool &&
694+
accessTier != AccessTier.Archive &&
695+
accessTier != AccessTier.Cold)
696+
{
697+
return new PremiumPageBlobAccessTier(accessTier.ToString());
698+
}
699+
return default;
700+
}
685701
}
686702
}

sdk/storage/Azure.Storage.DataMovement.Blobs/tests/BlobDestinationCheckpointDetailsTests.cs

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ private BlobDestinationCheckpointDetails CreatePreserveValues()
4646
default,
4747
false,
4848
default,
49+
false,
4950
default,
5051
false,
5152
default,
@@ -66,6 +67,7 @@ private BlobDestinationCheckpointDetails CreateSetSampleValues()
6667
contentDisposition: DefaultContentDisposition,
6768
isCacheControlSet: true,
6869
cacheControl: DefaultCacheControl,
70+
isAccessTierSet: true,
6971
accessTier: DefaultAccessTier,
7072
isMetadataSet: true,
7173
metadata: DefaultMetadata,
@@ -86,6 +88,7 @@ private BlobDestinationCheckpointDetails CreateSetDefaultValues()
8688
default,
8789
true,
8890
default,
91+
true,
8992
default,
9093
true,
9194
default,
@@ -94,7 +97,7 @@ private BlobDestinationCheckpointDetails CreateSetDefaultValues()
9497

9598
private void TestAssertSerializedData(BlobDestinationCheckpointDetails data)
9699
{
97-
string samplePath = Path.Combine("Resources", "BlobDestinationCheckpointDetails.3.bin");
100+
string samplePath = Path.Combine("Resources", "BlobDestinationCheckpointDetails.4.bin");
98101
using (MemoryStream dataStream = new MemoryStream(DataMovementBlobConstants.DestinationCheckpointDetails.VariableLengthStartIndex))
99102
using (FileStream fileStream = File.OpenRead(samplePath))
100103
{
@@ -126,6 +129,7 @@ public void Ctor()
126129
Assert.IsEmpty(data.ContentDispositionBytes);
127130
Assert.AreEqual(false, data.IsCacheControlSet);
128131
Assert.IsEmpty(data.CacheControlBytes);
132+
Assert.AreEqual(false, data.IsAccessTierSet);
129133
Assert.IsNull(data.AccessTierValue);
130134
Assert.AreEqual(false, data.IsMetadataSet);
131135
Assert.IsNull(data.Metadata);
@@ -138,7 +142,7 @@ public void Ctor_SetValues()
138142
{
139143
BlobDestinationCheckpointDetails data = CreateSetSampleValues();
140144

141-
VerifySampleValues(data, DataMovementBlobConstants.DestinationCheckpointDetails.SchemaVersion);
145+
VerifySampleValues_Version4(data);
142146
}
143147

144148
[Test]
@@ -156,7 +160,7 @@ public void Serialize_NoPreserveTags()
156160
data.PreserveTags = true;
157161
data.TagsBytes = default;
158162

159-
string samplePath = Path.Combine("Resources", "BlobDestinationCheckpointDetails.3.bin");
163+
string samplePath = Path.Combine("Resources", "BlobDestinationCheckpointDetails.4.bin");
160164
using (MemoryStream dataStream = new MemoryStream(DataMovementBlobConstants.DestinationCheckpointDetails.VariableLengthStartIndex))
161165
using (FileStream fileStream = File.OpenRead(samplePath))
162166
{
@@ -195,7 +199,7 @@ public void Serialize_SetAccessTier()
195199
BlobDestinationCheckpointDetails data = CreateSetSampleValues();
196200
data.AccessTierValue = AccessTier.Cold;
197201

198-
string samplePath = Path.Combine("Resources", "BlobDestinationCheckpointDetails.3.bin");
202+
string samplePath = Path.Combine("Resources", "BlobDestinationCheckpointDetails.4.bin");
199203
using (MemoryStream dataStream = new MemoryStream(DataMovementBlobConstants.DestinationCheckpointDetails.VariableLengthStartIndex))
200204
using (FileStream fileStream = File.OpenRead(samplePath))
201205
{
@@ -225,10 +229,11 @@ public void Deserialize()
225229
data.Serialize(stream);
226230
stream.Position = 0;
227231
BlobDestinationCheckpointDetails deserialized = BlobDestinationCheckpointDetails.Deserialize(stream);
228-
VerifySampleValues(deserialized, DataMovementBlobConstants.DestinationCheckpointDetails.SchemaVersion);
232+
VerifySampleValues_Version4(deserialized);
229233
}
230234
}
231235

236+
[Ignore("Renable after implementing backwards compatibility for older versions")]
232237
[Test]
233238
public void Deserialize_File_Version_3()
234239
{
@@ -237,13 +242,48 @@ public void Deserialize_File_Version_3()
237242
{
238243
stream.Position = 0;
239244
BlobDestinationCheckpointDetails deserialized = BlobDestinationCheckpointDetails.Deserialize(stream);
240-
VerifySampleValues(deserialized, 3);
245+
VerifySampleValues_Version3(deserialized);
241246
}
242247
}
243248

244-
private void VerifySampleValues(BlobDestinationCheckpointDetails data, int version)
249+
[Test]
250+
public void Deserialize_File_Version_4()
251+
{
252+
string samplePath = Path.Combine("Resources", "BlobDestinationCheckpointDetails.4.bin");
253+
using (FileStream stream = File.OpenRead(samplePath))
254+
{
255+
stream.Position = 0;
256+
BlobDestinationCheckpointDetails deserialized = BlobDestinationCheckpointDetails.Deserialize(stream);
257+
VerifySampleValues_Version4(deserialized);
258+
}
259+
}
260+
261+
private void VerifySampleValues_Version3(BlobDestinationCheckpointDetails data)
262+
{
263+
Assert.AreEqual(3, data.Version);
264+
Assert.IsTrue(data.IsBlobTypeSet);
265+
Assert.AreEqual(DefaultBlobType, data.BlobType);
266+
Assert.AreEqual(true, data.IsContentTypeSet);
267+
Assert.AreEqual(StringToByteArray(DefaultContentType), data.ContentTypeBytes);
268+
Assert.AreEqual(true, data.IsContentEncodingSet);
269+
Assert.AreEqual(StringToByteArray(DefaultContentEncoding), data.ContentEncodingBytes);
270+
Assert.AreEqual(true, data.IsContentLanguageSet);
271+
Assert.AreEqual(StringToByteArray(DefaultContentLanguage), data.ContentLanguageBytes);
272+
Assert.AreEqual(true, data.IsContentDispositionSet);
273+
Assert.AreEqual(StringToByteArray(DefaultContentDisposition), data.ContentDispositionBytes);
274+
Assert.AreEqual(true, data.IsCacheControlSet);
275+
Assert.AreEqual(StringToByteArray(DefaultCacheControl), data.CacheControlBytes);
276+
Assert.AreEqual(true, data.IsAccessTierSet);
277+
Assert.AreEqual(DefaultAccessTier, data.AccessTierValue);
278+
Assert.AreEqual(true, data.IsMetadataSet);
279+
CollectionAssert.AreEquivalent(DefaultMetadata, data.Metadata);
280+
Assert.AreEqual(false, data.PreserveTags);
281+
CollectionAssert.AreEquivalent(DefaultTags, data.Tags);
282+
}
283+
284+
private void VerifySampleValues_Version4(BlobDestinationCheckpointDetails data)
245285
{
246-
Assert.AreEqual(version, data.Version);
286+
Assert.AreEqual(4, data.Version);
247287
Assert.IsTrue(data.IsBlobTypeSet);
248288
Assert.AreEqual(DefaultBlobType, data.BlobType);
249289
Assert.AreEqual(true, data.IsContentTypeSet);
@@ -256,6 +296,7 @@ private void VerifySampleValues(BlobDestinationCheckpointDetails data, int versi
256296
Assert.AreEqual(StringToByteArray(DefaultContentDisposition), data.ContentDispositionBytes);
257297
Assert.AreEqual(true, data.IsCacheControlSet);
258298
Assert.AreEqual(StringToByteArray(DefaultCacheControl), data.CacheControlBytes);
299+
Assert.AreEqual(true, data.IsAccessTierSet);
259300
Assert.AreEqual(DefaultAccessTier, data.AccessTierValue);
260301
Assert.AreEqual(true, data.IsMetadataSet);
261302
CollectionAssert.AreEquivalent(DefaultMetadata, data.Metadata);

sdk/storage/Azure.Storage.DataMovement.Blobs/tests/BlockBlobStartTransferCopyTests.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ protected override StorageResourceItem GetDestinationStorageResourceItem(
172172
{
173173
options = new BlockBlobStorageResourceOptions
174174
{
175+
AccessTier = default,
175176
ContentDisposition = default,
176177
ContentLanguage = default,
177178
CacheControl = default,
@@ -211,7 +212,9 @@ protected override async Task VerifyPropertiesCopyAsync(
211212
Assert.IsNull(destinationProperties.ContentDisposition);
212213
Assert.IsNull(destinationProperties.ContentLanguage);
213214
Assert.IsNull(destinationProperties.CacheControl);
214-
Assert.AreEqual(_defaultAccessTier.ToString(), destinationProperties.AccessTier);
215+
// Because AccessTier is not preserved, the access tier value on the destination will
216+
// default to what the storage account sets (normally AccessTier.Hot)
217+
Assert.AreNotEqual(_defaultAccessTier.ToString(), destinationProperties.AccessTier);
215218
Assert.That(destinationProperties.ContentType, Is.Not.EqualTo(_defaultContentType));
216219

217220
GetBlobTagResult destinationTags = await destinationClient.GetTagsAsync();

0 commit comments

Comments
 (0)