Skip to content

Commit 7e0ea20

Browse files
authored
Storage Error Codes for Copies (#42845)
1 parent a07c156 commit 7e0ea20

12 files changed

Lines changed: 227 additions & 2 deletions

File tree

sdk/storage/Azure.Storage.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.Blobs",
5-
"Tag": "net/storage/Azure.Storage.Blobs_9497f4a828"
5+
"Tag": "net/storage/Azure.Storage.Blobs_62a8c8fa29"
66
}

sdk/storage/Azure.Storage.Blobs/src/BlobClientOptions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,8 @@ private void AddHeadersAndQueryParameters()
306306
Diagnostics.LoggedHeaderNames.Add("x-ms-source-if-unmodified-since");
307307
Diagnostics.LoggedHeaderNames.Add("x-ms-tag-count");
308308
Diagnostics.LoggedHeaderNames.Add("x-ms-encryption-key-sha256");
309+
Diagnostics.LoggedHeaderNames.Add("x-ms-copy-source-error-code");
310+
Diagnostics.LoggedHeaderNames.Add("x-ms-copy-source-status-code");
309311

310312
Diagnostics.LoggedQueryParameters.Add("comp");
311313
Diagnostics.LoggedQueryParameters.Add("maxresults");

sdk/storage/Azure.Storage.Blobs/tests/AppendBlobClientTests.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1317,6 +1317,27 @@ public async Task AppendBlockFromUriAsync_Min()
13171317
}
13181318
}
13191319

1320+
[RecordedTest]
1321+
[ServiceVersion(Min = BlobClientOptions.ServiceVersion.V2024_08_04)]
1322+
public async Task AppendBlockFromUriAsync_SourceErrorAndStatusCode()
1323+
{
1324+
await using DisposingContainer test = await GetTestContainerAsync(publicAccessType: PublicAccessType.None);
1325+
1326+
AppendBlobClient sourceBlob = InstrumentClient(test.Container.GetAppendBlobClient(GetNewBlobName()));
1327+
AppendBlobClient destBlob = InstrumentClient(test.Container.GetAppendBlobClient(GetNewBlobName()));
1328+
await destBlob.CreateIfNotExistsAsync();
1329+
1330+
// Act
1331+
await TestHelper.AssertExpectedExceptionAsync<RequestFailedException>(
1332+
destBlob.AppendBlockFromUriAsync(sourceBlob.Uri),
1333+
e =>
1334+
{
1335+
Assert.IsTrue(e.Message.Contains("CopySourceStatusCode: 409"));
1336+
Assert.IsTrue(e.Message.Contains("CopySourceErrorCode: PublicAccessNotPermitted"));
1337+
Assert.IsTrue(e.Message.Contains("CopySourceErrorMessage: Public access is not permitted on this storage account."));
1338+
});
1339+
}
1340+
13201341
[RecordedTest]
13211342
[TestCase(nameof(AppendBlobRequestConditions.LeaseId))]
13221343
[TestCase(nameof(AppendBlobRequestConditions.TagConditions))]

sdk/storage/Azure.Storage.Blobs/tests/BlobBaseClientTests.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1999,6 +1999,29 @@ public async Task StartCopyFromUriAsync()
19991999
Assert.IsTrue(operation.HasValue);
20002000
}
20012001

2002+
[RecordedTest]
2003+
[ServiceVersion(Min = BlobClientOptions.ServiceVersion.V2024_08_04)]
2004+
public async Task StartCopyFromUriAsync_SourceErrorAndStatusCode()
2005+
{
2006+
await using DisposingContainer test = await GetTestContainerAsync(publicAccessType: PublicAccessType.None);
2007+
2008+
// Arrange
2009+
BlobBaseClient srcBlob = InstrumentClient(test.Container.GetBlockBlobClient(GetNewBlobName()));
2010+
BlockBlobClient destBlob = InstrumentClient(test.Container.GetBlockBlobClient(GetNewBlobName()));
2011+
2012+
Uri sourceUri = srcBlob.GenerateSasUri(BlobSasPermissions.Read, GetUtcNow().AddDays(1));
2013+
2014+
// Act
2015+
await TestHelper.AssertExpectedExceptionAsync<RequestFailedException>(
2016+
destBlob.StartCopyFromUriAsync(sourceUri),
2017+
e =>
2018+
{
2019+
Assert.IsTrue(e.Message.Contains("CopySourceStatusCode: 404"));
2020+
Assert.IsTrue(e.Message.Contains("CopySourceErrorCode: BlobNotFound"));
2021+
Assert.IsTrue(e.Message.Contains("CopySourceErrorMessage: The specified blob does not exist."));
2022+
});
2023+
}
2024+
20022025
[RecordedTest]
20032026
[TestCase(nameof(BlobRequestConditions.LeaseId))]
20042027
public async Task StartCopyFromUriAsync_InvalidSourceRequestConditions(string invalidSourceCondition)
@@ -3332,6 +3355,27 @@ await destBlob.SyncCopyFromUriAsync(
33323355
}
33333356
}
33343357

