azure-sdk-for-net icon indicating copy to clipboard operation
azure-sdk-for-net copied to clipboard

[BUG] Ambiguous behavior downloading blobs using conditions between v11 and v12

Open lumalav opened this issue 2 years ago • 7 comments

Library name and version

Azure.Storage.Blobs 12.13.1

Describe the bug

Downloading a blob using the SDK v11, throws a StorageException when the blob exists and a condition is passed like this:

blob.DownloadToStream(memStream2, AccessCondition.GenerateIfNoneMatchCondition(eTag));
//or
blob.DownloadTo(filePath, AccessCondition.GenerateIfNoneMatchCondition(eTag));

However, in v12. Two things are happening:

  1. No exception is thrown (was expecting RequestFailedException)
  2. The Stream/File is empty
blob.DownloadTo(memStream2, conditions: new BlobRequestConditions
{
    IfNoneMatch = new ETag(eTag),
});
//or
blob.DownloadTo(filePath, conditions: new BlobRequestConditions
{
    IfNoneMatch = new ETag(eTag),
});

Expected behavior

v12 version should throw a RequestFailedException where ErrorCode == BlobErrorCode.ConditionNotMet

Actual behavior

  1. No exception is thrown (was expecting RequestFailedException)
  2. The Stream/File is empty

Reproduction Steps

//v11 test

//storageAcct is a valid instance of CloudStorageAccount
var blobClient = storageAcct.CreateCloudBlobClient();

var container = "test";
var blobContainer = blobClient.GetContainerReference(container);
blobContainer.CreateIfNotExists(BlobContainerPublicAccessType.Off);

var fileName = "TestFile.txt";

