Skip to content

Commit

Permalink
Merge 7c8ba7e into 8255260
Browse files Browse the repository at this point in the history
  • Loading branch information
Diego committed Feb 7, 2019
2 parents 8255260 + 7c8ba7e commit 17ea841
Show file tree
Hide file tree
Showing 11 changed files with 379 additions and 59 deletions.
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ recursive-include docs *.rst
recursive-include docs *.txt
recursive-include reana_workflow_controller *.html
recursive-include reana_workflow_controller *.py
recursive-include reana_workflow_controller/templates *.template
recursive-include scripts *.py
recursive-include tests *.py
recursive-include tests *.finished
Expand Down
83 changes: 83 additions & 0 deletions docs/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,89 @@
"summary": "Returns logs of a specific workflow from a workflow engine."
}
},
"/api/workflows/{workflow_id_or_name}/open": {
"post": {
"consumes": [
"application/json"
],
"description": "This resource is expecting a workflow to start an interactive session within its workspace.",
"operationId": "open_interactive_session",
"parameters": [
{
"description": "Required. UUID of workflow owner.",
"in": "query",
"name": "user",
"required": true,
"type": "string"
},
{
"description": "Required. Workflow UUID or name.",
"in": "path",
"name": "workflow_id_or_name",
"required": true,
"type": "string"
},
{
"description": "Optional. Image to use when spawning the interactive session along with the needed port.",
"in": "body",
"name": "interactive_environment",
"required": false,
"schema": {
"properties": {
"image": {
"type": "string"
},
"port": {
"type": "integer"
}
},
"type": "object"
}
}
],
"produces": [
"application/json"
],
"responses": {
"200": {
"description": "Request succeeded. The interactive session has been opened.",
"examples": {
"application/json": {
"path": "/dd4e93cf-e6d0-4714-a601-301ed97eec60"
}
},
"schema": {
"properties": {
"path": {
"type": "string"
}
},
"type": "object"
}
},
"400": {
"description": "Request failed. The incoming data specification seems malformed.",
"examples": {
"application/json": {
"message": "Malformed request."
}
}
},
"404": {
"description": "Request failed. Either User or Workflow does not exist.",
"examples": {
"application/json": {
"message": "Workflow 256b25f4-4cfb-4684-b7a8-73872ef455a1 does not exist"
}
}
},
"500": {
"description": "Request failed. Internal controller error."
}
},
"summary": "Start an interactive session inside the workflow workspace."
}
},
"/api/workflows/{workflow_id_or_name}/parameters": {
"get": {
"description": "This resource reports the input parameters of workflow.",
Expand Down
10 changes: 10 additions & 0 deletions reana_workflow_controller/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,13 @@
'mountPath': '/cvmfs/atlas.cern.ch'
}
}

K8S_INTERACTIVE_DEPLOYMENT_TEMPLATE_PATH = \
"templates/k8s/interactive-deployment.template"
"""Path to the k8s interactive deployment template, relative to the package."""

DEFAULT_INTERACTIVE_SESSION_IMAGE = "jupyter/scipy-notebook"
"""Docker image to use by default when opening interactive sessions."""

DEFAULT_INTERACTIVE_SESSION_PORT = 8888
"""Default port used by the default interactive session image."""
10 changes: 7 additions & 3 deletions reana_workflow_controller/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,21 @@
"""REANA Workflow Controller errors."""


class WorkflowNameError(Exception):
class REANAWorkflowNameError(Exception):
"""."""


class REANAWorkflowControllerError(Exception):
"""Error when trying to manage workflows."""


class UploadPathError(Exception):
class REANAUploadPathError(Exception):
"""Provided paths contain '../'."""


class WorkflowDeletionError(Exception):
class REANAWorkflowDeletionError(Exception):
"""Error when trying to delete a workflow."""


class REANAInteractiveSessionError(Exception):
"""Error when trying to create an interactive session."""
118 changes: 110 additions & 8 deletions reana_workflow_controller/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@
WORKFLOW_QUEUES,
WORKFLOW_TIME_FORMAT)
from reana_workflow_controller.errors import (REANAWorkflowControllerError,
UploadPathError,
WorkflowDeletionError,
WorkflowNameError)
REANAUploadPathError,
REANAWorkflowDeletionError,
REANAWorkflowNameError)
from reana_workflow_controller.utils import (create_workflow_workspace,
list_directory_files,
remove_files_recursive_wildcard,
Expand Down Expand Up @@ -262,8 +262,8 @@ def create_workflow(): # noqa
workflow_name.encode('ascii')
except UnicodeEncodeError:
# `workflow_name` contains something else than just ASCII.
raise WorkflowNameError('Workflow name {} is not valid.'.
format(workflow_name))
raise REANAWorkflowNameError('Workflow name {} is not valid.'.
format(workflow_name))
# add spec and params to DB as JSON
workflow = Workflow(id_=workflow_uuid,
name=workflow_name,
Expand All @@ -282,7 +282,7 @@ def create_workflow(): # noqa
'workflow_id': workflow.id_,
'workflow_name': _get_workflow_name(workflow)}), 201