3358+
[RecordedTest]
3359+
[ServiceVersion(Min = BlobClientOptions.ServiceVersion.V2024_08_04)]
3360+
public async Task SyncCopyFromUriAsync_SourceErrorAndStatusCode()
3361+
{
3362+
await using DisposingContainer test = await GetTestContainerAsync(publicAccessType: PublicAccessType.None);
3363+
3364+
// Arrange
3365+
BlockBlobClient srcBlob = InstrumentClient(test.Container.GetBlockBlobClient(GetNewBlobName()));
3366+
BlockBlobClient destBlob = InstrumentClient(test.Container.GetBlockBlobClient(GetNewBlobName()));
3367+
3368+
// Act
3369+
await TestHelper.AssertExpectedExceptionAsync<RequestFailedException>(
3370+
destBlob.SyncCopyFromUriAsync(srcBlob.Uri),
3371+
e =>
3372+
{
3373+
Assert.IsTrue(e.Message.Contains("CopySourceStatusCode: 409"));
3374+
Assert.IsTrue(e.Message.Contains("CopySourceErrorCode: PublicAccessNotPermitted"));
3375+
Assert.IsTrue(e.Message.Contains("CopySourceErrorMessage: Public access is not permitted on this storage account."));
3376+
});
3377+
}
3378+
33353379
[RecordedTest]
33363380
public async Task DeleteAsync()
33373381
{

sdk/storage/Azure.Storage.Blobs/tests/BlockBlobClientTests.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,27 @@ await RetryAsync(
614614
_retryStageBlockFromUri);
615615
}
616616

617+
[RecordedTest]
618+
[ServiceVersion(Min = BlobClientOptions.ServiceVersion.V2024_08_04)]
619+
public async Task StageBlobFromUriAsync_SourceErrorAndStatusCode()
620+
{
621+
// Arrange
622+
var constants = TestConstants.Create(this);
623+
await using DisposingContainer test = await GetTestContainerAsync(publicAccessType: PublicAccessType.None);
624+
BlockBlobClient sourceBlob = InstrumentClient(test.Container.GetBlockBlobClient(GetNewBlobName()));
625+
BlockBlobClient destBlob = InstrumentClient(test.Container.GetBlockBlobClient(GetNewBlobName()));
626+
627+
// Act
628+
await TestHelper.AssertExpectedExceptionAsync<RequestFailedException>(
629+
destBlob.StageBlockFromUriAsync(sourceBlob.Uri, ToBase64(GetNewBlockName())),
630+
e =>
631+
{
632+
Assert.IsTrue(e.Message.Contains("CopySourceStatusCode: 409"));
633+
Assert.IsTrue(e.Message.Contains("CopySourceErrorCode: PublicAccessNotPermitted"));
634+
Assert.IsTrue(e.Message.Contains("CopySourceErrorMessage: Public access is not permitted on this storage account."));
635+
});
636+
}
637+
617638
[RecordedTest]
618639
[TestCase(nameof(BlobRequestConditions.IfModifiedSince))]
619640
[TestCase(nameof(BlobRequestConditions.IfUnmodifiedSince))]
@@ -2821,6 +2842,27 @@ await TestHelper.AssertExpectedExceptionAsync<RequestFailedException>(
28212842
e => Assert.AreEqual(BlobErrorCode.CannotVerifyCopySource.ToString(), e.ErrorCode));
28222843
}
28232844

2845+
[RecordedTest]
2846+
[ServiceVersion(Min = BlobClientOptions.ServiceVersion.V2024_08_04)]
2847+
public async Task SyncUploadFromUriAsync_SourceErrorAndStatusCode()
2848+
{
2849+
// Arrange
2850+
var constants = TestConstants.Create(this);
2851+
await using DisposingContainer test = await GetTestContainerAsync(publicAccessType: PublicAccessType.None);
2852+
BlockBlobClient sourceBlob = InstrumentClient(test.Container.GetBlockBlobClient(GetNewBlobName()));
2853+
BlockBlobClient destBlob = InstrumentClient(test.Container.GetBlockBlobClient(GetNewBlobName()));
2854+
2855+
// Act
2856+
await TestHelper.AssertExpectedExceptionAsync<RequestFailedException>(
2857+
destBlob.SyncUploadFromUriAsync(sourceBlob.Uri),
2858+
e =>
2859+
{
2860+
Assert.IsTrue(e.Message.Contains("CopySourceStatusCode: 409"));
2861+
Assert.IsTrue(e.Message.Contains("CopySourceErrorCode: PublicAccessNotPermitted"));
2862+
Assert.IsTrue(e.Message.Contains("CopySourceErrorMessage: Public access is not permitted on this storage account."));
2863+
});
2864+
}
2865+
28242866
[RecordedTest]
28252867
[ServiceVersion(Min = BlobClientOptions.ServiceVersion.V2020_04_08)]
28262868
public async Task SyncUploadFromUriAsync_OverwriteSourceBlobProperties()

sdk/storage/Azure.Storage.Blobs/tests/PageBlobClientTests.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3370,6 +3370,31 @@ public async Task UploadPagesFromUriAsync_Min()
33703370
}
33713371
}
33723372

