Exemple #1
0
 def reject_publication(self, topic_name, is_gd):
     """ Raises an exception to indicate that a publication was rejected.
     """
     raise ServiceUnavailable(
         self.cid,
         'Publication rejected - would exceed {} max depth for `{}`'.format(
             'GD' if is_gd else 'non-GD', topic_name))
Exemple #2
0
    def handle(self):

        input = self.request.input
        pubsub = self.server.worker_store.pubsub  # type: PubSub
        endpoint_id = input.endpoint_id

        # Will return publication pattern matched or raise an exception that we don't catch
        endpoint_id, pub_pattern_matched = self.get_pub_pattern_matched(
            endpoint_id, input)

        try:
            topic = pubsub.get_topic_by_name(input.topic_name)  # type: Topic
        except KeyError:
            raise NotFound(self.cid,
                           'No such topic `{}`'.format(input.topic_name))

        # Reject the message is topic is not active
        if not topic.is_active:
            raise ServiceUnavailable(
                self.cid, 'Topic is inactive `{}`'.format(input.topic_name))

        # We always count time in milliseconds since UNIX epoch
        now = utcnow_as_ms()

        # Get all subscribers for that topic from local worker store
        all_subscriptions_by_topic = pubsub.get_subscriptions_by_topic(
            topic.name)
        len_all_sub = len(all_subscriptions_by_topic)

        # If we are to deliver the message(s) to only selected subscribers only,
        # filter out any unwated ones first.
        if input.deliver_to_sk:

            has_all = False
            subscriptions_by_topic = []

            # Get any matching subscriptions out of the whole set
            for sub in all_subscriptions_by_topic:
                if sub.sub_key in input.deliver_to_sk:
                    subscriptions_by_topic.append(sub)

        else:
            # We deliver this message to all of the topic's subscribers
            has_all = True
            subscriptions_by_topic = all_subscriptions_by_topic

        # This is only for logging purposes
        _subs_found = []

        # Assume that there are no missing servers for WSX clients by default
        has_wsx_no_server = False

        for sub in subscriptions_by_topic:

            # Prepare data for logging
            _subs_found.append({sub.sub_key: sub.sub_pattern_matched})

            # Is there at least one WSX subscriber to this topic that is currently not connected?
            # If so, later on we will need to turn all the messages into GD ones.
            sk_server = self.pubsub.get_sub_key_server(sub.sub_key)
            if not sk_server:
                if has_logger_pubsub_debug:
                    logger_pubsub.debug(
                        'No sk_server for sub_key `%s` among `%s`',
                        sub.sub_key,
                        sorted(self.pubsub.sub_key_servers.keys()))
                has_wsx_no_server = True  # We have found at least one WSX subscriber that has no server = it is not connected

        logger_pubsub.info(
            'Subscriptions for topic `%s` `%s` (a:%d, %d/%d, cid:%s)',
            topic.name, _subs_found, has_all, len(subscriptions_by_topic),
            len_all_sub, self.cid)

        # If input.data is a list, it means that it is a list of messages, each of which has its own
        # metadata. Otherwise, it's a string to publish and other input parameters describe it.
        data_list = input.data_list if input.data_list else None

        # Input messages may contain a mix of GD and non-GD messages, and we need to extract them separately.
        msg_id_list, gd_msg_list, non_gd_msg_list = self._get_messages_from_data(
            topic, data_list, input, now, pub_pattern_matched, endpoint_id,
            subscriptions_by_topic, has_wsx_no_server,
            input.get('reply_to_sk', None))

        # Create a wrapper object for all the input data and metadata
        ctx = PubCtx(self.server.cluster_id, pubsub, topic, endpoint_id,
                     pubsub.get_endpoint_by_id(endpoint_id).name,
                     subscriptions_by_topic, msg_id_list, gd_msg_list,
                     non_gd_msg_list, pub_pattern_matched,
                     input.get('ext_client_id'), False, now)

        # We have all the input data, publish the message(s) now
        self._publish(ctx)
