Skip to content

Commit 3732c0b

Browse files
committed
Add OpenWriteAsync to ISimpleCloudBlob to allow streaming writes (#7056)
Progress on #6475
1 parent ce221a0 commit 3732c0b

3 files changed

Lines changed: 114 additions & 0 deletions

File tree

src/NuGetGallery.Core/Services/CloudBlobWrapper.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ public async Task<Stream> OpenReadAsync(AccessCondition accessCondition)
5454
operationContext: null);
5555
}
5656

57+
public async Task<Stream> OpenWriteAsync(AccessCondition accessCondition)
58+
{
59+
return await _blob.OpenWriteAsync(
60+
accessCondition: accessCondition,
61+
options: null,
62+
operationContext: null);
63+
}
64+
5765
public async Task DeleteIfExistsAsync()
5866
{
5967
await _blob.DeleteIfExistsAsync(

src/NuGetGallery.Core/Services/ISimpleCloudBlob.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public interface ISimpleCloudBlob
2222
string ETag { get; }
2323

2424
Task<Stream> OpenReadAsync(AccessCondition accessCondition);
25+
Task<Stream> OpenWriteAsync(AccessCondition accessCondition);
2526

2627
Task DeleteIfExistsAsync();
2728
Task DownloadToStreamAsync(Stream target);

tests/NuGetGallery.Core.Facts/Services/CloudBlobCoreFileStorageServiceIntegrationTests.cs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.Concurrent;
66
using System.IO;
77
using System.Net;
8+
using System.Security.Cryptography;
89
using System.Text;
910
using System.Threading;
1011
using System.Threading.Tasks;
@@ -59,6 +60,110 @@ public CloudBlobCoreFileStorageServiceIntegrationTests(BlobStorageFixture fixtur
5960
_targetB = new CloudBlobCoreFileStorageService(_clientB, Mock.Of<IDiagnosticsService>());
6061
}
6162

63+
[BlobStorageFact]
64+
public async Task OpenWriteAsyncReturnsWritableStream()
65+
{
66+
// Arrange
67+
var folderName = CoreConstants.Folders.ValidationFolderName;
68+
var fileName = _prefixA;
69+
var expectedContent = "Hello, world.";
70+
var bytes = Encoding.UTF8.GetBytes(expectedContent);
71+
string expectedContentMD5;
72+
using (var md5 = MD5.Create())
73+
{
74+
expectedContentMD5 = Convert.ToBase64String(md5.ComputeHash(bytes));
75+
}
76+
77+
var container = _clientA.GetContainerReference(folderName);
78+
var file = container.GetBlobReference(fileName);
79+
80+
// Act
81+
using (var stream = await file.OpenWriteAsync(accessCondition: null))
82+
{
83+
await stream.WriteAsync(bytes, 0, bytes.Length);
84+
}
85+
86+
// Assert
87+
// Reinitialize the blob to verify the metadata is fresh.
88+
file = container.GetBlobReference(fileName);
89+
using (var memoryStream = new MemoryStream())
90+
{
91+
await file.DownloadToStreamAsync(memoryStream);
92+
var actualContent = Encoding.ASCII.GetString(memoryStream.ToArray());
93+
Assert.Equal(expectedContent, actualContent);
94+
95+
Assert.NotNull(file.ETag);
96+
Assert.NotEmpty(file.ETag);
97+
Assert.Equal(expectedContentMD5, file.Properties.ContentMD5);
98+
}
99+
}
100+
101+
[BlobStorageFact]
102+
public async Task OpenWriteAsyncRejectsETagMismatchFoundBeforeUploadStarts()
103+
{
104+
// Arrange
105+
var folderName = CoreConstants.Folders.ValidationFolderName;
106+
var fileName = _prefixA;
107+
var expectedContent = "Hello, world.";
108+
109+
await _targetA.SaveFileAsync(
110+
folderName,
111+
fileName,
112+
new MemoryStream(Encoding.ASCII.GetBytes(expectedContent)),
113+
overwrite: false);
114+
115+
var container = _clientA.GetContainerReference(folderName);
116+
var file = container.GetBlobReference(fileName);
117+
118+
// Act & Assert
119+
var ex = await Assert.ThrowsAsync<StorageException>(
120+
async () =>
121+
{
122+
using (var stream = await file.OpenWriteAsync(AccessCondition.GenerateIfNotExistsCondition()))
123+
{
124+
await stream.WriteAsync(new byte[0], 0, 0);
125+
}
126+
});
127+
Assert.Equal(HttpStatusCode.Conflict, (HttpStatusCode)ex.RequestInformation.HttpStatusCode);
128+
}
129+
130+
[BlobStorageFact]
131+
public async Task OpenWriteAsyncRejectsETagMismatchFoundAfterUploadStarts()
132+
{
133+
// Arrange
134+
var folderName = CoreConstants.Folders.ValidationFolderName;
135+
var fileName = _prefixA;
136+
var expectedContent = "Hello, world.";
137+
138+
var container = _clientA.GetContainerReference(folderName);
139+
var file = container.GetBlobReference(fileName);
140+
var writeCount = 0;
141+
142+
// Act & Assert
143+
var ex = await Assert.ThrowsAsync<StorageException>(
144+
async () =>
145+
{
146+
using (var stream = await file.OpenWriteAsync(AccessCondition.GenerateIfNotExistsCondition()))
147+
{
148+
stream.Write(new byte[1], 0, 1);
149+
await stream.FlushAsync();
150+
writeCount++;
151+
152+
await _targetA.SaveFileAsync(
153+
folderName,
154+
fileName,
155+
new MemoryStream(Encoding.ASCII.GetBytes(expectedContent)),
156+
overwrite: false);
157+
158+
stream.Write(new byte[1], 0, 1);
159+
await stream.FlushAsync();
160+
writeCount++;
161+
}
162+
});
163+
Assert.Equal(HttpStatusCode.Conflict, (HttpStatusCode)ex.RequestInformation.HttpStatusCode);
164+
Assert.Equal(2, writeCount);
165+
}
166+
62167
[BlobStorageFact]
63168
public async Task OpenReadAsyncReturnsReadableStreamWhenBlobExistsAndPopulatesProperties()
64169
{

0 commit comments

Comments
 (0)