from argparse import ArgumentParser from bisect import bisect_left, bisect_right from threading import Thread from xmlrpc.client import ServerProxy from xmlrpc.server import SimpleXMLRPCServer import traceback M = 5 PORT = 1234 RING = [2, 7, 11, 17, 22, 27] class Node: def __init__(self, node_id): """Initializes the node properties and constructs the finger table according to the Chord formula""" # Assuming that the program knows all the nodes and stores them in a sorted array RING self.node_id = node_id self.finger_table = [] self.successor_id = RING[(RING.index(node_id) + 1) % len(RING)] self.predecessor_id = RING[RING.index(node_id) - 1] self.table = {} for i in range(M): self.finger_table.append(RING[bisect_left(RING, ((node_id + (2 ** i)) % (2 ** M))) % len(RING)]) print(f"Node created! Finger table = {self.finger_table}, [pred, succ] = [{self.predecessor_id}, {self.successor_id}]") def closest_preceding_node(self, id): """Returns node_id of the closest preceeding node (from n.finger_table) for a given id""" for i in reversed(self.finger_table): if i == RING[-1]: idx = bisect_left([RING[0], RING[-1]], id) if idx == 0 or idx == 2: return i elif self.node_id > i: idx = bisect_left([i, self.node_id], id) if idx == 1: return i else: if i > self.node_id and i < id: return i return self.finger_table[-1] def find_successor(self, id): """Recursive function returning the identifier of the node responsible for a given id""" if id == self.node_id: return id # Note the half-open interval and that L <= R does not necessarily hold if self.successor_id < self.node_id: idx = bisect_left([self.successor_id, self.node_id], id) if idx == 0 or idx == 2: return self.successor_id elif id in range(self.node_id, self.successor_id + 1): return self.successor_id # Forward the query to the closest preceding node in the finger table for n n0 = self.closest_preceding_node(id) print(f'Forwarding request to node {n0}') with ServerProxy(f'http://node_{n0}:{PORT}') as proxy: return proxy.find_successor(id) def put(self, key, value): """Stores the given key-value pair in the node responsible for it""" try: print(f"put({key}, {value})") if self.node_id < self.predecessor_id: idx = bisect_left([self.node_id, self.predecessor_id], key) if idx == 0 or idx == 2: return self.store_item(key, value) elif key in range(self.predecessor_id, self.node_id + 1): return self.store_item(key, value) n0 = self.find_successor(key) if self.node_id == n0: return self.store_item(key, value) with ServerProxy(f'http://node_{n0}:{PORT}') as proxy: return proxy.store_item(key, value) except Exception as e: print(f"couldn't put({key}, {value})") print(traceback.format_exc()) print(e) return False def get(self, key): """Gets the value for a given key from the node responsible for it""" try: print(f"get({key})") if self.node_id < self.predecessor_id: idx = bisect_left([self.node_id, self.predecessor_id], key) if idx == 0 or idx == 2: return self.retrieve_item(key) elif key in range(self.predecessor_id, self.node_id + 1): return self.retrieve_item(key) n0 = self.find_successor(key) if self.node_id == n0: return self.retrieve_item(key) with ServerProxy(f'http://node_{n0}:{PORT}') as proxy: return proxy.retrieve_item(key) except Exception as e: print(f"couldn't get({key})") print(traceback.format_exc()) print(e) return -1 def store_item(self, key, value): """Stores a key-value pair into the data store of this node""" self.table[key] = value return True def retrieve_item(self, key): """Retrieves a value for a given key from the data store of this node""" if key in self.table: return self.table[key] return -1 if __name__ == '__main__': try: parser = ArgumentParser() parser.add_argument('node_id') args = parser.parse_args() node = Node(int(args.node_id)) server = SimpleXMLRPCServer(('0.0.0.0', PORT)) print("Listening on port 1234...") server.register_function(node.get, "get") server.register_function(node.put, "put") server.register_function(node.retrieve_item, "retrieve_item") server.register_function(node.store_item, "store_item") server.register_function(node.find_successor, "find_successor") server.serve_forever() except KeyboardInterrupt: print("node killed...") exit()