コード例 #1
0
ファイル: View.cs プロジェクト: srinathsetty/rtable
        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);
        }
コード例 #2
0
        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);
            }
        }
コード例 #3
0
        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;
        }
コード例 #5
0
ファイル: View.cs プロジェクト: isabella232/rtable
        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);
        }
コード例 #6
0
        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);
        }
コード例 #7
0
        /*
         * 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);
        }
コード例 #8
0
        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);
            }
        }
コード例 #9
0
ファイル: View.cs プロジェクト: isabella232/rtable
        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);
        }
コード例 #10
0
        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);
        }
コード例 #11
0
ファイル: View.cs プロジェクト: farukc/rtable
        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;
        }
コード例 #12
0
        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);
                }
            }
        }
コード例 #13
0
        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++;
        }
コード例 #14
0
        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++;
        }
コード例 #15
0
        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++;
        }
コード例 #16
0
        /// <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);
        }
コード例 #17
0
        /*
         * 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);
        }
コード例 #18
0
ファイル: View.cs プロジェクト: farukc/rtable
        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;
        }
コード例 #19
0
        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();
        }
コード例 #20
0
        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();
        }
コード例 #21
0
        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);
                }
            }
        }