def _check_services(self, timeout, initial_retry_delay=0.01, max_delay=1): """Check that all Localstack services are running and accessible. Does exponential backoff up to `max_delay`. Args: timeout (float): Number of seconds to wait for services to be available. initial_retry_delay (float, optional): Initial retry delay value in seconds. Will be multiplied by `2^n` for each retry. Default: 0.01 max_delay (float, optional): Max time in seconds to wait between checking service availability. Default: 1 Returns: None Raises: pytest_localstack.exceptions.TimeoutError: If not all services started before `timeout` was reached. """ services = set(self.services) num_retries = 0 start_time = time.time() while services and (time.time() - start_time) < timeout: for service_name in list( services ): # list() because set may change during iteration try: service_checks.SERVICE_CHECKS[service_name](self) services.discard(service_name) except exceptions.ServiceError as e: if (time.time() - start_time) >= timeout: six.raise_from( exceptions.TimeoutError( "Localstack service not started: {0}".format( service_name)), e, ) if services: delay = (2**num_retries) * initial_retry_delay if delay > max_delay: delay = max_delay time.sleep(delay) num_retries += 1
def start(self, timeout=60): """Start the Localstack container. Args: timeout (float, optional): Wait at most this many seconds for the Localstack services to start. Default is 1 minute. Raises: pytest_localstack.exceptions.TimeoutError: If *timeout* was reached before all Localstack services were available. docker.errors.APIError: If the Docker daemon returns an error. """ if self._container is not None: raise exceptions.ContainerAlreadyStartedError(self) logger.debug("Starting Localstack container %s", self.container_name) logger.debug("%r running starting hooks", self) plugin.manager.hook.session_starting(session=self) image_name = self.image_name + ":" + self.localstack_version if self.pull_image: logger.debug("Pulling docker image %r", image_name) self.docker_client.images.pull(image_name) start_time = time.time() services = ",".join("%s:%s" % pair for pair in self.services.items()) kinesis_error_probability = "%f" % self.kinesis_error_probability dynamodb_error_probability = "%f" % self.dynamodb_error_probability use_ssl = str(self.use_ssl).lower() self._container = self.docker_client.containers.run( image_name, name=self.container_name, detach=True, auto_remove=self.auto_remove, environment={ "DEFAULT_REGION": self.region_name, "SERVICES": services, "KINESIS_ERROR_PROBABILITY": kinesis_error_probability, "DYNAMODB_ERROR_PROBABILITY": dynamodb_error_probability, "USE_SSL": use_ssl, }, ports={port: None for port in self.services.values()}, ) logger.debug( "Started Localstack container %s (id: %s)", self.container_name, self._container.short_id, ) # Tail container logs container_logger = logger.getChild("containers.%s" % self._container.short_id) self._stdout_tailer = container.DockerLogTailer( self._container, container_logger.getChild("stdout"), self.container_log_level, stdout=True, stderr=False, ) self._stdout_tailer.start() self._stderr_tailer = container.DockerLogTailer( self._container, container_logger.getChild("stderr"), self.container_log_level, stdout=False, stderr=True, ) self._stderr_tailer.start() try: timeout_remaining = timeout - (time.time() - start_time) if timeout_remaining <= 0: raise exceptions.TimeoutError( "Container took too long to start.") self._check_services(timeout_remaining) logger.debug("%r running started hooks", self) plugin.manager.hook.session_started(session=self) logger.debug("%r finished started hooks", self) except exceptions.TimeoutError: if self._container is not None: self.stop(0.1) raise