0

I was having as tough time getting Microsoft.Azure.Cosmos.Table to automatically initialise the SecondaryUri when parsing a connection string that used a SAS token.

So I ended up explicitly specifying the TableSecondaryEndpoint in the connection string, that works but I'm unable to query the secondary because the SDK throws an Exception before even attempting the request.

In my testing, I have identified that this is a regression not present in Microsoft.WindowsAzure.Storage.Table 8.7.0 (The basis for Microsoft.Azure.Cosmos.Table 1.0.6)

Expert opinions very welcome that this point. Thank you.

Project code for this Exception here (also copied below): https://github.com/golfalot/SOshowAzureTableBug

Side issue detailing the SecondaryUri initialisation problem raised here: https://github.com/Azure/azure-cosmos-table-dotnet/issues/36

using System;
using System.Collections.Generic;

using LEGACY_STORAGE = Microsoft.WindowsAzure.Storage;
using LEGACY_RETRY = Microsoft.WindowsAzure.Storage.RetryPolicies;
using LEGACY_TABLE = Microsoft.WindowsAzure.Storage.Table; //8.7.0 because this is the base for 1.0.6

using NEWEST_TABLE = Microsoft.Azure.Cosmos.Table; // version 1.0.6
using Microsoft.Azure.Cosmos.Table; // had to add this to get access CreateCloudTableClient extension method

using System.Diagnostics;

namespace SOshowAzureTableBug
{
    class Program
    {
        // the SAS token is immaterial in reproducing the problem
        const string connectionTableSAS = "TableSecondaryEndpoint=http://127.0.0.1:10002/devstoreaccount1-secondary;TableEndpoint=http://127.0.0.1:10002/devstoreaccount1;SharedAccessSignature=immaterial";
        static void Main(string[] args)
        {

            /* Legacy Table SDK */
            var storageAccountLegacy = LEGACY_STORAGE.CloudStorageAccount.Parse(connectionTableSAS);
            var tableClientLegacy = storageAccountLegacy.CreateCloudTableClient();
            Debug.Assert(tableClientLegacy.StorageUri.SecondaryUri != null); // demonstrate SecondaryUri initialised

            var tableRequestOptionsLegacy = new LEGACY_TABLE.TableRequestOptions () { LocationMode = LEGACY_RETRY.LocationMode.SecondaryOnly };
            tableClientLegacy.DefaultRequestOptions = tableRequestOptionsLegacy;

            var tableLegacy = tableClientLegacy.GetTableReference("foo"); // don't need table to exist to show the issue
            var retrieveOperation = LEGACY_TABLE.TableOperation.Retrieve(string.Empty, string.Empty, new List<string>() { "bar" });

            var tableResult = tableLegacy.Execute(retrieveOperation);
            Console.WriteLine("Legacy PASS");


            /* Newset Table SDK */
            var storageAccountNewest = NEWEST_TABLE.CloudStorageAccount.Parse(connectionTableSAS);
            var tableClientNewest = storageAccountNewest.CreateCloudTableClient(new TableClientConfiguration());
            Debug.Assert(tableClientNewest.StorageUri.SecondaryUri != null); // demonstrate SecondaryUri initialised

            var tableRequestOptionsNewest = new NEWEST_TABLE.TableRequestOptions() { LocationMode = NEWEST_TABLE.LocationMode.SecondaryOnly };
            tableClientNewest.DefaultRequestOptions = tableRequestOptionsNewest;

            var tableNewset = tableClientNewest.GetTableReference("foo"); // don't need table to exist to show the issue
            var retrieveOperationNewset = NEWEST_TABLE.TableOperation.Retrieve(string.Empty, string.Empty, new List<string>() { "bar" });

            /* throws Microsoft.Azure.Cosmos.Table.StorageException
             * Exception thrown while initializing request: This operation can only be executed against the primary storage location
             */
            var tableResultNewset = tableNewset.Execute(retrieveOperationNewset);

            Console.WriteLine("Press any key to exit");
            Console.Read();
        }
    }
}

