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(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 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)
def test_post(self): name = 'TestAlias' body = { 'function_id': self.func_id, 'name': name, 'description': 'new alias' } resp = self.app.post_json('/v1/aliases', body) self.assertEqual(201, resp.status_int) self._assertDictContainsSubset(resp.json, body) context.set_ctx(self.ctx) func_alias_db = db_api.get_function_alias(name) self.assertEqual(name, func_alias_db.name) self.assertEqual(0, func_alias_db.function_version)
def put(self, id, webhook): 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_alias = webhook_db.function_alias pre_function_id = webhook_db.function_id pre_version = webhook_db.function_version new_alias = values.get("function_alias") new_function_id = values.get("function_id", pre_function_id) new_version = values.get("function_version", pre_version) function_id = pre_function_id version = pre_version if new_alias and new_alias != pre_alias: alias_db = db_api.get_function_alias(new_alias) function_id = alias_db.function_id version = alias_db.function_version # If function_alias is provided, we don't store either functin id # or function version. values.update({'function_id': None, 'function_version': None}) elif new_function_id != pre_function_id or new_version != pre_version: function_id = new_function_id version = new_version values.update({"function_alias": None}) db_api.get_function(function_id, insecure=False) if version and version > 0: db_api.get_function_version(function_id, version) webhook = db_api.update_webhook(id, values).to_dict() return resources.Webhook.from_dict(self._add_webhook_url(id, webhook))
def invoke(self, id, **kwargs): with db_api.transaction(): # The webhook url can be accessed without authentication, so # insecure is used here webhook_db = db_api.get_webhook(id, insecure=True) function_alias = webhook_db.function_alias if function_alias: alias = db_api.get_function_alias(function_alias, insecure=True) function_id = alias.function_id function_version = alias.function_version function_db = db_api.get_function(function_id, insecure=True) else: function_db = webhook_db.function function_id = webhook_db.function_id function_version = webhook_db.function_version trust_id = function_db.trust_id project_id = function_db.project_id LOG.info('Invoking function %s(version %s) by webhook %s', function_id, function_version, id) # Setup user context ctx = keystone_utils.create_trust_context(trust_id, project_id) context.set_ctx(ctx) params = { 'function_id': function_id, 'function_version': function_version, 'sync': False, 'input': json.dumps(kwargs), 'description': constants.EXECUTION_BY_WEBHOOK % id } execution = executions.create_execution(self.engine_client, params) pecan.response.status = 202 return {'execution_id': execution.id}
def create_execution(engine_client, params): function_alias = params.get('function_alias') function_id = params.get('function_id') version = params.get('function_version', 0) is_sync = params.get('sync', True) input = params.get('input') 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, 'version': version}) 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
def handle_job(engine_client): """Execute job task with no db transactions.""" jobs_db = db_api.get_next_jobs(timeutils.utcnow() + timedelta(seconds=3)) for job in jobs_db: job_id = job.id func_alias = job.function_alias if func_alias: alias = db_api.get_function_alias(func_alias, insecure=True) func_id = alias.function_id func_version = alias.function_version else: func_id = job.function_id func_version = job.function_version LOG.debug("Processing job: %s, function: %s(version %s)", job_id, func_id, func_version) func_db = db_api.get_function(func_id, insecure=True) trust_id = func_db.trust_id try: # Setup context before schedule job. ctx = keystone_utils.create_trust_context( trust_id, job.project_id ) context.set_ctx(ctx) if (job.count is not None and job.count > 0): job.count -= 1 # Job delete/update is done using UPDATE ... FROM ... WHERE # non-locking clause. if job.count == 0: modified = db_api.conditional_update( models.Job, { 'status': status.DONE, 'count': 0 }, { 'id': job_id, 'status': status.RUNNING }, insecure=True, ) else: next_time = jobs.get_next_execution_time( job.pattern, job.next_execution_time ) modified = db_api.conditional_update( models.Job, { 'next_execution_time': next_time, 'count': job.count }, { 'id': job_id, 'next_execution_time': job.next_execution_time }, insecure=True, ) if not modified: LOG.warning( 'Job %s has been already handled by another periodic ' 'task.', job_id ) continue LOG.debug( "Starting to execute function %s(version %s) by job %s", func_id, func_version, job_id ) params = { 'function_id': func_id, 'function_version': func_version, 'input': job.function_input, 'sync': False, 'description': constants.EXECUTION_BY_JOB % job_id } executions.create_execution(engine_client, params) except Exception: LOG.exception("Failed to process job %s", job_id) finally: context.set_ctx(None)