# -*- coding: utf-8 -*-
#
# This file is part of REANA.
# Copyright (C) 2017, 2018 CERN.
#
# REANA is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.
"""Reana-Server workflow-functionality Flask-Blueprint."""
import io
import logging
import traceback
from bravado.exception import HTTPError
from flask import current_app as app
from flask import Blueprint, jsonify, request, send_file
from reana_server.utils import get_user_from_token, is_uuid_v4
from ..api_client import current_rwc_api_client
blueprint = Blueprint('workflows', __name__)
@blueprint.route('/workflows', methods=['GET'])
[docs]def get_workflows(): # noqa
r"""Get all current workflows in REANA.
---
get:
summary: Returns list of all current workflows in REANA.
description: >-
This resource return all current workflows in JSON format.
operationId: get_workflows
produces:
- application/json
parameters:
- name: access_token
in: query
description: Required. The API access_token of workflow owner.
required: true
type: string
responses:
200:
description: >-
Request succeeded. The response contains the list of all workflows.
schema:
type: array
items:
type: object
properties:
id:
type: string
name:
type: string
status:
type: string
user:
type: string
created:
type: string
examples:
application/json:
[
{
"id": "256b25f4-4cfb-4684-b7a8-73872ef455a1",
"name": "mytest-1",
"status": "running",
"user": "00000000-0000-0000-0000-000000000000",
"created": "2018-06-13T09:47:35.66097",
},
{
"id": "3c9b117c-d40a-49e3-a6de-5f89fcada5a3",
"name": "mytest-2",
"status": "finished",
"user": "00000000-0000-0000-0000-000000000000",
"created": "2018-06-13T09:47:35.66097",
},
{
"id": "72e3ee4f-9cd3-4dc7-906c-24511d9f5ee3",
"name": "mytest-3",
"status": "created",
"user": "00000000-0000-0000-0000-000000000000",
"created": "2018-06-13T09:47:35.66097",
},
{
"id": "c4c0a1a6-beef-46c7-be04-bf4b3beca5a1",
"name": "mytest-4",
"status": "created",
"user": "00000000-0000-0000-0000-000000000000",
"created": "2018-06-13T09:47:35.66097",
}
]
400:
description: >-
Request failed. The incoming payload seems malformed.
403:
description: >-
Request failed. User is not allowed to access workflow.
examples:
application/json:
{
"message": "User 00000000-0000-0000-0000-000000000000
is not allowed to access workflow
256b25f4-4cfb-4684-b7a8-73872ef455a1"
}
404:
description: >-
Request failed. User does not exist.
examples:
application/json:
{
"message": "User 00000000-0000-0000-0000-000000000000 does not
exist."
}
500:
description: >-
Request failed. Internal controller error.
examples:
application/json:
{
"message": "Something went wrong."
}
"""
try:
user_id = get_user_from_token(request.args.get('access_token'))
response, http_response = current_rwc_api_client.api.\
get_workflows(
user=user_id).result()
return jsonify(response), http_response.status_code
except HTTPError as e:
logging.error(traceback.format_exc())
return jsonify(e.response.json()), e.response.status_code
except ValueError as e:
logging.error(traceback.format_exc())
return jsonify({"message": str(e)}), 403
except Exception as e:
logging.error(traceback.format_exc())
return jsonify({"message": str(e)}), 500
@blueprint.route('/workflows', methods=['POST'])
[docs]def create_workflow(): # noqa
r"""Create a workflow.
---
post:
summary: Creates a new workflow based on a REANA specification file.
description: >-
This resource is expecting a REANA specification in JSON format with
all the necessary information to instantiate a workflow.
operationId: create_workflow
consumes:
- application/json
produces:
- application/json
parameters:
- name: workflow_name
in: query
description: Name of the workflow to be created. If not provided
name will be generated.
required: true
type: string
# probably need to rename this to something more specific
- name: spec
in: query
description: Remote repository which contains a valid REANA
specification.
required: false
type: string
- name: reana_specification
in: body
description: REANA specification with necessary data to instantiate
a workflow.
required: false
schema:
type: object
- name: access_token
in: query
description: Required. The API access_token of workflow owner.
required: true
type: string
responses:
201:
description: >-
Request succeeded. The workflow has been created.
schema:
type: object
properties:
message:
type: string
workflow_id:
type: string
workflow_name:
type: string
examples:
application/json:
{
"message": "The workflow has been successfully created.",
"workflow_id": "cdcf48b1-c2f3-4693-8230-b066e088c6ac",
"workflow_name": "mytest-1"
}
400:
description: >-
Request failed. The incoming payload seems malformed
403:
description: >-
Request failed. User is not allowed to access workflow.
examples:
application/json:
{
"message": "User 00000000-0000-0000-0000-000000000000
is not allowed to access workflow
256b25f4-4cfb-4684-b7a8-73872ef455a1"
}
404:
description: >-
Request failed. User does not exist.
examples:
application/json:
{
"message": "User 00000000-0000-0000-0000-000000000000 does not
exist."
}
500:
description: >-
Request failed. Internal controller error.
501:
description: >-
Request failed. Not implemented.
"""
try:
user_id = get_user_from_token(request.args.get('access_token'))
if request.json:
# validate against schema
reana_spec_file = request.json
workflow_engine = reana_spec_file['workflow']['type']
elif request.args.get('spec'):
return jsonify('Not implemented'), 501
else:
raise Exception('Either remote repository or a reana spec need to \
be provided')
if workflow_engine not in app.config['AVAILABLE_WORKFLOW_ENGINES']:
raise Exception('Unknown workflow type.')
workflow_name = request.args.get('workflow_name', '')
if is_uuid_v4(workflow_name):
return jsonify({'message':
'Workflow name cannot be a valid UUIDv4.'}), \
400
workflow_dict = {'reana_specification': reana_spec_file,
'workflow_name': workflow_name}
workflow_dict['operational_parameters'] = \
reana_spec_file.get('inputs', {}).get('parameters', {})
response, http_response = current_rwc_api_client.api.\
create_workflow(
workflow=workflow_dict,
user=user_id).result()
return jsonify(response), http_response.status_code
except HTTPError as e:
logging.error(traceback.format_exc())
return jsonify(e.response.json()), e.response.status_code
except KeyError as e:
logging.error(traceback.format_exc())
return jsonify({"message": str(e)}), 400
except ValueError as e:
logging.error(traceback.format_exc())
return jsonify({"message": str(e)}), 403
except Exception as e:
logging.error(traceback.format_exc())
return jsonify({"message": str(e)}), 500
@blueprint.route('/workflows/<workflow_id_or_name>/logs', methods=['GET'])
[docs]def get_workflow_logs(workflow_id_or_name): # noqa
r"""Get workflow logs.
---
get:
summary: Get workflow logs of a workflow.
description: >-
This resource reports the status of a workflow.
Resource is expecting a workflow UUID.
operationId: get_workflow_logs
produces:
- application/json
parameters:
- name: access_token
in: query
description: Required. API access_token of workflow owner.
required: true
type: string
- name: workflow_id_or_name
in: path
description: Required. Analysis UUID or name.
required: true
type: string
responses:
200:
description: >-
Request succeeded. Info about a workflow, including the status is
returned.
schema:
type: object
properties:
workflow_id:
type: string
workflow_name:
type: string
logs:
type: string
user:
type: string
examples:
application/json:
{
"workflow_id": "256b25f4-4cfb-4684-b7a8-73872ef455a1",
"workflow_name": "mytest-1",
"logs": "<Workflow engine log output>",
"user": "00000000-0000-0000-0000-000000000000"
}
400:
description: >-
Request failed. The incoming data specification seems malformed.
examples:
application/json:
{
"message": "Malformed request."
}
403:
description: >-
Request failed. User is not allowed to access workflow.
examples:
application/json:
{
"message": "User 00000000-0000-0000-0000-000000000000
is not allowed to access workflow
256b25f4-4cfb-4684-b7a8-73872ef455a1"
}
404:
description: >-
Request failed. User does not exist.
examples:
application/json:
{
"message": "Workflow cdcf48b1-c2f3-4693-8230-b066e088c6ac does
not exist"
}
500:
description: >-
Request failed. Internal controller error.
"""
try:
user_id = get_user_from_token(request.args.get('access_token'))
workflow_id_or_name = workflow_id_or_name
if not workflow_id_or_name:
raise KeyError("workflow_id_or_name is not supplied")
response, http_response = current_rwc_api_client.api.\
get_workflow_logs(
user=user_id,
workflow_id_or_name=workflow_id_or_name).result()
return jsonify(response), http_response.status_code
except HTTPError as e:
logging.error(traceback.format_exc())
return jsonify(e.response.json()), e.response.status_code
except KeyError as e:
logging.error(traceback.format_exc())
return jsonify({"message": str(e)}), 400
except ValueError as e:
logging.error(traceback.format_exc())
return jsonify({"message": str(e)}), 403
except Exception as e:
logging.error(traceback.format_exc())
return jsonify({"message": str(e)}), 500
@blueprint.route('/workflows/<workflow_id_or_name>/status', methods=['GET'])
[docs]def get_workflow_status(workflow_id_or_name): # noqa
r"""Get workflow status.
---
get:
summary: Get status of a workflow.
description: >-
This resource reports the status of a workflow.
Resource is expecting a workflow UUID.
operationId: get_workflow_status
produces:
- application/json
parameters:
- name: workflow_id_or_name
in: path
description: Required. Analysis UUID or name.
required: true
type: string
- name: access_token
in: query
description: Required. The API access_token of workflow owner.
required: true
type: string
responses:
200:
description: >-
Request succeeded. Info about a workflow, including the status is
returned.
schema:
type: object
properties:
id:
type: string
name:
type: string
created:
type: string
status:
type: string
user:
type: string
progress:
type: object
logs:
type: string
examples:
application/json:
{
"id": "256b25f4-4cfb-4684-b7a8-73872ef455a1",
"name": "mytest-1",
"created": "2018-06-13T09:47:35.66097",
"status": "created",
"user": "00000000-0000-0000-0000-000000000000"
}
400:
description: >-
Request failed. The incoming payload seems malformed.
examples:
application/json:
{
"message": "Malformed request."
}
403:
description: >-
Request failed. User is not allowed to access workflow.
examples:
application/json:
{
"message": "User 00000000-0000-0000-0000-000000000000
is not allowed to access workflow
256b25f4-4cfb-4684-b7a8-73872ef455a1"
}
404:
description: >-
Request failed. Either User or Analysis does not exist.
examples:
application/json:
{
"message": "Analysis 256b25f4-4cfb-4684-b7a8-73872ef455a1 does
not exist."
}
500:
description: >-
Request failed. Internal controller error.
"""
try:
user_id = get_user_from_token(request.args.get('access_token'))
workflow_id_or_name = workflow_id_or_name
if not workflow_id_or_name:
raise KeyError("workflow_id_or_name is not supplied")
response, http_response = current_rwc_api_client.api.\
get_workflow_status(
user=user_id,
workflow_id_or_name=workflow_id_or_name).result()
return jsonify(response), http_response.status_code
except HTTPError as e:
logging.error(traceback.format_exc())
return jsonify(e.response.json()), e.response.status_code
except KeyError as e:
logging.error(traceback.format_exc())
return jsonify({"message": str(e)}), 400
except ValueError as e:
logging.error(traceback.format_exc())
return jsonify({"message": str(e)}), 403
except Exception as e:
logging.error(traceback.format_exc())
return jsonify({"message": str(e)}), 500
@blueprint.route('/workflows/<workflow_id_or_name>/status', methods=['PUT'])
[docs]def set_workflow_status(workflow_id_or_name): # noqa
r"""Set workflow status.
---
put:
summary: Set status of a workflow.
description: >-
This resource reports the status of a workflow.
Resource is expecting a workflow UUID.
operationId: set_workflow_status
consumes:
- application/json
produces:
- application/json
parameters:
- name: workflow_id_or_name
in: path
description: Required. Analysis UUID or name.
required: true
type: string
- name: status
in: query
description: Required. New workflow status.
required: true
type: string
- name: access_token
in: query
description: Required. The API access_token of workflow owner.
required: true
type: string
- name: parameters
in: body
description: Optional. Extra parameters for workflow status.
required: false
schema:
type: object
responses:
200:
description: >-
Request succeeded. Info about a workflow, including the status is
returned.
schema:
type: object
properties:
message:
type: string
workflow_id:
type: string
workflow_name:
type: string
status:
type: string
user:
type: string
examples:
application/json:
{
"message": "Workflow successfully launched",
"id": "256b25f4-4cfb-4684-b7a8-73872ef455a1",
"workflow_name": "mytest-1",
"status": "created",
"user": "00000000-0000-0000-0000-000000000000"
}
400:
description: >-
Request failed. The incoming payload seems malformed.
examples:
application/json:
{
"message": "Malformed request."
}
403:
description: >-
Request failed. User is not allowed to access workflow.
examples:
application/json:
{
"message": "User 00000000-0000-0000-0000-000000000000
is not allowed to access workflow
256b25f4-4cfb-4684-b7a8-73872ef455a1"
}
404:
description: >-
Request failed. Either User or Workflow does not exist.
examples:
application/json:
{
"message": "Workflow 256b25f4-4cfb-4684-b7a8-73872ef455a1
does not exist"
}
409:
description: >-
Request failed. The workflow could not be started due to a
conflict.
examples:
application/json:
{
"message": "Workflow 256b25f4-4cfb-4684-b7a8-73872ef455a1
could not be started because it is already
running."
}
500:
description: >-
Request failed. Internal controller error.
501:
description: >-
Request failed. The specified status change is not implemented.
examples:
application/json:
{
"message": "Status resume is not supported yet."
}
"""
try:
user_id = get_user_from_token(request.args.get('access_token'))
workflow_id_or_name = workflow_id_or_name
if not workflow_id_or_name:
raise KeyError("workflow_id_or_name is not supplied")
status = request.args.get('status')
parameters = request.json
response, http_response = current_rwc_api_client.api.\
set_workflow_status(
user=user_id,
workflow_id_or_name=workflow_id_or_name,
status=status,
parameters=parameters).result()
return jsonify(response), http_response.status_code
except HTTPError as e:
logging.error(traceback.format_exc())
return jsonify(e.response.json()), e.response.status_code
except KeyError as e:
logging.error(traceback.format_exc())
return jsonify({"message": str(e)}), 400
except ValueError as e:
logging.error(traceback.format_exc())
return jsonify({"message": str(e)}), 403
except Exception as e:
logging.error(traceback.format_exc())
return jsonify({"message": str(e)}), 500
@blueprint.route('/workflows/<workflow_id_or_name>/workspace',
methods=['POST'])
[docs]def upload_file(workflow_id_or_name): # noqa
r"""Upload file to workspace.
---
post:
summary: Adds a file to the workspace.
description: >-
This resource is expecting a file to place in the workspace.
operationId: upload_file
consumes:
- multipart/form-data
produces:
- application/json
parameters:
- name: workflow_id_or_name
in: path
description: Required. Analysis UUID or name.
required: true
type: string
- name: file_content
in: formData
description: >-
Required. File to be transferred to the workflow workspace.
required: true
type: file
- name: file_name
in: query
description: Required. File name.
required: true
type: string
- name: access_token
in: query
description: Required. The API access_token of workflow owner.
required: true
type: string
responses:
200:
description: >-
Request succeeded. File successfully transferred.
schema:
type: object
properties:
message:
type: string
400:
description: >-
Request failed. The incoming payload seems malformed
403:
description: >-
Request failed. User is not allowed to access workflow.
examples:
application/json:
{
"message": "User 00000000-0000-0000-0000-000000000000
is not allowed to access workflow
256b25f4-4cfb-4684-b7a8-73872ef455a1"
}
404:
description: >-
Request failed. User does not exist.
examples:
application/json:
{
"message": "Workflow cdcf48b1-c2f3-4693-8230-b066e088c6ac does
not exist"
}
500:
description: >-
Request failed. Internal server error.
examples:
application/json:
{
"message": "Internal server error."
}
"""
try:
user_id = get_user_from_token(request.args.get('access_token'))
workflow_id_or_name = workflow_id_or_name
if not workflow_id_or_name:
raise KeyError("workflow_id_or_name is not supplied")
file_ = request.files['file_content'].stream.read()
response, http_response = current_rwc_api_client.api.\
upload_file(
user=user_id,
workflow_id_or_name=workflow_id_or_name,
file_content=file_,
file_name=request.args['file_name']).result()
return jsonify(response), http_response.status_code
except HTTPError as e:
logging.error(traceback.format_exc())
return jsonify(e.response.json()), e.response.status_code
except KeyError as e:
logging.error(traceback.format_exc())
return jsonify({"message": str(e)}), 400
except ValueError as e:
logging.error(traceback.format_exc())
return jsonify({"message": str(e)}), 403
except Exception as e:
logging.error(traceback.format_exc())
return jsonify({"message": str(e)}), 500
@blueprint.route(
'/workflows/<workflow_id_or_name>/workspace/<path:file_name>',
methods=['GET'])
[docs]def download_file(workflow_id_or_name, file_name): # noqa
r"""Download a file from the workspace.
---
get:
summary: Returns the requested file.
description: >-
This resource is expecting a workflow UUID and a file name existing
inside the workspace to return its content.
operationId: download_file
produces:
- multipart/form-data
parameters:
- name: workflow_id_or_name
in: path
description: Required. workflow UUID or name.
required: true
type: string
- name: file_name
in: path
description: Required. Name (or path) of the file to be downloaded.
required: true
type: string
- name: access_token
in: query
description: Required. The API access_token of workflow owner.
required: true
type: string
responses:
200:
description: >-
Requests succeeded. The file has been downloaded.
schema:
type: file
400:
description: >-
Request failed. The incoming payload seems malformed.
403:
description: >-
Request failed. User is not allowed to access workflow.
examples:
application/json:
{
"message": "User 00000000-0000-0000-0000-000000000000
is not allowed to access workflow
256b25f4-4cfb-4684-b7a8-73872ef455a1"
}
404:
description: >-
Request failed. `file_name` does not exist .
examples:
application/json:
{
"message": "input.csv does not exist"
}
500:
description: >-
Request failed. Internal server error.
examples:
application/json:
{
"message": "Internal server error."
}
"""
try:
user_id = get_user_from_token(request.args.get('access_token'))
workflow_id_or_name = workflow_id_or_name
if not workflow_id_or_name:
raise KeyError("workflow_id_or_name is not supplied")
response, http_response = current_rwc_api_client.api.\
download_file(
user=user_id,
workflow_id_or_name=workflow_id_or_name,
file_name=file_name).result()
return send_file(
io.BytesIO(http_response.raw_bytes),
attachment_filename=file_name,
mimetype='multipart/form-data'), 200
except HTTPError as e:
logging.error(traceback.format_exc())
return jsonify(e.response.json()), e.response.status_code
except KeyError as e:
logging.error(traceback.format_exc())
return jsonify({"message": str(e)}), 400
except ValueError as e:
logging.error(traceback.format_exc())
return jsonify({"message": str(e)}), 403
except Exception as e:
logging.error(traceback.format_exc())
return jsonify({"message": str(e)}), 500
@blueprint.route('/workflows/<workflow_id_or_name>/workspace',
methods=['GET'])
[docs]def get_files(workflow_id_or_name): # noqa
r"""List all files contained in a workspace.
---
get:
summary: Returns the workspace file list.
description: >-
This resource retrieves the file list of a workspace, given
its workflow UUID.
operationId: get_files
produces:
- application/json
parameters:
- name: workflow_id_or_name
in: path
description: Required. Analysis UUID or name.
required: true
type: string
- name: access_token
in: query
description: Required. The API access_token of workflow owner.
required: true
type: string
responses:
200:
description: >-
Requests succeeded. The list of files has been retrieved.
schema:
type: array
items:
type: object
properties:
name:
type: string
last-modified:
type: string
size:
type: integer
400:
description: >-
Request failed. The incoming payload seems malformed.
403:
description: >-
Request failed. User is not allowed to access workflow.
examples:
application/json:
{
"message": "User 00000000-0000-0000-0000-000000000000
is not allowed to access workflow
256b25f4-4cfb-4684-b7a8-73872ef455a1"
}
404:
description: >-
Request failed. Analysis does not exist.
examples:
application/json:
{
"message": "Analysis 256b25f4-4cfb-4684-b7a8-73872ef455a1 does
not exist."
}
500:
description: >-
Request failed. Internal server error.
examples:
application/json:
{
"message": "Internal server error."
}
"""
try:
user_id = get_user_from_token(request.args.get('access_token'))
workflow_id_or_name = workflow_id_or_name
if not workflow_id_or_name:
raise KeyError("workflow_id_or_name is not supplied")
response, http_response = current_rwc_api_client.api.\
get_files(
user=user_id,
workflow_id_or_name=workflow_id_or_name).result()
return jsonify(http_response.json()), http_response.status_code
except HTTPError as e:
logging.error(traceback.format_exc())
return jsonify(e.response.json()), e.response.status_code
except KeyError as e:
logging.error(traceback.format_exc())
return jsonify({"message": str(e)}), 400
except ValueError as e:
logging.error(traceback.format_exc())
return jsonify({"message": str(e)}), 403
except Exception as e:
logging.error(traceback.format_exc())
return jsonify({"message": str(e)}), 500