Beispiel #1
0
    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)
Beispiel #2
0
 def setUp(self):
     host = "localhost"
     self.ports = [50140]  #range(50140,50144)
     self.servers = []
     for port in self.ports:
         self.servers.append(Server(host, port))
     self.dht = MyDHTClient(True)
Beispiel #3
0
 def start(self,
           host,
           port,
           replicas,
           remote_server=None,
           is_process=False):
     """ Starts the server with `hostname`, `port`
         If `remove_server` is not None it will be contacted to join an existing ring
         `is_process` is used when starting servers as processes, it is used to exit
         in a way pyunit likes if True.
     """
     self.this_server = Server(host, port)
     self.remote_server = remote_server
     self.replicas = replicas
     self.is_process = is_process
     self.serve()
Beispiel #4
0
 def start(self,host,port,replicas,remote_server=None,is_process=False):
     """ Starts the server with `hostname`, `port`
         If `remove_server` is not None it will be contacted to join an existing ring
         `is_process` is used when starting servers as processes, it is used to exit
         in a way pyunit likes if True.
     """
     self.this_server = Server(host,port)
     self.remote_server = remote_server
     self.replicas = replicas
     self.is_process = is_process
     self.serve()
Beispiel #5
0
    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()
Beispiel #6
0
    def cmdlinestart(self):
        """ Parse command line parameters and start client
        """
        try:
            if self.getopt("--help"):
                self.help()
            port = int(self.getarg("-p") or self.getarg("--port", 50140))
            host = self.getarg("-h") or self.getarg("--hostname", "localhost")
            server = Server(host, port)
            no_serves = self.getarg("-n") or self.getarg("--no_servers", 5)
            no_serves = int(no_serves)
            remoteserver = self.getarg("-s") or self.getarg("-server")
            if not remoteserver:
                remoteserver = str(server)
                print self.get_start_string(host, port)
            else:
                print self.get_start_string(host, port, remoteserver)

            for i in range(1, no_serves):
                print self.get_start_string(host, port + i, remoteserver)

        except TypeError:
            self.help()
Beispiel #7
0
    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)
