Esempio n. 1
0
class RecurringTask(object):

    def __init__(self, interval, fn):
        self.interval = interval
        self.fn = fn
        self._wakeup = Event()
        self._stopped = Event()
        self._gthread = None

    def touch(self):
        """Make sure the task is executed now."""
        self._wakeup.set()
    
    def start(self):
        self._gthread = gevent.spawn(self._run)

    def stop(self):
        self._stopped.set()
        self._wakeup.set()

    def _run(self):
        while not self._stopped.is_set():
            self.fn()
            self._wakeup.wait(timeout=self.interval)
            self._wakeup.clear()
Esempio n. 2
0
class GeventCursor(net.Cursor):
    def __init__(self, *args, **kwargs):
        super(GeventCursor, self).__init__(*args, **kwargs)
        self.new_response = Event()

    def __iter__(self):
        return self

    def __next__(self):
        return self._get_next(None)

    def _empty_error(self):
        return GeventCursorEmpty()

    def _extend(self, res):
        super(GeventCursor, self)._extend(res)
        self.new_response.set()
        self.new_response.clear()

    def _get_next(self, timeout):
        with gevent.Timeout(timeout, RqlTimeoutError()) as timeout:
            self._maybe_fetch_batch()
            while len(self.items) == 0:
                if self.error is not None:
                    raise self.error
                self.new_response.wait()
            return self.items.popleft()
    def __call__(self, environ, start_response):
        handler = self.websocket.routes.get(environ['PATH_INFO'])

        if not handler:
            return self.wsgi_app(environ, start_response)

        # do handshake
        uwsgi.websocket_handshake(environ['HTTP_SEC_WEBSOCKET_KEY'], environ.get('HTTP_ORIGIN', ''))

        # setup events
        send_event = Event()
        send_queue = Queue(maxsize=1)

        recv_event = Event()
        recv_queue = Queue(maxsize=1)

        # create websocket client
        client = self.client(environ, uwsgi.connection_fd(), send_event,
                             send_queue, recv_event, recv_queue, self.websocket.timeout)

        # spawn handler
        handler = spawn(handler, client)

        # spawn recv listener
        def listener(client):
            ready = select([client.fd], [], [], client.timeout)
            recv_event.set()
        listening = spawn(listener, client)

        while True:
            if not client.connected:
                recv_queue.put(None)
                listening.kill()
                handler.join(client.timeout)
                return ''

            # wait for event to draw our attention
            ready = wait([handler, send_event, recv_event], None, 1)

            # handle send events
            if send_event.is_set():
                try:
                    uwsgi.websocket_send(send_queue.get())
                    send_event.clear()
                except IOError:
                    client.connected = False

            # handle receive events
            elif recv_event.is_set():
                recv_event.clear()
                try:
                    recv_queue.put(uwsgi.websocket_recv_nb())
                    listening = spawn(listener, client)
                except IOError:
                    client.connected = False

            # handler done, we're outta here
            elif handler.ready():
                listening.kill()
                return ''
Esempio n. 4
0
class Chat(object):
    def __init__(self):
        # at some point, may want to implement a buffer for messages
        # self.buffer = []
        self.msg_event = Event()

    def index(self, request):
        form = models.MsgForm()
        msg_list = models.Msg.objects.all()
        return render(request, 'index.html', {
            'form': form,
            'msg_list': msg_list,
        })

    def send(self, request):
        if request.method == 'POST':
            form = models.MsgForm(request.POST)
            if form.is_valid():
                form.save()
                # tell everyone who's waiting on msg_event that a msg was just
                # posted
                self.msg_event.set()
                self.msg_event.clear()
                return HttpResponse(json.dumps(True), mimetype='application/json')
        return HttpResponse(json.dumps(False), mimetype='application/json')

    def update(self, request):
        check_time = datetime.now()
        # wait for next msg post
        self.msg_event.wait()
        msg_list = models.Msg.objects.filter(time_stamp__gte=check_time)
        return HttpResponse(serializers.serialize('xml', msg_list), mimetype='text/xml')
Esempio n. 5
0
class Console(BaseService):

    """A service starting an interactive ipython session when receiving the
    SIGSTP signal (e.g. via keyboard shortcut CTRL-Z).
    """

    name = 'console'

    def __init__(self, app):
        super(Console, self).__init__(app)
        self.interrupt = Event()
        gevent.signal(signal.SIGTSTP, self.interrupt.set)
        self.console_locals = []

    def start(self):
        super(Console, self).start()
        self.console_locals = {}
        self.console_locals.update(self.app.services)
        self.console_locals['app'] = self.app

    def _run(self):
        while True:
            self.interrupt.wait()
            IPython.start_ipython(argv=['--gui', 'gevent'], user_ns=self.console_locals)
            self.interrupt.clear()
Esempio n. 6
0
class TestEvent(Actor):

    '''**Generates a test event at the chosen interval.**

    This module is only available for testing purposes and has further hardly any use.

    Events have following format:

        { "header":{}, "data":"test" }

    Parameters:

        - name (str):               The instance name when initiated.

        - interval (float):         The interval in seconds between each generated event.
                                    Should have a value > 0.
                                    default: 1

    Queues:

        - outbox:    Contains the generated events.
    '''

    def __init__(self, name, interval=1):
        Actor.__init__(self, name, setupbasic=False)
        self.createQueue("outbox")
        self.name = name
        self.interval=interval
        if interval == 0:
            self.sleep = self.doNoSleep
        else:
            self.sleep = self.doSleep

        self.throttle=Event()
        self.throttle.set()

    def preHook(self):
        spawn(self.go)

    def go(self):
        switcher = self.getContextSwitcher(100)
        while switcher():
            self.throttle.wait()
            try:
                self.queuepool.outbox.put({"header":{},"data":"test"})
            except (QueueFull, QueueLocked):
                self.queuepool.outbox.waitUntilPutAllowed()
            self.sleep(self.interval)

    def doSleep(self, interval):
        sleep(interval)

    def doNoSleep(self, interval):
        pass

    def enableThrottling(self):
        self.throttle.clear()

    def disableThrottling(self):
        self.throttle.set()
Esempio n. 7
0
class Chat(object):
    def __init__(self):
        self.new_msg_event = Event()

    def write_message(self, request):
        if not request.user.is_authenticated() or request.method != 'POST':
            return HttpResponse(status=404)
        form = MessageForm(request.POST)
        output = dict(success=False)
        if form.is_valid():
            form.save(request.user)
            output['success'] = True
        else:
            output['errors'] = form.get_errors()
        self.new_msg_event.set()
        self.new_msg_event.clear()
        return HttpResponse(json.dumps(output))

    def get_messages(self, request):
        if not request.user.is_authenticated():
            return HttpResponse(status=404)
        pk = int(request.GET.get('pk', 1))
        messages = [{'created_at': DateFormat(el.created_at).format('H:i:s'),
                    'username': el.username, 'pk': el.pk,
                    'msg': el.msg} for el in Message.objects.filter(pk__gt=int(pk))
                                            .order_by('-created_at')[:100]]
        if not messages:
            self.new_msg_event.wait()
        return HttpResponse(json.dumps(messages[::-1]))
Esempio n. 8
0
 def test(self):
     e = Event()
     waiters = [gevent.spawn(e.wait) for i in range(self.N)]
     gevent.sleep(0.001)
     e.set()
     e.clear()
     for t in waiters:
         t.join()
Esempio n. 9
0
class Streamer(object):
    def __init__(self, data_product_id, interval=1, simple_time=False, connection=False):
        self.resource_registry = Container.instance.resource_registry
        self.pubsub_management = PubsubManagementServiceClient()
        self.data_product_id = data_product_id
        self.i = 0
        self.interval = interval
        self.simple_time = simple_time
        self.finished = Event()
        self.g = gevent.spawn(self.run)
        self.connection = connection

    def run(self):
        connection = uuid4().hex
        while not self.finished.wait(self.interval):
            rdt = ParameterHelper.rdt_for_data_product(self.data_product_id)
            now = time.time()
            if self.simple_time:
                rdt["time"] = [self.i]
            else:
                rdt["time"] = np.array([now + 2208988800])
            rdt["temp"] = self.float_range(10, 14, np.array([now]))
            rdt["pressure"] = self.float_range(11, 12, np.array([now]))
            rdt["lat"] = [41.205]
            rdt["lon"] = [-71.74]
            rdt["conductivity"] = self.float_range(3.3, 3.5, np.array([now]))
            rdt["driver_timestamp"] = np.array([now + 2208988800])
            rdt["preferred_timestamp"] = ["driver_timestamp"]
            if self.connection:
                ParameterHelper.publish_rdt_to_data_product(
                    self.data_product_id, rdt, connection_id=connection, connection_index=self.i
                )
            else:
                ParameterHelper.publish_rdt_to_data_product(self.data_product_id, rdt)
            self.i += 1

    def stop(self):
        self.finished.set()
        self.g.join(5)
        self.g.kill()

    def start(self):
        self.finished.clear()
        self.g = gevent.spawn(self.run)

    @classmethod
    def float_range(cls, minvar, maxvar, t):
        """
        Produces a signal with values between minvar and maxvar 
        at a frequency of 1/60 Hz centered at the midpoint 
        between minvar and maxvar.


        This method provides a deterministic function that 
        varies over time and is sinusoidal when graphed.
        """
        a = (maxvar - minvar) / 2
        return np.sin(np.pi * 2 * t / 60) * a + (minvar + a)
Esempio n. 10
0
class AvailManager(object):
    SIM_RUNNING = False
    sim_thread = None

    def __init__(self):
        self.updates = []
        self.event = Event()


    def run_sim(self, delay, size):
        roomids = Room.objects.filter(avail=True).values_list('id',flat=True)
        #print 'Room ids: %s ' % roomids
        ids = []
        for i in range(0, len(roomids)):
            ids.append(roomids[i])
            random.shuffle(ids)
        for i in range(0, len(ids)):
            roomg = Room.objects.filter(id=ids[i])
            updateavail(Room.objects.filter(id=ids[i]))
            room = roomg[0]
            self.updates.append(RoomUpdate(room))
            self.event.set()
            self.event.clear()
            if i % size == 0:
                sleep(delay)
        self.SIM_RUNNING = False

    def start_sim(self, delay, size=1):
        if self.SIM_RUNNING:
            kill(self.sim_thread)
        self.updates = []
        Room.objects.all().update(avail=True)
        self.sim_thread = spawn(self.run_sim, delay=delay, size=size)
        self.SIM_RUNNING = True
        return HttpResponse('Started Simulation with delay %d' % delay)

    def stop_sim(self):
        if not self.SIM_RUNNING:
            return HttpResponse('No current simulation')
        kill(self.sim_thread)
        self.SIM_RUNNING = False
        return HttpResponse('Stopped simulation')
    
    def check_avail(self, timestamp):
        if len(self.updates) == 0 or timestamp > self.updates[0].timestamp:
            self.event.wait()
        room_ids = []
        i = len(self.updates) - 1
        while i >= 0:
            i = i - 1
            update = self.updates[i]
            if timestamp <= update.timestamp:
                room_ids.append(update.room_id)
            else:
                break

        return {'timestamp':int(time.time()),
                'rooms':room_ids}
Esempio n. 11
0
class SphinxServer(object):
    '''Coordinate sphinx greenlets.
    manage method render sphinx pages, initialize and give control to serve,
    watch and render greenlets.
    '''
    def __init__(self, c):
        self.c = c
        self.watch_ev = Event()
        self.render_ev = Event()

    def serve(self):
        '''Serve web requests from path as soon docs are ready.
        Reload remote browser when updates are rendered using websockets
        '''
        host, port = self.c.socket.split(':')
        server = Webserver(
            os.path.join(self.c.sphinx_path, self.c.output),
            host,
            port,
            self.render_ev
        )
        server.run()

    def watch(self):
        '''Watch sphinx_path signalling render when rst files change
        '''
        with fs_event_ctx(self.c.sphinx_path, self.c.extensions) as fs_ev_iter:
            for event in fs_ev_iter:
                log.debug('filesystem event: {}'.format(event, event.ev_name))
                self.watch_ev.set()

    def render(self):
        '''Render and listen for doc changes (watcher events)
        '''
        while True:
            self.watch_ev.wait()  # Wait for docs changes
            self.watch_ev.clear()
            with capture_streams() as streams:
                self.build()
            log.debug(streams.getvalue())
            self.render_ev.set()

    def build(self):
        '''Render reStructuredText files with sphinx'''
        return build_main(['sphinx-build', self.c.sphinx_path,
            os.path.join(self.c.sphinx_path, self.c.output)])

    def manage(self):
        '''Manage web server, watcher and sphinx docs renderer
        '''
        with capture_streams() as streams:
            ret = self.build()
        if ret != 0:
            sys.exit(streams.getvalue())
        log.debug(streams.getvalue())
        workers = [spawn(self.serve), spawn(self.watch), spawn(self.render)]
        joinall(workers)
Esempio n. 12
0
class _ResponseIterator(object):
    _global_counter = 0

    def __init__(self, maintainOrder, preprocessor):
        self._currentIndex = 0 if maintainOrder else None
        self._preprocessor = preprocessor
        self._responseAdded = Event()
        self._responses = OrderedDict()
        
        # A request is in-flight the moment it is popped off the request queue, until it is either added to this iterator or discarded (due to being killed)
        # The "done" variable is necessary to handle some corner cases, such as right at the beginning before the first request is in-flight
        self._inflight = 0
        self._done = False

        # Purely for debugging purposes to help differentiate the iterators
        # It's surprisingly frequent that new iterators will occupy exactly the same stop
        # in memory as a previous iterator, making id(self) not very useful.
        _ResponseIterator._global_counter += 1
        self._counter = _ResponseIterator._global_counter

    def __iter__(self):
        return self

    def _add(self, bundle, requestIndex):
        if self._responses is not None:
            self._responses[requestIndex] = bundle
        self._inflight -= 1
        #print('(Notify it.) %s [%d] %d, %s, %d, %s' % ( time(), self._counter, self._inflight, self._done, len(self._responses), bundle.request.url ))
        self._responseAdded.set()

    def next(self):
        while True:
            #print('(Loop it.  ) %s [%d] %d, %s, %d' % ( time(), self._counter, self._inflight, self._done, len(self._responses) ))
            if self._inflight == 0 and self._done and (self._responses is None or len(self._responses) == 0):
                raise StopIteration

            found = False
            if self._responses is not None and len(self._responses) > 0:
                if self._currentIndex is None:
                    found = True
                    bundle = self._responses.popitem(last = False)[1]
                else:
                    if self._currentIndex in self._responses:
                        found = True
                        bundle = self._responses.pop(self._currentIndex)
                        self._currentIndex += 1

            if found:
                #print('(Return it.) %s [%d] %d, %s, %d, %s' % ( time(), self._counter, self._inflight, self._done, len(self._responses), bundle.request.url ))
                if bundle.exception is None:
                    return self._preprocessor.success(bundle)
                else:
                    return self._preprocessor.error(bundle)
            else:
                #print('(Wait it.  ) %s [%d] %d, %s, %d' % ( time(), self._counter, self._inflight, self._done, len(self._responses) ))
                self._responseAdded.clear()
                self._responseAdded.wait()
Esempio n. 13
0
class SphinxServer(object):
    '''Coordinate sphinx greenlets.
       manage method render sphinx pages, initialize and give control to serve,
       watch and render greenlets.'''
    def __init__(self, c):
        self.c = c
        self.watch_ev = Event()
        self.render_ev = Event()

    def serve(self):
        '''Serve web requests from path as soon docs are ready.
           Reload remote browser when updates are rendered using websockets'''
        host, port = self.c.socket.split(':')
        server = Webserver(self.c.path_dest, host, port, self.render_ev)
        log.warning('Listening on http://%s:%s', host, port)
        server.run()

    def watch(self):
        '''Watch sphinx_path signalling render when rst files change'''
        with fs_event_ctx(self.c.sphinx_path, self.c.extensions) as fs_ev_iter:
            for event in fs_ev_iter:
                log.info('%s %s', event, event.ev_name)
                self.watch_ev.set()

    @elapsed
    def build(self):
        '''Render reStructuredText files with sphinx'''
        proc = Process(target=sphinx.main,
            args=[['sphinx-build'] + self.c.quiet +
            [shlex_quote(self.c.sphinx_path), shlex_quote(self.c.path_dest)]])
        proc.start()
        proc.join()
        return proc.exitcode

    def render(self):
        '''Render and listen for doc changes (watcher events)'''
        while True:
            self.watch_ev.wait()  # Wait for docs changes
            self.watch_ev.clear()
            if self.build() != 0:
                exit_msg('rendering %s. Aborting.' % self.c.sphinx_path)
            self.render_ev.set()

    def manage(self):
        '''Manage web server, watcher and sphinx docs renderer.'''
        def shutdown_handler():
            log.info('Received SIGTERM signal to shut down!')
            killall(workers)

        if self.build() != 0:
            exit_msg('rendering %s. Aborting.' % self.c.sphinx_path)
        workers = [spawn(self.serve), spawn(self.watch), spawn(self.render)]
        signal(SIGTERM, shutdown_handler)
        joinall(workers)
        exit(0)
Esempio n. 14
0
File: irc.py Progetto: Ivoz/Modum
class Irc(object):
    """Handles a client connection to the IRC protocol"""

    def __init__(self, server, publisher, flood_prevention=1):
        self.server = server
        self.publisher = publisher
        self.flood_prevention = flood_prevention
        self._conn = Connection(server['host'], server['port'],
                    server['ssl'], server['timeout'])  # Internal connection
        # Timer to prevent flooding
        self.timer = Event()
        self.timer.set()
        # The canonical channels of IRC to subscribe / publish
        # Receives input to send to irc server
        self.sender = Queue()
        # Receives output to publish
        self.receiver = Queue()
        # Suscribe my output to receive data from connection
        self.publisher.subscribe(self.receiver,
                self._conn.receiver, Msg.from_msg)
        # Subscribe connection to send data from my input
        self.publisher.subscribe(self._conn.sender,
                self.sender, self._prevent_flood)

    @property
    def connected(self):
        return self._conn.connected

    def connect(self):
        if self.connected:
            return True
        self._conn.connect()
        if self.connected:
            return True
        else:
            return self._conn.state

    def disconnect(self):
        self._conn.disconnect()
        return self.connected

    def kill(self):
        """Completely terminate the irc connection"""
        self.publisher.unsubscribe(self.receiver, self._conn.receiver)
        self.publisher.unsubscribe(self._conn.sender, self.sender)
        self._conn.kill()

    def _prevent_flood(self, msg):
        """Used to prevent sending messages extremely quickly"""
        if self.flood_prevention > 0 and msg.cmd != 'PONG':
            self.timer.wait()
            self.timer.clear()
            gevent.spawn_later(self.flood_prevention, self.timer.set)
        return str(msg)
Esempio n. 15
0
class Votes(object):

    def __init__(self):
        self.event = Event()

    def release(self):
        self.event.set()
        self.event.clear()

    def wait(self):
        self.event.wait()
Esempio n. 16
0
class Socket(socket.Socket):
    """Green version of :class:`zmq.core.socket.Socket`

    The following four methods are overridden:

        * _send_message
        * _send_copy
        * _recv_message
        * _recv_copy

    To ensure that the ``zmq.NOBLOCK`` flag is set and that sending or recieving
    is deferred to the hub if a ``zmq.EAGAIN`` (retry) error is raised.
    
    The `__setup_events` method is triggered when the zmq.FD for the socket is
    marked as readable and triggers the necessary read and write events (which
    are waited for in the recv and send methods).
    """

    def __init__(self, context, socket_type):
        super(Socket, self).__init__(context, socket_type)
        self.__setup_events()

    def __setup_events(self):
        self._read_ready = Event()
        self._write_ready = Event()
        try:
            read_event = get_hub().reactor.read_event
            self._state_event = read_event(self.getsockopt(zmq.FD), persist=True)
            self._state_event.add(None, self.__state_changed)
        except AttributeError:
            # for gevent<=0.14 compatibility
            from gevent.core import read_event

            self._state_event = read_event(self.getsockopt(zmq.FD), self.__state_changed, persist=True)

    def __state_changed(self, event, _evtype):
        events = self.getsockopt(zmq.EVENTS)
        if events & zmq.POLLOUT:
            self._write_ready.set()
        if events & zmq.POLLIN:
            self._read_ready.set()

    def _send_message(self, msg, flags=0):
        flags |= zmq.NOBLOCK
        while True:
            try:
                super(Socket, self)._send_message(msg, flags)
                return
            except zmq.ZMQError, e:
                if e.errno != zmq.EAGAIN:
                    raise
                self._write_ready.clear()
            self._write_ready.wait()
Esempio n. 17
0
class Condition(object):
    def __init__(self):
        self._event = Event()

    def broadcast(self):
        try:
            self._event.set()
        finally:
            self._event.clear()

    def wait(self):
        self._event.wait()
Esempio n. 18
0
    class Connection:
        def __init__( self, env, connections, handler, timeout ):
            self.ack_timeout = timeout
            self.ack_done = Event()
            self.ack_id = None
            self.connections = connections
            self.handler = handler
            self.server_event_queue = Queue()
            self.client_event_queue = Queue()
            self._id = uuid().hex

            self.env = deepcopydict( env )
            self.env['connection'] = self

            spawn( self._kill_idle )
            spawn\
                ( self.handler
                , self.env
                , lambda: self.client_event_queue
                , self.server_event_queue.put )


        def _check_next( self, confirm_ID ):
            result = self.server_event_queue.get()
            self.ack_id = confirm_ID
            self.current_result.set( (confirm_ID, result ) )
            self._kill_idle()

        def next( self, confirm_ID, current_ID ):
            if confirm_ID == self.ack_id:
                self.ack_id = None
                self.ack_done.set()
                self.current_result = AsyncResult()
                spawn( self._check_next, current_ID )

            result = self.current_result.get( )
            return result

        def emit( self, event ):
            self.client_event_queue.put( event )

        def _kill_idle( self ):
            self.ack_done.clear()
            self.ack_done.wait( timeout=self.ack_timeout )
            if not self.ack_done.isSet():
                self.close()


        def close( self ):
            self.client_event_queue.put( StopIteration )
            self.server_event_queue.put( StopIteration )
            del self.connections[ self._id ]
Esempio n. 19
0
class Channel(object):
  """
  Represent a channel in shaveet,a channel is something that clients can subscribe to and publish messages to.
  Each subscribed client get all the messages published to the channel.
  Each channel is identified by a name unique to that channel
  """
  
  __slots__ = ('clients','messages','new_message_event','max_messages','id','name')
  
  def __init__(self,name,max_messages):
    self.name = name
    #a cache of recent messages limited by max_messages
    self.messages = []
    #a event that fire on every new message
    self.new_message_event = Event()
    self.max_messages = max_messages
    self.id = 1
    self.clients = set()
    
  def add_client(self,client_id):
    self.clients.add(client_id)
    self.new_message(client_id,dumps({"type":"subscribe"}),True)
    
  def remove_client(self,client_id,gc=False):
    self.clients.discard(client_id)
    self.new_message(client_id,dumps({"type":"unsubscribe","gc":gc}),True)

  def new_message(self,client_id,message,system=False):
    self.messages.append(Message(client_id,message,self.id,system))
    self.id += 1
    #trim list,remove old messages
    if len(self.messages) > self.max_messages:
      self.messages = self.messages[-self.max_messages:]
    self.new_message_event.set()
    self.new_message_event.clear()

  def get_updates(self,cursor):
    """
    get updates for the channel,if there is no updates returns a event for notification
    """
    if not self.messages or cursor >= self.messages[-1].id:
      return self.new_message_event
    
    return [message for message in self.messages if message.id > cursor]
    
  def get_active_clients(self):
    now = int(time())#saves call to time
    return [client_id for client_id in self.clients
              if client_id and not "admin" in client_id and client_exists(client_id) and get_client(client_id).is_active(now)]
              
  def __hash__(self):
    return hash(self.name)
Esempio n. 20
0
class JoinableQueue(Queue):
    """A subclass of :class:`Queue` that additionally has :meth:`task_done` and :meth:`join` methods."""

    def __init__(self, maxsize=None, items=None, unfinished_tasks=None):
        from gevent.event import Event

        Queue.__init__(self, maxsize, items)
        self.unfinished_tasks = unfinished_tasks or 0
        self._cond = Event()
        self._cond.set()

    def copy(self):
        return type(self)(self.maxsize, self.queue, self.unfinished_tasks)

    def _format(self):
        result = Queue._format(self)
        if self.unfinished_tasks:
            result += " tasks=%s _cond=%s" % (self.unfinished_tasks, self._cond)
        return result

    def _put(self, item):
        Queue._put(self, item)
        self.unfinished_tasks += 1
        self._cond.clear()

    def task_done(self):
        """Indicate that a formerly enqueued task is complete. Used by queue consumer threads.
        For each :meth:`get <Queue.get>` used to fetch a task, a subsequent call to :meth:`task_done` tells the queue
        that the processing on the task is complete.

        If a :meth:`join` is currently blocking, it will resume when all items have been processed
        (meaning that a :meth:`task_done` call was received for every item that had been
        :meth:`put <Queue.put>` into the queue).

        Raises a :exc:`ValueError` if called more times than there were items placed in the queue.
        """
        if self.unfinished_tasks <= 0:
            raise ValueError("task_done() called too many times")
        self.unfinished_tasks -= 1
        if self.unfinished_tasks == 0:
            self._cond.set()

    def join(self):
        """Block until all items in the queue have been gotten and processed.

        The count of unfinished tasks goes up whenever an item is added to the queue.
        The count goes down whenever a consumer thread calls :meth:`task_done` to indicate
        that the item was retrieved and all work on it is complete. When the count of
        unfinished tasks drops to zero, :meth:`join` unblocks.
        """
        self._cond.wait()
Esempio n. 21
0
class Observer(Queue):
    def __init__(self, *args, **kw):
        game = kw.pop('game')
        self.event = Event()
        Queue.__init__(self, *args, **kw)
        def reaper():
            self.event.clear()
            self.event.wait(30)
            game.remove_observer(self)
        gevent.spawn(reaper)

    def get(self, *args, **kw):
        self.event.set()
        return Queue.get(self, *args, **kw)
Esempio n. 22
0
class UpdateManager(object):
     
    def __init__(self):
        self.event    = Event()
        self.messages = []
        
    def trigger(self, message):
        try:  
            self.messages.append(message);    
            self.event.set()
            self.event.clear() 
        except Exception, e:   
            log.error("exception notifying") 
            print e
Esempio n. 23
0
class Signals(object):
	def __init__(self):
		self.new_user_event = Event()

	def signaluser(self, request):
		user_id = request.GET.get('id')	
		print self.new_user_event.is_set()	
		self.new_user_event.set()
		self.new_user_event.clear()
		return HttpResponse("New User Signaled")

	def receiveuser(self, request):
		self.new_user_event.wait()
		return HttpResponse("New User Received")
Esempio n. 24
0
class Test_DataServer(unittest.TestCase):
    def janitor(self, *args, **kwargs):
        pass

    def setUp(self):
        self.heartbeat_event = Event()
        self.cfg = Mock()
        self.cfg.data_port = DATA_PORT
        self.q = Queue()
        self.orbpktsrc = MagicMock()
        self.orbpktsrc.subscription.return_value.__enter__.return_value = self.q
        self.ds = DataServer(('localhost', DATA_PORT),
            self.orbpktsrc.subscription, self.heartbeat_event, self.janitor)

    def test_DataServer(self):
        self.cfg.heartbeat_interval = 10
        [self.q.put(('', float(n))) for n in xrange(15)]
        received = []
        def rx():
            try:
                self.ds.start()
                sock = create_connection(('127.0.0.1', DATA_PORT), timeout=2)
                while True:
                    received.append(sock.recv(0x10))
            finally:
                self.ds.stop()
        rxg = spawn(rx)
        sleep(1)
        rxg.kill()
        self.assertEquals(len(received), 15)

    def test_DataServer_heartbeat(self):
        self.cfg.heartbeat_interval = 1
        received = []
        def rx():
            try:
                self.ds.start()
                sock = create_connection(('127.0.0.1', DATA_PORT), timeout=2)
                while True:
                    received.append(sock.recv(0x10))
            finally:
                self.ds.stop()
        rxg = spawn(rx)
        sleep(0.1)
        self.heartbeat_event.set()
        self.heartbeat_event.clear()
        sleep(0.1)
        rxg.kill()
        self.assertEquals(len(received), 1)
Esempio n. 25
0
    def test_data_watcher(self):
        client = self._get_client(handler=self._makeOne())
        client.start()
        client.ensure_path('/some/node')
        ev = Event()

        @client.DataWatch('/some/node')
        def changed(d, stat):
            ev.set()

        ev.wait()
        ev.clear()
        client.set('/some/node', 'newvalue')
        ev.wait()
        client.stop()
class UpdateManager(object):
    def __init__(self):
        print "created update manager"
        self.event = Event()
        print "created event object"
        self.messages = []
        print "inited message queue!"

    def trigger(self, message):
        try:
            self.messages.append(message)
            self.event.set()
            self.event.clear()
        except Exception, e:
            print "exception notifying"
            print e
Esempio n. 27
0
class ChatRoom(object):

    def __init__(self):
        self.msg = []
        self.event = Event()

    def new_message(self, message):
        self.msg.append(message)
        self.event.set()
        self.event.clear()

    def wait(self):
        self.event.wait()

    def get_messages(self):
        return self.msg
Esempio n. 28
0
def test_stopping_an_actor_prevents_it_from_processing_any_more_messages(defer):
    class MyActor(Actor):
        def receive(self, _):
            received.set()
    node = DummyNode()
    defer(node.stop)
    received = Event()
    a = node.spawn(MyActor)
    a << None
    received.wait()
    received.clear()
    a.stop()
    sleep(.001)
    ok_(not received.is_set(), "the '_stop' message should not be receivable in the actor")
    with expect_one_event(DeadLetter(a, None, sender=None)):
        a << None
Esempio n. 29
0
class Site(Greenlet):
    def __init__(self, managers):
        super(Site,self).__init__()
        self.event = Event()
        self.event.clear()
        self.managers = managers
        self.managers.observe(self)

    def update(self):
        self.event.set()

    def _run(self):
        logger.debug("Site started")
        app = Flask(__name__)

        @app.route('/', methods=['GET'])
        def index():
            return render_template('index.html')

        @app.route('/data', methods=['GET'])
        def data():
            now = time.time()
            d = {'contents':[]}

            apps = [value for key,value in self.managers.managers.iteritems() if value['running']]
            d['contents'] = apps
            return jsonify(d)

        @app.route('/updated', methods=['GET'])
        def updated():
            changed = self.event.wait(5.0)
            if changed:
                self.event.clear()
                return 'changed'
            else:
                return 'timeout'

        @app.route('/send', methods=['POST'])
        def send():
            s = sender.AMQPSender()
            req = request
            s.send(exchange='manage',key=request.json['key'],object=request.json['body'])
            return 'OK'

        self.new_data = False
        http_server = WSGIServer(('', 5000), app)
        http_server.serve_forever()
Esempio n. 30
0
def pipe_socket(client, remote):
    def copy(a, b, finish):
        while not finish.is_set():
            try:
                data = a.recv(CHUNKSIZE)
                if not data:
                    break
                b.sendall(data)
            except:
                break
        finish.set()

    finish = Event()
    finish.clear()
    threads = [gevent.spawn(copy, client, remote, finish), gevent.spawn(copy, remote, client, finish)]
    [t.join() for t in threads]
    client.close()
    remote.close()
Esempio n. 31
0
class Group(object):
    """Maintain a group of greenlets that are still running.

    Links to each item and removes it upon notification.
    """
    greenlet_class = Greenlet

    def __init__(self, *args):
        assert len(args) <= 1, args
        self.greenlets = set(*args)
        if args:
            for greenlet in args[0]:
                greenlet.rawlink(self._discard)
        # each item we kill we place in dying, to avoid killing the same greenlet twice
        self.dying = set()
        self._empty_event = Event()
        self._empty_event.set()

    def __repr__(self):
        return '<%s at 0x%x %s>' % (self.__class__.__name__, id(self),
                                    self.greenlets)

    def __len__(self):
        return len(self.greenlets)

    def __contains__(self, item):
        return item in self.greenlets

    def __iter__(self):
        return iter(self.greenlets)

    def add(self, greenlet):
        try:
            rawlink = greenlet.rawlink
        except AttributeError:
            pass  # non-Greenlet greenlet, like MAIN
        else:
            rawlink(self._discard)
        self.greenlets.add(greenlet)
        self._empty_event.clear()

    def _discard(self, greenlet):
        self.greenlets.discard(greenlet)
        self.dying.discard(greenlet)
        if not self.greenlets:
            self._empty_event.set()

    def discard(self, greenlet):
        self._discard(greenlet)
        try:
            unlink = greenlet.unlink
        except AttributeError:
            pass  # non-Greenlet greenlet, like MAIN
        else:
            unlink(self._discard)

    def start(self, greenlet):
        self.add(greenlet)
        greenlet.start()

    def spawn(self, *args, **kwargs):
        greenlet = self.greenlet_class(*args, **kwargs)
        self.start(greenlet)
        return greenlet


#     def close(self):
#         """Prevents any more tasks from being submitted to the pool"""
#         self.add = RaiseException("This %s has been closed" % self.__class__.__name__)

    def join(self, timeout=None, raise_error=False):
        if raise_error:
            greenlets = self.greenlets.copy()
            self._empty_event.wait(timeout=timeout)
            for greenlet in greenlets:
                if greenlet.exception is not None:
                    raise greenlet.exception
        else:
            self._empty_event.wait(timeout=timeout)

    def kill(self, exception=GreenletExit, block=True, timeout=None):
        timer = Timeout.start_new(timeout)
        try:
            try:
                while self.greenlets:
                    for greenlet in list(self.greenlets):
                        if greenlet not in self.dying:
                            try:
                                kill = greenlet.kill
                            except AttributeError:
                                _kill(greenlet, exception)
                            else:
                                kill(exception, block=False)
                            self.dying.add(greenlet)
                    if not block:
                        break
                    joinall(self.greenlets)
            except Timeout:
                ex = sys.exc_info()[1]
                if ex is not timer:
                    raise
        finally:
            timer.cancel()

    def killone(self,
                greenlet,
                exception=GreenletExit,
                block=True,
                timeout=None):
        if greenlet not in self.dying and greenlet in self.greenlets:
            greenlet.kill(exception, block=False)
            self.dying.add(greenlet)
            if block:
                greenlet.join(timeout)

    def apply(self, func, args=None, kwds=None):
        """Equivalent of the apply() builtin function. It blocks till the result is ready."""
        if args is None:
            args = ()
        if kwds is None:
            kwds = {}
        if getcurrent() in self:
            return func(*args, **kwds)
        else:
            return self.spawn(func, *args, **kwds).get()

    def apply_cb(self, func, args=None, kwds=None, callback=None):
        result = self.apply(func, args, kwds)
        if callback is not None:
            Greenlet.spawn(callback, result)
        return result

    def apply_async(self, func, args=None, kwds=None, callback=None):
        """A variant of the apply() method which returns a Greenlet object.

        If callback is specified then it should be a callable which accepts a single argument. When the result becomes ready
        callback is applied to it (unless the call failed)."""
        if args is None:
            args = ()
        if kwds is None:
            kwds = {}
        if self.full():
            # cannot call spawn() directly because it will block
            return Greenlet.spawn(self.apply_cb, func, args, kwds, callback)
        else:
            greenlet = self.spawn(func, *args, **kwds)
            if callback is not None:
                greenlet.link(pass_value(callback))
            return greenlet

    def map(self, func, iterable):
        return list(self.imap(func, iterable))

    def map_cb(self, func, iterable, callback=None):
        result = self.map(func, iterable)
        if callback is not None:
            callback(result)
        return result

    def map_async(self, func, iterable, callback=None):
        """
        A variant of the map() method which returns a Greenlet object.

        If callback is specified then it should be a callable which accepts a
        single argument.
        """
        return Greenlet.spawn(self.map_cb, func, iterable, callback)

    def imap(self, func, iterable):
        """An equivalent of itertools.imap()"""
        return IMap.spawn(func, iterable, spawn=self.spawn)

    def imap_unordered(self, func, iterable):
        """The same as imap() except that the ordering of the results from the
        returned iterator should be considered in arbitrary order."""
        return IMapUnordered.spawn(func, iterable, spawn=self.spawn)

    def full(self):
        return False

    def wait_available(self):
        pass
