Skip to content

Commit a735ca7

Browse files
bluewwerich-wang
andauthored
[Storage] Support upload 4TB Azure file (Azure#13495)
* [Storage] Support upload 4TB Azure file * change Azure.Core version to 1.6 (#10) * Upgrade System.Buffers to 4.5 (#11) * change Azure.Core version to 1.6 * Upgrade System.Buffers to 4.5.0 * Add missing assemblity to exception for CI fail Co-authored-by: erich-wang <[email protected]>
1 parent 018ef81 commit a735ca7

File tree

16 files changed

+273
-32
lines changed

16 files changed

+273
-32
lines changed

src/Accounts/Authentication/Utilities/CustomAssemblyResolver.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public static class CustomAssemblyResolver
1010
private static IDictionary<string, Version> NetFxPreloadAssemblies =
1111
new Dictionary<string, Version>(StringComparer.InvariantCultureIgnoreCase)
1212
{
13-
{"Azure.Core", new Version("1.5.1.0")},
13+
{"Azure.Core", new Version("1.6.0.0")},
1414
{"Microsoft.Bcl.AsyncInterfaces", new Version("1.0.0.0")},
1515
{"Microsoft.Identity.Client", new Version("4.21.0.0") },
1616
{"Microsoft.Identity.Client.Extensions.Msal", new Version("2.16.2.0") },

src/DataLakeStore/DataLakeStore/DataLakeStore.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<PackageReference Include="Microsoft.Azure.DataLake.Store" Version="1.2.4-alpha" />
1616
<PackageReference Include="Microsoft.Azure.Management.DataLake.Store" Version="2.4.2-preview" />
1717
<PackageReference Include="NLog" Version="4.5.0" />
18-
<PackageReference Include="System.Buffers" Version="4.3.0" />
18+
<PackageReference Include="System.Buffers" Version="4.5.0" />
1919
<PackageReference Include="System.Net.Requests" Version="4.3.0" />
2020
</ItemGroup>
2121

src/Storage/Storage.Management.Test/Storage.Management.Test.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
</PropertyGroup>
1212

1313
<ItemGroup>
14-
<PackageReference Include="Azure.Storage.Blobs" Version="12.7.0-preview.1" />
15-
<PackageReference Include="Azure.Storage.Files.DataLake" Version="12.5.0-preview.1" />
16-
<PackageReference Include="Azure.Storage.Files.Shares" Version="12.5.0-preview.1" />
14+
<PackageReference Include="Azure.Storage.Blobs" Version="12.7.0" />
15+
<PackageReference Include="Azure.Storage.Files.DataLake" Version="12.5.0" />
16+
<PackageReference Include="Azure.Storage.Files.Shares" Version="12.5.0" />
1717
<PackageReference Include="Azure.Storage.Queues" Version="12.5.0-preview.1" />
1818
<PackageReference Include="Microsoft.Azure.Management.Storage" Version="17.2.0" />
1919
</ItemGroup>

src/Storage/Storage.Management/ChangeLog.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@
1818
- Additional information about change #1
1919
-->
2020
## Upcoming Release
21+
* Support upload Azure File size up to 4 TiB
22+
- `Set-AzStorageFileContent`
23+
* Upgraded Azure.Storage..Blobs to 12.7.0
24+
* Upgraded Azure.Storage.Files.Shares to 12.5.0
25+
* Upgraded Azure.Storage.Files.DataLake to 12.5.0
2126

2227
## Version 3.0.0
2328
* Removed obsolete property RestorePolicy.LastEnabledTime

src/Storage/Storage/Common/AzureStorageFile.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ public AzureStorageFile(CloudFile file, AzureStorageContext storageContext)
9292
}
9393

9494
// Convert Track1 File object to Track 2 file Client
95-
protected static ShareFileClient GetTrack2FileClient(CloudFile cloudFile, AzureStorageContext context)
95+
public static ShareFileClient GetTrack2FileClient(CloudFile cloudFile, AzureStorageContext context)
9696
{
9797
ShareFileClient fileClient;
9898
if (cloudFile.ServiceClient.Credentials.IsSAS) //SAS

src/Storage/Storage/Common/Util.cs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ namespace Microsoft.WindowsAzure.Commands.Storage.Common
2020
using Microsoft.Azure.Storage.File;
2121
using XTable = Microsoft.Azure.Cosmos.Table;
2222
using System;
23+
using System.IO;
2324
using System.Globalization;
2425
using System.Net;
2526
using System.Threading.Tasks;
@@ -29,6 +30,7 @@ namespace Microsoft.WindowsAzure.Commands.Storage.Common
2930
using System.Collections;
3031
using global::Azure.Storage.Blobs;
3132
using global::Azure.Storage;
33+
using global::Azure.Storage.Files.Shares.Models;
3234

3335
internal static class Util
3436
{
@@ -596,5 +598,86 @@ public static void ValidateUserDelegationKeyStartEndTime(DateTimeOffset userDele
596598
return null;
597599
}
598600
}
601+
602+
public static FileAttributes AzureFileNtfsAttributesToLocalAttributes(NtfsFileAttributes cloudFileNtfsAttributes)
603+
{
604+
FileAttributes fileAttributes = FileAttributes.Normal;
605+
606+
if ((cloudFileNtfsAttributes & NtfsFileAttributes.ReadOnly) == NtfsFileAttributes.ReadOnly)
607+
fileAttributes |= FileAttributes.ReadOnly;
608+
609+
if ((cloudFileNtfsAttributes & NtfsFileAttributes.Hidden) == NtfsFileAttributes.Hidden)
610+
fileAttributes |= FileAttributes.Hidden;
611+
612+
if ((cloudFileNtfsAttributes & NtfsFileAttributes.System) == NtfsFileAttributes.System)
613+
fileAttributes |= FileAttributes.System;
614+
615+
if ((cloudFileNtfsAttributes & NtfsFileAttributes.Directory) == NtfsFileAttributes.Directory)
616+
fileAttributes |= FileAttributes.Directory;
617+
618+
if ((cloudFileNtfsAttributes & NtfsFileAttributes.Archive) == NtfsFileAttributes.Archive)
619+
fileAttributes |= FileAttributes.Archive;
620+
621+
if ((cloudFileNtfsAttributes & NtfsFileAttributes.Temporary) == NtfsFileAttributes.Temporary)
622+
fileAttributes |= FileAttributes.Temporary;
623+
624+
if ((cloudFileNtfsAttributes & NtfsFileAttributes.Offline) == NtfsFileAttributes.Offline)
625+
fileAttributes |= FileAttributes.Offline;
626+
627+
if ((cloudFileNtfsAttributes & NtfsFileAttributes.NotContentIndexed) == NtfsFileAttributes.NotContentIndexed)
628+
fileAttributes |= FileAttributes.NotContentIndexed;
629+
630+
if ((cloudFileNtfsAttributes & NtfsFileAttributes.NoScrubData) == NtfsFileAttributes.NoScrubData)
631+
fileAttributes |= FileAttributes.NoScrubData;
632+
633+
if ((cloudFileNtfsAttributes & NtfsFileAttributes.None) == NtfsFileAttributes.None)
634+
{
635+
if (fileAttributes != FileAttributes.Normal)
636+
{
637+
fileAttributes = fileAttributes & (~FileAttributes.Normal);
638+
}
639+
}
640+
641+
return fileAttributes;
642+
}
643+
644+
public static NtfsFileAttributes LocalAttributesToAzureFileNtfsAttributes(FileAttributes fileAttributes)
645+
{
646+
NtfsFileAttributes cloudFileNtfsAttributes = NtfsFileAttributes.None;
647+
648+
if ((fileAttributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
649+
cloudFileNtfsAttributes |= NtfsFileAttributes.ReadOnly;
650+
651+
if ((fileAttributes & FileAttributes.Hidden) == FileAttributes.Hidden)
652+
cloudFileNtfsAttributes |= NtfsFileAttributes.Hidden;
653+
654+
if ((fileAttributes & FileAttributes.System) == FileAttributes.System)
655+
cloudFileNtfsAttributes |= NtfsFileAttributes.System;
656+
657+
if ((fileAttributes & FileAttributes.Directory) == FileAttributes.Directory)
658+
cloudFileNtfsAttributes |= NtfsFileAttributes.Directory;
659+
660+
if ((fileAttributes & FileAttributes.Archive) == FileAttributes.Archive)
661+
cloudFileNtfsAttributes |= NtfsFileAttributes.Archive;
662+
663+
if ((fileAttributes & FileAttributes.Normal) == FileAttributes.Normal)
664+
cloudFileNtfsAttributes |= NtfsFileAttributes.None;
665+
666+
if ((fileAttributes & FileAttributes.Temporary) == FileAttributes.Temporary)
667+
cloudFileNtfsAttributes |= NtfsFileAttributes.Temporary;
668+
669+
if ((fileAttributes & FileAttributes.Offline) == FileAttributes.Offline)
670+
cloudFileNtfsAttributes |= NtfsFileAttributes.Offline;
671+
672+
if ((fileAttributes & FileAttributes.NotContentIndexed) == FileAttributes.NotContentIndexed)
673+
cloudFileNtfsAttributes |= NtfsFileAttributes.NotContentIndexed;
674+
675+
if ((fileAttributes & FileAttributes.NoScrubData) == FileAttributes.NoScrubData)
676+
cloudFileNtfsAttributes |= NtfsFileAttributes.NoScrubData;
677+
678+
if (cloudFileNtfsAttributes != NtfsFileAttributes.None) cloudFileNtfsAttributes &= (~NtfsFileAttributes.None);
679+
680+
return cloudFileNtfsAttributes;
681+
}
599682
}
600683
}

src/Storage/Storage/File/Cmdlet/SetAzureStorageFileContent.cs

Lines changed: 164 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,26 @@
1414

1515
namespace Microsoft.WindowsAzure.Commands.Storage.File.Cmdlet
1616
{
17+
using global::Azure;
18+
using global::Azure.Storage.Files.Shares;
19+
using global::Azure.Storage.Files.Shares.Models;
20+
using Microsoft.Azure.Storage;
21+
using Microsoft.Azure.Storage.DataMovement;
22+
using Microsoft.Azure.Storage.File;
1723
using Microsoft.WindowsAzure.Commands.Common;
24+
using Microsoft.WindowsAzure.Commands.Common.Storage.ResourceModel;
1825
using Microsoft.WindowsAzure.Commands.Storage.Common;
1926
using Microsoft.WindowsAzure.Commands.Utilities.Common;
20-
using Microsoft.Azure.Storage;
21-
using Microsoft.Azure.Storage.File;
2227
using System;
28+
using System.Collections.Generic;
2329
using System.Globalization;
2430
using System.IO;
2531
using System.Management.Automation;
2632
using System.Net;
33+
using System.Runtime.InteropServices;
34+
using System.Security.Cryptography;
2735
using System.Threading.Tasks;
2836
using LocalConstants = Microsoft.WindowsAzure.Commands.Storage.File.Constants;
29-
using System.Runtime.InteropServices;
30-
using Microsoft.Azure.Storage.DataMovement;
31-
using Microsoft.WindowsAzure.Commands.Common.CustomAttributes;
32-
using Microsoft.WindowsAzure.Commands.Common.Storage.ResourceModel;
3337

3438
[Cmdlet("Set", Azure.Commands.ResourceManager.Common.AzureRMConstants.AzurePrefix + "StorageFileContent", SupportsShouldProcess = true, DefaultParameterSetName = LocalConstants.ShareNameParameterSetName), OutputType(typeof(AzureStorageFile))]
3539
public class SetAzureStorageFileContent : StorageFileDataManagementCmdletBase, IDynamicParameters
@@ -110,11 +114,19 @@ public override void ExecuteCmdlet()
110114
{
111115
throw new FileNotFoundException(string.Format(CultureInfo.CurrentCulture, Resources.SourceFileNotFound, this.Source));
112116
}
117+
long fileSize = localFile.Length;
113118

114-
// if FIPS policy is enabled, must use native MD5
119+
// if FIPS policy is enabled, must use native MD5 for DMlib.
115120
if (fipsEnabled)
116121
{
117-
CloudStorageAccount.UseV1MD5 = false;
122+
if (fileSize < sizeTB)
123+
{
124+
CloudStorageAccount.UseV1MD5 = false;
125+
}
126+
else // use Track2 SDK
127+
{
128+
WriteWarning("The uploaded file won't have Content MD5 hash, since caculate MD5 hash fail, most possiblly caused by FIPS is enabled on this machine.");
129+
}
118130
}
119131

120132
bool isDirectory;
@@ -133,19 +145,141 @@ public override void ExecuteCmdlet()
133145
cloudFileToBeUploaded.GetFullPath(), cloudFileToBeUploaded.Share.Name),
134146
Resources.PrepareUploadingFile);
135147

136-
await DataMovementTransferHelper.DoTransfer(() =>
137-
this.TransferManager.UploadAsync(
138-
localFile.FullName,
139-
cloudFileToBeUploaded,
140-
new UploadOptions
148+
if (fileSize <= sizeTB)
149+
{
150+
151+
152+
await DataMovementTransferHelper.DoTransfer(() =>
153+
this.TransferManager.UploadAsync(
154+
localFile.FullName,
155+
cloudFileToBeUploaded,
156+
new UploadOptions
157+
{
158+
PreserveSMBAttributes = context is null ? false : context.PreserveSMBAttribute.IsPresent
159+
},
160+
this.GetTransferContext(progressRecord, localFile.Length),
161+
this.CmdletCancellationToken),
162+
progressRecord,
163+
this.OutputStream).ConfigureAwait(false);
164+
}
165+
else // use Track2 SDK
166+
{
167+
//Create File
168+
ShareFileClient fileClient = AzureStorageFile.GetTrack2FileClient(cloudFileToBeUploaded, Channel.StorageContext);
169+
170+
// confirm overwrite if file exist
171+
if(!this.Force.IsPresent &&
172+
fileClient.Exists(this.CmdletCancellationToken) &&
173+
!await this.OutputStream.ConfirmAsync(string.Format(CultureInfo.CurrentCulture, Resources.OverwriteConfirmation, Util.ConvertToString(cloudFileToBeUploaded))))
174+
{
175+
return;
176+
}
177+
178+
await fileClient.CreateAsync(fileSize, cancellationToken: this.CmdletCancellationToken).ConfigureAwait(false);
179+
180+
//Prepare progress Handler
181+
IProgress<long> progressHandler = new Progress<long>((finishedBytes) =>
182+
{
183+
if (progressRecord != null)
184+
{
185+
// Size of the source file might be 0, when it is, directly treat the progress as 100 percent.
186+
progressRecord.PercentComplete = 0 == fileSize ? 100 : (int)(finishedBytes * 100 / fileSize);
187+
progressRecord.StatusDescription = string.Format(CultureInfo.CurrentCulture, Resources.FileTransmitStatus, progressRecord.PercentComplete);
188+
this.OutputStream.WriteProgress(progressRecord);
189+
}
190+
});
191+
192+
long blockSize = 4 * 1024 * 1024;
193+
int maxWorkers = 4;
194+
195+
List<Task> runningTasks = new List<Task>();
196+
197+
IncrementalHash hash = null;
198+
if (!fipsEnabled)
199+
{
200+
hash = IncrementalHash.CreateHash(HashAlgorithmName.MD5);
201+
}
202+
203+
using (FileStream stream = File.OpenRead(localFile.FullName))
204+
{
205+
byte[] buffer = null;
206+
long lastBlockSize = 0;
207+
for (long offset = 0; offset < fileSize; offset += blockSize)
208+
{
209+
long currentBlockSize = offset + blockSize < fileSize ? blockSize : fileSize - offset;
210+
211+
// Only need to create new buffer when chunk size change
212+
if (currentBlockSize != lastBlockSize)
213+
{
214+
buffer = new byte[currentBlockSize];
215+
lastBlockSize = currentBlockSize;
216+
}
217+
await stream.ReadAsync(buffer: buffer, offset: 0, count: (int)currentBlockSize);
218+
if (!fipsEnabled && hash != null)
219+
{
220+
hash.AppendData(buffer);
221+
}
222+
223+
Task task = UploadFileRangAsync(fileClient,
224+
new HttpRange(offset, currentBlockSize),
225+
new MemoryStream(buffer),
226+
progressHandler);
227+
runningTasks.Add(task);
228+
229+
// Check if any of upload range tasks are still busy
230+
if (runningTasks.Count >= maxWorkers)
231+
{
232+
await Task.WhenAny(runningTasks).ConfigureAwait(false);
233+
234+
// Clear any completed blocks from the task list
235+
for (int i = 0; i < runningTasks.Count; i++)
236+
{
237+
Task runningTask = runningTasks[i];
238+
if (!runningTask.IsCompleted)
239+
{
240+
continue;
241+
}
242+
243+
await runningTask.ConfigureAwait(false);
244+
runningTasks.RemoveAt(i);
245+
i--;
246+
}
247+
}
248+
}
249+
// Wait for all upload range tasks finished
250+
await Task.WhenAll(runningTasks).ConfigureAwait(false);
251+
}
252+
253+
// Need set file properties
254+
if ((!fipsEnabled && hash != null) || (context != null && context.PreserveSMBAttribute.IsPresent))
255+
{
256+
ShareFileHttpHeaders header = null;
257+
if (!fipsEnabled && hash != null)
258+
{
259+
header = new ShareFileHttpHeaders();
260+
header.ContentHash = hash.GetHashAndReset();
261+
}
262+
263+
FileSmbProperties smbProperties = null;
264+
if (context != null && context.PreserveSMBAttribute.IsPresent)
141265
{
142-
PreserveSMBAttributes = context is null ? false : context.PreserveSMBAttribute.IsPresent
143-
},
144-
this.GetTransferContext(progressRecord, localFile.Length),
145-
this.CmdletCancellationToken),
146-
progressRecord,
147-
this.OutputStream).ConfigureAwait(false);
266+
FileInfo sourceFileInfo = new FileInfo(localFile.FullName);
267+
smbProperties = new FileSmbProperties();
268+
smbProperties.FileCreatedOn = sourceFileInfo.CreationTimeUtc;
269+
smbProperties.FileLastWrittenOn = sourceFileInfo.LastWriteTimeUtc;
270+
smbProperties.FileAttributes = Util.LocalAttributesToAzureFileNtfsAttributes(File.GetAttributes(localFile.FullName));
271+
}
148272

273+
// set file header and attributes to the file
274+
fileClient.SetHttpHeaders(httpHeaders: header, smbProperties: smbProperties);
275+
}
276+
277+
if (this.PassThru)
278+
{
279+
// fetch latest file properties for output
280+
cloudFileToBeUploaded.FetchAttributes();
281+
}
282+
}
149283

150284
if (this.PassThru)
151285
{
@@ -160,6 +294,17 @@ await DataMovementTransferHelper.DoTransfer(() =>
160294
}
161295
}
162296

297+
private long Finishedbytes = 0;
298+
private async Task UploadFileRangAsync(ShareFileClient file, HttpRange range, Stream content, IProgress<long> progressHandler = null)
299+
{
300+
await file.UploadRangeAsync(
301+
range,
302+
content,
303+
cancellationToken: this.CmdletCancellationToken).ConfigureAwait(false);
304+
Finishedbytes += range.Length is null? 0 : range.Length.Value;
305+
progressHandler.Report(Finishedbytes);
306+
}
307+
163308
private async Task<CloudFile> BuildCloudFileInstanceFromPathAsync(string defaultFileName, string[] path, bool pathIsDirectory)
164309
{
165310
CloudFileDirectory baseDirectory = null;

0 commit comments

Comments
 (0)