public static View InitFromConfigVer2(string name, ReplicatedTableConfigurationStore configurationStore, Action <ReplicaInfo> SetConnectionString) { View view = new View(name); if (configurationStore != null) { view.ViewId = configurationStore.ViewId; foreach (ReplicaInfo replica in configurationStore.GetCurrentReplicaChain()) { SetConnectionString(replica); CloudTableClient tableClient = ReplicatedTableConfigurationManager.GetTableClientForReplica(replica); if (tableClient == null) { // All replicas MUST exist or View is not relevant view.Chain.Clear(); ReplicatedTableLogger.LogError("ViewName={0} could not load replica ({1})", name, replica); break; } view.Chain.Add(new Tuple <ReplicaInfo, CloudTableClient>(replica, tableClient)); } // Infered: first readable replica view.ReadHeadIndex = view.Chain.FindIndex(tuple => tuple.Item1.IsReadable()); } return(view); }
private void ThrowIfAnyPartitionViewHasManyReplicasInConversionMode(ReplicatedTableConfiguredTable config) { if (config.ConvertToRTable == false || config.PartitionsToViewMap == null) { return; } /* * Key = "" - View = "" => Ignore * Key = "" - View = "viewName" => Ignore * Key = "X" - View = "" => Should never happen (already tken care by the flow) * Key = "Y" - View = "viewName" => 'viewName' can't have more than 1 replica */ foreach (var entry in config.PartitionsToViewMap.Where(e => !string.IsNullOrEmpty(e.Key))) { string viewName = entry.Value; ReplicatedTableConfigurationStore viewConfig = GetView(viewName); // Assert (viewConfig != null) // In Conversion mode, view should not have more than 1 replica List <ReplicaInfo> chainList = viewConfig.GetCurrentReplicaChain(); if (chainList.Count <= 1) { continue; } var msg = string.Format("Table:\'{0}\' refers a partition view:\'{1}\' with more than 1 replica while in Conversion mode!", config.TableName, viewName); throw new Exception(msg); } }
private void ThrowIfViewIsNotValid(string viewName, ReplicatedTableConfigurationStore config) { if (config.ReplicaChain == null || config.ReplicaChain.Any(replica => replica == null)) { var msg = string.Format("View:\'{0}\' has a null replica(s) !!!", viewName); throw new Exception(msg); } config.ThrowIfChainIsNotValid(viewName); }
public void UpdateConfiguration(List <ReplicaInfo> replicaChain, int readViewHeadIndex, bool convertXStoreTableMode = false) { Parallel.ForEach(this.blobs, blob => { ReplicatedTableConfigurationStore configurationStore = null; long newViewId = 0; if (!CloudBlobHelpers.TryReadBlob <ReplicatedTableConfigurationStore>(blob.Value, out configurationStore)) { //This is the first time we are uploading the config configurationStore = new ReplicatedTableConfigurationStore(); } newViewId = configurationStore.ViewId + 1; configurationStore.LeaseDuration = Constants.LeaseDurationInSec; configurationStore.Timestamp = DateTime.UtcNow; configurationStore.ReplicaChain = replicaChain; configurationStore.ReadViewHeadIndex = readViewHeadIndex; configurationStore.ConvertXStoreTableMode = convertXStoreTableMode; configurationStore.ViewId = newViewId; //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 (readViewHeadIndex != 0) { for (int i = 0; i < readViewHeadIndex; i++) { replicaChain[i].ViewInWhichAddedToChain = newViewId; } } try { //Step 1: Delete the current configuration blob.Value.UploadText(Constants.ConfigurationStoreUpdatingText); //Step 2: Wait for L + CF to make sure no pending transaction working on old views // Chunzhi: removed this, original code hangs here // Matt: restore this: it's essential for consistency. Thread.Sleep(TimeSpan.FromSeconds(Constants.LeaseDurationInSec + Constants.ClockFactorInSec)); //Step 3: Update new config blob.Value.UploadText(JsonStore <ReplicatedTableConfigurationStore> .Serialize(configurationStore)); } catch (StorageException e) { ReplicatedTableLogger.LogError("Updating the blob: {0} failed. Exception: {1}", blob.Value, e.Message); } }); //Invalidate the lastViewRefreshTime so that updated views get returned this.lastViewRefreshTime = DateTime.MinValue; }
public static View InitFromConfigVer1(string name, ReplicatedTableConfigurationStore configurationStore, Action <ReplicaInfo> SetConnectionString) { View view = new View(name); if (configurationStore != null) { view.ViewId = configurationStore.ViewId; view.ReadHeadIndex = configurationStore.ReadViewHeadIndex; view.RefreshTime = DateTime.UtcNow; foreach (ReplicaInfo replica in configurationStore.ReplicaChain) { if (replica == null) { continue; } SetConnectionString(replica); CloudTableClient tableClient = ReplicatedTableConfigurationManager.GetTableClientForReplica(replica); if (tableClient == null) { // All replicas MUST exist or View is not relevant view.Chain.Clear(); ReplicatedTableLogger.LogError("ViewName={0} could not load replica ({1})", name, replica); break; } view.Chain.Add(new Tuple <ReplicaInfo, CloudTableClient>(replica, tableClient)); } // If not configured use Tail. Chain must be defined at this point, so don't move this code up! view.ReadTailIndex = configurationStore.ReadViewTailIndex; if (view.ReadTailIndex < 0) { view.ReadTailIndex = view.TailIndex; } if (!view.IsEmpty) { ReplicaInfo head = view.GetReplicaInfo(0); head.Status = ReplicaStatus.WriteOnly; if (view.IsStable) { head.Status = ReplicaStatus.ReadWrite; } } } return(view); }
public override bool Equals(object obj) { ReplicatedTableConfigurationStore other = obj as ReplicatedTableConfigurationStore; if (other == null) { return(false); } if (this.ViewId == other.ViewId) { return(true); } return(false); }
/* * View APIs: */ public void SetView(string viewName, ReplicatedTableConfigurationStore config) { if (string.IsNullOrEmpty(viewName)) { throw new ArgumentNullException("viewName"); } if (config == null) { throw new ArgumentNullException("config"); } ThrowIfViewIsNotValid(viewName, config); // In case this is an update to an existing view, // the view should not break any existing constraint. ThrowIfViewBreaksTableConstraint(viewName, config); viewMap.Remove(viewName); viewMap.Add(viewName, config); }
private void ThrowIfViewBreaksTableConstraint(string viewName, ReplicatedTableConfigurationStore config) { foreach (ReplicatedTableConfiguredTable table in tableList) { if (table.ConvertToRTable == false || !table.IsViewReferenced(viewName)) { continue; } // Conversion mode: view shoud not have more than 1 replica List <ReplicaInfo> chainList = config.GetCurrentReplicaChain(); if (chainList.Count <= 1) { continue; } var msg = string.Format("Table:\'{0}\' should not have a view:\'{1}\' with more than 1 replica since it is in Conversion mode!", table.TableName, viewName); throw new Exception(msg); } }
public static View InitFromConfigVer2(string name, ReplicatedTableConfigurationStore configurationStore, Action <ReplicaInfo> SetConnectionString) { View view = new View(name); if (configurationStore != null) { view.ViewId = configurationStore.ViewId; view.RefreshTime = DateTime.UtcNow; foreach (ReplicaInfo replica in configurationStore.GetCurrentReplicaChain()) { SetConnectionString(replica); CloudTableClient tableClient = ReplicatedTableConfigurationManager.GetTableClientForReplica(replica); if (tableClient == null) { // All replicas MUST exist or View is not relevant view.Chain.Clear(); ReplicatedTableLogger.LogError("ViewName={0} could not load replica ({1})", name, replica); break; } view.Chain.Add(new Tuple <ReplicaInfo, CloudTableClient>(replica, tableClient)); } // Infered: first readable replica view.ReadHeadIndex = view.Chain.FindIndex(tuple => tuple.Item1.IsReadable()); // If not configured use Tail. Chain must be defined at this point, so don't move this code up! view.ReadTailIndex = configurationStore.ReadViewTailIndex; if (view.ReadTailIndex < 0) { view.ReadTailIndex = view.TailIndex; } } return(view); }
private void ThrowIfViewHasManyReplicasInConvertionMode(ReplicatedTableConfiguredTable config) { if (config.ConvertToRTable == false || string.IsNullOrEmpty(config.ViewName)) { return; } ReplicatedTableConfigurationStore viewConfig = GetView(config.ViewName); // Assert (viewConfig != null) // In Convertion mode, view should not have more than 1 replica List <ReplicaInfo> chainList = viewConfig.GetCurrentReplicaChain(); if (chainList.Count <= 1) { return; } var msg = string.Format("Table:\'{0}\' refers a view:\'{1}\' with more than 1 replica while in Convertion mode!", config.TableName, config.ViewName); throw new Exception(msg); }
public static View InitFromConfigVer1(string name, ReplicatedTableConfigurationStore configurationStore, Action<ReplicaInfo> SetConnectionString) { View view = new View(name); if (configurationStore != null) { view.ViewId = configurationStore.ViewId; view.ReadHeadIndex = configurationStore.ReadViewHeadIndex; foreach (ReplicaInfo replica in configurationStore.ReplicaChain) { SetConnectionString(replica); CloudTableClient tableClient = ReplicatedTableConfigurationManager.GetTableClientForReplica(replica); if (replica != null && tableClient != null) { view.Chain.Add(new Tuple<ReplicaInfo, CloudTableClient>(replica, tableClient)); } } if (!view.IsEmpty) { ReplicaInfo head = view.GetReplicaInfo(0); head.Status = ReplicaStatus.WriteOnly; if (view.IsStable) { head.Status = ReplicaStatus.ReadWrite; } } } return view; }
private void ThrowIfViewIsNotValid(string viewName, ReplicatedTableConfigurationStore config) { if (config.ReplicaChain == null || config.ReplicaChain.Any(replica => replica == null)) { var msg = string.Format("View:\'{0}\' has a null replica(s) !!!", viewName); throw new Exception(msg); } List <ReplicaInfo> chainList = config.GetCurrentReplicaChain(); if (chainList.Any()) { /* RULE 1: * ======= * Read replicas rule: * - [R] replicas are contiguous from Tail backwards * - [R] replica count >= 1 */ string readPattern = "^W*R+$"; /* RULE 2: * ======= * Write replicas rule: * - [W] replicas are contiguous from Head onwards * - [W] replica count = 0 or = ChainLength */ string writePattern = "^((R+)|(W+))$"; // Get replica sequences string readSeq = ""; string writeSeq = ""; foreach (var replica in chainList) { // Read sequence: if (replica.IsReadable()) { readSeq += "R"; } else { readSeq += "W"; } // Write sequence: if (replica.IsWritable()) { writeSeq += "W"; } else { writeSeq += "R"; } } // Verify RULE 1: if (!Regex.IsMatch(readSeq, readPattern)) { var msg = string.Format("View:\'{0}\' has invalid Read chain:\'{1}\' !!!", viewName, readSeq); throw new Exception(msg); } // Verify RULE 2: if (!Regex.IsMatch(writeSeq, writePattern)) { var msg = string.Format("View:\'{0}\' has invalid Write chain:\'{1}\' !!!", viewName, writeSeq); throw new Exception(msg); } } }
private static void MoveReplicaToFrontAndSetViewToReadOnly(string viewName, ReplicatedTableConfigurationStore conf, string storageAccountName) { List<ReplicaInfo> list = conf.ReplicaChain; int matchIndex = list.FindIndex(r => r.StorageAccountName == storageAccountName); if (matchIndex == -1) { return; } // - Ensure its status is *None* ReplicaInfo candidateReplica = list[matchIndex]; candidateReplica.Status = ReplicaStatus.None; // - Move it to the front of the chain list.RemoveAt(matchIndex); list.Insert(0, candidateReplica); // Set all active replicas to *ReadOnly* foreach (ReplicaInfo replica in conf.GetCurrentReplicaChain()) { if (replica.Status == ReplicaStatus.WriteOnly) { var msg = string.Format("View:\'{0}\' : can't set a WriteOnly replica to ReadOnly !!!", viewName); ReplicatedTableLogger.LogError(msg); throw new Exception(msg); } replica.Status = ReplicaStatus.ReadOnly; } // Update view id conf.ViewId++; }
private static void EnableWriteOnReplicas(string viewName, ReplicatedTableConfigurationStore conf, string storageAccountName) { List<ReplicaInfo> list = conf.ReplicaChain; if (!list.Any() || list[0].StorageAccountName != storageAccountName) { return; } // First, enable Write on all replicas foreach (ReplicaInfo replica in conf.GetCurrentReplicaChain()) { replica.Status = ReplicaStatus.ReadWrite; } // Then, set the head to WriteOnly list[0].Status = ReplicaStatus.WriteOnly; // one replica chain ? Force to ReadWrite if (conf.GetCurrentReplicaChain().Count == 1) { list[0].Status = ReplicaStatus.ReadWrite; } // Update view id conf.ViewId++; }
private static void EnableReadWriteOnReplicas(string viewName, ReplicatedTableConfigurationStore conf, string storageAccountName) { List<ReplicaInfo> list = conf.ReplicaChain; if (!list.Any() || list[0].StorageAccountName != storageAccountName || list[0].Status != ReplicaStatus.WriteOnly) { return; } list[0].Status = ReplicaStatus.ReadWrite; // Update view id conf.ViewId++; }
/// <summary> /// Parses the RTable configuration blobs. /// Returns the list of views, the list of configured tables and the lease duration. /// If null is returned, then the value of tableConfigList/leaseDuration are not relevant. /// </summary> /// <param name="blobs"></param> /// <param name="useHttps"></param> /// <param name="tableConfigList"></param> /// <param name="leaseDuration"></param> /// <returns></returns> public List <View> ParseBlob( List <CloudBlockBlob> blobs, Action <ReplicaInfo> SetConnectionString, out List <ReplicatedTableConfiguredTable> tableConfigList, out int leaseDuration, out Guid configId) { tableConfigList = null; leaseDuration = 0; configId = Guid.Empty; ReplicatedTableConfiguration configuration; List <string> eTags; ReplicatedTableQuorumReadResult result = CloudBlobHelpers.TryReadBlobQuorum( blobs, out configuration, out eTags, ReplicatedTableConfiguration.FromJson); if (result.Code != ReplicatedTableQuorumReadCode.Success) { ReplicatedTableLogger.LogError("Unable to refresh views, \n{0}", result.ToString()); return(null); } /** * Views: */ var viewList = new List <View>(); foreach (var entry in configuration.viewMap) { ReplicatedTableConfigurationStore configurationStore = entry.Value; var view = View.InitFromConfigVer2(entry.Key, configurationStore, SetConnectionString); view.RefreshTime = DateTime.UtcNow; if (view.ViewId <= 0) { ReplicatedTableLogger.LogError("ViewId={0} of ViewName={1} is invalid. Must be >= 1.", view.ViewId, view.Name); continue; } if (view.IsEmpty) { ReplicatedTableLogger.LogError("ViewName={0} is empty, skipping ...", view.Name); continue; } // - ERROR! if (view.ReadHeadIndex > view.TailIndex) { ReplicatedTableLogger.LogError("ReadHeadIndex={0} of ViewName={1} is out of range. Must be <= {2}", view.ReadHeadIndex, view.Name, view.TailIndex); continue; } viewList.Add(view); } if (!viewList.Any()) { ReplicatedTableLogger.LogError("Config has no active Views !"); return(null); } /** * Tables: */ tableConfigList = configuration.tableList.ToList(); // - lease duration leaseDuration = configuration.LeaseDuration; // - ConfigId configId = configuration.GetConfigId(); return(viewList); }
/* * View APIs: */ public void SetView(string viewName, ReplicatedTableConfigurationStore config) { if (string.IsNullOrEmpty(viewName)) { throw new ArgumentNullException("viewName"); } if (config == null) { throw new ArgumentNullException("config"); } ThrowIfViewIsNotValid(viewName, config); viewMap.Remove(viewName); viewMap.Add(viewName, config); }
public static View InitFromConfigVer2(string name, ReplicatedTableConfigurationStore configurationStore, Action<ReplicaInfo> SetConnectionString) { View view = new View(name); if (configurationStore != null) { view.ViewId = configurationStore.ViewId; foreach (ReplicaInfo replica in configurationStore.GetCurrentReplicaChain()) { SetConnectionString(replica); CloudTableClient tableClient = ReplicatedTableConfigurationManager.GetTableClientForReplica(replica); if (tableClient != null) { view.Chain.Add(new Tuple<ReplicaInfo, CloudTableClient>(replica, tableClient)); } } // Infered: first readable replica view.ReadHeadIndex = view.Chain.FindIndex(tuple => tuple.Item1.IsReadable()); } return view; }
public void UpdateConfiguration(List<ReplicaInfo> replicaChain, int readViewHeadIndex, bool convertXStoreTableMode = false, long viewId = 0) { View currentView = GetWriteView(); if (viewId == 0) { if (!currentView.IsEmpty) { viewId = currentView.ViewId + 1; } else { viewId = 1; } } ReplicatedTableConfigurationStore newConfig = new ReplicatedTableConfigurationStore { LeaseDuration = Constants.LeaseDurationInSec, Timestamp = DateTime.UtcNow, ReplicaChain = replicaChain, ReadViewHeadIndex = readViewHeadIndex, ConvertXStoreTableMode = convertXStoreTableMode, ViewId = viewId }; //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 (readViewHeadIndex != 0) { for (int i = 0; i < readViewHeadIndex; i++) { replicaChain[i].ViewInWhichAddedToChain = viewId; } } Parallel.ForEach(this.configManager.GetBlobs(), blob => { ReplicatedTableConfigurationStore configurationStore = null; string eTag; /* * TODO: (per Parveen Patel <*****@*****.**>) * The below code is incomplete because we are supposed to use eTag to make the changes if the old blob exists. * This is to support multiple clients updating the config, not a high priority scenario but something we should look at. */ ReplicatedTableReadBlobResult result = CloudBlobHelpers.TryReadBlob( blob, out configurationStore, out eTag, JsonStore<ReplicatedTableConfigurationStore>.Deserialize); if (result.Code != ReadBlobCode.Success) { //This is the first time we are uploading the config configurationStore = new ReplicatedTableConfigurationStore(); } configurationStore = newConfig; CloudBlobHelpers.TryWriteBlob(blob, configurationStore.ToJson()); }); this.configManager.Invalidate(); }
public void UpdateConfiguration(List <ReplicaInfo> replicaChain, int readViewHeadIndex, bool convertXStoreTableMode = false, long viewId = 0) { View currentView = GetWriteView(); if (viewId == 0) { if (!currentView.IsEmpty) { viewId = currentView.ViewId + 1; } else { viewId = 1; } } ReplicatedTableConfigurationStore newConfig = new ReplicatedTableConfigurationStore { LeaseDuration = Constants.LeaseDurationInSec, Timestamp = DateTime.UtcNow, ReplicaChain = replicaChain, ReadViewHeadIndex = readViewHeadIndex, ConvertXStoreTableMode = convertXStoreTableMode, ViewId = viewId }; //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 (readViewHeadIndex != 0) { for (int i = 0; i < readViewHeadIndex; i++) { replicaChain[i].ViewInWhichAddedToChain = viewId; } } Parallel.ForEach(this.configManager.GetBlobs(), blob => { ReplicatedTableConfigurationStore configurationStore = null; string eTag; /* * TODO: (per Parveen Patel <*****@*****.**>) * The below code is incomplete because we are supposed to use eTag to make the changes if the old blob exists. * This is to support multiple clients updating the config, not a high priority scenario but something we should look at. */ ReplicatedTableReadBlobResult result = CloudBlobHelpers.TryReadBlob( blob, out configurationStore, out eTag, JsonStore <ReplicatedTableConfigurationStore> .Deserialize); if (result.Code != ReadBlobCode.Success) { //This is the first time we are uploading the config configurationStore = new ReplicatedTableConfigurationStore(); } configurationStore = newConfig; CloudBlobHelpers.TryWriteBlob(blob, configurationStore.ToJson()); }); this.configManager.Invalidate(); }
private void ThrowIfViewIsNotValid(string viewName, ReplicatedTableConfigurationStore config) { if (config.ReplicaChain == null || config.ReplicaChain.Any(replica => replica == null)) { var msg = string.Format("View:\'{0}\' has a null replica(s) !!!", viewName); throw new Exception(msg); } List<ReplicaInfo> chainList = config.GetCurrentReplicaChain(); if (chainList.Any()) { /* RULE 1: * ======= * Read replicas rule: * - [R] replicas are contiguous from Tail backwards * - [R] replica count >= 1 */ string readPattern = "^W*R+$"; /* RULE 2: * ======= * Write replicas rule: * - [W] replicas are contiguous from Head onwards * - [W] replica count = 0 or = ChainLength */ string writePattern = "^((R+)|(W+))$"; // Get replica sequences string readSeq = ""; string writeSeq = ""; foreach (var replica in chainList) { // Read sequence: if (replica.IsReadable()) { readSeq += "R"; } else { readSeq += "W"; } // Write sequence: if (replica.IsWritable()) { writeSeq += "W"; } else { writeSeq += "R"; } } // Verify RULE 1: if (!Regex.IsMatch(readSeq, readPattern)) { var msg = string.Format("View:\'{0}\' has invalid Read chain:\'{1}\' !!!", viewName, readSeq); throw new Exception(msg); } // Verify RULE 2: if (!Regex.IsMatch(writeSeq, writePattern)) { var msg = string.Format("View:\'{0}\' has invalid Write chain:\'{1}\' !!!", viewName, writeSeq); throw new Exception(msg); } } }