private DataSampling DeleteEntriesPerf(ReplicatedTable rtable, List <CustomerEntity> entries)
        {
            var stats = new DataSampling("[D]elete");

            foreach (var entry in entries)
            {
                TableOperation retrieveOperation = TableOperation.Retrieve <CustomerEntity>(entry.PartitionKey, entry.RowKey);
                TableResult    retrieveResult    = rtable.Execute(retrieveOperation);

                Assert.IsNotNull(retrieveResult, "retrieveResult = null");
                Assert.AreEqual((int)HttpStatusCode.OK, retrieveResult.HttpStatusCode, "retrieveResult.HttpStatusCode mismatch");
                Assert.IsNotNull((CustomerEntity)retrieveResult.Result, "Retrieve: customer = null");

                // Delete entity
                var customer = (CustomerEntity)retrieveResult.Result;
                Assert.IsTrue(customer._rtable_Version == 2, "entry was updated once, version should be 2");

                TableOperation deleteOperation = TableOperation.Delete(customer);

                TableResult deleteResult;
                var         watch = System.Diagnostics.Stopwatch.StartNew();
                {
                    deleteResult = rtable.Execute(deleteOperation);
                }
                watch.Stop();
                stats.AddPoint(watch.ElapsedMilliseconds);

                Assert.IsNotNull(deleteResult, "deleteResult = null");
                Assert.AreEqual((int)HttpStatusCode.NoContent, deleteResult.HttpStatusCode, "deleteResult.HttpStatusCode mismatch");
            }

            return(stats);
        }
        private DataSampling RetrieveEntriesPerf(ReplicatedTable rtable, List <CustomerEntity> entries)
        {
            var stats = new DataSampling("[R]etrieve");

            foreach (var entry in entries)
            {
                TableOperation retrieveOperation = TableOperation.Retrieve <CustomerEntity>(entry.PartitionKey, entry.RowKey);

                TableResult retrieveResult = null;
                var         watch          = System.Diagnostics.Stopwatch.StartNew();
                {
                    retrieveResult = rtable.Execute(retrieveOperation);
                }
                watch.Stop();
                stats.AddPoint(watch.ElapsedMilliseconds);

                Assert.IsNotNull(retrieveResult, "retrieveResult = null");
                Assert.AreEqual((int)HttpStatusCode.OK, retrieveResult.HttpStatusCode, "retrieveResult.HttpStatusCode mismatch");

                var customer = (CustomerEntity)retrieveResult.Result;
                Assert.IsNotNull(customer, "Retrieve: customer = null");

                Assert.IsTrue(customer._rtable_Version == 1, "new entry should have version 1");
            }

            return(stats);
        }
        public void RTableStableTwoReplicaCRUDTest()
        {
            try
            {
                string tableName = this.GenerateRandomTableName("RTable2Perf");
                this.SetupRTableEnv(tableName, true, "", new List <int> {
                    0, 1
                });
                Console.WriteLine("tableName = {0}", tableName);

                // two-replica and stable?
                View view = this.configurationService.GetTableView(tableName);
                Assert.IsTrue(view != null && view.Chain != null && view.Chain.Count == 2 && view.IsStable);
                // convert mode is Off ? yes, because we can't have more than 1 replica while in convert mode!

                ReplicatedTable rtable = this.repTable;

                Console.WriteLine("[C]reate stats ...");
                entries = ShuffleList(entries);
                DataSampling createStats = CreateEntriesStats(rtable, entries);

                Console.WriteLine("[R]etrieve stats ...");
                entries = ShuffleList(entries);
                DataSampling retrieveStats = RetrieveEntriesPerf(rtable, entries);

                Console.WriteLine("[U]pdate stats ...");
                entries = ShuffleList(entries);
                DataSampling updateStats = UpdateEntriesPerf(rtable, entries);

                Console.WriteLine("[D]elete stats ...");
                entries = ShuffleList(entries);
                DataSampling deleteStats = DeleteEntriesPerf(rtable, entries);


                //// TODO: Add perf. tests for LINQ query ...


                IEnumerable <CustomerEntity> customers = GetCustomerEntities(rtable);
                Assert.AreEqual(customers.Count(), 0);

                var report = new StringBuilder();
                report.AppendLine("RTable Stable Two-Replicas CRUD perf (ms)");
                report.AppendFormat("{0}\n", createStats.ToString());
                report.AppendFormat("{0}\n", retrieveStats.ToString());
                report.AppendFormat("{0}\n", updateStats.ToString());
                report.AppendFormat("{0}\n", deleteStats.ToString());
                report.AppendLine("End report.");

                Console.WriteLine(report);
            }
            catch (Exception ex)
            {
                Console.WriteLine("{0}", ex);
                Assert.Fail();
            }
            finally
            {
                base.DeleteAllRtableResources();
            }
        }
Example #4
0
        /// <summary>
        /// Call this function to modify the contents of the RTable configuration blob to use the specified viewId
        /// and convertXStoreTableMode and update the appropriate wrapper.
        /// </summary>
        /// <param name="updatedViewId"></param>
        protected void RefreshRTableEnvJsonConfigBlob(long updatedViewId, bool convertXStoreTableMode = false,
                                                      int readViewHeadIndex = 0, List <int> blobIndexes = null, bool waitForConfig = true)
        {
            if (blobIndexes == null)
            {
                blobIndexes = new List <int> {
                    0
                };
            }

            Console.WriteLine(
                "Calling RefreshRTableEnvJsonConfigBlob() with updatedViewId={0} convertXStoreTableMode={1} readViewHeadIndex={2}",
                updatedViewId, convertXStoreTableMode, readViewHeadIndex);
            this.ModifyConfigurationBlob(updatedViewId, convertXStoreTableMode, readViewHeadIndex, blobIndexes);
            this.repTable      = new ReplicatedTable(this.repTable.TableName, this.configurationService);
            this.rtableWrapper = RTableWrapperForSampleRTableEntity.GetRTableWrapper(this.repTable);

            if (!waitForConfig)
            {
                return;
            }

            while (this.configurationWrapper.GetWriteView().ViewId != updatedViewId)
            {
                Console.WriteLine("Sleeping {0} seconds for new viewId to take effect...",
                                  Constants.LeaseDurationInSec / 2);
                Thread.Sleep((Constants.LeaseDurationInSec / 2) * 1000);
            }
        }
        private DataSampling UpdateEntriesPerf(ReplicatedTable rtable, List <CustomerEntity> entries)
        {
            var stats = new DataSampling("[U]pdate");

            foreach (var entry in entries)
            {
                TableOperation retrieveOperation = TableOperation.Retrieve <CustomerEntity>(entry.PartitionKey, entry.RowKey);
                TableResult    retrieveResult    = rtable.Execute(retrieveOperation);

                Assert.IsNotNull(retrieveResult, "retrieveResult = null");
                Assert.AreEqual((int)HttpStatusCode.OK, retrieveResult.HttpStatusCode, "retrieveResult.HttpStatusCode mismatch");
                Assert.IsNotNull((CustomerEntity)retrieveResult.Result, "Retrieve: customer = null");

                // Update entity
                var customer = (CustomerEntity)retrieveResult.Result;
                customer.Email = string.Format("{0}.{1}@email.com", entry.PartitionKey, entry.RowKey);

                TableOperation updateOperation = TableOperation.Replace(customer);

                TableResult updateResult;
                var         watch = System.Diagnostics.Stopwatch.StartNew();
                {
                    updateResult = rtable.Execute(updateOperation);
                }
                watch.Stop();
                stats.AddPoint(watch.ElapsedMilliseconds);

                Assert.IsNotNull(updateResult, "updateResult = null");
                Assert.AreEqual((int)HttpStatusCode.NoContent, updateResult.HttpStatusCode, "updateResult.HttpStatusCode mismatch");
            }

            return(stats);
        }
Example #6
0
        public ReplicatedTableV2(string name, ReplicatedTableConfigurationServiceV2 replicatedTableConfigurationAgent)
        {
            this._configurationWrapper = new ReplicatedTableConfigurationV2Wrapper(name, replicatedTableConfigurationAgent);
            TableName = name;

            _replicatedTableInstance = new ReplicatedTable(name, replicatedTableConfigurationAgent);
        }
Example #7
0
        private void UpdateCustomer(CustomerEntity customer, ReplicatedTable repTable)
        {
            TableOperation operation    = TableOperation.Replace(customer);
            TableResult    updateResult = repTable.Execute(operation);

            Assert.IsNotNull(updateResult, "updateResult = null");
            Assert.AreEqual((int)HttpStatusCode.NoContent, updateResult.HttpStatusCode, "updateResult.HttpStatusCode mismatch");
        }
Example #8
0
        /// <summary>
        /// Setup the RTable Env in order to run unit tests.
        /// </summary>
        /// <param name="tableName">Name of the RTable. If string.Empty or null, then use this.rtableTestConfiguration.RTableInformation.RTableName</param>
        /// <param name="useHttps">When using HttpMangler, set this to false</param>
        /// <param name="viewIdString">_rtable_ViewId used in the RTable config blob. Set to string.Empty or null to use the _rtable_ViewId value in the xml config</param>
        /// <param name="actualStorageAccountsToBeUsed">Specify how/which Storage accounts in the config.xml will be used to construct the RTable. Set to null to use all accounts in xml. E.g., {0,1}</param>
        protected void SetupRTableEnv(
            string tableName    = "",
            bool useHttps       = true,
            string viewIdString = "",
            List <int> actualStorageAccountsToBeUsed = null,
            bool convertXStoreTableMode = false,
            int numberOfBlobs           = 0)
        {
            int numberOfStorageAccounts = this.rtableTestConfiguration.StorageInformation.AccountNames.Count();

            // If user does not specify which storage accounts to construct the RTable, then use all the storage accounts in the xml.
            this.actualStorageAccountsUsed = actualStorageAccountsToBeUsed;
            if (this.actualStorageAccountsUsed == null || this.actualStorageAccountsUsed.Count == 0)
            {
                this.actualStorageAccountsUsed = new List <int>();
                for (int i = 0; i < numberOfStorageAccounts; i++)
                {
                    this.actualStorageAccountsUsed.Add(i);
                }
            }

            this.UpdateConfigurationConstants();

            // Upload RTable configuration to blob
            int viewId = this.rtableTestConfiguration.RTableInformation.ViewId;

            if (!string.IsNullOrEmpty(viewIdString))
            {
                viewId = int.Parse(viewIdString);
            }

            numberOfBlobs = numberOfBlobs == 0
                ? this.rtableTestConfiguration.StorageInformation.AccountKeys.Count()
                : numberOfBlobs;

            if (string.IsNullOrEmpty(tableName))
            {
                tableName = this.rtableTestConfiguration.RTableInformation.RTableName;
            }

            this.InitConnectionStringMap(useHttps);

            InitialSetup(tableName, useHttps, viewId, convertXStoreTableMode, numberOfBlobs);

            this.repTable = new ReplicatedTable(tableName, this.configurationService);
            this.repTable.CreateIfNotExists();
            this.rtableWrapper = RTableWrapperForSampleRTableEntity.GetRTableWrapper(this.repTable);

            this.cloudTableClients = new List <CloudTableClient>();
            this.cloudTables       = new List <CloudTable>();
            for (int i = 0; i < this.actualStorageAccountsUsed.Count; i++)
            {
                CloudTableClient cloudBloblClient = repTable.GetReplicaTableClient(i);
                this.cloudTableClients.Add(cloudBloblClient);
                this.cloudTables.Add(cloudBloblClient.GetTableReference(repTable.TableName));
            }
        }
Example #9
0
        private void InsertCustormer(CustomerEntity customer, ReplicatedTable repTable)
        {
            TableOperation operation = TableOperation.Insert(customer);
            TableResult    result    = repTable.Execute(operation);

            Assert.AreNotEqual(null, result, "result = null");
            Assert.AreEqual((int)HttpStatusCode.NoContent, result.HttpStatusCode, "result.HttpStatusCode mismatch");
            Assert.AreNotEqual(null, result.Result, "result.Result = null");
        }
        public void LinqQueriesDontReturnPhysicallyDeletedEntries()
        {
            var rtable = new ReplicatedTable(this.repTable.TableName, this.configurationService);

            string firstName = "FirstName";
            string lastName  = "LastName";

            /*
             * 1 - insert entries
             */
            for (int i = 0; i < 10; i++)
            {
                var customer = new CustomerEntity(firstName + i, lastName + i);

                TableOperation operation = TableOperation.Insert(customer);
                rtable.Execute(operation);
            }

            /*
             * 2 - delete entries #2 and #4
             */
            foreach (var i in new int[] { 2, 4 })
            {
                TableOperation operation       = TableOperation.Retrieve <CustomerEntity>(firstName + i, lastName + i);
                TableResult    retrievedResult = rtable.Execute(operation);

                var customer = (CustomerEntity)retrievedResult.Result;

                TableOperation deleteOperation = TableOperation.Delete(customer);
                TableResult    deleteResult    = rtable.Execute(deleteOperation);

                Assert.IsNotNull(deleteResult, "deleteResult = null");
                Assert.AreEqual((int)HttpStatusCode.NoContent, deleteResult.HttpStatusCode, "deleteResult.HttpStatusCode mismatch");
            }

            /*
             * 3 - CreateQuery doesn't return entries #2 and #4
             */
            foreach (var customer in rtable.CreateReplicatedQuery <CustomerEntity>().AsEnumerable())
            {
                int id = int.Parse(customer.PartitionKey.Replace(firstName, ""));

                Assert.AreNotEqual(id, 2, "entry #2 should have been deleted");
                Assert.AreNotEqual(id, 4, "entry #4 should have been deleted");
            }

            /*
             * 4 - ExecuteQuery doesn't return entries #2 and #4
             */
            foreach (var customer in rtable.ExecuteQuery <CustomerEntity>(new TableQuery <CustomerEntity>()))
            {
                int id = int.Parse(customer.PartitionKey.Replace(firstName, ""));

                Assert.AreNotEqual(id, 2, "entry #2 should have been deleted");
                Assert.AreNotEqual(id, 4, "entry #4 should have been deleted");
            }
        }
Example #11
0
        private CustomerEntity RetrieveCustomer(string firstName, string lastName, ReplicatedTable repTable)
        {
            TableOperation operation       = TableOperation.Retrieve <CustomerEntity>(firstName, lastName);
            TableResult    retrievedResult = repTable.Execute(operation);

            Assert.AreNotEqual(null, retrievedResult, "retrievedResult = null");
            Assert.AreEqual((int)HttpStatusCode.OK, retrievedResult.HttpStatusCode, "retrievedResult.HttpStatusCode mismatch");
            Assert.AreNotEqual(null, retrievedResult.Result, "retrievedResult.Result = null");

            return((CustomerEntity)retrievedResult.Result);
        }
Example #12
0
 public IChainTable GetChainTable(string tableId)
 {
     if (tableId.StartsWith("__RTable_"))
     {
         var name = tableId.Substring(9);
         ReplicatedTableConfigurationService rtableConfig = new ReplicatedTableConfigurationService(rTabConfLocs, true);
         ReplicatedTable rTable = new ReplicatedTable(name, rtableConfig);
         return(new RTableAdapter(rTable));
     }
     else
     {
         return(new ATableAdapter(client.GetTableReference(tableId)));
     }
 }
Example #13
0
        public void RTableCreateTableAlreadyExistsSync()
        {
            try
            {
                string tableName = this.GenerateRandomTableName();
                this.SetupRTableEnv(tableName);
                Assert.IsTrue(this.repTable.Exists(), "RTable does not exist");

                // Try to create the same RTable again.
                ReplicatedTable curTable = new ReplicatedTable(this.repTable.TableName, this.configurationService);
                Assert.IsFalse(curTable.CreateIfNotExists(), "Calling CreateIfNotExists() again returned true. Should be false.");
                int replicasCreated = curTable.replicasCreated;
                Assert.AreEqual(replicasCreated, 0, "Calling CreateIfNotExists() again returned replicasCreated={0} > 0. Should be 0.", replicasCreated);
            }
            finally
            {
                base.DeleteAllRtableResources();
            }
        }
Example #14
0
        public void RTableCreateTableAlreadyExistsSync()
        {
            try
            {
                string tableName = this.GenerateRandomTableName();
                this.SetupRTableEnv(tableName);
                Assert.IsTrue(this.repTable.Exists(), "RTable does not exist");

                // Try to create the same RTable again.
                ReplicatedTable curTable = new ReplicatedTable(this.repTable.TableName, this.configurationService);
                Assert.IsFalse(curTable.CreateIfNotExists(), "Calling CreateIfNotExists() again returned true. Should be false.");
                int replicasCreated = curTable.replicasCreated;
                Assert.AreEqual(replicasCreated, 0, "Calling CreateIfNotExists() again returned replicasCreated={0} > 0. Should be 0.", replicasCreated);
            }
            finally
            {
                base.DeleteAllRtableResources();
            }
        }
Example #15
0
        private DataSampling CreateEntriesStats(ReplicatedTable rtable, List <CustomerEntity> entries)
        {
            var stats = new DataSampling("[C]reate");

            foreach (var entry in entries)
            {
                TableOperation insertOperation = TableOperation.Insert(entry);

                TableResult insertResult = null;
                var         watch        = System.Diagnostics.Stopwatch.StartNew();
                {
                    insertResult = rtable.Execute(insertOperation);
                }
                watch.Stop();
                stats.AddPoint(watch.ElapsedMilliseconds);

                Assert.IsNotNull(insertResult, "insertResult = null");
                Assert.AreEqual((int)HttpStatusCode.NoContent, insertResult.HttpStatusCode, "insertResult.HttpStatusCode mismatch");
            }

            return(stats);
        }
