Commit 79b8f5c7 authored by David Mendez's avatar David Mendez
Browse files

Review basic status function

parent ea464118
......@@ -9,6 +9,7 @@ from app.config import RunEnvs
from app.db import DB
from app.blueprints.swagger_description.swagger_description_blueprint import SWAGGER_BLUEPRINT
from app.blueprints.job_submission.controllers.job_submissions_controller import SUBMISSION_BLUEPRINT
from app.blueprints.job_status.job_status_controller import JOB_STATUS_BLUEPRINT
def create_app():
......@@ -51,6 +52,7 @@ def create_app():
flask_app.register_blueprint(SWAGGER_BLUEPRINT, url_prefix=f'{base_path}/swagger')
flask_app.register_blueprint(SUBMISSION_BLUEPRINT, url_prefix=f'{base_path}/submit')
flask_app.register_blueprint(JOB_STATUS_BLUEPRINT, url_prefix=f'{base_path}/status')
return flask_app
......
"""
Module that describes and handles the requests concerned with the job status
The blueprint used for handling the jobs status
"""
import werkzeug
# pylint: disable=W0622,C0103, R0201
from flask import abort, request
from flask_restx import Namespace, Resource, fields, reqparse
from flask import Blueprint, jsonify, request
from app.authorisation.decorators import token_required_for_job_id
from app.models import delayed_job_models
from app.namespaces.job_status import job_status_service
from app.blueprints.job_status import job_status_service
API = Namespace('status', description='Requests related to Job Status')
JOB_STATUS_BLUEPRINT = Blueprint('job_status', __name__)
MODIFIABLE_STATUS = API.model('ModifiableStatus', {
'status': fields.String(required=True, description='The status of the job ',
enum=[str(possible_status) for possible_status in delayed_job_models.JobStatuses]),
'status_comment': fields.String(required=True, description='A comment on the status of the job'),
'log': fields.String(required=True, description='The log of messages from the job'),
'progress': fields.String(required=True, description='The progress percentage of the job'),
'api_initial_url': fields.String(required=True, description='The initial URL of the API calls'),
})
@JOB_STATUS_BLUEPRINT.route('/<job_id>')
def get_job_status(job_id):
PUBLIC_STATUS = API.inherit('Status', MODIFIABLE_STATUS, {
'id': fields.String(required=True, description='The job identifier'),
'type': fields.String(required=True, description='The type of the job ',
enum=[str(possible_type) for possible_type in delayed_job_models.JobTypes]),
'created_at': fields.String(required=True, description='The time at which the job was created'),
'started_at': fields.String(required=True, description='The time at which the job started to run'),
'finished_at': fields.String(required=True, description='The time at which the job finished'),
'raw_params': fields.String(required=True, description='The stringified version of the parameters'),
'expires_at': fields.String(required=True, description='The date at which the job results will expire'),
'timezone': fields.String(required=True, description='The timezone where the job ran'),
'output_file_url': fields.String(required=True, description='The relative url for downloading the job result file'),
})
@API.route('/<id>')
@API.param('id', 'The job identifier')
@API.response(404, 'Job not found')
class JobStatus(Resource):
"""
Resource that handles job status requests
"""
@API.marshal_with(PUBLIC_STATUS)
def get(self, id): # pylint: disable=no-self-use
"""
Returns the status of a job
:return: a json response with the current job status
"""
try:
return job_status_service.get_job_status(id)
except job_status_service.JobNotFoundError:
abort(404)
@API.marshal_with(MODIFIABLE_STATUS)
@API.doc(security='jobKey', body=MODIFIABLE_STATUS)
@token_required_for_job_id
def patch(self, id):
"""
Updates a job with the data provided
:param id:
:return:
"""
new_data = {} # this is to avoid using custom data structures i.e CombinedMultiDict
for key in request.values.keys():
new_value = request.values.get(key)
new_data[key] = new_value
try:
return job_status_service.update_job_status(id, new_data)
except job_status_service.JobNotFoundError:
abort(404)
RESULT_FILE_OPERATION = API.model('Result File Operation', {
'result': fields.String(description='The result of the operation')
})
FILE_TO_UPLOAD = reqparse.RequestParser()
FILE_TO_UPLOAD.add_argument('file',
type=werkzeug.datastructures.FileStorage,
required=True,
help='Results file of the job')
@API.route('/<id>/results_file')
@API.param('id', 'The job identifier')
@API.response(404, 'Job not found')
class JobResultsFileUpload(Resource):
"""
Resource to handle the upload of a results file for a job
"""
@API.marshal_with(RESULT_FILE_OPERATION)
@API.expect(FILE_TO_UPLOAD)
@token_required_for_job_id
def post(self, id):
"""
Handles the upload of a results file for a job
:param id: job id
:return:
"""
received_file = request.files['file']
try:
return job_status_service.save_uploaded_file(id, received_file)
except job_status_service.JobNotFoundError:
abort(404)
response = job_status_service.get_job_status(job_id)
return jsonify(response)
\ No newline at end of file
"""
Module that describes and handles the requests concerned with the job status
"""
import werkzeug
# pylint: disable=W0622,C0103, R0201
from flask import abort, request
from flask_restx import Namespace, Resource, fields, reqparse
from app.authorisation.decorators import token_required_for_job_id
from app.models import delayed_job_models
from app.namespaces.job_status import job_status_service
API = Namespace('status', description='Requests related to Job Status')
MODIFIABLE_STATUS = API.model('ModifiableStatus', {
'status': fields.String(required=True, description='The status of the job ',
enum=[str(possible_status) for possible_status in delayed_job_models.JobStatuses]),
'status_comment': fields.String(required=True, description='A comment on the status of the job'),
'log': fields.String(required=True, description='The log of messages from the job'),
'progress': fields.String(required=True, description='The progress percentage of the job'),
'api_initial_url': fields.String(required=True, description='The initial URL of the API calls'),
})
PUBLIC_STATUS = API.inherit('Status', MODIFIABLE_STATUS, {
'id': fields.String(required=True, description='The job identifier'),
'type': fields.String(required=True, description='The type of the job ',
enum=[str(possible_type) for possible_type in delayed_job_models.JobTypes]),
'created_at': fields.String(required=True, description='The time at which the job was created'),
'started_at': fields.String(required=True, description='The time at which the job started to run'),
'finished_at': fields.String(required=True, description='The time at which the job finished'),
'raw_params': fields.String(required=True, description='The stringified version of the parameters'),
'expires_at': fields.String(required=True, description='The date at which the job results will expire'),
'timezone': fields.String(required=True, description='The timezone where the job ran'),
'output_file_url': fields.String(required=True, description='The relative url for downloading the job result file'),
})
@API.route('/<id>')
@API.param('id', 'The job identifier')
@API.response(404, 'Job not found')
class JobStatus(Resource):
"""
Resource that handles job status requests
"""
@API.marshal_with(PUBLIC_STATUS)
def get(self, id): # pylint: disable=no-self-use
"""
Returns the status of a job
:return: a json response with the current job status
"""
try:
return job_status_service.get_job_status(id)
except job_status_service.JobNotFoundError:
abort(404)
@API.marshal_with(MODIFIABLE_STATUS)
@API.doc(security='jobKey', body=MODIFIABLE_STATUS)
@token_required_for_job_id
def patch(self, id):
"""
Updates a job with the data provided
:param id:
:return:
"""
new_data = {} # this is to avoid using custom data structures i.e CombinedMultiDict
for key in request.values.keys():
new_value = request.values.get(key)
new_data[key] = new_value
try:
return job_status_service.update_job_status(id, new_data)
except job_status_service.JobNotFoundError:
abort(404)
RESULT_FILE_OPERATION = API.model('Result File Operation', {
'result': fields.String(description='The result of the operation')
})
FILE_TO_UPLOAD = reqparse.RequestParser()
FILE_TO_UPLOAD.add_argument('file',
type=werkzeug.datastructures.FileStorage,
required=True,
help='Results file of the job')
@API.route('/<id>/results_file')
@API.param('id', 'The job identifier')
@API.response(404, 'Job not found')
class JobResultsFileUpload(Resource):
"""
Resource to handle the upload of a results file for a job
"""
@API.marshal_with(RESULT_FILE_OPERATION)
@API.expect(FILE_TO_UPLOAD)
@token_required_for_job_id
def post(self, id):
"""
Handles the upload of a results file for a job
:param id: job id
:return:
"""
received_file = request.files['file']
try:
return job_status_service.save_uploaded_file(id, received_file)
except job_status_service.JobNotFoundError:
abort(404)
......@@ -38,28 +38,3 @@ def update_job_status(job_id, new_data):
return delayed_job_models.update_job_status(job_id, new_data)
except delayed_job_models.JobNotFoundError:
raise JobNotFoundError()
def save_uploaded_file(job_id, file):
"""
Receives an uploaded file and saves it in the static dir, it saves the local and public urls on the job.
:param job_id: job_id of the job to modify
:param file: flask file received
:return: a dict with the result of the process.
"""
try:
job = delayed_job_models.get_job_by_id(job_id)
output_dir_path = job.output_dir_path
os.makedirs(output_dir_path, exist_ok=True)
output_file_path = os.path.join(output_dir_path, file.filename)
file.save(output_file_path)
job.output_file_path = output_file_path
output_file_url = url_for('static', filename=f'jobs_output/{job.id}/{file.filename}')
job.output_file_url = output_file_url
delayed_job_models.save_job(job)
return {
'result': 'File received successfully'
}
except delayed_job_models.JobNotFoundError:
raise JobNotFoundError()
......@@ -108,7 +108,9 @@ def submit_job(job_type, input_files_desc, input_files_hashes, docker_image_url,
app_logging.info(f'Submitting Job: {job.id}')
prepare_job_and_submit(job, input_files_desc)
return job.public_dict()
return {
'job_id': job.id
}
return
try:
......
......@@ -103,9 +103,17 @@ class DelayedJob(DB.Model):
Returns a dictionary representation of the object with all the fields that are safe to be public
:return:
"""
return {key: str(getattr(self, key)) for key in ['id', 'type', 'status', 'status_log', 'progress',
plain_properties = {key: str(getattr(self, key)) for key in ['id', 'type', 'status', 'status_log', 'progress',
'created_at', 'started_at', 'finished_at', 'raw_params',
'expires_at', 'api_initial_url', 'timezone']}
'expires_at', 'api_initial_url', 'docker_image_url',
'timezone']}
output_files_urls = []
return {
**plain_properties,
'output_files_urls': output_files_urls
}
def update_run_status(self, new_value):
"""
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment