public async Task IfTheSourceDoesNotHaveMoreElementsTheExecutionWillStop() { // Arrange // Set a source that returns 1 element to process var uri = "https://dummy/foo.gz"; var cb = new CloudBlob(new Uri(uri)); var _logSource = new Mock <ILogSource>(); var dummyLockResult = new AzureBlobLockResult(cb, false, "leaseid", CancellationToken.None); _logSource.Setup(lS => lS.GetFilesAsync(It.IsAny <int>(), It.IsAny <CancellationToken>(), It.IsAny <string>())).ReturnsAsync(new Uri[] { new Uri(uri) }); _logSource.Setup(lS => lS.TakeLockAsync(It.IsAny <Uri>(), It.IsAny <CancellationToken>())).ReturnsAsync(dummyLockResult); // set a number larger than 1 int maxElementsToProcess = 2; var _logDestination = new Mock <ILogDestination>(); var sanitizerList = new List <ISanitizer>(); var logger = new Mock <ILogger <Processor> >(); using (var cancellationTokenSource = new CancellationTokenSource()) { var processor = new Processor(_logSource.Object, _logDestination.Object, maxElementsToProcess, sanitizerList, logger.Object); // Act await processor.ProcessAsync(cancellationTokenSource.Token); // Assert _logSource.Verify(logS => logS.GetFilesAsync(It.IsAny <int>(), It.IsAny <CancellationToken>(), It.IsAny <string>()), Times.Once); _logSource.Verify(logS => logS.TakeLockAsync(It.IsAny <Uri>(), It.IsAny <CancellationToken>()), Times.Once); } }
public async Task WhenLockIsNotTakenTheExecutionDoesNotProceed() { // Arrange int maxElementsToProcess = 1; var _logSource = new Mock <ILogSource>(); var dummyLockResult = new AzureBlobLockResult(new CloudBlob(new Uri("https://dummy/foo.gz")), false, "leaseid", CancellationToken.None); _logSource.Setup(lS => lS.GetFilesAsync(It.IsAny <int>(), It.IsAny <CancellationToken>(), It.IsAny <string>())) .ReturnsAsync(new Uri[] { new Uri("https://dummy/foo.gz"), new Uri("https://dummy/foo2.gz") }); _logSource.Setup(lS => lS.TakeLockAsync(It.IsAny <Uri>(), It.IsAny <CancellationToken>())) .ReturnsAsync(dummyLockResult); var _logDestination = new Mock <ILogDestination>(); var sanitizerList = new List <ISanitizer>(); var logger = new Mock <ILogger <Processor> >(); using (var cancellationTokenSource = new CancellationTokenSource()) { var processor = new Processor(_logSource.Object, _logDestination.Object, maxElementsToProcess, sanitizerList, logger.Object); // Act await processor.ProcessAsync(cancellationTokenSource.Token); // Assert // This will be invoked only once because the continuation should not continue after _logSource.Verify(logS => logS.GetFilesAsync(It.IsAny <int>(), It.IsAny <CancellationToken>(), It.IsAny <string>()), Times.Once); _logSource.Verify(logS => logS.TakeLockAsync(It.IsAny <Uri>(), It.IsAny <CancellationToken>()), Times.Exactly(2)); _logSource.Verify(logS => logS.OpenReadAsync(It.IsAny <Uri>(), ContentType.GZip, It.IsAny <CancellationToken>()), Times.Never); } }
public async Task TheProcessOfABlobIsStoppedOnCancellation() { // Arrange // Set a source that returns 1 element to process var _logSource = new Mock <ILogSource>(); var dummyLockResult = new AzureBlobLockResult(new CloudBlob(new Uri("https://dummy/foo.gz")), true, "leaseid", CancellationToken.None); dummyLockResult.BlobOperationToken.Cancel(); _logSource.Setup(lS => lS.GetFilesAsync(It.IsAny <int>(), It.IsAny <CancellationToken>(), It.IsAny <string>())) .ReturnsAsync(new Uri[] { new Uri("https://dummy") }); _logSource.Setup(lS => lS.TakeLockAsync(It.IsAny <Uri>(), It.IsAny <CancellationToken>())) .ReturnsAsync(dummyLockResult); // set a number larger than 1 int maxElementsToProcess = 2; var _logDestination = new Mock <ILogDestination>(); var sanitizerList = new List <ISanitizer>(); var logger = new Mock <ILogger <Processor> >(); using (var cancellationTokenSource = new CancellationTokenSource()) { var processor = new Processor(_logSource.Object, _logDestination.Object, maxElementsToProcess, sanitizerList, logger.Object); // Act await processor.ProcessAsync(cancellationTokenSource.Token); // Assert _logSource.Verify(logS => logS.GetFilesAsync(It.IsAny <int>(), It.IsAny <CancellationToken>(), It.IsAny <string>()), Times.Once); _logSource.Verify(logS => logS.TakeLockAsync(It.IsAny <Uri>(), It.IsAny <CancellationToken>()), Times.Once); _logSource.Verify(logS => logS.OpenReadAsync(It.IsAny <Uri>(), ContentType.GZip, It.IsAny <CancellationToken>()), Times.Never); } }
/// <summary> /// Take the lease on the blob. /// The lease will be renewed at every 60 seconds. In order to stop the renew of the lease: /// 1. Call ReleaseLockAsync /// or /// 2.Cancel the cancellation token /// </summary> /// <param name="blobUri">The blob uri.</param> /// <param name="token">A token to be used for cancellation.</param> /// <returns>True if the lock was taken. False otherwise. And the task that renews the lock overtime.</returns> public async Task <AzureBlobLockResult> TakeLockAsync(Uri blobUri, CancellationToken token) { var blob = await GetBlobAsync(blobUri); if (blob == null) { return(AzureBlobLockResult.FailedLockResult(blob)); } return(_blobLeaseManager.AcquireLease(blob, token)); }
public void VerifyThatTheTokenRespectsExternalCancellation() { // Arrange var cts = new CancellationTokenSource(); var testAzureBlobLockResult = new AzureBlobLockResult(blob: new CloudBlob(new Uri("https://test")), lockIsTaken: false, leaseId: string.Empty, linkToken: cts.Token); // Act cts.Cancel(); // Verify Assert.True(testAzureBlobLockResult.BlobOperationToken.IsCancellationRequested); }
/// <summary> /// Release the lock on the blob. /// </summary> /// <param name="blobLock">The blobLock as was taken at the begining of the operation.</param> /// <param name="token">A token to be used for cancellation. For this implemention the token is ignored.</param> /// <returns>True if the lease was released or the blob does not exist.</returns> public async Task <AsyncOperationResult> TryReleaseLockAsync(AzureBlobLockResult blobLock, CancellationToken token) { if (token.IsCancellationRequested) { _logger.LogInformation("TryReleaseLockAsync: The operation was cancelled. BlobUri {BlobUri}.", blobLock.Blob.Uri); new AsyncOperationResult(false, new OperationCanceledException(token)); } if (await blobLock.Blob.ExistsAsync()) { return(await _blobLeaseManager.TryReleaseLockAsync(blobLock)); } return(new AsyncOperationResult(false, null)); }
public void VerifyThatTheTokenCancellationDoesNotAffectExternalLinkedToken() { // Arrange var cts = new CancellationTokenSource(); var externalToken = cts.Token; var testAzureBlobLockResult = new AzureBlobLockResult(blob: new CloudBlob(new Uri("https://test")), lockIsTaken: false, leaseId: string.Empty, linkToken: externalToken); // Act testAzureBlobLockResult.BlobOperationToken.Cancel(); // Verify Assert.False(externalToken.IsCancellationRequested); }
/// <summary> /// Copy the blob from souurce to the destination /// </summary> /// <param name="sourceBlobInformation">The source blobLock as was taken at the begining of the operation.</param> /// <param name="destinationContainer">The destination Container.</param> /// <returns></returns> private async Task <bool> CopyBlobToContainerAsync(AzureBlobLockResult sourceBlobInformation, CloudBlobContainer destinationContainer, CancellationToken token) { if (token.IsCancellationRequested) { _logger.LogInformation("CopyBlobToContainerAsync: The operation was cancelled."); return(false); } //just get a reference to the future blob var destinationBlob = destinationContainer.GetBlobReference(GetBlobNameFromUri(sourceBlobInformation.Blob.Uri)); try { if (!await destinationBlob.ExistsAsync(token)) { return(await TryCopyInternalAsync(sourceBlobInformation.Blob.Uri, destinationBlob, destinationContainer)); } else { _logger.LogInformation("CopyBlobToContainerAsync: Blob already exists DestinationUri {DestinationUri}.", destinationBlob.Uri); // Overwrite var lease = destinationBlob.AcquireLease(TimeSpan.FromSeconds(CopyBlobLeaseTimeInSeconds), sourceBlobInformation.LeaseId); var destinationAccessCondition = new AccessCondition { LeaseId = lease }; await destinationBlob.DeleteAsync(deleteSnapshotsOption : DeleteSnapshotsOption.IncludeSnapshots, accessCondition : destinationAccessCondition, options : null, operationContext : null); var result = await TryCopyInternalAsync(sourceBlobInformation.Blob.Uri, destinationBlob, destinationContainer, destinationAccessCondition : destinationAccessCondition); try { destinationBlob.ReleaseLease(destinationAccessCondition); } catch (StorageException) { // do not do anything the lease will be released anyway } return(result); } } catch (StorageException exception) { _logger.LogCritical(LogEvents.FailedBlobCopy, exception, "CopyBlobToContainerAsync: Blob Copy Failed. SourceUri: {SourceUri}. DestinationUri {DestinationUri}", sourceBlobInformation.Blob.Uri, destinationBlob.Uri); return(false); } }
/// <summary> /// It will perform the clean up steps. /// In this case it will copy the blob in the archive or dead-letter and delete the blob. /// This method will not throw an exception. /// Use the <see cref="AsyncOperationResult.OperationException"/> for any exception during this execution. /// </summary> /// <param name="blobLock">The <see cref="AzureBlobLockResult"/> for the blob that needs clean-up.</param> /// <param name="onError">Flag to indicate if the cleanup is done because an error or not. </param> /// <param name="token">A token to be used for cancellation.</param> /// <returns>True is the cleanup was successful. If the blob does not exist the return value is false.</returns> public async Task <AsyncOperationResult> TryCleanAsync(AzureBlobLockResult blobLock, bool onError, CancellationToken token) { _logger.LogInformation("CleanAsync:Start cleanup for {Blob}", blobLock.Blob.Uri); if (token.IsCancellationRequested) { _logger.LogInformation("CleanAsync: The operation was cancelled. BlobUri {BlobUri}.", blobLock.Blob.Uri); new AsyncOperationResult(false, new OperationCanceledException(token)); } try { var sourceBlob = await GetBlobAsync(blobLock.Blob.Uri); if (sourceBlob == null) { _logger.LogError("CleanAsync: The blob {Blob} was not found.", blobLock.Blob.Uri); return(new AsyncOperationResult(false, null)); } string archiveTargetContainerName = onError ? _deadletterContainerName : _archiveContainerName; _logger.LogInformation("CleanAsync: Blob {Blob} will be copied to container {Container}", blobLock.Blob.Uri, archiveTargetContainerName); var archiveTargetContainer = await CreateContainerAsync(archiveTargetContainerName); if (await CopyBlobToContainerAsync(blobLock, archiveTargetContainer, token)) { _logger.LogInformation("CleanAsync: Blob {Blob} was copied to container {Container}", blobLock.Blob.Uri, archiveTargetContainerName); var accessCondition = new AccessCondition { LeaseId = blobLock.LeaseId }; // The operation will throw if the lease does not match bool deleteResult = await sourceBlob.DeleteIfExistsAsync(deleteSnapshotsOption : DeleteSnapshotsOption.IncludeSnapshots, accessCondition : accessCondition, options : _blobRequestOptions, operationContext : null, cancellationToken : token); _logger.LogInformation("CleanAsync: Blob {Blob} was deleted {DeletedResult}. The leaseId: {LeaseId}", blobLock.Blob.Uri, deleteResult, blobLock.LeaseId); return(new AsyncOperationResult(deleteResult, null)); } _logger.LogWarning("CleanAsync: Blob {Blob} failed to be copied to container {Container}", blobLock.Blob.Uri, archiveTargetContainerName); return(new AsyncOperationResult(false, null)); } catch (Exception exception) { _logger.LogCritical(LogEvents.FailedBlobDelete, exception, "CleanAsync: Blob {Blob} failed the clean-up. The current leaseId: {LeaseId}", blobLock.Blob.Uri, blobLock.LeaseId); return(new AsyncOperationResult(null, exception)); } }
public async Task VerifyCleanAsyncCallOnErrorIsInvokedWhenWriteAsyncThrows(bool writeAsyncThrows) { // Arrange // Set a source that returns 1 element to process var _logSource = new Mock <ILogSource>(); var dummyLockResult = new AzureBlobLockResult(new CloudBlob(new Uri("https://dummy/foo.gz")), true, "leaseid", CancellationToken.None); _logSource.Setup(lS => lS.GetFilesAsync(It.IsAny <int>(), It.IsAny <CancellationToken>(), It.IsAny <string>())) .ReturnsAsync(new Uri[] { new Uri("https://dummy") }); _logSource.Setup(lS => lS.TakeLockAsync(It.IsAny <Uri>(), It.IsAny <CancellationToken>())) .ReturnsAsync(dummyLockResult); _logSource.Setup(lS => lS.OpenReadAsync(It.IsAny <Uri>(), ContentType.GZip, It.IsAny <CancellationToken>())) .ReturnsAsync(new MemoryStream()); // set a number larger than 1 int maxElementsToProcess = 2; var _logDestination = new Mock <ILogDestination>(); var writeResult = writeAsyncThrows ? new AsyncOperationResult(null, new Exception("Boo")) : new AsyncOperationResult(true, null); _logDestination.Setup(lD => lD.TryWriteAsync(It.IsAny <Stream>(), It.IsAny <Action <Stream, Stream> >(), It.IsAny <string>(), ContentType.GZip, It.IsAny <CancellationToken>())) .ReturnsAsync(writeResult); var sanitizerList = new List <ISanitizer>(); var logger = new Mock <ILogger <Processor> >(); using (var cancellationTokenSource = new CancellationTokenSource()) { var processor = new Processor(_logSource.Object, _logDestination.Object, maxElementsToProcess, sanitizerList, logger.Object); // Act await processor.ProcessAsync(cancellationTokenSource.Token); // Assert _logSource.Verify(logS => logS.GetFilesAsync(It.IsAny <int>(), It.IsAny <CancellationToken>(), It.IsAny <string>()), Times.Once); _logSource.Verify(logS => logS.TakeLockAsync(It.IsAny <Uri>(), It.IsAny <CancellationToken>()), Times.Once); _logSource.Verify(logS => logS.TryCleanAsync(dummyLockResult, writeAsyncThrows, It.IsAny <CancellationToken>()), Times.Once); _logSource.Verify(logS => logS.TryReleaseLockAsync(dummyLockResult, It.IsAny <CancellationToken>()), Times.Once); } }