private ReplicatedTableQuorumWriteResult UpdateConfigurationInternal(ReplicatedTableConfiguration configuration, bool useConditionalUpdate) { SanitizeConfiguration(configuration); // - Upload configuration ... Func <ReplicatedTableConfiguration, ReplicatedTableConfiguration, bool> comparer = (a, b) => a.Id == b.Id; if (!useConditionalUpdate) { comparer = (a, b) => true; } ReplicatedTableQuorumWriteResult result = CloudBlobHelpers.TryWriteBlobQuorum( this.configManager.GetBlobs(), configuration, ReplicatedTableConfiguration.FromJson, comparer, ReplicatedTableConfiguration.GenerateNewConfigId); if (result.Code == ReplicatedTableQuorumWriteCode.Success) { this.configManager.Invalidate(); } else { ReplicatedTableLogger.LogError("Failed to update configuration, \n{0}", result.ToString()); } return(result); }
private void Initialize() { if ((this.blobLocations.Count % 2) == 0) { throw new ArgumentException("Number of blob locations must be odd"); } foreach (var blobLocation in blobLocations) { string accountConnectionString = String.Format(Constants.ShortConnectioStringTemplate, ((this.useHttps == true) ? "https" : "http"), blobLocation.StorageAccountName, blobLocation.StorageAccountKey); try { CloudBlockBlob blob = CloudBlobHelpers.GetBlockBlob(accountConnectionString, blobLocation.BlobPath); this.blobs.Add(blobLocation.StorageAccountName + ';' + blobLocation.BlobPath, blob); } catch (Exception e) { ReplicatedTableLogger.LogError("Failed to init blob Acc={0}, Blob={1}. Exception: {2}", blobLocation.StorageAccountName, blobLocation.BlobPath, e.Message); } } int quorumSize = (this.blobLocations.Count / 2) + 1; if (this.blobs.Count < quorumSize) { throw new Exception(string.Format("Retrieved blobs count ({0}) is less than quorum !", this.blobs.Count)); } }
public List <ReplicatedTableReadBlobResult> RetrieveConfiguration(out List <ReplicatedTableConfiguration> configuration) { List <string> eTagsArray; return(CloudBlobHelpers.TryReadAllBlobs( this.configManager.GetBlobs(), out configuration, out eTagsArray, ReplicatedTableConfiguration.FromJson)); }
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; }
/* * Class functions: */ static internal protected CloudTableClient GetTableClientForReplica(ReplicaInfo replica) { CloudTableClient tableClient = null; if (!CloudBlobHelpers.TryCreateCloudTableClient(replica.ConnectionString, out tableClient)) { ReplicatedTableLogger.LogError("No table client created for replica info: {0}", replica); } return(tableClient); }
private CloudTableClient GetTableClientForReplica(ReplicaInfo replica) { string connectionString = String.Format(cloudStorageAccountTemplate, this.useHttps ? "https" : "http", replica.StorageAccountName, replica.StorageAccountKey); CloudTableClient tableClient = null; if (!CloudBlobHelpers.TryCreateCloudTableClient(connectionString, out tableClient)) { ReplicatedTableLogger.LogError("No table client created for replica info: {0}", replica); } return(tableClient); }
/* * Configuration management APIs */ public ReplicatedTableQuorumReadResult RetrieveConfiguration(out ReplicatedTableConfiguration configuration) { List <string> eTags; ReplicatedTableQuorumReadResult result = CloudBlobHelpers.TryReadBlobQuorum( this.configManager.GetBlobs(), out configuration, out eTags, ReplicatedTableConfiguration.FromJson); if (result.Code != ReplicatedTableQuorumReadCode.Success) { ReplicatedTableLogger.LogError("Failed to read configuration, \n{0}", result.ToString()); } return(result); }
public ReplicatedTableQuorumWriteResult UploadConfigurationToBlobs(List <int> blobIndexes, ReplicatedTableConfiguration configuration) { if (blobIndexes == null || !blobIndexes.Any()) { throw new ArgumentNullException("blobIndexes"); } if (configuration == null) { throw new ArgumentNullException("configuration"); } List <CloudBlockBlob> blobs = new List <CloudBlockBlob>(); foreach (var blobIndex in blobIndexes) { if (blobIndex < this.configManager.GetBlobs().Count) { blobs.Add(this.configManager.GetBlobs()[blobIndex]); continue; } var msg = string.Format("blobIndex={0} >= BlobCount={1}", blobIndex, this.configManager.GetBlobs().Count); ReplicatedTableLogger.LogError(msg); throw new Exception(msg); } SanitizeConfiguration(configuration); // - Upload to blobs ... ReplicatedTableQuorumWriteResult result = CloudBlobHelpers.TryUploadBlobs(blobs, configuration); this.configManager.Invalidate(); if (result.Code != ReplicatedTableQuorumWriteCode.Success) { ReplicatedTableLogger.LogError("Failed to upload configuration to blobs, \n{0}", result.ToString()); } return(result); }
private void Initialize() { if ((this.blobLocations.Count % 2) == 0) { throw new ArgumentException("Number of blob locations must be odd"); } foreach (var blobLocation in blobLocations) { string accountConnectionString = String.Format(cloudStorageAccountTemplate, ((this.useHttps == true) ? "https" : "http"), blobLocation.StorageAccountName, blobLocation.StorageAccountKey); CloudBlockBlob blob = CloudBlobHelpers.GetBlockBlob(accountConnectionString, blobLocation.BlobPath); this.blobs.Add(blobLocation.StorageAccountName + ';' + blobLocation.BlobPath, blob); } this.quorumSize = (this.blobs.Count / 2) + 1; }
/// <summary> /// True, if the read and write views are the same. False, otherwise. /// </summary> public bool IsViewStable() { int viewStableCount = 0; foreach (var blob in this.blobs) { ReplicatedTableConfigurationStore configurationStore; if (!CloudBlobHelpers.TryReadBlob <ReplicatedTableConfigurationStore>(blob.Value, out configurationStore)) { continue; } if (configurationStore.ReadViewHeadIndex == 0) { viewStableCount++; } } return(viewStableCount >= this.quorumSize); }
/// <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); }
/// <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; ReplicatedTableConfigurationStore configurationStore; List <string> eTags; ReplicatedTableQuorumReadResult result = CloudBlobHelpers.TryReadBlobQuorum( blobs, out configurationStore, out eTags, JsonStore <ReplicatedTableConfigurationStore> .Deserialize); if (result.Code != ReplicatedTableQuorumReadCode.Success) { ReplicatedTableLogger.LogError("Unable to refresh view, \n{0}", result.ToString()); return(null); } /** * View: */ var view = View.InitFromConfigVer1(DefaultViewName, configurationStore, SetConnectionString); view.RefreshTime = DateTime.UtcNow; if (view.ViewId <= 0) { ReplicatedTableLogger.LogError("ViewId={0} is invalid. Must be >= 1.", view.ViewId); return(null); } if (view.IsEmpty) { ReplicatedTableLogger.LogError("ViewName={0} is empty, skipping ...", view.Name); return(null); } /** * Tables: */ tableConfigList = new List <ReplicatedTableConfiguredTable> { new ReplicatedTableConfiguredTable { TableName = AllTables, ViewName = DefaultViewName, ConvertToRTable = configurationStore.ConvertXStoreTableMode, } }; // - lease duration leaseDuration = configurationStore.LeaseDuration; return(new List <View> { 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(); }
private void RefreshReadAndWriteViewsFromBlobs(object arg) { lock (this) { this.lastRenewedReadView = new View(); this.lastRenewedWriteView = new View(); DateTime refreshStartTime = DateTime.UtcNow; Dictionary <long, List <CloudBlockBlob> > viewResult = new Dictionary <long, List <CloudBlockBlob> >(); foreach (var blob in this.blobs) { ReplicatedTableConfigurationStore configurationStore; if (!CloudBlobHelpers.TryReadBlob <ReplicatedTableConfigurationStore>(blob.Value, out configurationStore)) { continue; } if (configurationStore.ViewId <= 0) { ReplicatedTableLogger.LogInformational("ViewId={0} is invalid. Must be >= 1. Skipping this blob {1}.", configurationStore.ViewId, blob.Value.Uri); continue; } List <CloudBlockBlob> viewBlobs; if (!viewResult.TryGetValue(configurationStore.ViewId, out viewBlobs)) { viewBlobs = new List <CloudBlockBlob>(); viewResult.Add(configurationStore.ViewId, viewBlobs); } viewBlobs.Add(blob.Value); if (viewBlobs.Count >= this.quorumSize) { this.lastRenewedReadView.ViewId = this.lastRenewedWriteView.ViewId = configurationStore.ViewId; for (int i = 0; i < configurationStore.ReplicaChain.Count; i++) { ReplicaInfo replica = configurationStore.ReplicaChain[i]; CloudTableClient tableClient = GetTableClientForReplica(replica); if (replica != null && tableClient != null) { //Update the write view always this.lastRenewedWriteView.Chain.Add(new Tuple <ReplicaInfo, CloudTableClient>(replica, tableClient)); //Update the read view only for replicas part of the view if (i >= configurationStore.ReadViewHeadIndex) { this.lastRenewedReadView.Chain.Add(new Tuple <ReplicaInfo, CloudTableClient>(replica, tableClient)); } //Note the time when the view was updated this.lastViewRefreshTime = refreshStartTime; this.ConvertXStoreTableMode = configurationStore.ConvertXStoreTableMode; } } this.lastRenewedWriteView.ReadHeadIndex = configurationStore.ReadViewHeadIndex; break; } } } }