Beispiel #8
0
class MyDHTServer(CmdApp):
    def __init__(self):
        """ Main class for the DHT server
        """
        CmdApp.__init__(self)
        self.remote_server = None
        self.dht_table = None
        self.client = MyDHTClient()
        self.ring_lock = threading.RLock()
        self.usage = \
        """
           -p, --port
             specify port (default: 50140)
           -h, --hostname
             specify hostname (default: localhost)
           -s, --server
             specify server to join existing ring
           -r, --replicas
             specify the number of replicas (only first server may specify replicas, 3 is default)
        """

    def cmdlinestart(self):
        """ Start server from cmdline
        get -p, --port and -h, --hostname parameters
        """
        try:
            port = int(self.getarg("-p") or self.getarg("--port",50140))
            host = self.getarg("-h") or self.getarg("--hostname","localhost")
            # Check if we should connect to existing ring
            remoteserver = self.getarg("-s") or self.getarg("-server")
            replicas = self.getarg("-r") or self.getarg("--replicas", 3)
            replicas = int(replicas)
            # Start the server
            self.start(host=host,port=port,replicas=replicas,remote_server=remoteserver)
        except ValueError:
            self.help()

    def start(self,host,port,replicas,remote_server=None,is_process=False):
        """ Starts the server with `hostname`, `port`
            If `remove_server` is not None it will be contacted to join an existing ring
            `is_process` is used when starting servers as processes, it is used to exit
            in a way pyunit likes if True.
        """
        self.this_server = Server(host,port)
        self.remote_server = remote_server
        self.replicas = replicas
        self.is_process = is_process
        self.serve()

    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 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 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 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 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 forward_command(self,command):
        """ Forwards `command` to all other servers except this
            Sets command.forwarded to True so that the receiving
            nodes does not forward the command.
        """
        if not command.forwarded:
            command.forwarded = True
            for server in self.hash_ring.get_nodelist():
                if self.this_server != server:
                    remote_status = self.client.sendcommand(server,copy.deepcopy(command))
                    logging.debug(remote_status)
        return command

    def handle_replica_command(self,command,client_sock):
        """ Handle `command` from `client_sock`
            If the key is found on this server it will be handled here
            or else it will be forwarded to the first server that is
            responding.

            If it is a `DHTCommand.GET` and the key is found locally
            no other servers will be contacted.
        """
        # Find out where the key is found
        key_is_at = self.hash_ring.get_replicas(command.key)
        replica_servers = " ".join(map(lambda msg: str(msg), key_is_at))
        logging.debug("%s is at [%s] according to [%s]", command.key, replica_servers, str(self.hash_ring))

        # If key is local, remove this server from replicas
        local = (self.this_server in key_is_at)
        if local:
            key_is_at.remove(self.this_server)

        # If the command is PUT, download the data
        if command.action == DHTCommand.PUT:
            command.value = self.client.read_from_socket(command.size,client_sock)

        # If key is on this server, perform the action, if it was a get return immediately
        if local:
            status = self.dht_table.perform(command)
            if command.action == DHTCommand.GET and status != "ERR_VALUE_NOT_FOUND":
                return status

        # Replicate data to other nodes if it was not a GET command and the command was not already forwarded
        failures = 0
        if not command.forwarded:
            command.forwarded = True
            for server in key_is_at:
                response = self.client.sendcommand(server,copy.deepcopy(command))
                if response:
                    status = response
                    
                    if command.action != DHTCommand.GET:
                        logging.debug("remote status: %s", status)

                    # If this was a get and the response is valid, return
                    if command.action == DHTCommand.GET  and status != "ERR_VALUE_NOT_FOUND":
                        return status
                else:
                    failures += 1

        logging.debug("Performed command %s (failures: %d)", command, failures)
        return status

    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 signal_handler(self,signal,frame):
        """ Handle SIGINT by doing decommission.
        """
        logging.debug("Exiting from SIGINT")
        self.decommission()
        if self.is_process:
            # Exit softly if this is a subprocess
            os._exit(0)
        else:
            # Exit hard if standalone
            sys.exit(0)

    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 serve(self):
        """ Main server process
            Starts a new `server_thread` for new clients
        """
        if not self.this_server:
            self.help()

        # Register SIGINT handler
        signal.signal(signal.SIGINT, self.signal_handler)

        logging.info("Starting server at %s", str(self.this_server))
        server_sock = socket(AF_INET,SOCK_STREAM)
        try:
            server_sock.bind((self.this_server.bindaddress()))
            server_sock.listen(5)

            # Get hash ring, we want to do this after we know that
            # the socket was free
            self.initialise_hashring()

            while 1:
                client_sock, client_addr = server_sock.accept()
                thread.start_new_thread(self.server_thread, (client_sock,))
        except socket_error:
            errno, errstr = sys.exc_info()[:2]
            logging.error("Unable to bind to socket: %s", errstr)
        except RuntimeError, e:
            logging.error("%s", e)
