Esempio n. 1
0
    def post(self, **kwargs):
        LOG.info("Creating function, params=%s", kwargs)

        if not POST_REQUIRED.issubset(set(kwargs.keys())):
            raise exc.InputException(
                'Required param is missing. Required: %s' % POST_REQUIRED)

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

        values = {
            'name': kwargs['name'],
            'description': kwargs.get('description', None),
            'runtime_id': kwargs['runtime_id'],
            'code': json.loads(kwargs['code']),
            'entry': kwargs.get('entry', 'main'),
        }

        if values['code'].get('package', False):
            data = kwargs['package'].file.read()

        ctx = context.get_ctx()

        with db_api.transaction():
            func_db = db_api.create_function(values)

            self.storage_provider.store(ctx.projectid, func_db.id, data)

        pecan.response.status = 201
        return resources.Function.from_dict(func_db.to_dict()).to_dict()
Esempio n. 2
0
    def store(self, project_id, function, data, md5sum=None):
        """Store the function package data to local file system.

        :param project_id: Project ID.
        :param function: Function ID.
        :param data: Package file content.
        :param md5sum: The MD5 provided by the user.
        :return: MD5 value of the package.
        """
        LOG.debug('Store package, function: %s, project: %s', function,
                  project_id)

        project_path = os.path.join(self.base_path, project_id)
        fileutils.ensure_tree(project_path)

        # Check md5
        md5_actual = common.md5(content=data)
        if md5sum and md5_actual != md5sum:
            raise exc.InputException("Package md5 mismatch.")

        # The md5 is contained in the package path.
        new_func_zip = os.path.join(project_path, '%s.zip.new' % function)
        func_zip = os.path.join(project_path,
                                PACKAGE_NAME_TEMPLATE % (function, md5_actual))

        # Store package
        with open(new_func_zip, 'wb') as fd:
            fd.write(data)

        if not zipfile.is_zipfile(new_func_zip):
            fileutils.delete_if_exists(new_func_zip)
            raise exc.InputException("Package is not a valid ZIP package.")

        os.rename(new_func_zip, func_zip)
        return md5_actual
Esempio n. 3
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()
Esempio n. 4
0
def validate_job(params):
    first_time = params.get('first_execution_time')
    pattern = params.get('pattern')
    count = params.get('count')
    start_time = timeutils.utcnow()

    if not (first_time or pattern):
        raise exc.InputException(
            'Pattern or first_execution_time must be specified.')

    if first_time:
        first_time = validate_next_time(first_time)
        if not pattern and count and count > 1:
            raise exc.InputException(
                'Pattern must be provided if count is greater than 1.')

        next_time = first_time
        if not (pattern or count):
            count = 1

    if pattern:
        validate_pattern(pattern)
        if not first_time:
            next_time = croniter.croniter(pattern, start_time).get_next(
                datetime.datetime)

    return first_time, next_time, count
Esempio n. 5
0
    def post(self, **kwargs):
        LOG.info("Creating %s, params: %s", self.type, 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)

        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'),
        }

        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
        if values['code']['source'] == constants.PACKAGE_FUNCTION:
            store = True
            data = kwargs['package'].file.read()
        elif values['code']['source'] == constants.SWIFT_FUNCTION:
            swift_info = values['code'].get('swift', {})
            self._check_swift(swift_info.get('container'),
                              swift_info.get('object'))

        if cfg.CONF.pecan.auth_enable:
            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.')

        with db_api.transaction():
            func_db = db_api.create_function(values)

            if store:
                ctx = context.get_ctx()

                self.storage_provider.store(ctx.projectid, func_db.id, data)

        pecan.response.status = 201
        return resources.Function.from_dict(func_db.to_dict()).to_dict()
Esempio n. 6
0
    def _check_swift(self, container, object):
        # Auth needs to be enabled because qinling needs to check swift
        # object using user's credential.
        if not CONF.pecan.auth_enable:
            raise exc.InputException('Swift object not supported.')

        if not swift_util.check_object(container, object):
            raise exc.InputException('Object does not exist in Swift.')
