Пример #1
0
class Node(object):
    """
    Construct a new :class:`Node`.

    :param identity: The identity string of this Node.
    :param repAddr: The ZMQ address to bind the REP socket to.
    :param pubAddr: The ZMQ address to bind the PUB socket to.
    :param ctx: The ZMQ Context object to operate from.
    :param poolSize: The size of the greenlet pool this Node will operate from.

    """
    def __init__(self, identity, repAddr, pubAddr, ctx=None, poolSize=200):
        self._greenletPool = Pool(poolSize)
        self._id = identity
        self._repAddr = repAddr
        self._pubAddr = pubAddr
        self._ctx = ctx or zmq.Context.instance()
        self._rep = self._ctx.socket(zmq.XREP)
        self._rep.setsockopt(zmq.IDENTITY, self._id + ":REP")
        self._rep.bind(repAddr)
        self._pub = self._ctx.socket(zmq.PUB)
        self._pub.setsockopt(zmq.IDENTITY, self._id + ":PUB")
        self._pub.bind(pubAddr)
        self.__peersConnected = set()
        self._sub = self._ctx.socket(zmq.SUB)
        self._sub.setsockopt(zmq.SUBSCRIBE, "")
        self.__subConnected = set()
        self._req = self._ctx.socket(zmq.XREQ)
        self._peers = dict()
        self._table = Table()
        self._controlSock = self._ctx.socket(zmq.REP)
        self._controlSock.bind('ipc://.zhtnode-control-' + identity)

    def spawn(self, f, *args, **kwargs):
        """
        Spawn a new greenlet in this Node's pool

        :param f: A callable that will be called from the new greenlet.
        :param args: Argument list to call f with.
        :param kwargs: Keyword arguments to call f with.

        """
        return self._greenletPool.spawn(f, *args, **kwargs)

    def _subConnect(self, addr):
        """
        Connect this Node's SUB socket to an address.

        :param addr: The ZMQ address of the PUB socket to connect to.

        """
        connLog.debug("_subConnect('%s')", addr)
        self.__subConnected.add(addr)
        self._sub.connect(addr)

    def _reqConnect(self, addr):
        """
        Return a new REQ socket connected to the given address.

        :param addr" The ZMQ address of the REP socket to connect to.

        """
        sock = self._ctx.socket(zmq.REQ)
        sock.setsockopt(zmq.IDENTITY, str("%s:REQ:%s" % (self._id, addr)))
        sock.connect(addr)
        return sock

    def start(self):
        """
        Start Node operations.

        This method spawns off new greenlets to handle various control messages.
        """
        self.spawn(self._handleRep)
        self.spawn(self._handleSub)
        self.spawn(self._handleControl)
        self.spawn(self._heartbeat)

    def connect(self, addr):
        """
        Connect to a Peer.

        :param addr: The ZMQ address of the Peer's REP socket.

        This will create a new peer and add it to this Node's peer table. A synchronize operation will
        happen in a spawned greenlet.
        """
        connLog.debug("start connect:'%s' peersConnected:'%s'", addr, self.__peersConnected)
        if addr in self.__peersConnected:
            connLog.debug("duplicate")
            return
        else:
            self.__peersConnected.add(addr)
        requestSock = self._reqConnect(addr)
        requestSock.send_multipart(["PEER", self._id, self._repAddr, self._pubAddr])
        reply = requestSock.recv_multipart()
        if reply[1] != self._id and not reply[1] in self._peers:
            self._peers[reply[1]] = Peer(self, reply[1], addr, reply[2], requestSock)
            self._subConnect(reply[2])
            self._pubPeer(reply[1], addr)

    def _handleControl(self):
        """
        Handle commands given over the control socket.
        """
        while True:
            m = self._controlSock.recv_multipart()
            if m[0] == 'EOF':
                self._greenletPool.kill()
                self._controlSock.send('OK')
                return
            elif m[0] == 'CONNECT':
                self._greenletPool.map(self.connect, m[1:])
                self._controlSock.send('OK')
            elif m[0] == 'GET':
                r = []
                for key in m[1:]:
                    try:
                        r.append(self._table[key])
                    except KeyError:
                        if self._table.owns(key):
                            r.append('KeyError')
                        else:
                            r.append(self._rget(key))
                self._controlSock.send_multipart(r)
            elif m[0] == 'RGET':
                r = []
                for key in m[1:]:
                    r.append(self._rget(key))
                self._controlSock.send_multipart(r)
            elif m[0] == 'PUT':
                self._table[m[1]] = m[2]
                self._controlSock.send_multipart(['OK', m[1], m[2]])
                self._pubUpdate(m[1])
            elif m[0] == 'PEERS':
                self._controlSock.send_multipart(['PEERS'] + list(self._peers.keys()))
            else:
                self._controlSock.send(['ERR', 'UNKNOWN COMMAND'] + m)

    def _rget(self, key):
        h = hex_hash(key)
        for pName in self._peers.keys():
            peer = self._peers[pName]
            for b in peer._ownedBuckets:
                if h.startswith(b):
                    return peer._makeRequest(["GET", str(key)])[2] 

    def _handleRep(self):
        """
        Handle requests recieved over the REP socket.

        Each request is handled in a spawned greenlet.
        """
        while True:
            m = self._rep.recv_multipart()
            self.spawn(self._handleRepMessage, m)

    def _handleRepMessage(self, m):
        """
        Handle an individual request recieved over the REP socket.

        :param m: The request to handle.
        """
        log.debug(str(m))
        i = 0
        while m[i] != "":
            i += 1
        envelope = m[:i+1]
        msg = m[i+1:]
        reply = None
        if msg[0] == "PEER":
            peerInfo = (msg[1], msg[2], msg[3])
            repLog.debug("Recieved PEER request: identity:%s  REP:%s  PUB:%s", *peerInfo)
            reply = envelope + ["PEER", self._id, self._pubAddr]
            if not peerInfo[0] in self._peers.keys():
                self._subConnect(peerInfo[2])
                self._peers[peerInfo[0]] = Peer(self, peerInfo[0], peerInfo[1], peerInfo[2], self._reqConnect(peerInfo[1]))
                self._pubPeer(peerInfo[0], peerInfo[1])
        elif msg[0] == "PEERS":
            repLog.debug("Recieved PEERS request")
            reply = envelope
            reply.append("PEERS")
            reply.append(json.dumps(dict(((ident, peer._repAddr) for ident, peer in self._peers.items()))))
        elif msg[0] == "BUCKETS":
            repLog.debug("Recieved BUCKETS request")
            reply = envelope + ["BUCKETS", json.dumps(self._table.ownedBuckets())]
        elif msg[0] == "KEYS":
            repLog.debug("Recieved KEYS request for bucket '%s'" % (msg[1],))
            reply = envelope + ["KEYS", msg[1], json.dumps(self._table.getKeySet(msg[1], includeTimestamp=True))]
        elif msg[0] == "GET":
            repLog.debug("Recieved GET request for key '%s'", msg[1])
            try:
                entry = self._table.getValue(msg[1])
                reply = envelope + ["GET", msg[1], entry._value, repr(entry._timestamp)]
            except KeyError:
                reply = ["ERROR", "KeyError", "GET", msg[1]]
        else:
            reply = envelope + ["ECHO"] + msg
        repLog.debug("REPLY: %s", reply)
        self._rep.send_multipart(reply)

    def _pubUpdate(self, key):
        """
        Send an update message over the PUB socket for the given key.

        :param key: The key to give an update for.
        """
        entry = self._table.getValue(key)
        self._pub.send_multipart(["UPDATE|" + entry._hash, key, entry._value, repr(entry._timestamp)])

    def _pubPeer(self, id, addr):
        self._pub.send_multipart(["PEER", str(id), str(addr)])

    def _handleSub(self):
        """
        Handle messages recieved over the SUB socket.

        Each message is handled in a spawned greenlet.
        """
        while True:
            m = self._sub.recv_multipart()
            self.spawn(self._handleSubMessage, m)

    def _heartbeat(self):
        while True:
            self._pub.send_multipart(['HEARTBEAT', self._id])
            sleep(30)

    def _handleSubMessage(self, m):
        """
        Handle an individual message recieved over the SUB socket.

        :param m: The message to handle.
        """
        subLog.debug("SUB: Recieved %s", m)
        if m[0][:7] == 'UPDATE|':
            subLog.debug("UPDATE key:%s value:%s timestamp:%s", m[1], m[2], m[3])
            if self._table.putValue(m[1], m[2], float(m[3])):
                self._pubUpdate(m[1])
        elif m[0] == 'HEARTBEAT':
            id = m[1]
            subLog.debug("HEARTBEAT: id:'%s'", id)
        elif m[0] == 'PEER':
            id = m[1]
            addr = m[2]
            subLog.debug("PEER: id:'%s', addr:'%s'", id, addr)
            if not id in self._peers.keys() and id != self._id:
                self.connect(addr)