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)
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)
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 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)
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()
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 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 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()