Exemple #1
0
 def kasaya_connection_broken(self, addr):
     """
     Should be called when connection with kasaya is broken.
     """
     LOG.debug("Connection closed with %s", addr)
     if self.status < 3:  # is worker is already stopping?
         self.status = 1  #set status as 1 - waiting for start
Exemple #2
0
    def handle_request(self, message):
        """
        All control requests are handled here.
           message - message body
           islocal - if true then request is from localhost
        """
        method = message['method']  #".".join(message['method'])
        LOG.debug("Management call [%s]" % method)
        #LOG.debug(repr(message))
        try:
            func = self.__ctltasks[method]  # get handler for method
        except KeyError:
            raise exceptions.MethodNotFound("Control method %s not exists" %
                                            method)

        # fill missing parameters
        if 'args' not in message:
            message['args'] = []
        if 'kwargs' not in message:
            message['kwargs'] = {}

        try:
            # call internal function
            result = func(*message['args'], **message['kwargs'])

        except RedirectRequiredToIP as e:
            # redirect to IP
            addr = self.ip_to_zmq_addr(e.remote_ip)
            return self.redirect(addr, message)
        except RedirectRequiredToAddr as e:
            # redirect to address
            return self.redirect(e.remote_addr, message)

        return result
Exemple #3
0
    def run_task(self, funcname, args, kwargs):
        # find task in worker db
        try:
            task = worker_methods_db[funcname]
        except KeyError:
            self._tasks_nonex += 1
            LOG.info("Unknown worker task called [%s]" % funcname)
            return exception_serialize_internal('Method %s not found' %
                                                funcname)

        # try to run function and catch exceptions
        try:
            LOG.debug("task %s, args %s, kwargs %s" %
                      (funcname, repr(args), repr(kwargs)))
            func = task['func']
            tout = task['timeout']
            if tout is None:
                # call task without timeout
                result = func(*args, **kwargs)
            else:
                # call task with timeout
                with gevent.Timeout(tout, TaskTimeout):
                    result = func(*args, **kwargs)
            self._tasks_succes += 1
            task['res_succ'] += 1

            return {'message': messages.RESULT, 'result': result}

        except TaskTimeout as e:
            # timeout exceeded
            self._tasks_error += 1
            task['res_tout'] += 1
            err = exception_serialize(e, internal=False)
            LOG.info("Task [%s] timeout (after %i s)." % (funcname, tout))
            return err

        except Exception as e:
            # exception occured
            self._tasks_error += 1
            task['res_err'] += 1
            err = exception_serialize(e, internal=False)
            LOG.info("Task [%s] exception [%s]. Message: %s" %
                     (funcname, err['name'], err['description']))
            LOG.debug(err['traceback'])
            return err

        finally:
            # close django connection
            # if worker is using Django ORM we must close database connection manually,
            # or each task will leave one unclosed connection. This is done automatically.
            if task['close_djconn']:
                try:
                    _close_dj_connection()
                except Exception as e:
                    if e.__class__.__name__ == "ImproperlyConfigured":
                        # django connection is not required or diango orm is not used at all,
                        # because of that we replace _close_dj_connection function by empty lambda
                        global _close_dj_connection
                        _close_dj_connection = lambda: None
Exemple #4
0
 def stop(self):
     self.__hbloop = False
     self.status = 3
     LOG.debug("Sending stop notification. Address [%s]" %
               self.loop.address)
     self.SYNC.notify_worker_stop()
     self.loop.stop()
     # killing greenlets
     for g in self.__greens:
         g.kill(block=True)
     LOG.debug("Worker [%s] stopped" % self.servicename)
 def register_raw_task(self, message_type, raw_responce, func):
     """
     Raw task is used internally to enhance kasaya protocol
     by handling special types of messages. It's used internally
     by kasaya own daemons
     """
     self._raw_tasks[message_type] = {
         'func': func,
         'raw_resp': raw_responce
     }
     LOG.debug("Registered raw task handler %s -> %s" %
               (message_type, func.__name__))
Exemple #6
0
    def worker_prepare(self, worker_id):
        """
        After start, worker is in offline state.
        It need to be configured and after then it can be activated to be online.
        This function make all required things and when worker is online it broadcast new worker in network.
        """
        wrknfo = self.DB.worker_get(worker_id)

        # all configuration of worker should be there
        pass
        # send information to worker to start processing tasks
        msg = {'message': messages.CTL_CALL, 'method': 'start'}
        res = send_and_receive_response(wrknfo['addr'], msg)
        LOG.debug("Local worker [%s] on [%s] is now online" %
                  (wrknfo['service'], wrknfo['addr']))
        # broadcast new worker state
        self.DB.worker_set_state(worker_id, True)