Esempio n. 32
0
class UserAddressManager:
    """ Matrix user <-> eth address mapping and user / address reachability helper.

    In Raiden the smallest unit of addressability is a node with an associated Ethereum address.
    In Matrix it's a user. Matrix users are (at the moment) bound to a specific homeserver.
    Since we want to provide resiliency against unavailable homeservers a single Raiden node with
    a single Ethereum address can be in control over multiple Matrix users on multiple homeservers.

    Therefore we need to perform a many-to-one mapping of Matrix users to Ethereum addresses.
    Each Matrix user has a presence state (ONLINE, OFFLINE).
    One of the preconditions of running a Raiden node is that there can always only be one node
    online for a particular address at a time.
    That means we can synthesize the reachability of an address from the user presence states.

    This helper internally tracks both the user presence and address reachability for addresses
    that have been marked as being 'interesting' (by calling the `.add_address()` method).
    Additionally it provides the option of passing callbacks that will be notified when
    presence / reachability change.
    """
    def __init__(
        self,
        client: GMatrixClient,
        get_user_callable: Callable[[Union[User, str]], User],
        address_reachability_changed_callback: Callable[
            [Address, AddressReachability], None],
        user_presence_changed_callback: Optional[Callable[[User, UserPresence],
                                                          None]] = None,
        _log_context: Optional[Dict[str, Any]] = None,
    ) -> None:
        self._client = client
        self._get_user = get_user_callable
        self._address_reachability_changed_callback = address_reachability_changed_callback
        self._user_presence_changed_callback = user_presence_changed_callback
        self._stop_event = Event()

        self._reset_state()

        self._log_context = _log_context
        self._log = None
        self._listener_id: Optional[UUID] = None

    def start(self) -> None:
        """ Start listening for presence updates.

        Should be called before ``.login()`` is called on the underlying client. """
        assert self._listener_id is None, "UserAddressManager.start() called twice"
        self._stop_event.clear()
        self._listener_id = self._client.add_presence_listener(
            self._presence_listener)

    def stop(self) -> None:
        """ Stop listening on presence updates. """
        assert self._listener_id is not None, "UserAddressManager.stop() called before start"
        self._stop_event.set()
        self._client.remove_presence_listener(self._listener_id)
        self._listener_id = None
        self._log = None
        self._reset_state()

    @property
    def known_addresses(self) -> KeysView[Address]:
        """ Return all addresses we keep track of """
        return self._address_to_userids.keys()

    def is_address_known(self, address: Address) -> bool:
        """ Is the given ``address`` reachability being monitored? """
        return address in self._address_to_userids

    def add_address(self, address: Address):
        """ Add ``address`` to the known addresses that are being observed for reachability. """
        # Since _address_to_userids is a defaultdict accessing the key creates the entry
        _ = self._address_to_userids[address]

    def add_userid_for_address(self, address: Address, user_id: str):
        """ Add a ``user_id`` for the given ``address``.

        Implicitly adds the address if it was unknown before.
        """
        self._address_to_userids[address].add(user_id)

    def add_userids_for_address(self, address: Address,
                                user_ids: Iterable[str]):
        """ Add multiple ``user_ids`` for the given ``address``.

        Implicitly adds any addresses if they were unknown before.
        """
        self._address_to_userids[address].update(user_ids)

    def get_userids_for_address(self, address: Address) -> Set[str]:
        """ Return all known user ids for the given ``address``. """
        if not self.is_address_known(address):
            return set()
        return self._address_to_userids[address]

    def get_userid_presence(self, user_id: str) -> UserPresence:
        """ Return the current presence state of ``user_id``. """
        return self._userid_to_presence.get(user_id, UserPresence.UNKNOWN)

    def get_address_reachability(self,
                                 address: Address) -> AddressReachability:
        """ Return the current reachability state for ``address``. """
        return self._address_to_reachability.get(address,
                                                 AddressReachability.UNKNOWN)

    def force_user_presence(self, user: User, presence: UserPresence):
        """ Forcibly set the ``user`` presence to ``presence``.

        This method is only provided to cover an edge case in our use of the Matrix protocol and
        should **not** generally be used.
        """
        self._userid_to_presence[user.user_id] = presence

    def populate_userids_for_address(self,
                                     address: Address,
                                     force: bool = False):
        """ Populate known user ids for the given ``address`` from the server directory.

        If ``force`` is ``True`` perform the directory search even if there
        already are known users.
        """
        if force or not self.get_userids_for_address(address):
            self.add_userids_for_address(
                address,
                (user.user_id for user in self._client.search_user_directory(
                    to_normalized_address(address))
                 if self._validate_userid_signature(user)),
            )

    def refresh_address_presence(self, address: Address):
        """
        Update synthesized address presence state from cached user presence states.

        Triggers callback (if any) in case the state has changed.

        This method is only provided to cover an edge case in our use of the Matrix protocol and
        should **not** generally be used.
        """
        composite_presence = {
            self._fetch_user_presence(uid)
            for uid in self._address_to_userids[address]
        }

        # Iterate over UserPresence in definition order (most to least online) and pick
        # first matching state
        new_presence = UserPresence.UNKNOWN
        for presence in UserPresence.__members__.values():
            if presence in composite_presence:
                new_presence = presence
                break

        new_address_reachability = USER_PRESENCE_TO_ADDRESS_REACHABILITY[
            new_presence]

        prev_addresss_reachability = self.get_address_reachability(address)
        if new_address_reachability == prev_addresss_reachability:
            # Cached address reachability matches new state, do nothing
            return
        self.log.debug(
            "Changing address reachability state",
            address=to_checksum_address(address),
            prev_state=prev_addresss_reachability,
            state=new_address_reachability,
        )
        self._address_to_reachability[address] = new_address_reachability
        self._address_reachability_changed_callback(address,
                                                    new_address_reachability)

    def _presence_listener(self, event: Dict[str, Any]):
        """
        Update cached user presence state from Matrix presence events.

        Due to the possibility of nodes using accounts on multiple homeservers a composite
        address state is synthesised from the cached individual user presence states.
        """
        if self._stop_event.ready():
            return
        user_id = event["sender"]
        if event["type"] != "m.presence" or user_id == self._user_id:
            return

        user = self._get_user(user_id)
        user.displayname = event["content"].get(
            "displayname") or user.displayname
        address = self._validate_userid_signature(user)
        if not address:
            # Malformed address - skip
            return

        # not a user we've whitelisted, skip
        if not self.is_address_known(address):
            return
        self.add_userid_for_address(address, user_id)

        new_state = UserPresence(event["content"]["presence"])
        if new_state == self.get_userid_presence(user_id):
            # Cached presence state matches, no action required
            return

        self.log.debug(
            "Changing user presence state",
            user_id=user_id,
            prev_state=self._userid_to_presence.get(user_id),
            state=new_state,
        )
        self._userid_to_presence[user_id] = new_state
        self.refresh_address_presence(address)

        if self._user_presence_changed_callback:
            self._user_presence_changed_callback(user, new_state)

    def log_status_message(self):
        while not self._stop_event.ready():
            addresses_uids_presence = {
                to_checksum_address(address): {
                    user_id: self.get_userid_presence(user_id).value
                    for user_id in self.get_userids_for_address(address)
                }
                for address in self.known_addresses
            }

            log.debug(
                "Matrix address manager status",
                addresses_uids_and_presence=addresses_uids_presence,
                current_user=self._user_id,
            )
            self._stop_event.wait(30)

    def _reset_state(self):
        self._address_to_userids: Dict[Address, Set[str]] = defaultdict(set)
        self._address_to_reachability: Dict[Address,
                                            AddressReachability] = dict()
        self._userid_to_presence: Dict[str, UserPresence] = dict()

    @property
    def _user_id(self) -> str:
        user_id = getattr(self._client, "user_id", None)
        assert user_id, f"{self.__class__.__name__}._user_id accessed before client login"
        return user_id

    def _fetch_user_presence(self, user_id: str) -> UserPresence:
        if user_id not in self._userid_to_presence:
            try:
                presence = UserPresence(
                    self._client.get_user_presence(user_id))
            except MatrixRequestError:
                presence = UserPresence.UNKNOWN
            self._userid_to_presence[user_id] = presence
        return self._userid_to_presence[user_id]

    @staticmethod
    def _validate_userid_signature(user: User) -> Optional[Address]:
        return validate_userid_signature(user)

    @property
    def log(self) -> BoundLoggerLazyProxy:
        if not self._log:
            if not hasattr(self._client, "user_id"):
                return log
            self._log = log.bind(
                **{
                    "current_user": self._user_id,
                    "node": to_checksum_address(
                        self._user_id.split(":", 1)[0][1:]),
                    **(self._log_context or {}),
                })
        return self._log
Esempio n. 33
0
def test_list_service_info(zk, test_application_name, service_holder, hub):
    is_reached = Event()
    is_reached.clear()

    @service_holder.tree_changed.connect_via(service_holder)
    def reach(sender, event):
        print event
        is_reached.set()

    def wait_and_reset():
        is_reached.wait()
        is_reached.clear()

    base_path = '/huskar/service/%s' % test_application_name
    zk.create('%s/stable' % base_path, makepath=True)
    wait_and_reset()
    assert dict(service_holder.list_service_info()) == {
        'overall': {},
        'stable': {}
    }

    zk.set(base_path, json.dumps({'info': {'balance_policy': 'RoundRobin'}}))
    wait_and_reset()
    assert dict(service_holder.list_service_info(['overall'])) == {
        'overall': {
            'balance_policy': {
                'value': '"RoundRobin"'
            }
        }
    }

    zk.set('%s/stable' % base_path,
           json.dumps({'info': {
               'dict': {
                   "port": 8080
               }
           }}))
    wait_and_reset()
    assert dict(service_holder.list_service_info()) == {
        'overall': {
            'balance_policy': {
                'value': '"RoundRobin"'
            }
        },
        'stable': {
            'dict': {
                'value': '{"port": 8080}'
            }
        }
    }

    assert dict(service_holder.list_service_info(['stable'])) == {
        'stable': {
            'dict': {
                'value': '{"port": 8080}'
            }
        }
    }

    zk.create('%s/overall' % base_path,
              json.dumps({'info': {
                  'balance_policy': 'Random'
              }}),
              makepath=True)
    wait_and_reset()
    assert dict(service_holder.list_service_info()) == {
        'overall': {
            'balance_policy': {
                'value': '"RoundRobin"'
            }
        },
        'stable': {
            'dict': {
                'value': '{"port": 8080}'
            }
        }
    }

    zk.set('%s/stable' % base_path, '233')
    wait_and_reset()
    assert dict(service_holder.list_service_info(['stable'])) == {'stable': {}}
Esempio n. 34
0
class RNOService(BaseService):

    # required by BaseService
    name = 'rno'
    default_config = dict(eth=dict(privkey_hex=''))

    # RNO address, where the requests for random number should be addressed to.
    my_addr = None

    # Keeps all transactions not yet processed by loop_body
    tx_queue = None

    # Will be used to a) sign transaction and b) encrypt random number using ECIES
    eccx = None

    privkey_hex = None

    def __init__(self, app):
        super(RNOService, self).__init__(app)
        log.info('Initializing RNO')
        self.config = app.config
        self.interrupt = Event()
        self.tx_queue = Queue()  # thread safe
        self.privkey_hex = self.config['eth']['privkey_hex'].decode('hex')
        self.my_addr = privtoaddr(self.privkey_hex)
        self.eccx = ECCx(None, self.privkey_hex)

    # Process the transaction queue. There is no concurrency problem here since
    # the Queue is thread-safe.
    def loop_body(self):
        log.debug('RNO body', my_addr=self.my_addr)
        while not self.tx_queue.empty():
            tx = self.tx_queue.get()
            if tx.to == self.my_addr:
                self.process_tx(tx)

    # Transactions should be added to a queue so that 'loop_body' process that queue
    # To minimize code dependency and coupling, this method will be called for ALL
    # transactions received.
    # It is called in the loop of eth_service.py -> on_receive_transactions
    def add_transaction(self, tx):
        log.debug('RNO received transaction', tx=tx)
        # All transactions are being queued here to minimize the blocking
        # of caller's thread. Transactions not addressed to RNO are discarded
        # in loop_body.
        self.tx_queue.put(tx)

    # This method is the core of the RNO. Transactions should NOT be processed in the
    # add_transaction otherwise it would block the caller.
    def process_tx(self, tx):
        log.debug('process tx', tx=tx)

        # 2) Extract sender's pubkey from the Electrum-style signature of the tx
        sender_pubkey = self.sender_pubkey_from_tx(tx)
        enc_num = self.generate_encrypted_random_number(sender_pubkey)

        # 5) encrypt RN using reveal host's pubkey (eRN2) (???)
        # this is not specified yet

        # 6) create/send transaction back to tx sender
        self.deliver(enc_num, tx.sender)

        # 7) create/send transaction to reveal host.
        # this is not specified yet

    def sender_pubkey_from_tx(self, tx):
        encoded_signature = _encode_sig(tx.v, tx.r, tx.s)
        message = None  # TODO: find out how to build the data (message) where the signature is applied.
        return recover(message, encoded_signature)

    def generate_encrypted_random_number(self, pubkey):
        # 3) generate the random number
        number = os.urandom(64)

        # 4) encrypt RN using sender's pubkey (eRN1)
        return self.eccx.encrypt(number, pubkey)

    def deliver(self, enc_num, to):
        # nonce = number of transactions already sent by that account
        head = self.app.services.chain.chain.head
        nonce = head.get_nonce(self.my_addr)

        # Took from buterin example:
        # https://blog.ethereum.org/2014/04/10/pyethereum-and-serpent-programming-guide/
        gasprice = 10**12

        # Took from buterin example:
        # https://blog.ethereum.org/2014/04/10/pyethereum-and-serpent-programming-guide/
        startgas = 10000
        value = 0  # It's just a message, don't need to send any value (TODO: confirm that info)

        # data is a json formatted message but has to be 'binary'
        unix_now = int(round(time()))
        payload = {}
        payload['when'] = unix_now
        payload['number'] = enc_num
        payload['publish_on'] = unix_now + 86400  # in 24 hours
        payload['published_at'] = 'http://www.example.com/foo'
        data = json.dumps(payload)

        deliver_tx = Transaction(nonce, gasprice, startgas, to, value, data)
        signed_deliver_tx = deliver_tx.sign(self.privkey_hex)
        success, output = apply_transaction(head, signed_deliver_tx)

    # Sends the reply back to Requester and Reveal Host
    def send_replies(self, number, requester_addr, reveal_host_addr,
                     publish_at, publish_on):
        log.debug('RNO reply',
                  number=number,
                  requester_addr=requester_addr,
                  publish_at=publish_at,
                  publish_on=publish_on)

    # Generates the public address (IPFS?) that will be used by the Reveal Host
    def generate_public_address(self, number):
        address = 'some public address'
        log.debug('RNO pub address', address=address)
        return address

    # This will make the loop_body be executed by RNOService thread.
    def wakeup(self):
        self.interrupt.set()

    # @override BaseService._run (Greenlet._run)
    def _run(self):
        while True:
            self.interrupt.wait()
            self.loop_body()
            self.interrupt.clear()
Esempio n. 35
0
class Console(BaseService):
    """A service starting an interactive ipython session when receiving the
    SIGSTP signal (e.g. via keyboard shortcut CTRL-Z).
    """

    name = 'console'

    def __init__(self, app):
        super(Console, self).__init__(app)
        self.interrupt = Event()
        self.console_locals = {}
        if app.start_console:
            self.start()
            self.interrupt.set()
        else:
            SigINTHandler(self.interrupt)

    def _stop_app(self):
        try:
            self.app.stop()
        except gevent.GreenletExit:
            pass

    def start(self):
        super(Console, self).start()

        class Eth(object):
            """
            convenience object to interact with the live chain
            """
            def __init__(this, app):
                this.app = app
                this.services = app.services
                this.stop = app.stop
                this.chainservice = app.services.chain
                this.chain = this.chainservice.chain
                this.coinbase = app.services.accounts.coinbase

            @property
            def pending(this):
                return this.chainservice.head_candidate

            head_candidate = pending

            @property
            def latest(this):
                return this.chain.head

            def transact(this,
                         to,
                         value=0,
                         data='',
                         sender=None,
                         startgas=25000,
                         gasprice=60 * denoms.shannon):
                sender = normalize_address(sender or this.coinbase)
                to = normalize_address(to, allow_blank=True)
                state = State(this.head_candidate.state_root, this.chain.env)
                nonce = state.get_nonce(sender)
                tx = Transaction(nonce, gasprice, startgas, to, value, data)
                this.app.services.accounts.sign_tx(sender, tx)
                assert tx.sender == sender
                this.chainservice.add_transaction(tx)
                return tx

            def call(this,
                     to,
                     value=0,
                     data='',
                     sender=None,
                     startgas=25000,
                     gasprice=60 * denoms.shannon):
                sender = normalize_address(sender or this.coinbase)
                to = normalize_address(to, allow_blank=True)
                block = this.head_candidate
                state_root_before = block.state_root
                assert block.prevhash == this.chain.head_hash
                # rebuild block state before finalization
                test_state = this.chain.mk_poststate_of_blockhash(
                    block.prevhash)
                initialize(test_state, block)
                for tx in block.transactions:
                    success, _ = apply_transaction(test_state, tx)
                    assert success

                # Need this because otherwise the Transaction.network_id
                # @property returns 0, which causes the tx to fail validation.
                class MockedTx(Transaction):
                    network_id = None

                # apply transaction
                nonce = test_state.get_nonce(sender)
                tx = MockedTx(nonce, gasprice, startgas, to, value, data)
                tx.sender = sender

                try:
                    success, output = apply_transaction(test_state, tx)
                except InvalidTransaction as e:
                    log.debug("error applying tx in Eth.call", exc=e)
                    success = False

                assert block.state_root == state_root_before

                if success:
                    return output
                else:
                    return False

            def find_transaction(this, tx):
                try:
                    t, blk, idx = this.chain.get_transaction(tx.hash)
                except:
                    return {}
                return dict(tx=t, block=blk, index=idx)

            def new_contract(this, abi, address, sender=None):
                return ABIContract(sender or this.coinbase, abi, address,
                                   this.call, this.transact)

            def block_from_rlp(this, rlp_data):
                from .eth_protocol import TransientBlock
                import rlp
                l = rlp.decode_lazy(rlp_data)
                return TransientBlock.init_from_rlp(l).to_block()

        try:
            from ethereum.tools._solidity import solc_wrapper
        except ImportError:
            solc_wrapper = None
            pass

        try:
            import serpent
        except ImportError:
            serpent = None
            pass

        self.console_locals = dict(eth=Eth(self.app),
                                   solidity=solc_wrapper,
                                   serpent=serpent,
                                   denoms=denoms,
                                   true=True,
                                   false=False,
                                   Eth=Eth)

        for k, v in list(self.app.script_globals.items()):
            self.console_locals[k] = v

    def _run(self):
        self.interrupt.wait()
        print('\n' * 2)
        print("Entering Console" + bc.OKGREEN)
        print("Tip:" + bc.OKBLUE)
        print(
            "\tuse `{}lastlog(n){}` to see n lines of log-output. [default 10] "
            .format(bc.HEADER, bc.OKBLUE))
        print("\tuse `{}lasterr(n){}` to see n lines of stderr.".format(
            bc.HEADER, bc.OKBLUE))
        print("\tuse `{}help(eth){}` for help on accessing the live chain.".
              format(bc.HEADER, bc.OKBLUE))
        print("\n" + bc.ENDC)

        # runmultiple hack in place?
        if hasattr(self.console_locals['eth'].app, 'apps'):
            print('\n' * 2 + bc.OKGREEN)
            print("Hint:" + bc.OKBLUE)
            print((
                '\tOther nodes are accessible from {}`eth.app.apps`{}').format(
                    bc.HEADER, bc.OKBLUE))
            print('\tThey where automatically assigned to:')
            print("\t`{}eth1{}`".format(bc.HEADER, bc.OKBLUE))
            if len(self.console_locals['eth'].app.apps) > 3:
                print("\t {}...{}".format(bc.HEADER, bc.OKBLUE))
            print("\t`{}eth{}{}`".format(
                bc.HEADER,
                len(self.console_locals['eth'].app.apps) - 1, bc.OKBLUE))
            print("\n" + bc.ENDC)

            # automatically assign different nodes to 'eth1.', 'eth2.'' , ....
            Eth = self.console_locals['Eth']
            for x in range(1, len(self.console_locals['eth'].app.apps)):
                self.console_locals['eth' + str(x)] = Eth(
                    self.console_locals['eth'].app.apps[x])

        # Remove handlers that log to stderr
        root = getLogger()
        for handler in root.handlers[:]:
            if isinstance(handler,
                          StreamHandler) and handler.stream == sys.stderr:
                root.removeHandler(handler)

        stream = io.StringIO()
        handler = StreamHandler(stream=stream)
        handler.formatter = Formatter("%(levelname)s:%(name)s %(message)s")
        root.addHandler(handler)

        def lastlog(n=10, prefix=None, level=None):
            """Print the last `n` log lines to stdout.
            Use `prefix='p2p'` to filter for a specific logger.
            Use `level=INFO` to filter for a specific level.

            Level- and prefix-filtering are applied before tailing the log.
            """
            lines = (stream.getvalue().strip().split('\n') or [])
            if prefix:
                lines = [
                    line for line in lines
                    if line.split(':')[1].startswith(prefix)
                ]
            if level:
                lines = [line for line in lines if line.split(':')[0] == level]
            for line in lines[-n:]:
                print(line)

        self.console_locals['lastlog'] = lastlog

        err = io.StringIO()
        sys.stderr = err

        def lasterr(n=1):
            """Print the last `n` entries of stderr to stdout.
            """
            for line in (err.getvalue().strip().split('\n') or [])[-n:]:
                print(line)

        self.console_locals['lasterr'] = lasterr

        IPython.start_ipython(argv=['--gui', 'gevent'],
                              user_ns=self.console_locals)
        self.interrupt.clear()

        sys.exit(0)
Esempio n. 36
0
class Session(object):
    """
    Base class for Session objects. Provides for different
    backends for queueing messages for sessions.

    Subclasses are expected to overload the add_message and
    get_messages to reflect their storage system.
    """

    # Session's timeout after 5 seconds
    expires = timedelta(seconds=5)

    def __init__(self, server, session_id=None):
        self.expires_at = datetime.now() + self.expires
        self.expired = False
        self.forever = False
        self.session_id = self.generate_uid()

        # Whether this was closed explictly by client vs
        # internally by garbage collection.
        self.interrupted = False

        # When a polling request is closed by a network error - not by
        # server, the session should be automatically closed. When there
        # is a network error - we're in an undefined state. Some messages
        # may have been lost, there is not much we can do about it.
        self.network_error = False

        # Async event, use rawlink to string callbacks
        self.timeout = Event()
        self.locked = Event()

    def generate_uid(self):
        """
        Returns a string of the unique identifier of the session.
        """
        return str(uuid.uuid4())

    def persist(self, extension=None, forever=False):
        """
        Bump the time to live of the session by a given amount,
        or forever.
        """
        self.expired = False

        if forever:
            self.forever = True
            return

        # Slide the expirtaion time one more expiration interval
        # into the future
        if extension is None:
            self.expires_at = datetime.now() + self.expires
        else:
            self.expires_at = datetime.now() + extension

        self.forever = False

    def post_delete(self):
        pass

    def kill(self):
        self.killed = True
        self.expire()

    def expire(self):
        """
        Manually expire a session.
        """
        self.expired = True
        self.forever = False

    def incr_hits(self):
        self.hits += 1

    def is_new(self):
        return self.hits == 0

    def heartbeat(self):
        self.persist()
        self.heartbeats += 1
        return self.heartbeats

    def add_message(self, msg):
        raise NotImplemented()

    def get_messages(self, **kwargs):
        raise NotImplemented()

    def is_locked(self):
        return self.locked.is_set()

    def is_network_error(self):
        return self.network_error

    def is_expired(self):
        return self.expired

    def is_interrupted(self):
        return self.interrupted

    def lock(self):
        self.locked.set()

    def unlock(self):
        self.locked.clear()

    def __str__(self):
        pass
Esempio n. 37
0
class JobsQueue(object):

    def __init__(self, middleware):
        self.middleware = middleware
        self.deque = JobsDeque()
        self.queue = []

        # Event responsible for the job queue schedule loop.
        # This event is set and a new job is potentially ready to run
        self.queue_event = Event()

        # Shared lock (JobSharedLock) dict
        self.job_locks = {}

    def all(self):
        return self.deque.all()

    def add(self, job):
        self.deque.add(job)
        self.queue.append(job)

        self.middleware.send_event('core.get_jobs', 'ADDED', id=job.id, fields=job.__encode__())

        # A job has been added to the queue, let the queue scheduler run
        self.queue_event.set()

    def get_lock(self, job):
        """
        Get a shared lock for a job
        """
        name = job.get_lock_name()
        if name is None:
            return None

        lock = self.job_locks.get(name)
        if lock is None:
            lock = JobSharedLock(self, name)
            self.job_locks[lock.name] = lock
        lock.add_job(job)
        return lock

    def release_lock(self, job):
        lock = job.get_lock()
        if not lock:
            return
        # Remove job from lock list and release it so another job can use it
        lock.remove_job(job)
        lock.release()

        if len(lock.get_jobs()) == 0:
            self.job_locks.pop(lock.name)

        # Once a lock is released there could be another job in the queue
        # waiting for the same lock
        self.queue_event.set()

    def next(self):
        """
        This is a blocking method.
        Returns when there is a new job ready to run.
        """
        while True:
            # Awaits a new event to look for a job
            self.queue_event.wait()
            found = None
            for job in self.queue:
                lock = self.get_lock(job)
                # Get job in the queue if it has no lock or its not locked
                if lock is None or not lock.locked():
                    found = job
                    if lock:
                        job.set_lock(lock)
                    break
            if found:
                # Unlocked job found to run
                self.queue.remove(found)
                # If there are no more jobs in the queue, clear the event
                if len(self.queue) == 0:
                    self.queue_event.clear()
                return found
            else:
                # No jobs available to run, clear the event
                self.queue_event.clear()

    def run(self):
        while True:
            job = self.next()
            gevent.spawn(job.run, self)
