def testDownloadFiles(self): """ For every file in upload/ download them from DHT to download/ """ for i,file in enumerate(glob.glob("upload/*")): with open(file.replace("upload","download"), "wb") as f: command = DHTCommand(DHTCommand.GET,file) self.dht.sendcommand(self.servers[i % len(self.ports)],command,f) command = DHTCommand(DHTCommand.GET,"key for:"+file) print self.dht.sendcommand(self.servers[i % len(self.ports)],command)
def testDeleteFiles(self): """ Open files in upload/ in binary mode and send them to dht """ for i, file in enumerate(glob.glob("upload/*")): command = DHTCommand(DHTCommand.DEL, file) response = self.dht.sendcommand(self.servers[i % len(self.ports)], command) self.assertEquals(response, "DEL OK " + file) command = DHTCommand(DHTCommand.DEL, "key for:" + file, "this is the key for:" + file) response = self.dht.sendcommand(self.servers[i % len(self.ports)], command) self.assertEquals(response, "DEL OK key for:" + file)
def initialise_hashring(self): """ Initialize the hash ring. If `self.remote_server` is not note, get it from remote_server or else just create a new one. Also set the hash_ring in `self.dht_table` """ if self.remote_server: remote_host, remote_port = self.remote_server.split(":") remote_server = Server(remote_host, remote_port) # Send a join command to the existing server command = DHTCommand(DHTCommand.JOIN, self.this_server) ring = self.client.sendcommand(remote_server, command) logging.debug("got ring from server: %s", str(ring)) # Get replicas if not ring: raise RuntimeError( ("Could not reach server: %s" % str(remote_server))) nodes, replicas = ring.split(",") # Convert |-separated list to Server-instances nodes = map( lambda server_string: Server( server_string.split(":")[0], server_string.split(":")[1]), nodes.split("|")) # Initialize local hash ring self.hash_ring = HashRing(nodes, int(replicas)) self.hash_ring.add_node(self.this_server) else: # First server so this server is added self.hash_ring = HashRing([self.this_server], self.replicas) # Initialize the hash map self.dht_table = MyDHTTable(self.this_server, self.hash_ring)
def server_thread(self, client_sock): """ Thread that handles a client `client_sock` is the socket where the client is connected perform the operation and connect to another server if necessary """ rawcommand = self.client.read_from_socket(_block, client_sock) command = DHTCommand().parse(rawcommand) logging.debug("received command: %s", str(command)) if command.action in [DHTCommand.PUT, DHTCommand.GET, DHTCommand.DEL]: # Perform the command and any replication status = self.handle_replica_command(command, client_sock) elif command.action == DHTCommand.JOIN: # A client wants to join the ring status = self.add_new_node(command.key) elif command.action == DHTCommand.ADDNODE: # A new client has joined and should be added to this servers ring self.hash_ring.add_node(command.key) status = "added by " + str(self.this_server) elif command.action == DHTCommand.LEAVE: self.hash_ring.remove_node(command.key) status = "removed: " + str(command.key) elif command.action == DHTCommand.REMOVE: # A server has left the ring without decommission status = self.remove_node(command.key, command.forwarded) elif command.action == DHTCommand.WHEREIS: # Just return the hostnames that holds a key status = ", ".join( map(lambda s: str(s), self.hash_ring.get_replicas(command.key))) elif command.action == DHTCommand.BALANCE: # Load balance this node status = self.load_balance(command.forwarded) elif command.action == DHTCommand.UNKNOWN: # Just send error and close socket status = "UNKNOWN_COMMAND" client_sock.send(status) client_sock.close() return else: # All other commands ends up in the table status = self.dht_table.perform(command) # Send length to all clients except a web browser (it will end up in the HTML) if command.action != DHTCommand.HTTPGET and command.action != DHTCommand.HTTPGETKEY: self.client.send_length_to_socket(len(status), client_sock) # Send response to client self.client.send_to_socket(status, len(status), client_sock) # Shutdown write end of socket client_sock.shutdown(SHUT_WR) # Close socket client_sock.close()
def load_balance(self, forwarded): """ Load balance this node and then all other. if `forwarded` is False BALANCE will be sent to all other nodes. """ status = self.internal_load_balance() if not forwarded: # And load balance the others command = DHTCommand(DHTCommand.BALANCE) self.forward_command(command) return status
def cmdlinestart(self): """ Parse command line parameters and start client """ try: port = int( self.getarg("-p") or self.getarg("--port",50140)) host = self.getarg("-h") or self.getarg("--hostname","localhost") key = self.getarg("-k") or self.getarg("--key") server = Server(host,port) command = self.getarg('-c') or self.getarg('--command') value = self.getarg("-val") or self.getarg("--value") file = self.getarg("-f") or self.getarg("--file") outfile = self.getarg("-o") or self.getarg("--outfile") logging.debug("command: %s %s %s %s", str(server), command, key, value) if command is None or server is None or file and value: self.help() command = self.get_command(command) if file: f = open(file, "rb") command = DHTCommand(command,key,f) else: command = DHTCommand(command,key,value) if outfile: # File was an argument, supply it with open(outfile, "wb") as out: print self.sendcommand(server,command,out) else: # Print output data = self.sendcommand(server,command) if data and len(data) < 1024: print data elif data: print "Return data is over 1K, please use the -o option to redirect it to a file" if file: f.close() except TypeError: self.help()
def decommission(self): """ Remove self from hash_ring and move all existing data to new nodes. """ self.ring_lock.acquire() self.hash_ring.remove_node(self.this_server) # First remove this server from all other servers command = DHTCommand(DHTCommand.LEAVE, self.this_server) self.forward_command(command) # Load balance only on this node self.load_balance(True) self.ring_lock.release()
def internal_load_balance(self): """ Go through all keys in map and check with all replica server if they have an older version of the key, send the value. if they have a newer version, get the value. """ # For all keys in this map for key in self.dht_table.get_keys(): key_is_at = self.hash_ring.get_replicas(key, self.this_server) for server in key_is_at: command = DHTCommand(DHTCommand.HASKEY, key) status = self.client.sendcommand(server, command) if not status: # None was returned, servers is probably down logging.error("Got no timestamp from %s, could be dead", str(server)) continue remote_time = float(status) local_timestamp = float(self.dht_table.perform(command)) if remote_time < local_timestamp: # Key is missing or old logging.debug("Copying: %s to %s", key, server) value = self.dht_table.perform( DHTCommand(DHTCommand.GET, key)) command = DHTCommand(DHTCommand.PUT, key, value, local_timestamp) status = self.client.sendcommand(server, command) logging.debug("status: %s", status) elif remote_time > local_timestamp: # Remote object is newer, get it logging.debug("Copying: %s from %s", key, server) command = DHTCommand(DHTCommand.GET, key) value = self.client.sendcommand(server, command) status = self.dht_table.perform( DHTCommand(DHTCommand.PUT, key, value, remote_time)) logging.debug(status) return "BALANCE ok"
def remove_node(self, node, forwarded): """ Remove `node` from ring If not `forwarder` tell all other nodes This usually happens if a node dies without being able to do a decommission. """ self.ring_lock.acquire() self.hash_ring.remove_node(node) if not forwarded: # Add the new server to all existing nodes command = DHTCommand(DHTCommand.REMOVE, node) self.forward_command(command) # Rebalance all nodes self.load_balance(False) self.ring_lock.release() return "REMOVE ok"
def add_new_node(self, new_node): """ Adds a new server to all existing nodes and returns a |-separated list of the current ring "," the number of replicas used. Example: localhost:50140|localhost:50141,3 """ self.ring_lock.acquire() logging.debug("adding: %s", new_node) host, port = new_node.split(":") newserver = Server(host, port) self.hash_ring.remove_node(newserver) # Add the new server to all existing nodes command = DHTCommand(DHTCommand.ADDNODE, newserver) self.forward_command(command) # Convert to a string list ring = map(lambda serv: str(serv), self.hash_ring.get_nodelist()) # Add new server to this ring self.hash_ring.add_node(newserver) # Return |-separated list of nodes self.ring_lock.release() return "|".join(ring) + "," + str(self.hash_ring.replicas)
def get_command(self, string): for i,command in DHTCommand().allcommands.iteritems(): if command == string.upper(): return i else: return 0