Example #1
0
    def test_run_execution_image_type_function_timeout(self):
        execution_id = common.generate_unicode_uuid()
        function_id = common.generate_unicode_uuid()
        pod1 = mock.Mock()
        pod1.status.phase = ''
        self.k8s_v1_api.read_namespaced_pod.return_value = pod1

        result, output = self.manager.run_execution(
            execution_id, function_id, 0,
            identifier='fake_identifier',
            timeout=1
        )

        self.assertFalse(result)

        expected_output = {
            'output': 'Function execution timeout.',
            'duration': 1
        }
        self.assertEqual(expected_output, output)
Example #2
0
    def test_run_execution_image_type_function_read_pod_exception(self):
        self.k8s_v1_api.read_namespaced_pod.side_effect = RuntimeError
        execution_id = common.generate_unicode_uuid()
        function_id = common.generate_unicode_uuid()

        result, output = self.manager.run_execution(execution_id,
                                                    function_id,
                                                    0,
                                                    timeout=5)

        self.k8s_v1_api.read_namespaced_pod.assert_called_once_with(
            None, self.fake_namespace)
        self.k8s_v1_api.read_namespaced_pod_log.assert_not_called()
        self.assertFalse(result)

        expected_output = {
            'output': 'Function execution failed.',
            'duration': 0
        }
        self.assertEqual(expected_output, output)
Example #3
0
    def test_scaleup_function_service_internal_ip(self):
        pod = mock.Mock()
        pod.metadata.name = self.rand_name('pod', prefix=self.prefix)
        pod.metadata.labels = None
        list_pod_ret = mock.Mock()
        list_pod_ret.items = [pod]
        self.k8s_v1_api.list_namespaced_pod.return_value = list_pod_ret
        self.k8s_v1_api.create_namespaced_service.return_value = (
            self._create_service())
        self.k8s_v1_api.list_node.return_value = (
            self._create_nodes_with_internal_ip())
        runtime_id = common.generate_unicode_uuid()
        function_id = common.generate_unicode_uuid()

        pod_names, service_url = self.manager.scaleup_function(
            function_id, 0, identifier=runtime_id)

        self.assertEqual([pod.metadata.name], pod_names)
        self.assertEqual(
            'http://%s:%s' % (SERVICE_ADDRESS_INTERNAL, SERVICE_PORT),
            service_url)
Example #4
0
    def test_scaleup_function_service_already_exists(self):
        pod = mock.Mock()
        pod.metadata.name = self.rand_name('pod', prefix=self.prefix)
        pod.metadata.labels = {'pod1_key1': 'pod1_value1'}
        list_pod_ret = mock.Mock()
        list_pod_ret.items = [pod]
        self.k8s_v1_api.list_namespaced_pod.return_value = list_pod_ret
        exception = RuntimeError()
        exception.status = 409
        self.k8s_v1_api.create_namespaced_service.side_effect = exception
        self.k8s_v1_api.read_namespaced_service.return_value = (
            self._create_service())
        self.k8s_v1_api.list_node.return_value = (
            self._create_nodes_with_external_ip())
        runtime_id = common.generate_unicode_uuid()
        function_id = common.generate_unicode_uuid()

        pod_names, service_url = self.manager.scaleup_function(
            function_id, 0, identifier=runtime_id)

        # in _prepare_pod
        self.k8s_v1_api.read_namespaced_service.assert_called_once_with(
            'service-%s-0' % function_id, self.fake_namespace)
Example #5
0
    def test_scaledown_function(self, etcd_util_get_workers_mock,
                                etcd_util_delete_workers_mock):
        function_id = common.generate_unicode_uuid()
        etcd_util_get_workers_mock.return_value = [
            'worker_%d' % i for i in range(4)
        ]

        self.default_engine.scaledown_function(mock.Mock(), function_id)

        etcd_util_get_workers_mock.assert_called_once_with(function_id, 0)
        self.orchestrator.delete_worker.assert_called_once_with('worker_0')
        etcd_util_delete_workers_mock.assert_called_once_with(function_id,
                                                              'worker_0',
                                                              version=0)
