Exemplo n.º 1
0
class LambdaFunctionObserver_stop(TestCase):
    @patch("samcli.lib.utils.file_observer.FileObserver")
    @patch("samcli.lib.utils.file_observer.ImageObserver")
    def setUp(self, ImageObserverMock, FileObserverMock):
        self.on_change = Mock()
        self.image_observer_mock = Mock()
        ImageObserverMock.return_value = self.image_observer_mock
        self.file_observer_mock = Mock()
        FileObserverMock.return_value = self.file_observer_mock
        self.lambda_function_observer = LambdaFunctionObserver(self.on_change)

    def test_successfully_start_observing(self):
        self.lambda_function_observer.stop()
        self.file_observer_mock.stop.assert_called_once_with()
        self.image_observer_mock.stop.assert_called_once_with()
Exemplo n.º 2
0
class WarmLambdaRuntime(LambdaRuntime):
    """
    This class extends the LambdaRuntime class to add the Warm containers feature. This class handles the
    warm containers life cycle.
    """
    def __init__(self, container_manager, image_builder):
        """
        Initialize the Local Lambda runtime

        Parameters
        ----------
        container_manager samcli.local.docker.manager.ContainerManager
            Instance of the ContainerManager class that can run a local Docker container
        image_builder samcli.local.docker.lambda_image.LambdaImage
            Instance of the LambdaImage class that can create am image
        warm_containers bool
            Determines if the warm containers is enabled or not.
        """
        self._containers = {}

        self._observer = LambdaFunctionObserver(self._on_code_change)

        super().__init__(container_manager, image_builder)

    def create(self,
               function_config,
               debug_context=None,
               container_host=None,
               container_host_interface=None):
        """
        Create a new Container for the passed function, then store it in a dictionary using the function name,
        so it can be retrieved later and used in the other functions. Make sure to use the debug_context only
        if the function_config.name equals debug_context.debug-function or the warm_containers option is disabled

        Parameters
        ----------
        function_config FunctionConfig
            Configuration of the function to create a new Container for it.
        debug_context DebugContext
            Debugging context for the function (includes port, args, and path)
        container_host string
            Host of locally emulated Lambda container
        container_host_interface string
            Interface that Docker host binds ports to

        Returns
        -------
        Container
            the created container
        """

        # reuse the cached container if it is created
        container = self._containers.get(function_config.name, None)
        if container and container.is_created():
            LOG.info(
                "Reuse the created warm container for Lambda function '%s'",
                function_config.name)
            return container

        # debug_context should be used only if the function name is the one defined
        # in debug-function option
        if debug_context and debug_context.debug_function != function_config.name:
            LOG.debug(
                "Disable the debugging for Lambda Function %s, as the passed debug function is %s",
                function_config.name,
                debug_context.debug_function,
            )
            debug_context = None

        container = super().create(function_config, debug_context,
                                   container_host, container_host_interface)
        self._containers[function_config.name] = container

        self._observer.watch(function_config)
        self._observer.start()

        return container

    def _on_invoke_done(self, container):
        """
        Cleanup the created resources, just before the invoke function ends.
        In warm containers, the running containers will be closed just before the end of te command execution,
        so no action is done here

        Parameters
        ----------
        container: Container
           The current running container
        """

    def _configure_interrupt(self, function_name, timeout, container,
                             is_debugging):
        """
        When a Lambda function is executing, we setup certain interrupt handlers to stop the execution.
        Usually, we setup a function timeout interrupt to kill the container after timeout expires. If debugging though,
        we don't enforce a timeout. But we setup a SIGINT interrupt to catch Ctrl+C and terminate the container.

        Parameters
        ----------
        function_name: str
            Name of the function we are running
        timeout: int
            Timeout in seconds
        container: samcli.local.docker.container.Container
            Instance of a container to terminate
        is_debugging: bool
            Are we debugging?

        Returns
        -------
        threading.Timer
            Timer object, if we setup a timer. None otherwise
        """
        def timer_handler():
            # NOTE: This handler runs in a separate thread. So don't try to mutate any non-thread-safe data structures
            LOG.info("Function '%s' timed out after %d seconds", function_name,
                     timeout)

        def signal_handler(sig, frame):
            # NOTE: This handler runs in a separate thread. So don't try to mutate any non-thread-safe data structures
            LOG.info("Execution of function %s was interrupted", function_name)

        if is_debugging:
            LOG.debug("Setting up SIGTERM interrupt handler")
            signal.signal(signal.SIGTERM, signal_handler)
            return None

        # Start a timer, we'll use this to abort the function if it runs beyond the specified timeout
        LOG.debug("Starting a timer for %s seconds for function '%s'", timeout,
                  function_name)
        timer = threading.Timer(timeout, timer_handler, ())
        timer.start()
        return timer

    def clean_running_containers_and_related_resources(self):
        """
        Clean the running containers, the decompressed code dirs, and stop the created observer
        """
        LOG.debug("Terminating all running warm containers")
        for function_name, container in self._containers.items():
            LOG.debug(
                "Terminate running warm container for Lambda Function '%s'",
                function_name)
            self._container_manager.stop(container)
        self._clean_decompressed_paths()
        self._observer.stop()

    def _on_code_change(self, functions):
        """
        Handles the lambda function code change event. it determines if there is a real change in the code
        by comparing the checksum of the code path before and after the event.

        Parameters
        ----------
        functions: list [FunctionConfig]
            the lambda functions that their source code or images got changed
        """
        for function_config in functions:
            function_name = function_config.name
            resource = "source code" if function_config.packagetype == ZIP else f"{function_config.imageuri} image"
            LOG.info(
                "Lambda Function '%s' %s has been changed, terminate its warm container. "
                "The new container will be created in lazy mode",
                function_name,
                resource,
            )
            container = self._containers.get(function_name, None)
            if container:
                self._container_manager.stop(container)
                self._containers.pop(function_name, None)
            self._observer.unwatch(function_config)