class ZyreNode(): def __init__(self, interface, netiface=None): self.interface = interface # Peers book self.book = {} self.topics = [] # Publisher self.pub_cache = {} self.publisher = Zsock.new_xpub(("tcp://*:*").encode()) # TimeServer self.timereply = Zsock.new_rep(("tcp://*:*").encode()) # Zyre self.zyre = Zyre(None) if netiface: self.zyre.set_interface(string_at(netiface)) self.interface.log("ZYRE Node forced netiface: ", string_at(netiface)) self.zyre.set_name(str(self.interface.hplayer.name()).encode()) self.zyre.set_header(b"TS-PORT", str(get_port(self.timereply)).encode()) self.zyre.set_header(b"PUB-PORT", str(get_port(self.publisher)).encode()) self.zyre.set_interval(PING_PEER) self.zyre.set_evasive_timeout(PING_PEER * 3) self.zyre.set_silent_timeout(PING_PEER * 5) self.zyre.set_expired_timeout(PING_PEER * 10) self.zyre.start() self.zyre.join(b"broadcast") self.zyre.join(b"sync") # Add self to book self.book[self.zyre.uuid()] = Peer( self, { 'uuid': self.zyre.uuid(), 'name': self.zyre.name().decode(), 'ip': '127.0.0.1', 'ts_port': get_port(self.timereply), 'pub_port': get_port(self.publisher) }) self.book[self.zyre.uuid()].subscribe(self.topics) # Start Poller self._actor_fn = zactor_fn( self.actor_fn ) # ctypes function reference must live as long as the actor. if netiface: netiface = create_string_buffer(str.encode(netiface)) self.actor = Zactor(self._actor_fn, netiface) self.done = False # ZYRE Zactor def actor_fn(self, pipe, netiface): # Internal internal_pipe = Zsock(pipe, False) # We don't own the pipe, so False. # Poller poller = Zpoller(self.zyre.socket(), internal_pipe, self.publisher, self.timereply, None) # RUN self.interface.log('Node started') internal_pipe.signal(0) while True: sock = poller.wait(500) if not sock: continue # # ZYRE receive # if sock == self.zyre.socket(): e = ZyreEvent(self.zyre) uuid = e.peer_uuid() # self.interface.log("ZYREmsg", uuid, e.peer_name().decode(), e.type().decode()) # ENTER: add to book for external contact (i.e. TimeSync) if e.type() == b"ENTER": newpeer = Peer(self, e) existing = None if uuid in self.book: # print ('UUID already exist: replacing') ## PROBLEM : Same name may appear with different uuid (not a real problem, only if crash and restart with new uuid in a short time..) self.book[uuid].stop() existing = uuid for p in self.book.values(): if p.name == newpeer.name: # print ('Name already exist: replacing') p.stop() existing = p.uuid if existing: del self.book[existing] self.book[uuid] = newpeer self.book[uuid].subscribe(self.topics) # EVASIVE elif e.type() == b"EVASIVE": # if uuid in self.book: # self.book[uuid].linker(2) pass # SILENT elif e.type() == b"SILENT": if uuid in self.book: self.book[uuid].linker(1) # EXIT elif e.type() == b"EXIT": if uuid in self.book: self.book[uuid].linker(0) self.book[uuid].stop() del self.book[uuid] # JOIN elif e.type() == b"JOIN": # self.interface.log("peer join a group..", e.peer_name(), e.group().decode()) # SYNC clocks if e.group() == b"sync": if self.peer(uuid): self.peer(uuid).sync() # LEAVE elif e.type() == b"LEAVE": # self.interface.log("peer left a group..") pass # SHOUT -> process event elif e.type() == b"SHOUT" or e.type() == b"WHISPER": # Parsing message data = json.loads(e.msg().popstr().decode()) data['from'] = uuid # add group if e.type() == b"SHOUT": data['group'] = e.group().decode() else: data['group'] = 'whisper' self.preProcessor1(data) # # PUBLISHER event # elif sock == self.publisher: msg = Zmsg.recv(self.publisher) if not msg: break topic = msg.popstr() # Somebody subscribed: push Last Value Cache ! if len(topic) > 0 and topic[0] == 1: topic = topic[1:] if topic in self.pub_cache: # self.interface.log('XPUB lvc send for', topic.decode()) msg = Zmsg.dup(self.pub_cache[topic]) Zmsg.send(msg, self.publisher) # else: # self.interface.log('XPUB lvc empty for', topic.decode()) # # TIMESERVER event # elif sock == self.timereply: msgin = Zmsg.recv(self.timereply) msg = Zmsg() msg.addstr(str(int(time.time() * PRECISION)).encode()) Zmsg.send(msg, self.timereply) # # INTERNAL commands # elif sock == internal_pipe: msg = Zmsg.recv(internal_pipe) if not msg or msg.popstr() == b"$TERM": # print('ZYRE Node TERM') break internal_pipe.__del__() self.interface.log(' - node stopped' ) # WEIRD: print helps the closing going smoothly.. self.done = True def stop(self): for peer in self.book.values(): peer.stop() self.actor.sock().send(b"ss", b"$TERM") retry = 0 while not self.done and retry < 10: sleep(0.1) retry += 1 # self.zyre.stop() # HANGS ! self.zyre.__del__() self.publisher.__del__() self.timereply.__del__() def peer(self, uuid): if uuid in self.book and self.book[uuid].active: return self.book[uuid] def peerByName(self, name): for peer in self.book.values(): if peer.active and peer.name == name: return peer # # PUB/SUB # def subscribe(self, topics): if not isinstance(topics, list): topics = [topics] self.topics = list(set(self.topics) | set(topics)) # merge lists and remove duplicates for peer in self.book.values(): peer.subscribe(self.topics) def publish(self, topic, args=None): topic = topic.encode() msg = Zmsg() msg.addstr(topic) msg.addstr(self.zyre.uuid()) msg.addstr(json.dumps(args).encode()) self.pub_cache[topic] = Zmsg.dup(msg) Zmsg.send(msg, self.publisher) # # ZYRE send messages # def makeMsg(self, event, args=None, delay_ms=0, at=0): data = {} data['event'] = event data['args'] = [] if args: if not isinstance(args, list): # self.interface.log('NOT al LIST', args) args = [args] data['args'] = args # at time if at > 0: data['at'] = at # add delay if delay_ms > 0: if not 'at' in data: data['at'] = 0 data['at'] += int(time.time() * PRECISION + delay_ms * PRECISION / 1000) return json.dumps(data).encode() def whisper(self, uuid, event, args=None, delay_ms=0, at=0): data = self.makeMsg(event, args, delay_ms, at) if uuid == self.zyre.uuid(): data = json.loads(data.decode()) data['from'] = 'self' data['group'] = 'whisper' self.preProcessor1(data) else: self.zyre.whispers(uuid, data) def shout(self, group, event, args=None, delay_ms=0, at=0): data = self.makeMsg(event, args, delay_ms, at) self.zyre.shouts(group.encode(), data) # if own group -> send to self too ! groups = zlist_strlist(self.zyre.own_groups()) if group in groups: data = json.loads(data.decode()) data['from'] = 'self' data['group'] = group self.preProcessor1(data) def broadcast(self, event, args=None, delay_ms=0, at=0): self.shout('broadcast', event, args, delay_ms, at) def join(self, group): self.zyre.join(group.encode()) def leave(self, group): self.zyre.leave(group.encode()) # # ZYRE messages processor # def preProcessor1(self, data): # if a programmed time is provided, correct it with peer CS # Set timer if 'at' in data: if self.peer(data['from']): data['at'] -= self.peer(data['from']).clockshift() delay = (data['at']) / PRECISION - time.time() if delay <= -10000: self.interface.log('WARNING event already passed by', delay, 's, its too late !! discarding... ') elif delay <= 0: self.interface.log('WARNING event already passed by', delay, 's, playing late... ') self.preProcessor2(data) elif delay > 3000: self.interface.log('WARNING event in', delay, 's, thats weird, playing now... ') self.preProcessor2(data) else: self.interface.log('programmed event in', delay, 's') t = Timer(delay, self.preProcessor2, args=[data]) t.start() self.interface.emit('planned', data) else: self.preProcessor2(data) def preProcessor2(self, data): self.interface.emit('event', *[data]) self.interface.emit(data['event'], *data['args'])
class FMMonitor(threading.Thread): def __init__(self, context, hostAddress, riapsHome): threading.Thread.__init__(self) self.logger = logging.getLogger(__name__) self.context = context self.hostAddress = hostAddress self.riapsHome = riapsHome self.control = None self.peers = {} # uuid : address - all peers self.groups = {} # groupName : { peers* } - peers in group self.actors = {} # appName : { actors* } - actors in apps self.uuid = None self.logger.info('FMMon:__inited__') def setup(self): self.logger.info('FMMon:setup()') self.control = self.context.socket(zmq.PAIR) self.control.bind(const.fmMonitorEndpoint) return self.control def getUUID(self): while self.uuid == None: time.sleep(0.1) return self.uuid def setupApp(self, appName): msg = ('setupApp', appName) self.control.send_pyobj(msg) def cleanupApp(self, appName): msg = ('cleanupApp', appName) self.control.send_pyobj(msg) def addActor(self, appName, actorName): msg = ('addActor', appName, actorName) self.control.send_pyobj(msg) def delActor(self, appName, actorName): msg = ('delActor', appName, actorName) self.control.send_pyobj(msg) def dieActor(self, appName, actorName): msg = ('dieActor', appName, actorName) self.control.send_pyobj(msg) def terminate(self): if self.control != None: self.control.send_pyobj(('stop', )) else: self.setup() time.sleep(0.1) self.control.send_pyobj(('stop', )) def groupName(self, appName): return b'riaps.' + appName.encode('utf-8') def run(self): self.zyre = Zyre(None) if self.logger.level > 0: self.zyre.set_verbose() # Zsys.set_logsystem(1) else: # Zsys.set_logsystem(0) pass self.uuid = self.zyre.uuid() self.zyre.set_interface(Config.NIC_NAME.encode('utf-8')) if Config.SECURITY: certFile = os.path.join(self.riapsHome, "keys", const.zmqCertificate) cert = czmq.Zcert.load(ctypes.c_char_p(certFile.encode('utf-8'))) self.zyre.set_zcert(cert) self.zyre.set_evasive_timeout(const.peerEvasiveTimeout) self.zyre.set_expired_timeout(const.peerExpiredTimeout) self.zyre.set_header(b'riaps@' + self.hostAddress.encode('utf-8'), b'hello') self.command = self.context.socket(zmq.PAIR) self.command.connect(const.fmMonitorEndpoint) self.zyre.start() self.zyre.join(b'riaps') self.zyreSocket = self.zyre.socket() self.poller = czmq.Zpoller(zyre.c_void_p(self.command.underlying), self.zyreSocket, 0) while True: reader = self.poller.wait(-1) # Wait forever if self.poller.terminated(): self.logger.warning("FMMon.run - poller terminated") break if type( reader ) == zyre.c_void_p and reader.value == self.command.underlying: msg = self.command.recv_pyobj() self.logger.info('FMMon.run - command: %s' % str(msg)) cmd = msg[0] if cmd == 'stop': break elif cmd == 'setupApp': appName = msg[1] group = self.groupName(appName) self.zyre.join(group) if appName not in self.groups: self.groups[appName] = set() self.actors[appName] = set() elif cmd == 'cleanupApp': appName = msg[1] group = self.groupName(appName) self.zyre.leave(group) if appName in self.groups: del self.groups[appName] elif cmd == 'addActor': appName, actorName = msg[1:] assert appName in self.groups self.actors[appName].add(actorName) group = self.groupName(appName) arg = "+ %s.%s" % (appName, actorName) self.logger.info("FMMon.addActor.shout: %s" % arg) self.zyre.shouts(group, arg.encode('utf-8')) for peer in self.groups[appName]: self.logger.info( "FMMon.addactor tell %s.%s has peer at %s" % (appName, actorName, str(peer))) info = ('peer+', appName, actorName, peer) self.command.send_pyobj(info) elif cmd == 'delActor': appName, actorName = msg[1:] assert appName in self.groups and actorName in self.actors[ appName] self.actors[appName].remove(actorName) group = self.groupName(appName) arg = "- %s.%s" % (appName, actorName) self.logger.info("FMMon.delActor.shout: %s" % arg) self.zyre.shouts(group, arg.encode('utf-8')) elif cmd == 'dieActor': appName, actorName = msg[1:] assert appName in self.groups and actorName in self.actors[ appName] self.actors[appName].remove(actorName) group = self.groupName(appName) arg = "? %s.%s" % (appName, actorName) self.logger.info("FMMon.dieActor.shout: %s" % arg) self.zyre.shouts(group, arg.encode('utf-8')) else: pass # Should be error elif reader == self.zyreSocket: event = ZyreEvent(self.zyre) eType = event.type() _pName = event.peer_name() pUUID = event.peer_uuid() pAddr = event.peer_addr() group = event.group() _headers = event.headers() msg = event.get_msg() # if eType != b'EVASIVE': # print("# %s %s %s %s %s %s %s" # % (str(eType),str(_pName),str(pUUID),str(pAddr), # str(group),str(_headers),str(msg))) if eType == b'ENTER': self.logger.info("FMMon.ENTER %s from %s" % (str(pUUID), str(pAddr))) self.peers[pUUID] = pAddr elif eType == b'JOIN': groupName = group.decode() self.logger.info("FMMon.JOIN %s from %s" % (str(groupName), str(pUUID))) if groupName == 'riaps': continue # Joined riaps group - should be validated else: _, appName = groupName.split('.') if appName not in self.groups: self.groups[appName] = {pUUID} if appName not in self.actors: self.actors[appName] = set() else: self.groups[appName].add(pUUID) if appName in self.actors: peer = pUUID for actorName in self.actors[appName]: arg = "+ %s.%s" % (appName, actorName) self.logger.info( "FMMon.JOIN.whisper: %s to %s " % (arg, str(peer))) self.zyre.whispers(peer, arg.encode('utf-8')) self.logger.info( "FMMon.JOIN tell %s.%s has peer at %s" % (appName, actorName, str(peer))) info = ('peer+', appName, actorName, peer) self.command.send_pyobj(info) elif eType == b'LEAVE': groupName = group.decode() self.logger.info("FMMon.LEAVE %s from %s" % (str(pUUID), str(group))) if groupName == 'riaps': continue # Left riaps group - should be validated else: _, appName = groupName.split('.') if appName in self.groups: self.groups[appName].remove(pUUID) if appName in self.actors: for actorName in self.actors[appName]: peer = pUUID self.logger.info( "FMMon.LEAVE tell %s.%s lost peer at %s" % (appName, actorName, str(peer))) info = ('peer-', appName, actorName, peer) self.command.send_pyobj(info) elif eType == b'EXIT': self.logger.info("FMMon.EXIT %s " % (str(pUUID))) for appName, group in self.groups.items(): if pUUID in group: if appName in self.actors: for actorName in self.actors[appName]: peer = pUUID self.logger.info( "FMMon.EXIT tell %s.%s lost peer at %s" % (appName, actorName, str(peer))) info = ('peer-', appName, actorName, peer) self.command.send_pyobj(info) del self.peers[pUUID] elif eType == b'SHOUT' or eType == b'WHISPER': arg = msg.popstr().decode() self.logger.info("FMMon.SHOUT %s = %s " % (str(pUUID), arg)) peer = pUUID cmd, pair = arg.split(' ') appName, actorName = pair.split('.') head, cast = '?', '?' if cmd == '+': head, cast = 'peer+', ' has peer ' elif cmd == '-': head, cast = 'peer-', ' lost peer ' elif cmd == '?': head, cast = 'peer-', ' lost peer ' if appName not in self.groups: self.groups[appName] = {pUUID} if appName in self.actors: for actorName in self.actors[appName]: self.logger.info("FMMon.%s: tell %s.%s %s %s" % (eType.decode(), appName, actorName, cast, str(peer))) info = (head, appName, actorName, peer) self.command.send_pyobj(info) else: pass self.command.close() for appName in self.actors: if appName in self.groups: group = self.groupName(appName) arg = "- %s.%s" % (appName, actorName) self.logger.info("FMMon.terminate.shout: %s" % arg) self.zyre.shouts(group, arg.encode('utf-8')) self.zyre.leave(b'riaps') self.zyre.stop()
class DhtPeerMon(threading.Thread): def __init__(self, context, hostAddress, riapsHome, dht, dhtPort): threading.Thread.__init__(self, daemon=True) self.logger = logging.getLogger(__name__) self.context = context self.hostAddress = hostAddress self.riapsHome = riapsHome self.control = None self.dht = dht self.dhtPort = dhtPort self.peers = {} # uuid : address - all peers self.peerGroup = set() # set(uuid) of peer group members self.uuid = None self.logger.info('DhtPeerMon:__inited__') def setup(self): self.logger.info('DhtPeerMon:setup()') self.control = self.context.socket(zmq.PAIR) self.control.bind(const.discoDhtPeerMonEndpoint) return self.control def terminate(self): if self.control != None: self.control.send_pyobj(('stop', )) else: self.setup() time.sleep(0.1) self.control.send_pyobj(('stop', )) def peerHeaderKey(self, ipAddress): return b'riaps_disco@' + ipAddress.encode('utf-8') PEERMARK = b'CAFE' PEERGROUP = b'riaps_disco' def run(self): self.zyre = Zyre(None) if self.logger.level == logging.DEBUG: self.zyre.set_verbose() else: pass self.uuid = self.zyre.uuid() self.zyre.set_interface(Config.NIC_NAME.encode('utf-8')) if Config.SECURITY: certFile = os.path.join(self.riapsHome, "keys", const.zmqCertificate) cert = czmq.Zcert.load(ctypes.c_char_p(certFile.encode('utf-8'))) self.zyre.set_zcert(cert) self.zyre.set_evasive_timeout(const.peerEvasiveTimeout) self.zyre.set_expired_timeout(const.peerExpiredTimeout) self.zyre.set_header(self.peerHeaderKey(self.hostAddress), self.PEERMARK) self.command = self.context.socket(zmq.PAIR) self.command.connect(const.discoDhtPeerMonEndpoint) self.zyre.start() self.zyre.join(self.PEERGROUP) self.zyreSocket = self.zyre.socket() self.poller = czmq.Zpoller(zyre.c_void_p(self.command.underlying), self.zyreSocket, 0) while True: reader = self.poller.wait(-1) # Wait forever if self.poller.terminated(): self.logger.info("DhtPeerMon.run - poller terminated") break if type( reader ) == zyre.c_void_p and reader.value == self.command.underlying: msg = self.command.recv_pyobj() self.logger.info('DhtPeerMon.run - command: %s' % str(msg)) cmd = msg[0] if cmd == 'stop': break else: pass # Should be error elif reader == self.zyreSocket: event = ZyreEvent(self.zyre) eType = event.type() _pName = event.peer_name() pUUID = event.peer_uuid() pAddr = event.peer_addr() group = event.group() _headers = event.headers() msg = event.get_msg() # if eType != b'EVASIVE': # print("# %s %s %s %s %s %s %s" # % (str(eType),str(_pName),str(pUUID),str(pAddr), # str(group),str(_headers),str(msg))) if eType == b'ENTER': self.logger.info("DhtPeerMon.ENTER %s from %s" % (str(pUUID), str(pAddr))) try: pAddrStr = pAddr.decode('UTF-8') (peerIp, _peerPort) = parse.parse("tcp://{}:{}", pAddrStr) peerHeaderKey = self.peerHeaderKey(peerIp) _value = _headers.lookup(peerHeaderKey) if (_value): try: value = ctypes.cast(_value, ctypes.c_char_p).value assert value == self.PEERMARK self.peers[pUUID] = peerIp self.logger.info("DhtPeerMon.ENTER valid peer") except: self.logger.info( "DhtPeerMon.ENTER header value mismatch") else: self.logger.info( "DhtPeerMon.ENTER header key mismatch") except: self.logger.info( "DhtPeerMon.ENTER peer addr parsing error") elif pUUID not in self.peers: # Skip the rest if this is not a peer continue elif eType == b'JOIN': groupName = group.decode() peer = pUUID self.logger.info("DhtPeerMon.JOIN %s from %s" % (str(groupName), str(pUUID))) if groupName != self.PEERGROUP: self.logger.info("DhtPeerMon.JOIN another group") pass else: self.peerGroup.add(peer) self.zyre.whispers( peer, ("%s://%d" % (self.PEERGROUP, self.dhtPort)).encode('utf-8')) elif eType == b'SHOUT' or eType == b'WHISPER': arg = msg.popstr().decode() self.logger.info("DhtPeerMon.SHOUT %s = %s " % (str(pUUID), arg)) try: pAddrStr = pAddr.decode('UTF-8') (peerIp, _peerPort) = parse.parse("tcp://{}:{}", pAddrStr) assert peerIp == self.peers[pUUID] peerDhtPort = parse.parse("%s://{}" % self.PEERGROUP, arg) if peerDhtPort: self.logger.info("DhtPeerMon.bootstrap %s:%s" % (peerIp, peerDhtPort)) self.dht.bootstrap(str(peerIp), str(peerDhtPort)) except: self.logger.error("DhtPeerMon.bootstrap failed") elif eType == b'LEAVE': groupName = group.decode() self.logger.info("DhtPeerMon.LEAVE %s from %s" % (str(pUUID), str(group))) if groupName != self.PEERGROUP: self.logger.info("DhtPeerMon.LEAVE another group") pass else: self.peerGroup.discard(pUUID) elif eType == b'EXIT': self.logger.info("DhtPeerMon.EXIT %s " % (str(pUUID))) if pUUID in self.peers: del self.peers[pUUID] self.peerGroup.discard(pUUID) else: pass self.command.close() self.zyre.leave(self.PEERGROUP) self.zyre.stop()