Ejemplo n.º 1
0
    def put(self, id, **kwargs):
        """Update function.

        - Function can not being used by job.
        - Function can not being executed.
        - (TODO)Function status should be changed so no execution will create
           when function is updating.
        """
        values = {}
        for key in UPDATE_ALLOWED:
            if kwargs.get(key) is not None:
                values.update({key: kwargs[key]})

        LOG.info('Update resource, params: %s',
                 values,
                 resource={
                     'type': self.type,
                     'id': id
                 })

        ctx = context.get_ctx()

        if set(values.keys()).issubset(set(['name', 'description'])):
            func_db = db_api.update_function(id, values)
        else:
            source = values.get('code', {}).get('source')
            with db_api.transaction():
                pre_func = db_api.get_function(id)

                if len(pre_func.jobs) > 0:
                    raise exc.NotAllowedException(
                        'The function is still associated with running job(s).'
                    )

                pre_source = pre_func.code['source']
                if source and source != pre_source:
                    raise exc.InputException(
                        "The function code type can not be changed.")
                if source == constants.IMAGE_FUNCTION:
                    raise exc.InputException(
                        "The image type function code can not be changed.")
                if (pre_source == constants.PACKAGE_FUNCTION
                        and values.get('package') is not None):
                    # Update the package data.
                    data = values['package'].file.read()
                    self.storage_provider.store(ctx.projectid, id, data)
                    values.pop('package')
                if pre_source == constants.SWIFT_FUNCTION:
                    swift_info = values['code'].get('swift', {})
                    self._check_swift(swift_info.get('container'),
                                      swift_info.get('object'))

                # Delete allocated resources in orchestrator.
                db_api.delete_function_service_mapping(id)
                self.engine_client.delete_function(id)

                func_db = db_api.update_function(id, values)

        pecan.response.status = 200
        return resources.Function.from_dict(func_db.to_dict()).to_dict()
Ejemplo n.º 2
0
    def create_function_version(self, old_version, function_id=None, **kwargs):
        if not function_id:
            function_id = self.create_function().id

        db_api.increase_function_version(function_id, old_version, **kwargs)
        db_api.update_function(function_id,
                               {"latest_version": old_version + 1})
Ejemplo n.º 3
0
    def test_create_execution_prepare_execution_exception(
            self, etcd_util_get_service_url_mock):
        """test_create_execution_prepare_execution_exception

        Create execution for image type function, prepare_execution method
        raises exception.
        """
        function = self.create_function()
        function_id = function.id
        runtime_id = function.runtime_id
        db_api.update_function(
            function_id, {
                'code': {
                    'source': constants.IMAGE_FUNCTION,
                    'image': self.rand_name('image', prefix=self.prefix)
                }
            })
        function = db_api.get_function(function_id)
        execution = self.create_execution(function_id=function_id)
        execution_id = execution.id
        prepare_execution = self.orchestrator.prepare_execution
        prepare_execution.side_effect = exc.OrchestratorException(
            'Exception in prepare_execution')
        etcd_util_get_service_url_mock.return_value = None

        self.default_engine.create_execution(mock.Mock(), execution_id,
                                             function_id, 0, runtime_id)

        execution = db_api.get_execution(execution_id)
        self.assertEqual(execution.status, status.ERROR)
        self.assertEqual(execution.logs, '')
        self.assertEqual(execution.result,
                         {'error': 'Function execution failed.'})
Ejemplo n.º 4
0
    def put(self, id, func):
        """Update function.

        Currently, we only support update name, description, entry.
        """
        values = {}
        for key in UPDATE_ALLOWED:
            if func.to_dict().get(key) is not None:
                values.update({key: func.to_dict()[key]})

        LOG.info('Update resource, params: %s',
                 values,
                 resource={
                     'type': self.type,
                     'id': id
                 })

        with db_api.transaction():
            func_db = db_api.update_function(id, values)
            if 'entry' in values:
                # Update entry will delete allocated resources in orchestrator.
                db_api.delete_function_service_mapping(id)
                self.engine_client.delete_function(id)

        return resources.Function.from_dict(func_db.to_dict())