Esempio n. 38
0
class RaidenService(Runnable):
    """ A Raiden node. """
    def __init__(
        self,
        chain: BlockChainService,
        query_start_block: BlockNumber,
        default_registry: TokenNetworkRegistry,
        default_secret_registry: SecretRegistry,
        default_service_registry: Optional[ServiceRegistry],
        default_one_to_n_address: Optional[Address],
        transport,
        raiden_event_handler,
        message_handler,
        config,
        discovery=None,
        user_deposit=None,
    ):
        super().__init__()
        self.tokennetworkids_to_connectionmanagers: ConnectionManagerDict = dict(
        )
        self.targets_to_identifiers_to_statuses: StatusesDict = defaultdict(
            dict)

        self.chain: BlockChainService = chain
        self.default_registry = default_registry
        self.query_start_block = query_start_block
        self.default_one_to_n_address = default_one_to_n_address
        self.default_secret_registry = default_secret_registry
        self.default_service_registry = default_service_registry
        self.config = config

        self.signer: Signer = LocalSigner(self.chain.client.privkey)
        self.address = self.signer.address
        self.discovery = discovery
        self.transport = transport

        self.user_deposit = user_deposit

        self.blockchain_events = BlockchainEvents()
        self.alarm = AlarmTask(chain)
        self.raiden_event_handler = raiden_event_handler
        self.message_handler = message_handler

        self.stop_event = Event()
        self.stop_event.set()  # inits as stopped
        self.greenlets: List[Greenlet] = list()

        self.snapshot_group = 0

        self.contract_manager = ContractManager(config["contracts_path"])
        self.database_path = config["database_path"]
        self.wal = None
        if self.database_path != ":memory:":
            database_dir = os.path.dirname(config["database_path"])
            os.makedirs(database_dir, exist_ok=True)

            self.database_dir = database_dir

            # Two raiden processes must not write to the same database. Even
            # though it's possible the database itself would not be corrupt,
            # the node's state could. If a database was shared among multiple
            # nodes, the database WAL would be the union of multiple node's
            # WAL. During a restart a single node can't distinguish its state
            # changes from the others, and it would apply it all, meaning that
            # a node would execute the actions of itself and the others.
            #
            # Additionally the database snapshots would be corrupt, because it
            # would not represent the effects of applying all the state changes
            # in order.
            lock_file = os.path.join(self.database_dir, ".lock")
            self.db_lock = filelock.FileLock(lock_file)
        else:
            self.database_path = ":memory:"
            self.database_dir = None
            self.serialization_file = None
            self.db_lock = None

        self.event_poll_lock = gevent.lock.Semaphore()
        self.gas_reserve_lock = gevent.lock.Semaphore()
        self.payment_identifier_lock = gevent.lock.Semaphore()

        # Flag used to skip the processing of all Raiden events during the
        # startup.
        #
        # Rationale: At the startup, the latest snapshot is restored and all
        # state changes which are not 'part' of it are applied. The criteria to
        # re-apply the state changes is their 'absence' in the snapshot, /not/
        # their completeness. Because these state changes are re-executed
        # in-order and some of their side-effects will already have been
        # completed, the events should be delayed until the state is
        # synchronized (e.g. an open channel state change, which has already
        # been mined).
        #
        # Incomplete events, i.e. the ones which don't have their side-effects
        # applied, will be executed once the blockchain state is synchronized
        # because of the node's queues.
        self.ready_to_process_events = False

    def start(self):
        """ Start the node synchronously. Raises directly if anything went wrong on startup """
        assert self.stop_event.ready(), f"Node already started. node:{self!r}"
        self.stop_event.clear()
        self.greenlets = list()

        self.ready_to_process_events = False  # set to False because of restarts

        if self.database_dir is not None:
            self.db_lock.acquire(timeout=0)
            assert self.db_lock.is_locked, f"Database not locked. node:{self!r}"

        # start the registration early to speed up the start
        if self.config["transport_type"] == "udp":
            endpoint_registration_greenlet = gevent.spawn(
                self.discovery.register,
                self.address,
                self.config["transport"]["udp"]["external_ip"],
                self.config["transport"]["udp"]["external_port"],
            )

        self.maybe_upgrade_db()

        storage = sqlite.SerializedSQLiteStorage(
            database_path=self.database_path,
            serializer=serialize.JSONSerializer())
        storage.update_version()
        storage.log_run()
        self.wal = wal.restore_to_state_change(
            transition_function=node.state_transition,
            storage=storage,
            state_change_identifier="latest",
        )

        if self.wal.state_manager.current_state is None:
            log.debug("No recoverable state available, creating inital state.",
                      node=pex(self.address))
            # On first run Raiden needs to fetch all events for the payment
            # network, to reconstruct all token network graphs and find opened
            # channels
            last_log_block_number = self.query_start_block
            last_log_block_hash = self.chain.client.blockhash_from_blocknumber(
                last_log_block_number)

            state_change = ActionInitChain(
                pseudo_random_generator=random.Random(),
                block_number=last_log_block_number,
                block_hash=last_log_block_hash,
                our_address=self.chain.node_address,
                chain_id=self.chain.network_id,
            )
            self.handle_and_track_state_change(state_change)

            payment_network = PaymentNetworkState(
                self.default_registry.address,
                [],  # empty list of token network states as it's the node's startup
            )
            state_change = ContractReceiveNewPaymentNetwork(
                transaction_hash=constants.EMPTY_HASH,
                payment_network=payment_network,
                block_number=last_log_block_number,
                block_hash=last_log_block_hash,
            )
            self.handle_and_track_state_change(state_change)
        else:
            # The `Block` state change is dispatched only after all the events
            # for that given block have been processed, filters can be safely
            # installed starting from this position without losing events.
            last_log_block_number = views.block_number(
                self.wal.state_manager.current_state)
            log.debug(
                "Restored state from WAL",
                last_restored_block=last_log_block_number,
                node=pex(self.address),
            )

            known_networks = views.get_payment_network_identifiers(
                views.state_from_raiden(self))
            if known_networks and self.default_registry.address not in known_networks:
                configured_registry = pex(self.default_registry.address)
                known_registries = lpex(known_networks)
                raise RuntimeError(
                    f"Token network address mismatch.\n"
                    f"Raiden is configured to use the smart contract "
                    f"{configured_registry}, which conflicts with the current known "
                    f"smart contracts {known_registries}")

        # Restore the current snapshot group
        state_change_qty = self.wal.storage.count_state_changes()
        self.snapshot_group = state_change_qty // SNAPSHOT_STATE_CHANGES_COUNT

        # Install the filters using the latest confirmed from_block value,
        # otherwise blockchain logs can be lost.
        self.install_all_blockchain_filters(self.default_registry,
                                            self.default_secret_registry,
                                            last_log_block_number)

        # Complete the first_run of the alarm task and synchronize with the
        # blockchain since the last run.
        #
        # Notes about setup order:
        # - The filters must be polled after the node state has been primed,
        # otherwise the state changes won't have effect.
        # - The alarm must complete its first run before the transport is started,
        #   to reject messages for closed/settled channels.
        self.alarm.register_callback(self._callback_new_block)
        self.alarm.first_run(last_log_block_number)

        chain_state = views.state_from_raiden(self)

        self._initialize_payment_statuses(chain_state)
        self._initialize_transactions_queues(chain_state)
        self._initialize_messages_queues(chain_state)
        self._initialize_whitelists(chain_state)
        self._initialize_monitoring_services_queue(chain_state)
        self._initialize_ready_to_processed_events()

        if self.config["transport_type"] == "udp":
            endpoint_registration_greenlet.get(
            )  # re-raise if exception occurred

        # Start the side-effects:
        # - React to blockchain events
        # - React to incoming messages
        # - Send pending transactions
        # - Send pending message
        self.alarm.link_exception(self.on_error)
        self.transport.link_exception(self.on_error)
        self._start_transport(chain_state)
        self._start_alarm_task()

        log.debug("Raiden Service started", node=pex(self.address))
        super().start()

    def _run(self, *args, **kwargs):  # pylint: disable=method-hidden
        """ Busy-wait on long-lived subtasks/greenlets, re-raise if any error occurs """
        self.greenlet.name = f"RaidenService._run node:{pex(self.address)}"
        try:
            self.stop_event.wait()
        except gevent.GreenletExit:  # killed without exception
            self.stop_event.set()
            gevent.killall([self.alarm, self.transport])  # kill children
            raise  # re-raise to keep killed status
        except Exception:
            self.stop()
            raise

    def stop(self):
        """ Stop the node gracefully. Raise if any stop-time error occurred on any subtask """
        if self.stop_event.ready():  # not started
            return

        # Needs to come before any greenlets joining
        self.stop_event.set()

        # Filters must be uninstalled after the alarm task has stopped. Since
        # the events are polled by an alarm task callback, if the filters are
        # uninstalled before the alarm task is fully stopped the callback
        # `poll_blockchain_events` will fail.
        #
        # We need a timeout to prevent an endless loop from trying to
        # contact the disconnected client
        self.transport.stop()
        self.alarm.stop()

        self.transport.join()
        self.alarm.join()

        self.blockchain_events.uninstall_all_event_listeners()

        # Close storage DB to release internal DB lock
        self.wal.storage.conn.close()

        if self.db_lock is not None:
            self.db_lock.release()

        log.debug("Raiden Service stopped", node=pex(self.address))

    @property
    def confirmation_blocks(self):
        return self.config["blockchain"]["confirmation_blocks"]

    @property
    def privkey(self):
        return self.chain.client.privkey

    def add_pending_greenlet(self, greenlet: Greenlet):
        """ Ensures an error on the passed greenlet crashes self/main greenlet. """
        def remove(_):
            self.greenlets.remove(greenlet)

        self.greenlets.append(greenlet)
        greenlet.link_exception(self.on_error)
        greenlet.link_value(remove)

    def __repr__(self):
        return f"<{self.__class__.__name__} node:{pex(self.address)}>"

    def _start_transport(self, chain_state: ChainState):
        """ Initialize the transport and related facilities.

        Note:
            The transport must not be started before the node has caught up
            with the blockchain through `AlarmTask.first_run()`. This
            synchronization includes the on-chain channel state and is
            necessary to reject new messages for closed channels.
        """
        assert self.alarm.is_primed(), f"AlarmTask not primed. node:{self!r}"
        assert self.ready_to_process_events, f"Event procossing disable. node:{self!r}"

        self.transport.start(
            raiden_service=self,
            message_handler=self.message_handler,
            prev_auth_data=chain_state.last_transport_authdata,
        )

        for neighbour in views.all_neighbour_nodes(chain_state):
            if neighbour != ConnectionManager.BOOTSTRAP_ADDR:
                self.start_health_check_for(neighbour)

    def _start_alarm_task(self):
        """Start the alarm task.

        Note:
            The alarm task must be started only when processing events is
            allowed, otherwise side-effects of blockchain events will be
            ignored.
        """
        assert self.ready_to_process_events, f"Event procossing disable. node:{self!r}"
        self.alarm.start()

    def _initialize_ready_to_processed_events(self):
        assert not self.transport
        assert not self.alarm

        # This flag /must/ be set to true before the transport or the alarm task is started
        self.ready_to_process_events = True

    def get_block_number(self) -> BlockNumber:
        assert self.wal, f"WAL object not yet initialized. node:{self!r}"
        return views.block_number(self.wal.state_manager.current_state)

    def on_message(self, message: Message):
        self.message_handler.on_message(self, message)

    def handle_and_track_state_change(self, state_change: StateChange):
        """ Dispatch the state change and does not handle the exceptions.

        When the method is used the exceptions are tracked and re-raised in the
        raiden service thread.
        """
        for greenlet in self.handle_state_change(state_change):
            self.add_pending_greenlet(greenlet)

    def handle_state_change(self, state_change: StateChange) -> List[Greenlet]:
        """ Dispatch the state change and return the processing threads.

        Use this for error reporting, failures in the returned greenlets,
        should be re-raised using `gevent.joinall` with `raise_error=True`.
        """
        assert self.wal, f"WAL not restored. node:{self!r}"
        log.debug(
            "State change",
            node=pex(self.address),
            state_change=_redact_secret(
                serialize.JSONSerializer.serialize(state_change)),
        )

        old_state = views.state_from_raiden(self)
        new_state, raiden_event_list = self.wal.log_and_dispatch(state_change)

        for changed_balance_proof in views.detect_balance_proof_change(
                old_state, new_state):
            update_services_from_balance_proof(self, new_state,
                                               changed_balance_proof)

        log.debug(
            "Raiden events",
            node=pex(self.address),
            raiden_events=[
                _redact_secret(serialize.JSONSerializer.serialize(event))
                for event in raiden_event_list
            ],
        )

        greenlets: List[Greenlet] = list()
        if self.ready_to_process_events:
            for raiden_event in raiden_event_list:
                greenlets.append(
                    self.handle_event(chain_state=new_state,
                                      raiden_event=raiden_event))

            state_changes_count = self.wal.storage.count_state_changes()
            new_snapshot_group = state_changes_count // SNAPSHOT_STATE_CHANGES_COUNT
            if new_snapshot_group > self.snapshot_group:
                log.debug("Storing snapshot", snapshot_id=new_snapshot_group)
                self.wal.snapshot()
                self.snapshot_group = new_snapshot_group

        return greenlets

    def handle_event(self, chain_state: ChainState,
                     raiden_event: RaidenEvent) -> Greenlet:
        """Spawn a new thread to handle a Raiden event.

        This will spawn a new greenlet to handle each event, which is
        important for two reasons:

        - Blockchain transactions can be queued without interfering with each
          other.
        - The calling thread is free to do more work. This is specially
          important for the AlarmTask thread, which will eventually cause the
          node to send transactions when a given Block is reached (e.g.
          registering a secret or settling a channel).

        Important:

            This is spawing a new greenlet for /each/ transaction. It's
            therefore /required/ that there is *NO* order among these.
        """
        return gevent.spawn(self._handle_event, chain_state, raiden_event)

    def _handle_event(self, chain_state: ChainState,
                      raiden_event: RaidenEvent):
        assert isinstance(chain_state, ChainState)
        assert isinstance(raiden_event, RaidenEvent)

        try:
            self.raiden_event_handler.on_raiden_event(raiden=self,
                                                      chain_state=chain_state,
                                                      event=raiden_event)
        except RaidenRecoverableError as e:
            log.error(str(e))
        except InvalidDBData:
            raise
        except RaidenUnrecoverableError as e:
            log_unrecoverable = (
                self.config["environment_type"] == Environment.PRODUCTION
                and not self.config["unrecoverable_error_should_crash"])
            if log_unrecoverable:
                log.error(str(e))
            else:
                raise

    def set_node_network_state(self, node_address: Address,
                               network_state: str):
        state_change = ActionChangeNodeNetworkState(node_address,
                                                    network_state)
        self.handle_and_track_state_change(state_change)

    def start_health_check_for(self, node_address: Address):
        """Start health checking `node_address`.

        This function is a noop during initialization, because health checking
        can be started as a side effect of some events (e.g. new channel). For
        these cases the healthcheck will be started by
        `start_neighbours_healthcheck`.
        """
        if self.transport:
            self.transport.start_health_check(node_address)

    def _callback_new_block(self, latest_block: Dict):
        """Called once a new block is detected by the alarm task.

        Note:
            This should be called only once per block, otherwise there will be
            duplicated `Block` state changes in the log.

            Therefore this method should be called only once a new block is
            mined with the corresponding block data from the AlarmTask.
        """
        # User facing APIs, which have on-chain side-effects, force polled the
        # blockchain to update the node's state. This force poll is used to
        # provide a consistent view to the user, e.g. a channel open call waits
        # for the transaction to be mined and force polled the event to update
        # the node's state. This pattern introduced a race with the alarm task
        # and the task which served the user request, because the events are
        # returned only once per filter. The lock below is to protect against
        # these races (introduced by the commit
        # 3686b3275ff7c0b669a6d5e2b34109c3bdf1921d)
        with self.event_poll_lock:
            latest_block_number = latest_block["number"]

            # Handle testing with private chains. The block number can be
            # smaller than confirmation_blocks
            confirmed_block_number = max(
                GENESIS_BLOCK_NUMBER,
                latest_block_number -
                self.config["blockchain"]["confirmation_blocks"],
            )
            confirmed_block = self.chain.client.web3.eth.getBlock(
                confirmed_block_number)

            # These state changes will be procesed with a block_number which is
            # /larger/ than the ChainState's block_number.
            for event in self.blockchain_events.poll_blockchain_events(
                    confirmed_block_number):
                on_blockchain_event(self, event)

            # On restart the Raiden node will re-create the filters with the
            # ethereum node. These filters will have the from_block set to the
            # value of the latest Block state change. To avoid missing events
            # the Block state change is dispatched only after all of the events
            # have been processed.
            #
            # This means on some corner cases a few events may be applied
            # twice, this will happen if the node crashed and some events have
            # been processed but the Block state change has not been
            # dispatched.
            state_change = Block(
                block_number=confirmed_block_number,
                gas_limit=confirmed_block["gasLimit"],
                block_hash=BlockHash(bytes(confirmed_block["hash"])),
            )

            # Note: It's important to /not/ block here, because this function
            # can be called from the alarm task greenlet, which should not
            # starve.
            self.handle_and_track_state_change(state_change)

    def _initialize_transactions_queues(self, chain_state: ChainState):
        """Initialize the pending transaction queue from the previous run.

        Note:
            This will only send the transactions which don't have their
            side-effects applied. Transactions which another node may have sent
            already will be detected by the alarm task's first run and cleared
            from the queue (e.g. A monitoring service update transfer).
        """
        assert self.alarm.is_primed(), f"AlarmTask not primed. node:{self!r}"

        pending_transactions = views.get_pending_transactions(chain_state)

        log.debug(
            "Processing pending transactions",
            num_pending_transactions=len(pending_transactions),
            node=pex(self.address),
        )

        for transaction in pending_transactions:
            try:
                self.raiden_event_handler.on_raiden_event(
                    raiden=self, chain_state=chain_state, event=transaction)
            except RaidenRecoverableError as e:
                log.error(str(e))
            except InvalidDBData:
                raise
            except RaidenUnrecoverableError as e:
                log_unrecoverable = (
                    self.config["environment_type"] == Environment.PRODUCTION
                    and not self.config["unrecoverable_error_should_crash"])
                if log_unrecoverable:
                    log.error(str(e))
                else:
                    raise

    def _initialize_payment_statuses(self, chain_state: ChainState):
        """ Re-initialize targets_to_identifiers_to_statuses.

        Restore the PaymentStatus for any pending payment. This is not tied to
        a specific protocol message but to the lifecycle of a payment, i.e.
        the status is re-created if a payment itself has not completed.
        """

        with self.payment_identifier_lock:
            for task in chain_state.payment_mapping.secrethashes_to_task.values(
            ):
                if not isinstance(task, InitiatorTask):
                    continue

                # Every transfer in the transfers_list must have the same target
                # and payment_identifier, so using the first transfer is
                # sufficient.
                initiator = next(
                    iter(task.manager_state.initiator_transfers.values()))
                transfer = initiator.transfer
                transfer_description = initiator.transfer_description
                target = transfer.target
                identifier = transfer.payment_identifier
                balance_proof = transfer.balance_proof
                self.targets_to_identifiers_to_statuses[target][
                    identifier] = PaymentStatus(
                        payment_identifier=identifier,
                        amount=transfer_description.amount,
                        token_network_identifier=TokenNetworkID(
                            balance_proof.token_network_identifier),
                        payment_done=AsyncResult(),
                    )

    def _initialize_messages_queues(self, chain_state: ChainState):
        """Initialize all the message queues with the transport.

        Note:
            All messages from the state queues must be pushed to the transport
            before it's started. This is necessary to avoid a race where the
            transport processes network messages too quickly, queueing new
            messages before any of the previous messages, resulting in new
            messages being out-of-order.

            The Alarm task must be started before this method is called,
            otherwise queues for channel closed while the node was offline
            won't be properly cleared. It is not bad but it is suboptimal.
        """
        assert not self.transport, f"Transport is running. node:{self!r}"
        assert self.alarm.is_primed(), f"AlarmTask not primed. node:{self!r}"

        events_queues = views.get_all_messagequeues(chain_state)

        for queue_identifier, event_queue in events_queues.items():
            self.start_health_check_for(queue_identifier.recipient)

            for event in event_queue:
                message = message_from_sendevent(event)
                self.sign(message)
                self.transport.send_async(queue_identifier, message)

    def _initialize_monitoring_services_queue(self, chain_state: ChainState):
        """Send the monitoring requests for all current balance proofs.

        Note:
            The node must always send the *received* balance proof to the
            monitoring service, *before* sending its own locked transfer
            forward. If the monitoring service is updated after, then the
            following can happen:

            For a transfer A-B-C where this node is B

            - B receives T1 from A and processes it
            - B forwards its T2 to C
            * B crashes (the monitoring service is not updated)

            For the above scenario, the monitoring service would not have the
            latest balance proof received by B from A available with the lock
            for T1, but C would. If the channel B-C is closed and B does not
            come back online in time, the funds for the lock L1 can be lost.

            During restarts the rationale from above has to be replicated.
            Because the initialization code *is not* the same as the event
            handler. This means the balance proof updates must be done prior to
            the processing of the message queues.
        """
        msg = (
            "Transport was started before the monitoring service queue was updated. "
            "This can lead to safety issue. node:{self!r}")
        assert not self.transport, msg

        msg = "The node state was not yet recovered, cant read balance proofs. node:{self!r}"
        assert self.wal, msg

        current_balance_proofs = views.detect_balance_proof_change(
            old_state=ChainState(
                pseudo_random_generator=chain_state.pseudo_random_generator,
                block_number=GENESIS_BLOCK_NUMBER,
                block_hash=constants.EMPTY_HASH,
                our_address=chain_state.our_address,
                chain_id=chain_state.chain_id,
            ),
            current_state=chain_state,
        )
        for balance_proof in current_balance_proofs:
            update_services_from_balance_proof(self, chain_state,
                                               balance_proof)

    def _initialize_whitelists(self, chain_state: ChainState):
        """ Whitelist neighbors and mediated transfer targets on transport """

        for neighbour in views.all_neighbour_nodes(chain_state):
            if neighbour == ConnectionManager.BOOTSTRAP_ADDR:
                continue
            self.transport.whitelist(neighbour)

        events_queues = views.get_all_messagequeues(chain_state)

        for event_queue in events_queues.values():
            for event in event_queue:
                if isinstance(event, SendLockedTransfer):
                    transfer = event.transfer
                    if transfer.initiator == self.address:
                        self.transport.whitelist(address=transfer.target)

    def sign(self, message: Message):
        """ Sign message inplace. """
        if not isinstance(message, SignedMessage):
            raise ValueError("{} is not signable.".format(repr(message)))

        message.sign(self.signer)

    def install_all_blockchain_filters(
        self,
        token_network_registry_proxy: TokenNetworkRegistry,
        secret_registry_proxy: SecretRegistry,
        from_block: BlockNumber,
    ):
        with self.event_poll_lock:
            node_state = views.state_from_raiden(self)
            token_networks = views.get_token_network_identifiers(
                node_state, token_network_registry_proxy.address)

            self.blockchain_events.add_token_network_registry_listener(
                token_network_registry_proxy=token_network_registry_proxy,
                contract_manager=self.contract_manager,
                from_block=from_block,
            )
            self.blockchain_events.add_secret_registry_listener(
                secret_registry_proxy=secret_registry_proxy,
                contract_manager=self.contract_manager,
                from_block=from_block,
            )

            for token_network in token_networks:
                token_network_proxy = self.chain.token_network(
                    TokenNetworkAddress(token_network))
                self.blockchain_events.add_token_network_listener(
                    token_network_proxy=token_network_proxy,
                    contract_manager=self.contract_manager,
                    from_block=from_block,
                )

    def connection_manager_for_token_network(
            self,
            token_network_identifier: TokenNetworkID) -> ConnectionManager:
        if not is_binary_address(token_network_identifier):
            raise InvalidAddress("token address is not valid.")

        known_token_networks = views.get_token_network_identifiers(
            views.state_from_raiden(self), self.default_registry.address)

        if token_network_identifier not in known_token_networks:
            raise InvalidAddress("token is not registered.")

        manager = self.tokennetworkids_to_connectionmanagers.get(
            token_network_identifier)

        if manager is None:
            manager = ConnectionManager(self, token_network_identifier)
            self.tokennetworkids_to_connectionmanagers[
                token_network_identifier] = manager

        return manager

    def mediated_transfer_async(
        self,
        token_network_identifier: TokenNetworkID,
        amount: PaymentAmount,
        target: TargetAddress,
        identifier: PaymentID,
        fee: FeeAmount = MEDIATION_FEE,
        secret: Secret = None,
        secrethash: SecretHash = None,
    ) -> PaymentStatus:
        """ Transfer `amount` between this node and `target`.

        This method will start an asynchronous transfer, the transfer might fail
        or succeed depending on a couple of factors:

            - Existence of a path that can be used, through the usage of direct
              or intermediary channels.
            - Network speed, making the transfer sufficiently fast so it doesn't
              expire.
        """
        if secret is None:
            if secrethash is None:
                secret = random_secret()
            else:
                secret = EMPTY_SECRET

        payment_status = self.start_mediated_transfer_with_secret(
            token_network_identifier=token_network_identifier,
            amount=amount,
            fee=fee,
            target=target,
            identifier=identifier,
            secret=secret,
            secrethash=secrethash,
        )

        return payment_status

    def start_mediated_transfer_with_secret(
        self,
        token_network_identifier: TokenNetworkID,
        amount: PaymentAmount,
        fee: FeeAmount,
        target: TargetAddress,
        identifier: PaymentID,
        secret: Secret,
        secrethash: SecretHash = None,
    ) -> PaymentStatus:

        if secrethash is None:
            secrethash = sha3(secret)
        elif secrethash != sha3(secret):
            raise InvalidSecretHash(
                "provided secret and secret_hash do not match.")

        if len(secret) != SECRET_LENGTH:
            raise InvalidSecret("secret of invalid length.")

        # We must check if the secret was registered against the latest block,
        # even if the block is forked away and the transaction that registers
        # the secret is removed from the blockchain. The rationale here is that
        # someone else does know the secret, regardless of the chain state, so
        # the node must not use it to start a payment.
        #
        # For this particular case, it's preferable to use `latest` instead of
        # having a specific block_hash, because it's preferable to know if the secret
        # was ever known, rather than having a consistent view of the blockchain.
        secret_registered = self.default_secret_registry.is_secret_registered(
            secrethash=secrethash, block_identifier="latest")
        if secret_registered:
            raise RaidenUnrecoverableError(
                f"Attempted to initiate a locked transfer with secrethash {pex(secrethash)}."
                f" That secret is already registered onchain.")

        self.start_health_check_for(Address(target))

        if identifier is None:
            identifier = create_default_identifier()

        with self.payment_identifier_lock:
            payment_status = self.targets_to_identifiers_to_statuses[
                target].get(identifier)
            if payment_status:
                payment_status_matches = payment_status.matches(
                    token_network_identifier, amount)
                if not payment_status_matches:
                    raise PaymentConflict(
                        "Another payment with the same id is in flight")

                return payment_status

            payment_status = PaymentStatus(
                payment_identifier=identifier,
                amount=amount,
                token_network_identifier=token_network_identifier,
                payment_done=AsyncResult(),
            )
            self.targets_to_identifiers_to_statuses[target][
                identifier] = payment_status

        init_initiator_statechange = initiator_init(
            raiden=self,
            transfer_identifier=identifier,
            transfer_amount=amount,
            transfer_secret=secret,
            transfer_secrethash=secrethash,
            transfer_fee=fee,
            token_network_identifier=token_network_identifier,
            target_address=target,
        )

        # Dispatch the state change even if there are no routes to create the
        # wal entry.
        self.handle_and_track_state_change(init_initiator_statechange)

        return payment_status

    def mediate_mediated_transfer(self, transfer: LockedTransfer):
        init_mediator_statechange = mediator_init(self, transfer)
        self.handle_and_track_state_change(init_mediator_statechange)

    def target_mediated_transfer(self, transfer: LockedTransfer):
        self.start_health_check_for(Address(transfer.initiator))
        init_target_statechange = target_init(transfer)
        self.handle_and_track_state_change(init_target_statechange)

    def maybe_upgrade_db(self) -> None:
        manager = UpgradeManager(db_filename=self.database_path,
                                 raiden=self,
                                 web3=self.chain.client.web3)
        manager.run()
Esempio n. 39
0
class JoinableQueue(Queue):
    """
    A subclass of :class:`Queue` that additionally has
    :meth:`task_done` and :meth:`join` methods.
    """
    def __init__(self, maxsize=None, items=None, unfinished_tasks=None):
        """

        .. versionchanged:: 1.1a1
           If *unfinished_tasks* is not given, then all the given *items*
           (if any) will be considered unfinished.

        """
        from gevent.event import Event
        Queue.__init__(self, maxsize, items)
        self._cond = Event()
        self._cond.set()

        if unfinished_tasks:
            self.unfinished_tasks = unfinished_tasks
        elif items:
            self.unfinished_tasks = len(items)
        else:
            self.unfinished_tasks = 0

        if self.unfinished_tasks:
            self._cond.clear()

    def copy(self):
        return type(self)(self.maxsize, self.queue, self.unfinished_tasks)

    def _format(self):
        result = Queue._format(self)
        if self.unfinished_tasks:
            result += ' tasks=%s _cond=%s' % (self.unfinished_tasks,
                                              self._cond)
        return result

    def _put(self, item):
        Queue._put(self, item)
        self.unfinished_tasks += 1
        self._cond.clear()

    def task_done(self):
        '''Indicate that a formerly enqueued task is complete. Used by queue consumer threads.
        For each :meth:`get <Queue.get>` used to fetch a task, a subsequent call to :meth:`task_done` tells the queue
        that the processing on the task is complete.

        If a :meth:`join` is currently blocking, it will resume when all items have been processed
        (meaning that a :meth:`task_done` call was received for every item that had been
        :meth:`put <Queue.put>` into the queue).

        Raises a :exc:`ValueError` if called more times than there were items placed in the queue.
        '''
        if self.unfinished_tasks <= 0:
            raise ValueError('task_done() called too many times')
        self.unfinished_tasks -= 1
        if self.unfinished_tasks == 0:
            self._cond.set()

    def join(self, timeout=None):
        '''
        Block until all items in the queue have been gotten and processed.

        The count of unfinished tasks goes up whenever an item is added to the queue.
        The count goes down whenever a consumer thread calls :meth:`task_done` to indicate
        that the item was retrieved and all work on it is complete. When the count of
        unfinished tasks drops to zero, :meth:`join` unblocks.

        :param float timeout: If not ``None``, then wait no more than this time in seconds
            for all tasks to finish.
        :return: ``True`` if all tasks have finished; if ``timeout`` was given and expired before
            all tasks finished, ``False``.

        .. versionchanged:: 1.1a1
           Add the *timeout* parameter.
        '''
        return self._cond.wait(timeout=timeout)
class HighAvailabilityAgent(SimpleResourceAgent):
    """Agent to manage high availability processes

    """
    def __init__(self):
        SimpleResourceAgent.__init__(self)
        self.dashi_handler = None
        self.service_id = None
        self.policy_thread = None
        self.policy_event = None
        self._policy_loop_event = Event()

    def on_init(self):
        if not HighAvailabilityCore:
            msg = "HighAvailabilityCore isn't available. Use autolaunch.cfg buildout"
            log.error(msg)
            return

        cfg = self.CFG.get_safe("highavailability")

        # use default PD name as the sole PD if none are provided in config
        self.pds = self.CFG.get_safe("highavailability.process_dispatchers",
                                     [ProcessDispatcherService.name])
        if not len(self.pds) == 1:
            raise Exception(
                "HA Service doesn't support multiple Process Dispatchers")

        self.process_definition_id, self.process_definition = self._get_process_definition(
        )

        self.process_configuration = self.CFG.get_safe(
            "highavailability.process_configuration")
        aggregator_config = _get_aggregator_config(self.CFG)

        self.service_id, self.service_name = self._register_service()
        self.policy_event = Event()

        stored_policy = self._stored_policy
        if stored_policy != {}:
            policy_name = stored_policy.get('name')
            policy_parameters = stored_policy.get('parameters')
            self._validate_policy_name(policy_name)
            self.policy_name = policy_name.lower()
            self.policy_parameters = policy_parameters
        else:

            policy_name = self.CFG.get_safe("highavailability.policy.name")
            self._validate_policy_name(policy_name)
            self.policy_name = policy_name.lower()
            self.policy_parameters = self.CFG.get_safe(
                "highavailability.policy.parameters")

        self.policy_interval = self.CFG.get_safe(
            "highavailability.policy.interval", DEFAULT_INTERVAL)

        self.logprefix = "HA Agent (%s): " % self.service_name

        self.control = HAProcessControl(self.pds[0],
                                        self.container.resource_registry,
                                        self.service_id,
                                        self.policy_event.set,
                                        logprefix=self.logprefix)

        self.core = HighAvailabilityCore(
            cfg,
            self.control,
            self.pds,
            self.policy_name,
            process_definition_id=self.process_definition_id,
            parameters=self.policy_parameters,
            process_configuration=self.process_configuration,
            aggregator_config=aggregator_config,
            name=self.service_name)

        dashi_messaging = self.CFG.get_safe("highavailability.dashi_messaging",
                                            False)
        if dashi_messaging:

            dashi_name = self.CFG.get_safe("highavailability.dashi_name")
            if not dashi_name:
                raise Exception("dashi_name unknown")
            dashi_uri = self.CFG.get_safe("highavailability.dashi_uri")
            if not dashi_uri:
                rabbit_host = self.CFG.get_safe("server.amqp.host")
                rabbit_user = self.CFG.get_safe("server.amqp.username")
                rabbit_pass = self.CFG.get_safe("server.amqp.password")

                if not (rabbit_host and rabbit_user and rabbit_pass):
                    raise Exception("cannot form dashi URI")

                dashi_uri = "amqp://%s:%s@%s/" % (rabbit_user, rabbit_pass,
                                                  rabbit_host)
            dashi_exchange = self.CFG.get_safe(
                "highavailability.dashi_exchange")
            if not dashi_exchange:
                dashi_exchange = get_sys_name()

            self.dashi_handler = HADashiHandler(self, dashi_name, dashi_uri,
                                                dashi_exchange)

        else:
            self.dashi_handler = None

    def _get_process_definition(self):
        process_definition_id = self.CFG.get_safe(
            "highavailability.process_definition_id")
        process_definition_name = self.CFG.get_safe(
            "highavailability.process_definition_name")

        if process_definition_id:
            pd_name = self.pds[0]
            pd = ProcessDispatcherServiceClient(to_name=pd_name)
            definition = pd.read_process_definition(process_definition_id)

        elif process_definition_name:
            definitions, _ = self.container.resource_registry.find_resources(
                restype="ProcessDefinition", name=process_definition_name)
            if len(definitions) == 0:
                raise Exception("Process definition with name '%s' not found" %
                                process_definition_name)
            elif len(definitions) > 1:
                raise Exception(
                    "multiple process definitions found with name '%s'" %
                    process_definition_name)
            definition = definitions[0]
            process_definition_id = definition._id

        else:
            raise Exception(
                "HA Agent requires either process definition ID or name")

        return process_definition_id, definition

    def on_start(self):
        if self.dashi_handler:
            self.dashi_handler.start()

        self.control.start()

        # override the core's list of currently managed processes. This is to support
        # restart of an HAAgent.
        self.core.set_managed_upids(self.control.get_managed_upids())

        self.policy_thread = gevent.spawn(self._policy_thread_loop)

        # kickstart the policy once. future invocations will happen via event callbacks.
        self.policy_event.set()

    def on_quit(self):
        self.control.stop()
        self._policy_loop_event.set()
        self.policy_thread.join()
        self.policy_thread.kill(block=True, timeout=3)
        if self.dashi_handler:
            self.dashi_handler.stop()

        # DL: do we ever want to remove this object?
        #self._unregister_service()

    def _register_service(self):

        definition = self.process_definition
        existing_services, _ = self.container.resource_registry.find_resources(
            restype="Service", name=definition.name)

        if len(existing_services) > 0:
            if len(existing_services) > 1:
                log.warning(
                    "There is more than one service object for %s. Using the first one"
                    % definition.name)
            service_id = existing_services[0]._id
        else:
            svc_obj = Service(name=definition.name,
                              exchange_name=definition.name)
            service_id, _ = self.container.resource_registry.create(svc_obj)

        svcdefs, _ = self.container.resource_registry.find_resources(
            restype="ServiceDefinition", name=definition.name)

        if svcdefs:
            try:
                self.container.resource_registry.create_association(
                    service_id, "hasServiceDefinition", svcdefs[0]._id)
            except BadRequest:
                log.warn(
                    "Failed to associate %s Service and ServiceDefinition. It probably exists.",
                    definition.name)
        else:
            log.error("Cannot find ServiceDefinition resource for %s",
                      definition.name)

        return service_id, definition.name

    def _unregister_service(self):
        if not self.service_id:
            log.error("No service id. Cannot unregister service")
            return

        self.container.resource_registry.delete(self.service_id,
                                                del_associations=True)

    def _policy_thread_loop(self):
        """Single thread runs policy loops, to prevent races
        """
        while not self._policy_loop_event.wait(timeout=0.1):
            # wait until our event is set, up to policy_interval seconds
            self.policy_event.wait(self.policy_interval)
            if self.policy_event.is_set():
                self.policy_event.clear()
                log.debug("%sapplying policy due to event", self.logprefix)
            else:

                # on a regular basis, we check for the current state of each process.
                # this is essentially a hedge against bugs in the HAAgent, or in the
                # ION events system that could prevent us from seeing state changes
                # of processes.
                log.debug(
                    "%sapplying policy due to timer. Reloading process cache first.",
                    self.logprefix)
                try:
                    self.control.reload_processes()
                except (Exception, gevent.Timeout):
                    log.warn(
                        "%sFailed to reload processes from PD. Will retry later.",
                        self.logprefix,
                        exc_info=True)

            try:
                self._apply_policy()
            except (Exception, gevent.Timeout):
                log.warn("%sFailed to apply policy. Will retry later.",
                         self.logprefix,
                         exc_info=True)

    def _validate_policy_name(self, policy_name):
        if policy_name is None:
            msg = "HA service requires a policy name at CFG.highavailability.policy.name"
            raise Exception(msg)
        try:
            policy.policy_map[policy_name.lower()]
        except KeyError:
            raise Exception("HA Service doesn't support '%s' policy" %
                            policy_name)

    @property
    def _policy_dict(self):
        policy_dict = {
            'name': self.core.policy_type,
            'parameters': self.core.policy.parameters
        }
        return policy_dict

    @property
    def _stored_policy(self):
        service = self.container.resource_registry.read(self.service_id)
        return service.policy

    def _apply_policy(self):

        self.core.apply_policy()

        try:
            new_service_state = _core_hastate_to_service_state(
                self.core.status())
            new_policy = self._policy_dict
            service = self.container.resource_registry.read(self.service_id)

            update_service = False
            if service.state != new_service_state:
                service.state = new_service_state
                update_service = True

            if service.policy != new_policy:
                service.policy = new_policy
                update_service = True

            if update_service is True:
                self.container.resource_registry.update(service)
        except Exception:
            log.warn("%sProblem when updating Service state",
                     self.logprefix,
                     exc_info=True)

    def rcmd_reconfigure_policy(self, new_policy_params, new_policy_name=None):
        """Service operation: Change the parameters of the policy used for service

        @param new_policy_params: parameters of policy
        @param new_policy_name: name of policy
        @return:
        """
        self.core.reconfigure_policy(new_policy_params, new_policy_name)
        #trigger policy thread to wake up
        self.policy_event.set()

    def rcmd_status(self):
        """Service operation: Get the status of the HA Service

        @return: {PENDING, READY, STEADY, BROKEN}
        """
        return self.core.status()

    def rcmd_dump(self):
        dump = self.core.dump()
        dump['service_id'] = self.service_id
        return dump
Esempio n. 41
0
class TangoChannel(ChannelObject):
    _tangoEventsQueue = Queue()
    _eventReceivers = {}
    _tangoEventsProcessingTimer = gevent.get_hub().loop.async_()

    # start Tango events processing timer
    _tangoEventsProcessingTimer.start(processTangoEvents)

    def __init__(self,
                 name,
                 attribute_name,
                 tangoname=None,
                 username=None,
                 polling=None,
                 timeout=10000,
                 **kwargs):
        ChannelObject.__init__(self, name, username, **kwargs)

        self.attributeName = attribute_name
        self.deviceName = tangoname
        self.device = None
        self.value = Poller.NotInitializedValue
        self.polling = polling
        self.pollingTimer = None
        self.pollingEvents = False
        self.timeout = int(timeout)
        self.read_as_str = kwargs.get("read_as_str", False)
        self._device_initialized = Event()
        logging.getLogger("HWR").debug(
            "creating Tango attribute %s/%s, polling=%s, timeout=%d",
            self.deviceName,
            self.attributeName,
            polling,
            self.timeout,
        )
        self.init_device()
        self.continue_init(None)
        """
        self.init_poller = Poller.poll(self.init_device,
                                       polling_period = 3000,
                                       value_changed_callback = self.continue_init,
                                       error_callback = self.init_poll_failed,
                                       start_delay=100)
        """

    def init_poll_failed(self, e, poller_id):
        self._device_initialized.clear()
        logging.warning(
            "%s/%s (%s): could not complete init. (hint: device server is not running, or has to be restarted)",
            self.deviceName,
            self.attributeName,
            self.name(),
        )
        self.init_poller = self.init_poller.restart(3000)

    def continue_init(self, _):
        # self.init_poller.stop()

        if isinstance(self.polling, int):
            self.raw_device = RawDeviceProxy(self.deviceName)
            Poller.poll(
                self.poll,
                polling_period=self.polling,
                value_changed_callback=self.update,
                error_callback=self.pollFailed,
            )
        else:
            if self.polling == "events":
                # try to register event
                try:
                    self.pollingEvents = True
                    # logging.getLogger("HWR").debug("subscribing to CHANGE event for %s", self.attributeName)
                    self.device.subscribe_event(
                        self.attributeName,
                        PyTango.EventType.CHANGE_EVENT,
                        self,
                        [],
                        True,
                    )
                    # except PyTango.EventSystemFailed:
                    #   pass
                except Exception:
                    logging.getLogger("HWR").exception(
                        "could not subscribe event")
        self._device_initialized.set()

    def init_device(self):
        try:
            self.device = DeviceProxy(self.deviceName)
        except PyTango.DevFailed as traceback:
            self.imported = False
            last_error = traceback[-1]
            logging.getLogger("HWR").error("%s: %s", str(self.name()),
                                           last_error["desc"])
        else:
            self.imported = True
            try:
                self.device.ping()
            except PyTango.ConnectionFailed:
                self.device = None
                raise ConnectionError
            else:
                self.device.set_timeout_millis(self.timeout)

                # check that the attribute exists (to avoid Abort in PyTango grrr)
                if not self.attributeName.lower() in [
                        attr.name.lower()
                        for attr in self.device.attribute_list_query()
                ]:
                    logging.getLogger("HWR").error(
                        "no attribute %s in Tango device %s",
                        self.attributeName,
                        self.deviceName,
                    )
                    self.device = None

    def push_event(self, event):
        # logging.getLogger("HWR").debug("%s | attr_value=%s, event.errors=%s, quality=%s", self.name(), event.attr_value, event.errors,event.attr_value is None and "N/A" or event.attr_value.quality)
        if (event.attr_value is None or event.err
                or event.attr_value.quality != PyTango.AttrQuality.ATTR_VALID):
            # logging.getLogger("HWR").debug("%s, receving BAD event... attr_value=%s, event.errors=%s, quality=%s", self.name(), event.attr_value, event.errors, event.attr_value is None and "N/A" or event.attr_value.quality)
            return
        else:
            pass
            # logging.getLogger("HWR").debug("%s, receiving good event", self.name())
        ev = E(event)
        TangoChannel._eventReceivers[id(ev)] = saferef.safe_ref(self.update)
        TangoChannel._tangoEventsQueue.put(ev)
        TangoChannel._tangoEventsProcessingTimer.send()

    def poll(self):
        if self.read_as_str:
            value = self.raw_device.read_attribute(
                self.attributeName,
                PyTango.DeviceAttribute.ExtractAs.String).value
            # value = self.device.read_attribute_as_str(self.attributeName).value
        else:
            value = self.raw_device.read_attribute(self.attributeName).value
        self.emit("update", value)
        return value

    def pollFailed(self, e, poller_id):
        self.emit("update", None)
        """
        emit_update = True
        if self.value is None:
          emit_update = False
        else:
          self.value = None

        try:
            self.init_device()
        except:
            pass

        poller = Poller.get_poller(poller_id)
        if poller is not None:
            poller.restart(1000)

        try:
          raise e
        except:
          logging.exception("%s: Exception happened while polling %s", self.name(), self.attributeName)

        if emit_update:
          # emit at the end => can raise exceptions in callbacks
          self.emit('update', None)
        """

    def getInfo(self):
        self._device_initialized.wait(timeout=3)
        return self.device.get_attribute_config(self.attributeName)

    def update(self, value=Poller.NotInitializedValue):
        if value == Poller.NotInitializedValue:
            value = self.getValue()
        if isinstance(value, types.TupleType):
            value = list(value)

        self.value = value
        self.emit("update", value)

    def getValue(self):
        self._device_initialized.wait(timeout=3)

        if self.read_as_str:
            value = self.device.read_attribute(
                self.attributeName,
                PyTango.DeviceAttribute.ExtractAs.String).value
        else:
            value = self.device.read_attribute(self.attributeName).value

        return value

    def setValue(self, newValue):
        self.device.write_attribute(self.attributeName, newValue)
        # attr = PyTango.AttributeProxy(self.deviceName + "/" + self.attributeName)
        # a = attr.read()
        # a.value = newValue
        # attr.write(a)

    def isConnected(self):
        return self.device is not None