Example #16
0
        public void TxnViewExpiresAndViewIdHasChanged()
        {
            // Insert an entry
            string entityPartitionKey = "jobType-ReplaceWhenTxnViewExpires";
            string entityRowKey       = "jobType-ReplaceWhenTxnViewExpires";

            this.ForceDeleteEntryFromStorageTablesDirectly(entityPartitionKey, entityRowKey);

            Console.WriteLine("Inserting entry ...");
            var entry = new SampleRTableEntity(entityPartitionKey, entityRowKey, "insert message");

            this.rtableWrapper.InsertRow(entry);

            // Retrieve the config ...
            ReplicatedTableConfiguration    config;
            ReplicatedTableQuorumReadResult readStatus = this.configurationService.RetrieveConfiguration(out config);

            Assert.IsTrue(readStatus.Code == ReplicatedTableQuorumReadCode.Success);

            // Reconfigure RTable config with a short LeaseDuration
            int leaseDurationInSec = 3;

            config.LeaseDuration = leaseDurationInSec;
            this.configurationService.UpdateConfiguration(config);

            string accountNameToTamper = this.rtableTestConfiguration.StorageInformation.AccountNames[0];

            Console.WriteLine("RunHttpManglerBehaviorHelper(): accountNameToTamper={0}", accountNameToTamper);

            // Delay behavior
            ProxyBehavior[] behaviors = new[]
            {
                DelayBehaviors.DelayAllResponsesIf(
                    1,
                    (session =>
                {
                    var body = session.GetRequestBodyAsString();

                    // Delay lock of the Head response enough so the txView expires ...
                    // And, upload a new config with a new viewId
                    if (session.hostname.Contains(accountNameToTamper + ".") &&
                        session.HTTPMethodIs("PUT") &&
                        body.Contains("\"_rtable_Operation\":\"Replace\"") &&
                        body.Contains("\"_rtable_RowLock\":true"))
                    {
                        // Upload a new config with a new ViewId
                        View view = this.configurationService.GetTableView(this.repTable.TableName);
                        config.GetView(view.Name).ViewId++;
                        this.configurationService.UpdateConfiguration(config, false);

                        // ensure txView expires
                        Thread.Sleep(2 * leaseDurationInSec * 1000);
                        return(true);
                    }

                    return(false);
                })),
            };

            // Launch an Update
            using (new HttpMangler(false, behaviors))
            {
                try
                {
                    entry         = this.rtableWrapper.ReadEntity(entityPartitionKey, entityRowKey);
                    entry.Message = "update message";

                    var            table            = new ReplicatedTable(this.repTable.TableName, this.configurationService);
                    TableOperation replaceOperation = TableOperation.Replace(entry);

                    // Get a snapshot of txView before transaction starts ...
                    View viewBeforeTx = this.configurationService.GetTableView(this.repTable.TableName);

                    // Transaction will get delayed 2*LeaseDuration sec. and Config/ViewId will be changed
                    TableResult replaceResult = table.Execute(replaceOperation);

                    Assert.IsTrue(viewBeforeTx.IsExpired(), "txView expected to expire!");
                    Assert.AreNotEqual(this.configurationService.GetTableView(this.repTable.TableName).ViewId, viewBeforeTx.ViewId, "ViewId should have changed!");

                    Assert.IsNotNull(replaceResult, "upsertResult = null");
                    Assert.AreNotEqual((int)HttpStatusCode.NoContent, replaceResult.HttpStatusCode, "upsertResult.HttpStatusCode mismatch");
                }
                catch (ReplicatedTableStaleViewException ex)
                {
                    // ValidateTxnView is called form everywhere ... we may endup here too
                    switch (ex.ErrorCode)
                    {
                    case ReplicatedTableViewErrorCodes.ViewIdChanged:
                        break;

                    default:
                        Console.WriteLine(ex);
                        Assert.IsTrue(false);
                        break;
                    }
                }
            }
        }
Example #17
0
        public void TableQueryableCreateQueryNoPartitionKey()
        {
            Thread.Sleep(10000);

            string tableName = this.GenerateRandomTableName();
            ReplicatedTable localRTable = new ReplicatedTable(tableName, this.configurationService);
            localRTable.CreateIfNotExists();
            RTableWrapperForSampleRTableEntity localRTableWrapper = RTableWrapperForSampleRTableEntity.GetRTableWrapper(localRTable);

            CloudTableClient tableClient = localRTable.GetTailTableClient();
            CloudTable table = tableClient.GetTableReference(localRTable.TableName);

            string pk = "0";
            try
            {
                try
                {
                    TableBatchOperation batch = new TableBatchOperation();

                    for (int j = 0; j < 10; j++)
                    {
                        BaseEntity ent = GenerateRandomEntity(pk);
                        ent.RowKey = string.Format("{0:0000}", j);
                        batch.Insert(ent);
                    }

                    localRTable.ExecuteBatch(batch);
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Exception during test case init {0}", ex.ToString());
                    throw;
                }

                try
                {
                    pk = "1";
                    TableBatchOperation batch = new TableBatchOperation();

                    for (int j = 0; j < 10; j++)
                    {
                        BaseEntity ent = GenerateRandomEntity(pk);
                        ent.RowKey = string.Format("{0:0000}", j);
                        batch.Insert(ent);
                    }

                    localRTable.ExecuteBatch(batch);
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Exception during test case init {0}", ex.ToString());
                    throw;
                }

                IQueryable<BaseEntity> tableQuery = table.CreateQuery<BaseEntity>();
                IQueryable<BaseEntity> rtableQuery = localRTable.CreateQuery<BaseEntity>();

                var list = tableQuery.AsEnumerable();

                int tableCount = 0;
                int rtableCount = 0;
                foreach (BaseEntity ent in list)
                {
                    tableCount++;
                }
                foreach (BaseEntity ent in rtableQuery.ToList())
                {
                    rtableCount++;
                }

                Assert.IsTrue(tableCount == rtableCount, "Query counts are different");
                Assert.IsTrue(tableCount == 20, "Query counts are different");
            }
            catch (Exception e)
            {
                Console.WriteLine("Error during query processing: {0}", e.ToString());
            }
            finally
            {
                localRTable.DeleteIfExists();
            }
        }