Esempio n. 7
0
    def put(self, id, job):
        """Update job definition.

        1. Can not update a finished job.
        2. Can not change job type.
        3. Allow to pause a one-shot job and resume before its first execution
           time.
        """
        values = {}
        for key in UPDATE_ALLOWED:
            if job.to_dict().get(key) is not None:
                values.update({key: job.to_dict()[key]})

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

        new_status = values.get('status')
        pattern = values.get('pattern')
        next_execution_time = values.get('next_execution_time')

        job_db = db_api.get_job(id)

        if job_db.status in [status.DONE, status.CANCELLED]:
            raise exc.InputException('Can not update a finished job.')

        if pattern:
            if not job_db.pattern:
                raise exc.InputException('Can not change job type.')
            jobs.validate_pattern(pattern)
        elif pattern == '' and job_db.pattern:
            raise exc.InputException('Can not change job type.')

        valid_states = [status.RUNNING, status.CANCELLED, status.PAUSED]
        if new_status and new_status not in valid_states:
            raise exc.InputException('Invalid status.')

        if next_execution_time:
            values['next_execution_time'] = jobs.validate_next_time(
                next_execution_time)
        elif (job_db.status == status.PAUSED and new_status == status.RUNNING):
            p = job_db.pattern or pattern

            if not p:
                # Check if the next execution time for one-shot job is still
                # valid.
                jobs.validate_next_time(job_db.next_execution_time)
            else:
                # Update next_execution_time for recurring job.
                values['next_execution_time'] = croniter.croniter(
                    p, timeutils.utcnow()).get_next(datetime.datetime)

        updated_job = db_api.update_job(id, values)
        return resources.Job.from_dict(updated_job.to_dict())
Esempio n. 8
0
def validate_int_in_range(name, value, min_allowed, max_allowed):
    try:
        value_int = int(value)
    except ValueError:
        raise exc.InputException(
            'Invalid %s resource specified. An integer is required.' % name)

    if (value_int < min_allowed or value_int > max_allowed):
        raise exc.InputException(
            '%s resource limitation not within the allowable range: '
            '%s ~ %s(%s).' % (name, min_allowed, max_allowed,
                              'millicpu' if name == 'cpu' else 'bytes'))
Esempio n. 9
0
def validate_int_in_range(name, value, min_allowed, max_allowed):
    unit_mapping = {"cpu": "millicpu", "memory": "bytes", "timeout": "seconds"}

    try:
        value_int = int(value)
    except ValueError:
        raise exc.InputException(
            'Invalid %s resource specified. An integer is required.' % name)

    if (value_int < min_allowed or value_int > max_allowed):
        raise exc.InputException(
            '%s resource limitation not within the allowable range: '
            '%s ~ %s(%s).' %
            (name, min_allowed, max_allowed, unit_mapping[name]))
Esempio n. 10
0
def validate_next_time(next_execution_time):
    next_time = next_execution_time
    if isinstance(next_execution_time, six.string_types):
        try:
            # We need naive datetime object.
            next_time = parser.parse(next_execution_time, ignoretz=True)
        except ValueError as e:
            raise exc.InputException(str(e))

    valid_min_time = timeutils.utcnow() + datetime.timedelta(0, 60)
    if valid_min_time > next_time:
        raise exc.InputException(
            'Execution time must be at least 1 minute in the future.')

    return next_time
Esempio n. 11
0
    def post(self, webhook):
        acl.enforce('webhook:create', context.get_ctx())

        params = webhook.to_dict()
        if not (params.get("function_id") or params.get("function_alias")):
            raise exc.InputException(
                'Either function_alias or function_id must be provided.')

        # if function_alias provided
        function_alias = params.get('function_alias')
        if function_alias:
            alias_db = db_api.get_function_alias(function_alias)
            function_id = alias_db.function_id
            version = alias_db.function_version
            params.update({
                'function_id': function_id,
                'function_version': version
            })

        LOG.info("Creating %s, params: %s", self.type, params)

        # Even admin user can not expose normal user's function
        db_api.get_function(params['function_id'], insecure=False)

        version = params.get('function_version', 0)
        if version > 0:
            db_api.get_function_version(params['function_id'], version)

        webhook_d = db_api.create_webhook(params).to_dict()

        return resources.Webhook.from_dict(
            self._add_webhook_url(webhook_d['id'], webhook_d))