Ejemplo n.º 5
0
    def test_handle_function_service_no_function_version(
            self, mock_etcd_url, mock_etcd_delete):
        db_func = self.create_function()
        function_id = db_func.id
        # Update function to simulate function execution
        db_api.update_function(function_id, {'count': 1})
        time.sleep(1.5)

        mock_etcd_url.return_value = 'http://localhost:37718'
        self.override_config('function_service_expiration', 1, 'engine')
        mock_engine = mock.Mock()

        periodics.handle_function_service_expiration(self.ctx, mock_engine)

        mock_engine.delete_function.assert_called_once_with(
            self.ctx, function_id, 0)
        mock_etcd_delete.assert_called_once_with(function_id, 0)
Ejemplo n.º 6
0
    def test_function_service_expiration_handler(self, mock_etcd_url,
                                                 mock_etcd_delete):
        db_func = self.create_function(runtime_id=None,
                                       prefix=self.TEST_CASE_NAME)
        function_id = db_func.id
        # Update function to simulate function execution
        db_api.update_function(function_id, {'count': 1})
        time.sleep(1.5)

        mock_k8s = mock.Mock()
        mock_etcd_url.return_value = 'http://localhost:37718'
        self.override_config('function_service_expiration', 1, 'engine')
        engine = default_engine.DefaultEngine(mock_k8s, CONF.qinling_endpoint)
        periodics.handle_function_service_expiration(self.ctx, engine)

        self.assertEqual(1, mock_k8s.delete_function.call_count)
        args, kwargs = mock_k8s.delete_function.call_args
        self.assertIn(function_id, args)
        mock_etcd_delete.assert_called_once_with(function_id)
Ejemplo n.º 7
0
    def test_handle_function_service_with_versioned_function_version_0(
            self, mock_srv_url, mock_etcd_delete):
        # This case tests that if a function has multiple versions, service
        # which serves executions of function version 0 is correctly handled
        # when expired.
        db_func = self.create_function()
        function_id = db_func.id
        self.create_function_version(0, function_id, description="new_version")
        # Simulate an execution using version 0
        db_api.update_function(function_id, {'count': 1})
        time.sleep(1.5)

        self.override_config('function_service_expiration', 1, 'engine')
        mock_srv_url.return_value = 'http://localhost:37718'
        mock_engine = mock.Mock()

        periodics.handle_function_service_expiration(self.ctx, mock_engine)

        mock_engine.delete_function.assert_called_once_with(
            self.ctx, function_id, 0)
        mock_etcd_delete.assert_called_once_with(function_id, 0)
