예제 #1
0
        /// <summary>
        /// Retrieve the string value for a given ulong
        /// key.
        /// </summary>
        /// <param name="key">The key whose value should be returned.</param>
        /// <returns>The string value for the given key, or an empty string if not found.</returns>
        public string FindKey(ulong key)
        {
            // determine the owning node for the key
            ChordNode owningNode = ChordServer.CallFindSuccessor(key);

            if (owningNode != ChordServer.LocalNode)
            {
                // if this is not the owning node, call
                // FindKey on the remote owning node
                return(ChordServer.CallFindKey(owningNode, key));
            }
            else
            {
                // if this is the owning node, check
                // to see if the key exists in the data store
                if (this.m_DataStore.ContainsKey(key))
                {
                    // if the key exists, return the value
                    return(this.m_DataStore[key]);
                }
                else
                {
                    // if the key does not exist, return empty string
                    return(string.Empty);
                }
            }
        }
예제 #2
0
 /// <summary>
 /// Creates a new instance of the finger table
 /// </summary>
 /// <param name="nodeIn">The node to populate the finger table with</param>
 public ChordFingerTable(ChordNode seed)
 {
     // populate the start array and successors
     for (int i = 0; i < this.Length; i++)
     {
         this.StartValues[i] = (seed.ID + (UInt64)Math.Pow(2, i)) % UInt64.MaxValue;
         this.Successors[i]  = seed;
     }
 }
        /// <summary>
        /// Maintenance task to ensure that the local node has valid successor node.  Roughly equivalent
        /// to what is called out in the Chord paper.
        /// </summary>
        /// <param name="sender">The worker thread the task is running on.</param>
        /// <param name="ea">Args (ignored here).</param>
        private void StabilizeSuccessors(object sender, DoWorkEventArgs ea)
        {
            BackgroundWorker me = (BackgroundWorker)sender;

            while (!me.CancellationPending)
            {
                try
                {
                    // check in successor and if it's bad, replace it with
                    // the next live entry in the successor cache
                    ChordNode succPredNode = ChordServer.GetPredecessor(this.Successor);
                    if (succPredNode != null)
                    {
                        if (ChordServer.IsIDInRange(succPredNode.ID, this.ID, this.Successor.ID))
                        {
                            this.Successor = succPredNode;
                        }

                        // ignoring return because bad node will be detected on next invocation
                        ChordServer.CallNotify(this.Successor, ChordServer.LocalNode);
                        GetSuccessorCache(this.Successor);
                    }
                    else
                    {
                        bool successorCacheHelped = false;
                        foreach (ChordNode entry in this.m_SuccessorCache)
                        {
                            ChordInstance instance = ChordServer.GetInstance(entry);
                            if (ChordServer.IsInstanceValid(instance))
                            {
                                this.Successor = entry;
                                ChordServer.CallNotify(this.Successor, ChordServer.LocalNode);
                                GetSuccessorCache(this.Successor);
                                successorCacheHelped = true;
                                break;
                            }
                        }

                        // if we get here, then we got no help and have no other recourse than to re-join using the initial seed...
                        if (!successorCacheHelped)
                        {
                            ChordServer.Log(LogLevel.Error, "StabilizeSuccessors", "Ring consistency error, Re-Joining Chord ring.");
                            Join(this.m_SeedNode, this.Host, this.Port);
                            return;
                        }
                    }
                }
                catch (Exception e)
                {
                    ChordServer.Log(LogLevel.Error, "Maintenance", "Error occured during StabilizeSuccessors ({0})", e.Message);
                }

                // TODO: this could be tweaked and/or made configurable elsewhere or passed in as arguments
                Thread.Sleep(5000);
            }
        }