Esempio n. 42
0
class HTTPServer(object):

    spawn = Greenlet.spawn # set to None to avoid spawning at all
    backlog = 5

    def __init__(self, handle=None, spawn='default'):
        self.listeners = []
        self._stopped_event = Event()
        self._no_connections_event = Event()
        self._requests = {} # maps connection -> list of requests
        self.http = core.http()
        self.http.set_gencb(self._cb_request)
        if handle is not None:
            self.handle = handle
        if spawn != 'default':
            self.spawn = spawn

    def start(self, socket_or_address, backlog=None):
        """Start accepting connections"""
        fileno = getattr(socket_or_address, 'fileno', None)
        if fileno is not None:
            fd = fileno()
            sock = socket_or_address
        else:
            sock = self.make_listener(socket_or_address, backlog=backlog)
            fd = sock.fileno()
        self.http.accept(fd)
        self.listeners.append(sock)
        self._stopped_event.clear()
        if self._requests:
            self._no_connections_event.clear()
        else:
            self._no_connections_event.set()
        return sock

    def make_listener(self, address, backlog=None):
        if backlog is None:
            backlog = self.backlog
        sock = socket.socket()
        try:
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, sock.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR) | 1)
        except socket.error:
            pass
        sock.bind(address)
        sock.listen(backlog)
        sock.setblocking(False)
        return sock

    def stop(self, timeout=0):
        """Shutdown the server."""
        for sock in self.listeners:
            sock.close()
        self.socket = []
        #2. Set "keep-alive" connections to "close"
        # TODO
        #3a. set low timeout (min(1s, timeout or 1)) on events belonging to connection (to kill long-polling connections
        # TODO
        #3. Wait until every connection is closed or timeout expires
        if self._requests:
            timer = Timeout.start_new(timeout)
            try:
                try:
                    self._no_connections_event.wait(timeout=timeout)
                except Timeout, ex:
                    if timer is not ex:
                        raise
            finally:
                timer.cancel()
        #4. forcefull close all the connections
        # TODO
        #5. free http instance
        self.http = None
        #6. notify event created in serve_forever()
        self._stopped_event.set()

    def handle(self, request):
        request.send_reply(200, 'OK', 'It works!')

    def _cb_connection_close(self, connection):
        # make sure requests belonging to this connection cannot be accessed anymore
        # because they've been freed by libevent
        requests = self._requests.pop(connection._obj, [])
        for request in requests:
            request.detach()
        if not self._requests:
            self._no_connections_event.set()

    def _cb_request_processed(self, greenlet):
        request = greenlet._request
        greenlet._request = None
        if request:
            if not greenlet.successful():
                self.reply_error(request)
            requests = self._requests.get(request.connection._obj)
            if requests is not None:
                requests.remove(request)

    def _cb_request(self, request):
        try:
            spawn = self.spawn
            request.connection.set_closecb(self)
            self._requests.setdefault(request.connection._obj, []).append(request)
            if spawn is None:
                self.handle(request)
            else:
                greenlet = spawn(wrap_errors(core.HttpRequestDeleted, self.handle), request)
                rawlink = getattr(greenlet, 'rawlink', None)
                if rawlink is not None:
                    greenlet._request = request
                    rawlink(self._cb_request_processed)
        except:
            traceback.print_exc()
            try:
                sys.stderr.write('Failed to handle request: %s\n\n' % (request, ))
            except:
                pass
            self.reply_error(request)

    def reply_error(self, request):
        try:
            if request.response == (0, None):
                request.send_reply(500, 'Internal Server Error', '<h1>Internal Server Error</h1>')
        except core.HttpRequestDeleted:
            pass

    def serve_forever(self, *args, **kwargs):
        stop_timeout = kwargs.pop('stop_timeout', 0)
        self.start(*args, **kwargs)
        try:
            self._stopped_event.wait()
        finally:
            Greenlet.spawn(self.stop, timeout=stop_timeout).join()
Esempio n. 43
0
class Socket(object):
    """
    Virtual Socket implementation, checks heartbeats, writes to local queues
    for message passing, holds the Namespace objects, dispatches de packets
    to the underlying namespaces.

    This is the abstraction on top of the different transports. It's like
    if you used a WebSocket only...
    """

    STATE_CONNECTING = "CONNECTING"
    STATE_CONNECTED = "CONNECTED"
    STATE_DISCONNECTING = "DISCONNECTING"
    STATE_DISCONNECTED = "DISCONNECTED"

    GLOBAL_NS = ''
    """Use this to be explicit when specifying a Global Namespace (an endpoint
    with no name, not '/chat' or anything."""

    json_loads = staticmethod(default_json_loads)
    json_dumps = staticmethod(default_json_dumps)

    def __init__(self, server, config, error_handler=None):
        self.server = weakref.proxy(server)
        self.sessid = str(random.random())[2:]
        self.session = {}  # the session dict, for general developer usage
        self.client_queue = Queue()  # queue for messages to client
        self.server_queue = Queue()  # queue for messages to server
        self.hits = 0
        self.heartbeats = 0
        self.timeout = Event()
        self.wsgi_app_greenlet = None
        self.state = "NEW"
        self.connection_established = False
        self.ack_callbacks = {}
        self.ack_counter = 0
        self.request = None
        self.environ = None
        self.namespaces = {}
        self.active_ns = {}  # Namespace sessions that were instantiated
        self.jobs = []
        self.error_handler = default_error_handler
        self.config = config
        if error_handler is not None:
            self.error_handler = error_handler

    def _set_namespaces(self, namespaces):
        """This is a mapping (dict) of the different '/namespaces' to their
        BaseNamespace object derivative.

        This is called by socketio_manage()."""
        self.namespaces = namespaces

    def _set_request(self, request):
        """Saves the request object for future use by the different Namespaces.

        This is called by socketio_manage().
        """
        self.request = request

    def _set_environ(self, environ):
        """Save the WSGI environ, for future use.

        This is called by socketio_manage().
        """
        self.environ = environ

    def _set_error_handler(self, error_handler):
        """Changes the default error_handler function to the one specified

        This is called by socketio_manage().
        """
        self.error_handler = error_handler

    def _set_json_loads(self, json_loads):
        """Change the default JSON decoder.

        This should be a callable that accepts a single string, and returns
        a well-formed object.
        """
        self.json_loads = json_loads

    def _set_json_dumps(self, json_dumps):
        """Change the default JSON decoder.

        This should be a callable that accepts a single string, and returns
        a well-formed object.
        """
        self.json_dumps = json_dumps

    def _get_next_msgid(self):
        """This retrieves the next value for the 'id' field when sending
        an 'event' or 'message' or 'json' that asks the remote client
        to 'ack' back, so that we trigger the local callback.
        """
        self.ack_counter += 1
        return self.ack_counter

    def _save_ack_callback(self, msgid, callback):
        """Keep a reference of the callback on this socket."""
        if msgid in self.ack_callbacks:
            return False
        self.ack_callbacks[msgid] = callback

    def _pop_ack_callback(self, msgid):
        """Fetch the callback for a given msgid, if it exists, otherwise,
        return None"""
        if msgid not in self.ack_callbacks:
            return None
        return self.ack_callbacks.pop(msgid)

    def __str__(self):
        result = ['sessid=%r' % self.sessid]
        if self.state == self.STATE_CONNECTED:
            result.append('connected')
        if self.client_queue.qsize():
            result.append('client_queue[%s]' % self.client_queue.qsize())
        if self.server_queue.qsize():
            result.append('server_queue[%s]' % self.server_queue.qsize())
        if self.hits:
            result.append('hits=%s' % self.hits)
        if self.heartbeats:
            result.append('heartbeats=%s' % self.heartbeats)

        return ' '.join(result)

    def __getitem__(self, key):
        """This will get the nested Namespace using its '/chat' reference.

        Using this, you can go from one Namespace to the other (to emit, add
        ACLs, etc..) with:

          adminnamespace.socket['/chat'].add_acl_method('kick-ban')

        """
        return self.active_ns[key]

    def __hasitem__(self, key):
        """Verifies if the namespace is active (was initialized)"""
        return key in self.active_ns

    @property
    def connected(self):
        """Returns whether the state is CONNECTED or not."""
        return self.state == self.STATE_CONNECTED

    def incr_hits(self):
        self.hits += 1

    def heartbeat(self):
        """This makes the heart beat for another X seconds.  Call this when
        you get a heartbeat packet in.

        This clear the heartbeat disconnect timeout (resets for X seconds).
        """
        self.timeout.set()

    def kill(self, detach=False):
        """This function must/will be called when a socket is to be completely
        shut down, closed by connection timeout, connection error or explicit
        disconnection from the client.

        It will call all of the Namespace's
        :meth:`~socketio.namespace.BaseNamespace.disconnect` methods
        so that you can shut-down things properly.

        """
        # Clear out the callbacks
        self.ack_callbacks = {}
        if self.connected:
            self.state = self.STATE_DISCONNECTING
            self.server_queue.put_nowait(None)
            self.client_queue.put_nowait(None)
            if len(self.active_ns) > 0:
                log.debug("Calling disconnect() on %s" % self)
                self.disconnect()

        if detach:
            self.detach()

        gevent.killall(self.jobs)

    def detach(self):
        """Detach this socket from the server. This should be done in
        conjunction with kill(), once all the jobs are dead, detach the
        socket for garbage collection."""

        log.debug("Removing %s from server sockets" % self)
        if self.sessid in self.server.sockets:
            self.server.sockets.pop(self.sessid)

    def put_server_msg(self, msg):
        """Writes to the server's pipe, to end up in in the Namespaces"""
        self.heartbeat()
        self.server_queue.put_nowait(msg)

    def put_client_msg(self, msg):
        """Writes to the client's pipe, to end up in the browser"""
        self.client_queue.put_nowait(msg)

    def get_client_msg(self, **kwargs):
        """Grab a message to send it to the browser"""
        return self.client_queue.get(**kwargs)

    def get_server_msg(self, **kwargs):
        """Grab a message, to process it by the server and dispatch calls
        """
        return self.server_queue.get(**kwargs)

    def get_multiple_client_msgs(self, **kwargs):
        """Get multiple messages, in case we're going through the various
        XHR-polling methods, on which we can pack more than one message if the
        rate is high, and encode the payload for the HTTP channel."""
        client_queue = self.client_queue
        msgs = [client_queue.get(**kwargs)]
        while client_queue.qsize():
            msgs.append(client_queue.get())
        return msgs

    def error(self, error_name, error_message, endpoint=None, msg_id=None,
              quiet=False):
        """Send an error to the user, using the custom or default
        ErrorHandler configured on the [TODO: Revise this] Socket/Handler
        object.

        :param error_name: is a simple string, for easy association on
                           the client side

        :param error_message: is a human readable message, the user
                              will eventually see

        :param endpoint: set this if you have a message specific to an
                         end point

        :param msg_id: set this if your error is relative to a
                       specific message

        :param quiet: way to make the error handler quiet. Specific to
                      the handler.  The default handler will only log,
                      with quiet.
        """
        handler = self.error_handler
        return handler(
            self, error_name, error_message, endpoint, msg_id, quiet)

    # User facing low-level function
    def disconnect(self, silent=False):
        """Calling this method will call the
        :meth:`~socketio.namespace.BaseNamespace.disconnect` method on
        all the active Namespaces that were open, killing all their
        jobs and sending 'disconnect' packets for each of them.

        Normally, the Global namespace (endpoint = '') has special meaning,
        as it represents the whole connection,

        :param silent: when True, pass on the ``silent`` flag to the Namespace
                       :meth:`~socketio.namespace.BaseNamespace.disconnect`
                       calls.
        """
        for ns_name, ns in list(self.active_ns.items()):
            ns.recv_disconnect()

    def remove_namespace(self, namespace):
        """This removes a Namespace object from the socket.

        This is usually called by
        :meth:`~socketio.namespace.BaseNamespace.disconnect`.

        """
        if namespace in self.active_ns:
            del self.active_ns[namespace]

        if len(self.active_ns) == 0 and self.connected:
            self.kill(detach=True)

    def send_packet(self, pkt):
        """Low-level interface to queue a packet on the wire (encoded as wire
        protocol"""
        self.put_client_msg(packet.encode(pkt, self.json_dumps))

    def spawn(self, fn, *args, **kwargs):
        """Spawn a new Greenlet, attached to this Socket instance.

        It will be monitored by the "watcher" method
        """

        log.debug("Spawning sub-Socket Greenlet: %s" % fn.__name__)
        job = gevent.spawn(fn, *args, **kwargs)
        self.jobs.append(job)
        return job

    def _receiver_loop(self):
        """This is the loop that takes messages from the queue for the server
        to consume, decodes them and dispatches them.

        It is the main loop for a socket.  We join on this process before
        returning control to the web framework.

        This process is not tracked by the socket itself, it is not going
        to be killed by the ``gevent.killall(socket.jobs)``, so it must
        exit gracefully itself.
        """

        while True:
            rawdata = self.get_server_msg()

            if not rawdata:
                continue  # or close the connection ?
            try:
                pkt = packet.decode(rawdata, self.json_loads)
            except (ValueError, KeyError, Exception) as e:
                self.error('invalid_packet',
                    "There was a decoding error when dealing with packet "
                    "with event: %s... (%s)" % (rawdata[:20], e))
                continue

            if pkt['type'] == 'heartbeat':
                # This is already dealth with in put_server_msg() when
                # any incoming raw data arrives.
                continue

            if pkt['type'] == 'disconnect' and pkt['endpoint'] == '':
                # On global namespace, we kill everything.
                self.kill(detach=True)
                continue

            endpoint = pkt['endpoint']

            if endpoint not in self.namespaces:
                self.error("no_such_namespace",
                    "The endpoint you tried to connect to "
                    "doesn't exist: %s" % endpoint, endpoint=endpoint)
                continue
            elif endpoint in self.active_ns:
                pkt_ns = self.active_ns[endpoint]
            else:
                new_ns_class = self.namespaces[endpoint]
                pkt_ns = new_ns_class(self.environ, endpoint,
                                        request=self.request)
                # This calls initialize() on all the classes and mixins, etc..
                # in the order of the MRO
                for cls in type(pkt_ns).__mro__:
                    if hasattr(cls, 'initialize'):
                        cls.initialize(pkt_ns)  # use this instead of __init__,
                                                # for less confusion

                self.active_ns[endpoint] = pkt_ns

            retval = pkt_ns.process_packet(pkt)

            # Has the client requested an 'ack' with the reply parameters ?
            if pkt.get('ack') == "data" and pkt.get('id'):
                if type(retval) is tuple:
                    args = list(retval)
                else:
                    args = [retval]
                returning_ack = dict(type='ack', ackId=pkt['id'],
                                     args=args,
                                     endpoint=pkt.get('endpoint', ''))
                self.send_packet(returning_ack)

            # Now, are we still connected ?
            if not self.connected:
                self.kill(detach=True)  # ?? what,s the best clean-up
                                        # when its not a
                                        # user-initiated disconnect
                return

    def _spawn_receiver_loop(self):
        """Spawns the reader loop.  This is called internall by
        socketio_manage().
        """
        job = gevent.spawn(self._receiver_loop)
        self.jobs.append(job)
        return job

    def _watcher(self):
        """Watch out if we've been disconnected, in that case, kill
        all the jobs.

        """
        while True:
            gevent.sleep(1.0)
            if not self.connected:
                for ns_name, ns in list(self.active_ns.items()):
                    ns.recv_disconnect()
                # Killing Socket-level jobs
                gevent.killall(self.jobs)
                break

    def _spawn_watcher(self):
        """This one is not waited for with joinall(socket.jobs), as it
        is an external watcher, to clean up when everything is done."""
        job = gevent.spawn(self._watcher)
        return job

    def _heartbeat(self):
        """Start the heartbeat Greenlet to check connection health."""
        interval = self.config['heartbeat_interval']
        while self.connected:
            gevent.sleep(interval)
            # TODO: this process could use a timeout object like the disconnect
            #       timeout thing, and ONLY send packets when none are sent!
            #       We would do that by calling timeout.set() for a "sending"
            #       timeout.  If we're sending 100 messages a second, there is
            #       no need to push some heartbeats in there also.
            self.put_client_msg("2::")

    def _heartbeat_timeout(self):
        timeout = float(self.config['heartbeat_timeout'])
        while True:
            self.timeout.clear()
            gevent.sleep(0)
            wait_res = self.timeout.wait(timeout=timeout)
            if not wait_res:
                if self.connected:
                    log.debug("heartbeat timed out, killing socket")
                    self.kill(detach=True)
                return


    def _spawn_heartbeat(self):
        """This functions returns a list of jobs"""
        self.spawn(self._heartbeat)
        self.spawn(self._heartbeat_timeout)
Esempio n. 44
0
class RaidenService:
    """ A Raiden node. """

    def __init__(
            self,
            chain: BlockChainService,
            query_start_block: typing.BlockNumber,
            default_registry: TokenNetworkRegistry,
            default_secret_registry: SecretRegistry,
            private_key_bin,
            transport,
            config,
            discovery=None,
    ):
        if not isinstance(private_key_bin, bytes) or len(private_key_bin) != 32:
            raise ValueError('invalid private_key')

        self.tokennetworkids_to_connectionmanagers = dict()
        self.identifier_to_results = defaultdict(list)

        self.chain: BlockChainService = chain
        self.default_registry = default_registry
        self.query_start_block = query_start_block
        self.default_secret_registry = default_secret_registry
        self.config = config
        self.privkey = private_key_bin
        self.address = privatekey_to_address(private_key_bin)
        self.discovery = discovery

        self.private_key = PrivateKey(private_key_bin)
        self.pubkey = self.private_key.public_key.format(compressed=False)
        self.transport = transport

        self.blockchain_events = BlockchainEvents()
        self.alarm = AlarmTask(chain)
        self.shutdown_timeout = config['shutdown_timeout']
        self.stop_event = Event()
        self.start_event = Event()
        self.chain.client.inject_stop_event(self.stop_event)

        self.wal = None

        self.database_path = config['database_path']
        if self.database_path != ':memory:':
            database_dir = os.path.dirname(config['database_path'])
            os.makedirs(database_dir, exist_ok=True)

            self.database_dir = database_dir
            # Prevent concurrent access to the same db
            self.lock_file = os.path.join(self.database_dir, '.lock')
            self.db_lock = filelock.FileLock(self.lock_file)
        else:
            self.database_path = ':memory:'
            self.database_dir = None
            self.lock_file = None
            self.serialization_file = None
            self.db_lock = None

        self.event_poll_lock = gevent.lock.Semaphore()

    def start_async(self) -> Event:
        """ Start the node asynchronously. """
        self.start_event.clear()
        self.stop_event.clear()

        if self.database_dir is not None:
            self.db_lock.acquire(timeout=0)
            assert self.db_lock.is_locked

        # start the registration early to speed up the start
        if self.config['transport_type'] == 'udp':
            endpoint_registration_greenlet = gevent.spawn(
                self.discovery.register,
                self.address,
                self.config['external_ip'],
                self.config['external_port'],
            )

        # The database may be :memory:
        storage = sqlite.SQLiteStorage(self.database_path, serialize.PickleSerializer())
        self.wal, unapplied_events = wal.restore_from_latest_snapshot(
            node.state_transition,
            storage,
        )

        if self.wal.state_manager.current_state is None:
            block_number = self.chain.block_number()

            state_change = ActionInitChain(
                random.Random(),
                block_number,
                self.chain.network_id,
            )
            self.wal.log_and_dispatch(state_change, block_number)
            payment_network = PaymentNetworkState(
                self.default_registry.address,
                [],  # empty list of token network states as it's the node's startup
            )
            state_change = ContractReceiveNewPaymentNetwork(payment_network)
            self.handle_state_change(state_change)

            # On first run Raiden needs to fetch all events for the payment
            # network, to reconstruct all token network graphs and find opened
            # channels
            last_log_block_number = 0
        else:
            # The `Block` state change is dispatched only after all the events
            # for that given block have been processed, filters can be safely
            # installed starting from this position without losing events.
            last_log_block_number = views.block_number(self.wal.state_manager.current_state)

        # Install the filters using the correct from_block value, otherwise
        # blockchain logs can be lost.
        self.install_all_blockchain_filters(
            self.default_registry,
            self.default_secret_registry,
            last_log_block_number,
        )

        # Complete the first_run of the alarm task and synchronize with the
        # blockchain since the last run.
        #
        # Notes about setup order:
        # - The filters must be polled after the node state has been primed,
        # otherwise the state changes won't have effect.
        # - The alarm must complete its first run  before the transport is started,
        #  to avoid rejecting messages for unknown channels.
        self.alarm.register_callback(self._callback_new_block)
        self.alarm.first_run()

        self.alarm.start()

        queueids_to_queues = views.get_all_messagequeues(views.state_from_raiden(self))
        self.transport.start(self, queueids_to_queues)

        # Health check needs the transport layer
        self.start_neighbours_healthcheck()

        for event in unapplied_events:
            on_raiden_event(self, event)

        if self.config['transport_type'] == 'udp':
            def set_start_on_registration(_):
                self.start_event.set()

            endpoint_registration_greenlet.link(set_start_on_registration)
        else:
            self.start_event.set()

        return self.start_event

    def start(self) -> Event:
        """ Start the node. """
        self.start_async().wait()

    def start_neighbours_healthcheck(self):
        for neighbour in views.all_neighbour_nodes(self.wal.state_manager.current_state):
            if neighbour != ConnectionManager.BOOTSTRAP_ADDR:
                self.start_health_check_for(neighbour)

    def stop(self):
        """ Stop the node. """
        # Needs to come before any greenlets joining
        self.stop_event.set()
        self.transport.stop_and_wait()
        self.alarm.stop_async()

        wait_for = [self.alarm]
        wait_for.extend(getattr(self.transport, 'greenlets', []))
        # We need a timeout to prevent an endless loop from trying to
        # contact the disconnected client
        gevent.wait(wait_for, timeout=self.shutdown_timeout)

        # Filters must be uninstalled after the alarm task has stopped. Since
        # the events are polled by an alarm task callback, if the filters are
        # uninstalled before the alarm task is fully stopped the callback
        # `poll_blockchain_events` will fail.
        #
        # We need a timeout to prevent an endless loop from trying to
        # contact the disconnected client
        try:
            with gevent.Timeout(self.shutdown_timeout):
                self.blockchain_events.uninstall_all_event_listeners()
        except (gevent.timeout.Timeout, RaidenShuttingDown):
            pass

        self.blockchain_events.reset()

        if self.db_lock is not None:
            self.db_lock.release()

    def __repr__(self):
        return '<{} {}>'.format(self.__class__.__name__, pex(self.address))

    def get_block_number(self):
        return views.block_number(self.wal.state_manager.current_state)

    def handle_state_change(self, state_change, block_number=None):
        log.debug('STATE CHANGE', node=pex(self.address), state_change=state_change)

        if block_number is None:
            block_number = self.get_block_number()

        event_list = self.wal.log_and_dispatch(state_change, block_number)

        for event in event_list:
            log.debug('RAIDEN EVENT', node=pex(self.address), raiden_event=event)

            on_raiden_event(self, event)

        return event_list

    def set_node_network_state(self, node_address, network_state):
        state_change = ActionChangeNodeNetworkState(node_address, network_state)
        self.wal.log_and_dispatch(state_change, self.get_block_number())

    def start_health_check_for(self, node_address):
        self.transport.start_health_check(node_address)

    def _callback_new_block(self, current_block_number, chain_id):
        """Called once a new block is detected by the alarm task.

        Note:
            This should be called only once per block, otherwise there will be
            duplicated `Block` state changes in the log.

            Therefore this method should be called only once a new block is
            mined with the appropriate block_number argument from the
            AlarmTask.
        """
        # Raiden relies on blockchain events to update its off-chain state,
        # therefore some APIs /used/ to forcefully poll for events.
        #
        # This was done for APIs which have on-chain side-effects, e.g.
        # openning a channel, where polling the event is required to update
        # off-chain state to providing a consistent view to the caller, e.g.
        # the channel exists after the API call returns.
        #
        # That pattern introduced a race, because the events are returned only
        # once per filter, and this method would be called concurrently by the
        # API and the AlarmTask. The following lock is necessary, to ensure the
        # expected side-effects are properly applied (introduced by the commit
        # 3686b3275ff7c0b669a6d5e2b34109c3bdf1921d)
        with self.event_poll_lock:
            for event in self.blockchain_events.poll_blockchain_events(current_block_number):
                # These state changes will be procesed with a block_number
                # which is /larger/ than the ChainState's block_number.
                on_blockchain_event(self, event, current_block_number, chain_id)

            # On restart the Raiden node will re-create the filters with the
            # ethereum node. These filters will have the from_block set to the
            # value of the latest Block state change. To avoid missing events
            # the Block state change is dispatched only after all of the events
            # have been processed.
            #
            # This means on some corner cases a few events may be applied
            # twice, this will happen if the node crashed and some events have
            # been processed but the Block state change has not been
            # dispatched.
            state_change = Block(current_block_number)
            self.handle_state_change(state_change, current_block_number)

    def sign(self, message):
        """ Sign message inplace. """
        if not isinstance(message, SignedMessage):
            raise ValueError('{} is not signable.'.format(repr(message)))

        message.sign(self.private_key)

    def install_all_blockchain_filters(
            self,
            token_network_registry_proxy,
            secret_registry_proxy,
            from_block,
    ):
        with self.event_poll_lock:
            node_state = views.state_from_raiden(self)
            channels = views.list_all_channelstate(node_state)
            token_networks = views.get_token_network_identifiers(
                node_state,
                token_network_registry_proxy.address,
            )

            self.blockchain_events.add_token_network_registry_listener(
                token_network_registry_proxy,
                from_block,
            )
            self.blockchain_events.add_secret_registry_listener(
                secret_registry_proxy,
                from_block,
            )

            for token_network in token_networks:
                token_network_proxy = self.chain.token_network(token_network)
                self.blockchain_events.add_token_network_listener(
                    token_network_proxy,
                    from_block,
                )

            for channel_state in channels:
                channel_proxy = self.chain.payment_channel(
                    channel_state.token_network_identifier,
                    channel_state.identifier,
                )
                self.blockchain_events.add_payment_channel_listener(
                    channel_proxy,
                    from_block,
                )

    def connection_manager_for_token_network(self, token_network_identifier):
        if not is_binary_address(token_network_identifier):
            raise InvalidAddress('token address is not valid.')

        known_token_networks = views.get_token_network_identifiers(
            views.state_from_raiden(self),
            self.default_registry.address,
        )

        if token_network_identifier not in known_token_networks:
            raise InvalidAddress('token is not registered.')

        manager = self.tokennetworkids_to_connectionmanagers.get(token_network_identifier)

        if manager is None:
            manager = ConnectionManager(self, token_network_identifier)
            self.tokennetworkids_to_connectionmanagers[token_network_identifier] = manager

        return manager

    def leave_all_token_networks(self):
        state_change = ActionLeaveAllNetworks()
        self.wal.log_and_dispatch(state_change, self.get_block_number())

    def close_and_settle(self):
        log.info('raiden will close and settle all channels now')

        self.leave_all_token_networks()

        connection_managers = [cm for cm in self.tokennetworkids_to_connectionmanagers.values()]

        if connection_managers:
            waiting.wait_for_settle_all_channels(
                self,
                self.alarm.sleep_time,
            )

    def mediated_transfer_async(
            self,
            token_network_identifier,
            amount,
            target,
            identifier,
    ):
        """ Transfer `amount` between this node and `target`.

        This method will start an asyncronous transfer, the transfer might fail
        or succeed depending on a couple of factors:

            - Existence of a path that can be used, through the usage of direct
              or intermediary channels.
            - Network speed, making the transfer sufficiently fast so it doesn't
              expire.
        """

        async_result = self.start_mediated_transfer(
            token_network_identifier,
            amount,
            target,
            identifier,
        )

        return async_result

    def direct_transfer_async(self, token_network_identifier, amount, target, identifier):
        """ Do a direct transfer with target.

        Direct transfers are non cancellable and non expirable, since these
        transfers are a signed balance proof with the transferred amount
        incremented.

        Because the transfer is non cancellable, there is a level of trust with
        the target. After the message is sent the target is effectively paid
        and then it is not possible to revert.

        The async result will be set to False iff there is no direct channel
        with the target or the payer does not have balance to complete the
        transfer, otherwise because the transfer is non expirable the async
        result *will never be set to False* and if the message is sent it will
        hang until the target node acknowledge the message.

        This transfer should be used as an optimization, since only two packets
        are required to complete the transfer (from the payers perspective),
        whereas the mediated transfer requires 6 messages.
        """

        self.start_health_check_for(target)

        if identifier is None:
            identifier = create_default_identifier()

        direct_transfer = ActionTransferDirect(
            token_network_identifier,
            target,
            identifier,
            amount,
        )

        self.handle_state_change(direct_transfer)

    def start_mediated_transfer(
            self,
            token_network_identifier,
            amount,
            target,
            identifier,
    ):

        self.start_health_check_for(target)

        if identifier is None:
            identifier = create_default_identifier()

        assert identifier not in self.identifier_to_results

        async_result = AsyncResult()
        self.identifier_to_results[identifier].append(async_result)

        secret = random_secret()
        init_initiator_statechange = initiator_init(
            self,
            identifier,
            amount,
            secret,
            token_network_identifier,
            target,
        )

        # TODO: implement the network timeout raiden.config['msg_timeout'] and
        # cancel the current transfer if it happens (issue #374)
        #
        # Dispatch the state change even if there are no routes to create the
        # wal entry.
        self.handle_state_change(init_initiator_statechange)

        return async_result

    def mediate_mediated_transfer(self, transfer: LockedTransfer):
        init_mediator_statechange = mediator_init(self, transfer)
        self.handle_state_change(init_mediator_statechange)

    def target_mediated_transfer(self, transfer: LockedTransfer):
        self.start_health_check_for(transfer.initiator)
        init_target_statechange = target_init(transfer)
        self.handle_state_change(init_target_statechange)
Esempio n. 45
0
class Group(GroupMappingMixin):
    """
    Maintain a group of greenlets that are still running, without
    limiting their number.

    Links to each item and removes it upon notification.

    Groups can be iterated to discover what greenlets they are tracking,
    they can be tested to see if they contain a greenlet, and they know the
    number (len) of greenlets they are tracking. If they are not tracking any
    greenlets, they are False in a boolean context.
    """

    #: The type of Greenlet object we will :meth:`spawn`. This can be changed
    #: on an instance or in a subclass.
    greenlet_class = Greenlet

    def __init__(self, *args):
        assert len(args) <= 1, args
        self.greenlets = set(*args)
        if args:
            for greenlet in args[0]:
                greenlet.rawlink(self._discard)
        # each item we kill we place in dying, to avoid killing the same greenlet twice
        self.dying = set()
        self._empty_event = Event()
        self._empty_event.set()

    def __repr__(self):
        return '<%s at 0x%x %s>' % (self.__class__.__name__, id(self), self.greenlets)

    def __len__(self):
        """
        Answer how many greenlets we are tracking. Note that if we are empty,
        we are False in a boolean context.
        """
        return len(self.greenlets)

    def __contains__(self, item):
        """
        Answer if we are tracking the given greenlet.
        """
        return item in self.greenlets

    def __iter__(self):
        """
        Iterate across all the greenlets we are tracking, in no particular order.
        """
        return iter(self.greenlets)

    def add(self, greenlet):
        """
        Begin tracking the greenlet.

        If this group is :meth:`full`, then this method may block
        until it is possible to track the greenlet.
        """
        try:
            rawlink = greenlet.rawlink
        except AttributeError:
            pass  # non-Greenlet greenlet, like MAIN
        else:
            rawlink(self._discard)
        self.greenlets.add(greenlet)
        self._empty_event.clear()

    def _discard(self, greenlet):
        self.greenlets.discard(greenlet)
        self.dying.discard(greenlet)
        if not self.greenlets:
            self._empty_event.set()

    def discard(self, greenlet):
        """
        Stop tracking the greenlet.
        """
        self._discard(greenlet)
        try:
            unlink = greenlet.unlink
        except AttributeError:
            pass  # non-Greenlet greenlet, like MAIN
        else:
            unlink(self._discard)

    def start(self, greenlet):
        """
        Start the un-started *greenlet* and add it to the collection of greenlets
        this group is monitoring.
        """
        self.add(greenlet)
        greenlet.start()

    def spawn(self, *args, **kwargs):
        """
        Begin a new greenlet with the given arguments (which are passed
        to the greenlet constructor) and add it to the collection of greenlets
        this group is monitoring.

        :return: The newly started greenlet.
        """
        greenlet = self.greenlet_class(*args, **kwargs)
        self.start(greenlet)
        return greenlet

