예제 #1
0
        public async Task SaveFileAsync(string folderName, string fileName, Stream file, IAccessCondition accessConditions)
        {
            ICloudBlobContainer container = await GetContainerAsync(folderName);

            var blob = container.GetBlobReference(fileName);

            accessConditions = accessConditions ?? AccessConditionWrapper.GenerateIfNotExistsCondition();

            var mappedAccessCondition = new AccessCondition
            {
                IfNoneMatchETag = accessConditions.IfNoneMatchETag,
                IfMatchETag     = accessConditions.IfMatchETag,
            };

            try
            {
                await blob.UploadFromStreamAsync(file, mappedAccessCondition);
            }
            catch (StorageException ex) when(ex.IsFileAlreadyExistsException())
            {
                throw new FileAlreadyExistsException(
                          String.Format(
                              CultureInfo.CurrentCulture,
                              "There is already a blob with name {0} in container {1}.",
                              fileName,
                              folderName),
                          ex);
            }

            blob.Properties.ContentType = GetContentType(folderName);
            await blob.SetPropertiesAsync();
        }
예제 #2
0
            public async Task UsesProvidedMatchETag()
            {
                // Arrange
                AccessCondition  srcAccessCondition  = null;
                AccessCondition  destAccessCondition = null;
                ISimpleCloudBlob srcBlob             = null;

                _destBlobMock
                .Setup(x => x.StartCopyAsync(It.IsAny <ISimpleCloudBlob>(), It.IsAny <AccessCondition>(), It.IsAny <AccessCondition>()))
                .Returns(Task.FromResult(0))
                .Callback <ISimpleCloudBlob, AccessCondition, AccessCondition>((b, s, d) =>
                {
                    srcBlob             = b;
                    srcAccessCondition  = s;
                    destAccessCondition = d;
                    SetDestCopyStatus(CopyStatus.Success);
                });

                // Act
                await _target.CopyFileAsync(
                    _srcFolderName,
                    _srcFileName,
                    _destFolderName,
                    _destFileName,
                    AccessConditionWrapper.GenerateIfMatchCondition("etag!"));

                // Assert
                _destBlobMock.Verify(
                    x => x.StartCopyAsync(It.IsAny <ISimpleCloudBlob>(), It.IsAny <AccessCondition>(), It.IsAny <AccessCondition>()),
                    Times.Once);
                Assert.Equal("etag!", destAccessCondition.IfMatchETag);
                Assert.Null(destAccessCondition.IfNoneMatchETag);
            }
예제 #3
0
            public async Task NoOpsIfPackageLengthAndHashMatch()
            {
                // Arrange
                SetBlobContentMD5(_srcProperties, "mwgwUC0MwohHxgMmvQzO7A==");
                SetBlobLength(_srcProperties, 42);
                SetBlobContentMD5(_destProperties, _srcProperties.ContentMD5);
                SetBlobLength(_destProperties, _srcProperties.Length);

                _destBlobMock
                .Setup(x => x.ExistsAsync())
                .ReturnsAsync(true);

                // Act
                await _target.CopyFileAsync(
                    _srcFolderName,
                    _srcFileName,
                    _destFolderName,
                    _destFileName,
                    AccessConditionWrapper.GenerateIfNotExistsCondition());

                // Assert
                _destBlobMock.Verify(
                    x => x.StartCopyAsync(It.IsAny <ISimpleCloudBlob>(), It.IsAny <AccessCondition>(), It.IsAny <AccessCondition>()),
                    Times.Never);
            }
예제 #4
0
            public async Task WillCopyTheFileIfDestinationDoesNotExist()
            {
                // Arrange
                AccessCondition  srcAccessCondition  = null;
                AccessCondition  destAccessCondition = null;
                ISimpleCloudBlob srcBlob             = null;

                _destBlobMock
                .Setup(x => x.StartCopyAsync(It.IsAny <ISimpleCloudBlob>(), It.IsAny <AccessCondition>(), It.IsAny <AccessCondition>()))
                .Returns(Task.FromResult(0))
                .Callback <ISimpleCloudBlob, AccessCondition, AccessCondition>((b, s, d) =>
                {
                    srcBlob             = b;
                    srcAccessCondition  = s;
                    destAccessCondition = d;
                });

                // Act
                await _target.CopyFileAsync(
                    _srcFolderName,
                    _srcFileName,
                    _destFolderName,
                    _destFileName,
                    AccessConditionWrapper.GenerateIfNotExistsCondition());

                // Assert
                _destBlobMock.Verify(
                    x => x.StartCopyAsync(It.IsAny <ISimpleCloudBlob>(), It.IsAny <AccessCondition>(), It.IsAny <AccessCondition>()),
                    Times.Once);
                Assert.Equal(_srcFileName, srcBlob.Name);
                Assert.Equal(_srcETag, srcAccessCondition.IfMatchETag);
                Assert.Equal("*", destAccessCondition.IfNoneMatchETag);
            }