예제 #4
0
        /// <summary>
        /// Compares ChordNodes on their ID hash value.
        /// </summary>
        /// <param name="obj">The object to compare this ChordNode to.</param>
        /// <returns>A negative value if the object is smaller; zero if they are equal; positive if the object is larger.</returns>
        public int CompareTo(object obj)
        {
            if (obj is ChordNode)
            {
                ChordNode node = (ChordNode)obj;
                return(this.ID.CompareTo(node.ID));
            }

            throw new ArgumentException("Object is not a ChordNode.");
        }
 /// <summary>
 /// Get the successor cache from a remote node and assign an altered version the local successorCache.
 /// Gets the remote successor cache, prepends remoteNode and lops off the last entry from the remote
 /// successorcache.
 /// </summary>
 /// <param name="remoteNode">The remote node to get the succesorCache from.</param>
 private void GetSuccessorCache(ChordNode remoteNode)
 {
     ChordNode[] remoteSuccessorCache = ChordServer.GetSuccessorCache(remoteNode);
     if (remoteSuccessorCache != null)
     {
         this.SuccessorCache[0] = remoteNode;
         for (int i = 1; i < this.SuccessorCache.Length; i++)
         {
             this.SuccessorCache[i] = remoteSuccessorCache[i - 1];
         }
     }
 }
        /// <summary>
        /// Maintenance task to perform ring consistency checking and re-joining to keep a Chord
        /// ring stable under extreme churn and in cases of ring damage.
        /// </summary>
        /// <param name="sender">The calling backgroundworker.</param>
        /// <param name="ea">Args (ignored for this task).</param>
        private void ReJoin(object sender, DoWorkEventArgs ea)
        {
            BackgroundWorker me = (BackgroundWorker)sender;

            while (!me.CancellationPending)
            {
                try
                {
                    // if this is the first iteration, then the core logic
                    // is skipped, as the first iteration generally occurs
                    // right after node Join - allowing a short buffer for
                    // routing structures to stabilize improves the utility
                    // of the ReJoin facility.
                    if (this.m_HasReJoinRun)
                    {
                        // first find the successor for the seed node
                        if (this.m_SeedNode != null)
                        {
                            ChordNode seedSuccessor = FindSuccessor(this.m_SeedNode.ID);

                            // if the successor is not equal to the seed node, something is fishy
                            if (seedSuccessor.ID != this.m_SeedNode.ID)
                            {
                                // if the seed node is still active, re-join the ring to the seed node
                                ChordInstance instance = ChordServer.GetInstance(this.m_SeedNode);
                                if (ChordServer.IsInstanceValid(instance))
                                {
                                    ChordServer.Log(LogLevel.Error, "ReJoin", "Unable to contact initial seed node {0}.  Re-Joining...", this.m_SeedNode);
                                    Join(this.m_SeedNode, this.Host, this.Port);
                                }

                                // otherwise, in the future, there will be a cache of seed nodes to check/join from...
                                // as it may be the case that the seed node simply has disconnected from the network.
                            }
                        }
                    }
                    else
                    {
                        // subsequent iterations will go through the core logic
                        this.m_HasReJoinRun = true;
                    }
                }
                catch (Exception e)
                {
                    ChordServer.Log(LogLevel.Error, "Maintenance", "Error occured during ReJoin ({0})", e.Message);
                }

                // TODO: the delay between iterations, here, is configurable, and
                // ideally should be retrieved from configuration or otherwise passed in...
                Thread.Sleep(30000);
            }
        }
예제 #7
0
 /// <summary>
 /// Perform equality comparison on ChordNodes on their ID hash value.
 /// </summary>
 /// <param name="obj">The object to compare this ChordNode to.</param>
 /// <returns>True if equal; false, otherwise.</returns>
 public override bool Equals(object obj)
 {
     try
     {
         ChordNode node = (ChordNode)obj;
         return(this.ID == node.ID);
     }
     catch
     {
         // Equality operation should not throw...
         return(false);
     }
 }
