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)
    def test_must_provide_entrypoint_for_certain_runtimes_only(self, runtime):

        if runtime in RUNTIMES_WITH_ENTRYPOINT:
            result = LambdaContainer._get_entry_point(runtime, self.debug_options)
            self.assertIsNotNone(result, "{} runtime must provide entrypoint".format(runtime))
        else:
            with self.assertRaises(DebuggingNotSupported):
                LambdaContainer._get_entry_point(runtime, self.debug_options)
    def test_debug_port_is_created_on_host(self):

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

        with self._create(container):

            container.start()

            # After container is started, query the container to make sure it is bound to the right ports
            port_binding = self.docker_client.api.port(container.id, self.debug_port)
            self.assertIsNotNone(port_binding, "Container must be bound to a port on host machine")
            self.assertEquals(1, len(port_binding), "Only one port must be bound to the container")
            self.assertEquals(port_binding[0]["HostPort"], str(self.debug_port))
    def test_go_runtime_returns_additional_options(self, runtime):
        expected = {"security_opt": ["seccomp:unconfined"], "cap_add": ["SYS_PTRACE"]}

        debug_options = DebugContext(debug_port=1235)

        result = LambdaContainer._get_additional_options(runtime, debug_options)
        self.assertEquals(result, expected)
    def test_must_map_same_port_on_host_and_container(self):

        debug_options = DebugContext(debug_port=12345)
        expected = {debug_options.debug_port: debug_options.debug_port}
        result = LambdaContainer._get_exposed_ports(debug_options)

        self.assertEquals(expected, result)
    def test_additional_volumes_returns_volume_with_debugger_path_is_set(self):
        expected = {'/somepath': {"bind": "/tmp/lambci_debug_files", "mode": "ro"}}

        debug_options = DebugContext(debug_port=1234, debugger_path='/somepath')

        result = LambdaContainer._get_additional_volumes(debug_options)
        self.assertEquals(result, expected)
    def test_must_return_lambci_image(self):

        expected = "lambci/lambda:foo"

        image_builder = Mock()
        image_builder.build.return_value = expected

        self.assertEquals(LambdaContainer._get_image(image_builder, 'foo', []), expected)
    def test_debug_arg_must_be_split_by_spaces_and_appended_to_entrypoint(self, runtime):
        """
        Debug args list is appended starting at second position in the array
        """
        expected_debug_args = ["a=b", "c=d", "e=f"]
        result = LambdaContainer._get_entry_point(runtime, self.debug_options)
        actual = result[1:4]

        self.assertEquals(actual, expected_debug_args)
    def test_container_is_attached_to_network(self):
        layer_downloader = LayerDownloader("./", "./")
        image_builder = LambdaImage(layer_downloader, False, False)
        container = LambdaContainer(self.runtime, self.handler, self.code_dir, self.layers, image_builder)

        with self._network_create() as network:

            # Ask the container to attach to the network
            container.network_id = network.id
            with self._create(container):

                container.start()

                # Now that the container has been created, it would be connected to the network
                # Fetch the latest information about this network from server
                network.reload()

                self.assertEquals(1, len(network.containers))
                self.assertEquals(container.id, network.containers[0].id)
    def test_must_return_lambci_image_with_debug(self):
        debug_options = DebugContext(debug_ports=[1235], debugger_path="a", debug_args="a=b c=d e=f")

        expected = "lambci/lambda:foo"

        image_builder = Mock()
        image_builder.build.return_value = expected

        self.assertEqual(LambdaContainer._get_image(image_builder, "foo", [], debug_options), expected)

        image_builder.build.assert_called_with("foo", [], True)
    def test_must_return_lambci_image_without_debug(self):
        debug_options = DebugContext()

        expected = "lambci/lambda:foo"

        image_builder = Mock()
        image_builder.build.return_value = expected

        self.assertEqual(LambdaContainer._get_image(image_builder, "foo", [], debug_options), expected)

        image_builder.build.assert_called_with("foo", [], False)