Esempio n. 12
0
def validate_pattern(pattern):
    try:
        croniter.croniter(pattern)
    except (ValueError, KeyError):
        raise exc.InputException(
            'The specified pattern is not valid: {}'.format(pattern)
        )
Esempio n. 13
0
    def post(self, body):
        """Create a new alias for the specified function.

        The supported body params:
            - function_id: Required. Function id the alias points to.
            - name: Required. Alias name, must be unique within the project.
            - function_version: Optional. Version number the alias points to.
            - description: Optional. The description of the new alias.
        """
        ctx = context.get_ctx()
        acl.enforce('function_alias:create', ctx)

        params = body.to_dict()
        if not POST_REQUIRED.issubset(set(params.keys())):
            raise exc.InputException(
                'Required param is missing. Required: %s' % POST_REQUIRED
            )
        LOG.info("Creating Alias, params: %s", params)

        values = {
            'function_id': params.get('function_id'),
            'name': params.get('name'),
            'function_version': params.get('function_version'),
            'description': params.get('description'),
        }

        alias = db_api.create_function_alias(**values)

        LOG.info("New alias created.")
        return resources.FunctionAlias.from_db_obj(alias)
Esempio n. 14
0
    def post(self, job):
        """Creates a new job."""
        params = job.to_dict()
        if not POST_REQUIRED.issubset(set(params.keys())):
            raise exc.InputException(
                'Required param is missing. Required: %s' % POST_REQUIRED)

        # Check the input params.
        first_time, next_time, count = jobs.validate_job(params)
        LOG.info("Creating %s, params: %s", self.type, params)

        with db_api.transaction():
            db_api.get_function(params['function_id'])

            values = {
                'name': params.get('name'),
                'pattern': params.get('pattern'),
                'first_execution_time': first_time,
                'next_execution_time': next_time,
                'count': count,
                'function_id': params['function_id'],
                'function_input': params.get('function_input') or {},
                'status': status.RUNNING
            }
            db_job = db_api.create_job(values)

        return resources.Job.from_dict(db_job.to_dict())
Esempio n. 15
0
    def validate(self, value):
        if not value:
            return {}

        if not isinstance(value, dict):
            raise exc.InputException(
                'JsonType field value must be a dictionary [actual=%s]' %
                value)

        return value
Esempio n. 16
0
    def post(self, **kwargs):
        LOG.info("Creating %s, params: %s", self.type, 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)

        values = {
            'name': kwargs['name'],
            'description': kwargs.get('description'),
            'runtime_id': kwargs.get('runtime_id'),
            'code': json.loads(kwargs['code']),
            'entry': kwargs.get('entry', 'main.main'),
        }

        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 != 'image':
            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
        if values['code']['source'] == 'package':
            store = True
            data = kwargs['package'].file.read()
        elif values['code']['source'] == 'swift':
            # Auth needs to be enabled because qinling needs to check swift
            # object using user's credential.
            if not CONF.pecan.auth_enable:
                raise exc.InputException('Swift object not supported.')

            container = values['code']['swift'].get('container')
            object = values['code']['swift'].get('object')

            if not swift_util.check_object(container, object):
                raise exc.InputException('Object does not exist in Swift.')

        with db_api.transaction():
            func_db = db_api.create_function(values)

            if store:
                ctx = context.get_ctx()

                self.storage_provider.store(ctx.projectid, func_db.id, data)

        pecan.response.status = 201
        return resources.Function.from_dict(func_db.to_dict()).to_dict()