Example #18
0
        private ReplicatedTableRepairResult RepairTable(string tableName, string storageAccountName, ReplicatedTableConfiguration configuration)
        {
            string viewName = "";

            try
            {
                ReplicatedTableConfiguredTable tableConfig;
                if (!configuration.IsConfiguredTable(tableName, out tableConfig))
                {
                    return(new ReplicatedTableRepairResult(ReplicatedTableRepairCode.NotConfiguredTable, tableName));
                }

                viewName = tableConfig.ViewName;

                List <ReplicaInfo> list = configuration.GetView(viewName).ReplicaChain;
                if (!list.Any() ||
                    list[0].StorageAccountName != storageAccountName)
                {
                    return(new ReplicatedTableRepairResult(ReplicatedTableRepairCode.NotImpactedTable, tableName, viewName, storageAccountName));
                }

                ReplicaInfo head = list[0];
                if (head.Status != ReplicaStatus.WriteOnly)
                {
                    return(new ReplicatedTableRepairResult(ReplicatedTableRepairCode.StableTable, tableName, viewName, storageAccountName));
                }

                int viewIdToRecoverFrom = (int)head.ViewWhenTurnedOff;

                ReplicatedTableLogger.LogInformational("RepairTable={0}, View={1}, StorageAccountName={2}, from viewId={3} ...",
                                                       tableName,
                                                       viewName,
                                                       storageAccountName,
                                                       viewIdToRecoverFrom);
                // Repairing ...
                ReconfigurationStatus status = new ReplicatedTable(tableName, this).RepairTable(viewIdToRecoverFrom, null);

                ReplicatedTableLogger.LogInformational("RepairTable={0}, View={1}, StorageAccountName={2}, from viewId={3} => Status={4}",
                                                       tableName,
                                                       viewName,
                                                       storageAccountName,
                                                       viewIdToRecoverFrom,
                                                       status);

                if (status == ReconfigurationStatus.SUCCESS)
                {
                    return(new ReplicatedTableRepairResult(ReplicatedTableRepairCode.Success, tableName, viewName, storageAccountName));
                }

                // Failure!
                return(new ReplicatedTableRepairResult(ReplicatedTableRepairCode.Error, tableName, viewName, storageAccountName)
                {
                    Status = status,
                });
            }
            catch (Exception ex)
            {
                return(new ReplicatedTableRepairResult(ReplicatedTableRepairCode.Error, tableName, viewName, storageAccountName)
                {
                    Status = ReconfigurationStatus.FAILURE,
                    Message = ex.ToString(),
                });
            }
        }
        public void TableQueryableCreateQueryNoPartitionKey()
        {
            Thread.Sleep(10000);

            string          tableName   = this.GenerateRandomTableName();
            ReplicatedTable localRTable = new ReplicatedTable(tableName, this.configurationService);

            localRTable.CreateIfNotExists();
            RTableWrapperForSampleRTableEntity localRTableWrapper = RTableWrapperForSampleRTableEntity.GetRTableWrapper(localRTable);

            CloudTableClient tableClient = localRTable.GetTailTableClient();
            CloudTable       table       = tableClient.GetTableReference(localRTable.TableName);

            string pk = "0";

            try
            {
                try
                {
                    TableBatchOperation batch = new TableBatchOperation();

                    for (int j = 0; j < 10; j++)
                    {
                        BaseEntity ent = GenerateRandomEntity(pk);
                        ent.RowKey = string.Format("{0:0000}", j);
                        batch.Insert(ent);
                    }

                    localRTable.ExecuteBatch(batch);
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Exception during test case init {0}", ex.ToString());
                    throw;
                }

                try
                {
                    pk = "1";
                    TableBatchOperation batch = new TableBatchOperation();

                    for (int j = 0; j < 10; j++)
                    {
                        BaseEntity ent = GenerateRandomEntity(pk);
                        ent.RowKey = string.Format("{0:0000}", j);
                        batch.Insert(ent);
                    }

                    localRTable.ExecuteBatch(batch);
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Exception during test case init {0}", ex.ToString());
                    throw;
                }

                IQueryable <BaseEntity> tableQuery  = table.CreateQuery <BaseEntity>();
                IQueryable <BaseEntity> rtableQuery = localRTable.CreateQuery <BaseEntity>();

                var list = tableQuery.AsEnumerable();

                int tableCount  = 0;
                int rtableCount = 0;
                foreach (BaseEntity ent in list)
                {
                    tableCount++;
                }
                foreach (BaseEntity ent in rtableQuery.ToList())
                {
                    rtableCount++;

                    Assert.IsTrue(ent.ETag != ent._rtable_Version.ToString(), "ETag is not virtualized when using CreateQuery()");
                }

                Assert.IsTrue(tableCount == rtableCount, "Query counts are different");
                Assert.IsTrue(tableCount == 20, "Query counts are different");

                // But, with "CreateReplicatedQuery" ETag is virtualized
                IQueryable <BaseEntity> virtualizedRtableQuery = localRTable.CreateReplicatedQuery <BaseEntity>();

                foreach (BaseEntity ent in virtualizedRtableQuery.ToList())
                {
                    Assert.IsTrue(ent._rtable_Version == 0);
                    Assert.IsTrue(ent.ETag == ent._rtable_Version.ToString(), "ETag is virtualized when using CreateReplicatedQuery()");

                    ent.A += "`";

                    // Update should go fine since ETag is virtualized
                    TableOperation operation = TableOperation.Replace(ent);
                    TableResult    result    = localRTable.Execute(operation);
                    Assert.IsTrue(result != null && result.HttpStatusCode == (int)HttpStatusCode.NoContent);
                }

                virtualizedRtableQuery = localRTable.CreateReplicatedQuery <BaseEntity>();

                foreach (BaseEntity ent in virtualizedRtableQuery.ToList())
                {
                    Assert.IsTrue(ent._rtable_Version == 1);
                    Assert.IsTrue(ent.ETag == ent._rtable_Version.ToString(), "ETag is virtualized when using CreateReplicatedQuery()");
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("Error during query processing: {0}", e.ToString());
            }
            finally
            {
                localRTable.DeleteIfExists();
            }
        }
 /// <summary>
 /// Initializes a new instance of the <see cref="DynamicReplicatedTableEntity"/> class.
 /// </summary>
 public DynamicReplicatedTableEntity()
     : base()
 {
     this.Properties        = new Dictionary <string, EntityProperty>();
     this._rtable_Operation = ReplicatedTable.GetTableOperation(TableOperationType.Insert);
 }
Example #21
0
        public void ConfigurationChangeNotificationAfterDetectingStaleView()
        {
            ReplicatedTableConfigurationServiceV2 configServiceOne, configServiceTwo;

            // setup:
            //  stale view = [H] ->  [T]
            //  new view   = [H]
            SetupStaleViewAndNewView(out configServiceOne, out configServiceTwo);

            long staleViewId  = configServiceOne.GetTableView(this.repTable.TableName).ViewId;
            long latestViewId = configServiceTwo.GetTableView(this.repTable.TableName).ViewId;

            string firstName = "FirstName01";
            string lastName  = "LastName01";

            /*
             * 1 - WorkerOne => inserts an entry using the stale View
             */
            var workerOne = new ReplicatedTable(this.repTable.TableName, configServiceOne);

            var customer = new CustomerEntity(firstName, lastName);

            customer.Email = "*****@*****.**";
            InsertCustormer(customer, workerOne);

            customer = RetrieveCustomer(firstName, lastName, workerOne);

            Assert.AreEqual(staleViewId, customer._rtable_ViewId, "customer._rtable_ViewId mismatch");
            Assert.AreEqual("*****@*****.**", customer.Email, "customer.Email mismatch");
            Console.WriteLine("workerOne inserted a customer in ViewId={0}", staleViewId);


            /*
             * 2 - WorkerTwo => updates the entry using latest View
             *                  row is updated in [Head] replica
             */
            var workerTwo = new ReplicatedTable(this.repTable.TableName, configServiceTwo);

            customer       = RetrieveCustomer(firstName, lastName, workerTwo);
            customer.Email = "*****@*****.**";
            UpdateCustomer(customer, workerTwo);

            customer = RetrieveCustomer(firstName, lastName, workerTwo);

            Assert.AreEqual(latestViewId, customer._rtable_ViewId, "customer._rtable_ViewId mismatch");
            Assert.AreEqual("*****@*****.**", customer.Email, "customer.Email mismatch");
            Console.WriteLine("workerTwo updated the customer in ViewId={0}", latestViewId);


            /*
             * 3 - WorkerOne => access existing entry using "the stale View"
             *                  Read is served from [Head] i.e. we are not reading stale data from [Tail]
             */
            try
            {
                customer = RetrieveCustomer(firstName, lastName, workerOne);
                Assert.Fail("Retrieve() is expected to get an RTableStaleViewException but did not get it.");
            }
            catch (ReplicatedTableStaleViewException ex)
            {
                Console.WriteLine("workerOne got exception: {0}", ex.Message);
                Assert.IsTrue(ex.ErrorCode == ReplicatedTableViewErrorCodes.ViewIdSmallerThanEntryViewId);
                Assert.IsTrue(ex.Message.Contains(string.Format("current _rtable_ViewId {0} is smaller than _rtable_ViewId of existing row {1}", staleViewId, latestViewId)), "Got unexpected exception message");
            }

            // Note:
            //  No need to test all table APIs since already covered by UT:
            //  => "Microsoft.Azure.Toolkit.Replication.Test.RTableViewIdTests.ExceptionWhenUsingSmallerViewId()"


            /*
             * 4 - Notify "configServiceOne" instance of config change
             */
            Assert.AreEqual(configServiceOne.GetTableView(this.repTable.TableName).ViewId, staleViewId, "we should see old View!!!");
            {
                configServiceOne.ConfigurationChangeNotification();
            }
            Assert.AreEqual(configServiceOne.GetTableView(this.repTable.TableName).ViewId, latestViewId, "we should see latest View!!!");


            /*
             * 5 - WorkerOne => updates the entry using latest View
             */
            customer       = RetrieveCustomer(firstName, lastName, workerTwo);
            customer.Email = "*****@*****.**";
            UpdateCustomer(customer, workerTwo);

            customer = RetrieveCustomer(firstName, lastName, workerTwo);

            Assert.AreEqual(latestViewId, customer._rtable_ViewId, "customer._rtable_ViewId mismatch");
            Assert.AreEqual("*****@*****.**", customer.Email, "customer.Email mismatch");
            Console.WriteLine("workerOne updated the customer in ViewId={0}", latestViewId);
        }
Example #22
0
        public void ReadingUncommittedDataFromReplicaIdentifiedByReadTailIndexWontThrow()
        {
            ReplicatedTableConfigurationServiceV2 configServiceOne, configServiceTwo;

            // setup:        Acc0    Acc1
            //  stale view = [H] ->  [T]
            //  new view   = [H]
            SetupStaleViewAndNewView(out configServiceOne, out configServiceTwo);

            long staleViewId  = configServiceOne.GetTableView(this.repTable.TableName).ViewId;
            long latestViewId = configServiceTwo.GetTableView(this.repTable.TableName).ViewId;

            string firstName = "FirstName01";
            string lastName  = "LastName01";

            /*
             * 1 - WorkerOne => inserts an entry in stale View
             */
            var workerOne = new ReplicatedTable(this.repTable.TableName, configServiceOne);
            var workerTwo = new ReplicatedTable(this.repTable.TableName, configServiceTwo);

            var customer = new CustomerEntity(firstName, lastName);

            customer.Email = "***";
            InsertCustormer(customer, workerOne);

            string accountNameToTamper = this.rtableTestConfiguration.StorageInformation.AccountNames[1];

            Console.WriteLine("RunHttpManglerBehaviorHelper(): accountNameToTamper={0}", accountNameToTamper);

            TableResult    oldUpdateResult       = null;
            CustomerEntity customerSeenByNewView = null;

            // Delay behavior
            ProxyBehavior[] behaviors =
            {
                TamperBehaviors.TamperAllRequestsIf(
                    (session =>
                {
                    session.oRequest.FailSession((int)HttpStatusCode.ServiceUnavailable, "ServerBusy", "");
                }),
                    (session =>
                {
                    var body = session.GetRequestBodyAsString();

                    // Fail Lock to Tail by stale view
                    if (session.hostname.Contains(accountNameToTamper + ".") &&
                        session.HTTPMethodIs("PUT") &&
                        body.Contains("\"Email\":\"workerOne\"") &&
                        body.Contains(string.Format("\"_rtable_ViewId\":\"{0}\"", staleViewId)) &&
                        body.Contains("\"_rtable_RowLock\":true"))
                    {
                        return(true);
                    }

                    return(false);
                })),
            };

            /*
             * 2 - WorkerOne => update an entry using the stale View
             */
            using (new HttpMangler(false, behaviors))
            {
                customer       = RetrieveCustomer(firstName, lastName, workerOne);
                customer.Email = "workerOne";

                TableOperation operation = TableOperation.Replace(customer);
                oldUpdateResult = workerOne.Execute(operation);
            }


            // Expected behavior:

            // Thread_1 (stale ViewId) fails to commit to Tail
            Assert.IsNotNull(oldUpdateResult, "oldUpdateResult = null");
            Assert.AreEqual((int)HttpStatusCode.ServiceUnavailable, oldUpdateResult.HttpStatusCode, "oldUpdateResult.HttpStatusCode mismatch");
            Console.WriteLine("Update in stale View Succeeded with HttpStatus={0}", oldUpdateResult.HttpStatusCode);

            // Thread_1 (stale ViewId): Reads the entry => Succeeds eventhough the row is "uncommitted".
            //                          "ReadTailIndex = [Head]" means [Head] is treated as [Tail] for Reads.
            //                           => row is assumed "committed"
            customer = RetrieveCustomer(firstName, lastName, workerOne);
            Assert.IsNotNull(customer, "customer = null");
            Assert.AreEqual("workerOne", customer.Email, "customer.Email mismatch");

            // Thread_2 (new ViewId): Reads the entry => Succeed since reading from Head
            customerSeenByNewView = RetrieveCustomer(firstName, lastName, workerTwo);
            Assert.IsNotNull(customerSeenByNewView, "customerSeenByNewView = null");
            Assert.AreEqual("workerOne", customer.Email, "customer.Email mismatch");
        }
Example #23
0
        public void A00TwoInsertOrReplaceCallsConflictingOnTheHead()
        {
            this.rtableWrapper = RTableWrapperForSampleRTableEntity.GetRTableWrapper(this.repTable);

            string entityPartitionKey = "jobType-DelayInsertOrReplaceRowHeadTest";
            string entityRowKey       = "jobId-DelayInsertOrReplaceRowHeadTest";

            this.ForceDeleteEntryFromStorageTablesDirectly(entityPartitionKey, entityRowKey);

            // Insert one entry
            Console.WriteLine("Inserting entry ...");
            var entry = new SampleRTableEntity(entityPartitionKey, entityRowKey, "insert message");

            this.rtableWrapper.InsertRow(entry);

            string accountNameToTamper = this.rtableTestConfiguration.StorageInformation.AccountNames[0];

            Console.WriteLine("RunHttpManglerBehaviorHelper(): accountNameToTamper={0}", accountNameToTamper);

            int  delayInMs = 3000;
            bool firstWritterInitiatingCommit  = false;
            bool secondWritterTriedLockingHead = false;

            // Delay behavior
            ProxyBehavior[] behaviors = new[]
            {
                // Writter-1 tampering
                TamperBehaviors.TamperAllRequestsIf(
                    (session =>
                {
                    int iter = 0;

                    // Signal Writter-2
                    firstWritterInitiatingCommit = true;

                    // Blobk commit to head ... until Writter-2 try to lock the head
                    while (!secondWritterTriedLockingHead)
                    {
                        Console.WriteLine("Writter-1 waiting on Writter-2 to try to lock the Head (#{0})", iter);
                        Thread.Sleep(delayInMs);

                        if (++iter > 10)
                        {
                            break;
                        }
                    }
                }),
                    (session =>
                {
                    var body = session.GetRequestBodyAsString();

                    // Writter-1 committing to head
                    if (session.hostname.Contains(accountNameToTamper + ".") &&
                        session.HTTPMethodIs("PUT") &&
                        body.Contains("\"_rtable_Operation\":\"Replace\"") &&
                        body.Contains("\"_rtable_RowLock\":false") &&
                        body.Contains("\"Message\":\"upsert message 0\"")
                        )
                    {
                        return(true);
                    }

                    return(false);
                })),

                // Writter-2 tampering
                TamperBehaviors.TamperAllRequestsIf(
                    (session =>
                {
                    // Block till Writter-1 issues a commit to head
                    while (!firstWritterInitiatingCommit)
                    {
                        Console.WriteLine("Writter-2 waiting on Writter-1 to issue a commit to head");
                        Thread.Sleep(delayInMs);
                    }
                }),
                    (session =>
                {
                    var body = session.GetRequestBodyAsString();

                    // Writter-2 locking the head
                    if (session.hostname.Contains(accountNameToTamper + ".") &&
                        session.HTTPMethodIs("PUT") &&
                        body.Contains("\"_rtable_Operation\":\"Replace\"") &&
                        body.Contains("\"_rtable_RowLock\":true") &&
                        body.Contains("\"Message\":\"upsert message 1\"")
                        )
                    {
                        return(true);
                    }

                    return(false);
                })),

                // Delay Writter-2 lock-to-the-head's response, so Writter-1 can continue with its commit.
                DelayBehaviors.DelayAllResponsesIf(
                    delayInMs,
                    (session =>
                {
                    var body = session.GetRequestBodyAsString();

                    // Writter-2 locking the head response
                    if (session.hostname.Contains(accountNameToTamper + ".") &&
                        session.HTTPMethodIs("PUT") &&
                        body.Contains("\"_rtable_Operation\":\"Replace\"") &&
                        body.Contains("\"_rtable_RowLock\":true") &&
                        body.Contains("\"Message\":\"upsert message 1\"")
                        )
                    {
                        // Signal Writter-1 so it can continue with commit to head
                        secondWritterTriedLockingHead = true;
                        return(true);
                    }

                    return(false);
                })),
            };

            // Launch 2 concurrent Upserts
            var results = new TableResult[2];

            using (new HttpMangler(false, behaviors))
            {
                Parallel.For(0, 2, (index) =>
                {
                    entry = new SampleRTableEntity(entityPartitionKey, entityRowKey, string.Format("upsert message {0}", index));

                    try
                    {
                        var table = new ReplicatedTable(this.repTable.TableName, this.configurationService);

                        TableOperation operation = TableOperation.InsertOrReplace(entry);
                        results[index]           = table.Execute(operation);
                    }
                    catch (AggregateException ex)
                    {
                        Console.WriteLine(ex);
                    }
                });
            }

            // Writter-1 suceed?
            Assert.AreEqual((int)HttpStatusCode.NoContent, results[0].HttpStatusCode, "Writter-1 expected to suceed!");

            // Writter-2 suceeded?
            Assert.AreEqual((int)HttpStatusCode.NoContent, results[1].HttpStatusCode, "Writter-2 expected to suceed!");

            // Writter-2 upsert succeeded
            SampleRTableEntity upsertedEntity = this.rtableWrapper.ReadEntity(entityPartitionKey, entityRowKey);

            Assert.NotNull(upsertedEntity, "Writter-2 upsert succeeded");
            Assert.AreEqual(upsertedEntity.Message, string.Format("upsert message {0}", 1), "Writter-2 upsert succeeded");
        }
Example #24
0
        public void WhenReadFromTailFailsWithServiceUnavailableWeReadFromHeadAndSucceed()
        {
            View view = this.configurationWrapper.GetWriteView();

            Assert.IsTrue(view.Chain.Count > 1, "expects at least 2 replicas!");

            // Insert one entry
            var rtable = new ReplicatedTable(this.repTable.TableName, this.configurationService);

            string firstName = "FirstName01";
            string lastName  = "LastName01";

            var            customer  = new CustomerEntity(firstName, lastName);
            TableOperation operation = TableOperation.Insert(customer);

            rtable.Execute(operation);


            // Using xstore modify the row in each replica individually ... so we know, later, which replica RTable will retrieve from
            for (int replicaIndex = 0; replicaIndex < this.cloudTableClients.Count; replicaIndex++)
            {
                CloudTableClient tableClient = this.cloudTableClients[replicaIndex];
                CloudTable       table       = tableClient.GetTableReference(this.repTable.TableName);

                TableOperation retrieveOperation = TableOperation.Retrieve <CustomerEntity>(firstName, lastName);
                TableResult    retrieveResult    = table.Execute(retrieveOperation);

                customer       = (CustomerEntity)retrieveResult.Result;
                customer.Email = replicaIndex.ToString(); // Head = 0
                // ...  = 1
                // Tail = 2

                TableOperation updateOperation = TableOperation.Replace(customer);
                TableResult    updateResult    = table.Execute(updateOperation);

                Assert.IsNotNull(updateResult, "updateResult = null");
                Console.WriteLine("updateResult.HttpStatusCode = {0}", updateResult.HttpStatusCode);
                Assert.AreEqual((int)HttpStatusCode.NoContent, updateResult.HttpStatusCode, "updateResult.HttpStatusCode mismatch");
            }

            string accountNameToNotTamper = this.rtableTestConfiguration.StorageInformation.AccountNames[0];

            Console.WriteLine("RunHttpManglerBehaviorHelper(): accountNameToNotTamper={0}", accountNameToNotTamper);

            // Delay behavior
            ProxyBehavior[] behaviors =
            {
                TamperBehaviors.TamperAllRequestsIf(
                    (session =>
                {
                    session.oRequest.FailSession((int)HttpStatusCode.ServiceUnavailable, "ServerBusy", "");
                }),
                    (session =>
                {
                    if (session.HTTPMethodIs("Get"))
                    {
                        // Fail Get from all replicas, except Head
                        if (!session.hostname.Contains(accountNameToNotTamper + "."))
                        {
                            return(true);
                        }
                    }

                    return(false);
                })),
            };

            using (new HttpMangler(false, behaviors))
            {
                operation = TableOperation.Retrieve <CustomerEntity>(firstName, lastName);
                TableResult retrievedResult = repTable.Execute(operation);

                Assert.AreNotEqual(null, retrievedResult, "retrievedResult = null");
                Assert.AreEqual((int)HttpStatusCode.OK, retrievedResult.HttpStatusCode, "retrievedResult.HttpStatusCode mismatch");
                Assert.AreNotEqual(null, retrievedResult.Result, "retrievedResult.Result = null");

                customer = (CustomerEntity)retrievedResult.Result;
                Assert.AreEqual(customer.Email, (0).ToString(), "we should have read the row from Head");
            }
        }
        public void LinqQueriesAreServedFromHeadWhenReadViewTailIndexIsSet()
        {
            var rtable = new ReplicatedTable(this.repTable.TableName, this.configurationService);

            string firstName = "FirstName";
            string lastName  = "LastName";

            int dataSize = 5;

            /*
             * 1 - insert entries
             */
            for (int i = 0; i < dataSize; i++)
            {
                var customer = new CustomerEntity(firstName + i, lastName + i);
                customer.Email = "***";

                TableOperation operation = TableOperation.Insert(customer);
                rtable.Execute(operation);
            }


            // Identify the Tail account
            View view = this.configurationWrapper.GetWriteView();

            Assert.IsTrue(view.Chain.Count > 1, "expects at least one replica!");

            string accountNameToTamper = view.Chain.Last().Item1.StorageAccountName;

            Console.WriteLine("RunHttpManglerBehaviorHelper(): accountNameToTamper={0}", accountNameToTamper);

            // We will fail requests to update a row in Tail
            ProxyBehavior[] behaviors =
            {
                TamperBehaviors.TamperAllRequestsIf(
                    (session =>
                {
                    session.oRequest.FailSession((int)HttpStatusCode.ServiceUnavailable, "ServerBusy", "");
                }),
                    (session =>
                {
                    var body = session.GetRequestBodyAsString();

                    // Fail Lock to Tail by stale view
                    if (session.hostname.Contains(accountNameToTamper + ".") &&
                        session.HTTPMethodIs("PUT") &&
                        body.Contains("\"_rtable_RowLock\":true"))
                    {
                        return(true);
                    }

                    return(false);
                })),
            };

            using (new HttpMangler(false, behaviors))
            {
                /*
                 * 2 - update entries
                 */
                for (int i = 0; i < dataSize; i++)
                {
                    TableOperation operation       = TableOperation.Retrieve <CustomerEntity>(firstName + i, lastName + i);
                    TableResult    retrievedResult = rtable.Execute(operation);

                    var customer = (CustomerEntity)retrievedResult.Result;
                    customer.Email = "updated";

                    TableOperation replaceOperation = TableOperation.Replace(customer);
                    TableResult    replaceResult    = rtable.Execute(replaceOperation);

                    Assert.IsNotNull(replaceResult, "replaceResult = null");
                    Assert.AreEqual((int)HttpStatusCode.ServiceUnavailable, replaceResult.HttpStatusCode, "replaceResult.HttpStatusCode mismatch");
                }
            }


            SetReadViewTailIndex(0);
            Assert.AreEqual(0, this.configurationService.GetTableView(this.repTable.TableName).ReadTailIndex, "ReadTailIndex should be 0!!!");


            /*
             * 3 - CreateQuery is served from [Head], data should be new
             */
            foreach (var customer in rtable.CreateReplicatedQuery <CustomerEntity>().AsEnumerable())
            {
                Assert.AreEqual(customer.Email, "updated", "expected new data");
            }


            /*
             * 4 - ExecuteQuery is served from [Head], data should be new
             */
            foreach (var customer in rtable.ExecuteQuery <CustomerEntity>(new TableQuery <CustomerEntity>()))
            {
                Assert.AreEqual(customer.Email, "updated", "expected new data");
            }
        }
Example #26
0
        public void A00InsertOrReplaceCallConflictingWithDeleteOnTheHead()
        {
            this.rtableWrapper = RTableWrapperForSampleRTableEntity.GetRTableWrapper(this.repTable);

            string entityPartitionKey = "jobType-DelayInsertOrReplaceWhileDelete";
            string entityRowKey       = "jobType-DelayInsertOrReplaceWhileDelete";

            this.ForceDeleteEntryFromStorageTablesDirectly(entityPartitionKey, entityRowKey);

            // Insert one entry
            Console.WriteLine("Inserting entry ...");
            var entry = new SampleRTableEntity(entityPartitionKey, entityRowKey, "insert message");

            this.rtableWrapper.InsertRow(entry);

            // 1 - Launch an Upsert task in wait mode ...
            bool deleteLockedHead = false;

            TableResult upsertResult = null;
            var         upsertTask   = Task.Run(() =>
            {
                while (!deleteLockedHead)
                {
                    Thread.Sleep(5);
                }

                try
                {
                    entry     = new SampleRTableEntity(entityPartitionKey, entityRowKey, "upsert message");
                    var table = new ReplicatedTable(this.repTable.TableName, this.configurationService);

                    TableOperation upserOperation = TableOperation.InsertOrReplace(entry);

                    Console.WriteLine("Upsert started ...");
                    upsertResult = table.Execute(upserOperation);
                    Console.WriteLine("Upsert completed with HttpStatus={0}", upsertResult == null ? "NULL" : upsertResult.HttpStatusCode.ToString());
                }
                catch (AggregateException ex)
                {
                    Console.WriteLine(ex);
                }
            });


            string accountNameToTamper = this.rtableTestConfiguration.StorageInformation.AccountNames[0];

            Console.WriteLine("RunHttpManglerBehaviorHelper(): accountNameToTamper={0}", accountNameToTamper);

            int delayInMs = 5;

            // Delay behavior
            ProxyBehavior[] behaviors = new[]
            {
                DelayBehaviors.DelayAllResponsesIf(
                    delayInMs,
                    (session =>
                {
                    var body = session.GetRequestBodyAsString();

                    Console.WriteLine(body);

                    // Delete locking the head response
                    if (session.hostname.Contains(accountNameToTamper + ".") &&
                        session.HTTPMethodIs("PUT") &&
                        body.Contains("\"_rtable_Operation\":\"Replace\"") &&
                        body.Contains("\"_rtable_RowLock\":true") &&
                        body.Contains("\"_rtable_Tombstone\":true"))
                    {
                        // Signal upsert we locked the head, so it can continue ...
                        deleteLockedHead = true;
                        return(true);
                    }

                    return(false);
                })),
            };

            // Launch a delete
            using (new HttpMangler(false, behaviors))
            {
                try
                {
                    var table = new ReplicatedTable(this.repTable.TableName, this.configurationService);

                    // Retrieve entity
                    TableOperation retrieveOperation = TableOperation.Retrieve <SampleRTableEntity>(entry.PartitionKey, entry.RowKey);
                    TableResult    retrieveResult    = table.Execute(retrieveOperation);

                    Assert.IsNotNull(retrieveResult, "retrieveResult = null");
                    Assert.AreEqual((int)HttpStatusCode.OK, retrieveResult.HttpStatusCode, "retrieveResult.HttpStatusCode mismatch");
                    Assert.IsNotNull((SampleRTableEntity)retrieveResult.Result, "Retrieve: customer = null");


                    // Delete entity
                    TableOperation deleteOperation = TableOperation.Delete((SampleRTableEntity)retrieveResult.Result);
                    TableResult    deleteResult    = table.Execute(deleteOperation);

                    Assert.IsNotNull(deleteResult, "deleteResult = null");
                    Assert.AreEqual((int)HttpStatusCode.NoContent, deleteResult.HttpStatusCode, "deleteResult.HttpStatusCode mismatch");
                }
                catch (AggregateException ex)
                {
                    Console.WriteLine(ex);
                }
            }

            // wait on upsert to finish ...
            upsertTask.Wait();

            // Upsert suceeded?
            Assert.AreEqual((int)HttpStatusCode.NoContent, upsertResult.HttpStatusCode, "Upsert expected to suceed!");
            SampleRTableEntity upsertedEntity = this.rtableWrapper.ReadEntity(entityPartitionKey, entityRowKey);

            Assert.NotNull(upsertedEntity, "upsert should succeed");
            Assert.AreEqual(upsertedEntity.Message, "upsert message", "upsert should succeeded");
        }
Example #27
0
        private IEnumerable <CustomerEntity> GetCustomerEntities(ReplicatedTable rtable)
        {
            ReplicatedTableQuery <CustomerEntity> query = rtable.CreateReplicatedQuery <CustomerEntity>();

            return(query.AsEnumerable());
        }
        public void LinqQueriesDontReturnRowsWithHighViewIdWhenFlagIgnoreHigherViewIdRowsIsSet()
        {
            TableOperation operation;
            TableResult    result;
            CustomerEntity customer;


            /*
             * Set config viewId to 5
             */
            long staleViewId = 5;

            SetConfigViewIdAndIgnoreHigherViewIdRowsFlag(staleViewId, false);
            Assert.AreEqual(staleViewId, this.configurationService.GetTableView(this.repTable.TableName).ViewId, "View should be 5!!!");


            // Insert entries in stale viewId 5
            var rtable = new ReplicatedTable(this.repTable.TableName, this.configurationService);

            string firstName = "FirstName";
            string lastName  = "LastName";

            for (int i = 0; i < 10; i++)
            {
                customer = new CustomerEntity(firstName + i, lastName + i);

                operation = TableOperation.Insert(customer);
                rtable.Execute(operation);
            }


            /*
             * Set config new viewId to 6
             */
            long newViewId = 6;

            SetConfigViewIdAndIgnoreHigherViewIdRowsFlag(newViewId, false);
            Assert.AreEqual(newViewId, this.configurationService.GetTableView(this.repTable.TableName).ViewId, "View should be 6!!!");


            // Update entry #5 and #8 in new viewId 6
            foreach (int entryId in new int[] { 5, 8 })
            {
                operation = TableOperation.Retrieve <CustomerEntity>(firstName + entryId, lastName + entryId);
                result    = rtable.Execute(operation);

                Assert.IsTrue(result != null && result.HttpStatusCode == (int)HttpStatusCode.OK && (CustomerEntity)result.Result != null, "Retrieve customer failed");

                customer       = (CustomerEntity)result.Result;
                customer.Email = "*****@*****.**";

                operation = TableOperation.Replace(customer);
                result    = rtable.Execute(operation);

                Assert.IsTrue(result != null && result.HttpStatusCode == (int)HttpStatusCode.NoContent, "Update customer failed");
            }


            /*
             * Simulate a stale client => Set config viewId back to 5
             *                            and Set 'IgnoreHigherViewIdRows' flag so we ignore rows with higher viewIds
             */
            SetConfigViewIdAndIgnoreHigherViewIdRowsFlag(staleViewId, true);
            Assert.AreEqual(staleViewId, this.configurationService.GetTableView(this.repTable.TableName).ViewId, "View should be 5!!!");

            try
            {
                // Check Retrieve of row #5 and #8 returns NotFound
                foreach (int entryId in new int[] { 5, 8 })
                {
                    operation = TableOperation.Retrieve <CustomerEntity>(firstName + entryId, lastName + entryId);
                    var retrievedResult = rtable.Execute(operation);

                    Assert.AreNotEqual(null, retrievedResult, "retrievedResult = null");
                    Assert.AreEqual((int)HttpStatusCode.NotFound, retrievedResult.HttpStatusCode, "retrievedResult.HttpStatusCode mismatch");
                }
            }
            catch (ReplicatedTableStaleViewException)
            {
                Assert.Fail("Retrieve() is expected to NotFound the row, but got RTableStaleViewException !");
            }

            /*
             * stale client using LINQ: CreateReplicatedQuery
             */
            foreach (var entry in rtable.CreateReplicatedQuery <CustomerEntity>().AsEnumerable())
            {
                int id = int.Parse(entry.PartitionKey.Replace(firstName, ""));

                Assert.AreNotEqual(id, 5, "row #5 should not be returned");
                Assert.AreNotEqual(id, 8, "row #8 should not be returned");

                Assert.AreEqual(entry._rtable_ViewId, staleViewId, "CreateReplicatedQuery: entry viewId should be '5'");
            }

            /*
             * stale client using LINQ: ExecuteQuery
             */
            foreach (var entry in rtable.ExecuteQuery <CustomerEntity>(new TableQuery <CustomerEntity>()))
            {
                int id = int.Parse(entry.PartitionKey.Replace(firstName, ""));

                Assert.AreNotEqual(id, 5, "row #5 should not be returned");
                Assert.AreNotEqual(id, 8, "row #8 should not be returned");

                Assert.AreEqual(entry._rtable_ViewId, staleViewId, "CreateReplicatedQuery: entry viewId should be '5'");
            }
        }
        public void LinqQueriesThrowAfterDetectingStaleViewWhenThrowOnStaleViewInLinqQueryFlagIsSet()
        {
            TableOperation operation;
            TableResult    result;
            CustomerEntity customer;


            /*
             * Set config viewId to 5
             */
            long staleViewId = 5;

            SetConfigViewIdAndIgnoreHigherViewIdRowsFlag(staleViewId, false);
            Assert.AreEqual(staleViewId, this.configurationService.GetTableView(this.repTable.TableName).ViewId, "View should be 5!!!");


            // Insert entries in stale viewId 5
            var rtable = new ReplicatedTable(this.repTable.TableName, this.configurationService);

            string firstName = "FirstName";
            string lastName  = "LastName";

            for (int i = 0; i < 10; i++)
            {
                customer = new CustomerEntity(firstName + i, lastName + i);

                operation = TableOperation.Insert(customer);
                rtable.Execute(operation);
            }


            /*
             * Set config new viewId to 6
             */
            long newViewId = 6;

            SetConfigViewIdAndIgnoreHigherViewIdRowsFlag(newViewId, false);
            Assert.AreEqual(newViewId, this.configurationService.GetTableView(this.repTable.TableName).ViewId, "View should be 6!!!");


            // Update entry #5 in new viewId 6
            int entryId = 5;

            operation = TableOperation.Retrieve <CustomerEntity>(firstName + entryId, lastName + entryId);
            result    = rtable.Execute(operation);

            Assert.IsTrue(result != null && result.HttpStatusCode == (int)HttpStatusCode.OK && (CustomerEntity)result.Result != null, "Retrieve customer failed");

            customer       = (CustomerEntity)result.Result;
            customer.Email = "*****@*****.**";

            operation = TableOperation.Replace(customer);
            result    = rtable.Execute(operation);

            Assert.IsTrue(result != null && result.HttpStatusCode == (int)HttpStatusCode.NoContent, "Update customer failed");


            /*
             * Simulate a stale client => Set config viewId back to 5
             */
            SetConfigViewIdAndIgnoreHigherViewIdRowsFlag(staleViewId, false);
            Assert.AreEqual(staleViewId, this.configurationService.GetTableView(this.repTable.TableName).ViewId, "View should be 5!!!");

            try
            {
                // Check Retrieve of row #5 throws stale view as expected
                operation = TableOperation.Retrieve <CustomerEntity>(firstName + entryId, lastName + entryId);
                rtable.Execute(operation);

                Assert.Fail("Retrieve() is expected to get an RTableStaleViewException but did not get it.");
            }
            catch (ReplicatedTableStaleViewException ex)
            {
                Assert.IsTrue(ex.ErrorCode == ReplicatedTableViewErrorCodes.ViewIdSmallerThanEntryViewId);
                Assert.IsTrue(ex.Message.Contains(string.Format("current _rtable_ViewId {0} is smaller than _rtable_ViewId of existing row {1}", staleViewId, newViewId)), "Got unexpected exception message");
            }


            // Enable throwing on stale view detection
            rtable.ThrowOnStaleViewInLinqQueryFlag = true;

            /*
             * stale client using LINQ: CreateReplicatedQuery
             */
            try
            {
                foreach (var entry in rtable.CreateReplicatedQuery <CustomerEntity>().AsEnumerable())
                {
                    int id = int.Parse(entry.PartitionKey.Replace(firstName, ""));
                    Assert.IsTrue(id != entryId, "we should throw on entry #5");

                    Assert.AreEqual(entry._rtable_ViewId, staleViewId, "CreateReplicatedQuery: entry viewId should be '5'");
                }
            }
            catch (ReplicatedTableStaleViewException ex)
            {
                Assert.IsTrue(ex.ErrorCode == ReplicatedTableViewErrorCodes.ViewIdSmallerThanEntryViewId);
                Assert.IsTrue(ex.Message.Contains(string.Format("current _rtable_ViewId {0} is smaller than _rtable_ViewId of existing row {1}", staleViewId, newViewId)), "Got unexpected exception message");
            }

            /*
             * stale client using LINQ: ExecuteQuery
             */
            try
            {
                foreach (var entry in rtable.ExecuteQuery <CustomerEntity>(new TableQuery <CustomerEntity>()))
                {
                    int id = int.Parse(entry.PartitionKey.Replace(firstName, ""));
                    Assert.IsTrue(id != entryId, "we should throw on entry #5");

                    Assert.AreEqual(entry._rtable_ViewId, staleViewId, "ExecuteQuery: entry viewId should be '5'");
                }
            }
            catch (ReplicatedTableStaleViewException ex)
            {
                Assert.IsTrue(ex.ErrorCode == ReplicatedTableViewErrorCodes.ViewIdSmallerThanEntryViewId);
                Assert.IsTrue(ex.Message.Contains(string.Format("current _rtable_ViewId {0} is smaller than _rtable_ViewId of existing row {1}", staleViewId, newViewId)), "Got unexpected exception message");
            }
        }
Example #30
0
 public RTableAdapter(ReplicatedTable rtable)
 {
     this.rtable = rtable;
 }
Example #31
0
        public void RepairMidAnUpdate()
        {
            this.rtableWrapper = RTableWrapperForSampleRTableEntity.GetRTableWrapper(this.repTable);

            // Not-Stable chain
            // Reconfigure RTable so Head is WriteOnly.
            View view = this.configurationService.GetTableView(this.repTable.TableName);

            ReplicatedTableConfiguration    config;
            ReplicatedTableQuorumReadResult readStatus = this.configurationService.RetrieveConfiguration(out config);

            Assert.IsTrue(readStatus.Code == ReplicatedTableQuorumReadCode.Success);

            // Set Head as WriteOnly mode
            ReplicatedTableConfigurationStore viewConfg = config.GetView(view.Name);

            viewConfg.ReplicaChain[0].Status = ReplicaStatus.WriteOnly;
            config.SetView(view.Name, viewConfg);

            // Upload RTable config back
            this.configurationService.UpdateConfiguration(config);

            // Sanity: Replicated mode and chain Not-Stable
            view = this.configurationService.GetTableView(this.repTable.TableName);
            Assert.IsTrue(view != null && view.Chain.Count > 1, "Two replicas should be used.");
            Assert.IsFalse(view.IsStable);


            // Insert one entry
            Console.WriteLine("Inserting entry ...");
            string entityPartitionKey = "jobType-RepairMidAnUpdate-Replace";
            string entityRowKey       = "jobId-RepairMidAnUpdate-Replace";
            var    entry = new SampleRTableEntity(entityPartitionKey, entityRowKey, "message");

            this.rtableWrapper.InsertRow(entry);


            // 1 - Launch a RepairRow task in wait mode ...
            bool triggerRepair            = false;
            bool repaireDone              = false;
            bool headAfterRepairWasLocked = false;

            TableResult repairResult = null;

            Task.Run(() =>
            {
                ReplicatedTable repairTable = new ReplicatedTable(this.repTable.TableName, this.configurationService);

                while (!triggerRepair)
                {
                    Thread.Sleep(5);
                }

                Console.WriteLine("RepairRow started ...");
                repairResult = repairTable.RepairRow(entry.PartitionKey, entry.RowKey, null);
                Console.WriteLine("RepairRow completed with HttpStatus={0}", repairResult == null ? "NULL" : repairResult.HttpStatusCode.ToString());

                // Check the entry at the Head is still locked i.e. RepairRow was NOP
                headAfterRepairWasLocked = HeadIsLocked(entry);

                Console.WriteLine("Signal the commit to Head job");
                repaireDone = true;
            });


            // 2 - Configure Mangler ...
            string accountNameToTamper = this.rtableTestConfiguration.StorageInformation.AccountNames[0];

            Console.WriteLine("RunHttpManglerBehaviorHelper(): accountNameToTamper={0}", accountNameToTamper);

            ProxyBehavior[] behaviors = new[]
            {
                TamperBehaviors.TamperAllRequestsIf(
                    (session =>
                {
                    Console.WriteLine("Delaying commit to the Head ... => signal RepairRow job");

                    // Let RepairRow task go through
                    triggerRepair = true;

                    int iter = 0;
                    while (!repaireDone)
                    {
                        // TODO: break the loop after couple of iteration ...

                        Thread.Sleep(100);
                        Console.WriteLine("Waiting on RepairRow to finish ({0}) ...", ++iter);
                    }

                    Console.WriteLine("Request a commit to the head");
                }),
                    (session =>
                {
                    // Commit on head i.e. a PUT with RowLock == false
                    if (session.hostname.Contains(accountNameToTamper + ".") &&
                        session.HTTPMethodIs("PUT") &&
                        session.GetRequestBodyAsString().Contains("\"_rtable_RowLock\":false"))
                    {
                        return(true);
                    }

                    return(false);
                }))
            };

            using (new HttpMangler(false, behaviors))
            {
                Console.WriteLine("Updating entry ...");
                entry         = this.rtableWrapper.FindRow(entry.PartitionKey, entry.RowKey);
                entry.Message = "updated message";

                this.rtableWrapper.ReplaceRow(entry);
            }

            Assert.IsTrue(triggerRepair);
            Assert.IsTrue(repairResult != null && repairResult.HttpStatusCode == (int)HttpStatusCode.OK, "Repair failed.");
            Assert.IsTrue(repaireDone);
            Assert.IsTrue(headAfterRepairWasLocked);

            Console.WriteLine("DONE. Test passed.");
        }
        private ReplicatedTableRepairResult RepairTable(string tableName, string storageAccountName, ReplicatedTableConfiguration configuration)
        {
            string viewName = "";

            try
            {
                ReplicatedTableConfiguredTable tableConfig;
                if (!configuration.IsConfiguredTable(tableName, out tableConfig))
                {
                    return new ReplicatedTableRepairResult(ReplicatedTableRepairCode.NotConfiguredTable, tableName);
                }

                viewName = tableConfig.ViewName;

                List<ReplicaInfo> list = configuration.GetView(viewName).ReplicaChain;
                if (!list.Any() ||
                    list[0].StorageAccountName != storageAccountName)
                {
                    return new ReplicatedTableRepairResult(ReplicatedTableRepairCode.NotImpactedTable, tableName, viewName, storageAccountName);
                }

                ReplicaInfo head = list[0];
                if (head.Status != ReplicaStatus.WriteOnly)
                {
                    return new ReplicatedTableRepairResult(ReplicatedTableRepairCode.StableTable, tableName, viewName, storageAccountName);
                }

                int viewIdToRecoverFrom = (int)head.ViewWhenTurnedOff;

                ReplicatedTableLogger.LogInformational("RepairTable={0}, View={1}, StorageAccountName={2}, from viewId={3} ...",
                                                        tableName,
                                                        viewName,
                                                        storageAccountName,
                                                        viewIdToRecoverFrom);
                // Repairing ...
                ReconfigurationStatus status = new ReplicatedTable(tableName, this).RepairTable(viewIdToRecoverFrom, null);

                ReplicatedTableLogger.LogInformational("RepairTable={0}, View={1}, StorageAccountName={2}, from viewId={3} => Status={4}",
                                                        tableName,
                                                        viewName,
                                                        storageAccountName,
                                                        viewIdToRecoverFrom,
                                                        status);

                if (status == ReconfigurationStatus.SUCCESS)
                {
                    return new ReplicatedTableRepairResult(ReplicatedTableRepairCode.Success, tableName, viewName, storageAccountName);
                }

                // Failure!
                return new ReplicatedTableRepairResult(ReplicatedTableRepairCode.Error, tableName, viewName, storageAccountName)
                {
                    Status = status,
                };
            }
            catch (Exception ex)
            {
                return new ReplicatedTableRepairResult(ReplicatedTableRepairCode.Error, tableName, viewName, storageAccountName)
                {
                    Status = ReconfigurationStatus.FAILURE,
                    Message = ex.ToString(),
                };
            }
        }
        public void LinqQueriesDontReturnEntriesWithTombstone()
        {
            var rtable = new ReplicatedTable(this.repTable.TableName, this.configurationService);

            string firstName = "FirstName";
            string lastName  = "LastName";

            /*
             * 1 - insert entries
             */
            for (int i = 0; i < 10; i++)
            {
                var customer = new CustomerEntity(firstName + i, lastName + i);

                TableOperation operation = TableOperation.Insert(customer);
                rtable.Execute(operation);
            }


            // Identify the Tail account
            View view = this.configurationWrapper.GetWriteView();

            Assert.IsTrue(view.Chain.Count > 1, "expects at least one replica!");

            string accountNameToTamper = view.Chain.Last().Item1.StorageAccountName;

            Console.WriteLine("RunHttpManglerBehaviorHelper(): accountNameToTamper={0}", accountNameToTamper);

            // We will fail requests to delete a row in Tail
            ProxyBehavior[] behaviors =
            {
                TamperBehaviors.TamperAllRequestsIf(
                    (session =>
                {
                    session.oRequest.FailSession((int)HttpStatusCode.ServiceUnavailable, "ServerBusy", "");
                }),
                    (session =>
                {
                    // Commit to Tail i.e. physically delete the row
                    if (session.hostname.Contains(accountNameToTamper + ".") &&
                        session.HTTPMethodIs("DELETE"))
                    {
                        return(true);
                    }

                    return(false);
                })),
            };

            using (new HttpMangler(false, behaviors))
            {
                /*
                 * 2 - delete entries #2 and #4
                 */
                foreach (var i in new int[] { 2, 4 })
                {
                    TableOperation operation       = TableOperation.Retrieve <CustomerEntity>(firstName + i, lastName + i);
                    TableResult    retrievedResult = rtable.Execute(operation);

                    var customer = (CustomerEntity)retrievedResult.Result;

                    TableOperation deleteOperation = TableOperation.Delete(customer);
                    TableResult    deleteResult    = rtable.Execute(deleteOperation);

                    Assert.IsNotNull(deleteResult, "deleteResult = null");
                    Assert.AreEqual((int)HttpStatusCode.ServiceUnavailable, deleteResult.HttpStatusCode, "deleteResult.HttpStatusCode mismatch");
                }
            }


            // Verify, Retrieve doesn't return entries #2 and #4
            int deleteId = 2;
            var result   = rtable.Execute(TableOperation.Retrieve <CustomerEntity>(firstName + deleteId, lastName + deleteId));

            Assert.IsNotNull(result, "result = null");
            Assert.AreEqual((int)HttpStatusCode.NotFound, result.HttpStatusCode, "result.HttpStatusCode mismatch");

            deleteId = 4;
            result   = rtable.Execute(TableOperation.Retrieve <CustomerEntity>(firstName + deleteId, lastName + deleteId));
            Assert.IsNotNull(result, "result = null");
            Assert.AreEqual((int)HttpStatusCode.NotFound, result.HttpStatusCode, "result.HttpStatusCode mismatch");


            /*
             * 3 - CreateQuery doesn't return entries #2 and #4
             */
            foreach (var customer in rtable.CreateReplicatedQuery <CustomerEntity>().AsEnumerable())
            {
                int id = int.Parse(customer.PartitionKey.Replace(firstName, ""));
                Assert.AreNotEqual(id, 2, "entry #2 should have been deleted");
                Assert.AreNotEqual(id, 4, "entry #4 should have been deleted");
            }


            /*
             * 4 - ExecuteQuery doesn't return entries #2 and #4
             */
            foreach (var customer in rtable.ExecuteQuery <CustomerEntity>(new TableQuery <CustomerEntity>()))
            {
                int id = int.Parse(customer.PartitionKey.Replace(firstName, ""));
                Assert.AreNotEqual(id, 2, "entry #2 should have been deleted");
                Assert.AreNotEqual(id, 4, "entry #4 should have been deleted");
            }
        }