Example #12
0
    def test_must_provide_entrypoint_for_certain_runtimes_only(self, runtime):
        if runtime in RUNTIMES_WITH_ENTRYPOINT_OVERRIDES:
            result, _ = LambdaContainer._get_debug_settings(
                runtime, self.debug_options)
            self.assertIsNotNone(
                result, "{} runtime must provide entrypoint".format(runtime))

        elif runtime in RUNTIMES_WITH_DEBUG_ENV_VARS_ONLY:
            result, _ = LambdaContainer._get_debug_settings(
                runtime, self.debug_options)
            self.assertEquals(
                ["/var/rapid/aws-lambda-rie", "--log-level", "error"],
                result,
                "{} runtime must not override entrypoint".format(runtime),
            )

        else:
            with self.assertRaises(DebuggingNotSupported):
                LambdaContainer._get_debug_settings(runtime,
                                                    self.debug_options)
    def test_debug_arg_must_be_split_by_spaces_and_appended_to_entrypoint(
            self, runtime):
        """
        Debug args list is appended starting at second position in the array
        """
        expected_debug_args = ["a=b", "c=d", "e=f"]
        result, _ = LambdaContainer._get_debug_settings(
            runtime, self.debug_options)
        actual = result[1:4]

        self.assertEqual(actual, expected_debug_args)
    def test_go_runtime_returns_additional_options(self, runtime):
        expected = {
            "security_opt": ["seccomp:unconfined"],
            "cap_add": ["SYS_PTRACE"]
        }

        debug_options = DebugContext(debug_ports=[1235])

        result = LambdaContainer._get_additional_options(
            runtime, debug_options)
        self.assertEqual(result, expected)
    def test_must_configure_container_properly(
        self,
        get_additional_volumes_mock,
        get_additional_options_mock,
        get_debug_settings_mock,
        get_exposed_ports_mock,
        get_image_mock,
    ):

        image = "image"
        ports = {"a": "b"}
        addtl_options = {}
        addtl_volumes = {}
        debug_settings = ([1, 2, 3], {"a": "b"})
        expected_cmd = [self.handler]

        get_image_mock.return_value = image
        get_exposed_ports_mock.return_value = ports
        get_debug_settings_mock.return_value = debug_settings
        get_additional_options_mock.return_value = addtl_options
        get_additional_volumes_mock.return_value = addtl_volumes
        expected_env_vars = {**self.env_var, **debug_settings[1]}

        image_builder_mock = Mock()

        container = LambdaContainer(
            self.runtime,
            self.handler,
            self.code_dir,
            layers=[],
            image_builder=image_builder_mock,
            env_vars=self.env_var,
            memory_mb=self.memory_mb,
            debug_options=self.debug_options,
        )

        self.assertEqual(image, container._image)
        self.assertEqual(expected_cmd, container._cmd)
        self.assertEqual("/var/task", container._working_dir)
        self.assertEqual(self.code_dir, container._host_dir)
        self.assertEqual(ports, container._exposed_ports)
        self.assertEqual(debug_settings[0], container._entrypoint)
        self.assertEqual(expected_env_vars, container._env_vars)
        self.assertEqual(self.memory_mb, container._memory_limit_mb)

        get_image_mock.assert_called_with(image_builder_mock, self.runtime, [],
                                          self.debug_options)
        get_exposed_ports_mock.assert_called_with(self.debug_options)
        get_debug_settings_mock.assert_called_with(self.runtime,
                                                   self.debug_options)
        get_additional_options_mock.assert_called_with(self.runtime,
                                                       self.debug_options)
        get_additional_volumes_mock.assert_called_with(self.runtime,
                                                       self.debug_options)