예제 #8
0
 /// <summary>
 /// Find the node that is the rightful owner of a given id.
 /// </summary>
 /// <param name="id">The id whose successor should be found.</param>
 /// <param name="hopCount">The number of network hops taken in finding the successor.</param>
 /// <returns>The ChordNode that is the Successor of a given ID value.</returns>
 public ChordNode FindSuccessor(UInt64 id, int hopCountIn, out int hopCountOut)
 {
     // is the local node's successor the rightful owner?
     if (ChordServer.IsIDInRange(id, this.ID, this.Successor.ID))
     {
         hopCountOut = hopCountIn;
         return(this.Successor);
     }
     else
     {
         // otherwise, find the nearest preceding finger, and ask that node.
         ChordNode predNode = FindClosestPrecedingFinger(id);
         return(ChordServer.CallFindSuccessor(predNode, id, 0, ++hopCountIn, out hopCountOut));
     }
 }
예제 #9
0
        /// <summary>
        /// Called by the predecessor to a remote node, this acts as a dual heartbeat mechanism and more importantly
        /// notification mechanism between predecessor and successor.
        /// </summary>
        /// <param name="node">A ChordNode instance indicating who the calling node (predecessor) is.</param>
        public void Notify(ChordNode node)
        {
            // if the node has absolutely no predecessor, take
            // the first one it finds
            if (this.Predecessor == null)
            {
                this.Predecessor = node;
                return;
            }

            // otherwise, ensure that the predecessor that is calling in
            // is indeed valid...
            if (ChordServer.IsIDInRange(node.ID, this.Predecessor.ID, this.ID))
            {
                this.Predecessor = node;
                return;
            }
        }
예제 #10
0
        /// <summary>
        /// Get a (local or remote) ChordInstance given a ChordNode.
        /// </summary>
        /// <param name="node">The ChordNode specifying the node to get an instance of.</param>
        /// <returns>A ChordInstance from the specified node, or null if an error is encountered.</returns>
        public static ChordInstance GetInstance(ChordNode node)
        {
            if (node == null)
            {
                ChordServer.Log(LogLevel.Error, "Navigation", "Invalid Node ({0}).", "Null Argument.");
                return(null);
            }

            try
            {
                ChordInstance retInstance = (ChordInstance)Activator.GetObject(typeof(ChordInstance), string.Format("tcp://{0}:{1}/chord", node.Host, node.PortNumber));
                return(retInstance);
            }
            catch (Exception e)
            {
                // perhaps instead we should just pass on the error?
                ChordServer.Log(LogLevel.Error, "Navigation", "Unable to activate remote server {0}:{1} ({2}).", node.Host, node.PortNumber, e.Message);
                return(null);
            }
        }
예제 #11
0
        /// <summary>
        /// Gets the remote Successor property, given a custom retry count.
        /// </summary>
        /// <param name="remoteNode">The remote node from which to access the property.</param>
        /// <param name="retryCount">The number of times to retry the operation in case of error.</param>
        /// <returns>The remote successor, or NULL in case of error.</returns>
        public static ChordNode GetSuccessor(ChordNode remoteNode, int retryCount)
        {
            ChordInstance instance = ChordServer.GetInstance(remoteNode);

            try
            {
                return(instance.Successor);
            }
            catch (System.Exception ex)
            {
                ChordServer.Log(LogLevel.Debug, "Remote Accessor", "GetSuccessor error: {0}", ex.Message);

                if (retryCount > 0)
                {
                    return(GetSuccessor(remoteNode, --retryCount));
                }
                else
                {
                    return(null);
                }
            }
        }
예제 #12
0
        /// <summary>
        /// Calls AddKey remotely.
        /// </summary>
        /// <param name="remoteNode">The remote node on which to call AddKey.</param>
        /// <param name="value">The string value to add.</param>
        /// <param name="retryCount">The number of retries to attempt.</param>
        public static void CallAddKey(ChordNode remoteNode, string value, int retryCount)
        {
            ChordInstance instance = ChordServer.GetInstance(remoteNode);

            try
            {
                instance.AddKey(value);
            }
            catch (System.Exception ex)
            {
                ChordServer.Log(LogLevel.Debug, "Remote Invoker", "CallAddKey error: {0}", ex.Message);

                if (retryCount > 0)
                {
                    CallAddKey(remoteNode, value, --retryCount);
                }
                else
                {
                    ChordServer.Log(LogLevel.Debug, "Remote Invoker", "CallAddKey failed - error: {0}", ex.Message);
                }
            }
        }