using(Stream fileStream = new FileStream(@"..\..\" + fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
{
    CloudBlockBlob blob = blobContainer.GetBlockBlobReference(fileName);
    blob.UploadFromStream(fileStream, AccessCondition.GenerateIfNotExistsCondition());
}

string eTag;
using(MemoryStream memStream = new MemoryStream())
{
    CloudBlockBlob blob = blobContainer.GetBlockBlobReference(fileName);
    blob.DownloadToStream(memStream, AccessCondition.GenerateIfExistsCondition());
    eTag = blob.Properties.ETag;
    Assert.IsFalse(string.IsNullOrWhiteSpace(eTag), "ETag not provided");
}

using(MemoryStream memStream2 = new MemoryStream())
{
    CloudBlockBlob blob = blobContainer.GetBlockBlobReference(fileName);
    //the following line throws a StorageException ex 
    //where -> ex.RequestInformation.HttpStatusCode == (int)System.Net.HttpStatusCode.NotModified
    //this behavior is also confirmed in blob.DownloadTo(filePath)
    blob.DownloadToStream(memStream2, AccessCondition.GenerateIfNoneMatchCondition(eTag));
}

//v12 test

var container = "test";
//service is a valid instance of BlobServiceClient
var blobContainerClient = service.GetBlobContainerClient(container);
blobContainerClient.CreateIfNotExists(AzureStorage.Models.PublicAccessType.None);

var fileName = "TestFile.txt";

using(Stream fileStream = new FileStream(@"..\..\" + fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
{
    BlobClient blob = blobContainerClient.GetBlobClient(fileName);
    blob.Upload(fileData);
}

string eTag;
using(MemoryStream memStream = new MemoryStream())
{
    BlobClient blob = blobContainerClient.GetBlobClient(fileName);
    blob.DownloadTo(memStream);
    eTag = blob.GetProperties().ETag.ToString();
    Assert.IsFalse(string.IsNullOrWhiteSpace(eTag), "ETag not provided");
}

using(MemoryStream memStream2 = new MemoryStream())
{
    BlobClient blob = blobContainerClient.GetBlobClient(fileName);	
    //the following line does NOT throw a RequestFailedException
    //however the stream is completely empty (memStream2.Length == 0)
    //this was also confirmed with DownloadTo(filePath) => file is completely empty
    blob.DownloadTo(memStream2, conditions: new BlobRequestConditions
    {
	IfNoneMatch = new ETag(eTag),
    });
}

UPDATE:

I believe the bug is here. That case is not valid, the error should throw and not return an empty Stream

Environment

Hosting Platform and .NET runtime version: Windows 11 .NET Framework 4.6.1 IDE and version: Visual Studio 2019

lumalav avatar Aug 25 '22 14:08 lumalav

Thank you for your feedback. This has been routed to the support team for assistance.

ghost avatar Aug 25 '22 16:08 ghost

I also see the same problem.

cholt0425 avatar Aug 26 '22 16:08 cholt0425

Thanks for this information. It actually assisted a lot to what's going on.

So you are correct, I believe a 304 is being thrown because it's comparing the Etags and running into an error. However let me reconfirm if this is the correct response to do in terms of passing the 304. Because looks like we should check the BlobErrorCode if it is Not-Modified.

https://github.com/Azure/azure-sdk-for-net/blob/3724ce24abd58311ec7846661870213b5f826911/sdk/storage/Azure.Storage.Blobs/src/PartitionedDownloader.cs#L162

Looks like normally from the service we're expecting a 412 for the modified during in flight.

amnguye avatar Sep 09 '22 20:09 amnguye

Hi @amnguye, any news on this?

lumalav avatar Sep 19 '22 14:09 lumalav

Looks like the 304 is intentional considering the If-None-Match conditional header.

Related/Duplicate(kinda) https://github.com/Azure/azure-sdk-for-net/issues/13414#issuecomment-657816714

3xx have to be checked, that's why no RequestFailedException is not thrown. It would be a breaking change to throw a 304 error.

amnguye avatar Sep 28 '22 21:09 amnguye

Adding Service team to look into this

navba-MSFT avatar Nov 11 '22 04:11 navba-MSFT

Thanks for the feedback! We are routing this to the appropriate team for follow-up. cc @xgithubtriage.

Issue Details

Library name and version

Azure.Storage.Blobs 12.13.1

Describe the bug

Downloading a blob using the SDK v11, throws a StorageException when the blob exists and a condition is passed like this:

blob.DownloadToStream(memStream2, AccessCondition.GenerateIfNoneMatchCondition(eTag));
//or
blob.DownloadTo(filePath, AccessCondition.GenerateIfNoneMatchCondition(eTag));

However, in v12. Two things are happening:

  1. No exception is thrown (was expecting RequestFailedException)
  2. The Stream/File is empty
blob.DownloadTo(memStream2, conditions: new BlobRequestConditions
{
    IfNoneMatch = new ETag(eTag),
});
//or
blob.DownloadTo(filePath, conditions: new BlobRequestConditions
{
    IfNoneMatch = new ETag(eTag),
});

Expected behavior

v12 version should throw a RequestFailedException where ErrorCode == BlobErrorCode.ConditionNotMet

Actual behavior

  1. No exception is thrown (was expecting RequestFailedException)
  2. The Stream/File is empty

Reproduction Steps

//v11 test

//storageAcct is a valid instance of CloudStorageAccount
var blobClient = storageAcct.CreateCloudBlobClient();

var container = "test";
var blobContainer = blobClient.GetContainerReference(container);
blobContainer.CreateIfNotExists(BlobContainerPublicAccessType.Off);

var fileName = "TestFile.txt";

using(Stream fileStream = new FileStream(@"..\..\" + fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
{
    CloudBlockBlob blob = blobContainer.GetBlockBlobReference(fileName);
    blob.UploadFromStream(fileStream, AccessCondition.GenerateIfNotExistsCondition());
}

string eTag;
using(MemoryStream memStream = new MemoryStream())
{
    CloudBlockBlob blob = blobContainer.GetBlockBlobReference(fileName);
    blob.DownloadToStream(memStream, AccessCondition.GenerateIfExistsCondition());
    eTag = blob.Properties.ETag;
    Assert.IsFalse(string.IsNullOrWhiteSpace(eTag), "ETag not provided");
}

using(MemoryStream memStream2 = new MemoryStream())
{
    CloudBlockBlob blob = blobContainer.GetBlockBlobReference(fileName);
    //the following line throws a StorageException ex 
    //where -> ex.RequestInformation.HttpStatusCode == (int)System.Net.HttpStatusCode.NotModified
    //this behavior is also confirmed in blob.DownloadTo(filePath)
    blob.DownloadToStream(memStream2, AccessCondition.GenerateIfNoneMatchCondition(eTag));
}

//v12 test

var container = "test";
//service is a valid instance of BlobServiceClient
var blobContainerClient = service.GetBlobContainerClient(container);
blobContainerClient.CreateIfNotExists(AzureStorage.Models.PublicAccessType.None);

var fileName = "TestFile.txt";

using(Stream fileStream = new FileStream(@"..\..\" + fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
{
    BlobClient blob = blobContainerClient.GetBlobClient(fileName);
    blob.Upload(fileData);
}

string eTag;
using(MemoryStream memStream = new MemoryStream())
{
    BlobClient blob = blobContainerClient.GetBlobClient(fileName);
    blob.DownloadTo(memStream);
    eTag = blob.GetProperties().ETag.ToString();
    Assert.IsFalse(string.IsNullOrWhiteSpace(eTag), "ETag not provided");
}

using(MemoryStream memStream2 = new MemoryStream())
{
    BlobClient blob = blobContainerClient.GetBlobClient(fileName);	
    //the following line does NOT throw a RequestFailedException
    //however the stream is completely empty (memStream2.Length == 0)
    //this was also confirmed with DownloadTo(filePath) => file is completely empty
    blob.DownloadTo(memStream2, conditions: new BlobRequestConditions
    {
	IfNoneMatch = new ETag(eTag),
    });
}

UPDATE:

I believe the bug is here. That case is not valid, the error should throw and not return an empty Stream

Environment

Hosting Platform and .NET runtime version: Windows 11 .NET Framework 4.6.1 IDE and version: Visual Studio 2019

Author: lumalav
Assignees: -
Labels:

Storage, Service Attention, Client, customer-reported, question, needs-team-attention

Milestone: -

ghost avatar Nov 11 '22 04:11 ghost