Exemple #3
0
    def handle(self):

        input = self.request.input
        pubsub = self.server.worker_store.pubsub  # type: PubSub
        endpoint_id = input.endpoint_id

        # Will return publication pattern matched or raise an exception that we don't catch
        endpoint_id, pattern_matched = self.get_pattern_matched(
            endpoint_id, input)

        try:
            topic = pubsub.get_topic_by_name(input.topic_name)  # type: Topic
        except KeyError:
            raise NotFound(self.cid,
                           'No such topic `{}`'.format(input.topic_name))

        # We always count time in milliseconds since UNIX epoch
        now = utcnow_as_ms()

        # If input.data is a list, it means that it is a list of messages, each of which has its own
        # metadata. Otherwise, it's a string to publish and other input parameters describe it.
        data_list = input.data_list if input.data_list else None

        # Input messages may contain a mix of GD and non-GD messages, and we need to extract them separately.
        msg_id_list, gd_msg_list, non_gd_msg_list = self._get_messages_from_data(
            topic, data_list, input, now, pattern_matched, endpoint_id)

        len_gd_msg_list = len(gd_msg_list)
        has_gd_msg_list = bool(len_gd_msg_list)

        # Get all subscribers for that topic from local worker store
        subscriptions_by_topic = pubsub.get_subscriptions_by_topic(
            input.topic_name)

        # Just so it is not overlooked, log information that no subscribers are found for this topic
        if not subscriptions_by_topic:
            self.logger.warn('No subscribers found for topic `%s`',
                             input.topic_name)

        # Local aliases
        cluster_id = self.server.cluster_id
        has_pubsub_audit_log = self.server.has_pubsub_audit_log

        # This is initially unknown and will be set only for GD messages
        current_depth = 'n/a'

        # We don't always have GD messages on input so there is no point in running an SQL transaction otherwise.
        if has_gd_msg_list:

            # Operate under a global lock for that topic to rule out any interference from other publishers
            with self.lock('zato.pubsub.publish.%s' % input.topic_name):

                with closing(self.odb.session()) as session:

                    # Abort if max depth is already reached but check first if we should check the depth in this iteration.
                    topic.incr_gd_depth_check()

                    if topic.needs_gd_depth_check():

                        # Get current depth of this topic
                        current_depth = get_topic_depth(
                            session, cluster_id, topic.id)

                        if current_depth + len_gd_msg_list > topic.max_depth_gd:
                            raise ServiceUnavailable(
                                self.cid,
                                'Publication rejected - would have exceeded max depth for `{}`'
                                .format(topic.name))
                        else:

                            # This only updates the local variable
                            current_depth = current_depth + len_gd_msg_list

                    # This updates data in SQL
                    incr_topic_depth(session, cluster_id, topic.id, now,
                                     len_gd_msg_list)

                    # Publish messages - INSERT rows, each representing an individual message
                    insert_topic_messages(session, self.cid, gd_msg_list)

                    # Move messages to each subscriber's queue
                    if subscriptions_by_topic:
                        insert_queue_messages(session, cluster_id,
                                              subscriptions_by_topic,
                                              gd_msg_list, topic.id, now)

                    # Run an SQL commit for all queries above
                    session.commit()

                    # Update metadata in background
                    spawn(self._update_pub_metadata, cluster_id, topic.id,
                          endpoint_id, now, gd_msg_list, pattern_matched)

        # Either commit succeeded or there were no GD messages on input but in both cases we can now,
        # optionally, store data in pub/sub audit log.
        if has_pubsub_audit_log:

            msg = 'PUB. CID:`%s`, topic:`%s`, from:`%s`, ext_client_id:`%s`, pattern:`%s`, new_depth:`%s`' \
                  ', GD data:`%s`, non-GD data:`%s`'

            logger_audit.info(msg, self.cid, topic.name,
                              self.pubsub.endpoints[endpoint_id].name,
                              input.get('ext_client_id') or 'n/a',
                              pattern_matched, current_depth, gd_msg_list,
                              non_gd_msg_list)

        # Also in background, notify pub/sub task runners that there are new messages for them
        if subscriptions_by_topic:

            # Do not notify anything if there are no messages available - this is possible because,
            # for instance, we had a list of messages on input but a hook service filtered them out.
            if non_gd_msg_list or has_gd_msg_list:
                self._notify_pubsub_tasks(topic.id, topic.name,
                                          subscriptions_by_topic,
                                          non_gd_msg_list, has_gd_msg_list)

        # Return either a single msg_id if there was only one message published or a list of message IDs,
        # one for each message published.
        len_msg_list = len_gd_msg_list + len(non_gd_msg_list)

        if len_msg_list == 1:
            self.response.payload.msg_id = msg_id_list[0]
        else:
            self.response.payload.msg_id_list = msg_id_list