def test_function_result_is_available_in_stdout_and_logs_in_stderr(self):

        # This is the JSON result from Lambda function
        # Convert to proper binary type to be compatible with Python 2 & 3
        expected_output = b'{"a":"b"}'
        expected_stderr = b"**This string is printed from Lambda function**"

        layer_downloader = LayerDownloader("./", "./")
        image_builder = LambdaImage(layer_downloader, False, False)
        container = LambdaContainer(self.runtime, self.handler, self.code_dir,
                                    self.layers, image_builder)

        stdout_stream = io.BytesIO()
        stderr_stream = io.BytesIO()

        stdout_stream_writer = StreamWriter(stdout_stream)
        stderr_stream_writer = StreamWriter(stderr_stream)

        with self._create(container):

            container.start()
            container.wait_for_logs(stdout=stdout_stream_writer,
                                    stderr=stderr_stream_writer)

            function_output = stdout_stream.getvalue()
            function_stderr = stderr_stream.getvalue()

            self.assertEqual(function_output.strip(), expected_output)
            self.assertIn(expected_stderr, function_stderr)
    def test_function_result_is_available_in_stdout_and_logs_in_stderr(self):

        # This is the JSON result from Lambda function
        # Convert to proper binary type to be compatible with Python 2 & 3
        expected_output = b'{"a":"b"}'
        expected_stderr = b"**This string is printed from Lambda function**"

        layer_downloader = LayerDownloader("./", "./")
        image_builder = LambdaImage(layer_downloader, False, False)
        container = LambdaContainer(self.runtime, self.handler, self.code_dir, self.layers, image_builder)

        stdout_stream = io.BytesIO()
        stderr_stream = io.BytesIO()

        stdout_stream_writer = StreamWriter(stdout_stream)
        stderr_stream_writer = StreamWriter(stderr_stream)

        with self._create(container):

            container.start()
            container.wait_for_logs(stdout=stdout_stream_writer, stderr=stderr_stream_writer)

            function_output = stdout_stream.getvalue()
            function_stderr = stderr_stream.getvalue()

            self.assertEquals(function_output.strip(), expected_output)
            self.assertIn(expected_stderr, function_stderr)
Example #3
0
    def test_function_result_is_available_in_stdout_and_logs_in_stderr(self):

        # This is the JSON result from Lambda function
        # Convert to proper binary type to be compatible with Python 2 & 3
        expected_output = six.binary_type('{"a":"b"}'.encode('utf-8'))
        expected_stderr = six.binary_type(
            "**This string is printed from Lambda function**".encode("utf-8"))

        container = LambdaContainer(self.runtime, self.handler, self.code_dir)
        stdout_stream = io.BytesIO()
        stderr_stream = io.BytesIO()

        with self._create(container):

            container.start()
            container.wait_for_logs(stdout=stdout_stream, stderr=stderr_stream)

            function_output = stdout_stream.getvalue()
            function_stderr = stderr_stream.getvalue()

            self.assertEquals(function_output.strip(), expected_output)
            self.assertIn(expected_stderr, function_stderr)