Ejemplo n.º 8
0
    def put(self, id, **kwargs):
        """Update function.

        - Function can not being used by job.
        - Function can not being executed.
        - (TODO)Function status should be changed so no execution will create
           when function is updating.
        """
        values = {}

        try:
            for key in UPDATE_ALLOWED:
                if kwargs.get(key) is not None:
                    if key == "code":
                        kwargs[key] = json.loads(kwargs[key])
                    values.update({key: kwargs[key]})
        except Exception as e:
            raise exc.InputException("Invalid input, %s" % str(e))

        LOG.info('Update function %s, params: %s', id, values)
        ctx = context.get_ctx()

        if values.get('timeout'):
            common.validate_int_in_range('timeout', values['timeout'],
                                         CONF.resource_limits.min_timeout,
                                         CONF.resource_limits.max_timeout)

        db_update_only = set(['name', 'description', 'timeout'])
        if set(values.keys()).issubset(db_update_only):
            func_db = db_api.update_function(id, values)
        else:
            source = values.get('code', {}).get('source')
            md5sum = values.get('code', {}).get('md5sum')
            cpu = values.get('cpu')
            memory_size = values.get('memory_size')

            # Check cpu and memory_size values when updating.
            if cpu is not None:
                common.validate_int_in_range('cpu', values['cpu'],
                                             CONF.resource_limits.min_cpu,
                                             CONF.resource_limits.max_cpu)
            if memory_size is not None:
                common.validate_int_in_range('memory', values['memory_size'],
                                             CONF.resource_limits.min_memory,
                                             CONF.resource_limits.max_memory)

            with db_api.transaction():
                pre_func = db_api.get_function(id)

                if len(pre_func.jobs) > 0:
                    raise exc.NotAllowedException(
                        'The function is still associated with running job(s).'
                    )

                pre_source = pre_func.code['source']
                pre_md5sum = pre_func.code.get('md5sum')

                if source and source != pre_source:
                    raise exc.InputException(
                        "The function code type can not be changed.")

                if pre_source == constants.IMAGE_FUNCTION:
                    raise exc.InputException(
                        "The image type function code can not be changed.")

                # Package type function. 'code' and 'entry' make sense only if
                # 'package' is provided
                package_updated = False
                if (pre_source == constants.PACKAGE_FUNCTION
                        and values.get('package') is not None):
                    if md5sum and md5sum == pre_md5sum:
                        raise exc.InputException(
                            "The function code checksum is not changed.")

                    # Update the package data.
                    data = values['package'].file.read()
                    package_updated, md5sum = self.storage_provider.store(
                        ctx.projectid, id, data, md5sum=md5sum)
                    values.setdefault('code', {}).update({
                        "md5sum": md5sum,
                        "source": pre_source
                    })
                    values.pop('package')

                # Swift type function
                if (pre_source == constants.SWIFT_FUNCTION
                        and "swift" in values.get('code', {})):
                    swift_info = values['code']["swift"]

                    if not (swift_info.get('container')
                            or swift_info.get('object')):
                        raise exc.InputException(
                            "Either container or object must be provided for "
                            "swift type function update.")

                    new_swift_info = pre_func.code['swift']
                    new_swift_info.update(swift_info)

                    self._check_swift(new_swift_info.get('container'),
                                      new_swift_info.get('object'))

                    values['code'] = {
                        "source": pre_source,
                        "swift": new_swift_info
                    }

                # Delete allocated resources in orchestrator and etcd.
                self.engine_client.delete_function(id)
                etcd_util.delete_function(id)

                func_db = db_api.update_function(id, values)

            # Delete the old function package if needed
            if package_updated:
                self.storage_provider.delete(ctx.projectid, id, pre_md5sum)

        pecan.response.status = 200
        return resources.Function.from_db_obj(func_db).to_dict()
Ejemplo n.º 9
0
    def post(self, **kwargs):
        # When using image to create function, runtime_id is not a required
        # param.
        if not POST_REQUIRED.issubset(set(kwargs.keys())):
            raise exc.InputException(
                'Required param is missing. Required: %s' % POST_REQUIRED)
        LOG.info("Creating function, params: %s", kwargs)

        values = {
            'name':
            kwargs.get('name'),
            'description':
            kwargs.get('description'),
            'runtime_id':
            kwargs.get('runtime_id'),
            'code':
            json.loads(kwargs['code']),
            'entry':
            kwargs.get('entry', 'main.main'),
            'cpu':
            kwargs.get('cpu', CONF.resource_limits.default_cpu),
            'memory_size':
            kwargs.get('memory_size', CONF.resource_limits.default_memory),
            'timeout':
            kwargs.get('timeout', CONF.resource_limits.default_timeout),
        }

        common.validate_int_in_range('timeout', values['timeout'],
                                     CONF.resource_limits.min_timeout,
                                     CONF.resource_limits.max_timeout)
        common.validate_int_in_range('cpu', values['cpu'],
                                     CONF.resource_limits.min_cpu,
                                     CONF.resource_limits.max_cpu)
        common.validate_int_in_range('memory', values['memory_size'],
                                     CONF.resource_limits.min_memory,
                                     CONF.resource_limits.max_memory)

        source = values['code'].get('source')
        if not source or source not in CODE_SOURCE:
            raise exc.InputException(
                'Invalid code source specified, available sources: %s' %
                ', '.join(CODE_SOURCE))

        if source != constants.IMAGE_FUNCTION:
            if not kwargs.get('runtime_id'):
                raise exc.InputException('"runtime_id" must be specified.')

            runtime = db_api.get_runtime(kwargs['runtime_id'])
            if runtime.status != 'available':
                raise exc.InputException('Runtime %s is not available.' %
                                         kwargs['runtime_id'])

        store = False
        create_trust = True
        if source == constants.PACKAGE_FUNCTION:
            store = True
            md5sum = values['code'].get('md5sum')
            data = kwargs['package'].file.read()
        elif source == constants.SWIFT_FUNCTION:
            swift_info = values['code'].get('swift', {})

            if not (swift_info.get('container') and swift_info.get('object')):
                raise exc.InputException("Both container and object must be "
                                         "provided for swift type function.")

            self._check_swift(swift_info.get('container'),
                              swift_info.get('object'))
        else:
            create_trust = False
            values['entry'] = None

        if cfg.CONF.pecan.auth_enable and create_trust:
            try:
                values['trust_id'] = keystone_util.create_trust().id
                LOG.debug('Trust %s created', values['trust_id'])
            except Exception:
                raise exc.TrustFailedException(
                    'Trust creation failed for function.')

        # Create function and store the package data inside a db transaction so
        # that the function won't be created if any error happened during
        # package store.
        with db_api.transaction():
            func_db = db_api.create_function(values)
            if store:
                try:
                    ctx = context.get_ctx()
                    _, actual_md5 = self.storage_provider.store(ctx.projectid,
                                                                func_db.id,
                                                                data,
                                                                md5sum=md5sum)
                    values['code'].update({"md5sum": actual_md5})
                    func_db = db_api.update_function(func_db.id, values)
                except Exception as e:
                    LOG.exception("Failed to store function package.")
                    keystone_util.delete_trust(values['trust_id'])
                    raise e

        pecan.response.status = 201
        return resources.Function.from_db_obj(func_db).to_dict()
