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 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); }
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"); } }
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); }
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"); }
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"); }
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); }
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); }
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"); }
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"); }
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"); } }
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 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"); } }
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"); } }
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 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"); }
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(); } }
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; } } } }
public void InsertInStaleViewConflictingWithInsertInNewView() { ReplicatedTableConfigurationServiceV2 configServiceOne, configServiceTwo; // setup: Acc0 Acc1 // stale view = [None] -> [RW] // new view = [WO] -> [RW] 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"; var workerOne = new ReplicatedTable(this.repTable.TableName, configServiceOne); var workerTwo = new ReplicatedTable(this.repTable.TableName, configServiceTwo); string accountNameToTamper = this.rtableTestConfiguration.StorageInformation.AccountNames[1]; Console.WriteLine("RunHttpManglerBehaviorHelper(): accountNameToTamper={0}", accountNameToTamper); TableResult oldInsertResult = null; TableResult newInsertResult = null; bool triggerInsertWithNewView = false; bool oldInsertResume = false; // Start new newInsertTask in wait var newInsertTask = Task.Run(() => { while (!triggerInsertWithNewView) { Thread.Sleep(100); } /* * 2 - Executes after step 1 below: * WorkerTwo => Insert a new row using new view */ var customer = new CustomerEntity(firstName, lastName); customer.Email = "workerTwo"; TableOperation operation = TableOperation.Insert(customer); newInsertResult = workerTwo.Execute(operation); // Signal old Insert to resume oldInsertResume = true; }); // Delay behavior ProxyBehavior[] behaviors = { TamperBehaviors.TamperAllRequestsIf( (session => { // => trigger new insert to start triggerInsertWithNewView = true; // Delaying commit to the Tail by stale view Update while (!oldInsertResume) { Thread.Sleep(100); } }), (session => { var body = session.GetRequestBodyAsString(); // Commit 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\":false")) { return(true); } return(false); })), }; /* * 1 - WorkerOne => update an entry using the stale View */ using (new HttpMangler(false, behaviors)) { var customer = new CustomerEntity(firstName, lastName); customer.Email = "workerOne"; TableOperation operation = TableOperation.Insert(customer); oldInsertResult = workerOne.Execute(operation); } // Wait on new Insert to finish newInsertTask.Wait(); // Expected behavior: // Thread_1 (stale ViewId) Assert.IsNotNull(oldInsertResult, "oldInsertResult = null"); Assert.AreEqual((int)HttpStatusCode.NoContent, oldInsertResult.HttpStatusCode, "oldInsertResult.HttpStatusCode mismatch"); Console.WriteLine("Insert in stale View Succeeded with HttpStatus={0}", oldInsertResult.HttpStatusCode); // Thread_2 (new ViewId) Assert.IsNotNull(newInsertResult, "newInsertResult = null"); Assert.AreEqual((int)HttpStatusCode.Conflict, newInsertResult.HttpStatusCode, "newUpdateResult.HttpStatusCode mismatch"); Console.WriteLine("Insert in new View failed with HttpStatus={0}", newInsertResult.HttpStatusCode); var currCustomer = RetrieveCustomer(firstName, lastName, workerTwo); Assert.AreEqual(staleViewId, currCustomer._rtable_ViewId, "customer._rtable_ViewId mismatch"); Assert.AreEqual("workerOne", currCustomer.Email, "customer.Email mismatch"); Console.WriteLine("workerOne write Succeeded"); Console.WriteLine("workerTwo got Conflict because row was lock."); }