#     def close(self):
#         """Prevents any more tasks from being submitted to the pool"""
#         self.add = RaiseException("This %s has been closed" % self.__class__.__name__)

    def join(self, timeout=None, raise_error=False):
        """
        Wait for this group to become empty *at least once*.

        If there are no greenlets in the group, returns immediately.

        .. note:: By the time the waiting code (the caller of this
           method) regains control, a greenlet may have been added to
           this group, and so this object may no longer be empty. (That
           is, ``group.join(); assert len(group) == 0`` is not
           guaranteed to hold.) This method only guarantees that the group
           reached a ``len`` of 0 at some point.

        :keyword bool raise_error: If True (*not* the default), if any
            greenlet that finished while the join was in progress raised
            an exception, that exception will be raised to the caller of
            this method. If multiple greenlets raised exceptions, which
            one gets re-raised is not determined. Only greenlets currently
            in the group when this method is called are guaranteed to
            be checked for exceptions.

        :return bool: A value indicating whether this group became empty.
           If the timeout is specified and the group did not become empty
           during that timeout, then this will be a false value. Otherwise
           it will be a true value.

        .. versionchanged:: 1.2a1
           Add the return value.
        """
        greenlets = list(self.greenlets) if raise_error else ()
        result = self._empty_event.wait(timeout=timeout)

        for greenlet in greenlets:
            if greenlet.exception is not None:
                if hasattr(greenlet, '_raise_exception'):
                    greenlet._raise_exception()
                raise greenlet.exception

        return result

    def kill(self, exception=GreenletExit, block=True, timeout=None):
        """
        Kill all greenlets being tracked by this group.
        """
        timer = Timeout._start_new_or_dummy(timeout)
        try:
            while self.greenlets:
                for greenlet in list(self.greenlets):
                    if greenlet in self.dying:
                        continue
                    try:
                        kill = greenlet.kill
                    except AttributeError:
                        _kill(greenlet, exception)
                    else:
                        kill(exception, block=False)
                    self.dying.add(greenlet)
                if not block:
                    break
                joinall(self.greenlets)
        except Timeout as ex:
            if ex is not timer:
                raise
        finally:
            timer.cancel()

    def killone(self, greenlet, exception=GreenletExit, block=True, timeout=None):
        """
        If the given *greenlet* is running and being tracked by this group,
        kill it.
        """
        if greenlet not in self.dying and greenlet in self.greenlets:
            greenlet.kill(exception, block=False)
            self.dying.add(greenlet)
            if block:
                greenlet.join(timeout)

    def full(self):
        """
        Return a value indicating whether this group can track more greenlets.

        In this implementation, because there are no limits on the number of
        tracked greenlets, this will always return a ``False`` value.
        """
        return False

    def wait_available(self, timeout=None):
        """
        Block until it is possible to :meth:`spawn` a new greenlet.

        In this implementation, because there are no limits on the number
        of tracked greenlets, this will always return immediately.
        """
        pass

    # MappingMixin methods

    def _apply_immediately(self):
        # If apply() is called from one of our own
        # worker greenlets, don't spawn a new one---if we're full, that
        # could deadlock.
        return getcurrent() in self

    def _apply_async_cb_spawn(self, callback, result):
        Greenlet.spawn(callback, result)

    def _apply_async_use_greenlet(self):
        # cannot call self.spawn() because it will block, so
        # use a fresh, untracked greenlet that when run will
        # (indirectly) call self.spawn() for us.
        return self.full()
Esempio n. 46
0
class BaseServer(object):
    """
    An abstract base class that implements some common functionality for the servers in gevent.

    :param listener: Either be an address that the server should bind
        on or a :class:`gevent.socket.socket` instance that is already
        bound (and put into listening mode in case of TCP socket).

    :keyword handle: If given, the request handler. The request
        handler can be defined in a few ways. Most commonly,
        subclasses will implement a ``handle`` method as an
        instance method. Alternatively, a function can be passed
        as the ``handle`` argument to the constructor. In either
        case, the handler can later be changed by calling
        :meth:`set_handle`.

        When the request handler returns, the socket used for the
        request will be closed. Therefore, the handler must not return if
        the socket is still in use (for example, by manually spawned greenlets).

    :keyword spawn: If provided, is called to create a new
        greenlet to run the handler. By default,
        :func:`gevent.spawn` is used (meaning there is no
        artificial limit on the number of concurrent requests). Possible values for *spawn*:

        - a :class:`gevent.pool.Pool` instance -- ``handle`` will be executed
          using :meth:`gevent.pool.Pool.spawn` only if the pool is not full.
          While it is full, no new connections are accepted;
        - :func:`gevent.spawn_raw` -- ``handle`` will be executed in a raw
          greenlet which has a little less overhead then :class:`gevent.Greenlet` instances spawned by default;
        - ``None`` -- ``handle`` will be executed right away, in the :class:`Hub` greenlet.
          ``handle`` cannot use any blocking functions as it would mean switching to the :class:`Hub`.
        - an integer -- a shortcut for ``gevent.pool.Pool(integer)``

    .. versionchanged:: 1.1a1
       When the *handle* function returns from processing a connection,
       the client socket will be closed. This resolves the non-deterministic
       closing of the socket, fixing ResourceWarnings under Python 3 and PyPy.

    """
    # pylint: disable=too-many-instance-attributes,bare-except,broad-except

    #: the number of seconds to sleep in case there was an error in accept() call
    #: for consecutive errors the delay will double until it reaches max_delay
    #: when accept() finally succeeds the delay will be reset to min_delay again
    min_delay = 0.01
    max_delay = 1

    #: Sets the maximum number of consecutive accepts that a process may perform on
    #: a single wake up. High values give higher priority to high connection rates,
    #: while lower values give higher priority to already established connections.
    #: Default is 100. Note, that in case of multiple working processes on the same
    #: listening value, it should be set to a lower value. (pywsgi.WSGIServer sets it
    #: to 1 when environ["wsgi.multiprocess"] is true)
    max_accept = 100

    _spawn = Greenlet.spawn

    #: the default timeout that we wait for the client connections to close in stop()
    stop_timeout = 1

    fatal_errors = (errno.EBADF, errno.EINVAL, errno.ENOTSOCK)

    def __init__(self, listener, handle=None, spawn='default'):
        self._stop_event = Event()
        self._stop_event.set()
        self._watcher = None
        self._timer = None
        self._handle = None
        # XXX: FIXME: Subclasses rely on the presence or absence of the
        # `socket` attribute to determine whether we are open/should be opened.
        # Instead, have it be None.
        self.pool = None
        try:
            self.set_listener(listener)
            self.set_spawn(spawn)
            self.set_handle(handle)
            self.delay = self.min_delay
            self.loop = get_hub().loop
            if self.max_accept < 1:
                raise ValueError('max_accept must be positive int: %r' %
                                 (self.max_accept, ))
        except:
            self.close()
            raise

    def set_listener(self, listener):
        if hasattr(listener, 'accept'):
            if hasattr(listener, 'do_handshake'):
                raise TypeError(
                    'Expected a regular socket, not SSLSocket: %r' %
                    (listener, ))
            self.family = listener.family
            self.address = listener.getsockname()
            self.socket = listener
        else:
            self.family, self.address = parse_address(listener)

    def set_spawn(self, spawn):
        if spawn == 'default':
            self.pool = None
            self._spawn = self._spawn
        elif hasattr(spawn, 'spawn'):
            self.pool = spawn
            self._spawn = spawn.spawn
        elif isinstance(spawn, integer_types):
            from gevent.pool import Pool
            self.pool = Pool(spawn)
            self._spawn = self.pool.spawn
        else:
            self.pool = None
            self._spawn = spawn
        if hasattr(self.pool, 'full'):
            self.full = self.pool.full
        if self.pool is not None:
            self.pool._semaphore.rawlink(self._start_accepting_if_started)

    def set_handle(self, handle):
        if handle is not None:
            self.handle = handle
        if hasattr(self, 'handle'):
            self._handle = self.handle
        else:
            raise TypeError("'handle' must be provided")

    def _start_accepting_if_started(self, _event=None):
        if self.started:
            self.start_accepting()

    def start_accepting(self):
        if self._watcher is None:
            # just stop watcher without creating a new one?
            self._watcher = self.loop.io(self.socket.fileno(), 1)
            self._watcher.start(self._do_read)

    def stop_accepting(self):
        if self._watcher is not None:
            self._watcher.stop()
            self._watcher.close()
            self._watcher = None
        if self._timer is not None:
            self._timer.stop()
            self._timer.close()
            self._timer = None

    def do_handle(self, *args):
        spawn = self._spawn
        handle = self._handle
        close = self.do_close

        try:
            if spawn is None:
                _handle_and_close_when_done(handle, close, args)
            else:
                spawn(_handle_and_close_when_done, handle, close, args)
        except:
            close(*args)
            raise

    def do_close(self, *args):
        pass

    def do_read(self):
        raise NotImplementedError()

    def _do_read(self):
        for _ in xrange(self.max_accept):
            if self.full():
                self.stop_accepting()
                return
            try:
                args = self.do_read()
                self.delay = self.min_delay
                if not args:
                    return
            except:
                self.loop.handle_error(self, *sys.exc_info())
                ex = sys.exc_info()[1]
                if self.is_fatal_error(ex):
                    self.close()
                    sys.stderr.write('ERROR: %s failed with %s\n' %
                                     (self, str(ex) or repr(ex)))
                    return
                if self.delay >= 0:
                    self.stop_accepting()
                    self._timer = self.loop.timer(self.delay)
                    self._timer.start(self._start_accepting_if_started)
                    self.delay = min(self.max_delay, self.delay * 2)
                break
            else:
                try:
                    self.do_handle(*args)
                except:
                    self.loop.handle_error((args[1:], self), *sys.exc_info())
                    if self.delay >= 0:
                        self.stop_accepting()
                        self._timer = self.loop.timer(self.delay)
                        self._timer.start(self._start_accepting_if_started)
                        self.delay = min(self.max_delay, self.delay * 2)
                    break

    def full(self):
        # copied from self.pool
        # pylint: disable=method-hidden
        return False

    def __repr__(self):
        return '<%s at %s %s>' % (type(self).__name__, hex(
            id(self)), self._formatinfo())

    def __str__(self):
        return '<%s %s>' % (type(self).__name__, self._formatinfo())

    def _formatinfo(self):
        if hasattr(self, 'socket'):
            try:
                fileno = self.socket.fileno()
            except Exception as ex:
                fileno = str(ex)
            result = 'fileno=%s ' % fileno
        else:
            result = ''
        try:
            if isinstance(self.address, tuple) and len(self.address) == 2:
                result += 'address=%s:%s' % self.address
            else:
                result += 'address=%s' % (self.address, )
        except Exception as ex:
            result += str(ex) or '<error>'

        handle = self.__dict__.get('handle')
        if handle is not None:
            fself = getattr(handle, '__self__', None)
            try:
                if fself is self:
                    # Checks the __self__ of the handle in case it is a bound
                    # method of self to prevent recursivly defined reprs.
                    handle_repr = '<bound method %s.%s of self>' % (
                        self.__class__.__name__,
                        handle.__name__,
                    )
                else:
                    handle_repr = repr(handle)

                result += ' handle=' + handle_repr
            except Exception as ex:
                result += str(ex) or '<error>'

        return result

    @property
    def server_host(self):
        """IP address that the server is bound to (string)."""
        if isinstance(self.address, tuple):
            return self.address[0]

    @property
    def server_port(self):
        """Port that the server is bound to (an integer)."""
        if isinstance(self.address, tuple):
            return self.address[1]

    def init_socket(self):
        """If the user initialized the server with an address rather than socket,
        then this function will create a socket, bind it and put it into listening mode.

        It is not supposed to be called by the user, it is called by :meth:`start` before starting
        the accept loop."""

    @property
    def started(self):
        return not self._stop_event.is_set()

    def start(self):
        """Start accepting the connections.

        If an address was provided in the constructor, then also create a socket,
        bind it and put it into the listening mode.
        """
        self.init_socket()
        self._stop_event.clear()
        try:
            self.start_accepting()
        except:
            self.close()
            raise

    def close(self):
        """Close the listener socket and stop accepting."""
        self._stop_event.set()
        try:
            self.stop_accepting()
        finally:
            try:
                self.socket.close()
            except Exception:
                pass
            finally:
                self.__dict__.pop('socket', None)
                self.__dict__.pop('handle', None)
                self.__dict__.pop('_handle', None)
                self.__dict__.pop('_spawn', None)
                self.__dict__.pop('full', None)
                if self.pool is not None:
                    self.pool._semaphore.unlink(
                        self._start_accepting_if_started)
                    # If the pool's semaphore had a notifier already started,
                    # there's a reference cycle we're a part of
                    # (self->pool->semaphere-hub callback->semaphore)
                    # But we can't destroy self.pool, because self.stop()
                    # calls this method, and then wants to join self.pool()

    @property
    def closed(self):
        return not hasattr(self, 'socket')

    def stop(self, timeout=None):
        """
        Stop accepting the connections and close the listening socket.

        If the server uses a pool to spawn the requests, then
        :meth:`stop` also waits for all the handlers to exit. If there
        are still handlers executing after *timeout* has expired
        (default 1 second, :attr:`stop_timeout`), then the currently
        running handlers in the pool are killed.

        If the server does not use a pool, then this merely stops accepting connections;
        any spawned greenlets that are handling requests continue running until
        they naturally complete.
        """
        self.close()
        if timeout is None:
            timeout = self.stop_timeout
        if self.pool:
            self.pool.join(timeout=timeout)
            self.pool.kill(block=True, timeout=1)

    def serve_forever(self, stop_timeout=None):
        """Start the server if it hasn't been already started and wait until it's stopped."""
        # add test that serve_forever exists on stop()
        if not self.started:
            self.start()
        try:
            self._stop_event.wait()
        finally:
            Greenlet.spawn(self.stop, timeout=stop_timeout).join()

    def is_fatal_error(self, ex):
        return isinstance(ex,
                          _socket.error) and ex.args[0] in self.fatal_errors
Esempio n. 47
0
class SardanaMacro(CommandObject, SardanaObject):

    macroStatusAttr = None
    INIT, STARTED, RUNNING, DONE = range(4)

    def __init__(self, name, macro, doorname=None, username=None, **kwargs):
        super(SardanaMacro, self).__init__(name, username, **kwargs)

        self._reply_arrived_event = Event()
        self.macro_format = macro
        self.doorname = doorname
        self.door = None
        self.init_device()
        self.macrostate = SardanaMacro.INIT
        self.doorstate = None
        self.t0 = 0

    def init_device(self):
        self.door = Device(self.doorname)
        self.door.set_timeout_millis(10000)

        #
        # DIRTY FIX to make compatible taurus listeners and existence of Tango channels/commands
        # as defined in Command/Tango.py
        #
        # if self.door.__class__ == taurus.core.tango.tangodevice.TangoDevice:
        #    dp = self.door.getHWObj()
        #    try:
        #        dp.subscribe_event = dp._subscribe_event
        #    except AttributeError:
        #        pass

        if self.macroStatusAttr is None:
            self.macroStatusAttr = self.door.getAttribute("State")
            self.macroStatusAttr.addListener(self.objectListener)

    def __call__(self, *args, **kwargs):

        self._reply_arrived_event.clear()
        self.result = None

        wait = kwargs.get("wait", False)

        if self.door is None:
            self.init_device()

        logging.getLogger("HWR").debug(
            "Executing sardana macro: %s" % self.macro_format
        )
        logging.getLogger("HWR").debug(
            "   args=%s / kwargs=%s" % (str(args), str(kwargs))
        )

        try:
            fullcmd = self.macro_format + " " + " ".join([str(a) for a in args])
        except Exception:
            import traceback

            logging.getLogger("HWR").info(traceback.format_exc())
            logging.getLogger("HWR").info(
                "  - Wrong format for macro arguments. Macro is %s / args are (%s)"
                % (self.macro_format, str(args))
            )
            return

        try:
            import time

            self.t0 = time.time()
            if self.doorstate in ["ON", "ALARM"]:
                self.door.runMacro(fullcmd.split())
                self.macrostate = SardanaMacro.STARTED
                self.emit("commandBeginWaitReply", (str(self.name()),))
            else:
                logging.getLogger("HWR").error(
                    "%s. Cannot execute. Door is not READY", str(self.name())
                )
                self.emit("commandFailed", (-1, self.name()))
        except TypeError:
            logging.getLogger("HWR").error(
                "%s. Cannot properly format macro code. Format is: %s, args are %s",
                str(self.name()),
                self.macro_format,
                str(args),
            )
            self.emit("commandFailed", (-1, self.name()))
        except DevFailed as error_dict:
            logging.getLogger("HWR").error(
                "%s: Cannot run macro. %s", str(self.name()), error_dict
            )
            self.emit("commandFailed", (-1, self.name()))
        except AttributeError as error_dict:
            logging.getLogger("HWR").error(
                "%s: MacroServer not running?, %s", str(self.name()), error_dict
            )
            self.emit("commandFailed", (-1, self.name()))
        except Exception:
            logging.getLogger("HWR").exception(
                "%s: an error occured when calling Tango command %s",
                str(self.name()),
                self.macro_format,
            )
            self.emit("commandFailed", (-1, self.name()))

        if wait:
            logging.getLogger("HWR").debug("... start waiting...")
            t = gevent.spawn(endOfMacro, self)
            t.get()
            logging.getLogger("HWR").debug("... end waiting...")

        return

    def update(self, event):
        data = event.event[2]

        try:
            if not isinstance(data, PyTango.DeviceAttribute):
                # Events different than a value changed on attribute.  Taurus sends an event with attribute info
                # logging.getLogger('HWR').debug("==========. Got an event, but it is not an attribute . it is %s" % type(data))
                # logging.getLogger('HWR').debug("doorstate event. type is %s" % str(type(data)))
                return

            # Handling macro state changed event
            doorstate = str(data.value)
            logging.getLogger("HWR").debug(
                "doorstate changed. it is %s" % str(doorstate)
            )

            if doorstate != self.doorstate:
                self.doorstate = doorstate

                # logging.getLogger('HWR').debug("self.doorstate is %s" % self.canExecute())
                self.emit("commandCanExecute", (self.canExecute(),))

                if doorstate in ["ON", "ALARM"]:
                    # logging.getLogger('HWR').debug("Macroserver ready for commands")
                    self.emit("commandReady", ())
                else:
                    # logging.getLogger('HWR').debug("Macroserver busy ")
                    self.emit("commandNotReady", ())

            if self.macrostate == SardanaMacro.STARTED and doorstate == "RUNNING":
                # logging.getLogger('HWR').debug("Macro server is running")
                self.macrostate = SardanaMacro.RUNNING
            elif self.macrostate == SardanaMacro.RUNNING and (
                doorstate in ["ON", "ALARM"]
            ):
                logging.getLogger("HWR").debug("Macro execution finished")
                self.macrostate = SardanaMacro.DONE
                self.result = self.door.result
                self.emit("commandReplyArrived", (self.result, str(self.name())))
                if doorstate == "ALARM":
                    self.emit("commandAborted", (str(self.name()),))
                self._reply_arrived_event.set()
            elif (
                self.macrostate == SardanaMacro.DONE
                or self.macrostate == SardanaMacro.INIT
            ):
                # already handled in the general case above
                pass
            else:
                logging.getLogger("HWR").debug("Macroserver state changed")
                self.emit("commandFailed", (-1, str(self.name())))
        except ConnectionFailed:
            logging.getLogger("HWR").debug("Cannot connect to door %s" % self.doorname)
            self.emit("commandFailed", (-1, str(self.name())))
        except Exception:
            import traceback

            logging.getLogger("HWR").debug(
                "SardanaMacro / event handling problem. Uggh. %s"
                % traceback.format_exc()
            )
            self.emit("commandFailed", (-1, str(self.name())))

    def abort(self):
        if self.door is not None:
            logging.getLogger("HWR").debug("SardanaMacro / aborting macro")
            self.door.abortMacro()
            # self.emit('commandReady', ())

    def isConnected(self):
        return self.door is not None

    def canExecute(self):
        return self.door is not None and (self.doorstate in ["ON", "ALARM"])
Esempio n. 48
0
class State(object):
    def __init__(self):
        self._eid = 0
        self._event = Event()
        self._w = self._h = self._health = None
        self.size_changed_count = 0

    def wait(self, eid, timeout=5):
        if eid < self._eid:
            return
        self._event.clear()
        self._event.wait(timeout)
        return self._eid

    def notify(self):
        self._eid += 1
        self._event.set()

    def _update_health(self):
        health = True
        output = gsp.check_output([
            'supervisorctl', '-c', '/etc/supervisor/supervisord.conf', 'status'
        ])
        for line in output.strip().split('\n'):
            if not line.startswith('web') and line.find('RUNNING') < 0:
                health = False
                break
        if self._health != health:
            self._health = health
            self.notify()
        return self._health

    def to_dict(self):
        self._update_health()

        state = {
            'id': self._eid,
            'config': {
                'fixedResolution': 'RESOLUTION' in environ,
                'sizeChangedCount': self.size_changed_count
            }
        }

        self._update_size()
        state.update({
            'width': self.w,
            'height': self.h,
        })

        return state

    def set_size(self, w, h):
        gsp.check_call(('sed -i \'s#'
                        '^exec /usr/bin/Xvfb.*$'
                        '#'
                        'exec /usr/bin/Xvfb :1 -screen 0 {}x{}x16'
                        '#\' /usr/local/bin/xvfb.sh').format(w, h),
                       shell=True)
        self.size_changed_count += 1

    def apply_and_restart(self):
        gsp.check_call([
            'supervisorctl', '-c', '/etc/supervisor/supervisord.conf',
            'restart', 'x:'
        ])
        self._w = self._h = self._health = None
        self.notify()

    def switch_video(self, onoff):
        xenvs = {
            'DISPLAY': ':1',
        }
        try:
            cmd = 'nofb' if onoff else 'fb'
            gsp.check_output(['x11vnc', '-remote', cmd], env=xenvs)
        except gsp.CalledProcessError as e:
            log.warn('failed to set x11vnc fb: ' + str(e))

    def _update_size(self):
        if self._w is not None and self._h is not None:
            return
        xenvs = {
            'DISPLAY': ':1',
        }
        try:
            output = gsp.check_output(['x11vnc', '-query', 'dpy_x,dpy_y'],
                                      env=xenvs).decode('utf-8')
            mobj = research(r'dpy_x:(\d+).*dpy_y:(\d+)', output)
            if mobj is not None:
                w, h = int(mobj.group(1)), int(mobj.group(2))
                changed = False
                if self._w != w:
                    changed = True
                    self._w = w
                if self._h != h:
                    changed = True
                    self._h = h
                if changed:
                    self.notify()
        except gsp.CalledProcessError as e:
            log.warn('failed to get dispaly size: ' + str(e))

    def reset_size(self):
        self.size_changed_count = 0

    @property
    def w(self):
        return self._w

    @property
    def h(self):
        return self._h

    @property
    def health(self):
        self._update_health()
        return self._health
Esempio n. 49
0
class APIClient(BaseNamespace, plugintools.GreenletObject):
    node = ('ws.download.am', 443)
    change_node = True # change node if asked to, may be overridden by --testbackend for internal testing

    def __init__(self):
        plugintools.GreenletObject.__init__(self)

        self.io = None
        self.next_node = None
        self.greenlet = None
        self.lock = Semaphore()

        self.login_results = dict()
        self.connected_event = Event()
        self.disconnected_event = Event()

        self.connect_retry = 0
        self.connect_retry_last_msgbox = 0
        self.connect_error_dialog = None

        self.connection_states = None

    def __call__(self, socketio, path):
        BaseNamespace.__init__(self, socketio, path)
        return self

    def connect(self):
        try:
            if self.io:
                self.io.disconnect()

            if self.next_node is not None:
                node = self.next_node
                self.next_node = None
            else:
                node = self.node

            log.info('connecting to {}:{}'.format(*node))
            self.io = SocketIO(node[0], node[1], self, secure=True)

            all_events = ["disconnect", "reconnect", "open", "close", "error", "retry", "message"]
            for e in all_events:
                self.io.on(e, getattr(self, "on_" + e))
        except BaseException as e:
            return e

    def is_connected(self):
        return True if self.connected_event.is_set() and not self.disconnected_event.is_set() else False

    def wait_connected(self):
        self.connected_event.wait()

    def close(self):
        if self.greenlet and gevent.getcurrent() != self.greenlet:
            self.greenlet.kill()
        if self.io:
            io = self.io
            self.io = None
            try:
                io.disconnect()
            except:
                pass
            log.info('closed api connection')
        self.disconnected_event.set()

    def on_connect(self, *args, **kwargs):
        log.debug('connected')
    
    def on_disconnect(self, exception):
        if self.io:
            if exception:
                log.warning('disconnected with error: {}'.format(exception))
            else:
                log.debug('disconnected')
            self.close()
        
    def on_reconnect(self, *args):
        log.warning("unhandled event: RECONNECT: {}".format(args))

    def on_open(self, *args):
        log.warning("unhandled event: OPEN: {}".format(args))

    def on_close(self, *args):
        log.warning("unhandled event: CLOSE: {}".format(args))

    def on_error(self, reason, advice):
        log.warning("unhandled event: ERROR: reason: {}, advise: {}".format(reason, advice))

    def on_retry(self, *args):
        log.warning("unhandled event: RETRY: {}".format(args))

    def on_(self, *args):
        log.warning("unhandled event: ON_: {}".format(args))

    def send_message(self, message):
        with self.lock:
            #print ">>> SEND CLIENT", message['flags'], message.get('command'), message['payload']
            self.emit("message", message)

    def send(self, type, command=None, in_response_to=None, payload=None, channel=None, encrypt=True, emit=None):
        if type == 'frontend':
            if not self.connected_event.is_set():
                return

        message = proto.pack_message(type, command, in_response_to, payload, channel=channel, encrypt=encrypt)

        self.send_message(message)

    def on_message(self, message):
        message = proto.unpack_message(message)
        gevent.spawn(proto.process_message, self.send_message, message)

    def handshake(self):
        """returns None on error or (node_host, node_port)
        """
        data = {}
        data["name"] = base64.standard_b64encode(login.encrypt('frontend', socket.gethostname()))
        data["system"] = platform.system()
        data["machine"] = platform.machine()
        data["platform"] = platform.platform()
        data["release"] = platform.release()
        data["version"] = platform.version()

        result = AsyncResult()
        rid = str(id(result))
        self.login_results[rid] = result
        key = login.generate_backend_key()
        from .. import patch
        payload = {
            'id': rid,
            'version': proto.VERSION,
            'branch': patch.config.branch,
            'commit_id': patch.core_source.version,
            'l': login.get('login'),
            'system': data,
        }
        message_key = proto.pack_message('backend', 'api.set_key', payload=dict(key=key), encrypt="rsa")
        message = proto.pack_message('backend', 'api.login', payload=payload)

        def send_login():
            for i in xrange(7):
                try:
                    if result.value is not None:
                        return
                except AttributeError:
                    return
                msg = 'logging in (retry {})'.format(i)
                if i > 0:
                    if self.connection_state is not None:
                        self.connection_state.put(msg)
                    else:
                        log.info(msg)
                try:
                    self.send_message(message_key)
                    self.send_message(message)
                except AttributeError:
                    result.set([False, 'Client login error'])
                    return
                secs = 5*i
                if secs < 10:
                    secs = 10
                gevent.sleep(secs)
            if result.value is None:
                result.set([False, 'Login timed out'])

        g = gevent.spawn(send_login)
        try:
            result = result.get()
        finally:
            try:
                del self.login_results[rid]
            except KeyError:
                pass
            g.kill()

        if not result[0]:
            log.error('login failed: {}'.format(result[1]))
            if result[1] == 'Invalid Login Credentials':
                self.connect_retry = 0
                login.logout()
            return False

        node = result[1].rsplit(':', 1)
        node = str(node[0]), int(len(node) == 2 and node[1] or 443)
        if node != self.node and self.node != self.next_node:
            if not self.change_node:
                log.info("i was asked to switch my connection to `{}`. I refuse. (--testbackend is {})".format(node, self.node[0]))
                return True
            self.next_node = node
            log.info('switching to node {}:{}'.format(*self.next_node))
            return False

        return True

    def connection_error(self):
        try:
            if not self.connect_error_dialog:
                self.connect_error_dialog = gevent.spawn(self._connection_error)
        finally:
            self.connect_retry = 0
            self.connect_error_dialog = None

    def _connection_error(self):
        elements = list()
        elements.append(input.Float('left'))
        elements.append(input.Text('Failed connecting to download.am.\nPlease check your internet connection.'))
        elements.append(input.Text(''))
        elements.append(input.Text('Proxy settings'))
        elements.append([input.Text('Type:'), input.Select('type', ('direct', 'http', 'https', 'socks4', 'socks5'), default=proxy.proxy_enabled() and proxy.config.type or 'direct')])
        elements.append([input.Text('Hostname:'), input.Input('host', value=proxy.config.host or '')])
        elements.append([input.Text('Port:'), input.Input('port', value=proxy.config.port or '')])

        elements.append(input.Text(''))
        elements.append(input.Text('Proxy authorization'))
        elements.append([input.Text('Username:'******'username', value=proxy.config.username or '')])
        elements.append([input.Text('Password:'******'password', 'password', value=proxy.config.password or '')])

        elements.append(input.Text(''))

        elements.append(input.Float('center'))
        elements.append(input.Choice('result', choices=[
            dict(value='save', content='Save', ok=True),
            dict(value='retry', content='Retry', cancel=True),
            dict(value='exit', content='Exit download.am')]))

        try:
            result = input.get(elements, type='api_connect', timeout=None, ignore_api=True)
        except input.InputAborted:
            return
        finally:
            self.connect_retry_last_msgbox = time.time()

        if result.get('result') == 'save':
            if result['type'] == 'direct':
                result['type'] = None
            result['enabled'] = True
            del result['result']
            interface.call('proxy', 'set', **result)
        elif result.get('result') == 'retry':
            return
        elif result.get('result') == 'exit':
            sys.exit(1)
        else:
            raise RuntimeError('invalid response: {}'.format(result))

    def run(self):
        self.connect_retry = 0
        error = False
        self.connect_error_dialog = None

        while True:
            self.close()
            self.connected_event.clear()

            login.wait()    # wait until user entered login data
            # TODO: wait for reconnected event

            if error:
                self.connect_retry += 1
                error = False
            else:
                self.connect_retry = 0

            sleep_time = (self.connect_retry + 1)*3
            if sleep_time > 15:
                sleep_time = 15

            if self.connection_state is not None:
                self.connection_state.put('connecting')
            try:
                e = self.spawn(self.connect).get()
                if e is not None:
                    raise e
                self.emit("hello", settings.app_uuid, "client")
            except (KeyboardInterrupt, SystemExit, gevent.GreenletExit):
                raise
            except BaseException as e:
                if self.connection_state is not None:
                    self.connection_state.put('connection error')
                log.error('error connecting: {}'.format(e))
                self.close()
                event.fire('api:connection_error')
                error = True
                gevent.sleep(sleep_time)
                continue
            finally:
                self.kill()

            if self.connection_state is not None:
                self.connection_state.put('logging in')
            try:
                self.spawn(self.handshake)
                result = self.greenlet.get()
                if not result:
                    continue
            except (KeyboardInterrupt, SystemExit, gevent.GreenletExit):
                raise
            except BaseException as e:
                if self.connection_state is not None:
                    self.connection_state.put('login error')
                log.error('error handshaking: {}'.format(str(e)))
                self.close()
                error = True
                gevent.sleep(sleep_time)
                continue
            finally:
                self.kill()

            if self.io is None or not self.io.connected:
                log.warning('api bootstrap done but not connected. this is strange...')
                if self.connection_state is not None:
                    self.connection_state.put('connection error')
                self.close()
                error = True
                gevent.sleep(sleep_time)
                continue

            self.connect_retry = 0
            if self.connect_error_dialog:
                self.connect_error_dialog.kill()
                self.connect_error_dialog = None

            if self.connection_state is not None:
                self.connection_state.put('sending infos')
            payload = interface.call('api', 'expose_all')
            message = proto.pack_message('frontend', 'api.expose_all', payload=payload)
            try:
                self.send_message(message)
            except AttributeError:
                log.warning('api closed unexpected')
                if self.connection_state is not None:
                    self.connection_state.put('connection error')
                self.close()
                error = True
                gevent.sleep(sleep_time)
                continue

            self.connected_event.set()
            self.disconnected_event.clear()
            if self.connection_state is not None:
                self.connection_state.put('connected')

            event.fire('api:connected')
            self.disconnected_event.wait()
            login.hashes["backend"] = None
            event.fire('api:disconnected')