예제 #13
0
        /// <summary>
        /// Calls Notify() remotely, using a default retry value of three.
        /// </summary>
        /// <param name="remoteNode">The remote on which to call the method.</param>
        /// <param name="callingNode">The node to inform the remoteNode of.</param>
        /// <param name="retryCount">The number of times to retry the operation in case of error.</param>
        /// <returns>True if succeeded, FALSE otherwise.</returns>
        public static bool CallNotify(ChordNode remoteNode, ChordNode callingNode, int retryCount)
        {
            ChordInstance instance = ChordServer.GetInstance(remoteNode);

            try
            {
                instance.Notify(callingNode);
                return(true);
            }
            catch (System.Exception ex)
            {
                ChordServer.Log(LogLevel.Debug, "Remote Invoker", "CallNotify error: {0}", ex.Message);

                if (retryCount > 0)
                {
                    return(CallNotify(remoteNode, callingNode, --retryCount));
                }
                else
                {
                    return(false);
                }
            }
        }
예제 #14
0
        /// <summary>
        /// Calls FindKey remotely.
        /// </summary>
        /// <param name="remoteNode">The remote node on which to call FindKey.</param>
        /// <param name="key">The key to look up.</param>
        /// <param name="retryCount">The number of retries to attempt.</param>
        /// <returns>The value corresponding to the key, or empty string if not found.</returns>
        public static string CallFindKey(ChordNode remoteNode, ulong key, int retryCount)
        {
            ChordInstance instance = ChordServer.GetInstance(remoteNode);

            try
            {
                return(instance.FindKey(key));
            }
            catch (System.Exception ex)
            {
                ChordServer.Log(LogLevel.Debug, "Remote Invoker", "CallFindKey error: {0}", ex.Message);

                if (retryCount > 0)
                {
                    return(CallFindKey(remoteNode, key, --retryCount));
                }
                else
                {
                    ChordServer.Log(LogLevel.Debug, "Remote Invoker", "CallFindKey failed - error: {0}", ex.Message);
                    return(string.Empty);
                }
            }
        }
예제 #15
0
        /// <summary>
        /// Calls FindSuccessor() remotely, using a default retry value of three.
        /// </summary>
        /// <param name="remoteNode">The remote node on which to call FindSuccessor().</param>
        /// <param name="id">The ID to look up.</param>
        /// <param name="retryCount">The number of times to retry the operation in case of error.</param>
        /// <param name="hopCountIn">The known hopcount prior to calling FindSuccessor on this node.</param>
        /// <param name="hopCountOut">The total hopcount of this operation (either returned upwards, or reported for hopcount efficiency validation).</param>
        /// <returns>The Successor of ID, or NULL in case of error.</returns>
        public static ChordNode CallFindSuccessor(ChordNode remoteNode, UInt64 id, int retryCount, int hopCountIn, out int hopCountOut)
        {
            ChordInstance instance = ChordServer.GetInstance(remoteNode);

            try
            {
                return(instance.FindSuccessor(id, hopCountIn, out hopCountOut));
            }
            catch (System.Exception ex)
            {
                ChordServer.Log(LogLevel.Debug, "Remote Invoker", "CallFindSuccessor error: {0}", ex.Message);

                if (retryCount > 0)
                {
                    return(CallFindSuccessor(remoteNode, id, --retryCount, hopCountIn, out hopCountOut));
                }
                else
                {
                    hopCountOut = hopCountIn;
                    return(null);
                }
            }
        }
예제 #16
0
        /// <summary>
        /// Add a key to the store.  Gets a hash of the key, determines
        /// the correct owning node, and stores the string value
        /// on that node.
        /// </summary>
        /// <param name="value">The value to add.</param>
        public void AddKey(string value)
        {
            // the key is the hash of the value to
            // add to the store, and determines the
            // owning NChord node
            ulong key = ChordServer.GetHash(value);

            // using the local node, determine the correct owning
            // node for the data to be stored given the key value
            ChordNode owningNode = ChordServer.CallFindSuccessor(key);

            if (owningNode != ChordServer.LocalNode)
            {
                // if this is not the owning node, then call AddKey
                // on the actual owning node
                ChordServer.CallAddKey(owningNode, value);
            }
            else
            {
                // if this is the owning node, then add the
                // key to the local data store
                this.m_DataStore.Add(key, value);
            }
        }
