Skip to content

Commit ed17988

Browse files
[Storage][DataMovement] Fix transfers using root BlobStorageResourceContainer (Azure#36023)
1 parent 656d6b3 commit ed17988

8 files changed

+149
-18
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,9 @@ public BlobStorageResourceContainer(BlobContainerClient blobContainerClient, Blo
4343

4444
/// <summary>
4545
/// Gets the path of the storage resource.
46+
/// Return empty string since we are using the root of the container.
4647
/// </summary>
47-
public override string Path => _blobContainerClient.Name;
48+
public override string Path => string.Empty;
4849

4950
/// <summary>
5051
/// Gets the URL of the storage resource.

sdk/storage/Azure.Storage.DataMovement/src/ServiceToServiceTransferJob.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,10 @@ private async IAsyncEnumerable<JobPartInternal> GetStorageResourcesAsync()
173173
StorageResourceBase current = enumerator.Current;
174174
if (lastResource != default)
175175
{
176-
string sourceName = lastResource.Path.Substring(_sourceResourceContainer.Path.Length + 1);
176+
string sourceName = string.IsNullOrEmpty(_sourceResourceContainer.Path)
177+
? lastResource.Path
178+
: lastResource.Path.Substring(_sourceResourceContainer.Path.Length + 1);
179+
177180
if (!existingSources.Contains(sourceName))
178181
{
179182
// Because AsyncEnumerable doesn't let us know which storage resource is the last resource
@@ -210,7 +213,10 @@ private async IAsyncEnumerable<JobPartInternal> GetStorageResourcesAsync()
210213
{
211214
// Return last part but enable the part to be the last job part of the entire job
212215
// so we know that we've finished listing in the container
213-
string lastSourceName = lastResource.Path.Substring(_sourceResourceContainer.Path.Length + 1);
216+
string lastSourceName = string.IsNullOrEmpty(_sourceResourceContainer.Path)
217+
? lastResource.Path
218+
: lastResource.Path.Substring(_sourceResourceContainer.Path.Length + 1);
219+
214220
lastPart = await ServiceToServiceJobPart.CreateJobPartAsync(
215221
job: this,
216222
partNumber: partNumber,

sdk/storage/Azure.Storage.DataMovement/src/StreamToUriTransferJob.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,10 @@ private async IAsyncEnumerable<JobPartInternal> GetStorageResourcesAsync()
171171
StorageResourceBase current = enumerator.Current;
172172
if (lastResource != default)
173173
{
174-
string sourceName = lastResource.Path.Substring(_sourceResourceContainer.Path.Length + 1);
174+
string sourceName = string.IsNullOrEmpty(_sourceResourceContainer.Path)
175+
? lastResource.Path
176+
: lastResource.Path.Substring(_sourceResourceContainer.Path.Length + 1);
177+
175178
if (!existingSources.Contains(sourceName))
176179
{
177180
// Because AsyncEnumerable doesn't let us know which storage resource is the last resource
@@ -208,7 +211,10 @@ private async IAsyncEnumerable<JobPartInternal> GetStorageResourcesAsync()
208211
{
209212
// Return last part but enable the part to be the last job part of the entire job
210213
// so we know that we've finished listing in the container
211-
string lastSourceName = lastResource.Path.Substring(_sourceResourceContainer.Path.Length + 1);
214+
string lastSourceName = string.IsNullOrEmpty(_sourceResourceContainer.Path)
215+
? lastResource.Path
216+
: lastResource.Path.Substring(_sourceResourceContainer.Path.Length + 1);
217+
212218
lastPart = await StreamToUriJobPart.CreateJobPartAsync(
213219
job: this,
214220
partNumber: partNumber,

sdk/storage/Azure.Storage.DataMovement/src/UriToStreamTransferJob.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,10 @@ private async IAsyncEnumerable<JobPartInternal> GetStorageResourcesAsync()
171171
StorageResourceBase current = enumerator.Current;
172172
if (lastResource != default)
173173
{
174-
string sourceName = lastResource.Path.Substring(_sourceResourceContainer.Path.Length + 1);
174+
string sourceName = string.IsNullOrEmpty(_sourceResourceContainer.Path)
175+
? lastResource.Path
176+
: lastResource.Path.Substring(_sourceResourceContainer.Path.Length + 1);
177+
175178
if (!existingSources.Contains(sourceName))
176179
{
177180
// Because AsyncEnumerable doesn't let us know which storage resource is the last resource
@@ -208,7 +211,10 @@ private async IAsyncEnumerable<JobPartInternal> GetStorageResourcesAsync()
208211
{
209212
// Return last part but enable the part to be the last job part of the entire job
210213
// so we know that we've finished listing in the container
211-
string lastSourceName = lastResource.Path.Substring(_sourceResourceContainer.Path.Length + 1);
214+
string lastSourceName = string.IsNullOrEmpty(_sourceResourceContainer.Path)
215+
? lastResource.Path
216+
: lastResource.Path.Substring(_sourceResourceContainer.Path.Length + 1);
217+
212218
lastPart = await UriToStreamJobPart.CreateJobPartAsync(
213219
job: this,
214220
partNumber: partNumber,

sdk/storage/Azure.Storage.DataMovement/tests/StartTransferAsyncCopyDirectoryTests.cs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public StartTransferAsyncCopyDirectoryTests(bool async, BlobClientOptions.Servic
2626
/// <summary>
2727
/// Upload and verify the contents of the blob
2828
///
29-
/// By default in this function an event arguement will be added to the options event handler
29+
/// By default in this function an event argument will be added to the options event handler
3030
/// to detect when the upload has finished.
3131
/// </summary>
3232
/// <param name="container">The source container which will contains the source blobs</param>
@@ -441,6 +441,48 @@ await CopyBlobDirectoryAndVerify(
441441
options: options).ConfigureAwait(false);
442442
}
443443

444+
[Test]
445+
[LiveOnly] // https://github.com/Azure/azure-sdk-for-net/issues/33082
446+
public async Task BlockBlobDirectoryToDirectory_Root()
447+
{
448+
// Arrange
449+
string[] files = { "file1", "dir1/file1", "dir1/file2", "dir1/file3", "dir2/file1" };
450+
BinaryData data = BinaryData.FromString("Hello World");
451+
452+
await using DisposingBlobContainer source = await GetTestContainerAsync();
453+
await using DisposingBlobContainer destination = await GetTestContainerAsync();
454+
455+
foreach (string file in files)
456+
{
457+
await source.Container.UploadBlobAsync(file, data);
458+
}
459+
460+
TransferManager transferManager = new TransferManager();
461+
462+
StorageResourceContainer sourceResource =
463+
new BlobStorageResourceContainer(source.Container);
464+
StorageResourceContainer destinationResource = new BlobStorageResourceContainer(
465+
destination.Container,
466+
new BlobStorageResourceContainerOptions()
467+
{
468+
CopyMethod = TransferCopyMethod.AsyncCopy,
469+
});
470+
471+
// Act
472+
DataTransfer transfer = await transferManager.StartTransferAsync(sourceResource, destinationResource);
473+
474+
CancellationTokenSource tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(10));
475+
await transfer.AwaitCompletion(tokenSource.Token);
476+
477+
// Assert
478+
Assert.AreEqual(StorageTransferStatus.Completed, transfer.TransferStatus);
479+
480+
IEnumerable<string> destinationFiles =
481+
(await destination.Container.GetBlobsAsync().ToEnumerableAsync()).Select(b => b.Name);
482+
483+
Assert.IsTrue(destinationFiles.OrderBy(f => f).SequenceEqual(files.OrderBy(f => f)));
484+
}
485+
444486
#region Single Concurrency
445487
private async Task CreateBlobDirectoryTree(
446488
BlobContainerClient client,

sdk/storage/Azure.Storage.DataMovement/tests/StartTransferDownloadDirectoryTests.cs

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,16 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.IO;
67
using System.Linq;
78
using System.Threading;
89
using System.Threading.Tasks;
910
using Azure.Core.TestFramework;
1011
using Azure.Storage.Blobs;
11-
using Azure.Storage.DataMovement.Models;
12+
using Azure.Storage.Blobs.Models;
1213
using Azure.Storage.DataMovement.Blobs;
14+
using Azure.Storage.DataMovement.Models;
1315
using NUnit.Framework;
14-
using System.IO;
15-
using Azure.Storage.Blobs.Models;
16-
using Microsoft.Extensions.Options;
17-
using Microsoft.CodeAnalysis;
1816

1917
namespace Azure.Storage.DataMovement.Tests
2018
{
@@ -28,7 +26,7 @@ public StartTransferDownloadDirectoryTests(bool async, BlobClientOptions.Service
2826
/// <summary>
2927
/// Upload and verify the contents of the blob
3028
///
31-
/// By default in this function an event arguement will be added to the options event handler
29+
/// By default in this function an event argument will be added to the options event handler
3230
/// to detect when the upload has finished.
3331
/// </summary>
3432
/// <param name="sourceContainer">The source container which will contains the source blobs</param>
@@ -330,6 +328,40 @@ await DownloadBlobDirectoryAndVerify(
330328
blobNames,
331329
destinationFolder).ConfigureAwait(false);
332330
}
331+
332+
[Test]
333+
[LiveOnly] // https://github.com/Azure/azure-sdk-for-net/issues/33082
334+
public async Task DownloadDirectoryAsync_Root()
335+
{
336+
// Arrange
337+
string[] files = { "file1", "dir1/file1", "dir1/file2", "dir1/file3", "dir2/file1" };
338+
BinaryData data = BinaryData.FromString("Hello World");
339+
340+
await using DisposingBlobContainer source = await GetTestContainerAsync();
341+
using DisposingLocalDirectory destination = DisposingLocalDirectory.GetTestDirectory();
342+
343+
foreach (string file in files)
344+
{
345+
await source.Container.UploadBlobAsync(file, data);
346+
}
347+
348+
TransferManager transferManager = new TransferManager();
349+
350+
StorageResourceContainer sourceResource =
351+
new BlobStorageResourceContainer(source.Container);
352+
StorageResourceContainer destinationResource =
353+
new LocalDirectoryStorageResourceContainer(destination.DirectoryPath);
354+
355+
DataTransfer transfer = await transferManager.StartTransferAsync(sourceResource, destinationResource);
356+
357+
CancellationTokenSource tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(10));
358+
await transfer.AwaitCompletion(tokenSource.Token);
359+
360+
IEnumerable<string> destinationFiles = ListFilesInDirectory(destination.DirectoryPath)
361+
.Select(f => f.Substring(destination.DirectoryPath.Length + 1).Replace("\\", "/"));
362+
363+
Assert.IsTrue(destinationFiles.OrderBy(f => f).SequenceEqual(files.OrderBy(f => f)));
364+
}
333365
#endregion DirectoryDownloadTests
334366

335367
#region Single Concurrency

sdk/storage/Azure.Storage.DataMovement/tests/StartTransferSyncCopyDirectoryTests.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@
33

44
using System;
55
using System.Collections.Generic;
6-
using System.Drawing;
76
using System.IO;
87
using System.Linq;
9-
using System.Text;
108
using System.Threading;
119
using System.Threading.Tasks;
1210
using Azure.Core.TestFramework;

sdk/storage/Azure.Storage.DataMovement/tests/StartTransferUploadDirectoryTests.cs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
3+
34
using System;
45
using System.Collections.Generic;
56
using System.IO;
@@ -13,9 +14,7 @@
1314
using Azure.Storage.DataMovement.Blobs;
1415
using Azure.Storage.DataMovement.Models;
1516
using Microsoft.CodeAnalysis;
16-
using Microsoft.Extensions.Options;
1717
using NUnit.Framework;
18-
using NUnit.Framework.Internal;
1918

2019
namespace Azure.Storage.DataMovement.Tests
2120
{
@@ -363,6 +362,47 @@ await UploadBlobDirectoryAndVerify(
363362
destinationPrefix: dirName,
364363
waitTimeInSec: 10);
365364
}
365+
366+
[Test]
367+
[LiveOnly] // https://github.com/Azure/azure-sdk-for-net/issues/33082
368+
public async Task DirectoryUpload_Root()
369+
{
370+
// Arrange
371+
using DisposingLocalDirectory source = DisposingLocalDirectory.GetTestDirectory();
372+
await using DisposingBlobContainer destination = await GetTestContainerAsync();
373+
374+
string file1 = await CreateRandomFileAsync(source.DirectoryPath, size:10);
375+
376+
string dir1 = CreateRandomDirectory(source.DirectoryPath);
377+
string file2 = await CreateRandomFileAsync(dir1, size: 10);
378+
string file3 = await CreateRandomFileAsync(dir1, size: 10);
379+
string file4 = await CreateRandomFileAsync(dir1, size: 10);
380+
381+
string dir2 = CreateRandomDirectory(source.DirectoryPath);
382+
string file5 = await CreateRandomFileAsync(dir2, size: 10);
383+
384+
string[] files = {file1, file2, file3, file4, file5};
385+
386+
TransferManager transferManager = new TransferManager();
387+
388+
StorageResourceContainer sourceResource =
389+
new LocalDirectoryStorageResourceContainer(source.DirectoryPath);
390+
StorageResourceContainer destinationResource =
391+
new BlobStorageResourceContainer(destination.Container);
392+
393+
DataTransfer transfer = await transferManager.StartTransferAsync(sourceResource, destinationResource);
394+
395+
CancellationTokenSource tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(10));
396+
await transfer.AwaitCompletion(tokenSource.Token);
397+
398+
IEnumerable<string> destinationFiles =
399+
(await destination.Container.GetBlobsAsync().ToEnumerableAsync()).Select(b => b.Name);
400+
401+
Assert.IsTrue(files
402+
.Select(f => f.Substring(source.DirectoryPath.Length + 1).Replace("\\", "/"))
403+
.OrderBy(f => f)
404+
.SequenceEqual(destinationFiles.OrderBy(f => f)));
405+
}
366406
#endregion
367407

368408
#region DirectoryUploadTests

0 commit comments

Comments
 (0)