Example #4
0
    def invoke(self,
               function_config,
               event,
               debug_context=None,
               stdout=None,
               stderr=None):
        """
        Invoke the given Lambda function locally.

        ##### NOTE: THIS IS A LONG BLOCKING CALL #####
        This method will block until either the Lambda function completes or timed out, which could be seconds.
        A blocking call will block the thread preventing any other operations from happening. If you are using this
        method in a web-server or in contexts where your application needs to be responsive when function is running,
        take care to invoke the function in a separate thread. Co-Routines or micro-threads might not perform well
        because the underlying implementation essentially blocks on a socket, which is synchronous.

        :param FunctionConfig function_config: Configuration of the function to invoke
        :param event: String input event passed to Lambda function
        :param DebugContext debug_context: Debugging context for the function (includes port, args, and path)
        :param io.IOBase stdout: Optional. IO Stream to that receives stdout text from container.
        :param io.IOBase stderr: Optional. IO Stream that receives stderr text from container
        :raises Keyboard
        """
        timer = None

        # Update with event input
        environ = function_config.env_vars
        environ.add_lambda_event_body(event)
        # Generate a dictionary of environment variable key:values
        env_vars = environ.resolve()

        with self._get_code_dir(function_config.code_abs_path) as code_dir:
            container = LambdaContainer(function_config.runtime,
                                        function_config.handler,
                                        code_dir,
                                        function_config.layers,
                                        self._image_builder,
                                        memory_mb=function_config.memory,
                                        env_vars=env_vars,
                                        debug_options=debug_context)

            try:

                # Start the container. This call returns immediately after the container starts
                self._container_manager.run(container)

                # Setup appropriate interrupt - timeout or Ctrl+C - before function starts executing.
                #
                # Start the timer **after** container starts. Container startup takes several seconds, only after which,
                # our Lambda function code will run. Starting the timer is a reasonable approximation that function has
                # started running.
                timer = self._configure_interrupt(function_config.name,
                                                  function_config.timeout,
                                                  container,
                                                  bool(debug_context))

                # NOTE: BLOCKING METHOD
                # Block the thread waiting to fetch logs from the container. This method will return after container
                # terminates, either successfully or killed by one of the interrupt handlers above.
                container.wait_for_logs(stdout=stdout, stderr=stderr)

            except KeyboardInterrupt:
                # When user presses Ctrl+C, we receive a Keyboard Interrupt. This is especially very common when
                # container is in debugging mode. We have special handling of Ctrl+C. So handle KeyboardInterrupt
                # and swallow the exception. The ``finally`` block will also take care of cleaning it up.
                LOG.debug("Ctrl+C was pressed. Aborting Lambda execution")

            finally:
                # We will be done with execution, if either the execution completed or an interrupt was fired
                # Any case, cleanup the timer and container.
                #
                # If we are in debugging mode, timer would not be created. So skip cleanup of the timer
                if timer:
                    timer.cancel()
                self._container_manager.stop(container)
Example #5
0
    def invoke(self,
               function_config,
               event,
               debug_context=None,
               stdout=None,
               stderr=None):
        """
        Invoke the given Lambda function locally.

        ##### NOTE: THIS IS A LONG BLOCKING CALL #####
        This method will block until either the Lambda function completes or timed out, which could be seconds.
        A blocking call will block the thread preventing any other operations from happening. If you are using this
        method in a web-server or in contexts where your application needs to be responsive when function is running,
        take care to invoke the function in a separate thread. Co-Routines or micro-threads might not perform well
        because the underlying implementation essentially blocks on a socket, which is synchronous.

        :param FunctionConfig function_config: Configuration of the function to invoke
        :param event: String input event passed to Lambda function
        :param DebugContext debug_context: Debugging context for the function (includes port, args, and path)
        :param io.IOBase stdout: Optional. IO Stream to that receives stdout text from container.
        :param io.IOBase stderr: Optional. IO Stream that receives stderr text from container
        :raises Keyboard
        """
        timer = None

        # Update with event input
        environ = function_config.env_vars
        environ.add_lambda_event_body(event)
        # Generate a dictionary of environment variable key:values
        env_vars = environ.resolve()

        with self._get_code_dir(function_config.code_abs_path) as code_dir:
            container = LambdaContainer(function_config.runtime,
                                        function_config.handler,
                                        code_dir,
                                        function_config.layers,
                                        self._image_builder,
                                        memory_mb=function_config.memory,
                                        env_vars=env_vars,
                                        debug_options=debug_context)

            try:

                # Start the container. This call returns immediately after the container starts
                self._container_manager.run(container)

                # Setup appropriate interrupt - timeout or Ctrl+C - before function starts executing.
                #
                # Start the timer **after** container starts. Container startup takes several seconds, only after which,
                # our Lambda function code will run. Starting the timer is a reasonable approximation that function has
                # started running.
                timer = self._configure_interrupt(function_config.name,
                                                  function_config.timeout,
                                                  container,
                                                  bool(debug_context))

                # NOTE: BLOCKING METHOD
                # Block the thread waiting to fetch logs from the container. This method will return after container
                # terminates, either successfully or killed by one of the interrupt handlers above.
                container.wait_for_logs(stdout=stdout, stderr=stderr)

            except KeyboardInterrupt:
                # When user presses Ctrl+C, we receive a Keyboard Interrupt. This is especially very common when
                # container is in debugging mode. We have special handling of Ctrl+C. So handle KeyboardInterrupt
                # and swallow the exception. The ``finally`` block will also take care of cleaning it up.
                LOG.debug("Ctrl+C was pressed. Aborting Lambda execution")

            finally:
                # We will be done with execution, if either the execution completed or an interrupt was fired
                # Any case, cleanup the timer and container.
                #
                # If we are in debugging mode, timer would not be created. So skip cleanup of the timer
                if timer:
                    timer.cancel()
                self._container_manager.stop(container)