Esempio n. 50
0
class BaseServer(object):
    """An abstract base class that implements some common functionality for the servers in gevent.

    *listener* can either be an address that the server should bind on or a :class:`gevent.socket.socket`
    instance that is already bound (and put into listening mode in case of TCP socket).

    *spawn*, if provided, is called to create a new greenlet to run the handler. By default, :func:`gevent.spawn` is used.

    Possible values for *spawn*:

    * a :class:`gevent.pool.Pool` instance -- *handle* will be executed
      using :meth:`Pool.spawn` method only if the pool is not full.
      While it is full, all the connection are dropped;
    * :func:`gevent.spawn_raw` -- *handle* will be executed in a raw
      greenlet which have a little less overhead then :class:`gevent.Greenlet` instances spawned by default;
    * ``None`` -- *handle* will be executed right away, in the :class:`Hub` greenlet.
      *handle* cannot use any blocking functions as it means switching to the :class:`Hub`.
    * an integer -- a shortcut for ``gevent.pool.Pool(integer)``
    """
    # the number of seconds to sleep in case there was an error in accept() call
    # for consecutive errors the delay will double until it reaches max_delay
    # when accept() finally succeeds the delay will be reset to min_delay again
    min_delay = 0.01
    max_delay = 1

    # Sets the maximum number of consecutive accepts that a process may perform on
    # a single wake up. High values give higher priority to high connection rates,
    # while lower values give higher priority to already established connections.
    # Default is 100. Note, that in case of multiple working processes on the same
    # listening value, it should be set to a lower value. (pywsgi.WSGIServer sets it
    # to 1 when environ["wsgi.multiprocess"] is true)
    max_accept = 100

    _spawn = Greenlet.spawn

    # the default timeout that we wait for the client connections to close in stop()
    stop_timeout = 1

    fatal_errors = (errno.EBADF, errno.EINVAL, errno.ENOTSOCK)

    def __init__(self, listener, handle=None, spawn='default'):
        self._stop_event = Event()
        self._stop_event.set()
        self._watcher = None
        self._timer = None
        self.pool = None
        try:
            self.set_listener(listener)
            self.set_spawn(spawn)
            self.set_handle(handle)
            self.delay = self.min_delay
            self.loop = gevent.get_hub().loop
            if self.max_accept < 1:
                raise ValueError('max_accept must be positive int: %r' %
                                 (self.max_accept, ))
        except:
            self.close()
            raise

    def set_listener(self, listener):
        if hasattr(listener, 'accept'):
            if hasattr(listener, 'do_handshake'):
                raise TypeError(
                    'Expected a regular socket, not SSLSocket: %r' %
                    (listener, ))
            self.family = listener.family
            self.address = listener.getsockname()
            self.socket = listener
        else:
            self.family, self.address = parse_address(listener)

    def set_spawn(self, spawn):
        if spawn == 'default':
            self.pool = None
            self._spawn = self._spawn
        elif hasattr(spawn, 'spawn'):
            self.pool = spawn
            self._spawn = spawn.spawn
        elif isinstance(spawn, (int, long)):
            from gevent.pool import Pool
            self.pool = Pool(spawn)
            self._spawn = self.pool.spawn
        else:
            self.pool = None
            self._spawn = spawn
        if hasattr(self.pool, 'full'):
            self.full = self.pool.full
        if self.pool is not None:
            self.pool._semaphore.rawlink(self._start_accepting_if_started)

    def set_handle(self, handle):
        if handle is not None:
            self.handle = handle
        if hasattr(self, 'handle'):
            self._handle = self.handle
        else:
            raise TypeError("'handle' must be provided")

    def _start_accepting_if_started(self, _event=None):
        if self.started:
            self.start_accepting()

    def start_accepting(self):
        if self._watcher is None:
            # just stop watcher without creating a new one?
            self._watcher = self.loop.io(self.socket.fileno(), 1)
            self._watcher.start(self._do_read)

    def stop_accepting(self):
        if self._watcher is not None:
            self._watcher.stop()
            self._watcher = None
        if self._timer is not None:
            self._timer.stop()
            self._timer = None

    def do_handle(self, *args):
        spawn = self._spawn
        if spawn is None:
            self._handle(*args)
        else:
            spawn(self._handle, *args)

    def _do_read(self):
        for _ in xrange(self.max_accept):
            if self.full():
                self.stop_accepting()
                return
            try:
                args = self.do_read()
                self.delay = self.min_delay
                if not args:
                    return
            except:
                self.loop.handle_error(self, *sys.exc_info())
                ex = sys.exc_info()[1]
                if self.is_fatal_error(ex):
                    self.close()
                    sys.stderr.write('ERROR: %s failed with %s\n' %
                                     (self, str(ex) or repr(ex)))
                    return
                if self.delay >= 0:
                    self.stop_accepting()
                    self._timer = self.loop.timer(self.delay)
                    self._timer.start(self._start_accepting_if_started)
                    self.delay = min(self.max_delay, self.delay * 2)
                break
            else:
                try:
                    self.do_handle(*args)
                except:
                    self.loop.handle_error((args[1:], self), *sys.exc_info())
                    if self.delay >= 0:
                        self.stop_accepting()
                        self._timer = self.loop.timer(self.delay)
                        self._timer.start(self._start_accepting_if_started)
                        self.delay = min(self.max_delay, self.delay * 2)
                    break

    def full(self):
        return False

    def __repr__(self):
        return '<%s at %s %s>' % (type(self).__name__, hex(
            id(self)), self._formatinfo())

    def __str__(self):
        return '<%s %s>' % (type(self).__name__, self._formatinfo())

    def _formatinfo(self):
        if hasattr(self, 'socket'):
            try:
                fileno = self.socket.fileno()
            except Exception:
                ex = sys.exc_info()[1]
                fileno = str(ex)
            result = 'fileno=%s ' % fileno
        else:
            result = ''
        try:
            if isinstance(self.address, tuple) and len(self.address) == 2:
                result += 'address=%s:%s' % self.address
            else:
                result += 'address=%s' % (self.address, )
        except Exception:
            ex = sys.exc_info()[1]
            result += str(ex) or '<error>'
        try:
            handle = getfuncname(self.__dict__['handle'])
        except Exception:
            handle = None
        if handle is not None:
            result += ' handle=' + handle
        return result

    @property
    def server_host(self):
        """IP address that the server is bound to (string)."""
        if isinstance(self.address, tuple):
            return self.address[0]

    @property
    def server_port(self):
        """Port that the server is bound to (an integer)."""
        if isinstance(self.address, tuple):
            return self.address[1]

    def init_socket(self):
        """If the user initialized the server with an address rather than socket,
        then this function will create a socket, bind it and put it into listening mode.

        It is not supposed to be called by the user, it is called by :meth:`start` before starting
        the accept loop."""
        pass

    @property
    def started(self):
        return not self._stop_event.is_set()

    def start(self):
        """Start accepting the connections.

        If an address was provided in the constructor, then also create a socket,
        bind it and put it into the listening mode.
        """
        self.init_socket()
        self._stop_event.clear()
        try:
            self.start_accepting()
        except:
            self.kill()
            raise

    def close(self):
        """Close the listener socket and stop accepting."""
        self._stop_event.set()
        try:
            self.stop_accepting()
        finally:
            try:
                self.socket.close()
            except Exception:
                pass
            finally:
                self.__dict__.pop('socket', None)
                self.__dict__.pop('handle', None)
                self.__dict__.pop('_handle', None)
                self.__dict__.pop('_spawn', None)
                self.__dict__.pop('full', None)
                if self.pool is not None:
                    self.pool._semaphore.unlink(
                        self._start_accepting_if_started)

    def stop(self, timeout=None):
        """Stop accepting the connections and close the listening socket.

        If the server uses a pool to spawn the requests, then :meth:`stop` also waits
        for all the handlers to exit. If there are still handlers executing after *timeout*
        has expired (default 1 second), then the currently running handlers in the pool are killed."""
        self.close()
        if timeout is None:
            timeout = self.stop_timeout
        if self.pool:
            self.pool.join(timeout=timeout)
            self.pool.kill(block=True, timeout=1)

    def serve_forever(self, stop_timeout=None):
        """Start the server if it hasn't been already started and wait until it's stopped."""
        # add test that serve_forever exists on stop()
        if not self.started:
            self.start()
        try:
            self._stop_event.wait()
        finally:
            gevent.spawn(self.stop, timeout=stop_timeout).join()

    def is_fatal_error(self, ex):
        return isinstance(ex, _socket.error) and ex[0] in self.fatal_errors
Esempio n. 51
0
class ClientBase(object):
    _buffer = b''
    _receivedFDs = []
    _toBeSentFDs = []

    _nextMsgLen = 0
    _endian = '<'

    _firstByte = True
    _unix_creds = None  # (pid, uid, gid) from UnixSocket credential passing
    authenticator = None  # Class to handle DBus authentication
    MAX_MSG_LENGTH = 2**27

    guid = None  # Filled in with the GUID of the server (for client protocol)

    # or the username of the authenticated client (for server protocol)

    def __init__(self, transport):
        self.rx_ev = Event()
        self.ret_val = None
        self._transport = transport

    def connect(self, target=None):
        # self._transport.connect(target)
        # DBus specification requires that clients send a null byte upon
        # connection to the bus
        self._transport.send(b'\0')
        # do auth
        # print "authenticating"
        ClientAuthenticator().authenticate(self)
        return self

    def write(self, data):
        # print msg.rawMessage
        # self._transport.send(b"GET / HTTP/1.0\r\n'")
        # print ">>> %r" % data
        self._transport.send(data)

    def read(self):
        # print msg.rawMessage
        # self._transport.send(b"GET / HTTP/1.0\r\n'")
        data = self._transport.recv(1024)
        # print "<<< %r" % data
        return data

    def await_result(self):
        try:
            if self.rx_ev.is_set():
                raise RuntimeError("already pending result")

            while not self.rx_ev.is_set():
                data = self.read()
                if not data:
                    raise Exception("ConnectionClosed")
                # print data
                self.on_data_received(data)

            return self.ret_val
        finally:
            self.rx_ev.clear()

    def on_data_received(self, data):
        self._buffer = self._buffer + data
        buffer_len = len(self._buffer)

        if self._nextMsgLen == 0 and buffer_len >= 16:
            # There would be multiple clients using different endians.
            # Reset endian every time.
            if self._buffer[:1] != b'l':
                self._endian = '>'
            else:
                self._endian = '<'

            body_len = struct.unpack(self._endian + 'I', self._buffer[4:8])[0]
            harr_len = struct.unpack(self._endian + 'I',
                                     self._buffer[12:16])[0]

            hlen = MSG_HDR_LEN + harr_len

            padlen = hlen % 8 and (8 - hlen % 8) or 0

            self._nextMsgLen = (MSG_HDR_LEN + harr_len + padlen + body_len)

        if self._nextMsgLen != 0 and buffer_len >= self._nextMsgLen:
            raw_msg = self._buffer[:self._nextMsgLen]
            self._buffer = self._buffer[self._nextMsgLen:]

            self._nextMsgLen = 0

            self.process_raw_dbus_message(raw_msg)

            if self._buffer:
                # Recursively process any other complete messages
                self.on_data_received(b'')

    def process_raw_dbus_message(self, rawMsg):
        """
        Called when the raw bytes for a complete DBus message are received

        @param rawMsg: Byte-string containing the complete message
        @type rawMsg: C{str}
        """
        m = message.parse_message(rawMsg, self._receivedFDs)
        mt = m._messageType

        self._receivedFDs = []

        if mt == 1:
            self.on_method_call_received(m)
        elif mt == 2:
            self.on_method_return_received(m)
        elif mt == 3:
            self.on_error_received(m)
        elif mt == 4:
            self.on_signal_received(m)

    def teardown(self):
        self._transport.close()

    def on_method_call_received(self, mcall):
        """
        Called when a DBus METHOD_CALL message is received
        """
        raise NotImplementedError

    def on_method_return_received(self, mret):
        """
        Called when a DBus METHOD_RETURN message is received
        """
        self.rx_ev.set()
        self.ret_val = mret

    def on_error_received(self, merr):
        """
        Called when a DBus ERROR message is received
        """
        e = RemoteError(merr.error_name)
        e.message = ''
        e.values = []
        if merr.body:
            if isinstance(merr.body[0], six.string_types):
                e.message = merr.body[0]
            e.values = merr.body
        raise e

    def on_signal_received(self, msig):
        """
        Called when a DBus METHOD_CALL message is received
        """
        # raise NotImplementedError
        print "signal", msig

    def on_connection_authenticated(self):
        print "authenticated!!"
Esempio n. 52
0
class RaidenService:
    """ A Raiden node. """

    def __init__(
        self,
        chain,
        default_registry,
        private_key_bin,
        transport,
        config,
        discovery=None
    ):
        if not isinstance(private_key_bin, bytes) or len(private_key_bin) != 32:
            raise ValueError('invalid private_key')

        invalid_timeout = (
            config['settle_timeout'] < NETTINGCHANNEL_SETTLE_TIMEOUT_MIN or
            config['settle_timeout'] > NETTINGCHANNEL_SETTLE_TIMEOUT_MAX
        )
        if invalid_timeout:
            raise ValueError('settle_timeout must be in range [{}, {}]'.format(
                NETTINGCHANNEL_SETTLE_TIMEOUT_MIN, NETTINGCHANNEL_SETTLE_TIMEOUT_MAX
            ))

        self.tokens_to_connectionmanagers = dict()
        self.identifier_to_results = defaultdict(list)

        # This is a map from a secrethash to a list of channels, the same
        # secrethash can be used in more than one token (for tokenswaps), a
        # channel should be removed from this list only when the lock is
        # released/withdrawn but not when the secret is registered.
        self.token_to_secrethash_to_channels = defaultdict(lambda: defaultdict(list))

        self.chain = chain
        self.default_registry = default_registry
        self.config = config
        self.privkey = private_key_bin
        self.address = privatekey_to_address(private_key_bin)

        if config['transport_type'] == 'udp':
            endpoint_registration_event = gevent.spawn(
                discovery.register,
                self.address,
                config['external_ip'],
                config['external_port'],
            )
            endpoint_registration_event.link_exception(endpoint_registry_exception_handler)

        self.private_key = PrivateKey(private_key_bin)
        self.pubkey = self.private_key.public_key.format(compressed=False)
        self.protocol = transport

        self.blockchain_events = BlockchainEvents()
        self.alarm = AlarmTask(chain)
        self.shutdown_timeout = config['shutdown_timeout']
        self._block_number = None
        self.stop_event = Event()
        self.start_event = Event()
        self.chain.client.inject_stop_event(self.stop_event)

        self.wal = None

        self.database_path = config['database_path']
        if self.database_path != ':memory:':
            database_dir = os.path.dirname(config['database_path'])
            os.makedirs(database_dir, exist_ok=True)

            self.database_dir = database_dir
            # Prevent concurrent acces to the same db
            self.lock_file = os.path.join(self.database_dir, '.lock')
            self.db_lock = filelock.FileLock(self.lock_file)
        else:
            self.database_path = ':memory:'
            self.database_dir = None
            self.lock_file = None
            self.serialization_file = None
            self.db_lock = None

        if config['transport_type'] == 'udp':
            # If the endpoint registration fails the node will quit, this must
            # finish before starting the protocol
            endpoint_registration_event.join()

        # Lock used to serialize calls to `poll_blockchain_events`, this is
        # important to give a consistent view of the node state.
        self.event_poll_lock = gevent.lock.Semaphore()

        self.start()

    def start(self):
        """ Start the node. """
        if self.stop_event and self.stop_event.is_set():
            self.stop_event.clear()

        if self.database_dir is not None:
            self.db_lock.acquire(timeout=0)
            assert self.db_lock.is_locked

        # The database may be :memory:
        storage = sqlite.SQLiteStorage(self.database_path, serialize.PickleSerializer())
        self.wal, unapplied_events = wal.restore_from_latest_snapshot(
            node.state_transition,
            storage,
        )

        last_log_block_number = None
        # First run, initialize the basic state
        if self.wal.state_manager.current_state is None:
            block_number = self.chain.block_number()

            state_change = ActionInitNode(
                random.Random(),
                block_number,
            )
            self.wal.log_and_dispatch(state_change, block_number)
        else:
            # Get the last known block number after reapplying all the state changes from the log
            last_log_block_number = views.block_number(self.wal.state_manager.current_state)

        # The alarm task must be started after the snapshot is loaded or the
        # state is primed, the callbacks assume the node is initialized.
        self.alarm.start()
        self.alarm.register_callback(self.poll_blockchain_events)
        self.alarm.register_callback(self.set_block_number)
        self._block_number = self.chain.block_number()

        # Registry registration must start *after* the alarm task. This
        # avoids corner cases where the registry is queried in block A, a new
        # block B is mined, and the alarm starts polling at block C.

        # If last_log_block_number is None, the wal.state_manager.current_state was
        # None in the log, meaning we don't have any events we care about, so just
        # read the latest state from the network
        self.register_payment_network(self.default_registry.address, last_log_block_number)

        # Start the protocol after the registry is queried to avoid warning
        # about unknown channels.
        queueids_to_queues = views.get_all_messagequeues(views.state_from_raiden(self))

        # TODO: remove the cyclic dependency between the protocol and this instance
        self.protocol.start(self, queueids_to_queues)

        # Health check needs the protocol layer
        self.start_neighbours_healthcheck()

        for event in unapplied_events:
            on_raiden_event(self, event)

        self.start_event.set()

    def start_neighbours_healthcheck(self):
        for neighbour in views.all_neighbour_nodes(self.wal.state_manager.current_state):
            if neighbour != ConnectionManager.BOOTSTRAP_ADDR:
                self.start_health_check_for(neighbour)

    def stop(self):
        """ Stop the node. """
        # Needs to come before any greenlets joining
        self.stop_event.set()
        self.protocol.stop_and_wait()
        self.alarm.stop_async()

        wait_for = [self.alarm]
        wait_for.extend(getattr(self.protocol, 'greenlets', []))
        # We need a timeout to prevent an endless loop from trying to
        # contact the disconnected client
        gevent.wait(wait_for, timeout=self.shutdown_timeout)

        # Filters must be uninstalled after the alarm task has stopped. Since
        # the events are polled by an alarm task callback, if the filters are
        # uninstalled before the alarm task is fully stopped the callback
        # `poll_blockchain_events` will fail.
        #
        # We need a timeout to prevent an endless loop from trying to
        # contact the disconnected client
        try:
            with gevent.Timeout(self.shutdown_timeout):
                self.blockchain_events.uninstall_all_event_listeners()
        except (gevent.timeout.Timeout, RaidenShuttingDown):
            pass

        if self.db_lock is not None:
            self.db_lock.release()

    def __repr__(self):
        return '<{} {}>'.format(self.__class__.__name__, pex(self.address))

    def set_block_number(self, block_number):
        state_change = Block(block_number)
        self.handle_state_change(state_change, block_number)

        # To avoid races, only update the internal cache after all the state
        # tasks have been updated.
        self._block_number = block_number

    def handle_state_change(self, state_change, block_number=None):
        is_logging = log.isEnabledFor(logging.DEBUG)

        if is_logging:
            log.debug('STATE CHANGE', node=pex(self.address), state_change=state_change)

        if block_number is None:
            block_number = self.get_block_number()

        event_list = self.wal.log_and_dispatch(state_change, block_number)

        for event in event_list:
            if is_logging:
                log.debug('EVENT', node=pex(self.address), event=event)

            on_raiden_event(self, event)

        return event_list

    def set_node_network_state(self, node_address, network_state):
        state_change = ActionChangeNodeNetworkState(node_address, network_state)
        self.wal.log_and_dispatch(state_change, self.get_block_number())

    def start_health_check_for(self, node_address):
        self.protocol.start_health_check(node_address)

    def get_block_number(self):
        return views.block_number(self.wal.state_manager.current_state)

    def poll_blockchain_events(self, current_block=None):  # pylint: disable=unused-argument
        with self.event_poll_lock:
            for event in self.blockchain_events.poll_blockchain_events():
                on_blockchain_event(self, event)

    def sign(self, message):
        """ Sign message inplace. """
        if not isinstance(message, SignedMessage):
            raise ValueError('{} is not signable.'.format(repr(message)))

        message.sign(self.private_key, self.address)

    def register_payment_network(self, registry_address, from_block=None):
        proxies = get_relevant_proxies(
            self.chain,
            self.address,
            registry_address,
        )

        # Install the filters first to avoid missing changes, as a consequence
        # some events might be applied twice.
        self.blockchain_events.add_proxies_listeners(proxies, from_block)

        token_network_list = list()
        for manager in proxies.channel_managers:
            manager_address = manager.address
            netting_channel_proxies = proxies.channelmanager_nettingchannels[manager_address]
            network = get_token_network_state_from_proxies(self, manager, netting_channel_proxies)
            token_network_list.append(network)

        payment_network = PaymentNetworkState(
            registry_address,
            token_network_list,
        )

        state_change = ContractReceiveNewPaymentNetwork(payment_network)
        self.handle_state_change(state_change)

    def connection_manager_for_token(self, registry_address, token_address):
        if not isaddress(token_address):
            raise InvalidAddress('token address is not valid.')

        known_token_networks = views.get_token_network_addresses_for(
            self.wal.state_manager.current_state,
            registry_address,
        )

        if token_address not in known_token_networks:
            raise InvalidAddress('token is not registered.')

        manager = self.tokens_to_connectionmanagers.get(token_address)

        if manager is None:
            manager = ConnectionManager(self, registry_address, token_address)
            self.tokens_to_connectionmanagers[token_address] = manager

        return manager

    def leave_all_token_networks(self):
        state_change = ActionLeaveAllNetworks()
        self.wal.log_and_dispatch(state_change, self.get_block_number())

    def close_and_settle(self):
        log.info('raiden will close and settle all channels now')

        self.leave_all_token_networks()

        connection_managers = [
            self.tokens_to_connectionmanagers[token_address]
            for token_address in self.tokens_to_connectionmanagers
        ]

        if connection_managers:
            waiting.wait_for_settle_all_channels(
                self,
                self.alarm.wait_time,
            )

    def mediated_transfer_async(
            self,
            registry_address,
            token_address,
            amount,
            target,
            identifier,
    ):
        """ Transfer `amount` between this node and `target`.

        This method will start an asyncronous transfer, the transfer might fail
        or succeed depending on a couple of factors:

            - Existence of a path that can be used, through the usage of direct
              or intermediary channels.
            - Network speed, making the transfer sufficiently fast so it doesn't
              expire.
        """

        async_result = self.start_mediated_transfer(
            registry_address,
            token_address,
            amount,
            target,
            identifier,
        )

        return async_result

    def direct_transfer_async(self, registry_address, token_address, amount, target, identifier):
        """ Do a direct transfer with target.

        Direct transfers are non cancellable and non expirable, since these
        transfers are a signed balance proof with the transferred amount
        incremented.

        Because the transfer is non cancellable, there is a level of trust with
        the target. After the message is sent the target is effectively paid
        and then it is not possible to revert.

        The async result will be set to False iff there is no direct channel
        with the target or the payer does not have balance to complete the
        transfer, otherwise because the transfer is non expirable the async
        result *will never be set to False* and if the message is sent it will
        hang until the target node acknowledge the message.

        This transfer should be used as an optimization, since only two packets
        are required to complete the transfer (from the payers perspective),
        whereas the mediated transfer requires 6 messages.
        """

        self.protocol.start_health_check(target)

        if identifier is None:
            identifier = create_default_identifier()

        direct_transfer = ActionTransferDirect(
            registry_address,
            token_address,
            target,
            identifier,
            amount,
        )

        self.handle_state_change(direct_transfer)

    def start_mediated_transfer(
            self,
            registry_address,
            token_address,
            amount,
            target,
            identifier,
    ):

        self.protocol.start_health_check(target)

        if identifier is None:
            identifier = create_default_identifier()

        assert identifier not in self.identifier_to_results

        async_result = AsyncResult()
        self.identifier_to_results[identifier].append(async_result)

        secret = random_secret()
        init_initiator_statechange = initiator_init(
            self,
            identifier,
            amount,
            secret,
            registry_address,
            token_address,
            target,
        )

        # TODO: implement the network timeout raiden.config['msg_timeout'] and
        # cancel the current transfer if it happens (issue #374)
        #
        # Dispatch the state change even if there are no routes to create the
        # wal entry.
        self.handle_state_change(init_initiator_statechange)

        return async_result

    def mediate_mediated_transfer(self, transfer: LockedTransfer):
        init_mediator_statechange = mediator_init(self, transfer)
        self.handle_state_change(init_mediator_statechange)

    def target_mediated_transfer(self, transfer: LockedTransfer):
        init_target_statechange = target_init(self, transfer)
        self.handle_state_change(init_target_statechange)
Esempio n. 53
0
class ReplayProcess(BaseReplayProcess):
    '''
    ReplayProcess - A process spawned for the purpose of replaying data
    --------------------------------------------------------------------------------
    Configurations
    ==============
    process:
      dataset_id:      ""     # Dataset to be replayed
      delivery_format: {}     # Delivery format to be replayed back (unused for now)
      query:
        start_time: 0         # Start time (index value) to be replayed
        end_time:   0         # End time (index value) to be replayed
        parameters: []        # List of parameters to form in the granule
      

    '''
    process_type = 'standalone'
    publish_limit = 10
    dataset_id = None
    delivery_format = {}
    start_time = None
    end_time = None
    stride_time = None
    parameters = None
    stream_id = ''
    stream_def_id = ''

    def __init__(self, *args, **kwargs):
        super(ReplayProcess, self).__init__(*args, **kwargs)
        self.deserializer = IonObjectDeserializer(
            obj_registry=get_obj_registry())
        self.publishing = Event()
        self.play = Event()
        self.end = Event()

    def on_start(self):
        '''
        Starts the process
        '''
        log.info('Replay Process Started')
        super(ReplayProcess, self).on_start()
        dsm_cli = DatasetManagementServiceProcessClient(process=self)
        pubsub = PubsubManagementServiceProcessClient(process=self)

        self.dataset_id = self.CFG.get_safe('process.dataset_id', None)
        self.delivery_format = self.CFG.get_safe('process.delivery_format', {})
        self.start_time = self.CFG.get_safe('process.query.start_time', None)
        self.end_time = self.CFG.get_safe('process.query.end_time', None)
        self.stride_time = self.CFG.get_safe('process.query.stride_time', None)
        self.parameters = self.CFG.get_safe('process.query.parameters', None)
        self.publish_limit = self.CFG.get_safe('process.query.publish_limit',
                                               10)
        self.tdoa = self.CFG.get_safe('process.query.tdoa', None)
        self.stream_id = self.CFG.get_safe('process.publish_streams.output',
                                           '')
        self.stream_def = pubsub.read_stream_definition(
            stream_id=self.stream_id)
        self.stream_def_id = self.stream_def._id

        self.publishing.clear()
        self.play.set()
        self.end.clear()

        if self.dataset_id is None:
            raise BadRequest('dataset_id not specified')

        self.dataset = dsm_cli.read_dataset(self.dataset_id)
        self.pubsub = PubsubManagementServiceProcessClient(process=self)

    @classmethod
    def get_time_idx(cls, coverage, timeval):
        temporal_variable = coverage.temporal_parameter_name
        uom = coverage.get_parameter_context(temporal_variable).uom

        units = TimeUtils.ts_to_units(uom, timeval)

        idx = TimeUtils.get_relative_time(coverage, units)
        return idx

    @classmethod
    def _coverage_to_granule(cls,
                             coverage,
                             start_time=None,
                             end_time=None,
                             stride_time=None,
                             fuzzy_stride=True,
                             parameters=None,
                             stream_def_id=None,
                             tdoa=None):
        slice_ = slice(None)  # Defaults to all values

        # Validations
        if start_time is not None:
            validate_is_instance(start_time, Number,
                                 'start_time must be a number for striding.')
        if end_time is not None:
            validate_is_instance(end_time, Number,
                                 'end_time must be a number for striding.')
        if stride_time is not None:
            validate_is_instance(stride_time, Number,
                                 'stride_time must be a number for striding.')

        if tdoa is not None and isinstance(tdoa, slice):
            slice_ = tdoa

        elif stride_time is not None and not fuzzy_stride:  # SLOW
            ugly_range = np.arange(start_time, end_time, stride_time)
            idx_values = [cls.get_time_idx(coverage, i) for i in ugly_range]
            idx_values = list(
                set(idx_values)
            )  # Removing duplicates - also mixes the order of the list!!!
            idx_values.sort()
            slice_ = [idx_values]

        elif not (start_time is None and end_time is None):
            if start_time is not None:
                start_time = cls.get_time_idx(coverage, start_time)
            if end_time is not None:
                end_time = cls.get_time_idx(coverage, end_time)

            slice_ = slice(start_time, end_time, stride_time)
            log.info('Slice: %s', slice_)

        if stream_def_id:
            rdt = RecordDictionaryTool(stream_definition_id=stream_def_id)
        else:
            rdt = RecordDictionaryTool(
                param_dictionary=coverage.parameter_dictionary)
        if parameters is not None:
            # TODO: Improve efficiency here
            fields = list(set(parameters).intersection(rdt.fields))
        else:
            fields = rdt.fields

        if slice_.start == slice_.stop and slice_.start is not None:
            log.warning('Requested empty set of data.  %s', slice_)
            return rdt

        # Do time first
        tname = coverage.temporal_parameter_name
        cls.map_cov_rdt(coverage, rdt, tname, slice_)

        for field in fields:
            if field == tname:
                continue
            cls.map_cov_rdt(coverage, rdt, field, slice_)
        return rdt

    @classmethod
    def map_cov_rdt(cls, coverage, rdt, field, slice_):
        log.trace('Slice is %s', slice_)
        try:
            n = coverage.get_parameter_values(field, tdoa=slice_)
        except ParameterFunctionException:
            return
        if n is None:
            rdt[field] = [n]
        elif isinstance(n, np.ndarray):
            if coverage.get_data_extents(field)[0] < coverage.num_timesteps:
                log.error(
                    "Misformed coverage detected, padding with fill_value")
                arr_len = utils.slice_shape(slice_,
                                            (coverage.num_timesteps, ))[0]
                fill_arr = np.empty(arr_len - n.shape[0], dtype=n.dtype)
                fill_arr.fill(coverage.get_parameter_context(field).fill_value)
                n = np.append(n, fill_arr)
            elif coverage.get_data_extents(field)[0] > coverage.num_timesteps:
                raise CorruptionError(
                    'The coverage is corrupted:\n\tfield: %s\n\textents: %s\n\ttimesteps: %s'
                    % (field, coverage.get_data_extents(field),
                       coverage.num_timesteps))
            rdt[field] = np.atleast_1d(n)
        else:
            rdt[field] = [n]

    def execute_retrieve(self):
        '''
        execute_retrieve Executes a retrieval and returns the result 
        as a value in lieu of publishing it on a stream
        '''
        try:
            coverage = DatasetManagementService._get_coverage(self.dataset_id,
                                                              mode='r')
            if coverage.num_timesteps == 0:
                log.info('Reading from an empty coverage')
                rdt = RecordDictionaryTool(
                    param_dictionary=coverage.parameter_dictionary)
            else:
                rdt = self._coverage_to_granule(coverage=coverage,
                                                start_time=self.start_time,
                                                end_time=self.end_time,
                                                stride_time=self.stride_time,
                                                parameters=self.parameters,
                                                tdoa=self.tdoa)
        except:
            log.exception('Problems reading from the coverage')
            raise BadRequest('Problems reading from the coverage')
        finally:
            coverage.close(timeout=5)
        return rdt.to_granule()

    def execute_replay(self):
        '''
        execute_replay Performs a replay and publishes the results on a stream. 
        '''
        if self.publishing.is_set():
            return False
        gevent.spawn(self.replay)
        return True

    def replay(self):
        self.publishing.set(
        )  # Minimal state, supposed to prevent two instances of the same process from replaying on the same stream
        for rdt in self._replay():
            if self.end.is_set():
                return
            self.play.wait()
            self.output.publish(rdt.to_granule())

        self.publishing.clear()
        return

    def pause(self):
        self.play.clear()

    def resume(self):
        self.play.set()

    def stop(self):
        self.end.set()

    @classmethod
    def get_last_values(cls, dataset_id, number_of_points, delivery_format):
        coverage = DatasetManagementService._get_coverage(dataset_id, mode='r')
        if coverage.num_timesteps < number_of_points:
            if coverage.num_timesteps == 0:
                rdt = RecordDictionaryTool(
                    param_dictionary=coverage.parameter_dictionary)
                return rdt.to_granule()
            number_of_points = coverage.num_timesteps
        rdt = cls._coverage_to_granule(coverage,
                                       tdoa=slice(-number_of_points, None),
                                       stream_def_id=delivery_format)
        coverage.close(timeout=5)

        return rdt.to_granule()

    def _replay(self):
        coverage = DatasetManagementService._get_coverage(self.dataset_id,
                                                          mode='r')
        rdt = self._coverage_to_granule(coverage=coverage,
                                        start_time=self.start_time,
                                        end_time=self.end_time,
                                        stride_time=self.stride_time,
                                        parameters=self.parameters,
                                        stream_def_id=self.stream_def_id)
        elements = len(rdt)

        for i in xrange(elements / self.publish_limit):
            outgoing = RecordDictionaryTool(
                stream_definition_id=self.stream_def_id)
            fields = self.parameters or outgoing.fields
            for field in fields:
                v = rdt[field]
                if v is not None:
                    outgoing[field] = v[(
                        i * self.publish_limit):((i + 1) * self.publish_limit)]
            yield outgoing
        coverage.close(timeout=5)
        return
Esempio n. 54
0
class Group(GroupMappingMixin):
    """Maintain a group of greenlets that are still running.

    Links to each item and removes it upon notification.
    """
    greenlet_class = Greenlet

    def __init__(self, *args):
        assert len(args) <= 1, args
        self.greenlets = set(*args)
        if args:
            for greenlet in args[0]:
                greenlet.rawlink(self._discard)
        # each item we kill we place in dying, to avoid killing the same greenlet twice
        self.dying = set()
        self._empty_event = Event()
        self._empty_event.set()

    def __repr__(self):
        return '<%s at 0x%x %s>' % (self.__class__.__name__, id(self),
                                    self.greenlets)

    def __len__(self):
        return len(self.greenlets)

    def __contains__(self, item):
        return item in self.greenlets

    def __iter__(self):
        return iter(self.greenlets)

    def add(self, greenlet):
        try:
            rawlink = greenlet.rawlink
        except AttributeError:
            pass  # non-Greenlet greenlet, like MAIN
        else:
            rawlink(self._discard)
        self.greenlets.add(greenlet)
        self._empty_event.clear()

    def _discard(self, greenlet):
        self.greenlets.discard(greenlet)
        self.dying.discard(greenlet)
        if not self.greenlets:
            self._empty_event.set()

    def discard(self, greenlet):
        self._discard(greenlet)
        try:
            unlink = greenlet.unlink
        except AttributeError:
            pass  # non-Greenlet greenlet, like MAIN
        else:
            unlink(self._discard)

    def start(self, greenlet):
        """
        Start the un-started *greenlet* and add it to the collection of greenlets
        this group is monitoring.
        """
        self.add(greenlet)
        greenlet.start()

    def spawn(self, *args, **kwargs):
        """
        Begin a new greenlet with the given arguments (which are passed
        to the greenlet constructor) and add it to the collection of greenlets
        this group is monitoring.

        :return: The newly started greenlet.
        """
        greenlet = self.greenlet_class(*args, **kwargs)
        self.start(greenlet)
        return greenlet


#     def close(self):
#         """Prevents any more tasks from being submitted to the pool"""
#         self.add = RaiseException("This %s has been closed" % self.__class__.__name__)

    def join(self, timeout=None, raise_error=False):
        if raise_error:
            greenlets = self.greenlets.copy()
            self._empty_event.wait(timeout=timeout)
            for greenlet in greenlets:
                if greenlet.exception is not None:
                    if hasattr(greenlet, '_raise_exception'):
                        greenlet._raise_exception()
                    raise greenlet.exception
        else:
            self._empty_event.wait(timeout=timeout)

    def kill(self, exception=GreenletExit, block=True, timeout=None):
        timer = Timeout.start_new(timeout)
        try:
            try:
                while self.greenlets:
                    for greenlet in list(self.greenlets):
                        if greenlet not in self.dying:
                            try:
                                kill = greenlet.kill
                            except AttributeError:
                                _kill(greenlet, exception)
                            else:
                                kill(exception, block=False)
                            self.dying.add(greenlet)
                    if not block:
                        break
                    joinall(self.greenlets)
            except Timeout as ex:
                if ex is not timer:
                    raise
        finally:
            timer.cancel()

    def killone(self,
                greenlet,
                exception=GreenletExit,
                block=True,
                timeout=None):
        if greenlet not in self.dying and greenlet in self.greenlets:
            greenlet.kill(exception, block=False)
            self.dying.add(greenlet)
            if block:
                greenlet.join(timeout)

    def full(self):
        return False

    def wait_available(self):
        pass

    # MappingMixin methods

    def _apply_immediately(self):
        # If apply() is called from one of our own
        # worker greenlets, don't spawn a new one
        return getcurrent() in self

    def _apply_async_cb_spawn(self, callback, result):
        Greenlet.spawn(callback, result)

    def _apply_async_use_greenlet(self):
        return self.full()  # cannot call self.spawn() because it will block
Esempio n. 55
0
class Console(BaseService):
    """A service starting an interactive ipython session when receiving the
    SIGSTP signal (e.g. via keyboard shortcut CTRL-Z).
    """

    name = 'console'

    def __init__(self, app):
        super(Console, self).__init__(app)
        self.interrupt = Event()
        self.console_locals = {}
        if app.start_console:
            self.start()
            self.interrupt.set()
        else:
            SigINTHandler(self.interrupt)

    def start(self):
        # start console service
        super(Console, self).start()

        class Raiden(object):
            def __init__(self, app):
                self.app = app

        self.console_locals = dict(
            _raiden=Raiden(self.app),
            raiden=self.app.raiden,
            chain=self.app.raiden.chain,
            discovery=self.app.discovery,
            tools=ConsoleTools(
                self.app.raiden,
                self.app.discovery,
                self.app.config['settle_timeout'],
                self.app.config['reveal_timeout'],
            ),
            denoms=denoms,
            true=True,
            false=False,
            usage=print_usage,
        )

    def _run(self):
        self.interrupt.wait()
        print('\n' * 2)
        print("Entering Console" + bc.OKGREEN)
        print("Tip:" + bc.OKBLUE)
        print_usage()

        # Remove handlers that log to stderr
        root = getLogger()
        for handler in root.handlers[:]:
            if isinstance(handler,
                          StreamHandler) and handler.stream == sys.stderr:
                root.removeHandler(handler)

        stream = cStringIO.StringIO()
        handler = StreamHandler(stream=stream)
        handler.formatter = Formatter("%(levelname)s:%(name)s %(message)s")
        root.addHandler(handler)

        def lastlog(n=10, prefix=None, level=None):
            """Print the last `n` log lines to stdout.
            Use `prefix='p2p'` to filter for a specific logger.
            Use `level=INFO` to filter for a specific level.
            Level- and prefix-filtering are applied before tailing the log.
            """
            lines = (stream.getvalue().strip().split('\n') or [])
            if prefix:
                lines = [
                    line for line in lines
                    if line.split(':')[1].startswith(prefix)
                ]
            if level:
                lines = [line for line in lines if line.split(':')[0] == level]
            for line in lines[-n:]:
                print(line)

        self.console_locals['lastlog'] = lastlog

        err = cStringIO.StringIO()
        sys.stderr = err

        def lasterr(n=1):
            """Print the last `n` entries of stderr to stdout.
            """
            for line in (err.getvalue().strip().split('\n') or [])[-n:]:
                print(line)

        self.console_locals['lasterr'] = lasterr

        IPython.start_ipython(argv=['--gui', 'gevent'],
                              user_ns=self.console_locals)
        self.interrupt.clear()

        sys.exit(0)
