Exemplo n.º 1
0
 def _get_execution_output(self, arn):
     sfns = boto3.client('stepfunctions')
     execution = sfns.describe_execution(executionArn=arn)
     status = execution['status']
     require(status in ('SUCCEEDED', 'RUNNING'), f'Unexpected execution status: {status}')
     self.assertIn('output', execution)
     return execution['output']
Exemplo n.º 2
0
    def __init__(self,
                 deployment_stage: str,
                 delays: List[float],
                 num_workers: int = None,
                 sqs_polling_timeout: int = 20,
                 timeout: float = 10.0,
                 overhead: float = 30.0) -> None:
        """
        Create a new notifier.

        :param deployment_stage: the name of the deployment stage that hosts the cloud resources used by this
        notifier. Multiple notifier instances created with the same value for this parameter will share those
        resources.

        :param delays: a list of delays in seconds before notification attempts. There will be one SQS queue and
        typically one worker thread per entry in this list. The first value is typically zero, unless the first
        attempt should be delayed as well. Multiple notifier instances created with the same value for the
        `deployment_stage` parameter should have the same value for the delays parameter. If they don't,
        the resulting behavior is unspecififed.

        :param num_workers: the number of notification workers pulling notifications from the queues and delivering
        them. The default is to have one worker per queue. If the configured number of workers is less than the
        number of queues, the notifier will assign the workers to a weighted random choice among the queues,
        prioritizing longer queues. If the configured number of workers is greater than the number of queues,
        the notifier will first ensure that every queue is assigned one worker and then assign the remaining workers
        to a weighted random choice of queues, again prioritizing longer ones. Multiple notifier instances,
        each with their own set of workers, can be pulling from the same set of queues. If the number of workers is
        less than the number of queues, multiple notifier instances are needed to ensure that every queue is served.

        :param sqs_polling_timeout: the integral number of seconds that a SQS ReceiveMessages request waits on any
        queue before returning

        :param timeout: the notification timeout in seconds. A notification attempt will be considered a failure if
        the HTTP/HTTPS request to the corresponding notification endpoint takes longer than the specified amount of
        time.

        :param overhead: The expected amount of time needed for ancillary work around one notification attempt. This
        does not include the time for making the actual HTTP(S) notification request but includes dequeueing and
        potentially requeueing the notification. If this value is too low, duplicate notification attempts will be
        made. If it is too high, throughput might be slightly impaired on the happy path, and considerably impaired
        when workers (not the endpoints) fail and messages have to be requed by SQS VisibilityTimeout mechanism.
        """
        super().__init__()
        require(len(delays) > 0, "Must specify at least one delay value.")
        require(num_workers is None or num_workers > 0, "If specified, the number of workers must be greater than 0.")
        self._deployment_stage = deployment_stage
        self._delays = delays
        self._num_workers = len(delays) if num_workers is None else num_workers
        self._sqs_polling_timeout = sqs_polling_timeout
        self._timeout = timeout
        self._overhead = overhead
Exemplo n.º 3
0
    def create(cls,
               notification_id: str,
               subscription_id: str,
               url: str,
               method: str,
               encoding: str,
               body: JSON,
               attempts: Optional[int] = None,
               hmac_key: Optional[bytes] = None,
               hmac_key_id: Optional[str] = None,
               correlation_id: Optional[str] = None) -> 'Notification':

        allowed_schemes = {'https'} if DeploymentStage.IS_PROD() else {
            'https', 'http'
        }
        scheme = urlparse(url).scheme
        require(
            scheme in allowed_schemes,
            f"The scheme '{scheme}' of URL '{url}' is prohibited. Allowed schemes are {allowed_schemes}."
        )

        if DeploymentStage.IS_PROD():
            hostname = urlparse(url).hostname
            for family, socktype, proto, canonname, sockaddr in socket.getaddrinfo(
                    hostname, port=None):
                require(
                    ipaddress.ip_address(sockaddr[0]).is_global,
                    f"The hostname in URL '{url}' resolves to a private IP")

        if attempts is None:
            attempts = Config.notification_attempts()

        return cls(
            notification_id=notification_id,
            subscription_id=subscription_id,
            url=url,
            method=method,
            encoding=encoding,
            body=cls._bin2sqs(json.dumps(body).encode()),
            attempts=attempts,
            hmac_key=None if hmac_key is None else cls._bin2sqs(hmac_key),
            hmac_key_id=hmac_key_id,
            queued_at=
            None,  # this field will be set when the message comes out of the queue
            correlation_id=correlation_id)
Exemplo n.º 4
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())
Exemplo n.º 5
0
 def __init__(self, amount: float) -> None:
     require(amount >= 0, "Inital remaining time must be non-negative")
     super().__init__(time.time() + amount)