Search code examples
c#azurecopyhttp-status-code-404azure-blob-storage

Azure CloudBlob StartCopy method between 2 storage accounts - 404 Error


So I've been going through the https://learn.microsoft.com/en-us/learn/modules/copy-blobs-from-command-line-and-code/7-move-blobs-using-net-storage-client tutorial and am trying to copy blobs from one storage account to another storage account (both accounts are run locally on my computer as a local development environment emulator via Azurite). Trying to learn more about Azure SDK with C#.

I am able to connect to both accounts no problem and my program so far can upload/download blob files and create containers. However, it appears that when I execute my program in Visual Studio, it would reach the line that contains StartCopy (or StartCopyAsync) and throw a web exception: 404 Not Found error. Within the exception, when I expand the RequestInformation, the ErrorCode is "ContainerNotFound" and the HttpStatusMessage is "The specified container does not exist." Not sure why I am getting those messages since I can verify that the containers exist and just for heck of it, I prepopulated the source and destination azure storages with the files and containers beforehand just to see what happened. Unfortunately, the same error. I then tried setting the public accesss level of the container and blobs to be public. Unfortunately, that was a no go as well. What should happen is that it should just copy the blob file from source to destination and that would be the end of that.

At first, I thought maybe I didn't configure something right but that doesn't seem to be the case or at least I thought? Then I tried copying files between 1 local storage account emulator to an actual online Azure storage (student) account thru Microsoft and that did not work as well. I was able to copy between 2 containers within the same account so that part wasn't an issue but trying to copy between 2 different accounts has been making me "banging my head for a few hours now".

Code should be quite similar to how it was done in Microsoft's tutorial as mentioned in the first link in this post. Not exactly sure why I keep getting this 404 error. Turned off my firewall temporarily in case that was causing trouble but that did not make a difference. I also tried someone else's code (though had to modify it slightly to work in my case) and I'm still running into the same issue. So perhaps, maybe there is something wrong with my setup here?

In the console of my Azurite local servers, I can see server 2 (destination server for the Copy process) has:

Local Source Server

127.0.0.1 - - [11/Jan/2021:23:28:39 +0000] "PUT /devstoreaccount1/source/NewBlob HTTP/1.1" 201 -
127.0.0.1 - - [11/Jan/2021:23:28:39 +0000] "HEAD /devstoreaccount1/source/NewBlob HTTP/1.1" 200 0

Local Destination Server

127.0.0.1 - - [11/Jan/2021:23:28:39 +0000] "PUT /devstoreaccount1/destination/NewBlob HTTP/1.1" 404 -

Code:

        static async Task Main(string[] args)
        {
            string storageKey = "AccountName=devstoreaccount1;AccountKey=<MY_ACCOUNT_KEY_GOES_HERE>;DefaultEndpointsProtocol=http;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;QueueEndpoint=http://127.0.0.1:10001/devstoreaccount1;";

            string storageKey2 = "AccountName=devstoreaccount1;AccountKey=<MY_ACCOUNT_KEY_GOES_HERE>;DefaultEndpointsProtocol=http;BlobEndpoint=http://127.0.0.1:10100/devstoreaccount1;QueueEndpoint=http://127.0.0.1:10101/devstoreaccount1;";

            // Retrieve storage account from connection string.
            CloudStorageAccount storageAccount = CloudStorageAccount.Parse(storageKey);
            CloudStorageAccount storageAccount2 = CloudStorageAccount.Parse(storageKey2);

            //create client from storage account
            CloudBlobClient sourceClient = storageAccount.CreateCloudBlobClient();
            CloudBlobClient sourceClient2 = storageAccount2.CreateCloudBlobClient();

            //declare source container and create if doesn't exist
            CloudBlobContainer sourceContainer = sourceClient.GetContainerReference("source");
            await sourceContainer.CreateIfNotExistsAsync();

            //upload local file to azurite 1
            CloudBlockBlob uploadBlob = sourceContainer.GetBlockBlobReference("NewBlob");
            await uploadBlob.UploadFromFileAsync("NewBlob");

            // designate source blob as the new uploaded file
            ICloudBlob sourceBlob = await sourceContainer.GetBlobReferenceFromServerAsync("NewBlob");

            //declare destination container and create it if it doesn't exist
            CloudBlobContainer destBlobContainer = sourceClient2.GetContainerReference("destination");
            await destBlobContainer.CreateIfNotExistsAsync();

            //start copy process of blob between 2 servers
            CloudBlockBlob destBlob = destBlobContainer.GetBlockBlobReference(sourceBlob.Name);
            await destBlob.StartCopyAsync(new Uri(GetSharedAccessUri(sourceBlob.Name, sourceContainer)));

            // Display the status of the blob as it is copied
            ICloudBlob destBlobRef = await destBlobContainer.GetBlobReferenceFromServerAsync(destBlob.Name);
            while (destBlobRef.CopyState.Status == CopyStatus.Pending)
            {
                Console.WriteLine($"Blob: {destBlobRef.Name}, Copied: {destBlobRef.CopyState.BytesCopied ?? 0} of  {destBlobRef.CopyState.TotalBytes ?? 0}");
                await Task.Delay(500);
                destBlobRef = await destBlobContainer.GetBlobReferenceFromServerAsync(destBlobRef.Name);
            }
            Console.WriteLine($"Blob: {destBlob.Name} Complete");

        }

        // Create a SAS token for the source blob, to enable it to be read by the StartCopyAsync method
        private static string GetSharedAccessUri(string blobName, CloudBlobContainer container)
        {
            DateTime toDateTime = DateTime.Now.AddMinutes(60);

            SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy
            {
                Permissions = SharedAccessBlobPermissions.Read,
                SharedAccessStartTime = null,
                SharedAccessExpiryTime = new DateTimeOffset(toDateTime)
            };

            CloudBlockBlob blob = container.GetBlockBlobReference(blobName);
            string sas = blob.GetSharedAccessSignature(policy);

            return blob.Uri.AbsoluteUri + sas;
        }


Thanks in advance!

Solution

  • Your code above works on Azure Cloud perfectly and I also tested your code on my local based on Storage Emulator too. I can't find out the root reason based on your code logic, it is all right.

    Anyway, seems your issue has been solved on Azure Cloud, I assume this is due to some bug of the local storage emulator.