예제 #5
0
            public async Task WillCopyBlobFromSourceUri()
            {
                // Arrange
                _blobClient
                .Setup(x => x.GetBlobFromUri(It.IsAny <Uri>()))
                .Returns(_srcBlobMock.Object);

                _destBlobMock
                .Setup(x => x.StartCopyAsync(It.IsAny <ISimpleCloudBlob>(), It.IsAny <AccessCondition>(), It.IsAny <AccessCondition>()))
                .Returns(Task.FromResult(0))
                .Callback <ISimpleCloudBlob, AccessCondition, AccessCondition>((_, __, ___) =>
                {
                    SetDestCopyStatus(CopyStatus.Success);
                });

                // Act
                await _target.CopyFileAsync(
                    _srcUri,
                    _destFolderName,
                    _destFileName,
                    AccessConditionWrapper.GenerateIfNotExistsCondition());

                // Assert
                _destBlobMock.Verify(
                    x => x.StartCopyAsync(_srcBlobMock.Object, It.IsAny <AccessCondition>(), It.IsAny <AccessCondition>()),
                    Times.Once);
                _destBlobMock.Verify(
                    x => x.StartCopyAsync(It.IsAny <ISimpleCloudBlob>(), It.IsAny <AccessCondition>(), It.IsAny <AccessCondition>()),
                    Times.Once);
                _blobClient.Verify(
                    x => x.GetBlobFromUri(_srcUri),
                    Times.Once);
            }
예제 #6
0
        public async Task CopiesWhenDestinationHasNotHashButContentsAreTheSame()
        {
            // Arrange
            var srcFolderName = CoreConstants.Folders.ValidationFolderName;
            var srcFileName   = $"{_prefixA}/src";
            var srcContent    = "Hello, world.";
            var srcSha512     = "AD0C37C31D69B315F3A81F13C8CDE701094AD91725BA1B0DC3199CA9713661B8280" +
                                "D6EF7E68F133E6211E2E5A9A3150445D76F1708E04521B0EE034F0B0BAF26";

            var destFolderName = CoreConstants.Folders.PackagesFolderName;
            var destFileName   = $"{_prefixB}/dest";

            await _targetA.SaveFileAsync(
                srcFolderName,
                srcFileName,
                new MemoryStream(Encoding.ASCII.GetBytes(srcContent)),
                overwrite : false);

            await _targetA.SetMetadataAsync(srcFolderName, srcFileName, (lazyStream, metadata) =>
            {
                metadata[CoreConstants.Sha512HashAlgorithmId] = srcSha512;
                return(Task.FromResult(true));
            });

            await _targetB.SaveFileAsync(
                destFolderName,
                destFileName,
                new MemoryStream(Encoding.ASCII.GetBytes(srcContent)),
                overwrite : false);

            var originalDestFileReference = await _targetB.GetFileReferenceAsync(destFolderName, destFileName);

            var originalDestETag = originalDestFileReference.ContentId;

            var srcUri = await _targetA.GetFileReadUriAsync(
                srcFolderName,
                srcFileName,
                DateTimeOffset.UtcNow.AddHours(1));

            var destAccessCondition = AccessConditionWrapper.GenerateEmptyCondition();

            // Act
            await _targetB.CopyFileAsync(
                srcUri,
                destFolderName,
                destFileName,
                destAccessCondition);

            // Assert
            var finalDestFileReference = await _targetB.GetFileReferenceAsync(destFolderName, destFileName);

            var finalDestETag = finalDestFileReference.ContentId;

            Assert.NotEqual(originalDestETag, finalDestETag);
        }
        public async Task CopiesWhenDestinationHasNotHashButContentsAreTheSame()
        {
            // Arrange
            var srcFolderName = CoreConstants.Folders.ValidationFolderName;
            var srcFileName   = $"{_prefixA}/src";
            var srcContent    = "Hello, world.";

            var destFolderName = CoreConstants.Folders.PackagesFolderName;
            var destFileName   = $"{_prefixB}/dest";

            await _targetA.SaveFileAsync(
                srcFolderName,
                srcFileName,
                new MemoryStream(Encoding.ASCII.GetBytes(srcContent)),
                overwrite : false);

            await _targetB.SaveFileAsync(
                destFolderName,
                destFileName,
                new MemoryStream(Encoding.ASCII.GetBytes(srcContent)),
                overwrite : false);

            var originalDestFileReference = await _targetB.GetFileReferenceAsync(destFolderName, destFileName);

            var originalDestETag = originalDestFileReference.ContentId;

            await ClearContentMD5(_blobClientB, destFolderName, destFileName);

            var srcUri = await _targetA.GetFileReadUriAsync(
                srcFolderName,
                srcFileName,
                DateTimeOffset.UtcNow.AddHours(1));

            var destAccessCondition = AccessConditionWrapper.GenerateEmptyCondition();

            // Act
            await _targetB.CopyFileAsync(
                srcUri,
                destFolderName,
                destFileName,
                destAccessCondition);

            // Assert
            var finalDestFileReference = await _targetB.GetFileReferenceAsync(destFolderName, destFileName);

            var finalDestETag = finalDestFileReference.ContentId;

            Assert.NotEqual(originalDestETag, finalDestETag);
        }
        private static async Task CopyFileWithNamesAsync(
            CloudBlobCoreFileStorageService srcService,
            string srcFolderName,
            string srcFileName,
            CloudBlobCoreFileStorageService destService,
            string destFolderName,
            string destFileName)
        {
            var destAccessCondition = AccessConditionWrapper.GenerateIfNotExistsCondition();

            await destService.CopyFileAsync(
                srcFolderName,
                srcFileName,
                destFolderName,
                destFileName,
                destAccessCondition);
        }