Esempio n. 56
0
class AceClient(object):
    def __init__(self, host, port, connect_timeout=5, result_timeout=10):
        # Receive buffer
        self._recvbuffer = None
        # Stream URL
        self._url = None
        # Ace stream socket
        self._socket = None
        # Result timeout
        self._resulttimeout = result_timeout
        # Shutting down flag
        self._shuttingDown = Event()
        # Product key
        self._product_key = None
        # Current STATUS
        self._status = None
        # Current STATE
        self._state = None
        # Current video position
        self._position = None
        # Available video position (loaded data)
        self._position_last = None
        # Buffered video pieces
        self._position_buf = None
        # Current AUTH
        self._auth = None
        self._gender = None
        self._age = None
        # Result (Created with AsyncResult() on call)
        self._result = AsyncResult()
        self._authevent = Event()
        # Result for getURL()
        self._urlresult = AsyncResult()
        # Result for GETCID()
        self._cidresult = AsyncResult()
        # Event for resuming from PAUSE
        self._resumeevent = Event()
        # Seekback seconds.
        self._seekback = 0
        # Did we get START command again? For seekback.
        self._started_again = False

        self._idleSince = time.time()
        self._lock = threading.Condition(threading.Lock())
        self._streamReaderConnection = None
        self._streamReaderState = None
        self._streamReaderQueue = deque()
        self._engine_version_code = 0

        # Logger
        logger = logging.getLogger('AceClieimport tracebacknt_init')

        try:
            self._socket = telnetlib.Telnet(host, port, connect_timeout)
            logger.info("Successfully connected with Ace!")
        except Exception as e:
            raise AceException("Socket creation error! Ace is not running? " +
                               repr(e))

        # Spawning recvData greenlet
        gevent.spawn(self._recvData)
        gevent.sleep()

    def __del__(self):
        # Destructor just calls destroy() method
        self.destroy()

    def destroy(self):
        '''
        AceClient Destructor
        '''
        if self._shuttingDown.isSet():
            # Already in the middle of destroying
            return

        # Logger
        logger = logging.getLogger("AceClient_destroy")
        # We should resume video to prevent read greenlet deadlock
        self._resumeevent.set()
        # And to prevent getUrl deadlock
        self._urlresult.set()

        # Trying to disconnect
        try:
            logger.debug("Destroying client...")
            self._shuttingDown.set()
            self._write(AceMessage.request.SHUTDOWN)
        except:
            # Ignore exceptions on destroy
            pass
        finally:
            self._shuttingDown.set()

    def reset(self):
        self._started_again = False
        self._idleSince = time.time()
        self._streamReaderState = None

    def _write(self, message):
        try:
            logger = logging.getLogger("AceClient_write")
            logger.debug(message)
            self._socket.write(message + "\r\n")
        except EOFError as e:
            raise AceException("Write error! " + repr(e))

    def aceInit(self,
                gender=AceConst.SEX_MALE,
                age=AceConst.AGE_18_24,
                product_key=None,
                pause_delay=0,
                seekback=0):
        self._product_key = product_key
        self._gender = gender
        self._age = age
        # PAUSE/RESUME delay
        self._pausedelay = pause_delay
        # Seekback seconds
        self._seekback = seekback

        # Logger
        logger = logging.getLogger("AceClient_aceInit")

        # Sending HELLO
        self._write(AceMessage.request.HELLO)
        if not self._authevent.wait(self._resulttimeout):
            errmsg = "Authentication timeout. Wrong key?"
            logger.error(errmsg)
            raise AceException(errmsg)
            return

        if not self._auth:
            errmsg = "Authentication error. Wrong key?"
            logger.error(errmsg)
            raise AceException(errmsg)
            return

        logger.debug("aceInit ended")

    def _getResult(self):
        # Logger
        try:
            result = self._result.get(timeout=self._resulttimeout)
            if not result:
                raise AceException("Result not received")
        except gevent.Timeout:
            raise AceException("Timeout")

        return result

    def START(self, datatype, value):
        '''
        Start video method
        '''
        stream_type = 'output_format=http' if self._engine_version_code >= 3010500 and not AceConfig.vlcuse else ''
        self._urlresult = AsyncResult()
        self._write(
            AceMessage.request.START(datatype.upper(), value, stream_type))
        self._getResult()

    def STOP(self):
        '''
        Stop video method
        '''
        if self._state is not None and self._state != '0':
            self._result = AsyncResult()
            self._write(AceMessage.request.STOP)
            self._getResult()

    def LOADASYNC(self, datatype, url):
        self._result = AsyncResult()
        self._write(
            AceMessage.request.LOADASYNC(datatype.upper(), 0, {'url': url}))
        return self._getResult()

    def GETCID(self, datatype, url):
        contentinfo = self.LOADASYNC(datatype, url)
        self._cidresult = AsyncResult()
        self._write(
            AceMessage.request.GETCID(contentinfo.get('checksum'),
                                      contentinfo.get('infohash'), 0, 0, 0))
        cid = self._cidresult.get(True, 5)
        return '' if not cid or cid == '' else cid[2:]

    def GETCONTENTINFO(self, datatype, url):
        contentinfo = self.LOADASYNC(datatype, url)
        return contentinfo

    def getUrl(self, timeout=40):
        # Logger
        logger = logging.getLogger("AceClient_getURL")

        try:
            res = self._urlresult.get(timeout=timeout)
            return res
        except gevent.Timeout:
            errmsg = "getURL timeout!"
            logger.error(errmsg)
            raise AceException(errmsg)

    def startStreamReader(self, url, cid, counter):
        logger = logging.getLogger("StreamReader")
        self._streamReaderState = 1
        logger.debug("Opening video stream: %s" % url)

        try:
            connection = self._streamReaderConnection = urllib2.urlopen(url)

            if url.endswith('.m3u8'):
                logger.debug("Can't stream HLS in non VLC mode: %s" % url)
                return

            if connection.getcode() != 200:
                logger.error("Failed to open video stream %s" % connection)
                return

            with self._lock:
                self._streamReaderState = 2
                self._lock.notifyAll()

            while True:
                data = None
                clients = counter.getClients(cid)

                try:
                    data = connection.read(AceConfig.readchunksize)
                except:
                    break

                if data and clients:
                    with self._lock:
                        if len(self._streamReaderQueue
                               ) == AceConfig.readcachesize:
                            self._streamReaderQueue.popleft()
                        self._streamReaderQueue.append(data)

                    for c in clients:
                        try:
                            c.addChunk(data, 5.0)
                        except Queue.Full:
                            if len(clients) > 1:
                                logger.debug("Disconnecting client: %s" %
                                             str(c))
                                c.destroy()
                elif not clients:
                    logger.debug(
                        "All clients disconnected - closing video stream")
                    break
                else:
                    logger.warning("No data received")
                    break
        except urllib2.URLError:
            logger.error("Failed to open video stream")
            logger.error(traceback.format_exc())
        except:
            logger.error(traceback.format_exc())
            if counter.getClients(cid):
                logger.error("Failed to read video stream")
        finally:
            self.closeStreamReader()
            with self._lock:
                self._streamReaderState = 3
                self._lock.notifyAll()
            counter.deleteAll(cid)

    def closeStreamReader(self):
        logger = logging.getLogger("StreamReader")
        c = self._streamReaderConnection

        if c:
            self._streamReaderConnection = None
            c.close()
            logger.debug("Video stream closed")

        self._streamReaderQueue.clear()

    def getPlayEvent(self, timeout=None):
        '''
        Blocking while in PAUSE, non-blocking while in RESUME
        '''
        return self._resumeevent.wait(timeout=timeout)

    def pause(self):
        self._write(AceMessage.request.PAUSE)

    def play(self):
        self._write(AceMessage.request.PLAY)

    def _recvData(self):
        '''
        Data receiver method for greenlet
        '''
        logger = logging.getLogger('AceClient_recvdata')

        while True:
            gevent.sleep()
            try:
                self._recvbuffer = self._socket.read_until("\r\n")
                self._recvbuffer = self._recvbuffer.strip()
                # logger.debug('<<< ' + self._recvbuffer)
            except:
                # If something happened during read, abandon reader.
                if not self._shuttingDown.isSet():
                    logger.error("Exception at socket read")
                    self._shuttingDown.set()
                return

            if self._recvbuffer:
                # Parsing everything only if the string is not empty
                if self._recvbuffer.startswith(AceMessage.response.HELLO):
                    # Parse HELLO
                    if 'version_code=' in self._recvbuffer:
                        v = self._recvbuffer.find('version_code=')
                        self._engine_version_code = int(
                            self._recvbuffer[v + 13:v + 20])

                    if 'key=' in self._recvbuffer:
                        self._request_key_begin = self._recvbuffer.find('key=')
                        self._request_key = \
                            self._recvbuffer[self._request_key_begin + 4:self._request_key_begin + 14]
                        try:
                            self._write(
                                AceMessage.request.READY_key(
                                    self._request_key, self._product_key))
                        except urllib2.URLError as e:
                            logger.error("Can't connect to keygen server! " + \
                                repr(e))
                            self._auth = False
                            self._authevent.set()
                        self._request_key = None
                    else:
                        self._write(AceMessage.request.READY_nokey)

                elif self._recvbuffer.startswith(AceMessage.response.NOTREADY):
                    # NOTREADY
                    logger.error("Ace is not ready. Wrong auth?")
                    self._auth = False
                    self._authevent.set()

                elif self._recvbuffer.startswith(AceMessage.response.LOADRESP):
                    # LOADRESP
                    _contentinfo_raw = self._recvbuffer.split()[2:]
                    _contentinfo_raw = ' '.join(_contentinfo_raw)
                    _contentinfo = json.loads(_contentinfo_raw)
                    if _contentinfo.get('status') == 100:
                        logger.error(
                            "LOADASYNC returned error with message: %s" %
                            _contentinfo.get('message'))
                        self._result.set(False)
                    else:
                        logger.debug("Content info: %s", _contentinfo)
                        self._result.set(_contentinfo)

                elif self._recvbuffer.startswith(AceMessage.response.START):
                    # START
                    if not self._seekback or self._started_again or not self._recvbuffer.endswith(
                            ' stream=1'):
                        # If seekback is disabled, we use link in first START command.
                        # If seekback is enabled, we wait for first START command and
                        # ignore it, then do seeback in first EVENT position command
                        # AceStream sends us STOP and START again with new link.
                        # We use only second link then.
                        try:
                            self._url = self._recvbuffer.split()[1]
                            self._urlresult.set(self._url)
                            self._resumeevent.set()
                        except IndexError as e:
                            self._url = None
                    else:
                        logger.debug("START received. Waiting for %s." %
                                     AceMessage.response.LIVEPOS)

                elif self._recvbuffer.startswith(AceMessage.response.STOP):
                    pass

                elif self._recvbuffer.startswith(AceMessage.response.SHUTDOWN):
                    logger.debug("Got SHUTDOWN from engine")
                    self._socket.close()
                    return

                elif self._recvbuffer.startswith(AceMessage.response.AUTH):
                    try:
                        self._auth = self._recvbuffer.split()[1]
                        # Send USERDATA here
                        self._write(
                            AceMessage.request.USERDATA(
                                self._gender, self._age))
                    except:
                        pass
                    self._authevent.set()

                elif self._recvbuffer.startswith(
                        AceMessage.response.GETUSERDATA):
                    raise AceException("You should init me first!")

                elif self._recvbuffer.startswith(AceMessage.response.LIVEPOS):
                    self._position = self._recvbuffer.split()
                    self._position_last = self._position[2].split('=')[1]
                    self._position_buf = self._position[9].split('=')[1]
                    self._position = self._position[4].split('=')[1]

                    if self._seekback and not self._started_again:
                        self._write(AceMessage.request.SEEK(str(int(self._position_last) - \
                            self._seekback)))
                        logger.debug('Seeking back')
                        self._started_again = True

                elif self._recvbuffer.startswith(AceMessage.response.STATE):
                    self._state = self._recvbuffer.split()[1]

                elif self._recvbuffer.startswith(AceMessage.response.STATUS):
                    self._tempstatus = self._recvbuffer.split()[1].split(
                        ';')[0]
                    if self._tempstatus != self._status:
                        self._status = self._tempstatus
                        logger.debug("STATUS changed to " + self._status)

                    if self._status == 'main:err':
                        logger.error(self._status + ' with message ' +
                                     self._recvbuffer.split(';')[2])
                        self._result.set_exception(
                            AceException(self._status + ' with message ' +
                                         self._recvbuffer.split(';')[2]))
                        self._urlresult.set_exception(
                            AceException(self._status + ' with message ' +
                                         self._recvbuffer.split(';')[2]))
                    elif self._status == 'main:starting':
                        self._result.set(True)
                    elif self._status == 'main:idle':
                        self._result.set(True)

                elif self._recvbuffer.startswith(AceMessage.response.PAUSE):
                    logger.debug("PAUSE event")
                    self._resumeevent.clear()

                elif self._recvbuffer.startswith(AceMessage.response.RESUME):
                    logger.debug("RESUME event")
                    gevent.sleep(self._pausedelay)
                    self._resumeevent.set()

                elif self._recvbuffer.startswith('##') or len(
                        self._recvbuffer) == 0:
                    self._cidresult.set(self._recvbuffer)
                    logger.debug("CID: %s" % self._recvbuffer)

    def sendHeadersPT(self, client, code, headers):
        client.handler.send_response(code)
        for key, value in headers.items():
            client.handler.send_header(key, value)
        client.handler.end_headers()

    def openStreamReaderPT(self, url, req_headers):
        logger = logging.getLogger("openStreamReaderPT")
        logger.debug("Opening video stream: %s" % url)
        logger.debug("headers: %s" % req_headers)

        if url.endswith('.m3u8'):
            logger.debug("Can't stream HLS in non VLC mode: %s" % url)
            return None, None, None

        request = urllib2.Request(url, headers=req_headers)
        connection = self._streamReaderConnection = urllib2.urlopen(
            request, timeout=120)
        code = connection.getcode()

        if code not in (200, 206):
            logger.error("Failed to open video stream %s" % connection)
            return None, None, None

        FORWARD_HEADERS = [
            'Content-Range',
            'Connection',
            'Keep-Alive',
            'Content-Type',
            'Accept-Ranges',
            'X-Content-Duration',
            'Content-Length',
        ]
        SKIP_HEADERS = ['Server', 'Date']
        response_headers = {}
        for k in connection.info().headers:
            if k.split(':')[0] not in (FORWARD_HEADERS + SKIP_HEADERS):
                logger.debug('NEW HEADERS: %s' % k.split(':')[0])
        for h in FORWARD_HEADERS:
            if connection.info().getheader(h):
                response_headers[h] = connection.info().getheader(h)
                # self.connection.send_header(h, connection.info().getheader(h))
                logger.debug('key=%s value=%s' %
                             (h, connection.info().getheader(h)))

        with self._lock:
            self._streamReaderState = 2
            self._lock.notifyAll()

        return connection, code, response_headers

    def startStreamReaderPT(self, url, cid, counter, req_headers=None):
        logger = logging.getLogger("StreamReaderPT")
        self._streamReaderState = 1
        # current_req_headers = req_headers

        try:
            while True:
                data = None
                clients = counter.getClientsPT(cid)

                if not req_headers == self.req_headers:
                    self.req_headers = req_headers
                    connection, code, resp_headers = self.openStreamReaderPT(
                        url, req_headers)
                    if not connection:
                        return

                    for c in clients:
                        try:
                            c.headers_sent
                        except:
                            self.sendHeadersPT(c, code, resp_headers)
                            c.headers_sent = True

                # logger.debug("i")
                try:
                    data = connection.read(AceConfig.readchunksize)
                except:
                    break
                # logger.debug("d: %s c:%s" % (data, clients))
                if data and clients:
                    with self._lock:
                        if len(self._streamReaderQueue
                               ) == AceConfig.readcachesize:
                            self._streamReaderQueue.popleft()
                        self._streamReaderQueue.append(data)

                    for c in clients:
                        try:
                            c.addChunk(data, 5.0)
                        except Queue.Full:
                            if len(clients) > 1:
                                logger.debug("Disconnecting client: %s" %
                                             str(c))
                                c.destroy()
                elif not clients:
                    logger.debug(
                        "All clients disconnected - closing video stream")
                    break
                else:
                    logger.warning("No data received")
                    break
        except urllib2.URLError:
            logger.error("Failed to open video stream")
            logger.error(traceback.format_exc())
        except:
            logger.error(traceback.format_exc())
            if counter.getClientsPT(cid):
                logger.error("Failed to read video stream")
        finally:
            self.closeStreamReader()
            with self._lock:
                self._streamReaderState = 3
                self._lock.notifyAll()
            counter.deleteAll(cid)
Esempio n. 57
0
def run_(
    data_path: Path,
    auth: str,
    password: Optional[str],
    keystore_file: str,
    scenario_file: Path,
    enable_ui: bool,
    password_file: str,
    log_file_name: str,
    environment: EnvironmentConfig,
    delete_snapshots: bool,
    raiden_client: Optional[str],
    smoketest_deployment_data=None,
) -> None:
    """Execute a scenario as defined in scenario definition file.
    (Shared code for `run` and `smoketest` command).

    Calls :func:`exit` when done, with the following status codes:

        Exit code 1x
        There was a problem when starting up the SP, nodes, deploying tokens
        or setting up services. This points at an issue in the SP and of of its
        components.

        Exit code 2x
        There was an error when parsing or evaluating the given scenario definition file.
        This may be a syntax- or logic-related issue.

        Exit code 3x
        There was an assertion error while executing the scenario. This points
        to an error in a `raiden` component (the client, services or contracts).
    """
    log.info("Scenario Player version:", version_info=get_complete_spec())

    password = get_password(password, password_file)
    account = get_account(keystore_file, password)

    log_buffer = None
    if enable_ui:
        log_buffer = attach_urwid_logbuffer()

    # Dynamically import valid Task classes from scenario_player.tasks package.
    collect_tasks(tasks)

    # Start our Services
    report: Dict[str, str] = {}
    success = Event()
    success.clear()
    try:
        # We need to fix the log stream early in case the UI is active
        scenario_runner = ScenarioRunner(
            account=account,
            auth=auth,
            data_path=data_path,
            scenario_file=scenario_file,
            environment=environment,
            success=success,
            smoketest_deployment_data=smoketest_deployment_data,
            delete_snapshots=delete_snapshots,
            raiden_client=raiden_client,
        )
        if enable_ui:
            ui: AbstractContextManager = ScenarioUIManager(
                scenario_runner, log_buffer, log_file_name, success
            )
        else:
            ui = nullcontext()
        log.info("Startup complete")
        with ui:
            scenario_runner.run_scenario()
    except ScenarioAssertionError as ex:
        log.error("Run finished", result="assertion errors")
        if hasattr(ex, "exit_code"):
            exit_code = ex.exit_code
        else:
            exit_code = 30
        report.update(dict(subject=f"Assertion mismatch in {scenario_file.name}", message=str(ex)))
        exit(exit_code)
    except ScenarioError as ex:
        log.error("Run finished", result="scenario error", message=str(ex))
        if hasattr(ex, "exit_code"):
            exit_code = ex.exit_code
        else:
            exit_code = 20
        report.update(
            dict(
                subject=f"Invalid scenario {scenario_file.name}",
                message=traceback.format_exc(),
            )
        )
        exit(exit_code)
    except Exception as ex:
        log.exception("Exception while running scenario")
        if hasattr(ex, "exit_code"):
            exit_code = ex.exit_code  # type: ignore  # pylint: disable=no-member
        else:
            exit_code = 10
        report.update(
            dict(
                subject=f"Error running scenario {scenario_file.name}",
                message=traceback.format_exc(),
            )
        )
        exit(exit_code)
    else:
        exit_code = 0
        log.info("Run finished", result="success")
        report.update(dict(subject=f"Scenario successful {scenario_file.name}", message="Success"))
        log.info("Scenario player unwind complete")
        exit(exit_code)
Esempio n. 58
0
class AceClient(object):
    def __init__(self,
                 clientcounter,
                 ace,
                 connect_timeout=5,
                 result_timeout=10):
        # Telnet socket response buffer
        self._recvbuffer = None
        # AceEngine socket
        self._socket = None
        # AceEngine read result timeout
        self._resulttimeout = result_timeout
        # AceEngine product key
        self._product_key = None
        # Result (Created with AsyncResult() on call)
        self._auth = AsyncResult()
        # Result for START URL
        self._url = AsyncResult()
        # Response time from AceEngine to get URL or DATA
        self._videotimeout = None
        # Result for CID
        self._cid = AsyncResult()
        # Result fo LOADASYNC
        self._loadasync = AsyncResult()
        # Current STATUS
        self._status = AsyncResult()
        # Current EVENT
        self._event = AsyncResult()
        # Current STATE
        self._state = AsyncResult()
        # Current AUTH
        self._gender = self._age = None
        # Seekback seconds.
        self._seekback = None
        # Did we get START command again? For seekback.
        self._started_again = Event()
        # ClientCounter
        self._clientcounter = clientcounter
        # AceConfig.ace
        self._ace = ace

        try:
            self._socket = Telnet(self._ace['aceHostIP'],
                                  self._ace['aceAPIport'], connect_timeout)
            logging.debug('Successfully connected to AceStream on %s:%s' %
                          (self._ace['aceHostIP'], self._ace['aceAPIport']))
        except:
            errmsg = 'The are no alive AceStream Engines found!'
            raise AceException(errmsg)

    def destroy(self):
        '''
        AceClient Destructor
        '''
        # Send SHUTDOWN to AceEngine
        try:
            self._write(AceMessage.request.SHUTDOWN)
        except:
            pass  # Ignore exceptions on destroy
        finally:
            self._clientcounter.idleAce = None

    def reset(self):
        '''
        Reset initial values
        '''
        self._started_again.clear()
        self._url.set()
        self._loadasync.set()
        self._cid.set()
        self._status = AsyncResult()

    def _write(self, message):
        try:
            self._socket.write('%s\r\n' % message)
            logging.debug('>>> %s' % message)
        except gevent.socket.error:
            raise AceException('Error writing data to AceEngine API port')

    def _recvError(self):
        raise AceException('Error reading data from AceEngine API port')

    def aceInit(self,
                gender=AceConst.SEX_MALE,
                age=AceConst.AGE_25_34,
                product_key=None,
                videoseekback=0,
                videotimeout=30):
        self._gender = gender
        self._age = age
        self._product_key = product_key
        self._seekback = videoseekback
        self._videotimeout = videotimeout
        self._started_again.clear()
        # Spawning telnet data reader with recvbuffer read timeout (allowable STATE 0 (IDLE) time)
        gevent.spawn(
            wrap_errors((EOFError, gevent.socket.error), self._recvData),
            self._videotimeout).link_exception(lambda x: self._recvError())

        self._auth = AsyncResult()
        self._write(AceMessage.request.HELLO)  # Sending HELLOBG
        try:
            params = self._auth.get(timeout=self._resulttimeout)
        except gevent.Timeout as t:
            errmsg = 'Engine response time %s exceeded. HELLOTS not resived!' % t
            raise AceException(errmsg)

        self._auth = AsyncResult()
        self._write(
            AceMessage.request.READY(params.get('key', ''), self._product_key))
        try:
            if self._auth.get(
                    timeout=self._resulttimeout
            ) == 'NOTREADY':  # Get NOTREADY instead AUTH user_auth_level
                errmsg = 'NOTREADY recived from AceEngine! Wrong acekey?'
                raise AceException(errmsg)
        except gevent.Timeout as t:
            errmsg = 'Engine response time %s exceeded. AUTH not resived!' % t
            raise AceException(errmsg)

        if int(params.get('version_code',
                          0)) >= 3003600:  # Display download_stopped massage
            params_dict = {'use_stop_notifications': '1'}
            self._write(AceMessage.request.SETOPTIONS(params_dict))

    def START(self, command, paramsdict, acestreamtype):
        '''
        Start video method. Get url for play from AceEngine
        '''
        paramsdict['stream_type'] = ' '.join(
            ['{}={}'.format(k, v) for k, v in acestreamtype.items()])
        self._url = AsyncResult()
        self._write(AceMessage.request.START(command.upper(), paramsdict))
        try:
            return self._url.get(timeout=self._videotimeout)
        except gevent.Timeout as t:
            errmsg = 'START URL not received! Engine response time %s exceeded' % t
            raise AceException(errmsg)

    def STOP(self):
        '''
        Stop video method
        '''
        self._state = AsyncResult()
        self._write(AceMessage.request.STOP)
        try:
            self._state.get(timeout=self._resulttimeout)
        except gevent.Timeout as t:
            errmsg = 'Engine response time %s exceeded. STATE 0 (IDLE) not resived!' % t
            raise AceException(errmsg)

    def LOADASYNC(self, command, params, sessionid='0'):
        self._loadasync = AsyncResult()
        self._write(
            AceMessage.request.LOADASYNC(command.upper(), sessionid, params))
        try:
            return self._loadasync.get(
                timeout=self._resulttimeout)  # Get _contentinfo json
        except gevent.Timeout as t:
            errmsg = 'Engine response %s time exceeded. LOADARESP not resived!' % t
            raise AceException(errmsg)

    def GETCONTENTINFO(self, command, value, sessionid='0'):
        paramsdict = {
            command: value,
            'developer_id': '0',
            'affiliate_id': '0',
            'zone_id': '0'
        }
        return self.LOADASYNC(command, paramsdict, sessionid)

    def GETCID(self, command, value):
        contentinfo = self.GETCONTENTINFO(command, value)
        if contentinfo['status'] in (1, 2):
            paramsdict = {
                'checksum': contentinfo['checksum'],
                'infohash': contentinfo['infohash'],
                'developer_id': '0',
                'affiliate_id': '0',
                'zone_id': '0'
            }
            self._cid = AsyncResult()
            self._write(AceMessage.request.GETCID(paramsdict))
            try:
                return self._cid.get(timeout=self._resulttimeout)[2:]  # ##CID
            except gevent.Timeout as t:
                errmsg = 'Engine response time %s exceeded. CID not resived!' % t
                raise AceException(errmsg)
        else:
            errmsg = 'LOADASYNC returned error with message: %s' % contentinfo[
                'message']
            raise AceException(errmsg)

    def GETINFOHASH(self, command, value, sessionid='0', idx=0):
        contentinfo = self.GETCONTENTINFO(command, value, sessionid)
        if contentinfo['status'] in (1, 2):
            return contentinfo['infohash'], [
                x[0] for x in contentinfo['files'] if x[1] == int(idx)
            ][0]
        elif contentinfo['status'] == 0:
            errmsg = 'LOADASYNC returned status 0: The transport file does not contain audio/video files'
            raise AceException(errmsg)
        else:
            errmsg = 'LOADASYNC returned error with message: %s' % contentinfo[
                'message']
            raise AceException(errmsg)

    def _recvData(self, timeout=30):
        '''
        Data receiver method for greenlet
        '''
        while 1:
            # Destroy socket connection if AceEngine STATE 0 (IDLE) and we didn't read anything from socket until Nsec
            with gevent.Timeout(timeout, False):
                try:
                    self._recvbuffer = self._socket.read_until('\r\n',
                                                               None).strip()
                except gevent.Timeout:
                    self.destroy()
                except gevent.socket.timeout:
                    pass
                except:
                    raise
                else:
                    logging.debug('<<< %s' % unquote(self._recvbuffer))
                    # Parsing everything only if the string is not empty
                    # HELLOTS
                    if self._recvbuffer.startswith('HELLOTS'):
                        #version=engine_version version_code=version_code key=request_key http_port=http_port
                        self._auth.set({
                            k: v
                            for k, v in (x.split('=')
                                         for x in self._recvbuffer.split()
                                         if '=' in x)
                        })
                    # NOTREADY
                    elif self._recvbuffer.startswith('NOTREADY'):
                        self._auth.set('NOTREADY')
                        # AUTH
                    elif self._recvbuffer.startswith('AUTH'):
                        self._auth.set(
                            self._recvbuffer.split()[1])  # user_auth_level
                        # START
                    elif self._recvbuffer.startswith('START'):
                        # url [ad=1 [interruptable=1]] [stream=1] [pos=position]
                        params = {
                            k: v
                            for k, v in (x.split('=')
                                         for x in self._recvbuffer.split()
                                         if '=' in x)
                        }
                        if not self._seekback or self._started_again.ready(
                        ) or params.get('stream', '') is not '1':
                            # If seekback is disabled, we use link in first START command.
                            # If seekback is enabled, we wait for first START command and
                            # ignore it, then do seekback in first EVENT position command
                            # AceStream sends us STOP and START again with new link.
                            # We use only second link then.
                            self._url.set(
                                self._recvbuffer.split()[1])  # url for play
                    # LOADRESP
                    elif self._recvbuffer.startswith('LOADRESP'):
                        self._loadasync.set(
                            json.loads(
                                unquote(''.join(
                                    self._recvbuffer.split()[2:]))))
                    # STATE
                    elif self._recvbuffer.startswith('STATE'):
                        self._state.set(AceConst.STATE[self._recvbuffer.split(
                        )[1]])  # STATE state_id -> STATE_NAME
                    # STATUS
                    elif self._recvbuffer.startswith('STATUS'):
                        self._tempstatus = self._recvbuffer.split()[1]
                        stat = [self._tempstatus.split(';')[0].split(':')[1]
                                ]  # main:????
                        if self._tempstatus.startswith('main:idle'): pass
                        elif self._tempstatus.startswith('main:loading'): pass
                        elif self._tempstatus.startswith('main:starting'): pass
                        elif self._tempstatus.startswith('main:check'): pass
                        elif self._tempstatus.startswith('main:err'):
                            pass  # err;error_id;error_message
                        elif self._tempstatus.startswith('main:dl'):  #dl;
                            stat.extend(
                                map(int,
                                    self._tempstatus.split(';')[1:]))
                        elif self._tempstatus.startswith(
                                'main:wait'):  #wait;time;
                            stat.extend(
                                map(int,
                                    self._tempstatus.split(';')[2:]))
                        elif self._tempstatus.startswith(
                            ('main:prebuf', 'main:buf')):  #buf;progress;time;
                            stat.extend(
                                map(int,
                                    self._tempstatus.split(';')[3:]))
                        if len(stat) == len(AceConst.STATUS):
                            self._status.set({
                                k: v
                                for k, v in zip(AceConst.STATUS, stat)
                            })  # dl, wait, buf, prebuf
                        else:
                            self._status.set(
                                {'status':
                                 stat[0]})  # idle, loading, starting, check
                    # CID
                    elif self._recvbuffer.startswith('##'):
                        self._cid.set(self._recvbuffer)
                        # INFO
                    elif self._recvbuffer.startswith('INFO'):
                        pass
                        # EVENT
                    elif self._recvbuffer.startswith('EVENT'):
                        self._tempevent = self._recvbuffer.split()
                        if self._seekback and not self._started_again.ready(
                        ) and 'livepos' in self._tempevent:
                            params = {
                                k: v
                                for k, v in (x.split('=')
                                             for x in self._tempevent
                                             if '=' in x)
                            }
                            self._write(
                                AceMessage.request.LIVESEEK(
                                    int(params['last']) - self._seekback))
                            self._started_again.set()
                        elif 'getuserdata' in self._tempevent:
                            self._write(
                                AceMessage.request.USERDATA(
                                    self._gender, self._age))
                        elif 'cansave' in self._tempevent:
                            pass
                        elif 'showurl' in self._tempevent:
                            pass
                        elif 'download_stopped' in self._tempevent:
                            pass
                    # PAUSE
                    elif self._recvbuffer.startswith('PAUSE'):
                        pass  #self._write(AceMessage.request.EVENT('pause'))
                        # RESUME
                    elif self._recvbuffer.startswith('RESUME'):
                        pass  #self._write(AceMessage.request.EVENT('play'))
                        # STOP
                    elif self._recvbuffer.startswith('STOP'):
                        pass  #self._write(AceMessage.request.EVENT('stop'))
                        # SHUTDOWN
                    elif self._recvbuffer.startswith('SHUTDOWN'):
                        self._socket.close()
                        break
                finally:
                    gevent.sleep()
