示例#1
0
 def notify(
     expect: bool,  # whether the message should make it
     max_attempts: Optional[int] = None,  # how many attempts to allow
     responses: List[Tuple[
         float,
         int]] = None,  # a list of (delay, http_status) tuples, one per attempt
     attempts=None
 ):  # expected number of attempts, currently only used to estimate the running time
     if responses is None:
         responses = [(0.0, 200)]
     verify = random.random() > .5
     notification_id = str(next(self.notification_id))
     body: JSON = dict(notification_id=notification_id,
                       responses=responses,
                       verify=verify)
     notification = Notification.create(
         notification_id=notification_id,
         subscription_id=str(random.choice(self.subscription_ids)),
         url=f"http://{self.address}:{self.port}/{notification_id}",
         method='POST',
         encoding='application/json',
         body=body,
         attempts=max_attempts,
         hmac_key=PostTestHandler.hmac_secret_key if verify else None,
         hmac_key_id='1234' if verify else None)
     nonlocal total_attempts
     total_attempts += min(
         notification.attempts,
         self.num_queues) if attempts is None else attempts
     (expected_receptions
      if expect else expected_misses).add(notification_id)
     self.notifier.enqueue(notification)
示例#2
0
 def enqueue(self, notification: Notification, queue_index: int = 0) -> None:
     require(notification.attempts is not None,
             "Cannot enqueue a notification whose `attempts` attribute is None", notification)
     if queue_index is None:
         # Enqueueing a notification into the failure queue does not consume an attempt.
         logger.info(f"Adding notification to '{self._queue_name(None)}' queue as requested: {notification}")
     elif notification.attempts > 0:
         queues_left = self.num_queues - queue_index
         if notification.attempts < queues_left:
             logger.info(f"Notification would expire before proceeding through the remaining queues (%i): %s",
                         queues_left, notification)
         # Enqueueing a notification into one of the regular queues consumes one attempt.
         notification = notification.spend_attempt()
         logger.info(f"Adding notification to queue {queue_index}: {notification}")
     else:
         logger.warning(f"Notification has no attempts left, giving up. Adding it to '{self._queue_name(None)}' "
                        f"instead of ({self._queue_name(queue_index)}) as originally requested: {notification}")
         queue_index = None
     queue = self._queue(queue_index)
     queue.send_message(**notification.to_sqs_message())
示例#3
0
    def _notify_subscriber(self, doc: BundleDocument, subscription: dict):
        transaction_id = str(uuid.uuid4())
        subscription_id = subscription['id']
        endpoint = Endpoint.from_subscription(subscription)

        payload = dict(transaction_id=transaction_id,
                       subscription_id=subscription_id,
                       es_query=subscription['es_query'],
                       match=dict(bundle_uuid=doc.fqid.uuid,
                                  bundle_version=doc.fqid.version))

        definitions = subscription.get('attachments')
        # Only mention attachments in the notification if the subscription does, too.
        if definitions is not None:
            payload['attachments'] = attachment.select(definitions, doc)

        if endpoint.encoding == 'application/json':
            body = payload
        elif endpoint.encoding == 'multipart/form-data':
            body = endpoint.form_fields.copy()
            body[endpoint.payload_form_field] = json.dumps(payload)
        else:
            raise ValueError(f"Encoding {endpoint.encoding} is not supported")

        try:
            hmac_key = subscription['hmac_secret_key']
        except KeyError:
            hmac_key = None
            hmac_key_id = None
        else:
            hmac_key = hmac_key.encode()
            hmac_key_id = subscription.get('hmac_key_id', "hca-dss:" + subscription_id)

        notification = Notification.create(notification_id=transaction_id,
                                           subscription_id=subscription_id,
                                           url=endpoint.callback_url,
                                           method=endpoint.method,
                                           encoding=endpoint.encoding,
                                           body=body,
                                           hmac_key=hmac_key,
                                           hmac_key_id=hmac_key_id,
                                           correlation_id=str(doc.fqid))
        if self.notifier:
            logger.info(f"Queueing asynchronous notification {notification} for bundle {doc.fqid}")
            self.notifier.enqueue(notification)
        else:
            logger.info(f"Synchronously sending notification {notification} about bundle {doc.fqid}")
            notification.deliver_or_raise()
示例#4
0
    def _worker(self, worker_index: int, queue_index: int, remaining_time: RemainingTime) -> None:

        queue = self._queue(queue_index)
        while self._timeout + self._sqs_polling_timeout < remaining_time.get():
            visibility_timeout = self._timeout + self._overhead
            messages = queue.receive_messages(MaxNumberOfMessages=1,
                                              WaitTimeSeconds=self._sqs_polling_timeout,
                                              AttributeNames=['All'],
                                              MessageAttributeNames=['*'],
                                              VisibilityTimeout=int(math.ceil(visibility_timeout)))
            if messages:
                assert len(messages) == 1
                message = messages[0]
                notification = Notification.from_sqs_message(message)
                logger.info(f'Worker {worker_index} received message from queue {queue_index} for {notification}')
                seconds_to_maturity = notification.queued_at + self._delays[queue_index] - time.time()
                if seconds_to_maturity > 0:
                    # Hide the message and sleep until it matures. These two measures prevent immature messages from
                    # continuously bouncing between the queue and the workers consuming it, thereby preventing
                    # unnecessary churn on the queue. Consider that other messages further up in the queue
                    # invariantly mature after the current message, ensuring that this wait does not limit throughput
                    # or increase latency.
                    #
                    # TODO: determine how this interacts with FIFO queues and message groups as those yield
                    # messages in an ordering that, while being strict with respect to a group, is only partial
                    # with respect to the entire queue. The above invariant may not hold globally for that reason.
                    #
                    # SQS ignores a request to change the VTO of a message if the total VTO would exceed the max.
                    # allowed value of 12 hours. To be safe, we subtract the initial VTO from the max VTO.
                    max_visibility_timeout = SQS_MAX_VISIBILITY_TIMEOUT - visibility_timeout
                    visibility_timeout = min(seconds_to_maturity, max_visibility_timeout)
                    logger.info(f'Worker {worker_index} hiding message from queue {queue_index} '
                                f'for another {visibility_timeout:.3f}s. '
                                f'It will be {seconds_to_maturity:.3f}s to maturity of {notification}.')
                    message.change_visibility(VisibilityTimeout=int(visibility_timeout))
                    time.sleep(min(seconds_to_maturity, remaining_time.get()))
                elif remaining_time.get() < visibility_timeout:
                    logger.info(f'Worker {worker_index} returning message to queue {queue_index}. '
                                f'There is not enough time left to deliver {notification}.')
                    message.change_visibility(VisibilityTimeout=0)
                else:
                    if not notification.deliver(timeout=self._timeout, attempt=queue_index):
                        self.enqueue(notification, self._next_queue_index(queue_index))
                    message.delete()
        else:
            logger.info(f"Exiting worker {worker_index} due to insufficient time left.")