def stop_interactive_session(self, interactive_session_id): """Stop an interactive workflow run.""" int_session = InteractiveSession.query.filter_by( id_=interactive_session_id).first() if not int_session: raise REANAInteractiveSessionError( "Interactive session for workflow {} does not exist.".format( self.workflow.name)) action_completed = True try: delete_k8s_ingress_object( ingress_name=int_session.name, namespace=REANA_RUNTIME_KUBERNETES_NAMESPACE, ) except Exception as e: action_completed = False raise REANAInteractiveSessionError( "Unkown error while stopping interactive session:\n{}".format( e)) finally: if action_completed: # TODO: once multiple sessions will be supported instead of # deleting a session, its status should be changed to "stopped" # int_session.status = RunStatus.stopped current_db_sessions = Session.object_session(self.workflow) current_db_sessions.delete(int_session) current_db_sessions.commit()
def start_interactive_session(self, interactive_session_type, **kwargs): """Start an interactive workflow run. :param interactive_session_type: One of the available interactive session types. :return: Relative path to access the interactive session. """ action_completed = True try: if (interactive_session_type not in INTERACTIVE_SESSION_TYPES): raise REANAInteractiveSessionError( "Interactive type {} does not exist.".format( interactive_session_type)) access_path = self._generate_interactive_workflow_path() self.workflow.interactive_session_type = interactive_session_type self.workflow.interactive_session = access_path workflow_run_name = \ self._workflow_run_name_generator( 'interactive', type=interactive_session_type) self.workflow.interactive_session_name = workflow_run_name kubernetes_objects = \ build_interactive_k8s_objects[interactive_session_type]( workflow_run_name, self.workflow.workspace_path, access_path, access_token=self.workflow.get_owner_access_token(), cvmfs_repos=self.retrieve_required_cvmfs_repos(), **kwargs) instantiate_chained_k8s_objects( kubernetes_objects, KubernetesWorkflowRunManager.default_namespace) return access_path except KeyError: action_completed = False raise REANAInteractiveSessionError( "Unsupported interactive session type {}.".format( interactive_session_type)) except ApiException as api_exception: action_completed = False raise REANAInteractiveSessionError( "Connection to Kubernetes has failed:\n{}".format( api_exception)) except Exception as e: action_completed = False raise REANAInteractiveSessionError( "Unkown error while starting interactive workflow run:\n{}". format(e)) finally: if not action_completed: self.workflow.interactive_session = None if kubernetes_objects: delete_k8s_objects_if_exist( kubernetes_objects, KubernetesWorkflowRunManager.default_namespace) current_db_sessions = Session.object_session(self.workflow) current_db_sessions.add(self.workflow) current_db_sessions.commit()
def get_workflow_running_jobs_as_backend_ids(self): """Get all running jobs of a workflow as backend job IDs.""" session = Session.object_session(self.workflow) rows = session.query(Job).filter_by(workflow_uuid=str( self.workflow.id_), status=JobStatus.running) backend_ids = [j.backend_job_id for j in rows.all()] return backend_ids
def get_workflow_running_jobs_as_backend_ids(self): """Get all running jobs of a workflow as backend job IDs.""" session = Session.object_session(self.workflow) job_list = self.workflow.job_progress.get('running', {}).get('job_ids', []) rows = session.query(Job).filter(Job.id_.in_(job_list)) backend_ids = [j.backend_job_id for j in rows.all()] return backend_ids
def start_workflow(workflow, parameters): """Start a workflow.""" def _start_workflow_db(workflow, parameters): workflow.run_started_at = datetime.now() workflow.status = WorkflowStatus.running if parameters: workflow.input_parameters = parameters.get("input_parameters") workflow.operational_options = parameters.get("operational_options") current_db_sessions.add(workflow) current_db_sessions.commit() current_db_sessions = Session.object_session(workflow) kwrm = KubernetesWorkflowRunManager(workflow) failure_message = ( "Workflow {id_} could not be started because it {verb} " "already {status}." ).format( id_=workflow.id_, verb=get_workflow_status_change_verb(workflow.status.name), status=str(workflow.status.name), ) if "restart" in parameters.keys(): if parameters["restart"]: if workflow.status not in [ WorkflowStatus.failed, WorkflowStatus.finished, WorkflowStatus.queued, ]: raise REANAWorkflowControllerError(failure_message) elif workflow.status not in [WorkflowStatus.created, WorkflowStatus.queued]: if workflow.status == WorkflowStatus.deleted: raise REANAWorkflowStatusError(failure_message) raise REANAWorkflowControllerError(failure_message) try: kwrm.start_batch_workflow_run( overwrite_input_params=parameters.get("input_parameters"), overwrite_operational_options=parameters.get("operational_options"), ) _start_workflow_db(workflow, parameters) except SQLAlchemyError as e: message = "Database connection failed, please retry." logging.error( f"Error while creating {workflow.id_}: {message}\n{e}", exc_info=True ) # Rollback Kubernetes job creation kwrm.stop_batch_workflow_run() logging.error( f"Stopping Kubernetes jobs associated with workflow " f"{workflow.id_} ..." ) raise REANAExternalCallError(message) except ApiException as e: message = "Kubernetes connection failed, please retry." logging.error( f"Error while creating {workflow.id_}: {message}\n{e}", exc_info=True ) raise REANAExternalCallError(message)
def stop_interactive_session(self): """Stop an interactive workflow run.""" delete_k8s_ingress_object( ingress_name=self.workflow.interactive_session_name, namespace=KubernetesWorkflowRunManager.default_namespace) self.workflow.interactive_session_name = None self.workflow.interactive_session = None current_db_sessions = Session.object_session(self.workflow) current_db_sessions.add(self.workflow) current_db_sessions.commit()
def clone_workflow(workflow, reana_spec, restart_type): """Create a copy of workflow in DB for restarting.""" try: cloned_workflow = Workflow( id_=str(uuid4()), name=workflow.name, owner_id=workflow.owner_id, reana_specification=reana_spec or workflow.reana_specification, type_=restart_type or workflow.type_, logs="", workspace_path=workflow.workspace_path, restart=True, run_number=workflow.run_number, ) Session.add(cloned_workflow) Session.object_session(cloned_workflow).commit() return cloned_workflow except SQLAlchemyError as e: message = "Database connection failed, please retry." logging.error( f"Error while creating {cloned_workflow.id_}: {message}\n{e}", exc_info=True)
def stop_workflow(workflow): """Stop a given workflow.""" if workflow.status == WorkflowStatus.running: kwrm = KubernetesWorkflowRunManager(workflow) workflow.run_stopped_at = datetime.now() kwrm.stop_batch_workflow_run() workflow.status = WorkflowStatus.stopped current_db_sessions = Session.object_session(workflow) current_db_sessions.add(workflow) current_db_sessions.commit() else: message = ("Workflow {id_} is not running.").format(id_=workflow.id_) raise REANAWorkflowControllerError(message)
def create_workflow(): # noqa r"""Create workflow and its workspace. --- post: summary: Create workflow and its workspace. description: >- This resource expects all necessary data to represent a workflow so it is stored in database and its workspace is created. operationId: create_workflow produces: - application/json parameters: - name: user in: query description: Required. UUID of workflow owner. required: true type: string - name: workflow in: body description: >- JSON object including workflow parameters and workflow specification in JSON format (`yadageschemas.load()` output) with necessary data to instantiate a yadage workflow. required: true schema: type: object properties: operational_options: type: object description: Operational options. reana_specification: type: object description: >- Workflow specification in JSON format. workflow_name: type: string description: Workflow name. If empty name will be generated. git_data: type: object description: >- GitLab data. required: [reana_specification, workflow_name, operational_options] responses: 201: description: >- Request succeeded. The workflow has been created along with its workspace schema: type: object properties: message: type: string workflow_id: type: string workflow_name: type: string examples: application/json: { "message": "Workflow workspace has been created.", "workflow_id": "cdcf48b1-c2f3-4693-8230-b066e088c6ac", "workflow_name": "mytest-1" } 400: description: >- Request failed. The incoming data specification seems malformed 404: description: >- Request failed. User does not exist. examples: application/json: { "message": "User 00000000-0000-0000-0000-000000000000 does not exist" } """ try: user_uuid = request.args["user"] user = User.query.filter(User.id_ == user_uuid).first() if not user: return ( jsonify({ "message": "User with id:{} does not exist".format(user_uuid) }), 404, ) workflow_uuid = str(uuid4()) # Use name prefix user specified or use default name prefix # Actual name is prefix + autoincremented run_number. workflow_name = request.json.get("workflow_name", "") if workflow_name == "": workflow_name = DEFAULT_NAME_FOR_WORKFLOWS else: try: workflow_name.encode("ascii") except UnicodeEncodeError: # `workflow_name` contains something else than just ASCII. raise REANAWorkflowNameError( "Workflow name {} is not valid.".format(workflow_name)) git_ref = "" git_repo = "" if "git_data" in request.json: git_data = request.json["git_data"] git_ref = git_data["git_commit_sha"] git_repo = git_data["git_url"] # add spec and params to DB as JSON workflow = Workflow( id_=workflow_uuid, name=workflow_name, owner_id=request.args["user"], reana_specification=request.json["reana_specification"], operational_options=request.json.get("operational_options", {}), type_=request.json["reana_specification"]["workflow"]["type"], logs="", git_ref=git_ref, git_repo=git_repo, ) Session.add(workflow) Session.object_session(workflow).commit() if git_ref: create_workflow_workspace( workflow.workspace_path, user_id=user.id_, git_url=git_data["git_url"], git_branch=git_data["git_branch"], git_ref=git_ref, ) else: create_workflow_workspace(workflow.workspace_path) return ( jsonify({ "message": "Workflow workspace created", "workflow_id": workflow.id_, "workflow_name": get_workflow_name(workflow), }), 201, ) except (REANAWorkflowNameError, KeyError) as e: return jsonify({"message": str(e)}), 400 except Exception as e: return jsonify({"message": str(e)}), 500
def start_interactive_session(self, interactive_session_type, **kwargs): """Start an interactive workflow run. :param interactive_session_type: One of the available interactive session types. :return: Relative path to access the interactive session. """ action_completed = True try: if interactive_session_type not in InteractiveSessionType.__members__: raise REANAInteractiveSessionError( "Interactive type {} does not exist.".format( interactive_session_type)) access_path = self._generate_interactive_workflow_path() workflow_run_name = self._workflow_run_name_generator("session") kubernetes_objects = build_interactive_k8s_objects[ interactive_session_type]( workflow_run_name, self.workflow.workspace_path, access_path, access_token=self.workflow.get_owner_access_token(), cvmfs_repos=self.retrieve_required_cvmfs_repos(), **kwargs, ) instantiate_chained_k8s_objects( kubernetes_objects, REANA_RUNTIME_KUBERNETES_NAMESPACE) # Save interactive session to the database int_session = InteractiveSession( name=workflow_run_name, path=access_path, type_=interactive_session_type, owner_id=self.workflow.owner_id, ) self.workflow.sessions.append(int_session) current_db_sessions = Session.object_session(self.workflow) current_db_sessions.add(self.workflow) current_db_sessions.commit() return access_path except KeyError: action_completed = False raise REANAInteractiveSessionError( "Unsupported interactive session type {}.".format( interactive_session_type)) except ApiException as api_exception: action_completed = False raise REANAInteractiveSessionError( "Connection to Kubernetes has failed:\n{}".format( api_exception)) except Exception as e: action_completed = False raise REANAInteractiveSessionError( "Unkown error while starting interactive workflow run:\n{}". format(e)) finally: if not action_completed and kubernetes_objects: delete_k8s_objects_if_exist( kubernetes_objects, REANA_RUNTIME_KUBERNETES_NAMESPACE)
def _mark_workflow_as_deleted_in_db(workflow): """Mark workflow as deleted.""" workflow.status = WorkflowStatus.deleted current_db_sessions = Session.object_session(workflow) current_db_sessions.add(workflow) current_db_sessions.commit()