Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

api: add listing input files endpoint #47

Merged
merged 1 commit into from
Nov 6, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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