Example #16
0
    def test_container_is_attached_to_network(self):
        layer_downloader = LayerDownloader("./", "./")
        image_builder = LambdaImage(layer_downloader, False, False)
        container = LambdaContainer(self.runtime, self.handler, self.code_dir,
                                    self.layers, image_builder)

        with self._network_create() as network:

            # Ask the container to attach to the network
            container.network_id = network.id
            with self._create(container):

                container.start()

                # Now that the container has been created, it would be connected to the network
                # Fetch the latest information about this network from server
                network.reload()

                self.assertEqual(1, len(network.containers))
                self.assertEqual(container.id, network.containers[0].id)
Example #17
0
    def test_debug_port_is_created_on_host(self):

        container = LambdaContainer(self.runtime,
                                    self.handler,
                                    self.code_dir,
                                    debug_port=self.debug_port)

        with self._create(container):

            container.start()

            # After container is started, query the container to make sure it is bound to the right ports
            port_binding = self.docker_client.api.port(container.id,
                                                       self.debug_port)
            self.assertIsNotNone(
                port_binding,
                "Container must be bound to a port on host machine")
            self.assertEquals(1, len(port_binding),
                              "Only one port must be bound to the container")
            self.assertEquals(port_binding[0]["HostPort"],
                              str(self.debug_port))
    def test_debug_arg_must_be_split_by_spaces_and_appended_to_bootstrap_based_entrypoint(
            self, runtime):
        """
        Debug args list is appended as arguments to bootstrap-args, which is past the fourth position in the array
        """
        expected_debug_args = ["a=b", "c=d", "e=f"]
        result, _ = LambdaContainer._get_debug_settings(
            runtime, self.debug_options)
        actual = result[4:5][0]

        self.assertTrue(
            all(debug_arg in actual for debug_arg in expected_debug_args))
Example #19
0
    def test_must_fail_for_unsupported_runtime(self):

        runtime = "foo"

        image_builder_mock = Mock()

        with self.assertRaises(ValueError) as context:
            LambdaContainer(runtime, self.handler, self.code_dir, [],
                            image_builder_mock)

        self.assertEqual(str(context.exception),
                         "Unsupported Lambda runtime foo")
    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)
Example #21
0
    def test_additional_volumes_returns_volume_with_debugger_path_is_set(self):
        expected = {
            "/somepath": {
                "bind": "/tmp/lambci_debug_files",
                "mode": "ro"
            }
        }

        debug_options = DebugContext(debug_ports=[1234],
                                     debugger_path="/somepath")

        result = LambdaContainer._get_additional_volumes(debug_options)
        self.assertEqual(result, expected)