예제 #9
0
            public async Task WillCopyTheFileIfDestinationHasFailedCopy()
            {
                // Arrange
                AccessCondition  srcAccessCondition  = null;
                AccessCondition  destAccessCondition = null;
                ISimpleCloudBlob srcBlob             = null;

                SetDestCopyStatus(CopyStatus.Failed);

                _destBlobMock
                .Setup(x => x.StartCopyAsync(It.IsAny <ISimpleCloudBlob>(), It.IsAny <AccessCondition>(), It.IsAny <AccessCondition>()))
                .Returns(Task.FromResult(0))
                .Callback <ISimpleCloudBlob, AccessCondition, AccessCondition>((b, s, d) =>
                {
                    srcBlob             = b;
                    srcAccessCondition  = s;
                    destAccessCondition = d;
                    SetDestCopyStatus(CopyStatus.Pending);
                });

                _destBlobMock
                .Setup(x => x.ExistsAsync())
                .ReturnsAsync(true);

                _destBlobMock
                .Setup(x => x.FetchAttributesAsync())
                .Returns(Task.FromResult(0))
                .Callback(() => SetDestCopyStatus(CopyStatus.Success));

                // Act
                var srcETag = await _target.CopyFileAsync(
                    _srcFolderName,
                    _srcFileName,
                    _destFolderName,
                    _destFileName,
                    AccessConditionWrapper.GenerateIfNotExistsCondition());

                // Assert
                _destBlobMock.Verify(
                    x => x.StartCopyAsync(It.IsAny <ISimpleCloudBlob>(), It.IsAny <AccessCondition>(), It.IsAny <AccessCondition>()),
                    Times.Once);
                Assert.Equal(_srcETag, srcETag);
                Assert.Equal(_srcFileName, srcBlob.Name);
                Assert.Equal(_srcETag, srcAccessCondition.IfMatchETag);
                Assert.Equal(_destETag, destAccessCondition.IfMatchETag);
            }