golfalot
  • 956
  • 12
  • 22
  • Can you try with the following: `const string connectionTableSAS = "TableSecondaryEndpoint=http://127.0.0.1:10002/devstoreaccount1-secondary;TableEndpoint=http://127.0.0.1:10002/devstoreaccount1;SharedAccessSignature=st=2020-02-24T12%3A47%3A31Z&se=2020-02-25T12%3A47%3A31Z&sp=r&sv=2018-03-28&tn=ratingreflocationa20200217aandmarketvalueb20200217bxml&sig=wc7tW52nstzdGMzlQuaRuakShJ%2BHmpbv8jbMlnn1lug%3D";`? – Gaurav Mantri Feb 24 '20 at 14:53
  • @GauravMantri Hi. No change, same exception. Thanks though. To clarify I encountered the same problem on the real storage account before I created the example against the emulator. I don't believe the value of Secondary is material in this issue. – golfalot Feb 24 '20 at 14:59
  • I am also able to reproduce this issue. However I have found a workaround. Will that be acceptable? – Gaurav Mantri Feb 24 '20 at 15:35
  • @GauravMantri Please educate me! :-) – golfalot Feb 24 '20 at 16:08
  • Added an answer. Please check. HTH. – Gaurav Mantri Feb 24 '20 at 16:12

1 Answers1

2

I believe you've encountered a bug with the SDK.

When I try the following code, I get the same error as you:

        var account = CloudStorageAccount.Parse(connectionString);

        var requestOptions = new TableRequestOptions()
        {
            LocationMode = LocationMode.SecondaryOnly
        };
        var client = account.CreateCloudTableClient();
        client.DefaultRequestOptions = requestOptions;
        var table = client.GetTableReference("myTable");
        var op = TableOperation.Retrieve("", "");
        var result1 = table.Execute(op);

I decompiled the library code and found the culprit source code:

if (commandLocationMode == CommandLocationMode.PrimaryOnly)
                {
                    if (restCMD.LocationMode == LocationMode.SecondaryOnly)
                    {
                        throw new InvalidOperationException("This operation can only be executed against the primary storage location.");//This is the error that gets thrown.
                    }
                    Logger.LogInformational(executionState.OperationContext, "This operation can only be executed against the primary storage location.", Array.Empty<object>());
                    executionState.CurrentLocation = StorageLocation.Primary;
                    restCMD.LocationMode = LocationMode.PrimaryOnly;
                }

However, if I don't set DefaultRequestOptions at client level and specify it below in Execute method, I don't get the error but then it's because the primary endpoint is hit instead of secondary (I checked that in Fiddler).

        var account = CloudStorageAccount.Parse(connectionString);

        var requestOptions = new TableRequestOptions()
        {
            LocationMode = LocationMode.SecondaryOnly
        };
        var client = account.CreateCloudTableClient();
        var table = client.GetTableReference("myTable");
        var op = TableOperation.Retrieve("", "");
        var result1 = table.Execute(op, requestOptions);

Workaround

If your objective is to query entities from secondary location, then you can use ExecuteQuery method on CloudTable like shown below. This works (Again, I checked in Fiddler).

        var account = CloudStorageAccount.Parse(connectionString);

        var requestOptions = new TableRequestOptions()
        {
            LocationMode = LocationMode.SecondaryOnly
        };
        var client = account.CreateCloudTableClient();
        client.DefaultRequestOptions = requestOptions;
        var table = client.GetTableReference("myTable");
        TableQuery query = new TableQuery();
        var result = table.ExecuteQuery(query).ToList();
Gaurav Mantri
  • 128,066
  • 12
  • 206
  • 241
  • Nice one work! My initial reason for forcing SecondaryOnly was for testing geo-failover (as you likely guessed). You seem highly competent in this field, what do you suggest is the best way for me to raise this bug with the SDK owners ? I ask because there doesn't seem to be much activity on https://github.com/Azure/azure-cosmos-table-dotnet/issues/ – golfalot Feb 24 '20 at 16:36
  • If you're on Twitter, just tag tag the team there. I believe their handle is @AzureCosmosDB. There's also a feedback email: AskCosmosDB@microsoft.com. I found them pretty responsive there. – Gaurav Mantri Feb 24 '20 at 16:42
  • Thanks. Just to close the loop, the email returns and auto respond saying to tag with azure-cosmosdb which I've just done. – golfalot Feb 24 '20 at 16:53
  • Yes, we shut down that email. Please file an issue on this github repo. github.com/Azure/azure-cosmos-table-dotnet/issues Thanks. – Mark Brown Feb 25 '20 at 12:39
  • @MarkBrown Thanks! The issue was filed 6 days ago: https://github.com/Azure/azure-cosmos-table-dotnet/issues/36 :) – Gaurav Mantri Feb 25 '20 at 12:52