Example #6
0
    def test_prepare_execution_with_image(self):
        function_id = common.generate_unicode_uuid()
        image = self.rand_name('image', prefix=self.prefix)
        identifier = (
            '%s-%s' %
            (common.generate_unicode_uuid(dashed=False), function_id))[:63]

        pod_name, url = self.manager.prepare_execution(function_id,
                                                       0,
                                                       rlimit=self.rlimit,
                                                       image=image,
                                                       identifier=identifier)

        self.assertEqual(identifier, pod_name)
        self.assertIsNone(url)

        # in _create_pod
        pod_body = self.manager.pod_template.render({
            'pod_name':
            identifier,
            'labels': {
                'function_id': function_id
            },
            'pod_image':
            image,
            'input': [],
            'req_cpu':
            str(cfg.CONF.resource_limits.default_cpu),
            'req_memory':
            str(cfg.CONF.resource_limits.default_memory),
            'limit_cpu':
            str(cfg.CONF.resource_limits.default_cpu),
            'limit_memory':
            str(cfg.CONF.resource_limits.default_memory)
        })
        self.k8s_v1_api.create_namespaced_pod.assert_called_once_with(
            self.fake_namespace, body=yaml.safe_load(pod_body))
Example #7
0
    def test_prepare_execution_service_internal_ip(self):
        pod = mock.Mock()
        pod.metadata.name = self.rand_name('pod', prefix=self.prefix)
        pod.metadata.labels = {'pod1_key1': 'pod1_value1'}
        list_pod_ret = mock.Mock()
        list_pod_ret.items = [pod]
        self.k8s_v1_api.list_namespaced_pod.return_value = list_pod_ret
        self.k8s_v1_api.create_namespaced_service.return_value = (
            self._create_service()
        )
        self.k8s_v1_api.list_node.return_value = (
            self._create_nodes_with_internal_ip()
        )
        runtime_id = common.generate_unicode_uuid()
        function_id = common.generate_unicode_uuid()

        pod_names, service_url = self.manager.prepare_execution(
            function_id, 0, rlimit=None, image=None, identifier=runtime_id,
            labels={'runtime_id': runtime_id})

        self.assertEqual(pod.metadata.name, pod_names)
        self.assertEqual(
            'http://%s:%s' % (SERVICE_ADDRESS_INTERNAL, SERVICE_PORT),
            service_url)
Example #8
0
    def test_prepare_execution_with_image_json_input(self):
        function_id = common.generate_unicode_uuid()
        image = self.rand_name('image', prefix='TestKubernetesManager')
        identifier = (
            '%s-%s' %
            (common.generate_unicode_uuid(dashed=False), function_id))[:63]
        fake_input = '["input_item3", "input_item4"]'

        pod_name, url = self.manager.prepare_execution(function_id,
                                                       0,
                                                       rlimit=self.rlimit,
                                                       image=image,
                                                       identifier=identifier,
                                                       input=fake_input)

        # in _create_pod
        pod_body = self.manager.pod_template.render({
            'pod_name':
            identifier,
            'labels': {
                'function_id': function_id
            },
            'pod_image':
            image,
            'input': ['input_item3', 'input_item4'],
            'req_cpu':
            str(cfg.CONF.resource_limits.default_cpu),
            'req_memory':
            str(cfg.CONF.resource_limits.default_memory),
            'limit_cpu':
            str(cfg.CONF.resource_limits.default_cpu),
            'limit_memory':
            str(cfg.CONF.resource_limits.default_memory)
        })
        self.k8s_v1_api.create_namespaced_pod.assert_called_once_with(
            self.fake_namespace, body=yaml.safe_load(pod_body))
