1
votes

I am getting a

403 Forbidden

error when copying a large number of block blobs from one storage account to another storage account (in a different region as a backup). After 100,000+ are copied I get a 403 Forbidden error.

I have seen answers talking about a Quota but I believe that is for free accounts. I have a client with a 578,000 files that I moved to Azure from local and that worked fine but I cannot make a copy to another storage account I set up to act as a backup (in case of deletions mostly).

I am using StartCopyAsync and then checking the Copystate status to verify the copy succeeded and retrying in my code but it appears to be failing on the StartCopyAsync.

The copy works fine until I have copied well over 100,000 of the files, then the error occurs. I am not sure what is causing that since the same code works fine for so many blobs first. I have added a log file that told me which file it failed on and I can open that file in Azure explorer.

I can post the code but right now, I am wondering if I am hitting some sort of quote/bandwidth issue I do not know about.

    namespace BackupCloudContainers
    {

    class Program
    {
        static string privateconnectionstring = ConfigurationManager.AppSettings["StorageConnectionString"];
        static string privatebackupconnectionstring = ConfigurationManager.AppSettings["BackupStorageConnectionString"];
        static DateTime testdate = new DateTime(2017, 8, 28, 0, 0, 0);
        static string destContainerName = "";
        static void Main(string[] args)
        {
            try
            {
                //Console.WriteLine("Starting Backup at " + DateTime.Now.ToString("hh:mm:ss.ffff"));
                Log("Starting Incremental Backup (everything since " + testdate.ToString("f") + ") at " + DateTime.Now.ToString("hh:mm:ss.ffff"));
                Backup().GetAwaiter().GetResult();
                // Console.WriteLine("Backup Created as " + destContainerName);
                Log("Backup Created as " + destContainerName);
                //Console.WriteLine("Backup ended at " + DateTime.Now.ToString("hh:mm:ss.ffff"));
                Log("Backup ended at " + DateTime.Now.ToString("hh:mm:ss.ffff"));
                Console.WriteLine("\n\nPress Enter to close. ");
                Console.ReadLine();
            }
            catch (Exception e)
            {
                //Console.WriteLine("Exception - " + e.Message);
                Log("Exception - " + e.Message);
                if (e.InnerException != null)
                {
                    //Console.WriteLine("Inner Exception - " + e.InnerException.Message);
                    Log("Inner Exception - " + e.InnerException.Message);
                }
            }
        }
        static async Task Backup()
        {

            CloudStorageAccount _storageAccount = CloudStorageAccount.Parse(privateconnectionstring);
            CloudStorageAccount _storageBackupAccount = CloudStorageAccount.Parse(privatebackupconnectionstring);

            CloudBlobClient blobClient = _storageAccount.CreateCloudBlobClient();
            CloudBlobClient blobBackupClient = _storageBackupAccount.CreateCloudBlobClient();

            foreach (var srcContainer in blobClient.ListContainers())
            {
                // skip any containers with a backup name
                if (srcContainer.Name.IndexOf("-backup-") > -1)
                {
                    continue;
                }
                var backupTimeInTicks = DateTime.UtcNow.Ticks;
                //var destContainerName = srcContainer.Name + "-" + backupTimeInTicks;
                var backupDateTime = DateTime.UtcNow.ToString("yyyyMMdd-hhmmssfff");
                destContainerName = srcContainer.Name + "-backup-" + backupDateTime;

                var destContainer = blobBackupClient.GetContainerReference(destContainerName);
//                var destContainer = blobClient.GetContainerReference(destContainerName);

                // assume it does not exist already,
                // as that wouldn't make sense.
                await destContainer.CreateAsync();

                // ensure that the container is not accessible
                // to the outside world,
                // as we want all the backups to be internal.
                BlobContainerPermissions destContainerPermissions = destContainer.GetPermissions();
                if (destContainerPermissions.PublicAccess != BlobContainerPublicAccessType.Off)
                {
                    destContainerPermissions.PublicAccess = BlobContainerPublicAccessType.Off;
                    await destContainer.SetPermissionsAsync(destContainerPermissions);
                }

                // copy src container to dest container,
                // note that this is synchronous operation in reality,
                // as I want to only add real metadata to container
                // once all the blobs have been copied successfully.
                await CopyContainers(srcContainer, destContainer);
                await EnsureCopySucceeded(destContainer);

                // ensure we have some metadata for the container
                // as this will helps us to delete older containers
                // on a later date.
                await destContainer.FetchAttributesAsync();

                var destContainerMetadata = destContainer.Metadata;
                if (!destContainerMetadata.ContainsKey("BackupOf"))
                {
                    string cname = srcContainer.Name.ToLowerInvariant();
                    destContainerMetadata.Add("BackupOf", cname);
                    destContainerMetadata.Add("CreatedAt", backupTimeInTicks.ToString());
                    destContainerMetadata.Add("CreatedDate", backupDateTime);
                    await destContainer.SetMetadataAsync();
                    //destContainer.SetMetadata();
                }
            }

            // let's purge the older containers,
            // if we already have multiple newer backups of them.
            // why keep them around.
            // just asking for trouble.
            //var blobGroupedContainers = blobBackupClient.ListContainers()
            //    .Where(container => container.Metadata.ContainsKey("Backup-Of"))
            //    .Select(container => new
            //    {
            //        Container = container,
            //        BackupOf = container.Metadata["Backup-Of"],
            //        CreatedAt = new DateTime(long.Parse(container.Metadata["Created-At"]))
            //    }).GroupBy(arg => arg.BackupOf);

            var blobGroupedContainers = blobClient.ListContainers()
                .Where(container => container.Metadata.ContainsKey("BackupOf"))
                .Select(container => new
                {
                    Container = container,
                    BackupOf = container.Metadata["BackupOf"],
                    CreatedAt = new DateTime(long.Parse(container.Metadata["CreatedAt"]))
                }).GroupBy(arg => arg.BackupOf);

            // Remove the Delete for now
      //      foreach (var blobGroupedContainer in blobGroupedContainers)
      //      {
      //          var containersToDelete = blobGroupedContainer.Select(arg => new
      //          {
      //              Container = arg.Container,
      //              CreatedAt = new DateTime(arg.CreatedAt.Year, arg.CreatedAt.Month, arg.CreatedAt.Day)
      //          })
      //              .GroupBy(arg => arg.CreatedAt)
      //              .OrderByDescending(grouping => grouping.Key)
      //              .Skip(7) /* skip last 7 days worth of data */
      //              .SelectMany(grouping => grouping)
      //              .Select(arg => arg.Container);

      //// Remove the Delete for now
      //          //foreach (var containerToDelete in containersToDelete)
      //          //{
      //          //    await containerToDelete.DeleteIfExistsAsync();
      //          //}
      //      }
        }

        static async Task EnsureCopySucceeded(CloudBlobContainer destContainer)
        {
            bool pendingCopy = true;
            var retryCountLookup = new Dictionary<string, int>();

            while (pendingCopy)
            {
                pendingCopy = false;

                var destBlobList = destContainer.ListBlobs(null, true, BlobListingDetails.Copy);

                foreach (var dest in destBlobList)
                {
                    var destBlob = dest as CloudBlob;
                    if (destBlob == null)
                    {
                        continue;
                    }

                    var blobIdentifier = destBlob.Name;

                    if (destBlob.CopyState.Status == CopyStatus.Aborted ||
                        destBlob.CopyState.Status == CopyStatus.Failed)
                    {
                        int retryCount;
                        if (retryCountLookup.TryGetValue(blobIdentifier, out retryCount))
                        {
                            if (retryCount > 4)
                            {
                                throw new Exception("[CRITICAL] Failed to copy '"
                                                        + destBlob.CopyState.Source.AbsolutePath + "' to '"
                                                        + destBlob.StorageUri + "' due to reason of: " +
                                                        destBlob.CopyState.StatusDescription);
                            }

                            retryCountLookup[blobIdentifier] = retryCount + 1;
                        }
                        else
                        {
                            retryCountLookup[blobIdentifier] = 1;
                        }

                        pendingCopy = true;

                        // restart the copy process for src and dest blobs.
                        // note we also have retry count protection,
                        // so if any of the blobs fail too much,
                        // we'll give up.
                        await destBlob.StartCopyAsync(destBlob.CopyState.Source);
                    }
                    else if (destBlob.CopyState.Status == CopyStatus.Pending)
                    {
                        pendingCopy = true;
                    }
                }

                Thread.Sleep(1000);
            }
        }

        static async Task CopyContainers(
                CloudBlobContainer srcContainer,
                CloudBlobContainer destContainer)
        {
            // get the SAS token to use for all blobs
            string blobToken = srcContainer.GetSharedAccessSignature(new SharedAccessBlobPolicy()
            {
                Permissions = SharedAccessBlobPermissions.Read,
                SharedAccessStartTime = DateTime.Now.AddMinutes(-5),
                SharedAccessExpiryTime = DateTime.Now.AddHours(3)
            });
            int ii = 0;
            int cntr = 0;
            int waitcntr = 0;
            string sourceuri = "";
            int datecntr = 0;
            try
            {

                //Console.WriteLine("  container contains " + srcContainer.ListBlobs(null, true).Count().ToString());
                Log("  container contains " + srcContainer.ListBlobs(null, true).Count().ToString());
                foreach (var srcBlob in srcContainer.ListBlobs(null, true))
                {
                    ii++;

                    //THIS IS FOR COUNTING Blobs that would be on the Incremental Backup
                    CloudBlob blob = (CloudBlob)srcBlob;
                    if (blob.Properties.LastModified > testdate)
                    {
                        datecntr++;
                    }
                    else
                    {
                        // We are only doing an Incremental Backup this time - so skip all other files 
                        continue;
                    }


                    //if (ii > 2000)
                    //{
                    //    //Console.WriteLine("   test run ended ");
                    //    Log("   test run ended ");
                    //    break;
                    //}


                    cntr++;
                    if (cntr > 999)
                    {
                        //Console.WriteLine("    " + ii.ToString() + " processed at " + DateTime.Now.ToString("hh:mm:ss"));
                        Log("    " + ii.ToString() + " processed at " + DateTime.Now.ToString("hh:mm:ss"));

                        //Log("   EnsureCopySucceeded - finished at " + DateTime.Now.ToString("hh:mm:ss"));
                        //await EnsureCopySucceeded(destContainer);
                        //Log("   EnsureCopySucceeded - finished at " + DateTime.Now.ToString("hh:mm:ss"));

                        cntr = 0;

                    }

                    waitcntr++;
                    if (waitcntr > 29999)
                    {
                        Log("   EnsureCopySucceeded (ii=" + ii.ToString() + "- started at " + DateTime.Now.ToString("hh:mm:ss"));
                        await EnsureCopySucceeded(destContainer);
                        Log("   EnsureCopySucceeded - finished at " + DateTime.Now.ToString("hh:mm:ss"));
                        waitcntr = 0;
                    }


                    var srcCloudBlob = srcBlob as CloudBlob;
                    if (srcCloudBlob == null)
                    {
                        continue;
                    }

                    CloudBlob destCloudBlob;

                    if (srcCloudBlob.Properties.BlobType == BlobType.BlockBlob)
                    {
                        destCloudBlob = destContainer.GetBlockBlobReference(srcCloudBlob.Name);
                    }
                    else
                    {
                        destCloudBlob = destContainer.GetPageBlobReference(srcCloudBlob.Name);
                    }
                    sourceuri = srcCloudBlob.Uri.AbsoluteUri + blobToken;

                    try
                    {
                        await destCloudBlob.StartCopyAsync(new Uri(srcCloudBlob.Uri.AbsoluteUri + blobToken));
                    }
                    catch (Exception e)
                    {
                        Log("Error at item " + ii.ToString() + "      Source = " + sourceuri + "      Message = " + e.Message + "      Time = " + DateTime.Now.ToString("F") + "\r\n");
                    }
                }
                Log("Total Items checked = " + ii.ToString() + "    backed up files = " + datecntr.ToString());
                Log("TestDate = " + testdate.ToString("F") + "     datecntr = " + datecntr.ToString());
            }
            catch (Exception e)
            {
                Log("Error at item " + ii.ToString());
                Log("      Source = " + sourceuri);
                Log("      Message = " + e.Message);
                Log("      Time = " + DateTime.Now.ToString("F") + "\r\n");
                //throw e; 
            }
        }


        static void Log(string logdata)
        {
            Console.WriteLine(logdata);
            File.AppendAllText("c:\\junk\\dwlog.txt", logdata + "\r\n");
        }
    }
    }