Esempio n. 17
0
    def post(self, body):
        ctx = context.get_ctx()
        acl.enforce('execution:create', ctx)

        params = body.to_dict()
        if not (params.get("function_id") or params.get("function_alias")):
            raise exc.InputException(
                'Either function_alias or function_id must be provided.')

        LOG.info("Creating %s. [params=%s]", self.type, params)

        db_model = executions.create_execution(self.engine_client, params)

        return resources.Execution.from_db_obj(db_model)
Esempio n. 18
0
    def post(self, runtime):
        params = runtime.to_dict()

        if not POST_REQUIRED.issubset(set(params.keys())):
            raise exc.InputException(
                'Required param is missing. Required: %s' % POST_REQUIRED)

        LOG.info("Creating %s, params: %s", self.type, params)

        params.update({'status': status.CREATING})

        db_model = db_api.create_runtime(params)
        self.engine_client.create_runtime(db_model.id)

        return resources.Runtime.from_dict(db_model.to_dict())
Esempio n. 19
0
    def store(self, project_id, function, data):
        LOG.info('Store package, function: %s, project: %s', function,
                 project_id)

        project_path = os.path.join(CONF.storage.file_system_dir, project_id)
        fileutils.ensure_tree(project_path)

        func_zip = os.path.join(project_path, '%s.zip' % function)
        with open(func_zip, 'wb') as fd:
            fd.write(data)

        if not zipfile.is_zipfile(func_zip):
            fileutils.delete_if_exists(func_zip)

            raise exc.InputException("Package is not a valid ZIP package.")
Esempio n. 20
0
    def post(self, webhook):
        acl.enforce('webhook:create', context.get_ctx())

        params = webhook.to_dict()
        if not POST_REQUIRED.issubset(set(params.keys())):
            raise exc.InputException(
                'Required param is missing. Required: %s' % POST_REQUIRED)

        LOG.info("Creating %s, params: %s", self.type, params)

        # Even admin user can not expose normal user's function
        db_api.get_function(params['function_id'], insecure=False)
        webhook_d = db_api.create_webhook(params).to_dict()

        return resources.Webhook.from_dict(
            self._add_webhook_url(webhook_d['id'], webhook_d))
Esempio n. 21
0
    def post(self, runtime):
        acl.enforce('runtime:create', context.get_ctx())

        params = runtime.to_dict()
        if 'trusted' not in params:
            params['trusted'] = True

        if not POST_REQUIRED.issubset(set(params.keys())):
            raise exc.InputException(
                'Required param is missing. Required: %s' % POST_REQUIRED)

        LOG.info("Creating %s, params: %s", self.type, params)

        params.update({'status': status.CREATING})

        db_model = db_api.create_runtime(params)
        self.engine_client.create_runtime(db_model.id)

        return resources.Runtime.from_db_obj(db_model)
Esempio n. 22
0
    def post(self, job):
        """Creates a new job."""
        params = job.to_dict()
        if not (params.get("function_id") or params.get("function_alias")):
            raise exc.InputException(
                'Either function_alias or function_id must be provided.')

        LOG.info("Creating %s, params: %s", self.type, params)

        # Check the input params.
        first_time, next_time, count = jobs.validate_job(params)

        version = params.get('function_version', 0)
        function_alias = params.get('function_alias')

        if function_alias:
            # Check if the alias exists.
            db_api.get_function_alias(function_alias)
        else:
            # Check the function(version) exists.
            db_api.get_function(params['function_id'])
            if version > 0:
                # Check if the version exists.
                db_api.get_function_version(params['function_id'], version)

        values = {
            'name': params.get('name'),
            'pattern': params.get('pattern'),
            'first_execution_time': first_time,
            'next_execution_time': next_time,
            'count': count,
            'function_alias': function_alias,
            'function_id': params.get("function_id"),
            'function_version': version,
            'function_input': params.get('function_input'),
            'status': status.RUNNING
        }
        db_job = db_api.create_job(values)

        return resources.Job.from_db_obj(db_job)