Example #9
0
    def test_scaleup_function_multiple_workers(
        self,
        etcd_util_create_worker_mock,
        etcd_util_create_service_url_mock
    ):
        function_id = common.generate_unicode_uuid()
        runtime_id = common.generate_unicode_uuid()
        self.orchestrator.scaleup_function.return_value = (
            ['worker0', 'worker1'], 'url')

        self.default_engine.scaleup_function(
            mock.Mock(), function_id, 0, runtime_id, count=2
        )

        self.orchestrator.scaleup_function.assert_called_once_with(
            function_id, 0, identifier=runtime_id, count=2
        )
        # Two new workers are created.
        expected = [mock.call(function_id, 'worker0', version=0),
                    mock.call(function_id, 'worker1', version=0)]
        etcd_util_create_worker_mock.assert_has_calls(expected)
        etcd_util_create_service_url_mock.assert_called_once_with(
            function_id, 'url', version=0
        )
Example #10
0
    def test_delete_function_with_labels(self):
        services = mock.Mock()
        services.items = []
        labels = {'key1': 'value1', 'key2': 'value2'}
        selector = common.convert_dict_to_string(labels)
        self.k8s_v1_api.list_namespaced_service.return_value = services
        function_id = common.generate_unicode_uuid()

        self.manager.delete_function(function_id, 0, labels=labels)

        self.k8s_v1_api.list_namespaced_service.assert_called_once_with(
            self.fake_namespace, label_selector=selector)
        self.k8s_v1_api.delete_namespaced_service.assert_not_called()
        delete_pod = self.k8s_v1_api.delete_collection_namespaced_pod
        delete_pod.assert_called_once_with(self.fake_namespace,
                                           label_selector=selector)
Example #11
0
    def test_delete_function(self):
        # Deleting namespaced service is also tested in this.
        svc1 = mock.Mock()
        svc1_name = self.rand_name('service', prefix=self.prefix)
        svc1.metadata.name = svc1_name
        svc2 = mock.Mock()
        svc2_name = self.rand_name('service', prefix=self.prefix)
        svc2.metadata.name = svc2_name
        services = mock.Mock()
        services.items = [svc1, svc2]
        self.k8s_v1_api.list_namespaced_service.return_value = services
        function_id = common.generate_unicode_uuid()

        self.manager.delete_function(function_id, 0)

        args, kwargs = self.k8s_v1_api.list_namespaced_service.call_args
        self.assertIn(self.fake_namespace, args)
        self.assertIn(
            "function_id=%s" % function_id,
            kwargs.get("label_selector")
        )
        self.assertIn(
            "function_version=0",
            kwargs.get("label_selector")
        )

        delete_service_calls = [
            mock.call(svc1_name, self.fake_namespace, mock.ANY),
            mock.call(svc2_name, self.fake_namespace, mock.ANY)
        ]
        self.k8s_v1_api.delete_namespaced_service.assert_has_calls(
            delete_service_calls)
        self.assertEqual(
            2, self.k8s_v1_api.delete_namespaced_service.call_count
        )

        args, kwargs = self.k8s_v1_api.delete_collection_namespaced_pod. \
            call_args
        self.assertIn(self.fake_namespace, args)
        self.assertIn(
            "function_id=%s" % function_id,
            kwargs.get("label_selector")
        )
        self.assertIn(
            "function_version=0",
            kwargs.get("label_selector")
        )
Example #12
0
    def test_scaledown_function_multiple_workers(
            self, etcd_util_get_workers_mock, etcd_util_delete_workers_mock):
        function_id = common.generate_unicode_uuid()
        etcd_util_get_workers_mock.return_value = [
            'worker_%d' % i for i in range(4)
        ]

        self.default_engine.scaledown_function(mock.Mock(),
                                               function_id,
                                               count=2)

        etcd_util_get_workers_mock.assert_called_once_with(function_id, 0)
        # First two workers will be deleted.
        expected = [mock.call('worker_0'), mock.call('worker_1')]
        self.orchestrator.delete_worker.assert_has_calls(expected)
        self.assertEqual(2, self.orchestrator.delete_worker.call_count)
        expected = [
            mock.call(function_id, 'worker_0', version=0),
            mock.call(function_id, 'worker_1', version=0)
        ]
        etcd_util_delete_workers_mock.assert_has_calls(expected)
        self.assertEqual(2, etcd_util_delete_workers_mock.call_count)
