diff --git a/sdk/storage/Azure.Storage.Common/src/Shared/StorageVersionExtensions.cs b/sdk/storage/Azure.Storage.Common/src/Shared/StorageVersionExtensions.cs index 983666188a7e..e11d79d5f39d 100644 --- a/sdk/storage/Azure.Storage.Common/src/Shared/StorageVersionExtensions.cs +++ b/sdk/storage/Azure.Storage.Common/src/Shared/StorageVersionExtensions.cs @@ -20,6 +20,8 @@ Azure.Storage.Blobs.BlobClientOptions.ServiceVersion; #elif BlobDataMovementSDK Azure.Storage.Blobs.BlobClientOptions.ServiceVersion; +#elif ShareDataMovementSDK + Azure.Storage.Files.Shares.ShareClientOptions.ServiceVersion; #else // If you see this error, you've included this shared source file from a // client library that it doesn't know how to help you with. Either add @@ -39,7 +41,7 @@ internal static class StorageVersionExtensions /// Gets the latest version of the service supported by this SDK. /// public const ServiceVersion LatestVersion = -#if BlobSDK || QueueSDK || FileSDK || DataLakeSDK || ChangeFeedSDK || DataMovementSDK|| BlobDataMovementSDK +#if BlobSDK || QueueSDK || FileSDK || DataLakeSDK || ChangeFeedSDK || DataMovementSDK|| BlobDataMovementSDK || ShareDataMovementSDK ServiceVersion.V2023_11_03; #else ERROR_STORAGE_SERVICE_NOT_DEFINED; @@ -49,7 +51,7 @@ internal static class StorageVersionExtensions /// Gets the latest version of the service supported by this SDK. /// internal const ServiceVersion MaxVersion = -#if BlobSDK || QueueSDK || FileSDK || DataLakeSDK || ChangeFeedSDK || DataMovementSDK|| BlobDataMovementSDK +#if BlobSDK || QueueSDK || FileSDK || DataLakeSDK || ChangeFeedSDK || DataMovementSDK|| BlobDataMovementSDK || ShareDataMovementSDK ServiceVersion.V2023_11_03; #else ERROR_STORAGE_SERVICE_NOT_DEFINED; diff --git a/sdk/storage/Azure.Storage.DataMovement.Files.Shares/assets.json b/sdk/storage/Azure.Storage.DataMovement.Files.Shares/assets.json new file mode 100644 index 000000000000..8ca5d6870ec1 --- /dev/null +++ b/sdk/storage/Azure.Storage.DataMovement.Files.Shares/assets.json @@ -0,0 +1,6 @@ +{ + "AssetsRepo": "Azure/azure-sdk-assets", + "AssetsRepoPrefixPath": "net", + "TagPrefix": "net/storage/Azure.Storage.DataMovement.Files.Shares", + "Tag": "net/storage/Azure.Storage.DataMovement.Files.Shares_b5e6d0b779" +} diff --git a/sdk/storage/Azure.Storage.DataMovement.Files.Shares/src/ShareFileStorageResource.cs b/sdk/storage/Azure.Storage.DataMovement.Files.Shares/src/ShareFileStorageResource.cs index dd06ea2e7bed..60262de884ce 100644 --- a/sdk/storage/Azure.Storage.DataMovement.Files.Shares/src/ShareFileStorageResource.cs +++ b/sdk/storage/Azure.Storage.DataMovement.Files.Shares/src/ShareFileStorageResource.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using System.Text; using System.Threading; using System.Threading.Tasks; using Azure.Core; @@ -55,6 +56,32 @@ internal ShareFileStorageResource( _etagDownloadLock = etagLock; } + internal async Task CreateAsync( + bool overwrite, + long maxSize, + CancellationToken cancellationToken) + { + if (!overwrite) + { + // If overwrite is not enabled, we should check if the + // file exists first before creating because Create call will + // automatically overwrite the file if it already exists. + Response exists = await ShareFileClient.ExistsAsync(cancellationToken).ConfigureAwait(false); + if (exists.Value) + { + throw Errors.ShareFileAlreadyExists(ShareFileClient.Path); + } + } + await ShareFileClient.CreateAsync( + maxSize: maxSize, + httpHeaders: _options?.HttpHeaders, + metadata: _options?.FileMetadata, + smbProperties: _options?.SmbProperties, + filePermission: _options?.FilePermissions, + conditions: _options?.DestinationConditions, + cancellationToken: cancellationToken).ConfigureAwait(false); + } + protected override Task CompleteTransferAsync( bool overwrite, CancellationToken cancellationToken = default) @@ -72,11 +99,21 @@ protected override async Task CopyBlockFromUriAsync( CancellationToken cancellationToken = default) { CancellationHelper.ThrowIfCancellationRequested(cancellationToken); + + if (range.Offset == 0) + { + await CreateAsync(overwrite, completeLength, cancellationToken).ConfigureAwait(false); + if (range.Length == 0) + { + return; + } + } + await ShareFileClient.UploadRangeFromUriAsync( sourceUri: sourceResource.Uri, range: range, sourceRange: range, - options: _options.ToShareFileUploadRangeFromUriOptions(), + options: _options?.ToShareFileUploadRangeFromUriOptions(), cancellationToken: cancellationToken).ConfigureAwait(false); } @@ -91,21 +128,22 @@ protected override async Task CopyFromStreamAsync( CancellationHelper.ThrowIfCancellationRequested(cancellationToken); long position = options?.Position != default ? options.Position.Value : 0; - if ((streamLength == completeLength) && position == 0) + + // Create the File beforehand if it hasn't been created + if (position == 0) { - // Default to Upload - await ShareFileClient.UploadAsync( - stream, - _options.ToShareFileUploadOptions(), - cancellationToken: cancellationToken).ConfigureAwait(false); - return; + await CreateAsync(overwrite, completeLength, cancellationToken).ConfigureAwait(false); + if (completeLength == 0) + { + return; + } } // Otherwise upload the Range await ShareFileClient.UploadRangeAsync( new HttpRange(position, streamLength), stream, - _options.ToShareFileUploadRangeOptions(), + _options?.ToShareFileUploadRangeOptions(), cancellationToken).ConfigureAwait(false); } @@ -121,7 +159,7 @@ await ShareFileClient.UploadRangeFromUriAsync( sourceUri: sourceResource.Uri, range: new HttpRange(0, completeLength), sourceRange: new HttpRange(0, completeLength), - options: _options.ToShareFileUploadRangeFromUriOptions(), + options: _options?.ToShareFileUploadRangeFromUriOptions(), cancellationToken: cancellationToken).ConfigureAwait(false); } @@ -156,7 +194,7 @@ protected override async Task ReadStreamAsync( { CancellationHelper.ThrowIfCancellationRequested(cancellationToken); Response response = await ShareFileClient.DownloadAsync( - _options.ToShareFileDownloadOptions(new HttpRange(position, length)), + _options?.ToShareFileDownloadOptions(new HttpRange(position, length)), cancellationToken).ConfigureAwait(false); return response.Value.ToStorageResourceReadStreamResult(); } @@ -171,4 +209,12 @@ protected override StorageResourceCheckpointData GetDestinationCheckpointData() throw new NotImplementedException(); } } + +#pragma warning disable SA1402 // File may only contain a single type + internal partial class Errors +#pragma warning restore SA1402 // File may only contain a single type + { + public static InvalidOperationException ShareFileAlreadyExists(string pathName) + => new InvalidOperationException($"Share File `{pathName}` already exists. Cannot overwrite file."); + } } diff --git a/sdk/storage/Azure.Storage.DataMovement.Files.Shares/tests/Azure.Storage.DataMovement.Files.Shares.Tests.csproj b/sdk/storage/Azure.Storage.DataMovement.Files.Shares/tests/Azure.Storage.DataMovement.Files.Shares.Tests.csproj index eeca6c590db6..e0440459ba4b 100644 --- a/sdk/storage/Azure.Storage.DataMovement.Files.Shares/tests/Azure.Storage.DataMovement.Files.Shares.Tests.csproj +++ b/sdk/storage/Azure.Storage.DataMovement.Files.Shares/tests/Azure.Storage.DataMovement.Files.Shares.Tests.csproj @@ -16,12 +16,16 @@ + + + + @@ -33,6 +37,14 @@ + + + + + + + + PreserveNewest diff --git a/sdk/storage/Azure.Storage.DataMovement.Files.Shares/tests/ClientBuilderExtensions.cs b/sdk/storage/Azure.Storage.DataMovement.Files.Shares/tests/ClientBuilderExtensions.cs new file mode 100644 index 000000000000..23267959a0fa --- /dev/null +++ b/sdk/storage/Azure.Storage.DataMovement.Files.Shares/tests/ClientBuilderExtensions.cs @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Azure.Storage.Test.Shared; +using Azure.Storage.Files.Shares; +using Azure.Storage.Files.Shares.Tests; +using SharesClientBuilder = Azure.Storage.Test.Shared.ClientBuilder< + Azure.Storage.Files.Shares.ShareServiceClient, + Azure.Storage.Files.Shares.ShareClientOptions>; + +namespace Azure.Storage.DataMovement.Files.Shares.Tests +{ + internal static class ClientBuilderExtensions + { + public static string GetNewShareName(this SharesClientBuilder clientBuilder) + => $"test-share-{clientBuilder.Recording.Random.NewGuid()}"; + public static string GetNewDirectoryName(this SharesClientBuilder clientBuilder) + => $"test-directory-{clientBuilder.Recording.Random.NewGuid()}"; + public static string GetNewNonAsciiDirectoryName(this SharesClientBuilder clientBuilder) + => $"test-dire¢t Ø®ϒ%3A-{clientBuilder.Recording.Random.NewGuid()}"; + public static string GetNewFileName(this SharesClientBuilder clientBuilder) + => $"test-file-{clientBuilder.Recording.Random.NewGuid()}"; + public static string GetNewNonAsciiFileName(this SharesClientBuilder clientBuilder) + => $"test-ƒ¡£€‽%3A-{clientBuilder.Recording.Random.NewGuid()}"; + + /// + /// Creates a new + /// setup to generate s. + /// + /// powering this client builder. + /// Service version for clients to target. + public static SharesClientBuilder GetNewShareClientBuilder(TenantConfigurationBuilder tenants, ShareClientOptions.ServiceVersion serviceVersion) + => new SharesClientBuilder( + ServiceEndpoint.File, + tenants, + (uri, clientOptions) => new ShareServiceClient(uri, clientOptions), + (uri, sharedKeyCredential, clientOptions) => new ShareServiceClient(uri, sharedKeyCredential, clientOptions), + (uri, tokenCredential, clientOptions) => new ShareServiceClient(uri, tokenCredential, clientOptions), + (uri, azureSasCredential, clientOptions) => new ShareServiceClient(uri, azureSasCredential, clientOptions), + () => new ShareClientOptions(serviceVersion)); + + public static async Task GetTestShareAsync( + this SharesClientBuilder clientBuilder, + ShareServiceClient service = default, + string shareName = default, + IDictionary metadata = default, + ShareClientOptions options = default) + { + service ??= clientBuilder.GetServiceClientFromSharedKeyConfig(clientBuilder.Tenants.TestConfigDefault, options); + metadata ??= new Dictionary(StringComparer.OrdinalIgnoreCase); + shareName ??= clientBuilder.GetNewShareName(); + ShareClient share = clientBuilder.AzureCoreRecordedTestBase.InstrumentClient(service.GetShareClient(shareName)); + return await DisposingShare.CreateAsync(share, metadata); + } + } +} diff --git a/sdk/storage/Azure.Storage.DataMovement.Files.Shares/tests/ShareFileResourceTests.cs b/sdk/storage/Azure.Storage.DataMovement.Files.Shares/tests/ShareFileResourceTests.cs index fd2bc19fc596..5555330d32fe 100644 --- a/sdk/storage/Azure.Storage.DataMovement.Files.Shares/tests/ShareFileResourceTests.cs +++ b/sdk/storage/Azure.Storage.DataMovement.Files.Shares/tests/ShareFileResourceTests.cs @@ -2,6 +2,8 @@ // Licensed under the MIT License. using System; +using System.Collections; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; @@ -144,9 +146,9 @@ public async Task CopyFromStreamAsync() var data = GetRandomBuffer(length); using var stream = new MemoryStream(data); using var fileContentStream = new MemoryStream(); - mock.Setup(b => b.UploadAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .Callback( - async (uploadedstream, options, token) => + mock.Setup(b => b.UploadRangeAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback( + async (range, uploadedstream, options, token) => { await uploadedstream.CopyToAsync(fileContentStream).ConfigureAwait(false); fileContentStream.Position = 0; @@ -157,6 +159,22 @@ public async Task CopyFromStreamAsync() lastModified: DateTimeOffset.UtcNow, contentHash: default, isServerEncrypted: false), + new MockResponse(201)))); + mock.Setup(b => b.ExistsAsync(It.IsAny())) + .Returns(Task.FromResult(Response.FromValue(false, new MockResponse(200)))); + mock.Setup(b => b.CreateAsync(It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.FromResult(Response.FromValue( + FilesModelFactory.StorageFileInfo( + eTag: new ETag("eTag"), + lastModified: DateTimeOffset.UtcNow, + isServerEncrypted: false, + filePermissionKey: "rw", + fileAttributes: "Archive|ReadOnly", + fileCreationTime: DateTimeOffset.UtcNow, + fileLastWriteTime: DateTimeOffset.UtcNow, + fileChangeTime: DateTimeOffset.UtcNow, + fileId: "48903841", + fileParentId: "93024923"), new MockResponse(200)))); ShareFileStorageResource storageResource = new ShareFileStorageResource(mock.Object); @@ -169,7 +187,23 @@ await storageResource.CopyFromStreamInternalAsync( completeLength: length); Assert.That(data, Is.EqualTo(fileContentStream.AsBytes().ToArray())); - mock.Verify(b => b.UploadAsync(It.IsAny(), It.IsAny(), It.IsAny()), + mock.Verify(b => b.UploadRangeAsync( + new HttpRange(0, length), + stream, + It.IsAny(), + It.IsAny()), + Times.Once()); + mock.Verify(b => b.CreateAsync( + length, + It.IsAny(), + It.IsAny>(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny()), + Times.Once()); + mock.Verify(b => b.ExistsAsync( + It.IsAny()), Times.Once()); mock.VerifyNoOtherCalls(); } @@ -190,7 +224,7 @@ public async Task CopyFromStreamAsync_Position() .Callback( async (range, uploadedstream, options, token) => { - fileContentStream.Position = 5; + fileContentStream.Position = position; await uploadedstream.CopyToAsync(fileContentStream).ConfigureAwait(false); fileContentStream.Position = 0; }) @@ -214,9 +248,13 @@ await storageResource.CopyFromStreamInternalAsync( // Assert byte[] dataAt5 = new byte[data.Length + position]; - Array.Copy(data, 0, dataAt5, 5, length); + Array.Copy(data, 0, dataAt5, position, length); Assert.That(dataAt5, Is.EqualTo(fileContentStream.AsBytes().ToArray())); - mock.Verify(b => b.UploadRangeAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), + mock.Verify(b => b.UploadRangeAsync( + new HttpRange(position, length), + stream, + It.IsAny(), + It.IsAny()), Times.Once()); mock.VerifyNoOtherCalls(); } @@ -229,8 +267,8 @@ public async Task CopyFromStreamAsync_Error() new Uri("https://storageaccount.file.core.windows.net/container/file"), new ShareClientOptions()); - mock.Setup(b => b.UploadAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .Throws(new RequestFailedException(status: 404, message: "The specified resource does not exist.", errorCode: "ResourceNotFound", default)); + mock.Setup(b => b.ExistsAsync(It.IsAny())) + .Returns(Task.FromResult(Response.FromValue(true, new MockResponse(200)))); ShareFileStorageResource storageResource = new ShareFileStorageResource(mock.Object); @@ -239,15 +277,17 @@ public async Task CopyFromStreamAsync_Error() var data = GetRandomBuffer(length); using (var stream = new MemoryStream(data)) { - await TestHelper.AssertExpectedExceptionAsync( + await TestHelper.AssertExpectedExceptionAsync( storageResource.CopyFromStreamInternalAsync(stream, length, false, length), e => { - Assert.AreEqual("ResourceNotFound", e.ErrorCode); + Assert.IsTrue(e.Message.Contains("Cannot overwrite file.")); }); } - mock.Verify(b => b.UploadAsync(It.IsAny(), It.IsAny(), It.IsAny()), + mock.Verify(b => b.ExistsAsync( + It.IsAny()), Times.Once()); + mock.Verify(b => b.Path, Times.Once()); mock.VerifyNoOtherCalls(); } @@ -346,6 +386,22 @@ public async Task CopyBlockFromUriAsync() contentHash: default, isServerEncrypted: false), new MockResponse(200)))); + mockDestination.Setup(b => b.ExistsAsync(It.IsAny())) + .Returns(Task.FromResult(Response.FromValue(false,new MockResponse(200)))); + mockDestination.Setup(b => b.CreateAsync(It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.FromResult(Response.FromValue( + FilesModelFactory.StorageFileInfo( + eTag: new ETag("eTag"), + lastModified: DateTimeOffset.UtcNow, + isServerEncrypted: false, + filePermissionKey: "rw", + fileAttributes: "Archive|ReadOnly", + fileCreationTime: DateTimeOffset.UtcNow, + fileLastWriteTime: DateTimeOffset.UtcNow, + fileChangeTime: DateTimeOffset.UtcNow, + fileId: "48903841", + fileParentId: "93024923"), + new MockResponse(200)))); ShareFileStorageResource destinationResource = new ShareFileStorageResource(mockDestination.Object); // Act @@ -364,6 +420,18 @@ await destinationResource.CopyBlockFromUriInternalAsync( It.IsAny(), It.IsAny()), Times.Once()); + mockDestination.Verify(b => b.CreateAsync( + length, + It.IsAny(), + It.IsAny>(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny()), + Times.Once()); + mockDestination.Verify(b => b.ExistsAsync( + It.IsAny()), + Times.Once()); mockDestination.VerifyNoOtherCalls(); } @@ -379,28 +447,24 @@ public async Task CopyBlockFromUriAsync_Error() new Uri("https://storageaccount.file.core.windows.net/container/destinationfile"), new ShareClientOptions()); - mockDestination.Setup(b => b.UploadRangeFromUriAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Throws(new RequestFailedException(status: 404, message: "The specified resource does not exist.", errorCode: "ResourceNotFound", default)); + mockDestination.Setup(b => b.ExistsAsync(It.IsAny())) + .Returns(Task.FromResult(Response.FromValue(true, new MockResponse(200)))); ShareFileStorageResource destinationResource = new ShareFileStorageResource(mockDestination.Object); // Act int length = 1024; - await TestHelper.AssertExpectedExceptionAsync( + await TestHelper.AssertExpectedExceptionAsync( destinationResource.CopyBlockFromUriInternalAsync(sourceResource.Object, new HttpRange(0, length), false, length), e => { - Assert.AreEqual("ResourceNotFound", e.ErrorCode); + Assert.IsTrue(e.Message.Contains("Cannot overwrite file.")); }); - sourceResource.Verify(b => b.Uri, Times.Once()); sourceResource.VerifyNoOtherCalls(); - mockDestination.Verify(b => b.UploadRangeFromUriAsync( - sourceResource.Object.Uri, - new HttpRange(0, length), - new HttpRange(0, length), - It.IsAny(), + mockDestination.Verify(b => b.ExistsAsync( It.IsAny()), Times.Once()); + mockDestination.Verify(b => b.Path, Times.Once()); mockDestination.VerifyNoOtherCalls(); } diff --git a/sdk/storage/Azure.Storage.DataMovement.Files.Shares/tests/ShareFileStartTransferUploadTests.cs b/sdk/storage/Azure.Storage.DataMovement.Files.Shares/tests/ShareFileStartTransferUploadTests.cs new file mode 100644 index 000000000000..373861a5a3b0 --- /dev/null +++ b/sdk/storage/Azure.Storage.DataMovement.Files.Shares/tests/ShareFileStartTransferUploadTests.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Threading.Tasks; +using Azure.Storage.Test.Shared; +using Azure.Storage.DataMovement.Tests; +using Azure.Storage.Files.Shares; +using Azure.Storage.Files.Shares.Tests; +using System.IO; + +namespace Azure.Storage.DataMovement.Files.Shares.Tests +{ + [ShareClientTestFixture] + public class ShareFileStartTransferUploadTests : StartTransferUploadTestBase< + ShareServiceClient, + ShareClient, + ShareFileClient, + ShareClientOptions, + StorageTestEnvironment> + { + private const string _fileResourcePrefix = "test-file-"; + private const string _expectedOverwriteExceptionMessage = "Cannot overwrite file."; + + public ShareFileStartTransferUploadTests(bool async, ShareClientOptions.ServiceVersion serviceVersion) + : base(async, _expectedOverwriteExceptionMessage, _fileResourcePrefix, null /* RecordedTestMode.Record /* to re-record */) + { + ClientBuilder = ClientBuilderExtensions.GetNewShareClientBuilder(Tenants, serviceVersion); + } + + protected override async Task ExistsAsync(ShareFileClient objectClient) + => await objectClient.ExistsAsync(); + + protected override async Task> GetDisposingContainerAsync(ShareServiceClient service = null, string containerName = null) + => await ClientBuilder.GetTestShareAsync(service, containerName); + + protected override async Task GetObjectClientAsync( + ShareClient container, + long? resourceLength = null, + bool createResource = false, + string resourceName = null, + ShareClientOptions options = null, + Stream contents = default) + { + resourceName ??= GetNewObjectName(); + if (createResource) + { + if (!resourceLength.HasValue) + { + throw new InvalidOperationException($"Cannot create share file without size specified. Either set {nameof(createResource)} to false or specify a {nameof(resourceLength)}."); + } + ShareFileClient fileClient = container.GetRootDirectoryClient().GetFileClient(resourceName); + await fileClient.CreateAsync(resourceLength.Value); + + if (contents != default) + { + await fileClient.UploadAsync(contents); + } + + return fileClient; + } + return container.GetRootDirectoryClient().GetFileClient(resourceName); + } + + protected override StorageResourceItem GetStorageResourceItem(ShareFileClient resourceClient) + => new ShareFileStorageResource(resourceClient); + + protected override Task OpenReadAsync(ShareFileClient objectClient) + => objectClient.OpenReadAsync(); + } +} diff --git a/sdk/storage/Azure.Storage.DataMovement/src/StreamToUriJobPart.cs b/sdk/storage/Azure.Storage.DataMovement/src/StreamToUriJobPart.cs index 466e1fec9f6a..9fd5b083d9d1 100644 --- a/sdk/storage/Azure.Storage.DataMovement/src/StreamToUriJobPart.cs +++ b/sdk/storage/Azure.Storage.DataMovement/src/StreamToUriJobPart.cs @@ -283,6 +283,11 @@ await _destinationResource.CopyFromStreamAsync( { await InvokeSkippedArg().ConfigureAwait(false); } + catch (InvalidOperationException ex) + when (ex.Message.Contains("Cannot overwrite file.") && _createMode == StorageResourceCreationPreference.SkipIfExists) + { + await InvokeSkippedArg().ConfigureAwait(false); + } catch (Exception ex) { await InvokeFailedArg(ex).ConfigureAwait(false); diff --git a/sdk/storage/Azure.Storage.DataMovement/tests/Shared/StartTransferUploadTestBase.cs b/sdk/storage/Azure.Storage.DataMovement/tests/Shared/StartTransferUploadTestBase.cs new file mode 100644 index 000000000000..b531edf19557 --- /dev/null +++ b/sdk/storage/Azure.Storage.DataMovement/tests/Shared/StartTransferUploadTestBase.cs @@ -0,0 +1,737 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Azure.Core; +using Azure.Core.TestFramework; +using Azure.Storage.Test.Shared; +using NUnit.Framework; + +namespace Azure.Storage.DataMovement.Tests +{ + public abstract class StartTransferUploadTestBase + : StorageTestBase + where TServiceClient : class + where TContainerClient : class + where TObjectClient : class + where TClientOptions : ClientOptions + where TEnvironment : StorageTestEnvironment, new() + { + private readonly string _generatedResourceNamePrefix; + private readonly string _expectedOverwriteExceptionMessage; + + public ClientBuilder ClientBuilder { get; protected set; } + + /// + /// Constructor for TransferManager.StartTransferAsync tests + /// + /// The async is defaulted to true, since we do not have sync StartTransfer methods. + /// + /// + /// + public StartTransferUploadTestBase( + bool async, + string expectedOverwriteExceptionMessage, + string generatedResourceNamePrefix = default, + RecordedTestMode? mode = null) : base(async, mode) + { + Argument.CheckNotNullOrEmpty(expectedOverwriteExceptionMessage, nameof(expectedOverwriteExceptionMessage)); + _generatedResourceNamePrefix = generatedResourceNamePrefix ?? "test-resource-"; + _expectedOverwriteExceptionMessage = expectedOverwriteExceptionMessage; + } + + #region Service-Specific Methods + /// + /// Gets a service-specific disposing container for use with tests in this class. + /// + /// Optionally specified service client to get container from. + /// Optional container name specification. + protected abstract Task> GetDisposingContainerAsync( + TServiceClient service = default, + string containerName = default); + + /// + /// Gets a new service-specific child object client from a given container, e.g. a BlobClient from a + /// BlobContainerClient or a TObjectClient from a ShareClient. + /// + /// Container to get resource from. + /// Sets the resource size in bytes, for resources that require this upfront. + /// Whether to call CreateAsync on the resource, if necessary. + /// Optional name for the resource. + /// ClientOptions for the resource client. + /// If specified, the contents will be uploaded to the object client. + protected abstract Task GetObjectClientAsync( + TContainerClient container, + long? objectLength = default, + bool createResource = false, + string objectName = default, + TClientOptions options = default, + Stream contents = default); + + /// + /// Gets the specific storage resource from the given TObjectClient + /// e.g. ShareFileClient to a ShareFileStorageResource, BlockBlobClient to a BlockBlobStorageResource. + /// + /// The object client to create the storage resource object. + /// + protected abstract StorageResourceItem GetStorageResourceItem(TObjectClient objectClient); + + /// + /// Calls the OpenRead method on the TObjectClient. + /// + /// This is mainly used to verify the contents of the Object Client. + /// + /// The object client to get the Open Read Stream from. + /// + protected abstract Task OpenReadAsync(TObjectClient objectClient); + + /// + /// Checks if the Object Client exists. + /// + /// Object Client to call exists on. + /// + protected abstract Task ExistsAsync(TObjectClient objectClient); + #endregion + + protected string GetNewObjectName() + => _generatedResourceNamePrefix + ClientBuilder.Recording.Random.NewGuid(); + + private async Task CreateStartTransfer( + TContainerClient containerClient, + string localDirectoryPath, + int concurrency, + bool createFailedCondition = false, + DataTransferOptions options = default, + int size = Constants.KB) + { + // Arrange + string destinationName = GetNewObjectName(); + + // To create a transfer intended to end in failure, + // create the failed object so we can run into an overwrite error. + TObjectClient destinationClient = await GetObjectClientAsync( + containerClient, + objectLength: size, + createResource: createFailedCondition); + StorageResourceItem destinationResource = GetStorageResourceItem(destinationClient); + + // Create new source file + using Stream originalStream = await CreateLimitedMemoryStream(size); + string localSourceFile = Path.Combine(localDirectoryPath, destinationName); + // create a new file and copy contents of stream into it, and then close the FileStream + // so the StagedUploadAsync call is not prevented from reading using its FileStream. + using (FileStream fileStream = File.Create(localSourceFile)) + { + await originalStream.CopyToAsync(fileStream); + } + LocalFilesStorageResourceProvider files = new(); + StorageResource sourceResource = files.FromFile(localSourceFile); + + // Create Transfer Manager with single threaded operation + TransferManagerOptions managerOptions = new TransferManagerOptions() + { + MaximumConcurrency = concurrency, + }; + TransferManager transferManager = new TransferManager(managerOptions); + + // Start transfer and await for completion. + return await transferManager.StartTransferAsync( + sourceResource, + destinationResource, + options).ConfigureAwait(false); + } + + [RecordedTest] + public async Task StartTransfer_AwaitCompletion() + { + // Arrange + using DisposingLocalDirectory disposingLocalDirectory = DisposingLocalDirectory.GetTestDirectory(); + await using IDisposingContainer test = await GetDisposingContainerAsync(); + + DataTransferOptions options = new DataTransferOptions(); + TestEventsRaised testEventsRaised = new TestEventsRaised(options); + + // Create transfer to do a AwaitCompletion + DataTransfer transfer = await CreateStartTransfer( + containerClient: test.Container, + localDirectoryPath: disposingLocalDirectory.DirectoryPath, + 1, + options: options); + + // Act + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(5)); + await transfer.WaitForCompletionAsync(cancellationTokenSource.Token).ConfigureAwait(false); + + // Assert + await testEventsRaised.AssertSingleCompletedCheck(); + Assert.NotNull(transfer); + Assert.IsTrue(transfer.HasCompleted); + Assert.AreEqual(DataTransferState.Completed, transfer.TransferStatus.State); + } + + [RecordedTest] + public async Task StartTransfer_AwaitCompletion_Failed() + { + // Arrange + using DisposingLocalDirectory disposingLocalDirectory = DisposingLocalDirectory.GetTestDirectory(); + await using IDisposingContainer test = await GetDisposingContainerAsync(); + + DataTransferOptions options = new DataTransferOptions() + { + CreationPreference = StorageResourceCreationPreference.FailIfExists + }; + TestEventsRaised testEventsRaised = new TestEventsRaised(options); + + // Create transfer to do a AwaitCompletion + DataTransfer transfer = await CreateStartTransfer( + containerClient: test.Container, + localDirectoryPath: disposingLocalDirectory.DirectoryPath, + concurrency: 1, + createFailedCondition: true, + options: options); + + // Act + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(5)); + await transfer.WaitForCompletionAsync(cancellationTokenSource.Token).ConfigureAwait(false); + + // Assert + await testEventsRaised.AssertSingleFailedCheck(); + Assert.NotNull(transfer); + Assert.IsTrue(transfer.HasCompleted); + Assert.AreEqual(DataTransferState.Completed, transfer.TransferStatus.State); + Assert.AreEqual(true, transfer.TransferStatus.HasFailedItems); + Assert.IsTrue(testEventsRaised.FailedEvents.First().Exception.Message.Contains(_expectedOverwriteExceptionMessage)); + } + + [RecordedTest] + public async Task StartTransfer_AwaitCompletion_Skipped() + { + // Arrange + using DisposingLocalDirectory disposingLocalDirectory = DisposingLocalDirectory.GetTestDirectory(); + await using IDisposingContainer test = await GetDisposingContainerAsync(); + + // Create transfer options with Skipping available + DataTransferOptions options = new DataTransferOptions() + { + CreationPreference = StorageResourceCreationPreference.SkipIfExists + }; + TestEventsRaised testEventsRaised = new TestEventsRaised(options); + + // Create transfer to do a AwaitCompletion + DataTransfer transfer = await CreateStartTransfer( + containerClient: test.Container, + localDirectoryPath: disposingLocalDirectory.DirectoryPath, + concurrency: 1, + createFailedCondition: true, + options: options); + + // Act + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(5)); + await transfer.WaitForCompletionAsync(cancellationTokenSource.Token).ConfigureAwait(false); + + // Assert + await testEventsRaised.AssertSingleSkippedCheck(); + Assert.NotNull(transfer); + Assert.IsTrue(transfer.HasCompleted); + Assert.AreEqual(DataTransferState.Completed, transfer.TransferStatus.State); + Assert.AreEqual(true, transfer.TransferStatus.HasSkippedItems); + } + + #region Single Upload Verified + internal class VerifyUploadObjectContentInfo + { + public readonly string LocalPath; + public TObjectClient DestinationClient; + public TestEventsRaised EventsRaised; + public DataTransfer DataTransfer; + + public VerifyUploadObjectContentInfo( + string sourceFile, + TObjectClient destinationClient, + TestEventsRaised eventsRaised, + DataTransfer dataTransfer) + { + LocalPath = sourceFile; + DestinationClient = destinationClient; + EventsRaised = eventsRaised; + DataTransfer = dataTransfer; + } + }; + + /// + /// Upload and verify the contents of the object + /// + /// By default in this function an event arguement will be added to the options event handler + /// to detect when the upload has finished. + /// + /// + /// + /// + /// + private async Task UploadResourceAndVerify( + TContainerClient container, + long size = Constants.KB, + int waitTimeInSec = 30, + TransferManagerOptions transferManagerOptions = default, + int objectCount = 1, + List objectNames = default, + List options = default) + { + using DisposingLocalDirectory testDirectory = DisposingLocalDirectory.GetTestDirectory(); + + // Populate objectNames list for number of files to be created + if (objectNames == default || objectNames?.Count == 0) + { + objectNames ??= new List(); + for (int i = 0; i < objectCount; i++) + { + objectNames.Add(GetNewObjectName()); + } + } + else + { + // If objectNames is popluated make sure these number of files match + Assert.AreEqual(objectCount, objectNames.Count); + } + + // Populate Options and TestRaisedOptions + List eventRaisedList = TestEventsRaised.PopulateTestOptions(objectCount, ref options); + + transferManagerOptions ??= new TransferManagerOptions() + { + ErrorHandling = DataTransferErrorMode.ContinueOnFailure + }; + + List uploadedObjectInfo = new List(objectCount); + + // Initialize TransferManager + TransferManager transferManager = new TransferManager(transferManagerOptions); + + // Set up file to upload + for (int i = 0; i < objectCount; i++) + { + using Stream originalStream = await CreateLimitedMemoryStream(size); + string localSourceFile = Path.Combine(testDirectory.DirectoryPath, GetNewObjectName()); + // create a new file and copy contents of stream into it, and then close the FileStream + // so the StagedUploadAsync call is not prevented from reading using its FileStream. + using (FileStream fileStream = File.OpenWrite(localSourceFile)) + { + await originalStream.CopyToAsync(fileStream); + } + + // Set up destination client + TObjectClient destClient = await GetObjectClientAsync( + container: container, + objectLength: size, + objectName: objectNames[i]); + StorageResourceItem destinationResource = GetStorageResourceItem(destClient); + + // Act + LocalFilesStorageResourceProvider files = new(); + StorageResource sourceResource = files.FromFile(localSourceFile); + DataTransfer transfer = await transferManager.StartTransferAsync(sourceResource, destinationResource, options[i]); + + uploadedObjectInfo.Add(new VerifyUploadObjectContentInfo( + sourceFile: localSourceFile, + destinationClient: destClient, + eventsRaised: eventRaisedList[i], + dataTransfer: transfer)); + } + + for (int i = 0; i < objectCount; i++) + { + // Assert + Assert.NotNull(uploadedObjectInfo[i].DataTransfer); + CancellationTokenSource tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(waitTimeInSec)); + await uploadedObjectInfo[i].DataTransfer.WaitForCompletionAsync(tokenSource.Token); + Assert.IsTrue(uploadedObjectInfo[i].DataTransfer.HasCompleted); + + // Verify Upload + await uploadedObjectInfo[i].EventsRaised.AssertSingleCompletedCheck(); + using FileStream fileStream = File.OpenRead(uploadedObjectInfo[i].LocalPath); + using Stream stream = await OpenReadAsync(uploadedObjectInfo[i].DestinationClient); + Assert.AreEqual(fileStream, stream); + } + } + + [RecordedTest] + public async Task LocalToRemoteObject() + { + // Arrange + await using IDisposingContainer test = await GetDisposingContainerAsync(); + + await UploadResourceAndVerify(test.Container); + } + + [RecordedTest] + public async Task LocalToRemoteObject_EventHandler() + { + AutoResetEvent InProgressWait = new AutoResetEvent(false); + + bool progressSeen = false; + DataTransferOptions options = new DataTransferOptions(); + options.TransferStatusChanged += (TransferStatusEventArgs args) => + { + // Assert + if (args.TransferStatus.State == DataTransferState.InProgress) + { + progressSeen = true; + } + return Task.CompletedTask; + }; + + // Arrange + await using IDisposingContainer test = await GetDisposingContainerAsync(); + + List optionsList = new List() { options }; + await UploadResourceAndVerify( + container: test.Container, + objectCount: optionsList.Count, + options: optionsList); + + // Assert + Assert.IsTrue(progressSeen); + } + + [RecordedTest] + public async Task LocalToRemoteObjectSize_SmallChunk() + { + long size = Constants.KB; + int waitTimeInSec = 25; + DataTransferOptions options = new DataTransferOptions() + { + InitialTransferSize = 100, + MaximumTransferChunkSize = 200, + }; + + // Arrange + await using IDisposingContainer test = await GetDisposingContainerAsync(); + List optionsList = new List() { options }; + + await UploadResourceAndVerify( + size: size, + waitTimeInSec: waitTimeInSec, + container: test.Container, + options: optionsList); + } + + [RecordedTest] + public async Task LocalToRemoteObject_Overwrite_Exists() + { + // Arrange + await using IDisposingContainer test = await GetDisposingContainerAsync(); + string objectName = GetNewObjectName(); + int size = Constants.KB; + int waitTimeInSec = 10; + + // Create destination client with uploaded content. + // This will cause a overwrite to occur since we have OverwriteIfExists enabled, + // since the destination will pre-exist. + TObjectClient objectClient; + using (Stream originalStream = await CreateLimitedMemoryStream(size)) + { + objectClient = await GetObjectClientAsync( + container: test.Container, + objectLength: size, + createResource: true, + objectName: objectName, + contents: originalStream); + } + + // Act + // Create options bag to overwrite any existing destination. + DataTransferOptions options = new DataTransferOptions() + { + CreationPreference = StorageResourceCreationPreference.OverwriteIfExists, + }; + List optionsList = new List() { options }; + List objectNames = new List() { objectName }; + + // Start transfer and await for completion. + await UploadResourceAndVerify( + container: test.Container, + size: size, + waitTimeInSec: waitTimeInSec, + objectNames: objectNames, + options: optionsList); + } + + [RecordedTest] + public async Task LocalToRemoteObject_Overwrite_NotExists() + { + // Arrange + await using IDisposingContainer test = await GetDisposingContainerAsync(); + int size = Constants.KB; + int waitTimeInSec = 10; + + // Act + // Create options bag to overwrite any existing destination. + DataTransferOptions options = new DataTransferOptions() + { + CreationPreference = StorageResourceCreationPreference.OverwriteIfExists, + }; + List optionsList = new List() { options }; + + // Start transfer and await for completion. + await UploadResourceAndVerify( + container: test.Container, + size: size, + waitTimeInSec: waitTimeInSec, + options: optionsList); + } + + [RecordedTest] + public async Task LocalToRemoteObject_Skip_Exists() + { + // Arrange + await using IDisposingContainer test = await GetDisposingContainerAsync(); + using DisposingLocalDirectory testDirectory = DisposingLocalDirectory.GetTestDirectory(); + string objectName = GetNewObjectName(); + int size = Constants.KB; + + // Create destination client with uploaded content. + // This will cause a skip to occur since we have SkipIfExists enabled, + // since the destination will pre-exist. + using Stream originalStream = await CreateLimitedMemoryStream(size); + TObjectClient objectClient = await GetObjectClientAsync( + container: test.Container, + objectLength: size, + createResource: true, + objectName: objectName, + contents: originalStream); + + // Act + // Create new source file + string newSourceFile = await CreateRandomFileAsync(testDirectory.DirectoryPath, size: size); + // Create options bag to overwrite any existing destination. + DataTransferOptions options = new DataTransferOptions() + { + CreationPreference = StorageResourceCreationPreference.SkipIfExists, + }; + LocalFilesStorageResourceProvider files = new(); + StorageResource sourceResource = files.FromFile(newSourceFile); + StorageResourceItem destinationResource = GetStorageResourceItem(objectClient); + TestEventsRaised testEventsRaised = new TestEventsRaised(options); + + TransferManager transferManager = new TransferManager(); + + // Start transfer and await for completion. + DataTransfer transfer = await transferManager.StartTransferAsync( + sourceResource, + destinationResource, + options); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + await transfer.WaitForCompletionAsync(cancellationTokenSource.Token); + + // Assert + await testEventsRaised.AssertSingleSkippedCheck(); + Assert.NotNull(transfer); + Assert.IsTrue(transfer.HasCompleted); + Assert.AreEqual(DataTransferState.Completed, transfer.TransferStatus.State); + Assert.AreEqual(true, transfer.TransferStatus.HasSkippedItems); + Assert.IsTrue(await ExistsAsync(objectClient)); + // Verify Upload - That we skipped over and didn't reupload something new. + using Stream stream = await OpenReadAsync(objectClient); + Assert.AreEqual(originalStream, stream); + } + + [RecordedTest] + public async Task LocalToRemoteObject_Failure_Exists() + { + // Arrange + await using IDisposingContainer test = await GetDisposingContainerAsync(); + string objectName = GetNewObjectName(); + int size = Constants.KB; + + // Create destination client with uploaded content. + // This will cause a failure to occur since we have FailIfExists enabled, + // since the destination will pre-exist. + using Stream originalStream = await CreateLimitedMemoryStream(size); + TObjectClient objectClient = await GetObjectClientAsync( + container: test.Container, + objectLength: size, + createResource: true, + objectName: objectName, + contents: originalStream); + + // Make destination file name but do not create the file beforehand. + // Create new source file + string newSourceFile = await CreateRandomFileAsync(Path.GetTempPath(), size: size); + + // Act + // Create options bag to fail and keep track of the failure. + DataTransferOptions options = new DataTransferOptions() + { + CreationPreference = StorageResourceCreationPreference.FailIfExists, + }; + TestEventsRaised testEventRaised = new TestEventsRaised(options); + LocalFilesStorageResourceProvider files = new(); + StorageResource sourceResource = files.FromFile(newSourceFile); + StorageResourceItem destinationResource = GetStorageResourceItem(objectClient); + TransferManager transferManager = new TransferManager(); + + // Start transfer and await for completion. + DataTransfer transfer = await transferManager.StartTransferAsync( + sourceResource, + destinationResource, + options); + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + await transfer.WaitForCompletionAsync(cancellationTokenSource.Token); + + // Assert + Assert.NotNull(transfer); + Assert.IsTrue(transfer.HasCompleted); + Assert.AreEqual(DataTransferState.Completed, transfer.TransferStatus.State); + Assert.AreEqual(true, transfer.TransferStatus.HasFailedItems); + Assert.IsTrue(await ExistsAsync(objectClient)); + await testEventRaised.AssertSingleFailedCheck(); + Assert.NotNull(testEventRaised.FailedEvents.First().Exception, "Excepted failure: Overwrite failure was supposed to be raised during the test"); + Assert.IsTrue(testEventRaised.FailedEvents.First().Exception.Message.Contains(_expectedOverwriteExceptionMessage)); + // Verify Upload - That we skipped over and didn't reupload something new. + using Stream stream = await OpenReadAsync(objectClient); + Assert.AreEqual(originalStream, stream); + } + + [RecordedTest] + [TestCase(0, 10)] + [TestCase(1000, 10)] + [TestCase(Constants.KB, 20)] + [TestCase(4 * Constants.KB, 20)] + public async Task LocalToRemoteObject_SmallSize(long size, int waitTimeInSec) + { + // Arrange + await using IDisposingContainer test = await GetDisposingContainerAsync(); + + await UploadResourceAndVerify( + container: test.Container, + size: size, + waitTimeInSec: waitTimeInSec); + } + + [Ignore("These tests currently take 40+ mins for little additional coverage")] + [Test] + [LiveOnly] + [TestCase(257 * Constants.MB, 600)] + [TestCase(500 * Constants.MB, 200)] + [TestCase(700 * Constants.MB, 200)] + [TestCase(Constants.GB, 1500)] + public async Task LocalToRemoteObject_LargeSize(long size, int waitTimeInSec) + { + // Arrange + await using IDisposingContainer test = await GetDisposingContainerAsync(); + + await UploadResourceAndVerify( + container: test.Container, + size: size, + waitTimeInSec: waitTimeInSec); + } + + [RecordedTest] + [TestCase(1, Constants.KB, 10)] + [TestCase(2, Constants.KB, 10)] + [TestCase(1, 4 * Constants.KB, 60)] + [TestCase(2, 4 * Constants.KB, 60)] + [TestCase(4, 16 * Constants.KB, 60)] + public async Task LocalToRemoteObject_SmallConcurrency(int concurrency, long size, int waitTimeInSec) + { + // Arrange + await using IDisposingContainer test = await GetDisposingContainerAsync(); + + TransferManagerOptions managerOptions = new TransferManagerOptions() + { + ErrorHandling = DataTransferErrorMode.ContinueOnFailure, + MaximumConcurrency = concurrency, + }; + + DataTransferOptions options = new DataTransferOptions() + { + InitialTransferSize = 512, + MaximumTransferChunkSize = 512, + }; + List optionsList = new List { options }; + + await UploadResourceAndVerify( + container: test.Container, + size: size, + waitTimeInSec: waitTimeInSec, + transferManagerOptions: managerOptions, + options: optionsList); + } + + [Ignore("These tests currently take 40+ mins for little additional coverage")] + [Test] + [LiveOnly] + [TestCase(1, 257 * Constants.MB, 200)] + [TestCase(4, 257 * Constants.MB, 200)] + [TestCase(16, 257 * Constants.MB, 200)] + [TestCase(16, Constants.GB, 200)] + [TestCase(32, Constants.GB, 200)] + public async Task LocalToRemoteObject_LargeConcurrency(int concurrency, int size, int waitTimeInSec) + { + // Arrange + await using IDisposingContainer test = await GetDisposingContainerAsync(); + + TransferManagerOptions managerOptions = new TransferManagerOptions() + { + ErrorHandling = DataTransferErrorMode.ContinueOnFailure, + MaximumConcurrency = concurrency, + }; + + await UploadResourceAndVerify( + container: test.Container, + size: size, + waitTimeInSec: waitTimeInSec, + transferManagerOptions: managerOptions); + } + + [Ignore("https://github.com/Azure/azure-sdk-for-net/issues/33003")] + [Test] + [LiveOnly] + [TestCase(2, 0, 30)] + [TestCase(2, Constants.KB, 30)] + [TestCase(6, Constants.KB, 30)] + [TestCase(32, Constants.KB, 30)] + [TestCase(2, 2 * Constants.KB, 30)] + [TestCase(6, 2 * Constants.KB, 30)] + public async Task LocalToRemoteObject_SmallMultiple(int objectCount, long size, int waitTimeInSec) + { + // Arrange + await using IDisposingContainer test = await GetDisposingContainerAsync(); + + await UploadResourceAndVerify( + container: test.Container, + size: size, + waitTimeInSec: waitTimeInSec, + objectCount: objectCount); + } + + [Ignore("These tests currently take 40+ mins for little additional coverage")] + [Test] + [LiveOnly] + [TestCase(2, 257 * Constants.MB, 400)] + [TestCase(6, 257 * Constants.MB, 400)] + [TestCase(2, Constants.GB, 1000)] + [TestCase(3, Constants.GB, 2000)] + public async Task LocalToRemoteObject_LargeMultiple(int objectCount, long size, int waitTimeInSec) + { + // Arrange + await using IDisposingContainer test = await GetDisposingContainerAsync(); + + await UploadResourceAndVerify( + container: test.Container, + size: size, + waitTimeInSec: waitTimeInSec, + objectCount: objectCount); + } +#endregion + } +} diff --git a/sdk/storage/Azure.Storage.DataMovement/tests/TestEventsRaised.cs b/sdk/storage/Azure.Storage.DataMovement/tests/Shared/TestEventsRaised.cs similarity index 95% rename from sdk/storage/Azure.Storage.DataMovement/tests/TestEventsRaised.cs rename to sdk/storage/Azure.Storage.DataMovement/tests/Shared/TestEventsRaised.cs index 608381172706..40b2988a956e 100644 --- a/sdk/storage/Azure.Storage.DataMovement/tests/TestEventsRaised.cs +++ b/sdk/storage/Azure.Storage.DataMovement/tests/Shared/TestEventsRaised.cs @@ -20,17 +20,17 @@ namespace Azure.Storage.DataMovement.Tests /// an Assert.Failure in the middle of an event. /// /// Also if there's multiple failures then we will catch all of them. - /// (Which would mainly occur during + /// Which would mainly occur during /// internal class TestEventsRaised : IDisposable { - private static readonly DataTransferStatus InProgressStatus = new DataTransferStatus(DataTransferState.InProgress, false, false); - private static readonly DataTransferStatus InProgressFailedStatus = new DataTransferStatus(DataTransferState.InProgress, true, false); - private static readonly DataTransferStatus InProgressSkippedStatus = new DataTransferStatus(DataTransferState.InProgress, false, true); - private static readonly DataTransferStatus StoppingFailedStatus = new DataTransferStatus(DataTransferState.Stopping, true, false); - private static readonly DataTransferStatus SuccessfulCompletedStatus = new DataTransferStatus(DataTransferState.Completed, false, false); - private static readonly DataTransferStatus SkippedCompletedStatus = new DataTransferStatus(DataTransferState.Completed, false, true); - private static readonly DataTransferStatus FailedCompletedStatus = new DataTransferStatus(DataTransferState.Completed, true, false); + private static readonly DataTransferStatus InProgressStatus = new DataTransferStatusInternal(DataTransferState.InProgress, false, false); + private static readonly DataTransferStatus InProgressFailedStatus = new DataTransferStatusInternal(DataTransferState.InProgress, true, false); + private static readonly DataTransferStatus InProgressSkippedStatus = new DataTransferStatusInternal(DataTransferState.InProgress, false, true); + private static readonly DataTransferStatus StoppingFailedStatus = new DataTransferStatusInternal(DataTransferState.Stopping, true, false); + private static readonly DataTransferStatus SuccessfulCompletedStatus = new DataTransferStatusInternal(DataTransferState.Completed, false, false); + private static readonly DataTransferStatus SkippedCompletedStatus = new DataTransferStatusInternal(DataTransferState.Completed, false, true); + private static readonly DataTransferStatus FailedCompletedStatus = new DataTransferStatusInternal(DataTransferState.Completed, true, false); public List FailedEvents { get; internal set; } public List StatusEvents { get; internal set; } @@ -320,7 +320,7 @@ public async Task AssertPausedCheck() AssertTransferStatusCollection( new DataTransferStatus[] { InProgressStatus, - new DataTransferStatus(DataTransferState.Paused, false, false) }, + new DataTransferStatusInternal(DataTransferState.Paused, false, false) }, StatusEvents.Select(e => e.TransferStatus).ToArray()); }