diff --git a/api/jobs/handlers.py b/api/jobs/handlers.py index ebef965d67a68db8b6c75aa4436d7330d1f14b90..3c058c97083e2a4b7f34827b55f787856fcd9d0a 100644 --- a/api/jobs/handlers.py +++ b/api/jobs/handlers.py @@ -444,8 +444,9 @@ class JobHandler(base.RequestHandler): def prepare_complete(self, _id): payload = self.request.json success = payload['success'] + elapsed = payload['elapsed'] - ticket = JobTicket.create(_id, success) + ticket = JobTicket.create(_id, success, elapsed) return { 'ticket': ticket } @require_login diff --git a/api/jobs/jobs.py b/api/jobs/jobs.py index 35be594fd77952d7aab6fced4cbf568e4752ba20..7c408d0c411c602c84276bb90f6d12741fd7fa9a 100644 --- a/api/jobs/jobs.py +++ b/api/jobs/jobs.py @@ -21,7 +21,7 @@ class Job(object): modified=None, state='pending', request=None, id_=None, config_=None, now=False, origin=None, saved_files=None, produced_metadata=None, batch=None, - failed_output_accepted=False): + failed_output_accepted=False, profile=None): """ Creates a job. @@ -71,6 +71,8 @@ class Job(object): created = time_now if modified is None: modified = time_now + if profile is None: + profile = {} if destination is None and inputs is not None: # Grab an arbitrary input's container @@ -106,6 +108,7 @@ class Job(object): self.produced_metadata = produced_metadata self.batch = batch self.failed_output_accepted = failed_output_accepted + self.profile = profile def intention_equals(self, other_job): @@ -165,7 +168,9 @@ class Job(object): saved_files=d.get('saved_files'), produced_metadata=d.get('produced_metadata'), batch=d.get('batch'), - failed_output_accepted=d.get('failed_output_accepted', False)) + failed_output_accepted=d.get('failed_output_accepted', False), + profile=d.get('profile', {}) + ) @classmethod def get(cls, _id): @@ -380,12 +385,13 @@ class JobTicket(object): return config.db.job_tickets.find_one({'_id': bson.ObjectId(_id)}) @staticmethod - def create(job_id, success): + def create(job_id, success, elapsed): j = Job.get(job_id) result = config.db.job_tickets.insert_one({ 'job': j.id_, 'success': success, + 'elapsed': elapsed, }) return result.inserted_id diff --git a/api/placer.py b/api/placer.py index 2bba7ca5596ae425aca6f4b4202b868b3748b195..6f3339383cef1caa98c628d6fac27d3c0da8007a 100644 --- a/api/placer.py +++ b/api/placer.py @@ -251,6 +251,13 @@ class EnginePlacer(Placer): def check(self): self.requireTarget() + + # Check that required state exists + if self.context.get('job_id'): + Job.get(self.context.get('job_id')) + if self.context.get('job_ticket_id'): + JobTicket.get(self.context.get('job_ticket_id')) + if self.metadata is not None: validators.validate_data(self.metadata, 'enginemetadata.json', 'input', 'POST', optional=True) @@ -325,9 +332,19 @@ class EnginePlacer(Placer): if job_ticket is not None: if success: - Queue.mutate(job, {'state': 'complete'}) + Queue.mutate(job, { + 'state': 'complete', + 'profile': { + 'elapsed': job_ticket['elapsed'] + } + }) else: - Queue.mutate(job, {'state': 'failed'}) + Queue.mutate(job, { + 'state': 'failed', + 'profile': { + 'elapsed': job_ticket['elapsed'] + } + }) if self.context.get('job_id'): job = Job.get(self.context.get('job_id')) @@ -697,6 +714,12 @@ class AnalysisJobPlacer(Placer): if self.id_ is None: raise Exception('Must specify a target analysis') + # Check that required state exists + if self.context.get('job_id'): + Job.get(self.context.get('job_id')) + if self.context.get('job_ticket_id'): + JobTicket.get(self.context.get('job_ticket_id')) + def process_file_field(self, field, file_attrs): if self.metadata is not None: file_mds = self.metadata.get('acquisition', {}).get('files', []) diff --git a/tests/integration_tests/python/test_jobs.py b/tests/integration_tests/python/test_jobs.py index 72d76622fdaae5c31305e1ffc3f1904d85e73b3e..220b21140da218aaa69345ae6c9e8d257a1805fc 100644 --- a/tests/integration_tests/python/test_jobs.py +++ b/tests/integration_tests/python/test_jobs.py @@ -387,8 +387,10 @@ def test_failed_job_output(data_builder, default_payload, as_user, as_admin, as_ assert r.ok job = r.json()['_id'] + api_db.jobs.update_one({'_id': bson.ObjectId(job)}, {'$set':{'state': 'running'}}) + # prepare completion (send success status before engine upload) - r = as_drone.post('/jobs/' + job + '/prepare-complete', json={'success': False}) + r = as_drone.post('/jobs/' + job + '/prepare-complete', json={'success': False, 'elapsed': -1}) assert r.ok # verify that job ticket has been created