Example #13
0
    def create_execution(self,
                         ctx,
                         execution_id,
                         function_id,
                         runtime_id,
                         input=None):
        LOG.info(
            'Creating execution. execution_id=%s, function_id=%s, '
            'runtime_id=%s, input=%s', execution_id, function_id, runtime_id,
            input)

        # FIXME(kong): Make the transaction range smaller.
        with db_api.transaction():
            execution = db_api.get_execution(execution_id)
            function = db_api.get_function(function_id)

            if function.service:
                func_url = '%s/execute' % function.service.service_url
                LOG.debug('Found service url for function: %s, url: %s',
                          function_id, func_url)

                data = {'input': input, 'execution_id': execution_id}
                r = self.session.post(func_url, json=data)
                res = r.json()

                LOG.debug('Finished execution %s', execution_id)

                success = res.pop('success')
                execution.status = status.SUCCESS if success else status.FAILED
                execution.logs = res.pop('logs', '')
                execution.output = res
                return

            source = function.code['source']
            image = None
            identifier = None
            labels = None

            if source == constants.IMAGE_FUNCTION:
                image = function.code['image']
                identifier = ('%s-%s' %
                              (common.generate_unicode_uuid(dashed=False),
                               function_id))[:63]
                labels = {'function_id': function_id}
            else:
                identifier = runtime_id
                labels = {'runtime_id': runtime_id}

            worker_name, service_url = self.orchestrator.prepare_execution(
                function_id,
                image=image,
                identifier=identifier,
                labels=labels,
                input=input,
                entry=function.entry,
                trust_id=function.trust_id)
            output = self.orchestrator.run_execution(
                execution_id,
                function_id,
                input=input,
                identifier=identifier,
                service_url=service_url,
            )

            logs = ''
            # Execution log is only available for non-image source execution.
            if service_url:
                logs = output.pop('logs', '')
                success = output.pop('success')
            else:
                # If the function is created from docker image, the output is
                # direct output, here we convert to a dict to fit into the db
                # schema.
                output = {'output': output}
                success = True

            LOG.debug('Finished execution. execution_id=%s, output=%s',
                      execution_id, output)
            execution.output = output
            execution.logs = logs
            execution.status = status.SUCCESS if success else status.FAILED

            # No service is created in orchestrator for single container.
            if not image:
                mapping = {
                    'function_id': function_id,
                    'service_url': service_url,
                }
                db_api.create_function_service_mapping(mapping)
                worker = {
                    'function_id': function_id,
                    'worker_name': worker_name
                }
                db_api.create_function_worker(worker)