예제 #10
0
            public async Task WillThrowInvalidOperationExceptionForConflict()
            {
                // Arrange
                _destBlobMock
                .Setup(x => x.StartCopyAsync(It.IsAny <ISimpleCloudBlob>(), It.IsAny <AccessCondition>(), It.IsAny <AccessCondition>()))
                .Throws(new StorageException(new RequestResult {
                    HttpStatusCode = (int)HttpStatusCode.Conflict
                }, "Conflict!", inner: null));

                // Act & Assert
                await Assert.ThrowsAsync <InvalidOperationException>(
                    () => _target.CopyFileAsync(
                        _srcFolderName,
                        _srcFileName,
                        _destFolderName,
                        _destFileName,
                        AccessConditionWrapper.GenerateIfNotExistsCondition()));
            }
        private static async Task CopyFileWithUriAsync(
            CloudBlobCoreFileStorageService srcService,
            string srcFolderName,
            string srcFileName,
            CloudBlobCoreFileStorageService destService,
            string destFolderName,
            string destFileName)
        {
            var endOfAccess = DateTimeOffset.UtcNow.AddHours(1);
            var srcUri      = await srcService.GetFileReadUriAsync(srcFolderName, srcFileName, endOfAccess);

            var destAccessCondition = AccessConditionWrapper.GenerateIfNotExistsCondition();

            await destService.CopyFileAsync(
                srcUri,
                destFolderName,
                destFileName,
                destAccessCondition);
        }
        public async Task <RevalidationState> MaybeUpdateStateAsync(Func <RevalidationState, bool> updateAction)
        {
            var internalState = await GetInternalStateAsync();

            var fileReference = internalState.FileReference;
            var state         = internalState.State;

            // Only update the state if the update action returns true.
            var originalState = new RevalidationState
            {
                IsInitialized           = state.IsInitialized,
                IsKillswitchActive      = state.IsKillswitchActive,
                DesiredPackageEventRate = state.DesiredPackageEventRate,
            };

            if (!updateAction(state))
            {
                return(originalState);
            }

            try
            {
                var accessCondition = AccessConditionWrapper.GenerateIfMatchCondition(fileReference.ContentId);

                using (var stream = new MemoryStream())
                    using (var writer = new StreamWriter(stream))
                        using (var jsonWriter = new JsonTextWriter(writer))
                        {
                            _serializer.Serialize(jsonWriter, state);
                            jsonWriter.Flush();
                            stream.Position = 0;

                            await _storage.SaveFileAsync(CoreConstants.Folders.RevalidationFolderName, StateFileName, stream, accessCondition);
                        }

                return(state);
            }
            catch (StorageException e) when(e.IsPreconditionFailedException())
            {
                throw new InvalidOperationException("Failed to update the state blob since the access condition failed", e);
            }
        }
예제 #13
0
        /// <summary>
        /// Asynchronously sets blob metadata.
        /// </summary>
        /// <param name="folderName">The folder (container) name.</param>
        /// <param name="fileName">The blob file name.</param>
        /// <param name="updateMetadataAsync">A function which updates a metadata dictionary and returns <c>true</c>
        /// for changes to be persisted or <c>false</c> for changes to be discarded.</param>
        /// <returns>A task that represents the asynchronous operation.</returns>
        public async Task SetMetadataAsync(
            string folderName,
            string fileName,
            Func <Lazy <Task <Stream> >, IDictionary <string, string>, Task <bool> > updateMetadataAsync)
        {
            if (folderName == null)
            {
                throw new ArgumentNullException(nameof(folderName));
            }

            if (fileName == null)
            {
                throw new ArgumentNullException(nameof(fileName));
            }

            if (updateMetadataAsync == null)
            {
                throw new ArgumentNullException(nameof(updateMetadataAsync));
            }

            var container = await GetContainerAsync(folderName);

            var blob = container.GetBlobReference(fileName);

            await blob.FetchAttributesAsync();

            var lazyStream = new Lazy <Task <Stream> >(() => GetFileAsync(folderName, fileName));
            var wasUpdated = await updateMetadataAsync(lazyStream, blob.Metadata);

            if (wasUpdated)
            {
                var accessCondition       = AccessConditionWrapper.GenerateIfMatchCondition(blob.ETag);
                var mappedAccessCondition = new AccessCondition
                {
                    IfNoneMatchETag = accessCondition.IfNoneMatchETag,
                    IfMatchETag     = accessCondition.IfMatchETag
                };

                await blob.SetMetadataAsync(mappedAccessCondition);
            }
        }
예제 #14
0
            public async Task ThrowsIfCopyOperationFails()
            {
                // Arrange
                _destBlobMock
                .Setup(x => x.StartCopyAsync(It.IsAny <ISimpleCloudBlob>(), It.IsAny <AccessCondition>(), It.IsAny <AccessCondition>()))
                .Returns(Task.FromResult(0))
                .Callback <ISimpleCloudBlob, AccessCondition, AccessCondition>((_, __, ___) =>
                {
                    SetDestCopyStatus(CopyStatus.Failed);
                });

                // Act & Assert
                var ex = await Assert.ThrowsAsync <StorageException>(
                    () => _target.CopyFileAsync(
                        _srcFolderName,
                        _srcFileName,
                        _destFolderName,
                        _destFileName,
                        AccessConditionWrapper.GenerateIfNotExistsCondition()));

                Assert.Contains("The blob copy operation had copy status Failed", ex.Message);
            }