Exemple #7
0
 def run(self):
     if not self.__skip_loading_modules:
         self.__load_modules()
     self.status = 1
     LOG.debug("Service [%s] starting." % self.servicename)
     # before run...
     self._worker_just_started()
     self.__greens = []
     self.__greens.append(gevent.spawn(self.loop.loop))
     self.__greens.append(gevent.spawn(self.heartbeat_loop))
     try:
         gevent.joinall(self.__greens)
     finally:
         self.stop()
         self.close()
         # just finished working
         self._worker_just_stopped()
    def register_task(self, name, func, timeout, anonymous, permissions,
                      close_dj_conn):
        # task name
        if name is None:
            name = func.__name__
        if name in self.db:
            if func == self.db[name]['func']:
                return
            c = "Task %s is already registered" % name
            LOG.critical(c)
            raise Exception(c)

        # timeout
        if not timeout is None:
            if type(timeout) != int:
                raise Exception("Timeout must be integer value")
            if timeout < 0:
                raise Exception("Timeout cannot be negative value")

        # extra params for task
        doc = func.__doc__
        if not doc is None:
            doc = doc.strip()
        taskdata = {
            'func': func,
            'doc': doc,  # docstring
            'timeout': timeout,  # timeout in seconds
            'anon': anonymous,  # can task be executed without authorisation
            'perms': permissions,  # permissions required to call task
            'close_djconn': close_dj_conn,  # close django connection on exit
            'res_succ': 0,  # successful calls
            'res_err': 0,  # error finishing calls
            'res_tout': 0,  # timed out calls
        }

        self.db[name] = taskdata
        LOG.debug("Registered task %s" % name)
    def loop(self):
        while self.is_running:
            # receive data
            msgdata, addr = self.SOCK.recvfrom(4096)
            # skip own broadcast messages
            if addr[0] == self.own_ip:
                continue
            # deserialize
            try:
                msgdata, repreq = self.serializer.deserialize(msgdata)
            except NotOurMessage:
                continue
            except Exception:
                LOG.warning("Message from broadcast deserialisation error")
                LOG.debug(
                    "Broken message body dump in hex (only first 1024 bytes):\n%s"
                    % msgdata[:1024].encode("hex"))
                continue

            # own broadcast from another interface
            try:
                if msgdata['__sid__'] == self.ID:
                    continue
            except KeyError:
                continue

            # message type
            try:
                msg = msgdata['message']
            except KeyError:
                LOG.debug("Decoded message is incomplete. Message dump: %s" %
                          repr(msgdata))
                continue
            # find handler
            try:
                handler = self._msgdb[msg]
            except KeyError:
                # unknown messages are ignored silently
                LOG.warning("Unknown message received [%s]" % msg)
                LOG.debug("Message body dump:\n%s" % repr(msgdata))
                continue
            # run handler
            try:
                handler(msgdata)
            except Exception as e:
                # log exception details
                excname = e.__class__.__name__
                # traceback
                tback = traceback.format_exc()
                try:
                    tback = unicode(tback, "utf-8")
                except:
                    tback = repr(tback)
                # error message
                errmsg = e.message
                try:
                    errmsg = unicode(errmsg, "utf-8")
                except:
                    errmsg = repr(errmsg)
                # log & clean
                LOG.error(
                    "Exception [%s] when processing message [%s]. Message: %s."
                    % (excname, msg, errmsg))
                LOG.debug("Message dump: %s" % repr(msgdata))
                LOG.debug(tback)
                del excname, tback, errmsg
    def __init__(self, silentinit=False):
        # binary header
        import struct
        #
        # header format
        #  ! big-endian
        #  6s - 6 characters of header prefix
        #  h - protocol version (=1)
        #  L - unsigned long - data size
        #  16s - 16 bytes of initial vector (used for encryption)
        #  ? - boolean - compression flag
        #  H - unsigned short - how many bytes trim from data
        #  ? - boolean - response required
        #
        self.header = struct.Struct(b"!6s h L 16s ? H ?")
        self._version = 1

        # encrypted or not...
        if settings.ENCRYPTION:
            # encrypter, decrypter
            from kasaya.core.protocol.encryption import encrypt, decrypt
            self.encrypt = encrypt
            self.decrypt = decrypt

            self.serialize = self._encrypted_serialize
            self.deserialize = self._encrypted_deserialize
            self._passwd = _load_passwd()
        else:
            self.serialize = self._plain_serialize
            self.deserialize = self._plain_deserialize

        # servicebus name
        import sys
        py3 = sys.version_info >= (3, 0)
        if py3:
            busname = bytes(settings.SERVICE_BUS_NAME, "ascii")
            busname += b" " * (6 - len(busname))
        else:
            busname = str(settings.SERVICE_BUS_NAME)
            busname += b" " * (6 - len(busname))
        self._busname = busname

        # transport protocol
        if settings.TRANSPORT_PROTOCOL == "pickle":
            from kasaya.core.protocol.transport.tr_pickle import bin_2_data, data_2_bin

        elif settings.TRANSPORT_PROTOCOL == "bson":
            if py3:
                # python 3 bson
                from kasaya.core.protocol.transport.tr_bson3 import bin_2_data, data_2_bin
            else:
                # python 2 bson
                from kasaya.core.protocol.transport.tr_bson2 import bin_2_data, data_2_bin

        elif settings.TRANSPORT_PROTOCOL == "msgpack":
            from kasaya.core.protocol.transport.tr_msgpack import bin_2_data, data_2_bin
        else:
            raise Exception("Unsupported transport protocol %s" %
                            settings.TRANSPORT_PROTOCOL)

        self.bin_2_data = bin_2_data
        self.data_2_bin = data_2_bin

        if silentinit:
            return
        LOG.debug(
            "Service bus is configured to use %s as transport protocol." %
            settings.TRANSPORT_PROTOCOL)