Example #14
0
    def create_execution(self,
                         ctx,
                         execution_id,
                         function_id,
                         function_version,
                         runtime_id,
                         input=None):
        LOG.info(
            'Creating execution. execution_id=%s, function_id=%s, '
            'function_version=%s, runtime_id=%s, input=%s', execution_id,
            function_id, function_version, runtime_id, input)

        function = db_api.get_function(function_id)
        source = function.code['source']
        rlimit = {'cpu': function.cpu, 'memory_size': function.memory_size}
        image = None
        identifier = None
        labels = None
        svc_url = None
        is_image_source = source == constants.IMAGE_FUNCTION

        # Auto scale workers if needed
        if not is_image_source:
            try:
                svc_url = self.function_load_check(function_id,
                                                   function_version,
                                                   runtime_id)
            except exc.OrchestratorException as e:
                utils.handle_execution_exception(execution_id, str(e))
                return

        temp_url = etcd_util.get_service_url(function_id, function_version)
        svc_url = svc_url or temp_url
        if svc_url:
            func_url = '%s/execute' % svc_url
            LOG.debug(
                'Found service url for function: %s(version %s), execution: '
                '%s, url: %s', function_id, function_version, execution_id,
                func_url)

            data = utils.get_request_data(CONF, function_id, function_version,
                                          execution_id, rlimit, input,
                                          function.entry, function.trust_id,
                                          self.qinling_endpoint)
            success, res = utils.url_request(self.session, func_url, body=data)

            utils.finish_execution(execution_id,
                                   success,
                                   res,
                                   is_image_source=is_image_source)
            return

        if source == constants.IMAGE_FUNCTION:
            image = function.code['image']
            # Be consistent with k8s naming convention
            identifier = (
                '%s-%s' %
                (common.generate_unicode_uuid(dashed=False), function_id))[:63]
        else:
            identifier = runtime_id
            labels = {'runtime_id': runtime_id}

        try:
            # For image function, it will be executed inside this method; for
            # package type function it only sets up underlying resources and
            # get a service url. If the service url is already created
            # beforehand, nothing happens.
            _, svc_url = self.orchestrator.prepare_execution(
                function_id,
                function_version,
                rlimit=rlimit,
                image=image,
                identifier=identifier,
                labels=labels,
                input=input,
            )
        except exc.OrchestratorException as e:
            utils.handle_execution_exception(execution_id, str(e))
            return

        # For image type function, read the worker log; For package type
        # function, invoke and get log
        success, res = self.orchestrator.run_execution(
            execution_id,
            function_id,
            function_version,
            rlimit=rlimit if svc_url else None,
            input=input,
            identifier=identifier,
            service_url=svc_url,
            entry=function.entry,
            trust_id=function.trust_id)

        utils.finish_execution(execution_id,
                               success,
                               res,
                               is_image_source=is_image_source)
Example #15
0
    def create_execution(self,
                         ctx,
                         execution_id,
                         function_id,
                         runtime_id,
                         input=None):
        LOG.info(
            'Creating execution. execution_id=%s, function_id=%s, '
            'runtime_id=%s, input=%s', execution_id, function_id, runtime_id,
            input)

        function = db_api.get_function(function_id)
        source = function.code['source']
        image = None
        identifier = None
        labels = None
        svc_url = None

        # Auto scale workers if needed
        if source != constants.IMAGE_FUNCTION:
            svc_url = self.function_load_check(function_id, runtime_id)

        temp_url = etcd_util.get_service_url(function_id)
        svc_url = svc_url or temp_url
        if svc_url:
            func_url = '%s/execute' % svc_url
            LOG.debug(
                'Found service url for function: %s, execution: %s, url: %s',
                function_id, execution_id, func_url)

            data = utils.get_request_data(CONF, function_id, execution_id,
                                          input, function.entry,
                                          function.trust_id,
                                          self.qinling_endpoint)
            success, res = utils.url_request(self.session, func_url, body=data)
            success = success and res.pop('success')

            LOG.debug('Finished execution %s, success: %s', execution_id,
                      success)

            db_api.update_execution(
                execution_id, {
                    'status': status.SUCCESS if success else status.FAILED,
                    'logs': res.pop('logs', ''),
                    'result': res
                })
            return

        if source == constants.IMAGE_FUNCTION:
            image = function.code['image']
            identifier = (
                '%s-%s' %
                (common.generate_unicode_uuid(dashed=False), function_id))[:63]
            labels = {'function_id': function_id}
        else:
            identifier = runtime_id
            labels = {'runtime_id': runtime_id}

        _, svc_url = self.orchestrator.prepare_execution(
            function_id,
            image=image,
            identifier=identifier,
            labels=labels,
            input=input,
        )
        success, res = self.orchestrator.run_execution(
            execution_id,
            function_id,
            input=input,
            identifier=identifier,
            service_url=svc_url,
            entry=function.entry,
            trust_id=function.trust_id)

        logs = ''
        # Execution log is only available for non-image source execution.
        if svc_url:
            logs = res.pop('logs', '')
            success = success and res.pop('success')
        else:
            # If the function is created from docker image, the result is
            # direct output, here we convert to a dict to fit into the db
            # schema.
            res = {'output': res}

        LOG.debug('Finished execution %s, success: %s', execution_id, success)

        db_api.update_execution(
            execution_id, {
                'status': status.SUCCESS if success else status.FAILED,
                'logs': logs,
                'result': res
            })