def post(self, action_execution): """Create new action_execution.""" body = json.loads(pecan.request.body) LOG.info("Create action_execution [action_execution=%s]" % body) action_input = action_execution.input or None description = action_execution.description or None if action_input: try: action_input = json.loads(action_execution.input) if not isinstance(action_input, dict): raise TypeError("Input should be dict type.") except (TypeError, ValueError) as e: raise exc.InputException( "Input should be JSON-serialized dict string. Actual: %s, " "error: %s" % (action_execution.input, e)) name = action_execution.name params = body.get('params', {}) if not name: raise exc.InputException( "Please provide at least action name to run action.") action_ex = rpc.get_engine_client().start_action( name, action_input, description=description, **params) return ActionExecution.from_dict(action_ex).to_dict()
def _get_environment(params): env = params.get('env', {}) if not env: return {} if isinstance(env, dict): env_dict = env elif isinstance(env, six.string_types): env_db = db_api.load_environment(env) if not env_db: raise exc.InputException( 'Environment is not found: %s' % env ) env_dict = env_db.variables else: raise exc.InputException( 'Unexpected value type for environment [env=%s, type=%s]' % (env, type(env)) ) if ('evaluate_env' in params and not params['evaluate_env']): return env_dict else: return expr.evaluate_recursively(env_dict, {'__env': env_dict})
def _compute_delta(wf_ex): with db_api.transaction(): # ensure that workflow execution exists db_api.get_workflow_execution( id, fields=(db_models.WorkflowExecution.id,) ) delta = {} if wf_ex.state: delta['state'] = wf_ex.state if wf_ex.description: delta['description'] = wf_ex.description if wf_ex.params and wf_ex.params.get('env'): delta['env'] = wf_ex.params.get('env') # Currently we can change only state, description, or env. if len(delta.values()) <= 0: raise exc.InputException( 'The property state, description, or env ' 'is not provided for update.' ) # Description cannot be updated together with state. if delta.get('description') and delta.get('state'): raise exc.InputException( 'The property description must be updated ' 'separately from state.' ) # If state change, environment cannot be updated # if not RUNNING. if (delta.get('env') and delta.get('state') and delta['state'] != states.RUNNING): raise exc.InputException( 'The property env can only be updated when workflow ' 'execution is not running or on resume from pause.' ) if delta.get('description'): wf_ex = db_api.update_workflow_execution( id, {'description': delta['description']} ) if not delta.get('state') and delta.get('env'): wf_ex = db_api.get_workflow_execution(id) wf_ex = wf_service.update_workflow_execution_env( wf_ex, delta.get('env') ) return delta, wf_ex
def validate_input(with_items_input): # Take only mapped values and check them. values = list(with_items_input.values()) if not all([isinstance(v, list) for v in values]): raise exc.InputException("Wrong input format for: %s. List type is" " expected for each value." % with_items_input) required_len = len(values[0]) if not all(len(v) == required_len for v in values): raise exc.InputException("Wrong input format for: %s. All arrays must" " have the same length." % with_items_input)
def _get_with_items_values(self): """Returns all values evaluated from 'with-items' expression. Example: DSL: with-items: - var1 in <% $.arrayI %> - var2 in <% $.arrayJ %> where arrayI = [1,2,3] and arrayJ = [a,b,c] The result of the method in this case will be: { 'var1': [1,2,3], 'var2': [a,b,c] } :return: Evaluated 'with-items' expression values. """ exp_res = self.evaluate(self.task_spec.get_with_items()) # Expression result may contain iterables instead of lists in the # dictionary values. So we need to convert them into lists and # perform all needed checks. result = {} required_len = -1 for var, items in exp_res.items(): if not isinstance(items, collections.Iterable): raise exc.InputException( "Wrong input format for: %s. Iterable type is" " expected for each value." % result) items_list = list(items) result[var] = items_list if required_len < 0: required_len = len(items_list) elif len(items_list) != required_len: raise exc.InputException( "Wrong input format for: %s. All arrays must" " have the same length." % exp_res) return result
def post(self, action_ex): """Create new action_execution. :param action_ex: Action to execute """ acl.enforce('action_executions:create', context.ctx()) LOG.debug( "Create action_execution [action_execution=%s]", action_ex ) name = action_ex.name description = action_ex.description or None action_input = action_ex.input or {} params = action_ex.params or {} namespace = action_ex.workflow_namespace or '' if not name: raise exc.InputException( "Please provide at least action name to run action." ) values = rpc.get_engine_client().start_action( name, action_input, description=description, namespace=namespace, **params ) return resources.ActionExecution.from_dict(values)
def update_workflows(definition, scope='private', identifier=None, namespace=''): LOG.debug("updating workflows") wf_list_spec = spec_parser.get_workflow_list_spec_from_yaml(definition) wfs = wf_list_spec.get_workflows() if identifier and len(wfs) > 1: raise exc.InputException( "More than one workflows are not supported for " "update with identifier. [identifier: %s]" % identifier ) db_wfs = [] with db_api.transaction(): for wf_spec in wf_list_spec.get_workflows(): db_wfs.append(_update_workflow( wf_spec, definition, scope, namespace=namespace, identifier=identifier )) return db_wfs
def put(self, env): """Update an environment. :param env: Required. Environment structure to update """ acl.enforce('environments:update', context.ctx()) if not env.name: raise exceptions.InputException( 'Name of the environment is not provided.' ) LOG.info("Update environment [name=%s, env=%s]", env.name, env) definition = json.loads(wsme_pecan.pecan.request.body.decode()) definition.pop('name') self._validate_environment( definition, ['description', 'variables', 'scope'] ) db_model = db_api.update_environment(env.name, env.to_dict()) return resources.Environment.from_db_model(db_model)
def validate_input(definition, input, spec=None): input_param_names = copy.copy((input or {}).keys()) missing_param_names = [] spec_input = (spec.get_input() if spec else utils.get_input_dict_from_input_string(definition.input)) for p_name, p_value in six.iteritems(spec_input): if p_value is utils.NotDefined and p_name not in input_param_names: missing_param_names.append(p_name) if p_name in input_param_names: input_param_names.remove(p_name) if missing_param_names or input_param_names: msg = 'Invalid input [name=%s, class=%s' msg_props = [definition.name, spec.__class__.__name__] if missing_param_names: msg += ', missing=%s' msg_props.append(missing_param_names) if input_param_names: msg += ', unexpected=%s' msg_props.append(input_param_names) msg += ']' raise exc.InputException(msg % tuple(msg_props)) else: utils.merge_dicts(input, spec_input, overwrite=False)
def update_workflows(definition, scope='private', identifier=None): wf_list_spec = spec_parser.get_workflow_list_spec_from_yaml(definition) wfs = wf_list_spec.get_workflows() if identifier and len(wfs) > 1: raise exc.InputException( "More than one workflows are not supported for update with UUID " "provided.") db_wfs = [] with db_api.transaction(): for wf_spec in wf_list_spec.get_workflows(): db_wfs.append( _update_workflow(wf_spec, definition, scope, identifier=identifier)) # Once transaction has committed we need to update specification cache. for db_wf, wf_spec in zip(db_wfs, wf_list_spec.get_workflows()): spec_parser.update_workflow_cache(db_wf.id, wf_spec) return db_wfs
def update_workflows(definition, scope='private', identifier=None, namespace=''): LOG.debug("Updating workflows") wf_list_spec = spec_parser.get_workflow_list_spec_from_yaml(definition, validate=True) wfs = wf_list_spec.get_workflows() if identifier and len(wfs) > 1: raise exc.InputException( "More than one workflows are not supported for " "update with identifier. [identifier: %s]" % identifier) db_wfs = [] wfs_yaml = yaml.load(definition) if len(wfs) != 1 else None with db_api.transaction(): for wf_spec in wfs: if len(wfs) != 1: definition = _cut_wf_definition_from_all( wfs_yaml, wf_spec.get_name()) db_wfs.append( _update_workflow(wf_spec, definition, scope, namespace=namespace, identifier=identifier)) return db_wfs
def _validate_environment(env_dict, legal_keys): if env_dict is None: return if set(env_dict) - set(legal_keys): raise exceptions.InputException( "Please, check your environment definition. Only: " "%s are allowed as definition keys." % legal_keys)
def _get_environment(params): env = params.get('env', {}) if isinstance(env, dict): return env if isinstance(env, six.string_types): env_db = db_api.load_environment(env) if not env_db: raise exc.InputException('Environment is not found: %s' % env) return env_db.variables raise exc.InputException( 'Unexpected value type for environment [env=%s, type=%s]' % (env, type(env)))
def start_action(self, action_name, action_input, description=None, namespace='', **params): with db_api.transaction(): action = action_handler.build_action_by_name(action_name, namespace=namespace) action.validate_input(action_input) sync = params.get('run_sync') save = params.get('save_result') target = params.get('target') timeout = params.get('timeout') is_action_sync = action.is_sync(action_input) if sync and not is_action_sync: raise exceptions.InputException( "Action does not support synchronous execution.") if not sync and (save or not is_action_sync): action.schedule(action_input, target, timeout=timeout) return action.action_ex.get_clone() output = action.run(action_input, target, save=False, timeout=timeout) state = states.SUCCESS if output.is_success() else states.ERROR if not save: # Action execution is not created but we need to return similar # object to the client anyway. return db_models.ActionExecution(name=action_name, description=description, input=action_input, output=output.to_dict(), state=state, workflow_namespace=namespace) action_ex_id = u.generate_unicode_uuid() values = { 'id': action_ex_id, 'name': action_name, 'description': description, 'input': action_input, 'output': output.to_dict(), 'state': state, 'is_sync': is_action_sync, 'workflow_namespace': namespace } return db_api.create_action_execution(values)
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
def is_sync(self, input_dict): try: prepared_input_dict = self._prepare_input(input_dict) a = a_m.get_action_class( self.action_def.name)(**prepared_input_dict) return a.is_sync() except BaseException as e: raise exc.InputException(str(e))
def _get_action_input(self, ctx=None): input_dict = self._evaluate_expression(self.task_spec.get_input(), ctx) if not isinstance(input_dict, dict): raise exc.InputException( "Wrong dynamic input for task: %s. Dict type is expected. " "Actual type: %s. Actual value: %s" % (self.task_spec.get_name(), type(input_dict), str(input_dict))) return utils.merge_dicts(input_dict, self._get_action_defaults(), overwrite=False)
def put(self, environment): """Update an environment.""" if not environment.name: raise exceptions.InputException( 'Name of the environment is not provided.') LOG.info("Update environment [name=%s, env=%s]" % (environment.name, environment)) definition = json.loads(wsme_pecan.pecan.request.body) definition.pop('name') self._validate_environment(definition, ['description', 'variables', 'scope']) db_model = db_api.update_environment(environment.name, environment.to_dict()) return Environment(**db_model.to_dict())
def post(self, action_ex): """Create new action_execution.""" acl.enforce('action_executions:create', context.ctx()) LOG.info("Create action_execution [action_execution=%s]" % action_ex) name = action_ex.name description = action_ex.description or None action_input = action_ex.input or {} params = action_ex.params or {} if not name: raise exc.InputException( "Please provide at least action name to run action.") action_ex = rpc.get_engine_client().start_action( name, action_input, description=description, **params) return ActionExecution.from_dict(action_ex)
def update_actions(definition, scope='private', identifier=None): action_list_spec = spec_parser.get_action_list_spec_from_yaml(definition) actions = action_list_spec.get_actions() if identifier and len(actions) > 1: raise exc.InputException("More than one actions are not supported for " "update with identifier. [identifier: %s]" % identifier) db_actions = [] for action_spec in action_list_spec.get_actions(): db_actions.append( update_action(action_spec, definition, scope, identifier=identifier)) return db_actions
def update_workflows(definition, scope='private', identifier=None): wf_list_spec = spec_parser.get_workflow_list_spec_from_yaml(definition) wfs = wf_list_spec.get_workflows() if identifier and len(wfs) > 1: raise exc.InputException( "More than one workflows are not supported for update with UUID " "provided.") db_wfs = [] with db_api.transaction(): for wf_spec in wf_list_spec.get_workflows(): db_wfs.append( _update_workflow(wf_spec, definition, scope, identifier=identifier)) return db_wfs
def validate_input(expected_input, actual_input, obj_name, obj_class): actual_input = actual_input or {} missing, unexpected = _compare_parameters(expected_input, actual_input) if missing or unexpected: msg = 'Invalid input [name=%s, class=%s' msg_props = [obj_name, obj_class] if missing: msg += ', missing=%s' msg_props.append(missing) if unexpected: msg += ', unexpected=%s' msg_props.append(unexpected) msg += ']' raise exc.InputException(msg % tuple(msg_props))
def post(self, dyn_action): """Creates new dynamic action. :param dyn_action: Dynamic action to create. """ acl.enforce('dynamic_actions:create', context.ctx()) LOG.debug('Creating dynamic action [action=%s]', dyn_action) if not dyn_action.code_source_id and not dyn_action.code_source_name: raise exc.InputException( "Either 'code_source_id' or 'code_source_name'" " must be provided.") code_source = db_api.get_code_source(dyn_action.code_source_id or dyn_action.code_source_name, namespace=dyn_action.namespace) # TODO(rakhmerov): Ideally we also need to check if the specified # class exists in the specified code source. But probably it's not # a controller responsibility. db_model = rest_utils.rest_retry_on_db_error( db_api.create_dynamic_action_definition)({ 'name': dyn_action.name, 'namespace': dyn_action.namespace, 'class_name': dyn_action.class_name, 'code_source_id': code_source.id, 'code_source_name': code_source.name }) return resources.DynamicAction.from_db_model(db_model)
def put(self, dyn_action): """Update dynamic action. :param dyn_action: Dynamic action to create. """ acl.enforce('dynamic_actions:update', context.ctx()) LOG.debug('Updating dynamic action [action=%s]', dyn_action) if not dyn_action.id and not dyn_action.name: raise exc.InputException("Either 'name' or 'id' must be provided.") values = {'class_name': dyn_action.class_name} if dyn_action.scope: values['scope'] = dyn_action.scope # A client may also want to update a source code. if dyn_action.code_source_id or dyn_action.code_source_name: code_source = db_api.get_code_source( dyn_action.code_source_id or dyn_action.code_source_name, namespace=dyn_action.namespace) values['code_source_id'] = code_source.id values['code_source_name'] = code_source.name # TODO(rakhmerov): Ideally we also need to check if the specified # class exists in the specified code source. But probably it's not # a controller responsibility. db_model = rest_utils.rest_retry_on_db_error( db_api.update_dynamic_action_definition)( dyn_action.id or dyn_action.name, values, namespace=dyn_action.namespace) return resources.DynamicAction.from_db_model(db_model)
def validate(value): if not uuidutils.is_uuid_like(value): raise exc.InputException("Expected a uuid but received %s." % value) return value
def put(self, id, wf_ex): """Update the specified workflow execution. :param id: UUID of execution to update. :param wf_ex: Execution object. """ acl.enforce('executions:update', context.ctx()) LOG.debug('Update execution [id=%s, execution=%s]', id, wf_ex) @rest_utils.rest_retry_on_db_error def _compute_delta(wf_ex): with db_api.transaction(): # ensure that workflow execution exists db_api.get_workflow_execution(id) delta = {} if wf_ex.state: delta['state'] = wf_ex.state if wf_ex.description: delta['description'] = wf_ex.description if wf_ex.params and wf_ex.params.get('env'): delta['env'] = wf_ex.params.get('env') # Currently we can change only state, description, or env. if len(delta.values()) <= 0: raise exc.InputException( 'The property state, description, or env ' 'is not provided for update.') # Description cannot be updated together with state. if delta.get('description') and delta.get('state'): raise exc.InputException( 'The property description must be updated ' 'separately from state.') # If state change, environment cannot be updated # if not RUNNING. if (delta.get('env') and delta.get('state') and delta['state'] != states.RUNNING): raise exc.InputException( 'The property env can only be updated when workflow ' 'execution is not running or on resume from pause.') if delta.get('description'): wf_ex = db_api.update_workflow_execution( id, {'description': delta['description']}) if not delta.get('state') and delta.get('env'): wf_ex = db_api.get_workflow_execution(id) wf_ex = wf_service.update_workflow_execution_env( wf_ex, delta.get('env')) return delta, wf_ex delta, wf_ex = _compute_delta(wf_ex) if delta.get('state'): if states.is_paused(delta.get('state')): wf_ex = rpc.get_engine_client().pause_workflow(id) elif delta.get('state') == states.RUNNING: wf_ex = rpc.get_engine_client().resume_workflow( id, env=delta.get('env')) elif states.is_completed(delta.get('state')): msg = wf_ex.state_info if wf_ex.state_info else None wf_ex = rpc.get_engine_client().stop_workflow( id, delta.get('state'), msg) else: # To prevent changing state in other cases throw a message. raise exc.InputException( "Cannot change state to %s. Allowed states are: '%s" % (wf_ex.state, ', '.join([ states.RUNNING, states.PAUSED, states.SUCCESS, states.ERROR, states.CANCELLED ]))) return resources.Execution.from_dict( wf_ex if isinstance(wf_ex, dict) else wf_ex.to_dict())
def validate(value): _, data = filter_utils.extract_filter_type_and_value(value) if not uuidutils.is_uuid_like(data): raise exc.InputException("Expected a uuid but received %s." % data) return data