Skip to content

Commit

Permalink
api: add listing input files endpoint
Browse files Browse the repository at this point in the history
* (addresses reanahub/reana-client#40).

Signed-off-by: Diego Rodriguez <diego.rodriguez@cern.ch>
  • Loading branch information
Diego Rodriguez committed Nov 6, 2017
1 parent be4fd89 commit 1c83342
Show file tree
Hide file tree
Showing 5 changed files with 241 additions and 9 deletions.
75 changes: 75 additions & 0 deletions docs/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,81 @@
"summary": "Adds a file to the workflow workspace."
}
},
"/api/workflows/{workflow_id}/workspace/inputs": {
"get": {
"description": "This resource is expecting a workflow UUID and a filename to return its list of input files.",
"operationId": "get_workflow_inputs",
"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"
}
],
"produces": [
"multipart/form-data"
],
"responses": {
"200": {
"description": "Requests succeeded. The list of input files has been retrieved.",
"schema": {
"items": {
"properties": {
"last-modified": {
"format": "date-time",
"type": "string"
},
"name": {
"type": "string"
},
"size": {
"type": "integer"
}
},
"type": "object"
},
"type": "array"
}
},
"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 the list of input files for a specific workflow."
}
},
"/api/workflows/{workflow_id}/workspace/outputs/{file_name}": {
"get": {
"description": "This resource is expecting a workflow UUID and a filename to return its content.",
Expand Down
3 changes: 3 additions & 0 deletions reana_workflow_controller/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,6 @@

SQLALCHEMY_TRACK_MODIFICATIONS = False
"""Track modifications flag."""

INPUTS_RELATIVE_PATH = 'inputs'
"""Inputs directory name."""
26 changes: 19 additions & 7 deletions reana_workflow_controller/fsdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
# submit itself to any jurisdiction.
"""Workflow persistence management."""

import fs
from flask import current_app as app
from fs import open_fs, path


def get_user_analyses_dir(org, user):
Expand All @@ -32,12 +32,12 @@ def get_user_analyses_dir(org, user):
:param user: Working directory owner.
:return: Path to the user's analyses directory.
"""
return path.join(org, user, 'analyses')
return fs.path.join(org, user, 'analyses')


def create_user_space(user_id, org):
"""Create analyses directory for `user_id`."""
reana_fs = open_fs(app.config['SHARED_VOLUME_PATH'])
reana_fs = 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 @@ -55,15 +55,27 @@ def create_workflow_workspace(org, user, workflow_uuid):
:param workflow_uuid: Analysis UUID.
:return: Workflow and analysis workspace path.
"""
reana_fs = open_fs(app.config['SHARED_VOLUME_PATH'])
analysis_workspace = path.join(get_user_analyses_dir(org, user),
workflow_uuid)
reana_fs = fs.open_fs(app.config['SHARED_VOLUME_PATH'])
analysis_workspace = fs.path.join(get_user_analyses_dir(org, user),
workflow_uuid)

if not reana_fs.exists(analysis_workspace):
reana_fs.makedirs(analysis_workspace)

workflow_workspace = path.join(analysis_workspace, 'workspace')
workflow_workspace = fs.path.join(analysis_workspace, 'workspace')
if not reana_fs.exists(workflow_workspace):
reana_fs.makedirs(workflow_workspace)

return workflow_workspace, analysis_workspace


def list_directory_files(directory):
"""Return a list of files contained in a directory."""
fs_ = fs.open_fs(directory)
file_list = []
for file_name in fs_.walk.files():
file_details = fs_.getinfo(file_name, namespaces=['details'])
file_list.append({'name': file_name.lstrip('/'),
'last-modified': file_details.modified.isoformat(),
'size': file_details.size})
return file_list
95 changes: 94 additions & 1 deletion reana_workflow_controller/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from werkzeug.utils import secure_filename

from .factory import db
from .fsdb import create_workflow_workspace
from .fsdb import create_workflow_workspace, list_directory_files
from .models import User, Workflow
from .tasks import run_yadage_workflow

Expand Down Expand Up @@ -450,6 +450,99 @@ def get_workflow_outputs_file(workflow_id, file_name): # noqa
return jsonify({"message": str(e)}), 500


@restapi_blueprint.route('/workflows/<workflow_id>/workspace/inputs',
methods=['GET'])
def get_workflow_inputs(workflow_id): # noqa
r"""List all workflow input files.
---
get:
summary: Returns the list of input files for a specific workflow.
description: >-
This resource is expecting a workflow UUID and a filename to return
its list of input files.
operationId: get_workflow_inputs
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
responses:
200:
description: >-
Requests succeeded. The list of input files has been retrieved.
schema:
type: array
items:
type: object
properties:
name:
type: string
last-modified:
type: string
format: date-time
size:
type: integer
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()
if workflow:
outputs_directory = os.path.join(
current_app.config['SHARED_VOLUME_PATH'],
workflow.workspace_path,
current_app.config['INPUTS_RELATIVE_PATH'])

outputs_list = list_directory_files(outputs_directory)
return jsonify(outputs_list), 200
else:
return jsonify({'message': 'The workflow {} doesn\'t exist'.
format(str(workflow.id_))}), 404

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
51 changes: 50 additions & 1 deletion tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@
import os
import uuid

import fs
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 @@ -269,3 +269,52 @@ def test_get_workflow_outputs_file_with_path(app, db_session, default_user,
content_type='application/json',
data=json.dumps(data))
assert res.data == file_binary_content


def test_get_workflow_inputs_list(app, db_session, default_user,
tmp_shared_volume_path):
"""Test get list of input files."""
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
absolute_path_workflow_workspace = \
os.path.join(tmp_shared_volume_path,
workflow.workspace_path)
fs_ = fs.open_fs(absolute_path_workflow_workspace)
# from config
inputs_relative_path = app.config['INPUTS_RELATIVE_PATH']
fs_.makedirs(inputs_relative_path)
test_files = []
for i in range(5):
file_name = '{0}.csv'.format(i)
subdir_name = str(uuid.uuid4())
subdir = fs.path.join(inputs_relative_path, subdir_name)
fs_.makedirs(subdir)
fs_.touch('{0}/{1}'.format(subdir, file_name))
test_files.append(os.path.join(subdir_name, file_name))

res = client.get(
url_for('api.get_workflow_inputs', workflow_id=workflow_uuid),
query_string={"user": default_user.id_,
"organization": organization},
content_type='application/json',
data=json.dumps(data))
for file_ in json.loads(res.data.decode()):
assert file_.get('name') in test_files

0 comments on commit 1c83342

Please sign in to comment.