Beispiel #9
0
class MyDHTServer(CmdApp):
    def __init__(self):
        """ Main class for the DHT server
        """
        CmdApp.__init__(self)
        self.remote_server = None
        self.dht_table = None
        self.client = MyDHTClient()
        self.ring_lock = threading.RLock()
        self.usage = \
        """
           -p, --port
             specify port (default: 50140)
           -h, --hostname
             specify hostname (default: localhost)
           -s, --server
             specify server to join existing ring
           -r, --replicas
             specify the number of replicas (only first server may specify replicas, 3 is default)
        """

    def cmdlinestart(self):
        """ Start server from cmdline
        get -p, --port and -h, --hostname parameters
        """
        try:
            port = int(self.getarg("-p") or self.getarg("--port", 50140))
            host = self.getarg("-h") or self.getarg("--hostname", "localhost")
            # Check if we should connect to existing ring
            remoteserver = self.getarg("-s") or self.getarg("-server")
            replicas = self.getarg("-r") or self.getarg("--replicas", 3)
            replicas = int(replicas)
            # Start the server
            self.start(host=host,
                       port=port,
                       replicas=replicas,
                       remote_server=remoteserver)
        except ValueError:
            self.help()

    def start(self,
              host,
              port,
              replicas,
              remote_server=None,
              is_process=False):
        """ Starts the server with `hostname`, `port`
            If `remove_server` is not None it will be contacted to join an existing ring
            `is_process` is used when starting servers as processes, it is used to exit
            in a way pyunit likes if True.
        """
        self.this_server = Server(host, port)
        self.remote_server = remote_server
        self.replicas = replicas
        self.is_process = is_process
        self.serve()

    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 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 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 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 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 forward_command(self, command):
        """ Forwards `command` to all other servers except this
            Sets command.forwarded to True so that the receiving
            nodes does not forward the command.
        """
        if not command.forwarded:
            command.forwarded = True
            for server in self.hash_ring.get_nodelist():
                if self.this_server != server:
                    remote_status = self.client.sendcommand(
                        server, copy.deepcopy(command))
                    logging.debug(remote_status)
        return command

    def handle_replica_command(self, command, client_sock):
        """ Handle `command` from `client_sock`
            If the key is found on this server it will be handled here
            or else it will be forwarded to the first server that is
            responding.

            If it is a `DHTCommand.GET` and the key is found locally
            no other servers will be contacted.
        """
        # Find out where the key is found
        key_is_at = self.hash_ring.get_replicas(command.key)
        replica_servers = " ".join(map(lambda msg: str(msg), key_is_at))
        logging.debug("%s is at [%s] according to [%s]", command.key,
                      replica_servers, str(self.hash_ring))

        # If key is local, remove this server from replicas
        local = (self.this_server in key_is_at)
        if local:
            key_is_at.remove(self.this_server)

        # If the command is PUT, download the data
        if command.action == DHTCommand.PUT:
            command.value = self.client.read_from_socket(
                command.size, client_sock)

        # If key is on this server, perform the action, if it was a get return immediately
        if local:
            status = self.dht_table.perform(command)
            if command.action == DHTCommand.GET and status != "ERR_VALUE_NOT_FOUND":
                return status

        # Replicate data to other nodes if it was not a GET command and the command was not already forwarded
        failures = 0
        if not command.forwarded:
            command.forwarded = True
            for server in key_is_at:
                response = self.client.sendcommand(server,
                                                   copy.deepcopy(command))
                if response:
                    status = response

                    if command.action != DHTCommand.GET:
                        logging.debug("remote status: %s", status)

                    # If this was a get and the response is valid, return
                    if command.action == DHTCommand.GET and status != "ERR_VALUE_NOT_FOUND":
                        return status
                else:
                    failures += 1

        logging.debug("Performed command %s (failures: %d)", command, failures)
        return status

    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 signal_handler(self, signal, frame):
        """ Handle SIGINT by doing decommission.
        """
        logging.debug("Exiting from SIGINT")
        self.decommission()
        if self.is_process:
            # Exit softly if this is a subprocess
            os._exit(0)
        else:
            # Exit hard if standalone
            sys.exit(0)

    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 serve(self):
        """ Main server process
            Starts a new `server_thread` for new clients
        """
        if not self.this_server:
            self.help()

        # Register SIGINT handler
        signal.signal(signal.SIGINT, self.signal_handler)

        logging.info("Starting server at %s", str(self.this_server))
        server_sock = socket(AF_INET, SOCK_STREAM)
        try:
            server_sock.bind((self.this_server.bindaddress()))
            server_sock.listen(5)

            # Get hash ring, we want to do this after we know that
            # the socket was free
            self.initialise_hashring()

            while 1:
                client_sock, client_addr = server_sock.accept()
                thread.start_new_thread(self.server_thread, (client_sock, ))
        except socket_error:
            errno, errstr = sys.exc_info()[:2]
            logging.error("Unable to bind to socket: %s", errstr)
        except RuntimeError, e:
            logging.error("%s", e)