/// <summary> /// Create the contents of the RTable configuration, and upload it to the appropriate container and blob. /// </summary> private void UploadRTableConfigToBlob(int viewId, bool convertXStoreTableMode, int numberOfBlobs) { string blobName = this.configurationInfos.FirstOrDefault().BlobPath; int index = blobName.IndexOf("/"); blobName = blobName.Substring(index + 1); // +1 to acording for "/" Console.WriteLine("Uploading RTable config ..."); for (int blobIndex = 0; blobIndex < numberOfBlobs; blobIndex++) { string connectionString; CloudBlobClient cloudBloblClient = this.GenerateCloudBlobClient( this.rtableTestConfiguration.StorageInformation.AccountNames[blobIndex], this.rtableTestConfiguration.StorageInformation.AccountKeys[blobIndex], this.rtableTestConfiguration.StorageInformation.DomainName, out connectionString); CloudBlobContainer container = cloudBloblClient.GetContainerReference(this.rtableTestConfiguration.RTableInformation.ContainerName); container.CreateIfNotExists(); CloudBlockBlob blockBlob = container.GetBlockBlobReference(blobName); ReplicatedTableConfigurationStore configuration = this.GetRTableConfiguration(viewId, convertXStoreTableMode); string jsonConfigText = JsonStore <ReplicatedTableConfigurationStore> .Serialize(configuration); blockBlob.UploadText(jsonConfigText); } }
public void TestReplicatedTableConfigurationStoreEquality() { var conf1 = new ReplicatedTableConfigurationStore { ViewId = 5, }; Assert.IsFalse(conf1.Equals(null)); // - vs. different viewId var conf2 = new ReplicatedTableConfigurationStore { ViewId = 6, }; Assert.IsFalse(conf1.Equals(conf2)); // vs. same viewId var conf3 = new ReplicatedTableConfigurationStore { ViewId = 5, }; Assert.IsTrue(conf1.Equals(conf3)); }
/// <summary> /// Helper function to return the RTableConfig /// Note that even if 3 (say) accounts are specified in the xml, /// the test codes can specify that only accounts #0 and #2 are used to construct the RTable. /// </summary> /// <param name="viewId"></param> /// <param name="convertXStoreTableMode"></param> /// <param name="readViewHeadIndex"></param> /// <returns></returns> private ReplicatedTableConfiguration GetRTableConfiguration(long viewId, bool convertXStoreTableMode, int readViewHeadIndex = 0) { if (viewId <= 0) { throw new Exception(string.Format("GetRTableConfigText() was called with invalid viewId {0}", viewId)); } // 1 - Create a default view var viewConfig = new ReplicatedTableConfigurationStore { ViewId = viewId, ReadViewHeadIndex = readViewHeadIndex, }; int numberOfStorageAccounts = this.rtableTestConfiguration.StorageInformation.AccountNames.Count(); for (int i = 0; i < this.actualStorageAccountsUsed.Count; i++) { int index = this.actualStorageAccountsUsed[i]; if (index < 0 || index > numberOfStorageAccounts) { throw new Exception(string.Format("this.actualStorageAccountsUsed[{0}] = {1} is out of range.", i, index)); } ReplicaInfo replica = new ReplicaInfo { StorageAccountName = this.rtableTestConfiguration.StorageInformation.AccountNames[index], Status = ReplicaStatus.ReadWrite, }; if (readViewHeadIndex != 0 && i < readViewHeadIndex) { replica.Status = ReplicaStatus.WriteOnly; } viewConfig.ReplicaChain.Add(replica); } // 2 - Create a default table config. that references the "DefaultView" var tableConfig = new ReplicatedTableConfiguredTable { TableName = DefaultTableConfigName, ViewName = DefaultViewName, ConvertToRTable = convertXStoreTableMode, UseAsDefault = true, }; // 3 - Create the final RTable configuration ReplicatedTableConfiguration configuration = new ReplicatedTableConfiguration(); configuration.SetView(DefaultViewName, viewConfig); configuration.SetTable(tableConfig); return(configuration); }
private void SetReadViewTailIndex(int index) { View view = this.configurationWrapper.GetWriteView(); ReplicatedTableConfiguration config; ReplicatedTableQuorumReadResult readStatus = this.configurationService.RetrieveConfiguration(out config); Assert.IsTrue(readStatus.Code == ReplicatedTableQuorumReadCode.Success); ReplicatedTableConfigurationStore viewConfg = config.GetView(view.Name); viewConfg.ReadViewTailIndex = index; this.configurationService.UpdateConfiguration(config); }
private void SetConfigViewIdAndIgnoreHigherViewIdRowsFlag(long viewId, bool ignoreHigherViewIdRowsFlag) { View view = this.configurationWrapper.GetWriteView(); ReplicatedTableConfiguration config; ReplicatedTableQuorumReadResult readStatus = this.configurationService.RetrieveConfiguration(out config); Assert.IsTrue(readStatus.Code == ReplicatedTableQuorumReadCode.Success); ReplicatedTableConfigurationStore viewConfg = config.GetView(view.Name); viewConfg.ViewId = viewId; config.SetIgnoreHigherViewIdRowsFlag(ignoreHigherViewIdRowsFlag); this.configurationService.UpdateConfiguration(config); }
/// <summary> /// Modify the contents of the RTable configuration blob to use the updated viewId /// </summary> /// <param name="updatedViewId"></param> /// <param name="convertXStoreTableMode"></param> /// <param name="readViewHeadIndex"></param> private void ModifyConfigurationBlob(long updatedViewId, bool convertXStoreTableMode, int readViewHeadIndex, List <int> blobIndexes) { try { foreach (var blobIndex in blobIndexes) { Console.WriteLine("ModifyConfigurationBlob#{0} ...", blobIndex); string blobName = this.configurationInfos[blobIndex].BlobPath; int index = blobName.IndexOf("/"); blobName = blobName.Substring(index + 1); // +1 to acording for "/" Console.WriteLine("Configuration blobname = {0}", blobName); string connectionString; CloudBlobClient cloudBloblClient = this.GenerateCloudBlobClient( this.rtableTestConfiguration.StorageInformation.AccountNames[blobIndex], this.rtableTestConfiguration.StorageInformation.AccountKeys[blobIndex], this.rtableTestConfiguration.StorageInformation.DomainName, out connectionString); CloudBlobContainer container = cloudBloblClient.GetContainerReference( this.rtableTestConfiguration.RTableInformation.ContainerName); CloudBlockBlob blockBlob = container.GetBlockBlobReference(blobName); string currentConfigText = blockBlob.DownloadText(); Console.WriteLine("currentConfigText = {0}", currentConfigText); ReplicatedTableConfigurationStore configuration = this.GetRTableConfiguration(updatedViewId, convertXStoreTableMode, readViewHeadIndex); string updatedConfigText = JsonStore <ReplicatedTableConfigurationStore> .Serialize(configuration); Console.WriteLine("updatedConfigText = {0}", updatedConfigText); blockBlob.UploadText(updatedConfigText); } } catch (Exception ex) { Console.WriteLine("ModifyConfigurationBlob() encountered exception {0}", ex.ToString()); throw; } }
private void InsertHeadInView(string viewName, int leaseDuration, ReplicatedTableConfigurationServiceV2 configService) { // Download config using provided instance ReplicatedTableConfiguration config; ReplicatedTableQuorumReadResult readStatus = configurationService.RetrieveConfiguration(out config); Assert.IsTrue(readStatus.Code == ReplicatedTableQuorumReadCode.Success); // Add the Head ReplicatedTableConfigurationStore viewConfg = config.GetView(viewName); viewConfg.ReplicaChain[0].Status = ReplicaStatus.WriteOnly; viewConfg.ViewId++; config.LeaseDuration = leaseDuration; // Upload config using provided instance configService.UpdateConfiguration(config); }
public void TestGetCurrentReplicaChain() { var conf = new ReplicatedTableConfigurationStore(); conf.ReplicaChain.Add(new ReplicaInfo { Status = ReplicaStatus.None, }); conf.ReplicaChain.Add(new ReplicaInfo { Status = ReplicaStatus.ReadOnly, }); conf.ReplicaChain.Add(new ReplicaInfo { Status = ReplicaStatus.None, }); conf.ReplicaChain.Add(new ReplicaInfo { Status = ReplicaStatus.ReadWrite, }); conf.ReplicaChain.Add(new ReplicaInfo { Status = ReplicaStatus.WriteOnly, }); conf.ReplicaChain.Add(new ReplicaInfo { Status = ReplicaStatus.None, }); conf.ReplicaChain.Add(new ReplicaInfo { Status = ReplicaStatus.ReadWrite, }); conf.ReplicaChain.Add(new ReplicaInfo { Status = ReplicaStatus.None, }); // only Active replicas are returned i.e. RO, WO and RW var chain = conf.GetCurrentReplicaChain(); Assert.IsFalse(chain.Any(r => r.Status == ReplicaStatus.None)); }
/// <summary> /// Helper function to return the RTableConfig /// Note that even if 3 (say) accounts are specified in the xml, /// the test codes can specify that only accounts #0 and #2 are used to construct the RTable. /// </summary> /// <param name="viewId"></param> /// <param name="convertXStoreTableMode"></param> /// <param name="readViewHeadIndex"></param> /// <returns></returns> private ReplicatedTableConfigurationStore GetRTableConfiguration(long viewId, bool convertXStoreTableMode, int readViewHeadIndex = 0) { if (viewId <= 0) { throw new Exception(string.Format("GetRTableConfigText() was called with invalid viewId {0}", viewId)); } ReplicatedTableConfigurationStore configuration = new ReplicatedTableConfigurationStore(); configuration.ViewId = viewId; configuration.ReadViewHeadIndex = readViewHeadIndex; configuration.ConvertXStoreTableMode = convertXStoreTableMode; int numberOfStorageAccounts = this.rtableTestConfiguration.StorageInformation.AccountNames.Count(); for (int i = 0; i < this.actualStorageAccountsUsed.Count; i++) { int index = this.actualStorageAccountsUsed[i]; if (index < 0 || index > numberOfStorageAccounts) { throw new Exception(string.Format("this.actualStorageAccountsUsed[{0}] = {1} is out of range.", i, index)); } ReplicaInfo replica = new ReplicaInfo(); replica.StorageAccountName = this.rtableTestConfiguration.StorageInformation.AccountNames[index]; if (readViewHeadIndex != 0) { //If the read view head index is not 0, this means we are introducing 1 or more replicas at the head. For //each such replica, update the view id in which it was added to the write view of the chain if (i < readViewHeadIndex) { replica.ViewInWhichAddedToChain = viewId; } } configuration.ReplicaChain.Add(replica); } return(configuration); }
private void EnableTailInView(string viewName, int leaseDuration, ReplicatedTableConfigurationServiceV2 configService) { // Download config using provided instance ReplicatedTableConfiguration config; ReplicatedTableQuorumReadResult readStatus = configurationService.RetrieveConfiguration(out config); Assert.IsTrue(readStatus.Code == ReplicatedTableQuorumReadCode.Success); // Enable the Tail ReplicatedTableConfigurationStore viewConfg = config.GetView(viewName); Assert.IsTrue(viewConfg.ReplicaChain.Count == 2); Assert.IsTrue(viewConfg.ReadViewTailIndex == 0); viewConfg.ReplicaChain[1].Status = ReplicaStatus.ReadWrite; viewConfg.ViewId++; config.LeaseDuration = leaseDuration; // Upload config using provided instance configService.UpdateConfiguration(config); }
private void UploadHeadTailView(string viewName, int leaseDuration, ReplicatedTableConfigurationServiceV2 configService) { // Download config using provided instance ReplicatedTableConfiguration config; ReplicatedTableQuorumReadResult readStatus = configService.RetrieveConfiguration(out config); Assert.IsTrue(readStatus.Code == ReplicatedTableQuorumReadCode.Success); // [Head] -> [Tail] ReplicatedTableConfigurationStore viewConfg = config.GetView(viewName); Assert.IsTrue(viewConfg.GetCurrentReplicaChain().Count == 2); // Force Reads on Head replica viewConfg.ReadViewTailIndex = 0; viewConfg.ViewId++; config.LeaseDuration = leaseDuration; // Upload config using provided instance configService.UpdateConfiguration(config); }
protected void UpdateConfiguration(List <ReplicaInfo> replicaChain, int readViewHeadIndex, bool convertXStoreTableMode = false, long viewId = 0) { for (int i = 0; i < replicaChain.Count; i++) { replicaChain[i].Status = ReplicaStatus.ReadWrite; if (readViewHeadIndex != 0 && i < readViewHeadIndex) { replicaChain[i].Status = ReplicaStatus.WriteOnly; } } // - view config var viewConfig = new ReplicatedTableConfigurationStore { ViewId = viewId, ReadViewHeadIndex = readViewHeadIndex, ReplicaChain = replicaChain, }; // - table config var tableConfig = new ReplicatedTableConfiguredTable { TableName = DefaultTableConfigName, ViewName = DefaultViewName, ConvertToRTable = convertXStoreTableMode, UseAsDefault = true, }; // - Update RTable configuration ReplicatedTableConfiguration configuration = new ReplicatedTableConfiguration(); configuration.SetView(DefaultViewName, viewConfig); configuration.SetTable(tableConfig); this.configurationService.UpdateConfiguration(configuration, false); }
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."); }
public void CaseNoRepair_ReadRowFromNewViewCutOffTailUpdateRow() { string firstName = "FirstName"; string lastName = "LastName"; TableOperation operation; TableResult result; CustomerEntity customer; // - View has 2 replicas ? View view = this.configurationWrapper.GetWriteView(); Assert.IsTrue(view.Chain.Count == 2, "expects 2 replicas only!"); // 1 - [None]->[RW] ReplicatedTableConfiguration config; this.configurationService.RetrieveConfiguration(out config); ReplicatedTableConfigurationStore viewConfg = config.GetView(view.Name); viewConfg.ReplicaChain[0].Status = ReplicaStatus.None; viewConfg.ViewId++; this.configurationService.UpdateConfiguration(config); // *** - Insert entries in old viewId var rtable = new ReplicatedTable(this.repTable.TableName, this.configurationService); for (int i = 0; i < 3; i++) { customer = new CustomerEntity(firstName + i, lastName + i); operation = TableOperation.Insert(customer); rtable.Execute(operation); } // 2 - Insert Head => [WO]->[RW] this.configurationService.RetrieveConfiguration(out config); viewConfg = config.GetView(view.Name); viewConfg.ReplicaChain[0].Status = ReplicaStatus.WriteOnly; viewConfg.ViewId++; this.configurationService.UpdateConfiguration(config); // *** - Insert entries in new viewId rtable = new ReplicatedTable(this.repTable.TableName, this.configurationService); for (int i = 10; i < 13; i++) { customer = new CustomerEntity(firstName + i, lastName + i); operation = TableOperation.Insert(customer); rtable.Execute(operation); } // => Read old entry - from Tail - int entryId = 10; operation = TableOperation.Retrieve <CustomerEntity>(firstName + entryId, lastName + entryId); result = rtable.Execute(operation); // 3 - Cut-off Tail without repairing replicas configurationService.RetrieveConfiguration(out config); viewConfg = config.GetView(view.Name); viewConfg.ReplicaChain[0].Status = ReplicaStatus.ReadWrite; viewConfg.ReplicaChain[1].Status = ReplicaStatus.None; viewConfg.ViewId++; this.configurationService.UpdateConfiguration(config); // => Update the row 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"); }
public void RepairTableWontRepairRowsWithHigherViewIdWhenIgnoreHigherViewIdRowsFlagIsSet() { TableOperation operation; TableResult result; CustomerEntity customer; // - View has 2 replicas ? View view = this.configurationWrapper.GetWriteView(); Assert.IsTrue(view.Chain.Count == 2, "expects 2 replicas only!"); // - Remove the Head, [None]->[RW] RemoveHeadFromView(view.Name, 600, this.configurationService); // 1 - Insert entries in old viewId 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); } // 2 - Increase viewId => so we can create rows with higher viewId // - Update entry #5 and #8 in new viewId ReplicatedTableConfiguration config; ReplicatedTableQuorumReadResult readStatus = this.configurationService.RetrieveConfiguration(out config); Assert.IsTrue(readStatus.Code == ReplicatedTableQuorumReadCode.Success); ReplicatedTableConfigurationStore viewConfg = config.GetView(view.Name); viewConfg.ViewId += 100; this.configurationService.UpdateConfiguration(config); 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"); } // 3 - Restore previous viewId, and, // - Set 'IgnoreHigherViewIdRows' flag so we ignore rows with higher viewIds readStatus = this.configurationService.RetrieveConfiguration(out config); Assert.IsTrue(readStatus.Code == ReplicatedTableQuorumReadCode.Success); viewConfg = config.GetView(view.Name); viewConfg.ViewId -= 100; config.SetIgnoreHigherViewIdRowsFlag(true); this.configurationService.UpdateConfiguration(config); 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 !"); } // 4 - Now insert a Head [WO]->[RW] // - Then, call RepairTable ... InsertHeadInView(view.Name, 600, this.configurationService); ReconfigurationStatus status = rtable.RepairTable(0, null); Assert.AreEqual(status, ReconfigurationStatus.PARTIAL_FAILURE, "rows with higher viewId should not be repaired"); // 5 - Check rows with higher viewId still NotFound, even after repair ... 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 !"); } }
public void RowsWithHigherViewIdNotFoundWhenIgnoreHigherViewIdRowsFlagIsSet() { long higherViewId = 200; // Configure RTable with a higher ViewId this.UpdateConfiguration(replicas, 0, false, higherViewId); string firstName = "FirstName02"; string lastName = "LastName02"; string email = "*****@*****.**"; string phone = "1-800-123-0001"; // Insert entity CustomerEntity newCustomer = new CustomerEntity(firstName, lastName); newCustomer.Email = email; newCustomer.PhoneNumber = phone; TableOperation operation = TableOperation.Insert(newCustomer); TableResult result = this.repTable.Execute(operation); Assert.AreNotEqual(null, result, "result = null"); Assert.AreEqual((int)HttpStatusCode.NoContent, result.HttpStatusCode, "result.HttpStatusCode mismatch"); // Retrieve entity operation = TableOperation.Retrieve <CustomerEntity>(firstName, lastName); TableResult retrievedResult = this.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"); CustomerEntity customer = (CustomerEntity)retrievedResult.Result; Assert.AreEqual(higherViewId, customer._rtable_ViewId, "customer._rtable_ViewId mismatch"); Assert.AreEqual(newCustomer.PhoneNumber, customer.PhoneNumber, "customer.PhoneNumber mismatch"); Assert.AreEqual(newCustomer.Email, customer.Email, "customer.Email mismatch"); Console.WriteLine("Successfully retrieved the entity"); // // Change RTable config to a lower viewId, and force RTable to ignore rows with higher viewId // long currentViewId = 10; Assert.IsTrue(currentViewId < higherViewId, "expected currentViewId < higherViewId !"); Console.WriteLine("Changing the viewId to currentViewId {0}", currentViewId); ReplicatedTableConfiguration config; ReplicatedTableQuorumReadResult readStatus = this.configurationService.RetrieveConfiguration(out config); Assert.IsTrue(readStatus.Code == ReplicatedTableQuorumReadCode.Success); ReplicatedTableConfigurationStore viewConfg = config.GetView(this.configurationWrapper.GetWriteView().Name); viewConfg.ViewId = currentViewId; config.SetIgnoreHigherViewIdRowsFlag(true); this.configurationService.UpdateConfiguration(config); // // Retrieve with lower viewId // Console.WriteLine("\nCalling Retrieve with lower ViewId..."); operation = TableOperation.Retrieve <CustomerEntity>(firstName, lastName); try { retrievedResult = this.repTable.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 !"); } // // Replace with lower viewId // Console.WriteLine("\nCalling Replace with lower ViewId..."); operation = TableOperation.Replace(customer); try { retrievedResult = this.repTable.Execute(operation); Assert.AreNotEqual(null, retrievedResult, "retrievedResult = null"); Assert.AreEqual((int)HttpStatusCode.NotFound, retrievedResult.HttpStatusCode, "retrievedResult.HttpStatusCode mismatch"); } catch (ReplicatedTableStaleViewException) { Assert.Fail("Replace() is expected to NotFound the row, but got RTableStaleViewException !"); } // // InsertOrMerge with lower viewId // Console.WriteLine("\nCalling InsertOrMerge with lower ViewId..."); operation = TableOperation.InsertOrMerge(customer); try { retrievedResult = this.repTable.Execute(operation); Assert.AreNotEqual(null, retrievedResult, "retrievedResult = null"); Assert.AreEqual((int)HttpStatusCode.Conflict, retrievedResult.HttpStatusCode, "retrievedResult.HttpStatusCode mismatch"); } catch (ReplicatedTableStaleViewException) { Assert.Fail("InsertOrMerge() is expected to get Conflict, but got RTableStaleViewException !"); } // // InsertOrReplace with lower viewId // Console.WriteLine("\nCalling InsertOrReplace with lower ViewId..."); operation = TableOperation.InsertOrReplace(customer); try { retrievedResult = this.repTable.Execute(operation); Assert.AreNotEqual(null, retrievedResult, "retrievedResult = null"); Assert.AreEqual((int)HttpStatusCode.Conflict, retrievedResult.HttpStatusCode, "retrievedResult.HttpStatusCode mismatch"); } catch (ReplicatedTableStaleViewException) { Assert.Fail("InsertOrReplace() is expected to get Conflict, but got RTableStaleViewException !"); } // // Merge with lower viewId // Console.WriteLine("\nCalling Merge with lower ViewId..."); operation = TableOperation.Merge(customer); try { retrievedResult = this.repTable.Execute(operation); Assert.AreNotEqual(null, retrievedResult, "retrievedResult = null"); Assert.AreEqual((int)HttpStatusCode.NotFound, retrievedResult.HttpStatusCode, "retrievedResult.HttpStatusCode mismatch"); } catch (ReplicatedTableStaleViewException) { Assert.Fail("Merge() is expected to NotFound the row, but got RTableStaleViewException !"); } // // Delete with lower viewId // Console.WriteLine("\nCalling Delete with lower ViewId..."); operation = TableOperation.Delete(customer); try { retrievedResult = this.repTable.Execute(operation); Assert.AreNotEqual(null, retrievedResult, "retrievedResult = null"); Assert.AreEqual((int)HttpStatusCode.NotFound, retrievedResult.HttpStatusCode, "retrievedResult.HttpStatusCode mismatch"); } catch (ReplicatedTableStaleViewException) { Assert.Fail("Delete() is expected to NotFound the row, but got RTableStaleViewException !"); } }