def scale_down(self, function_id, version, scale): """Scale down the workers for function version execution. This is admin only operation. The load monitoring of execution depends on the monitoring solution of underlying orchestrator. """ acl.enforce('function_version:scale_down', context.get_ctx()) db_api.get_function(function_id) params = scale.to_dict() # If version=0, it's equivalent to /functions/<funcion-id>/scale_down if version > 0: db_api.get_function_version(function_id, version) workers = etcd_util.get_workers(function_id, version=version) if len(workers) <= 1: LOG.info('No need to scale down function %s(version %s)', function_id, version) return LOG.info('Starting to scale down function %s(version %s), params: %s', function_id, version, params) self.engine_client.scaledown_function(function_id, version=version, count=params['count'])
def get_all(self, function_id=None, all_projects=False, project_id=None, status=None, description=None): """Return a list of executions. :param function_id: Optional. Filtering executions by function_id. :param project_id: Optional. Admin user can query other projects resources, the param is ignored for normal user. :param all_projects: Optional. Get resources of all projects. :param status: Optional. Filter by execution status. :param description: Optional. Filter by description. """ ctx = context.get_ctx() if project_id and not ctx.is_admin: project_id = context.ctx().projectid if project_id and ctx.is_admin: all_projects = True if all_projects: acl.enforce('execution:get_all:all_projects', ctx) filters = rest_utils.get_filters( function_id=function_id, project_id=project_id, status=status, description=description ) LOG.info("Get all %ss. filters=%s", self.type, filters) db_execs = db_api.get_executions(insecure=all_projects, **filters) executions = [resources.Execution.from_dict(db_model.to_dict()) for db_model in db_execs] return resources.Executions(executions=executions)
def scale_up(self, function_id, version, scale): """Scale up the workers for function version execution. This is admin only operation. The load monitoring of execution depends on the monitoring solution of underlying orchestrator. """ acl.enforce('function_version:scale_up', context.get_ctx()) func_db = db_api.get_function(function_id) # If version=0, it's equivalent to /functions/<funcion-id>/scale_up if version > 0: db_api.get_function_version(function_id, version) params = scale.to_dict() LOG.info('Starting to scale up function %s(version %s), params: %s', function_id, version, params) self.engine_client.scaleup_function( function_id, runtime_id=func_db.runtime_id, version=version, count=params['count'] )
def get(self, alias_name): acl.enforce('function_alias:get', context.get_ctx()) LOG.info("Getting function alias %s.", alias_name) alias = db_api.get_function_alias(alias_name) return resources.FunctionAlias.from_db_obj(alias)
def get_all(self, all_projects=False, project_id=None): """Get all the function aliases. :param project_id: Optional. Admin user can query other projects resources, the param is ignored for normal user. :param all_projects: Optional. Get resources of all projects. """ ctx = context.get_ctx() project_id, all_projects = rest_utils.get_project_params( project_id, all_projects) if all_projects: acl.enforce('function_version:get_all:all_projects', ctx) filters = rest_utils.get_filters(project_id=project_id) LOG.info("Get all function aliases. filters=%s", filters) db_aliases = db_api.get_function_aliases(insecure=all_projects, **filters) aliases = [ resources.FunctionAlias.from_db_obj(db_model) for db_model in db_aliases ] return resources.FunctionAliases(function_aliases=aliases)
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)
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))
def get_all(self, all_projects=False, project_id=None): """Return a list of functions. :param project_id: Optional. Admin user can query other projects resources, the param is ignored for normal user. :param all_projects: Optional. Get resources of all projects. """ ctx = context.get_ctx() if project_id and not ctx.is_admin: project_id = context.ctx().projectid if project_id and ctx.is_admin: all_projects = True if all_projects: acl.enforce('function:get_all:all_projects', ctx) filters = rest_utils.get_filters(project_id=project_id, ) LOG.info("Get all functions. filters=%s", filters) db_functions = db_api.get_functions(insecure=all_projects, **filters) functions = [ resources.Function.from_dict(db_model.to_dict()) for db_model in db_functions ] return resources.Functions(functions=functions)
def put(self, id, webhook): """Update webhook. Currently, only function_id and function_version are allowed to update. """ acl.enforce('webhook:update', context.get_ctx()) values = {} for key in UPDATE_ALLOWED: if webhook.to_dict().get(key) is not None: values.update({key: webhook.to_dict()[key]}) LOG.info('Update %s %s, params: %s', self.type, id, values) # Even admin user can not expose normal user's function webhook_db = db_api.get_webhook(id, insecure=False) pre_function_id = webhook_db.function_id pre_version = webhook_db.function_version new_function_id = values.get("function_id", pre_function_id) new_version = values.get("function_version", pre_version) db_api.get_function(new_function_id, insecure=False) if new_version > 0: db_api.get_function_version(new_function_id, new_version) webhook = db_api.update_webhook(id, values).to_dict() return resources.Webhook.from_dict(self._add_webhook_url(id, webhook))
def post(self, function_id, body): """Create a new version for the function. Only allow to create version for package type function. The supported boy params: - description: Optional. The description of the new version. """ ctx = context.get_ctx() acl.enforce('function_version:create', ctx) params = body.to_dict() values = { 'description': params.get('description'), } # Try to create a new function version within lock and db transaction try: version = self._create_function_version(ctx.project_id, function_id, **values) except exc.EtcdLockException as e: LOG.exception(str(e)) # Reraise a generic exception as the end users should not know # the underlying details. raise exc.QinlingException('Internal server error.') return resources.FunctionVersion.from_db_obj(version)
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)
def post(self, body): ctx = context.get_ctx() acl.enforce('execution:create', ctx) params = body.to_dict() 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)
def put(self, id, runtime): """Update runtime. Currently, we support update name, description, image. When updating image, send message to engine for asynchronous handling. """ acl.enforce('runtime:update', context.get_ctx()) values = {} for key in UPDATE_ALLOWED: if runtime.to_dict().get(key) is not None: values.update({key: runtime.to_dict()[key]}) LOG.info('Update resource, params: %s', values, resource={ 'type': self.type, 'id': id }) image = values.get('image') with db_api.transaction(): if image is not None: pre_runtime = db_api.get_runtime(id) if pre_runtime.status != status.AVAILABLE: raise exc.RuntimeNotAvailableException( 'Runtime %s is not available.' % id) pre_image = pre_runtime.image if pre_image != image: # Ensure there is no function running in the runtime. db_funcs = db_api.get_functions(insecure=True, fields=['id'], runtime_id=id) func_ids = [func.id for func in db_funcs] for id in func_ids: if etcd_util.get_service_url(id): raise exc.NotAllowedException( 'Runtime %s is still in use by functions.' % id) values['status'] = status.UPGRADING self.engine_client.update_runtime( id, image=image, pre_image=pre_image, ) runtime_db = db_api.update_runtime(id, values) return resources.Runtime.from_db_obj(runtime_db)
def delete(self, alias_name): """Delete a specific alias. """ ctx = context.get_ctx() acl.enforce('function_alias:delete', ctx) LOG.info("Deleting alias %s.", alias_name) db_api.delete_function_alias(alias_name) LOG.info("Alias %s deleted.", alias_name)
def get_all(self, all_projects=False, project_id=None): project_id, all_projects = rest_utils.get_project_params( project_id, all_projects) if all_projects: acl.enforce('job:get_all:all_projects', context.get_ctx()) filters = rest_utils.get_filters(project_id=project_id, ) LOG.info("Get all %ss. filters=%s", self.type, filters) db_jobs = db_api.get_jobs(insecure=all_projects, **filters) jobs = [resources.Job.from_db_obj(db_model) for db_model in db_jobs] return resources.Jobs(jobs=jobs)
def get_all(self, function_id): acl.enforce('function_worker:get_all', context.get_ctx()) LOG.info("Get workers for function %s.", function_id) workers = etcd_util.get_workers(function_id, CONF) workers = [ resources.FunctionWorker.from_dict({ 'function_id': function_id, 'worker_name': w }) for w in workers ] return resources.FunctionWorkers(workers=workers)
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)
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)
def get_all(self, function_id, function_version=0): acl.enforce('function_worker:get_all', context.get_ctx()) LOG.info("Getting workers for function %s(version %s).", function_id, function_version) workers = etcd_util.get_workers(function_id, version=function_version) workers = [ resources.FunctionWorker.from_dict({ 'function_id': function_id, 'function_version': function_version, 'worker_name': w }) for w in workers ] return resources.FunctionWorkers(workers=workers)
def scale_up(self, id, scale): """Scale up the containers for function execution. This is admin only operation. The load monitoring of function execution depends on the monitoring solution of underlying orchestrator. """ acl.enforce('function:scale_up', context.get_ctx()) func_db = db_api.get_function(id) params = scale.to_dict() LOG.info('Starting to scale up function %s, params: %s', id, params) self.engine_client.scaleup_function(id, runtime_id=func_db.runtime_id, count=params['count'])
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))
def get_all(self, all_projects=False, project_id=None): project_id, all_projects = rest_utils.get_project_params( project_id, all_projects) if all_projects: acl.enforce('webhook:get_all:all_projects', context.get_ctx()) filters = rest_utils.get_filters(project_id=project_id, ) LOG.info("Get all %ss. filters=%s", self.type, filters) webhooks = [] for i in db_api.get_webhooks(insecure=all_projects, **filters): webhooks.append( resources.Webhook.from_dict( self._add_webhook_url(i.id, i.to_dict()))) return resources.Webhooks(webhooks=webhooks)
def get_all(self, function_id): """Get all the versions of the given function. Admin user can get all versions for the normal user's function. """ acl.enforce('function_version:get_all', context.get_ctx()) LOG.info("Getting versions for function %s.", function_id) # Getting function and versions needs to happen in a db transaction with db_api.transaction(): func_db = db_api.get_function(function_id) db_versions = func_db.versions versions = [resources.FunctionVersion.from_db_obj(v) for v in db_versions] return resources.FunctionVersions(function_versions=versions)
def scale_down(self, id, scale): """Scale down the containers for function execution. This is admin only operation. The load monitoring of function execution depends on the monitoring solution of underlying orchestrator. """ acl.enforce('function:scale_down', context.get_ctx()) db_api.get_function(id) workers = etcd_util.get_workers(id) params = scale.to_dict() if len(workers) <= 1: LOG.info('No need to scale down function %s', id) return LOG.info('Starting to scale down function %s, params: %s', id, params) self.engine_client.scaledown_function(id, count=params['count'])
def post(self, runtime): acl.enforce('runtime:create', context.get_ctx()) 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_db_obj(db_model)
def delete(self, id): acl.enforce('runtime:delete', context.get_ctx()) LOG.info("Delete resource.", resource={'type': self.type, 'id': id}) with db_api.transaction(): runtime_db = db_api.get_runtime(id) # Runtime can not be deleted if still associate with functions. funcs = db_api.get_functions(insecure=True, runtime_id={'eq': id}) if len(funcs): raise exc.NotAllowedException('Runtime %s is still in use.' % id) runtime_db.status = status.DELETING # Clean related resources asynchronously self.engine_client.delete_runtime(id)
def get(self, id): """Get function information or download function package. This method can support HTTP request using either 'Accept:application/json' or no 'Accept' header. """ ctx = context.get_ctx() acl.enforce('function:get', ctx) download = strutils.bool_from_string( pecan.request.GET.get('download', False) ) func_db = db_api.get_function(id) if not download: LOG.info("Getting function %s.", id) pecan.override_template('json') return resources.Function.from_db_obj(func_db).to_dict() LOG.info("Downloading function %s", id) source = func_db.code['source'] if source == constants.PACKAGE_FUNCTION: f = self.storage_provider.retrieve(func_db.project_id, id, func_db.code['md5sum']) elif source == constants.SWIFT_FUNCTION: container = func_db.code['swift']['container'] obj = func_db.code['swift']['object'] f = swift_util.download_object(container, obj) else: msg = 'Download image function is not allowed.' pecan.abort( status_code=405, detail=msg, headers={'Server-Error-Message': msg} ) pecan.response.app_iter = (f if isinstance(f, collections.Iterable) else FileIter(f)) pecan.response.headers['Content-Type'] = 'application/zip' pecan.response.headers['Content-Disposition'] = ( 'attachment; filename="%s"' % id )
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)
def pool(self, id): """Get the pool information for the runtime. This operation should be admin only. We don't check the runtime existence, because this function also helps us to check the underlying pool even after the runtime is already deleted. """ acl.enforce('runtime_pool:get_all', context.get_ctx()) LOG.info("Getting pool information for runtime %s.", id) capacity = self.engine_client.get_runtime_pool(id) pool_capacity = resources.RuntimePoolCapacity.from_dict(capacity) return resources.RuntimePool.from_dict({ "name": id, "capacity": pool_capacity })
def put(self, id, webhook): """Update webhook. Currently, we only support update function_id. """ acl.enforce('webhook:update', context.get_ctx()) values = {} for key in UPDATE_ALLOWED: if webhook.to_dict().get(key) is not None: values.update({key: webhook.to_dict()[key]}) LOG.info('Update %s %s, params: %s', self.type, id, values) if 'function_id' in values: # Even admin user can not expose normal user's function db_api.get_function(values['function_id'], insecure=False) webhook = db_api.update_webhook(id, values).to_dict() return resources.Webhook.from_dict(self._add_webhook_url(id, webhook))