예제 #17
0
        /*
         * Retry logic:
         *  The idea behind the retry logic is to provide a simple and common-case reusable call
         *  in to remote methods or properties.  This logic also serendipitously encapsulates and
         *  simplifies exception handling by performing a bounded number of retries as part of
         *  exception handling.  The retryCount that is passed along as part of the retry logic
         *  serves as a pleasant way to maintain state across node boundaries (thus enforcing a
         *  fixed number of N retries for a logical operation, no matter how many nodes the
         *  operation spans.
         *
         *  Currently, the default retry count is hardcoded; in the future it may be desirable to
         *  expose this value as a configurable parameter.
         *
         * Safe access & exception handling pattern:
         *  Anywhere client or server code needs to make remoting calls, there are typically two
         *  things people usually do: wrap the call in some sort of exception handling (not doing
         *  this is generally silly - and is a quick way to wreck whatever application is consuming
         *  that code upstream), and peform a fixed number of retries in case of transient errors
         *  (transient errors can be somewhat common when testing with many hundreds of Chord nodes
         *  running simultaneously on a single OS instance - often, retrying fatal-seeming errors
         *  can lead to success, reducing the need to exercise (harsher) upstream failure handling).
         *
         *  In almost all cases, upstream code patterns performing these remote access / invocations
         *  use a single exception handling path; therefore, error is signaled simply via return value
         *  for simple error-handling (since retry is not needed).
         *
         */


        /// <summary>
        /// Calls Notify() remotely, using a default retry value of three.
        /// </summary>
        /// <param name="remoteNode">The remote on which to call the method.</param>
        /// <param name="callingNode">The node to inform the remoteNode of.</param>
        /// <returns>True if succeeded, FALSE otherwise.</returns>
        public static bool CallNotify(ChordNode remoteNode, ChordNode callingNode)
        {
            return(CallNotify(remoteNode, callingNode, 3));
        }
예제 #18
0
        /// <summary>
        /// Join the current ChordInstance to a chord ring given a seed node.
        /// </summary>
        /// <param name="seed">Remote node to connect to that is a member of a Chord ring.  To start a new Chord ring, a null seed may be provided.</param>
        /// <param name="host">The local host name.</param>
        /// <param name="port">The tcp port on which this node will listen for calls from other Chord nodes.</param>
        /// <returns>true if the Join succeeds; false, otherwise.</returns>
        public bool Join(ChordNode seed, string host, int port)
        {
            // LocalNode is established first to ensure proper logging.
            ChordServer.LocalNode = new ChordNode(host, port);

            this.m_HasReJoinRun = false;
            this.m_SeedNode     = seed; // cache the seed node so the ring may re-form itself in case of partition or damage

            // safely establish finger table (startvalues and successors, using LocalNode as the default successor)
            this.FingerTable = new ChordFingerTable(ChordServer.LocalNode);

            // establish successor cache initially with all entries pointing loally
            this.SuccessorCache = new ChordNode[3]; // TODO: make this configurable
            for (int i = 0; i < this.SuccessorCache.Length; i++)
            {
                this.SuccessorCache[i] = ChordServer.LocalNode;
            }

            if (seed != null)   // join an existing chord ring
            {
                ChordServer.Log(LogLevel.Info, "Navigation", "Joining Ring @ {0}:{1}.", seed.Host, seed.PortNumber);

                // first, establish successor via seed node
                ChordInstance instance = ChordServer.GetInstance(seed);
                if (ChordServer.IsInstanceValid(instance))
                {
                    try
                    {
                        this.Successor = instance.FindSuccessor(this.ID);   // NOTE: this conceivably could be replaced with ChordServer.FindSuccessor()

                        // disabled: a clever trick that requires only one remote network call is to
                        //  append the successor's successor cache (minus its last entry) to the local
                        //  successor cache, starting at the second entry in the local successor cache.
                        //  during churn, this can break down, so instead the successor cache is populated
                        //  and maintained lazily by maintenance.
                        //  as the successor cache was initialized with the LocalNode as the default
                        //  instance values, "misses" on successor cache entries are gracefully handled by
                        //  simply being forwarded (via the local node) on to the successor node where
                        //  better information may be found and utilized.
                        //
                        //this.SuccessorCache = ChordServer.GetSuccessorCache(this.Successor);
                    }
                    catch (Exception e)
                    {
                        ChordServer.Log(LogLevel.Error, "Navigation", "Error setting Successor Node ({0}).", e.Message);
                        return(false);
                    }
                }
                else
                {
                    ChordServer.Log(LogLevel.Error, "Navigation", "Invalid Node Seed.");
                    return(false);
                }
            }
            else // start a new ring
            {
                // not much needs to happen - successor is already established as
                // ChordServer.LocalNode,everything else takes place lazily as part of maintenance
                ChordServer.Log(LogLevel.Info, "Navigation", "Starting Ring @ {0}:{1}.", this.Host, this.Port);
            }

            // everything that needs to be populated or kept up-to-date
            // lazily is handled via background maintenance threads running periodically
            StartMaintenance();

            return(true);
        }