1
Please post the code. 403 errors are not related to quota.Gaurav Mantri
There is no quota around free accounts and storage transactions. Please post your code and error. Otherwise, it's difficult (or impossible) to determine the issue.David Makogon
New to Stack Overflow - how can I post code - too long for "Add Comment".dwirth
I added the code - it kind of broke up (I must not have formatted everything right). The problem is that the code runs and copys over 100000 blobs successfully in around 3 hours or so then starts getting 403 errors on every blob.dwirth

1 Answers

2
votes

You mentioned that your code starts to fail after 3 hours. Well, the following lines of code are culprit for that:

    string blobToken = srcContainer.GetSharedAccessSignature(new SharedAccessBlobPolicy()
    {
        Permissions = SharedAccessBlobPermissions.Read,
        SharedAccessStartTime = DateTime.Now.AddMinutes(-5),
        SharedAccessExpiryTime = DateTime.Now.AddHours(3)
    });

If you notice, you are creating a shared access signature (SAS) that is valid for a duration of 3 hours and you're using this SAS for all blobs. Your code works as long as SAS is valid i.e. has not expired. Once the SAS expires, because now the SAS token is not authorized to perform the operation, you start getting 403 (Not Authorized) error.

My recommendation would be to create a SAS token that is valid for a longer duration. I would recommend a SAS token valid for 15 days because that's the maximum amount of time Azure Storage will try to copy your blob from one account to another.