except (WorkflowNameError, KeyError) as e:
except (REANAWorkflowNameError, KeyError) as e:
return jsonify({"message": str(e)}), 400
except Exception as e:
return jsonify({"message": str(e)}), 500
Expand Down Expand Up @@ -376,7 +376,7 @@ def upload_file(workflow_id_or_name):
if full_file_name[0] == '/':
full_file_name = full_file_name[1:]
elif '..' in full_file_name.split("/"):
raise UploadPathError('Path cannot contain "..".')
raise REANAUploadPathError('Path cannot contain "..".')
absolute_workspace_path = os.path.join(
current_app.config['SHARED_VOLUME_PATH'],
workflow.get_workspace())
Expand Down Expand Up @@ -1129,6 +1129,108 @@ def set_workflow_status(workflow_id_or_name): # noqa
return jsonify({"message": str(e)}), 500


@restapi_blueprint.route('/workflows/<workflow_id_or_name>/open',
methods=['POST'])
def open_interactive_session(workflow_id_or_name): # noqa
r"""Start an interactive session inside the workflow workspace.
---
post:
summary: Start an interactive session inside the workflow workspace.
description: >-
This resource is expecting a workflow to start an interactive session
within its workspace.
operationId: open_interactive_session
consumes:
- application/json
produces:
- application/json
parameters:
- name: user
in: query
description: Required. UUID of workflow owner.
required: true
type: string
- name: workflow_id_or_name
in: path
description: Required. Workflow UUID or name.
required: true
type: string
- name: interactive_environment
in: body
description: >-
Optional. Image to use when spawning the interactive session along
with the needed port.
required: false
schema:
type: object
properties:
image:
type: string
port:
type: integer
responses:
200:
description: >-
Request succeeded. The interactive session has been opened.
schema:
type: object
properties:
path:
type: string
examples:
application/json:
{
"path": "/dd4e93cf-e6d0-4714-a601-301ed97eec60",
}
400:
description: >-
Request failed. The incoming data specification seems malformed.
examples:
application/json:
{
"message": "Malformed request."
}
404:
description: >-
Request failed. Either User or Workflow does not exist.
examples:
application/json:
{
"message": "User 00000000-0000-0000-0000-000000000000 does not
exist"
}
application/json:
{
"message": "Workflow 256b25f4-4cfb-4684-b7a8-73872ef455a1
does not exist"
}
500:
description: >-
Request failed. Internal controller error.
"""
try:
if request.json and not request.json.get("image"):
raise ValueError("If interactive_environment payload is sent, it˛"
"should contain the image property.")

user_uuid = request.args["user"]
image = request.json.get("image", DEFAULT_INTERACTIVE_SESSION_IMAGE)
port = request.json.get("port", DEFAULT_INTERACTIVE_SESSION_PORT)
workflow = None
workflow = _get_workflow_with_uuid_or_name(workflow_id_or_name,
user_uuid)
kwrm = KubernetesWorkflowRunManager(workflow)
access_path = kwrm.start_interactive_session(image, port)
return jsonify({"path": "{}".format(access_path)}), 200

except (KeyError, ValueError) as e:
status_code = 400 if workflow else 404
return jsonify({"message": str(e)}), status_code
except Exception as e:
return jsonify({"message": str(e)}), 500


@restapi_blueprint.route('/workflows/<workflow_id_or_name>/parameters',
methods=['GET'])
def get_workflow_parameters(workflow_id_or_name): # noqa
Expand Down Expand Up @@ -1494,7 +1596,7 @@ def _delete_workflow(workflow,
'status': workflow.status.name,
'user': str(workflow.owner_id)}), 200
elif workflow.status == WorkflowStatus.running:
raise WorkflowDeletionError(
raise REANAWorkflowDeletionError(
'Workflow {0}.{1} cannot be deleted as it'
' is currently running.'.
format(
Expand Down
38 changes: 0 additions & 38 deletions reana_workflow_controller/templates/bg-form.html

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: $deployment_name
spec:
rules:
- http:
paths:
- path: $ingress_path
backend:
serviceName: $deployment_name
servicePort: 8081
---
apiVersion: v1
kind: Service
metadata:
name: $deployment_name
spec:
type: LoadBalancer
ports:
- port: 8081
targetPort: $service_port
selector:
app: $deployment_name
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: $deployment_name
spec:
replicas: 1
selector:
matchLabels:
app: $deployment_name
template:
metadata:
labels:
app: $deployment_name
spec:
containers:
- name: $deployment_name
image: $image
ports:
- containerPort: $service_port
Loading

0 comments on commit 17ea841

Please sign in to comment.