Exemple #11
0
    def __init__(self,
                 servicename=None,
                 load_config=True,
                 skip_loading_modules=False):

        super(WorkerDaemon, self).__init__()

        # config loader
        if servicename is None:
            load_config = True
        if load_config:
            LOG.info("Loading service.conf")
            if servicename is None:
                servicename = self.__load_config()
        self.servicename = servicename
        self.__skip_loading_modules = skip_loading_modules

        # worker status
        # 0 - initialized
        # 1 - starting or waiting for reconnect to kasaya
        # 2 - working
        # 3 - stopping
        # 4 - dead
        self.status = 0
        LOG.info("Starting worker daemon, service [%s], ID: [%s]" %
                 (self.servicename, self.ID))
        adr = "tcp://%s:%i" % (settings.BIND_WORKER_TO,
                               settings.WORKER_MIN_PORT)
        self.loop = MessageLoop(adr, settings.WORKER_MAX_PORT)

        add_event_handler("sender-conn-closed", self.kasaya_connection_broken)
        add_event_handler("sender-conn-started",
                          self.kasaya_connection_started)

        self.SYNC = KasayaLocalClient(autoreconnect=True, sessionid=self.ID)
        self.SYNC.setup(servicename, self.loop.address, self.ID, os.getpid())
        LOG.debug("Binded to socket [%s]" %
                  (",".join(self.loop.binded_ip_list())))

        # registering handlers
        self.loop.register_message(messages.SYNC_CALL,
                                   self.handle_sync_call,
                                   raw_msg_response=True)
        self.loop.register_message(messages.CTL_CALL,
                                   self.handle_control_request)
        # heartbeat
        self.__hbloop = True
        #exposing methods
        self.exposed_methods = []
        # control tasks
        self.ctl = ControlTasks()
        self.ctl.register_task("stop", self.CTL_stop)
        self.ctl.register_task("start", self.CTL_start)
        self.ctl.register_task("stats", self.CTL_stats)
        self.ctl.register_task("tasks", self.CTL_methods)
        # stats
        #self._sb_errors = 0 # internal service bus errors
        self._tasks_succes = 0  # succesfully processed tasks
        self._tasks_error = 0  # task which triggered exceptions
        self._tasks_nonex = 0  # non existing tasks called
        self._tasks_control = 0  # control tasks received
        self._start_time = datetime.datetime.now()  # time of worker start
Exemple #12
0
 def kasaya_connection_started(self, addr):
     """
     This will be called when connection with kasaya is started
     """
     LOG.debug("Connected to %s", addr)
     self.SYNC.notify_worker_live(self.status)
Exemple #13
0
    def connection_handler(self, SOCK, address):
        ssid = None
        while True:
            try:
                msgdata, resreq = _receive_and_deserialize(
                    SOCK, self.serializer)
            except (NoData, ConnectionClosed):
                return

            try:
                msg = msgdata['message']
            except KeyError:
                if resreq:
                    self._send_noop(SOCK)
                LOG.debug("Decoded message is incomplete. Message dump: %s" %
                          repr(msgdata))
                continue

            # message SET_SESSION_ID is special message
            # it never return reply and is not propagated to handlers
            if msg == messages.SET_SESSION_ID:
                try:
                    ssid = msgdata['id']
                    #print("conn session id" , address, ssid)
                except KeyError:
                    pass
                if resreq:
                    self._send_noop(SOCK)
                continue

            # find message handler
            try:
                handler, rawmsg = self._msgdb[msg]
            except KeyError:
                # unknown messages are ignored
                if resreq:
                    self._send_noop(SOCK)
                LOG.warning("Unknown message received [%s]" % msg)
                LOG.debug("Message body dump:\n%s" % repr(msgdata))
                continue

            # run handler
            try:
                result = handler(msgdata)
            except Exception as e:
                result = exception_serialize(e, False)
                LOG.info(
                    "Exception [%s] when processing message [%s]. Message: %s."
                    % (result['name'], msg, result['description']))
                #LOG.debug("Message dump: %s" % repr(msgdata) )
                #LOG.debug(result['traceback'])

                if not resreq:
                    # if response is not required, then don't send exceptions
                    continue

                _serialize_and_send(
                    SOCK,
                    self.serializer,
                    exception_serialize(e, False),
                    resreq=False,  # response never require another response
                )
                continue

            # response is not expected, throw result and back to loop
            if not resreq:
                continue

            try:
                # send result
                if rawmsg:
                    _serialize_and_send(
                        SOCK,
                        self.serializer,
                        result,
                        resreq=False,
                    )
                else:
                    _serialize_and_send(SOCK,
                                        self.serializer, {
                                            "message": messages.RESULT,
                                            "result": result,
                                        },
                                        resreq=False)
            except ConnectionClosed:
                return