Esempio n. 23
0
    def store(self, project_id, function, data):
        """Store the function package data to local file system.

        :param project_id: Project ID.
        :param function: Function ID.
        :param data: Package data.
        """
        LOG.debug('Store package, function: %s, project: %s', function,
                  project_id)

        project_path = os.path.join(CONF.storage.file_system_dir, project_id)
        fileutils.ensure_tree(project_path)

        new_func_zip = os.path.join(project_path, '%s.zip.new' % function)
        func_zip = os.path.join(project_path, '%s.zip' % function)
        with open(new_func_zip, 'wb') as fd:
            fd.write(data)

        if not zipfile.is_zipfile(new_func_zip):
            fileutils.delete_if_exists(new_func_zip)
            raise exc.InputException("Package is not a valid ZIP package.")

        os.rename(new_func_zip, func_zip)
Esempio n. 24
0
    def post(self, job):
        """Creates a new job."""
        params = job.to_dict()
        if not POST_REQUIRED.issubset(set(params.keys())):
            raise exc.InputException(
                'Required param is missing. Required: %s' % POST_REQUIRED)

        # Check the input params.
        first_time, next_time, count = jobs.validate_job(params)
        LOG.info("Creating %s, params: %s", self.type, params)

        with db_api.transaction():
            db_api.get_function(params['function_id'])

            values = {
                'name': params.get('name'),
                'pattern': params.get('pattern'),
                'first_execution_time': first_time,
                'next_execution_time': next_time,
                'count': count,
                'function_id': params['function_id'],
                'function_input': params.get('function_input') or {},
                'status': status.RUNNING
            }

            if cfg.CONF.pecan.auth_enable:
                values['trust_id'] = keystone_util.create_trust().id

            try:
                db_job = db_api.create_job(values)
            except Exception:
                # Delete trust before raising exception.
                keystone_util.delete_trust(values.get('trust_id'))
                raise

        return resources.Job.from_dict(db_job.to_dict())
Esempio n. 25
0
    def validate(value):
        if not uuidutils.is_uuid_like(value):
            raise exc.InputException("Expected a uuid but received %s." %
                                     value)

        return value
Esempio n. 26
0
def create_execution(engine_client, params):
    function_id = params['function_id']
    is_sync = params.get('sync', True)
    input = params.get('input')
    version = params.get('function_version', 0)

    func_db = db_api.get_function(function_id)
    runtime_id = func_db.runtime_id

    # Image type function does not need runtime
    if runtime_id:
        runtime_db = db_api.get_runtime(runtime_id)
        if runtime_db and runtime_db.status != status.AVAILABLE:
            raise exc.RuntimeNotAvailableException(
                'Runtime %s is not available.' % func_db.runtime_id)

    if version > 0:
        if func_db.code['source'] != constants.PACKAGE_FUNCTION:
            raise exc.InputException(
                "Can not specify version for %s type function." %
                constants.PACKAGE_FUNCTION)

        # update version count
        version_db = db_api.get_function_version(function_id, version)
        pre_version_count = version_db.count
        _update_function_version_db(version_db.id, pre_version_count)
    else:
        pre_count = func_db.count
        _update_function_db(function_id, pre_count)

    # input in params should be a string.
    if input:
        try:
            params['input'] = json.loads(input)
        except ValueError:
            params['input'] = {'__function_input': input}

    params.update({'status': status.RUNNING})
    db_model = db_api.create_execution(params)

    try:
        engine_client.create_execution(db_model.id,
                                       function_id,
                                       version,
                                       runtime_id,
                                       input=params.get('input'),
                                       is_sync=is_sync)
    except exc.QinlingException:
        # Catch RPC errors for executions:
        #   - for RemoteError in an RPC call, the execution status would be
        #     handled in the engine side;
        #   - for other exceptions in an RPC call or cast, the execution status
        #     would remain RUNNING so we should update it.
        db_model = db_api.get_execution(db_model.id)
        if db_model.status == status.RUNNING:
            db_model = db_api.update_execution(db_model.id,
                                               {'status': status.ERROR})
        return db_model

    if is_sync:
        # The execution should already be updated by engine service for sync
        # execution.
        db_model = db_api.get_execution(db_model.id)

    return db_model
Esempio n. 27
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()
Esempio n. 28
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()