Esempio n. 59
0
class RaidenService(Runnable):
    """ A Raiden node. """
    def __init__(
        self,
        chain: BlockChainService,
        query_start_block: typing.BlockNumber,
        default_registry: TokenNetworkRegistry,
        default_secret_registry: SecretRegistry,
        private_key_bin,
        transport,
        config,
        discovery=None,
    ):
        super().__init__()
        if not isinstance(private_key_bin,
                          bytes) or len(private_key_bin) != 32:
            raise ValueError('invalid private_key')

        self.tokennetworkids_to_connectionmanagers = dict()
        self.identifier_to_results: typing.Dict[typing.PaymentID,
                                                AsyncResult, ] = dict()

        self.chain: BlockChainService = chain
        self.default_registry = default_registry
        self.query_start_block = query_start_block
        self.default_secret_registry = default_secret_registry
        self.config = config
        self.privkey = private_key_bin
        self.address = privatekey_to_address(private_key_bin)
        self.discovery = discovery

        self.private_key = PrivateKey(private_key_bin)
        self.pubkey = self.private_key.public_key.format(compressed=False)
        self.transport = transport

        self.blockchain_events = BlockchainEvents()
        self.alarm = AlarmTask(chain)

        self.stop_event = Event()
        self.stop_event.set()  # inits as stopped

        self.wal = None
        self.snapshot_group = 0

        # This flag will be used to prevent the service from processing
        # state changes events until we know that pending transactions
        # have been dispatched.
        self.dispatch_events_lock = Semaphore(1)

        self.database_path = config['database_path']
        if self.database_path != ':memory:':
            database_dir = os.path.dirname(config['database_path'])
            os.makedirs(database_dir, exist_ok=True)

            self.database_dir = database_dir
            # Prevent concurrent access to the same db
            self.lock_file = os.path.join(self.database_dir, '.lock')
            self.db_lock = filelock.FileLock(self.lock_file)
        else:
            self.database_path = ':memory:'
            self.database_dir = None
            self.lock_file = None
            self.serialization_file = None
            self.db_lock = None

        self.event_poll_lock = gevent.lock.Semaphore()

    def start(self):
        """ Start the node synchronously. Raises directly if anything went wrong on startup """
        if not self.stop_event.ready():
            raise RuntimeError(f'{self!r} already started')
        self.stop_event.clear()

        if self.database_dir is not None:
            self.db_lock.acquire(timeout=0)
            assert self.db_lock.is_locked

        # start the registration early to speed up the start
        if self.config['transport_type'] == 'udp':
            endpoint_registration_greenlet = gevent.spawn(
                self.discovery.register,
                self.address,
                self.config['transport']['udp']['external_ip'],
                self.config['transport']['udp']['external_port'],
            )

        # The database may be :memory:
        storage = sqlite.SQLiteStorage(self.database_path,
                                       serialize.JSONSerializer())
        self.wal = wal.restore_from_latest_snapshot(
            node.state_transition,
            storage,
        )

        if self.wal.state_manager.current_state is None:
            log.debug(
                'No recoverable state available, created inital state',
                node=pex(self.address),
            )
            block_number = self.chain.block_number()

            state_change = ActionInitChain(
                random.Random(),
                block_number,
                self.chain.node_address,
                self.chain.network_id,
            )
            self.wal.log_and_dispatch(state_change)
            payment_network = PaymentNetworkState(
                self.default_registry.address,
                [],  # empty list of token network states as it's the node's startup
            )
            state_change = ContractReceiveNewPaymentNetwork(
                constants.NULL_HASH_BYTES,
                payment_network,
            )
            self.handle_state_change(state_change)

            # On first run Raiden needs to fetch all events for the payment
            # network, to reconstruct all token network graphs and find opened
            # channels
            last_log_block_number = 0
        else:
            # The `Block` state change is dispatched only after all the events
            # for that given block have been processed, filters can be safely
            # installed starting from this position without losing events.
            last_log_block_number = views.block_number(
                self.wal.state_manager.current_state)
            log.debug(
                'Restored state from WAL',
                last_restored_block=last_log_block_number,
                node=pex(self.address),
            )

            known_networks = views.get_payment_network_identifiers(
                views.state_from_raiden(self))
            if known_networks and self.default_registry.address not in known_networks:
                configured_registry = pex(self.default_registry.address)
                known_registries = lpex(known_networks)
                raise RuntimeError(
                    f'Token network address mismatch.\n'
                    f'Raiden is configured to use the smart contract '
                    f'{configured_registry}, which conflicts with the current known '
                    f'smart contracts {known_registries}', )

        # Clear ref cache & disable caching
        serialize.RaidenJSONDecoder.ref_cache.clear()
        serialize.RaidenJSONDecoder.cache_object_references = False

        # Restore the current snapshot group
        state_change_qty = self.wal.storage.count_state_changes()
        self.snapshot_group = state_change_qty // SNAPSHOT_STATE_CHANGES_COUNT

        # Install the filters using the correct from_block value, otherwise
        # blockchain logs can be lost.
        self.install_all_blockchain_filters(
            self.default_registry,
            self.default_secret_registry,
            last_log_block_number,
        )

        # Complete the first_run of the alarm task and synchronize with the
        # blockchain since the last run.
        #
        # Notes about setup order:
        # - The filters must be polled after the node state has been primed,
        # otherwise the state changes won't have effect.
        # - The alarm must complete its first run  before the transport is started,
        #  to avoid rejecting messages for unknown channels.
        self.alarm.register_callback(self._callback_new_block)

        self.alarm.first_run()

        chain_state = views.state_from_raiden(self)
        # Dispatch pending transactions
        pending_transactions = views.get_pending_transactions(chain_state, )
        log.debug(
            'Processing pending transactions',
            num_pending_transactions=len(pending_transactions),
            node=pex(self.address),
        )
        with self.dispatch_events_lock:
            for transaction in pending_transactions:
                on_raiden_event(self, transaction)

        self.alarm.start()

        queueids_to_queues = views.get_all_messagequeues(chain_state)
        # repopulate identifier_to_results for pending transfers
        for queue_messages in queueids_to_queues.values():
            for message in queue_messages:
                if isinstance(message, SendDirectTransfer):
                    self.identifier_to_results[
                        message.payment_identifier] = AsyncResult()
        self.transport.start(self, queueids_to_queues)

        # exceptions on these subtasks should crash the app and bubble up
        self.alarm.link_exception(self.on_error)
        self.transport.link_exception(self.on_error)

        # Health check needs the transport layer
        self.start_neighbours_healthcheck()

        if self.config['transport_type'] == 'udp':
            endpoint_registration_greenlet.get(
            )  # re-raise if exception occurred

        super().start()

    def _run(self):
        """ Busy-wait on long-lived subtasks/greenlets, re-raise if any error occurs """
        try:
            self.stop_event.wait()
        except gevent.GreenletExit:  # killed without exception
            self.stop_event.set()
            gevent.killall([self.alarm, self.transport])  # kill children
            raise  # re-raise to keep killed status
        except Exception:
            self.stop()
            raise

    def stop(self):
        """ Stop the node gracefully. Raise if any stop-time error occurred on any subtask """
        if self.stop_event.ready():  # not started
            return

        # Needs to come before any greenlets joining
        self.stop_event.set()

        # Filters must be uninstalled after the alarm task has stopped. Since
        # the events are polled by an alarm task callback, if the filters are
        # uninstalled before the alarm task is fully stopped the callback
        # `poll_blockchain_events` will fail.
        #
        # We need a timeout to prevent an endless loop from trying to
        # contact the disconnected client
        try:
            self.transport.stop()
            self.alarm.stop()
            self.transport.get()
            self.alarm.get()
            self.blockchain_events.uninstall_all_event_listeners()
        except (gevent.Timeout, RaidenShuttingDown):
            pass

        self.blockchain_events.reset()

        if self.db_lock is not None:
            self.db_lock.release()

    def __repr__(self):
        return '<{} {}>'.format(self.__class__.__name__, pex(self.address))

    def start_neighbours_healthcheck(self):
        for neighbour in views.all_neighbour_nodes(
                self.wal.state_manager.current_state):
            if neighbour != ConnectionManager.BOOTSTRAP_ADDR:
                self.start_health_check_for(neighbour)

    def get_block_number(self):
        return views.block_number(self.wal.state_manager.current_state)

    def handle_state_change(self, state_change):
        log.debug('STATE CHANGE',
                  node=pex(self.address),
                  state_change=state_change)

        event_list = self.wal.log_and_dispatch(state_change)

        if self.dispatch_events_lock.locked():
            return []

        for event in event_list:
            log.debug('RAIDEN EVENT',
                      node=pex(self.address),
                      raiden_event=event)
            on_raiden_event(self, event)

        # Take a snapshot every SNAPSHOT_STATE_CHANGES_COUNT
        # TODO: Gather more data about storage requirements
        # and update the value to specify how often we need
        # capturing a snapshot should take place
        new_snapshot_group = self.wal.storage.count_state_changes(
        ) // SNAPSHOT_STATE_CHANGES_COUNT
        if new_snapshot_group > self.snapshot_group:
            log.debug(f'Storing snapshot: {new_snapshot_group}')
            self.wal.snapshot()
            self.snapshot_group = new_snapshot_group

        return event_list

    def set_node_network_state(self, node_address, network_state):
        state_change = ActionChangeNodeNetworkState(node_address,
                                                    network_state)
        self.wal.log_and_dispatch(state_change)

    def start_health_check_for(self, node_address):
        self.transport.start_health_check(node_address)

    def _callback_new_block(self, current_block_number):
        """Called once a new block is detected by the alarm task.

        Note:
            This should be called only once per block, otherwise there will be
            duplicated `Block` state changes in the log.

            Therefore this method should be called only once a new block is
            mined with the appropriate block_number argument from the
            AlarmTask.
        """
        # Raiden relies on blockchain events to update its off-chain state,
        # therefore some APIs /used/ to forcefully poll for events.
        #
        # This was done for APIs which have on-chain side-effects, e.g.
        # openning a channel, where polling the event is required to update
        # off-chain state to providing a consistent view to the caller, e.g.
        # the channel exists after the API call returns.
        #
        # That pattern introduced a race, because the events are returned only
        # once per filter, and this method would be called concurrently by the
        # API and the AlarmTask. The following lock is necessary, to ensure the
        # expected side-effects are properly applied (introduced by the commit
        # 3686b3275ff7c0b669a6d5e2b34109c3bdf1921d)
        with self.event_poll_lock:
            for event in self.blockchain_events.poll_blockchain_events(
                    current_block_number):
                # These state changes will be procesed with a block_number
                # which is /larger/ than the ChainState's block_number.
                on_blockchain_event(self, event)

            # On restart the Raiden node will re-create the filters with the
            # ethereum node. These filters will have the from_block set to the
            # value of the latest Block state change. To avoid missing events
            # the Block state change is dispatched only after all of the events
            # have been processed.
            #
            # This means on some corner cases a few events may be applied
            # twice, this will happen if the node crashed and some events have
            # been processed but the Block state change has not been
            # dispatched.
            state_change = Block(current_block_number)
            self.handle_state_change(state_change)

    def sign(self, message):
        """ Sign message inplace. """
        if not isinstance(message, SignedMessage):
            raise ValueError('{} is not signable.'.format(repr(message)))

        message.sign(self.private_key)

    def install_all_blockchain_filters(
        self,
        token_network_registry_proxy: TokenNetworkRegistry,
        secret_registry_proxy: SecretRegistry,
        from_block: typing.BlockNumber,
    ):
        with self.event_poll_lock:
            node_state = views.state_from_raiden(self)
            token_networks = views.get_token_network_identifiers(
                node_state,
                token_network_registry_proxy.address,
            )

            self.blockchain_events.add_token_network_registry_listener(
                token_network_registry_proxy,
                from_block,
            )
            self.blockchain_events.add_secret_registry_listener(
                secret_registry_proxy,
                from_block,
            )

            for token_network in token_networks:
                token_network_proxy = self.chain.token_network(token_network)
                self.blockchain_events.add_token_network_listener(
                    token_network_proxy,
                    from_block,
                )

    def connection_manager_for_token_network(self, token_network_identifier):
        if not is_binary_address(token_network_identifier):
            raise InvalidAddress('token address is not valid.')

        known_token_networks = views.get_token_network_identifiers(
            views.state_from_raiden(self),
            self.default_registry.address,
        )

        if token_network_identifier not in known_token_networks:
            raise InvalidAddress('token is not registered.')

        manager = self.tokennetworkids_to_connectionmanagers.get(
            token_network_identifier)

        if manager is None:
            manager = ConnectionManager(self, token_network_identifier)
            self.tokennetworkids_to_connectionmanagers[
                token_network_identifier] = manager

        return manager

    def leave_all_token_networks(self):
        state_change = ActionLeaveAllNetworks()
        self.wal.log_and_dispatch(state_change)

    def close_and_settle(self):
        log.info('raiden will close and settle all channels now')

        self.leave_all_token_networks()

        connection_managers = [
            cm for cm in self.tokennetworkids_to_connectionmanagers.values()
        ]

        if connection_managers:
            waiting.wait_for_settle_all_channels(
                self,
                self.alarm.sleep_time,
            )

    def mediated_transfer_async(
        self,
        token_network_identifier,
        amount,
        target,
        identifier,
    ):
        """ Transfer `amount` between this node and `target`.

        This method will start an asynchronous transfer, the transfer might fail
        or succeed depending on a couple of factors:

            - Existence of a path that can be used, through the usage of direct
              or intermediary channels.
            - Network speed, making the transfer sufficiently fast so it doesn't
              expire.
        """

        async_result = self.start_mediated_transfer(
            token_network_identifier,
            amount,
            target,
            identifier,
        )

        return async_result

    def direct_transfer_async(self, token_network_identifier, amount, target,
                              identifier):
        """ Do a direct transfer with target.

        Direct transfers are non cancellable and non expirable, since these
        transfers are a signed balance proof with the transferred amount
        incremented.

        Because the transfer is non cancellable, there is a level of trust with
        the target. After the message is sent the target is effectively paid
        and then it is not possible to revert.

        The async result will be set to False iff there is no direct channel
        with the target or the payer does not have balance to complete the
        transfer, otherwise because the transfer is non expirable the async
        result *will never be set to False* and if the message is sent it will
        hang until the target node acknowledge the message.

        This transfer should be used as an optimization, since only two packets
        are required to complete the transfer (from the payers perspective),
        whereas the mediated transfer requires 6 messages.
        """

        self.start_health_check_for(target)

        if identifier is None:
            identifier = create_default_identifier()

        direct_transfer = ActionTransferDirect(
            token_network_identifier,
            target,
            identifier,
            amount,
        )

        async_result = AsyncResult()
        self.identifier_to_results[identifier] = async_result

        self.handle_state_change(direct_transfer)

    def start_mediated_transfer(
        self,
        token_network_identifier,
        amount,
        target,
        identifier,
    ):

        self.start_health_check_for(target)

        if identifier is None:
            identifier = create_default_identifier()

        if identifier in self.identifier_to_results:
            return self.identifier_to_results[identifier]

        async_result = AsyncResult()
        self.identifier_to_results[identifier] = async_result

        secret = random_secret()
        init_initiator_statechange = initiator_init(
            self,
            identifier,
            amount,
            secret,
            token_network_identifier,
            target,
        )

        # Dispatch the state change even if there are no routes to create the
        # wal entry.
        self.handle_state_change(init_initiator_statechange)

        return async_result

    def mediate_mediated_transfer(self, transfer: LockedTransfer):
        init_mediator_statechange = mediator_init(self, transfer)
        self.handle_state_change(init_mediator_statechange)

    def target_mediated_transfer(self, transfer: LockedTransfer):
        self.start_health_check_for(transfer.initiator)
        init_target_statechange = target_init(transfer)
        self.handle_state_change(init_target_statechange)
Esempio n. 60
0
class RaidenService(Runnable):
    """ A Raiden node. """
    def __init__(
        self,
        chain: BlockChainService,
        query_start_block: BlockNumber,
        default_registry: TokenNetworkRegistry,
        default_secret_registry: SecretRegistry,
        transport,
        raiden_event_handler,
        message_handler,
        config,
        discovery=None,
    ):
        super().__init__()
        self.tokennetworkids_to_connectionmanagers: ConnectionManagerDict = dict(
        )
        self.targets_to_identifiers_to_statuses: StatusesDict = defaultdict(
            dict)

        self.chain: BlockChainService = chain
        self.default_registry = default_registry
        self.query_start_block = query_start_block
        self.default_secret_registry = default_secret_registry
        self.config = config

        self.signer: Signer = LocalSigner(self.chain.client.privkey)
        self.address = self.signer.address
        self.discovery = discovery
        self.transport = transport

        self.blockchain_events = BlockchainEvents()
        self.alarm = AlarmTask(chain)
        self.raiden_event_handler = raiden_event_handler
        self.message_handler = message_handler

        self.stop_event = Event()
        self.stop_event.set()  # inits as stopped
        self.greenlets = list()

        self.wal: Optional[wal.WriteAheadLog] = None
        self.snapshot_group = 0

        # This flag will be used to prevent the service from processing
        # state changes events until we know that pending transactions
        # have been dispatched.
        self.dispatch_events_lock = Semaphore(1)

        self.contract_manager = ContractManager(config['contracts_path'])
        self.database_path = config['database_path']
        if self.database_path != ':memory:':
            database_dir = os.path.dirname(config['database_path'])
            os.makedirs(database_dir, exist_ok=True)

            self.database_dir = database_dir

            # Two raiden processes must not write to the same database, even
            # though the database itself may be consistent. If more than one
            # nodes writes state changes to the same WAL there are no
            # guarantees about recovery, this happens because during recovery
            # the WAL replay can not be deterministic.
            lock_file = os.path.join(self.database_dir, '.lock')
            self.db_lock = filelock.FileLock(lock_file)
        else:
            self.database_path = ':memory:'
            self.database_dir = None
            self.serialization_file = None
            self.db_lock = None

        self.event_poll_lock = gevent.lock.Semaphore()
        self.gas_reserve_lock = gevent.lock.Semaphore()
        self.payment_identifier_lock = gevent.lock.Semaphore()

    def start(self):
        """ Start the node synchronously. Raises directly if anything went wrong on startup """
        if not self.stop_event.ready():
            raise RuntimeError(f'{self!r} already started')
        self.stop_event.clear()
        self.greenlets = list()

        if self.database_dir is not None:
            self.db_lock.acquire(timeout=0)
            assert self.db_lock.is_locked

        # start the registration early to speed up the start
        if self.config['transport_type'] == 'udp':
            endpoint_registration_greenlet = gevent.spawn(
                self.discovery.register,
                self.address,
                self.config['transport']['udp']['external_ip'],
                self.config['transport']['udp']['external_port'],
            )

        self.maybe_upgrade_db()

        storage = sqlite.SerializedSQLiteStorage(
            database_path=self.database_path,
            serializer=serialize.JSONSerializer(),
        )
        storage.log_run()
        self.wal = wal.restore_to_state_change(
            transition_function=node.state_transition,
            storage=storage,
            state_change_identifier='latest',
        )

        if self.wal.state_manager.current_state is None:
            log.debug(
                'No recoverable state available, created inital state',
                node=pex(self.address),
            )
            # On first run Raiden needs to fetch all events for the payment
            # network, to reconstruct all token network graphs and find opened
            # channels
            last_log_block_number = self.query_start_block
            last_log_block_hash = self.chain.client.blockhash_from_blocknumber(
                last_log_block_number, )

            state_change = ActionInitChain(
                pseudo_random_generator=random.Random(),
                block_number=last_log_block_number,
                block_hash=last_log_block_hash,
                our_address=self.chain.node_address,
                chain_id=self.chain.network_id,
            )
            self.handle_and_track_state_change(state_change)

            payment_network = PaymentNetworkState(
                self.default_registry.address,
                [],  # empty list of token network states as it's the node's startup
            )
            state_change = ContractReceiveNewPaymentNetwork(
                transaction_hash=constants.EMPTY_HASH,
                payment_network=payment_network,
                block_number=last_log_block_number,
                block_hash=last_log_block_hash,
            )
            self.handle_and_track_state_change(state_change)
        else:
            # The `Block` state change is dispatched only after all the events
            # for that given block have been processed, filters can be safely
            # installed starting from this position without losing events.
            last_log_block_number = views.block_number(
                self.wal.state_manager.current_state)
            log.debug(
                'Restored state from WAL',
                last_restored_block=last_log_block_number,
                node=pex(self.address),
            )

            known_networks = views.get_payment_network_identifiers(
                views.state_from_raiden(self))
            if known_networks and self.default_registry.address not in known_networks:
                configured_registry = pex(self.default_registry.address)
                known_registries = lpex(known_networks)
                raise RuntimeError(
                    f'Token network address mismatch.\n'
                    f'Raiden is configured to use the smart contract '
                    f'{configured_registry}, which conflicts with the current known '
                    f'smart contracts {known_registries}', )

        # Restore the current snapshot group
        state_change_qty = self.wal.storage.count_state_changes()
        self.snapshot_group = state_change_qty // SNAPSHOT_STATE_CHANGES_COUNT

        # Install the filters using the correct from_block value, otherwise
        # blockchain logs can be lost.
        self.install_all_blockchain_filters(
            self.default_registry,
            self.default_secret_registry,
            last_log_block_number,
        )

        # Complete the first_run of the alarm task and synchronize with the
        # blockchain since the last run.
        #
        # Notes about setup order:
        # - The filters must be polled after the node state has been primed,
        # otherwise the state changes won't have effect.
        # - The alarm must complete its first run before the transport is started,
        #   to reject messages for closed/settled channels.
        self.alarm.register_callback(self._callback_new_block)
        with self.dispatch_events_lock:
            self.alarm.first_run(last_log_block_number)

        chain_state = views.state_from_raiden(self)
        self._initialize_transactions_queues(chain_state)
        self._initialize_whitelists(chain_state)
        self._initialize_payment_statuses(chain_state)
        # send messages in queue before starting transport,
        # this is necessary to avoid a race where, if the transport is started
        # before the messages are queued, actions triggered by it can cause new
        # messages to be enqueued before these older ones
        self._initialize_messages_queues(chain_state)

        # before we start the transport, we need to request monitoring for all current
        # balance proofs.
        current_balance_proofs = views.detect_balance_proof_change(
            State(),
            chain_state,
        )
        for balance_proof in current_balance_proofs:
            update_monitoring_service_from_balance_proof(self, balance_proof)

        # The transport must not ever be started before the alarm task's
        # `first_run()` has been, because it's this method which synchronizes the
        # node with the blockchain, including the channel's state (if the channel
        # is closed on-chain new messages must be rejected, which will not be the
        # case if the node is not synchronized)
        self.transport.start(
            raiden_service=self,
            message_handler=self.message_handler,
            prev_auth_data=chain_state.last_transport_authdata,
        )

        # First run has been called above!
        self.alarm.start()

        # exceptions on these subtasks should crash the app and bubble up
        self.alarm.link_exception(self.on_error)
        self.transport.link_exception(self.on_error)

        # Health check needs the transport layer
        self.start_neighbours_healthcheck(chain_state)

        if self.config['transport_type'] == 'udp':
            endpoint_registration_greenlet.get(
            )  # re-raise if exception occurred

        log.debug('Raiden Service started', node=pex(self.address))
        super().start()

    def _run(self, *args, **kwargs):  # pylint: disable=method-hidden
        """ Busy-wait on long-lived subtasks/greenlets, re-raise if any error occurs """
        self.greenlet.name = f'RaidenService._run node:{pex(self.address)}'
        try:
            self.stop_event.wait()
        except gevent.GreenletExit:  # killed without exception
            self.stop_event.set()
            gevent.killall([self.alarm, self.transport])  # kill children
            raise  # re-raise to keep killed status
        except Exception:
            self.stop()
            raise

    def stop(self):
        """ Stop the node gracefully. Raise if any stop-time error occurred on any subtask """
        if self.stop_event.ready():  # not started
            return

        # Needs to come before any greenlets joining
        self.stop_event.set()

        # Filters must be uninstalled after the alarm task has stopped. Since
        # the events are polled by an alarm task callback, if the filters are
        # uninstalled before the alarm task is fully stopped the callback
        # `poll_blockchain_events` will fail.
        #
        # We need a timeout to prevent an endless loop from trying to
        # contact the disconnected client
        self.transport.stop()
        self.alarm.stop()

        self.transport.join()
        self.alarm.join()

        self.blockchain_events.uninstall_all_event_listeners()

        # Close storage DB to release internal DB lock
        self.wal.storage.conn.close()

        if self.db_lock is not None:
            self.db_lock.release()

        log.debug('Raiden Service stopped', node=pex(self.address))

    @property
    def confirmation_blocks(self):
        return self.config['blockchain']['confirmation_blocks']

    def add_pending_greenlet(self, greenlet: Greenlet):
        """ Ensures an error on the passed greenlet crashes self/main greenlet. """
        def remove(_):
            self.greenlets.remove(greenlet)

        self.greenlets.append(greenlet)
        greenlet.link_exception(self.on_error)
        greenlet.link_value(remove)

    def __repr__(self):
        return f'<{self.__class__.__name__} node:{pex(self.address)}>'

    def start_neighbours_healthcheck(self, chain_state: ChainState):
        for neighbour in views.all_neighbour_nodes(chain_state):
            if neighbour != ConnectionManager.BOOTSTRAP_ADDR:
                self.start_health_check_for(neighbour)

    def get_block_number(self) -> BlockNumber:
        assert self.wal, 'WAL object not yet initialized.'
        return views.block_number(self.wal.state_manager.current_state)

    def on_message(self, message: Message):
        self.message_handler.on_message(self, message)

    def handle_and_track_state_change(self, state_change: StateChange):
        """ Dispatch the state change and does not handle the exceptions.

        When the method is used the exceptions are tracked and re-raised in the
        raiden service thread.
        """
        for greenlet in self.handle_state_change(state_change):
            self.add_pending_greenlet(greenlet)

    def handle_state_change(self, state_change: StateChange) -> List[Greenlet]:
        """ Dispatch the state change and return the processing threads.

        Use this for error reporting, failures in the returned greenlets,
        should be re-raised using `gevent.joinall` with `raise_error=True`.
        """
        assert self.wal
        log.debug(
            'State change',
            node=pex(self.address),
            state_change=_redact_secret(
                serialize.JSONSerializer.serialize(state_change)),
        )

        old_state = views.state_from_raiden(self)

        raiden_event_list = self.wal.log_and_dispatch(state_change)

        current_state = views.state_from_raiden(self)
        for balance_proof in views.detect_balance_proof_change(
                old_state, current_state):
            update_monitoring_service_from_balance_proof(self, balance_proof)

        log.debug(
            'Raiden events',
            node=pex(self.address),
            raiden_events=[
                _redact_secret(serialize.JSONSerializer.serialize(event))
                for event in raiden_event_list
            ],
        )

        greenlets: List[Greenlet] = list()
        if not self.dispatch_events_lock.locked():
            for raiden_event in raiden_event_list:
                greenlets.append(
                    self.handle_event(raiden_event=raiden_event), )

            state_changes_count = self.wal.storage.count_state_changes()
            new_snapshot_group = (state_changes_count //
                                  SNAPSHOT_STATE_CHANGES_COUNT)
            if new_snapshot_group > self.snapshot_group:
                log.debug('Storing snapshot', snapshot_id=new_snapshot_group)
                self.wal.snapshot()
                self.snapshot_group = new_snapshot_group

        return greenlets

    def handle_event(self, raiden_event: RaidenEvent) -> Greenlet:
        """Spawn a new thread to handle a Raiden event.

        This will spawn a new greenlet to handle each event, which is
        important for two reasons:

        - Blockchain transactions can be queued without interfering with each
          other.
        - The calling thread is free to do more work. This is specially
          important for the AlarmTask thread, which will eventually cause the
          node to send transactions when a given Block is reached (e.g.
          registering a secret or settling a channel).

        Important:

            This is spawing a new greenlet for /each/ transaction. It's
            therefore /required/ that there is *NO* order among these.
        """
        return gevent.spawn(self._handle_event, raiden_event)

    def _handle_event(self, raiden_event: RaidenEvent):
        assert isinstance(raiden_event, RaidenEvent)
        try:
            self.raiden_event_handler.on_raiden_event(
                raiden=self,
                event=raiden_event,
            )
        except RaidenRecoverableError as e:
            log.error(str(e))
        except InvalidDBData:
            raise
        except RaidenUnrecoverableError as e:
            log_unrecoverable = (
                self.config['environment_type'] == Environment.PRODUCTION
                and not self.config['unrecoverable_error_should_crash'])
            if log_unrecoverable:
                log.error(str(e))
            else:
                raise

    def set_node_network_state(self, node_address: Address,
                               network_state: str):
        state_change = ActionChangeNodeNetworkState(node_address,
                                                    network_state)
        self.handle_and_track_state_change(state_change)

    def start_health_check_for(self, node_address: Address):
        # This function is a noop during initialization. It can be called
        # through the alarm task while polling for new channel events.  The
        # healthcheck will be started by self.start_neighbours_healthcheck()
        if self.transport:
            self.transport.start_health_check(node_address)

    def _callback_new_block(self, latest_block: Dict):
        """Called once a new block is detected by the alarm task.

        Note:
            This should be called only once per block, otherwise there will be
            duplicated `Block` state changes in the log.

            Therefore this method should be called only once a new block is
            mined with the corresponding block data from the AlarmTask.
        """
        # User facing APIs, which have on-chain side-effects, force polled the
        # blockchain to update the node's state. This force poll is used to
        # provide a consistent view to the user, e.g. a channel open call waits
        # for the transaction to be mined and force polled the event to update
        # the node's state. This pattern introduced a race with the alarm task
        # and the task which served the user request, because the events are
        # returned only once per filter. The lock below is to protect against
        # these races (introduced by the commit
        # 3686b3275ff7c0b669a6d5e2b34109c3bdf1921d)
        with self.event_poll_lock:
            latest_block_number = latest_block['number']
            confirmed_block_number = latest_block_number - self.confirmation_blocks
            confirmed_block = self.chain.client.web3.eth.getBlock(
                confirmed_block_number)

            # handle testing private chains
            confirmed_block_number = max(GENESIS_BLOCK_NUMBER,
                                         confirmed_block_number)

            for event in self.blockchain_events.poll_blockchain_events(
                    confirmed_block_number):
                # These state changes will be procesed with a block_number
                # which is /larger/ than the ChainState's block_number.
                on_blockchain_event(self, event)

            # On restart the Raiden node will re-create the filters with the
            # ethereum node. These filters will have the from_block set to the
            # value of the latest Block state change. To avoid missing events
            # the Block state change is dispatched only after all of the events
            # have been processed.
            #
            # This means on some corner cases a few events may be applied
            # twice, this will happen if the node crashed and some events have
            # been processed but the Block state change has not been
            # dispatched.
            state_change = Block(
                block_number=confirmed_block_number,
                gas_limit=confirmed_block['gasLimit'],
                block_hash=BlockHash(bytes(confirmed_block['hash'])),
            )

            # Note: It's important to /not/ block here, because this function
            # can be called from the alarm task greenlet, which should not
            # starve.
            self.handle_and_track_state_change(state_change)

    def _initialize_transactions_queues(self, chain_state: ChainState):
        pending_transactions = views.get_pending_transactions(chain_state)

        log.debug(
            'Processing pending transactions',
            num_pending_transactions=len(pending_transactions),
            node=pex(self.address),
        )

        with self.dispatch_events_lock:
            for transaction in pending_transactions:
                self.add_pending_greenlet(
                    self.handle_event(raiden_event=transaction), )

    def _initialize_payment_statuses(self, chain_state: ChainState):
        """ Re-initialize targets_to_identifiers_to_statuses. """

        with self.payment_identifier_lock:
            for task in chain_state.payment_mapping.secrethashes_to_task.values(
            ):
                if not isinstance(task, InitiatorTask):
                    continue

                # Every transfer in the transfers_list must have the same target
                # and payment_identifier, so using the first transfer is
                # sufficient.
                initiator = next(
                    iter(task.manager_state.initiator_transfers.values()))
                transfer = initiator.transfer
                target = transfer.target
                identifier = transfer.payment_identifier
                balance_proof = transfer.balance_proof
                self.targets_to_identifiers_to_statuses[target][
                    identifier] = PaymentStatus(
                        payment_identifier=identifier,
                        amount=transfer.lock.amount,
                        token_network_identifier=balance_proof.
                        token_network_identifier,
                        payment_done=AsyncResult(),
                    )

    def _initialize_messages_queues(self, chain_state: ChainState):
        """ Push the message queues to the transport. """
        events_queues = views.get_all_messagequeues(chain_state)

        for queue_identifier, event_queue in events_queues.items():
            self.start_health_check_for(queue_identifier.recipient)

            for event in event_queue:
                message = message_from_sendevent(event, self.address)
                self.sign(message)
                self.transport.send_async(queue_identifier, message)

    def _initialize_whitelists(self, chain_state: ChainState):
        """ Whitelist neighbors and mediated transfer targets on transport """

        for neighbour in views.all_neighbour_nodes(chain_state):
            if neighbour == ConnectionManager.BOOTSTRAP_ADDR:
                continue
            self.transport.whitelist(neighbour)

        events_queues = views.get_all_messagequeues(chain_state)

        for event_queue in events_queues.values():
            for event in event_queue:
                if isinstance(event, SendLockedTransfer):
                    transfer = event.transfer
                    if transfer.initiator == self.address:
                        self.transport.whitelist(address=transfer.target)

    def sign(self, message: Message):
        """ Sign message inplace. """
        if not isinstance(message, SignedMessage):
            raise ValueError('{} is not signable.'.format(repr(message)))

        message.sign(self.signer)

    def install_all_blockchain_filters(
        self,
        token_network_registry_proxy: TokenNetworkRegistry,
        secret_registry_proxy: SecretRegistry,
        from_block: BlockNumber,
    ):
        with self.event_poll_lock:
            node_state = views.state_from_raiden(self)
            token_networks = views.get_token_network_identifiers(
                node_state,
                token_network_registry_proxy.address,
            )

            self.blockchain_events.add_token_network_registry_listener(
                token_network_registry_proxy=token_network_registry_proxy,
                contract_manager=self.contract_manager,
                from_block=from_block,
            )
            self.blockchain_events.add_secret_registry_listener(
                secret_registry_proxy=secret_registry_proxy,
                contract_manager=self.contract_manager,
                from_block=from_block,
            )

            for token_network in token_networks:
                token_network_proxy = self.chain.token_network(
                    TokenNetworkAddress(token_network), )
                self.blockchain_events.add_token_network_listener(
                    token_network_proxy=token_network_proxy,
                    contract_manager=self.contract_manager,
                    from_block=from_block,
                )

    def connection_manager_for_token_network(
        self,
        token_network_identifier: TokenNetworkID,
    ) -> ConnectionManager:
        if not is_binary_address(token_network_identifier):
            raise InvalidAddress('token address is not valid.')

        known_token_networks = views.get_token_network_identifiers(
            views.state_from_raiden(self),
            self.default_registry.address,
        )

        if token_network_identifier not in known_token_networks:
            raise InvalidAddress('token is not registered.')

        manager = self.tokennetworkids_to_connectionmanagers.get(
            token_network_identifier)

        if manager is None:
            manager = ConnectionManager(self, token_network_identifier)
            self.tokennetworkids_to_connectionmanagers[
                token_network_identifier] = manager

        return manager

    def mediated_transfer_async(
        self,
        token_network_identifier: TokenNetworkID,
        amount: PaymentAmount,
        target: TargetAddress,
        identifier: PaymentID,
        secret: Secret = None,
        secret_hash: SecretHash = None,
    ) -> PaymentStatus:
        """ Transfer `amount` between this node and `target`.

        This method will start an asynchronous transfer, the transfer might fail
        or succeed depending on a couple of factors:

            - Existence of a path that can be used, through the usage of direct
              or intermediary channels.
            - Network speed, making the transfer sufficiently fast so it doesn't
              expire.
        """
        if secret is None:
            secret = random_secret()

        payment_status = self.start_mediated_transfer_with_secret(
            token_network_identifier,
            amount,
            target,
            identifier,
            secret,
            secret_hash,
        )

        return payment_status

    def start_mediated_transfer_with_secret(
        self,
        token_network_identifier: TokenNetworkID,
        amount: PaymentAmount,
        target: TargetAddress,
        identifier: PaymentID,
        secret: Secret,
        secret_hash: SecretHash = None,
    ) -> PaymentStatus:

        if secret_hash is None:
            secret_hash = sha3(secret)

        # We must check if the secret was registered against the latest block,
        # even if the block is forked away and the transaction that registers
        # the secret is removed from the blockchain. The rationale here is that
        # someone else does know the secret, regardless of the chain state, so
        # the node must not use it to start a payment.
        #
        # For this particular case, it's preferable to use `latest` instead of
        # having a specific block_hash, because it's preferable to know if the secret
        # was ever known, rather than having a consistent view of the blockchain.
        secret_registered = self.default_secret_registry.check_registered(
            secrethash=secret_hash,
            block_identifier='latest',
        )
        if secret_registered:
            raise RaidenUnrecoverableError(
                f'Attempted to initiate a locked transfer with secrethash {pex(secret_hash)}.'
                f' That secret is already registered onchain.', )

        self.start_health_check_for(Address(target))

        if identifier is None:
            identifier = create_default_identifier()

        with self.payment_identifier_lock:
            payment_status = self.targets_to_identifiers_to_statuses[
                target].get(identifier)
            if payment_status:
                payment_status_matches = payment_status.matches(
                    token_network_identifier,
                    amount,
                )
                if not payment_status_matches:
                    raise PaymentConflict(
                        'Another payment with the same id is in flight', )

                return payment_status

            payment_status = PaymentStatus(
                payment_identifier=identifier,
                amount=amount,
                token_network_identifier=token_network_identifier,
                payment_done=AsyncResult(),
                secret=secret,
                secret_hash=secret_hash,
            )
            self.targets_to_identifiers_to_statuses[target][
                identifier] = payment_status

        init_initiator_statechange = initiator_init(
            raiden=self,
            transfer_identifier=identifier,
            transfer_amount=amount,
            transfer_secret=secret,
            token_network_identifier=token_network_identifier,
            target_address=target,
        )

        # Dispatch the state change even if there are no routes to create the
        # wal entry.
        self.handle_and_track_state_change(init_initiator_statechange)

        return payment_status

    def mediate_mediated_transfer(self, transfer: LockedTransfer):
        init_mediator_statechange = mediator_init(self, transfer)
        self.handle_and_track_state_change(init_mediator_statechange)

    def target_mediated_transfer(self, transfer: LockedTransfer):
        self.start_health_check_for(transfer.initiator)
        init_target_statechange = target_init(transfer)
        self.handle_and_track_state_change(init_target_statechange)

    def maybe_upgrade_db(self):
        manager = UpgradeManager(db_filename=self.database_path)
        manager.run()