Example #1
0
def handle_function_service_expiration(ctx, engine):
    """Clean up resources related to expired functions.

    If it's image function, we will rely on the orchestrator itself to do the
    image clean up, e.g. image collection feature in kubernetes.
    """
    context.set_ctx(ctx)
    delta = timedelta(seconds=CONF.engine.function_service_expiration)
    expiry_time = datetime.utcnow() - delta

    results = db_api.get_functions(sort_keys=['updated_at'],
                                   insecure=True,
                                   updated_at={'lte': expiry_time})
    if len(results) == 0:
        return

    for func_db in results:
        if not etcd_util.get_service_url(func_db.id):
            continue

        LOG.info('Deleting service mapping and workers for function %s',
                 func_db.id)

        # Delete resources related to the function
        engine.delete_function(ctx, func_db.id)
        # Delete etcd keys
        etcd_util.delete_function(func_db.id)
Example #2
0
    def delete(self, id):
        """Delete the specified function."""
        LOG.info("Delete function %s.", id)

        with db_api.transaction():
            func_db = db_api.get_function(id)
            if len(func_db.jobs) > 0:
                raise exc.NotAllowedException(
                    'The function is still associated with running job(s).')
            if func_db.webhook:
                raise exc.NotAllowedException(
                    'The function is still associated with webhook.')

            # Even admin user can not delete other project's function because
            # the trust associated can only be removed by function owner.
            if func_db.project_id != context.get_ctx().projectid:
                raise exc.NotAllowedException(
                    'Function can only be deleted by its owner.')

            source = func_db.code['source']
            if source == constants.PACKAGE_FUNCTION:
                self.storage_provider.delete(func_db.project_id, id)

            # Delete all resources created by orchestrator asynchronously.
            self.engine_client.delete_function(id)

            # Delete trust if needed
            if func_db.trust_id:
                keystone_util.delete_trust(func_db.trust_id)

            # Delete etcd keys
            etcd_util.delete_function(id)

            # This will also delete function service mapping as well.
            db_api.delete_function(id)
Example #3
0
    def delete(self, function_id, version):
        """Delete a specific function version.

        - The version should not being used by any job
        - The version should not being used by any webhook
        - Admin user can not delete normal user's version
        """
        ctx = context.get_ctx()
        acl.enforce('function_version:delete', ctx)
        LOG.info("Deleting version %s of function %s.", version, function_id)

        with db_api.transaction():
            version_db = db_api.get_function_version(function_id,
                                                     version,
                                                     insecure=False)
            latest_version = version_db.function.latest_version

            version_jobs = db_api.get_jobs(
                function_id=version_db.function_id,
                function_version=version_db.version_number,
                status={'nin': ['done', 'cancelled']})
            if len(version_jobs) > 0:
                raise exc.NotAllowedException(
                    'The function version is still associated with running '
                    'job(s).')

            version_webhook = db_api.get_webhooks(
                function_id=version_db.function_id,
                function_version=version_db.version_number,
            )
            if len(version_webhook) > 0:
                raise exc.NotAllowedException(
                    'The function version is still associated with webhook.')

            filters = rest_utils.get_filters(
                function_id=version_db.function_id,
                function_version=version_db.version_number)
            version_aliases = db_api.get_function_aliases(**filters)
            if len(version_aliases) > 0:
                raise exc.NotAllowedException(
                    'The function version is still associated with alias.')

            # Delete resources for function version
            self.engine_client.delete_function(function_id, version=version)
            etcd_util.delete_function(function_id, version=version)

            self.storage_provider.delete(ctx.projectid,
                                         function_id,
                                         None,
                                         version=version)

            db_api.delete_function_version(function_id, version)

            if latest_version == version:
                version_db.function.latest_version = latest_version - 1

        LOG.info("Version %s of function %s deleted.", version, function_id)
Example #4
0
    def delete(self, id):
        """Delete the specified function.

        Delete function will also delete all its versions.
        """
        LOG.info("Delete function %s.", id)

        with db_api.transaction():
            func_db = db_api.get_function(id)
            if len(func_db.jobs) > 0:
                raise exc.NotAllowedException(
                    'The function is still associated with running job(s).')
            if len(func_db.webhooks) > 0:
                raise exc.NotAllowedException(
                    'The function is still associated with webhook(s).')
            if len(func_db.aliases) > 0:
                raise exc.NotAllowedException(
                    'The function is still associated with function alias(es).'
                )

            # Even admin user can not delete other project's function because
            # the trust associated can only be removed by function owner.
            if func_db.project_id != context.get_ctx().projectid:
                raise exc.NotAllowedException(
                    'Function can only be deleted by its owner.')

            # Delete trust if needed
            if func_db.trust_id:
                keystone_util.delete_trust(func_db.trust_id)

            for version_db in func_db.versions:
                # Delete all resources created by orchestrator asynchronously.
                self.engine_client.delete_function(
                    id, version=version_db.version_number)
                # Delete etcd keys
                etcd_util.delete_function(id,
                                          version=version_db.version_number)
                # Delete function version packages. Versions is only supported
                # for package type function.
                self.storage_provider.delete(func_db.project_id,
                                             id,
                                             None,
                                             version=version_db.version_number)

            # Delete resources for function version 0(func_db.versions==[])
            self.engine_client.delete_function(id)
            etcd_util.delete_function(id)

            source = func_db.code['source']
            if source == constants.PACKAGE_FUNCTION:
                self.storage_provider.delete(func_db.project_id, id,
                                             func_db.code['md5sum'])

            # This will also delete function service mapping and function
            # versions as well.
            db_api.delete_function(id)
Example #5
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 function %s, params: %s', id, values)
        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 and etcd keys.
                self.engine_client.delete_function(id)
                etcd_util.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()
Example #6
0
    def detach(self, id):
        """Detach the function from its underlying workers.

        This is admin only operation, which gives admin user a safe way to
        clean up the underlying resources allocated for the function.
        """
        acl.enforce('function:detach', context.get_ctx())

        db_api.get_function(id)
        LOG.info('Starting to detach function %s', id)

        # Delete allocated resources in orchestrator and etcd keys.
        self.engine_client.delete_function(id)
        etcd_util.delete_function(id)
Example #7
0
    def detach(self, function_id, version):
        """Detach the function version from its underlying workers.

        This is admin only operation, which gives admin user a safe way to
        clean up the underlying resources allocated for the function version.
        """
        acl.enforce('function_version:detach', context.get_ctx())

        db_api.get_function(function_id)
        # If version=0, it's equivalent to /functions/<funcion-id>/detach
        if version > 0:
            db_api.get_function_version(function_id, version)

        LOG.info('Starting to detach function %s(version %s)', function_id,
                 version)

        # Delete allocated resources in orchestrator and etcd keys.
        self.engine_client.delete_function(function_id, version=version)
        etcd_util.delete_function(function_id, version=version)
Example #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()