3373+
[RecordedTest]
3374+
[ServiceVersion(Min = BlobClientOptions.ServiceVersion.V2024_08_04)]
3375+
public async Task UploadPagesFromUriAsync_SourceErrorAndStatusCode()
3376+
{
3377+
// Arrange
3378+
await using DisposingContainer test = await GetTestContainerAsync(publicAccessType: PublicAccessType.None);
3379+
3380+
PageBlobClient sourceBlob = InstrumentClient(test.Container.GetPageBlobClient(GetNewBlobName()));
3381+
3382+
PageBlobClient destBlob = InstrumentClient(test.Container.GetPageBlobClient(GetNewBlobName()));
3383+
await destBlob.CreateIfNotExistsAsync(Constants.KB);
3384+
3385+
HttpRange range = new HttpRange(0, Constants.KB);
3386+
3387+
// Act
3388+
await TestHelper.AssertExpectedExceptionAsync<RequestFailedException>(
3389+
destBlob.UploadPagesFromUriAsync(sourceBlob.Uri, range, range),
3390+
e =>
3391+
{
3392+
Assert.IsTrue(e.Message.Contains("CopySourceStatusCode: 409"));
3393+
Assert.IsTrue(e.Message.Contains("CopySourceErrorCode: PublicAccessNotPermitted"));
3394+
Assert.IsTrue(e.Message.Contains("CopySourceErrorMessage: Public access is not permitted on this storage account."));
3395+
});
3396+
}
3397+
33733398
[RecordedTest]
33743399
[TestCase(nameof(PageBlobRequestConditions.LeaseId))]
33753400
[TestCase(nameof(PageBlobRequestConditions.IfSequenceNumberLessThanOrEqual))]

sdk/storage/Azure.Storage.Common/src/Shared/Constants.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ internal static class HeaderNames
194194
public const string LeaseId = "x-ms-lease-id";
195195
public const string LastModified = "Last-Modified";
196196
public const string ETag = "ETag";
197+
public const string CopySourceErrorCode = "x-ms-copy-source-error-code";
197198
}
198199

199200
internal static class ErrorCodes

sdk/storage/Azure.Storage.Common/src/Shared/StorageResponseClassifier.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,20 @@ public override bool IsRetriableResponse(HttpMessage message)
4141
return true;
4242
}
4343
}
44+
45+
// Retry select Copy Source Error Codes.
46+
if (message.Response.Status >= 400 &&
47+
message.Response.Headers.TryGetValue(Constants.HeaderNames.CopySourceErrorCode, out string copySourceError))
48+
{
49+
switch (copySourceError)
50+
{
51+
case Constants.ErrorCodes.InternalError:
52+
case Constants.ErrorCodes.OperationTimedOut:
53+
case Constants.ErrorCodes.ServerBusy:
54+
return true;
55+
}
56+
}
57+
4458
return base.IsRetriableResponse(message);
4559
}
4660

sdk/storage/Azure.Storage.Common/tests/StorageResponseClassifierTests.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,30 @@ public void IsRetriableResponse_StorageErrors_SecondaryUri(string errorCode)
7070
Assert.IsTrue(classifier.IsRetriableResponse(message));
7171
}
7272

73+
[Test]
74+
[TestCase(Constants.ErrorCodes.ServerBusy)]
75+
[TestCase(Constants.ErrorCodes.InternalError)]
76+
[TestCase(Constants.ErrorCodes.OperationTimedOut)]
77+
public void IsRetriableResponse_CopySourceErrors(string errorCode)
78+
{
79+
var response = new MockResponse(Constants.HttpStatusCode.NotFound);
80+
response.AddHeader(new HttpHeader(Constants.HeaderNames.CopySourceErrorCode, errorCode));
81+
HttpMessage message = BuildMessage(response);
82+
Assert.IsTrue(classifier.IsRetriableResponse(message));
83+
}
84+
85+
[Test]
86+
[TestCase(Constants.ErrorCodes.ServerBusy)]
87+
[TestCase(Constants.ErrorCodes.InternalError)]
88+
[TestCase(Constants.ErrorCodes.OperationTimedOut)]
89+
public void IsRetriableResponse_CopySourceErrors_SecondaryUri(string errorCode)
90+
{
91+
var response = new MockResponse(Constants.HttpStatusCode.NotFound);
92+
response.AddHeader(new HttpHeader(Constants.HeaderNames.CopySourceErrorCode, errorCode));
93+
HttpMessage message = BuildMessage(response, MockSecondaryUri);
94+
Assert.IsTrue(classifier.IsRetriableResponse(message));
95+
}
96+
7397
[TestCase("ContainerAlreadyExists", "If-Match", false)]
7498
[TestCase("ContainerAlreadyExists","If-None-Match", false)]
7599
[TestCase("ContainerAlreadyExists","If-Unmodified-Since", false)]

sdk/storage/Azure.Storage.Files.Shares/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.Files.Shares",
5-
"Tag": "net/storage/Azure.Storage.Files.Shares_3a253f1460"
5+
"Tag": "net/storage/Azure.Storage.Files.Shares_07c3d59f5a"
66
}

0 commit comments

Comments
 (0)