1 ## Copyright 2002-2003 Andrew Loewenstern, All Rights Reserved
2 # see LICENSE.txt for license information
4 """Represents a node in the DHT.
6 @type NULL_ID: C{string}
7 @var NULL_ID: the node ID to use until one is known
10 from datetime import datetime, MINYEAR
11 from types import InstanceType
13 from twisted.trial import unittest
16 from util import compact
18 # magic id to use before we know a peer's id
19 NULL_ID = khash.HASH_LENGTH * '\0'
22 """Encapsulate a node's contact info.
24 @ivar conn: the connection to the remote node (added externally)
25 @ivar table: the routing table (added externally)
27 @ivar fails: number of times this node has failed in a row
28 @type lastSeen: C{datetime.datetime}
29 @ivar lastSeen: the last time a response was received from this node
31 @ivar id: the node's ID in the DHT
33 @ivar num: the node's ID in number form
35 @ivar host: the IP address of the node
37 @ivar port: the port of the node
38 @type token: C{string}
39 @ivar token: the last received token from the node
40 @type num_values: C{int}
41 @ivar num_values: the number of values the node has for the key in the
42 currently executing action
45 def __init__(self, id, host = None, port = None):
46 """Initialize the node.
48 @type id: C{string} or C{dictionary}
49 @param id: the node's ID in the DHT, or a dictionary containing the
50 node's id, host and port
52 @param host: the IP address of the node
53 (optional, but must be specified if id is not a dictionary)
55 @param port: the port of the node
56 (optional, but must be specified if id is not a dictionary)
59 self.lastSeen = datetime(MINYEAR, 1, 1)
61 # Alternate method, init Node from dictionary
62 if isinstance(id, dict):
67 assert isinstance(id, str)
68 assert isinstance(host, str)
70 self.num = khash.intify(id)
75 self._contactInfo = None
77 def updateLastSeen(self):
78 """Updates the last contact time of the node and resets the number of failures."""
79 self.lastSeen = datetime.now()
82 def updateToken(self, token):
83 """Update the token for the node."""
86 def updateNumValues(self, num_values):
87 """Update how many values the node has in the current search for a value."""
88 self.num_values = num_values
91 """Log a failed attempt to contact this node.
94 @return: the number of consecutive failures this node has
96 self.fails = self.fails + 1
99 def contactInfo(self):
100 """Get the compact contact info for the node."""
101 if self._contactInfo is None:
102 self._contactInfo = compact(self.id, self.host, self.port)
103 return self._contactInfo
106 return `(self.id, self.host, self.port)`
109 """Create a shallow copy of the node, resetting some values."""
110 cp = self.__class__(self.id, self.host, self.port)
111 cp.fails = self.fails
112 cp.lastSeen = self.lastSeen
113 if getattr(self, 'table', None) is not None:
114 cp.table = self.table
115 if getattr(self, 'conn', None) is not None:
119 #{ Comparators to bisect/index a list of nodes with either a node or a long
121 if type(a) == InstanceType:
125 if type(a) == InstanceType:
129 if type(a) == InstanceType:
133 if type(a) == InstanceType:
137 if type(a) == InstanceType:
141 if type(a) == InstanceType:
146 class TestNode(unittest.TestCase):
147 """Unit tests for the node implementation."""
149 self.node = Node(khash.newID(), '127.0.0.1', 2002)
150 def testUpdateLastSeen(self):
151 t = self.node.lastSeen
152 self.node.updateLastSeen()
153 self.failUnless(t < self.node.lastSeen)