Skip to content

Commit e8ac77d

Browse files
Test checkpointer on queue fail (#46936)
1 parent 5060ff5 commit e8ac77d

File tree

2 files changed

+73
-36
lines changed

2 files changed

+73
-36
lines changed

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

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -369,24 +369,38 @@ public virtual async Task<DataTransfer> StartTransferAsync(
369369
transferOptions ??= new DataTransferOptions();
370370

371371
string transferId = _generateTransferId();
372-
await _checkpointer.AddNewJobAsync(
373-
transferId,
374-
sourceResource,
375-
destinationResource,
376-
cancellationToken).ConfigureAwait(false);
377-
378-
// TODO: if the below fails for any reason, this job will still be in the checkpointer.
379-
// That seems not desirable.
380-
381-
DataTransfer dataTransfer = await BuildAndAddTransferJobAsync(
382-
sourceResource,
383-
destinationResource,
384-
transferOptions,
385-
transferId,
386-
false,
387-
cancellationToken).ConfigureAwait(false);
388-
389-
return dataTransfer;
372+
try
373+
{
374+
await _checkpointer.AddNewJobAsync(
375+
transferId,
376+
sourceResource,
377+
destinationResource,
378+
cancellationToken).ConfigureAwait(false);
379+
380+
DataTransfer dataTransfer = await BuildAndAddTransferJobAsync(
381+
sourceResource,
382+
destinationResource,
383+
transferOptions,
384+
transferId,
385+
false,
386+
cancellationToken).ConfigureAwait(false);
387+
388+
return dataTransfer;
389+
}
390+
catch (Exception ex)
391+
{
392+
// cleanup any state for a job that didn't even start
393+
try
394+
{
395+
_dataTransfers.Remove(transferId);
396+
await _checkpointer.TryRemoveStoredTransferAsync(transferId, cancellationToken).ConfigureAwait(false);
397+
}
398+
catch (Exception cleanupEx)
399+
{
400+
throw new AggregateException(ex, cleanupEx);
401+
}
402+
throw;
403+
}
390404
}
391405

392406
private async Task<DataTransfer> BuildAndAddTransferJobAsync(

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

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using Azure.Storage.DataMovement.Tests.Shared;
1616
using Moq;
1717
using NUnit.Framework;
18+
using NUnit.Framework.Constraints;
1819

1920
namespace Azure.Storage.DataMovement.Tests;
2021

@@ -286,6 +287,7 @@ public async Task BasicContainerTransfer(
286287
[Combinatorial]
287288
public async Task TransferFailAtQueue(
288289
[Values(0, 1)] int failAt,
290+
[Values(true, false)] bool throwCleanup,
289291
[Values(true, false)] bool isContainer)
290292
{
291293
Uri srcUri = new("file:///foo/bar");
@@ -302,19 +304,34 @@ public async Task TransferFailAtQueue(
302304
= GetBasicSetupResources(isContainer, srcUri, dstUri);
303305

304306
Exception expectedException = new();
305-
switch (failAt)
307+
Exception cleanupException = throwCleanup ? new() : null;
308+
List<string> capturedTransferIds = new();
306309
{
307-
case 0:
308-
jobBuilder.Setup(b => b.BuildJobAsync(It.IsAny<StorageResource>(), It.IsAny<StorageResource>(),
309-
It.IsAny<DataTransferOptions>(), It.IsAny<ITransferCheckpointer>(), It.IsAny<string>(),
310-
It.IsAny<bool>(), It.IsAny<CancellationToken>())
311-
).Throws(expectedException);
312-
break;
313-
case 1:
314-
checkpointer.Setup(c => c.AddNewJobAsync(It.IsAny<string>(), It.IsAny<StorageResource>(),
315-
It.IsAny<StorageResource>(), It.IsAny<CancellationToken>())
316-
).Throws(expectedException);
317-
break;
310+
var checkpointerAddJob = checkpointer.Setup(c => c.AddNewJobAsync(Capture.In(capturedTransferIds),
311+
It.IsAny<StorageResource>(), It.IsAny<StorageResource>(), It.IsAny<CancellationToken>()));
312+
var checkpointerRemoveJob = checkpointer.Setup(c => c.TryRemoveStoredTransferAsync(
313+
It.IsAny<string>(), It.IsAny<CancellationToken>()));
314+
315+
switch (failAt)
316+
{
317+
case 0:
318+
jobBuilder.Setup(b => b.BuildJobAsync(It.IsAny<StorageResource>(), It.IsAny<StorageResource>(),
319+
It.IsAny<DataTransferOptions>(), It.IsAny<ITransferCheckpointer>(), It.IsAny<string>(),
320+
It.IsAny<bool>(), It.IsAny<CancellationToken>())
321+
).Throws(expectedException);
322+
break;
323+
case 1:
324+
checkpointerAddJob.Throws(expectedException);
325+
break;
326+
}
327+
if (throwCleanup)
328+
{
329+
checkpointerRemoveJob.Throws(cleanupException);
330+
}
331+
else
332+
{
333+
checkpointerRemoveJob.Returns(Task.FromResult(true));
334+
}
318335
}
319336

320337
await using TransferManager transferManager = new(
@@ -326,15 +343,21 @@ public async Task TransferFailAtQueue(
326343
default);
327344

328345
DataTransfer transfer = null;
329-
330-
Assert.That(async () => transfer = await transferManager.StartTransferAsync(
331-
srcResource,
332-
dstResource), Throws.Exception.EqualTo(expectedException));
346+
IConstraint throwsConstraint = throwCleanup
347+
? Throws.TypeOf<AggregateException>().And.Property(nameof(AggregateException.InnerExceptions))
348+
.EquivalentTo(new List<Exception>() { expectedException, cleanupException })
349+
: Throws.Exception.EqualTo(expectedException);
350+
Assert.That(async () => transfer = await transferManager.StartTransferAsync(srcResource, dstResource),
351+
throwsConstraint);
333352

334353
Assert.That(transfer, Is.Null);
335354

336-
// TODO determine if checkpointer still has the job tracked even though it failed to queue (it shouldn't)
337-
// need checkpointer API refactor for this
355+
Assert.That(capturedTransferIds.Count, Is.EqualTo(1));
356+
checkpointer.Verify(c => c.AddNewJobAsync(capturedTransferIds.First(), It.IsAny<StorageResource>(),
357+
It.IsAny<StorageResource>(), It.IsAny<CancellationToken>()), Times.Once);
358+
checkpointer.Verify(c => c.TryRemoveStoredTransferAsync(capturedTransferIds.First(),
359+
It.IsAny<CancellationToken>()), Times.Once);
360+
checkpointer.VerifyNoOtherCalls();
338361
}
339362

340363
[Test]

0 commit comments

Comments
 (0)