Ejemplo n.º 10
0
    def test_create_execution_image_type_function(self, mock_svc_url):
        """Create 2 executions for an image type function."""
        function = self.create_function()
        function_id = function.id
        runtime_id = function.runtime_id
        db_api.update_function(
            function_id, {
                'code': {
                    'source': constants.IMAGE_FUNCTION,
                    'image': self.rand_name('image', prefix=self.prefix)
                }
            })
        function = db_api.get_function(function_id)
        execution_1 = self.create_execution(function_id=function_id)
        execution_1_id = execution_1.id
        execution_2 = self.create_execution(function_id=function_id)
        execution_2_id = execution_2.id
        mock_svc_url.return_value = None
        self.orchestrator.prepare_execution.return_value = (mock.Mock(), None)
        self.orchestrator.run_execution.side_effect = [(True, {
            'duration': 5,
            'logs': 'fake log'
        }), (False, {
            'duration': 0,
            'error': 'Function execution failed.'
        })]

        # Create two executions, with different results
        self.default_engine.create_execution(mock.Mock(), execution_1_id,
                                             function_id, 0, runtime_id)
        self.default_engine.create_execution(mock.Mock(),
                                             execution_2_id,
                                             function_id,
                                             0,
                                             runtime_id,
                                             input='input')

        get_service_url_calls = [
            mock.call(function_id, 0),
            mock.call(function_id, 0)
        ]
        mock_svc_url.assert_has_calls(get_service_url_calls)

        prepare_calls = [
            mock.call(function_id,
                      0,
                      rlimit=self.rlimit,
                      image=function.code['image'],
                      identifier=mock.ANY,
                      labels=None,
                      input=None),
            mock.call(function_id,
                      0,
                      rlimit=self.rlimit,
                      image=function.code['image'],
                      identifier=mock.ANY,
                      labels=None,
                      input='input')
        ]
        self.orchestrator.prepare_execution.assert_has_calls(prepare_calls)

        run_calls = [
            mock.call(execution_1_id,
                      function_id,
                      0,
                      rlimit=None,
                      input=None,
                      identifier=mock.ANY,
                      service_url=None,
                      entry=function.entry,
                      trust_id=function.trust_id),
            mock.call(execution_2_id,
                      function_id,
                      0,
                      rlimit=None,
                      input='input',
                      identifier=mock.ANY,
                      service_url=None,
                      entry=function.entry,
                      trust_id=function.trust_id)
        ]
        self.orchestrator.run_execution.assert_has_calls(run_calls)

        execution_1 = db_api.get_execution(execution_1_id)
        execution_2 = db_api.get_execution(execution_2_id)

        self.assertEqual(status.SUCCESS, execution_1.status)
        self.assertEqual('fake log', execution_1.logs)
        self.assertEqual({"duration": 5}, execution_1.result)
        self.assertEqual(status.FAILED, execution_2.status)
        self.assertEqual('', execution_2.logs)
        self.assertEqual({
            'duration': 0,
            'error': 'Function execution failed.'
        }, execution_2.result)