Example #22
0
    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

        Returns
        -------
        Container
            the created container
        """
        # Generate a dictionary of environment variable key:values
        env_vars = function_config.env_vars.resolve()

        code_dir = self._get_code_dir(function_config.code_abs_path)
        container = LambdaContainer(
            function_config.runtime,
            function_config.imageuri,
            function_config.handler,
            function_config.packagetype,
            function_config.imageconfig,
            code_dir,
            function_config.layers,
            self._image_builder,
            memory_mb=function_config.memory,
            env_vars=env_vars,
            debug_options=debug_context,
            container_host=container_host,
            container_host_interface=container_host_interface,
        )
        try:
            # create the container.
            self._container_manager.create(container)
            return container

        except KeyboardInterrupt:
            LOG.debug("Ctrl+C was pressed. Aborting container creation")
            raise
    def test_debug_port_is_created_on_host(self):

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

        with self._create(container):

            container.start()

            # After container is started, query the container to make sure it is bound to the right ports
            port_binding = self.docker_client.api.port(container.id,
                                                       self.debug_port)
            self.assertIsNotNone(
                port_binding,
                "Container must be bound to a port on host machine")
            self.assertEqual(1, len(port_binding),
                             "Only one port must be bound to the container")
            self.assertEqual(port_binding[0]["HostPort"], str(self.debug_port))
    def test_must_return_build_image(self):
        expected = "amazon/aws-sam-cli-emulation-image-foo:rapid-x.y.z"

        image_builder = Mock()
        image_builder.build.return_value = expected

        self.assertEqual(
            LambdaContainer._get_image(
                lambda_image=image_builder,
                runtime="foo",
                packagetype=ZIP,
                image=None,
                layers=[],
            ),
            expected,
        )

        image_builder.build.assert_called_with("foo", ZIP, None, [])
Example #25
0
    def test_basic_creation(self):
        """
        A docker container must be successfully created
        """
        layer_downloader = LayerDownloader("./", "./")
        image_builder = LambdaImage(layer_downloader, False, False)
        container = LambdaContainer(self.runtime, self.handler, self.code_dir, self.layers, image_builder)

        self.assertIsNone(container.id, "Container must not have ID before creation")

        # Create the container and verify its properties
        with self._create(container):
            self.assertIsNotNone(container.id, "Container must have an ID")

            # Call Docker API to make sure container indeed exists
            actual_container = self.docker_client.containers.get(container.id)
            self.assertEquals(actual_container.status, "created")
            self.assertTrue(self.expected_docker_image in actual_container.image.tags,
                            "Image name of the container must be " + self.expected_docker_image)
Example #26
0
    def test_must_fail_for_unsupported_runtime(self):

        runtime = "foo"

        image_builder_mock = Mock()

        with self.assertRaises(ValueError) as context:
            LambdaContainer(
                runtime=runtime,
                imageuri=self.imageuri,
                handler=self.handler,
                packagetype=self.packagetype,
                image_config=self.image_config,
                code_dir=self.code_dir,
                layers=[],
                lambda_image=image_builder_mock,
            )

        self.assertEqual(str(context.exception), "Unsupported Lambda runtime foo")
    def test_must_configure_container_properly(self,
                                               get_additional_volumes_mock,
                                               get_additional_options_mock,
                                               get_entry_point_mock,
                                               get_exposed_ports_mock,
                                               get_image_mock):

        image = "image"
        ports = {"a": "b"}
        addtl_options = {}
        addtl_volumes = {}
        entry = [1, 2, 3]
        expected_cmd = [self.handler]

        get_image_mock.return_value = image
        get_exposed_ports_mock.return_value = ports
        get_entry_point_mock.return_value = entry
        get_additional_options_mock.return_value = addtl_options
        get_additional_volumes_mock.return_value = addtl_volumes

        container = LambdaContainer(self.runtime,
                                    self.handler,
                                    self.code_dir,
                                    env_vars=self.env_var,
                                    memory_mb=self.memory_mb,
                                    debug_options=self.debug_options)

        self.assertEquals(image, container._image)
        self.assertEquals(expected_cmd, container._cmd)
        self.assertEquals("/var/task", container._working_dir)
        self.assertEquals(self.code_dir, container._host_dir)
        self.assertEquals(ports, container._exposed_ports)
        self.assertEquals(entry, container._entrypoint)
        self.assertEquals(self.env_var, container._env_vars)
        self.assertEquals(self.memory_mb, container._memory_limit_mb)

        get_image_mock.assert_called_with(self.runtime)
        get_exposed_ports_mock.assert_called_with(self.debug_options)
        get_entry_point_mock.assert_called_with(self.runtime, self.debug_options)
        get_additional_options_mock.assert_called_with(self.runtime, self.debug_options)
        get_additional_volumes_mock.assert_called_with(self.debug_options)
    def test_must_return_lambci_image_without_debug(self):
        debug_options = DebugContext()

        expected = "lambci/lambda:foo"

        image_builder = Mock()
        image_builder.build.return_value = expected

        self.assertEqual(
            LambdaContainer._get_image(
                lambda_image=image_builder,
                runtime="foo",
                packagetype=ZIP,
                image=None,
                layers=[],
                debug_options=debug_options,
            ),
            expected,
        )

        image_builder.build.assert_called_with("foo", ZIP, None, [], False)
    def test_must_return_lambci_image_with_debug(self):
        debug_options = DebugContext(debug_ports=[1235],
                                     debugger_path="a",
                                     debug_args="a=b c=d e=f")

        expected = "lambci/lambda:foo"

        image_builder = Mock()
        image_builder.build.return_value = expected

        self.assertEqual(
            LambdaContainer._get_image(
                lambda_image=image_builder,
                runtime="foo",
                packagetype=ZIP,
                image=None,
                layers=[],
                debug_options=debug_options,
            ),
            expected,
        )

        image_builder.build.assert_called_with("foo", ZIP, None, [], True)
Example #30
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 #31
0
    def test_no_additional_options_when_debug_options_is_none(self):
        debug_options = DebugContext(debug_ports=None)

        result = LambdaContainer._get_additional_options(
            "runtime", debug_options)
        self.assertIsNone(result)
Example #32
0
    def test_must_provide_entrypoint_even_without_debug_args(self, runtime):
        debug_options = DebugContext(debug_ports=[1235], debug_args=None)
        result = LambdaContainer._get_entry_point(runtime, debug_options)

        self.assertIsNotNone(result)
Example #33
0
 def test_must_skip_if_debug_port_is_not_specified(self):
     self.assertIsNone(
         LambdaContainer._get_entry_point("runtime", None),
         "Must not provide entrypoint if debug port is not given")
Example #34
0
    def test_must_skip_if_port_is_not_given(self):

        self.assertIsNone(LambdaContainer._get_exposed_ports(None),
                          "No ports should be exposed")
Example #35
0
    def test_none_ports_specified(self):

        debug_options = DebugContext(debug_ports=None)
        result = LambdaContainer._get_exposed_ports(debug_options)

        self.assertEqual(None, result)
    def test_no_additional_options_when_debug_options_is_none(self):
        debug_options = DebugContext(debug_port=None)

        result = LambdaContainer._get_additional_options('runtime', debug_options)
        self.assertIsNone(result)
Example #37
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)
    def test_no_additional_volumes_when_debuggr_path_is_none(self):
        debug_options = DebugContext(debug_port=1234)

        result = LambdaContainer._get_additional_volumes(debug_options)
        self.assertIsNone(result)
    def test_must_skip_if_port_is_not_given(self):

        self.assertIsNone(LambdaContainer._get_exposed_ports(None), "No ports should be exposed")
    def test_default_value_returned_for_non_go_runtimes(self, runtime):
        debug_options = DebugContext(debug_port=1235)

        result = LambdaContainer._get_additional_options(runtime, debug_options)
        self.assertEquals(result, {})
Example #41
0
    def test_default_value_returned_for_non_go_runtimes(self, runtime):
        debug_options = DebugContext(debug_ports=[1235])

        result = LambdaContainer._get_additional_options(
            runtime, debug_options)
        self.assertEqual(result, {})
Example #42
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 #43
0
    def test_no_additional_volumes_when_debuggr_path_is_none(self):
        debug_options = DebugContext(debug_ports=[1234])

        result = LambdaContainer._get_additional_volumes(debug_options)
        self.assertIsNone(result)
Example #44
0
    def test_empty_ports_list(self):

        debug_options = DebugContext(debug_ports=[])
        result = LambdaContainer._get_exposed_ports(debug_options)

        self.assertEqual(None, result)
 def test_must_skip_if_debug_port_is_not_specified(self):
     self.assertIsNone(LambdaContainer._get_entry_point("runtime", None),
                       "Must not provide entrypoint if debug port is not given")
    def test_must_provide_entrypoint_even_without_debug_args(self, runtime):
        debug_options = DebugContext(debug_port=1235, debug_args=None)
        result = LambdaContainer._get_entry_point(runtime, debug_options)

        self.assertIsNotNone(result)