예제 #15
0
        private async Task <string> CopyFileAsync(
            ISimpleCloudBlob srcBlob,
            string destFolderName,
            string destFileName,
            IAccessCondition destAccessCondition)
        {
            if (destFolderName == null)
            {
                throw new ArgumentNullException(nameof(destFolderName));
            }

            if (destFileName == null)
            {
                throw new ArgumentNullException(nameof(destFileName));
            }

            var destContainer = await GetContainerAsync(destFolderName);

            var destBlob = destContainer.GetBlobReference(destFileName);

            destAccessCondition = destAccessCondition ?? AccessConditionWrapper.GenerateIfNotExistsCondition();
            var mappedDestAccessCondition = new AccessCondition
            {
                IfNoneMatchETag = destAccessCondition.IfNoneMatchETag,
                IfMatchETag     = destAccessCondition.IfMatchETag,
            };

            if (!await srcBlob.ExistsAsync())
            {
                _trace.TraceEvent(
                    TraceEventType.Warning,
                    id: 0,
                    message: $"Before calling FetchAttributesAsync(), the source blob '{srcBlob.Name}' does not exist.");
            }

            // Determine the source blob etag.
            await srcBlob.FetchAttributesAsync();

            var srcAccessCondition = AccessCondition.GenerateIfMatchCondition(srcBlob.ETag);

            // Check if the destination blob already exists and fetch attributes.
            if (await destBlob.ExistsAsync())
            {
                if (destBlob.CopyState?.Status == CopyStatus.Failed)
                {
                    // If the last copy failed, allow this copy to occur no matter what the caller's destination
                    // condition is. This is because the source blob is preferable over a failed copy. We use the etag
                    // of the failed blob to avoid inadvertently replacing a blob that is now valid (i.e. has a
                    // successful copy status).
                    _trace.TraceEvent(
                        TraceEventType.Information,
                        id: 0,
                        message: $"Destination blob '{destFolderName}/{destFileName}' already exists but has a " +
                        $"failed copy status. This blob will be replaced if the etag matches '{destBlob.ETag}'.");

                    mappedDestAccessCondition = AccessCondition.GenerateIfMatchCondition(destBlob.ETag);
                }
                else if ((srcBlob.Properties.ContentMD5 != null &&
                          srcBlob.Properties.ContentMD5 == destBlob.Properties.ContentMD5 &&
                          srcBlob.Properties.Length == destBlob.Properties.Length))
                {
                    // If the blob hash is the same and the length is the same, no-op the copy.
                    _trace.TraceEvent(
                        TraceEventType.Information,
                        id: 0,
                        message: $"Destination blob '{destFolderName}/{destFileName}' already has hash " +
                        $"'{destBlob.Properties.ContentMD5}' and length '{destBlob.Properties.Length}'. The copy " +
                        $"will be skipped.");

                    return(srcBlob.ETag);
                }
            }

            _trace.TraceEvent(
                TraceEventType.Information,
                id: 0,
                message: $"Copying of source blob '{srcBlob.Uri}' to '{destFolderName}/{destFileName}' with source " +
                $"access condition {Log(srcAccessCondition)} and destination access condition " +
                $"{Log(mappedDestAccessCondition)}.");

            // Start the server-side copy and wait for it to complete. If "If-None-Match: *" was specified and the
            // destination already exists, HTTP 409 is thrown. If "If-Match: ETAG" was specified and the destination
            // has changed, HTTP 412 is thrown.
            try
            {
                await destBlob.StartCopyAsync(
                    srcBlob,
                    srcAccessCondition,
                    mappedDestAccessCondition);
            }
            catch (StorageException ex) when(ex.IsFileAlreadyExistsException())
            {
                throw new FileAlreadyExistsException(
                          String.Format(
                              CultureInfo.CurrentCulture,
                              "There is already a blob with name {0} in container {1}.",
                              destFileName,
                              destFolderName),
                          ex);
            }

            var stopwatch = Stopwatch.StartNew();

            while (destBlob.CopyState.Status == CopyStatus.Pending &&
                   stopwatch.Elapsed < MaxCopyDuration)
            {
                if (!await destBlob.ExistsAsync())
                {
                    _trace.TraceEvent(
                        TraceEventType.Warning,
                        id: 0,
                        message: $"Before calling FetchAttributesAsync(), the destination blob '{destBlob.Name}' does not exist.");
                }

                await destBlob.FetchAttributesAsync();

                await Task.Delay(CopyPollFrequency);
            }

            if (destBlob.CopyState.Status == CopyStatus.Pending)
            {
                throw new TimeoutException($"Waiting for the blob copy operation to complete timed out after {MaxCopyDuration.TotalSeconds} seconds.");
            }
            else if (destBlob.CopyState.Status != CopyStatus.Success)
            {
                throw new StorageException($"The blob copy operation had copy status {destBlob.CopyState.Status} ({destBlob.CopyState.StatusDescription}).");
            }

            return(srcBlob.ETag);
        }