Skip to content

Commit

Permalink
api: add endpoint to retrieve output files
Browse files Browse the repository at this point in the history
* Removes REANAFS singleton since it was making tests complicated.

Signed-off-by: Diego Rodriguez <diego.rodriguez@cern.ch>
  • Loading branch information
Diego Rodriguez committed Nov 2, 2017
1 parent 5ddae3e commit ce8bc66
Show file tree
Hide file tree
Showing 4 changed files with 209 additions and 16 deletions.
67 changes: 67 additions & 0 deletions docs/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,73 @@
"summary": "Adds a file to the workflow workspace."
}
},
"/api/workflows/{workflow_id}/workspace/outputs/{file_name}": {
"get": {
"description": "This resource is expecting a workflow UUID and a filename to return its content.",
"operationId": "get_workflow_outputs_file",
"parameters": [
{
"description": "Required. Organization which the worklow belongs to.",
"in": "query",
"name": "organization",
"required": true,
"type": "string"
},
{
"description": "Required. UUID of workflow owner.",
"in": "query",
"name": "user",
"required": true,
"type": "string"
},
{
"description": "Required. Workflow UUID.",
"in": "path",
"name": "workflow_id",
"required": true,
"type": "string"
},
{
"description": "Required. Name of the file to be downloaded.",
"in": "path",
"name": "file_name",
"required": true,
"type": "string"
}
],
"produces": [
"multipart/form-data"
],
"responses": {
"200": {
"description": "Requests succeeded. The file has been downloaded.",
"schema": {
"type": "file"
}
},
"400": {
"description": "Request failed. The incoming data specification seems malformed."
},
"404": {
"description": "Request failed. User doesn't exist.",
"examples": {
"application/json": {
"message": "User 00000000-0000-0000-0000-000000000000 doesn't exist"
}
}
},
"500": {
"description": "Request failed. Internal controller error.",
"examples": {
"application/json": {
"message": "Either organization or user doesn't exist."
}
}
}
},
"summary": "Returns all workflows."
}
},
"/api/yadage/remote": {
"post": {
"consumes": [
Expand Down
17 changes: 2 additions & 15 deletions reana_workflow_controller/fsdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,6 @@
from fs import open_fs, path


class REANAFS(object):
"""REANA file system object."""

__instance = None

def __new__(cls):
"""REANA file system object creation."""
if REANAFS.__instance is None:
with app.app_context():
REANAFS.__instance = open_fs(app.config['SHARED_VOLUME_PATH'])
return REANAFS.__instance


def get_user_analyses_dir(org, user):
"""Build the analyses directory path for certain user and organization.
Expand All @@ -50,7 +37,7 @@ def get_user_analyses_dir(org, user):

def create_user_space(user_id, org):
"""Create analyses directory for `user_id`."""
reana_fs = REANAFS()
reana_fs = open_fs(app.config['SHARED_VOLUME_PATH'])
user_analyses_dir = get_user_analyses_dir(org, user_id)
if not reana_fs.exists(user_analyses_dir):
reana_fs.makedirs(user_analyses_dir)
Expand All @@ -68,7 +55,7 @@ def create_workflow_workspace(org, user, workflow_uuid):
:param workflow_uuid: Analysis UUID.
:return: Workflow and analysis workspace path.
"""
reana_fs = REANAFS()
reana_fs = open_fs(app.config['SHARED_VOLUME_PATH'])
analysis_workspace = path.join(get_user_analyses_dir(org, user),
workflow_uuid)

Expand Down
91 changes: 90 additions & 1 deletion reana_workflow_controller/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
import traceback
from uuid import uuid4

from flask import Blueprint, abort, jsonify, request
from flask import (Blueprint, abort, current_app, jsonify, request,
send_from_directory)
from werkzeug.utils import secure_filename

from .factory import db
Expand Down Expand Up @@ -360,6 +361,94 @@ def seed_workflow_workspace(workflow_id):
return jsonify({"message": str(e)}), 500


@restapi_blueprint.route(
'/workflows/<workflow_id>/workspace/outputs/<file_name>', methods=['GET'])
def get_workflow_outputs_file(workflow_id, file_name): # noqa
r"""Get all workflows.
---
get:
summary: Returns all workflows.
description: >-
This resource is expecting a workflow UUID and a filename to return
its content.
operationId: get_workflow_outputs_file
produces:
- multipart/form-data
parameters:
- name: organization
in: query
description: Required. Organization which the worklow belongs to.
required: true
type: string
- name: user
in: query
description: Required. UUID of workflow owner.
required: true
type: string
- name: workflow_id
in: path
description: Required. Workflow UUID.
required: true
type: string
- name: file_name
in: path
description: Required. Name of the file to be downloaded.
required: true
type: string
responses:
200:
description: >-
Requests succeeded. The file has been downloaded.
schema:
type: file
400:
description: >-
Request failed. The incoming data specification seems malformed.
404:
description: >-
Request failed. User doesn't exist.
examples:
application/json:
{
"message": "User 00000000-0000-0000-0000-000000000000 doesn't
exist"
}
500:
description: >-
Request failed. Internal controller error.
examples:
application/json:
{
"message": "Either organization or user doesn't exist."
}
"""
try:
user_uuid = request.args['user']
user = User.query.filter(User.id_ == user_uuid).first()
if not user:
return jsonify(
{'message': 'User {} does not exist'.format(user)}), 404

workflow = Workflow.query.filter(Workflow.id_ == workflow_id).first()
outputs_directory = os.path.join(
current_app.config['SHARED_VOLUME_PATH'],
workflow.workspace_path,
'outputs')
# fix, we don't know wich encoding is being used
# check how to add it to HTTP headers with `send_from_directory`
# or `send_file`
return send_from_directory(outputs_directory,
secure_filename(file_name),
mimetype='multipart/form-data',
as_attachment=True), 200

except KeyError:
return jsonify({"message": "Malformed request."}), 400
except Exception as e:
return jsonify({"message": str(e)}), 500


@restapi_blueprint.route('/yadage/remote', methods=['POST'])
def run_yadage_workflow_from_remote_endpoint(): # noqa
r"""Create a new yadage workflow from a remote repository.
Expand Down
50 changes: 50 additions & 0 deletions tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import uuid

from flask import url_for
from werkzeug.utils import secure_filename

from reana_workflow_controller.fsdb import get_user_analyses_dir
from reana_workflow_controller.models import Workflow, WorkflowStatus
Expand Down Expand Up @@ -173,3 +174,52 @@ def test_create_workflow_wrong_user(app, db_session, tmp_shared_volume_path):
tmp_shared_volume_path,
user_analyses_workspace)
assert not os.path.exists(workflow_workspace)


def test_get_workflow_outputs_file(app, db_session, default_user,
tmp_shared_volume_path):
"""Test download output file."""
with app.test_client() as client:
# create workflow
organization = 'default'
data = {'parameters': {'min_year': '1991',
'max_year': '2001'},
'specification': {'first': 'do this',
'second': 'do that'},
'type': 'cwl'}
res = client.post(url_for('api.create_workflow'),
query_string={
"user": default_user.id_,
"organization": organization},
content_type='application/json',
data=json.dumps(data))

response_data = json.loads(res.get_data(as_text=True))
workflow_uuid = response_data.get('workflow_id')
workflow = Workflow.query.filter(
Workflow.id_ == workflow_uuid).first()
# create file
file_name = 'output name.csv'
file_binary_content = b'1,2,3,4\n5,6,7,8'
absolute_path_workflow_workspace = \
os.path.join(tmp_shared_volume_path,
workflow.workspace_path)
# write file in the workflow workspace under `outputs` directory
file_path = os.path.join(absolute_path_workflow_workspace,
'outputs',
# we use `secure_filename` here because
# we use it in server side when adding
# files
secure_filename(file_name))
# because outputs directory doesn't exist by default
os.makedirs(os.path.dirname(file_path), exist_ok=True)
with open(file_path, 'wb+') as f:
f.write(file_binary_content)
res = client.get(
url_for('api.get_workflow_outputs_file', workflow_id=workflow_uuid,
file_name=file_name),
query_string={"user": default_user.id_,
"organization": organization},
content_type='application/json',
data=json.dumps(data))
assert res.data == file_binary_content

0 comments on commit ce8bc66

Please sign in to comment.