예제 #19
0
 /// <summary>
 /// Calls FindKey() remotely, using a default retry value of three.
 /// </summary>
 /// <param name="remoteNode">The remote on which to call the method.</param>
 /// <param name="key">The key to look up.</param>
 /// <returns>The value corresponding to the key, or empty string if not found.</returns>
 public static string CallFindKey(ChordNode remoteNode, ulong key)
 {
     return(CallFindKey(remoteNode, key, 3));
 }
예제 #20
0
 /// <summary>
 /// Calls AddKey() remotely, using a default retry value of three.
 /// </summary>
 /// <param name="remoteNode">The remote on which to call the method.</param>
 /// <param name="value">The string value to add.</param>
 public static void CallAddKey(ChordNode remoteNode, string value)
 {
     CallAddKey(remoteNode, value, 3);
 }
예제 #21
0
 /// <summary>
 /// Calls ReplicateKey() remotely, using a default retry value of three.
 /// </summary>
 /// <param name="remoteNode">The remote on which to call the method.</param>
 /// <param name="key">The key to replicate.</param>
 /// <param name="value">The string value to replicate.</param>
 public static void CallReplicateKey(ChordNode remoteNode, ulong key, string value)
 {
     CallReplicateKey(remoteNode, key, value, 3);
 }
예제 #22
0
        /// <summary>
        /// Calls FindSuccessor() remotely, using a default retry value of three.  HopCount is ignored.
        /// </summary>
        /// <param name="remoteNode">The remote on which to call the method.</param>
        /// <param name="id">The ID to look up.</param>
        /// <returns>The Successor of ID, or NULL in case of error.</returns>
        public static ChordNode CallFindSuccessor(ChordNode remoteNode, UInt64 id)
        {
            int hopCountOut = 0;

            return(CallFindSuccessor(remoteNode, id, 3, 0, out hopCountOut));
        }
예제 #23
0
 /// <summary>
 /// Gets the remote SuccessorCache property, using a default retry value of three.
 /// </summary>
 /// <param name="remoteNode">The remote from which to access the Successor Cache.</param>
 /// <returns>The remote node's successorCache, or NULL in case of error.</returns>
 public static ChordNode[] GetSuccessorCache(ChordNode remoteNode)
 {
     return(GetSuccessorCache(remoteNode, 3));
 }
예제 #24
0
 /// <summary>
 /// Gets the remote Successor property, using a default retry value of three.
 /// </summary>
 /// <param name="remoteNode">The remote from which to access the property.</param>
 /// <returns>The remote node's successor, or NULL in case of error.</returns>
 public static ChordNode GetSuccessor(ChordNode remoteNode)
 {
     return(GetSuccessor(remoteNode, 3));
 }