2 """Represents a node in the DHT.
4 @type NULL_ID: C{string}
5 @var NULL_ID: the node ID to use until one is known
8 from datetime import datetime, MINYEAR
9 from types import InstanceType
11 from twisted.trial import unittest
14 from util import compact
16 # magic id to use before we know a peer's id
17 NULL_ID = khash.HASH_LENGTH * '\0'
20 """Encapsulate a node's contact info.
22 @ivar conn: the connection to the remote node (added externally)
23 @ivar table: the routing table (added externally)
25 @ivar fails: number of times this node has failed in a row
26 @type lastSeen: C{datetime.datetime}
27 @ivar lastSeen: the last time a response was received from this node
29 @ivar id: the node's ID in the DHT
31 @ivar num: the node's ID in number form
33 @ivar host: the IP address of the node
35 @ivar port: the port of the node
36 @type token: C{string}
37 @ivar token: the last received token from the node
38 @type num_values: C{int}
39 @ivar num_values: the number of values the node has for the key in the
40 currently executing action
43 def __init__(self, id, host = None, port = None):
44 """Initialize the node.
46 @type id: C{string} or C{dictionary}
47 @param id: the node's ID in the DHT, or a dictionary containing the
48 node's id, host and port
50 @param host: the IP address of the node
51 (optional, but must be specified if id is not a dictionary)
53 @param port: the port of the node
54 (optional, but must be specified if id is not a dictionary)
57 self.lastSeen = datetime(MINYEAR, 1, 1)
59 # Alternate method, init Node from dictionary
60 if isinstance(id, dict):
65 assert isinstance(id, str)
66 assert isinstance(host, str)
68 self.num = khash.intify(id)
73 self._contactInfo = None
75 def updateLastSeen(self):
76 """Updates the last contact time of the node and resets the number of failures."""
77 self.lastSeen = datetime.now()
80 def updateToken(self, token):
81 """Update the token for the node."""
84 def updateNumValues(self, num_values):
85 """Update how many values the node has in the current search for a value."""
86 self.num_values = num_values
89 """Log a failed attempt to contact this node.
92 @return: the number of consecutive failures this node has
94 self.fails = self.fails + 1
97 def contactInfo(self):
98 """Get the compact contact info for the node."""
99 if self._contactInfo is None:
100 self._contactInfo = compact(self.id, self.host, self.port)
101 return self._contactInfo
104 return `(self.id, self.host, self.port)`
107 """Create a shallow copy of the node, resetting some values."""
108 cp = self.__class__(self.id, self.host, self.port)
109 cp.fails = self.fails
110 cp.lastSeen = self.lastSeen
111 if getattr(self, 'table', None) is not None:
112 cp.table = self.table
113 if getattr(self, 'conn', None) is not None:
117 #{ Comparators to bisect/index a list of nodes with either a node or a long
119 if type(a) == InstanceType:
123 if type(a) == InstanceType:
127 if type(a) == InstanceType:
131 if type(a) == InstanceType:
135 if type(a) == InstanceType:
139 if type(a) == InstanceType:
143 return hash(self.num)
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)