From c60ebc49d23623dc945d499fdf4fc87ec509c5cd Mon Sep 17 00:00:00 2001
From: Ambrus Simon <ambrussimon@invenshure.com>
Date: Fri, 23 Jun 2017 19:33:51 +0200
Subject: [PATCH] POST /api/{cname}/{cid}/analyses working (including job=true)

---
 api/api.py                     | 10 +++----
 api/dao/containerstorage.py    | 51 ++++++++++++++++++----------------
 api/handlers/refererhandler.py | 33 +++++++++++-----------
 bin/database.py                |  2 +-
 4 files changed, 50 insertions(+), 46 deletions(-)

diff --git a/api/api.py b/api/api.py
index c4766f1f..d80ea88c 100644
--- a/api/api.py
+++ b/api/api.py
@@ -248,11 +248,11 @@ endpoints = [
                 route('/<list_name:files>/<name:{fname}>',      FileListHandler,                     m=['GET', 'DELETE']),
                 route('/<list_name:files>/<name:{fname}>/info', FileListHandler, h='get_info',       m=['GET']),
 
-                route( '/analyses>',                            AnalysesHandler,                  m=['POST']),
-                prefix('/analyses>', [
-                    route('/<_id:{cid}>',                       AnalysesHandler,                  m=['GET', 'DELETE']),
-                    route('/<_id:{cid}>/files',                 AnalysesHandler, h='download',    m=['GET']),
-                    route('/<_id:{cid}>/files/<name:{fname}>',  AnalysesHandler, h='download',    m=['GET']),
+                route( '/analyses',                             AnalysesHandler,                  m=['POST']),
+                prefix('/analyses', [
+                    route('/<_id:{cid}>',                      AnalysesHandler,                  m=['GET', 'DELETE']),
+                    route('/<_id:{cid}>/files',                AnalysesHandler, h='download',    m=['GET']),
+                    route('/<_id:{cid}>/files/<name:{fname}>', AnalysesHandler, h='download',    m=['GET']),
                 ]),
 
                 route('/<list_name:notes>',             NotesListHandler,               m=['POST']),
diff --git a/api/dao/containerstorage.py b/api/dao/containerstorage.py
index 3a49b47d..ccd2cb00 100644
--- a/api/dao/containerstorage.py
+++ b/api/dao/containerstorage.py
@@ -461,14 +461,19 @@ class AnalysisStorage(ContainerStorage):
 
 
     @staticmethod
-    def default_analysis(origin):
-        analysis_obj = {}
-        analysis_obj['_id'] = str(bson.objectid.ObjectId())
-        analysis_obj['created'] = datetime.datetime.utcnow()
-        analysis_obj['modified'] = datetime.datetime.utcnow()
-        analysis_obj['user'] = origin.get('id')
-
-        return analysis_obj
+    def fill_values(analysis, cont_name, cid, origin):
+        defaults = {
+            'parent':   {
+                'type': containerutil.singularize(cont_name),
+                'id':   bson.objectid.ObjectId(cid)
+            },
+            '_id':      bson.objectid.ObjectId(),
+            'created':  datetime.datetime.utcnow(),
+            'modified': datetime.datetime.utcnow(),
+            'user':     origin.get('id'),
+        }
+        defaults.update(analysis)
+        return defaults
 
 
     def create_job_and_analysis(self, cont_name, cid, analysis, job, origin):
@@ -480,11 +485,7 @@ class AnalysisStorage(ContainerStorage):
         from ..jobs.gears import validate_gear_config, get_gear
         from ..jobs.jobs import Job
 
-        cid = bson.objectid.ObjectId(cid)
-
-        default = self.default_analysis(origin)
-        default.update(analysis)
-        analysis = default
+        self.fill_values(analysis, cont_name, cid, origin)
 
         # Save inputs to analysis and job
         inputs = {} # For Job object (map of FileReferences)
@@ -502,33 +503,35 @@ class AnalysisStorage(ContainerStorage):
                 files.append(file_)
         analysis['files'] = files
 
-        result = self._create_el(cid, analysis, None)
-        if result.modified_count != 1:
-            raise APIStorageException('Element not added in list analyses of container {} {}'.format(cont_name, cid))
+        result = self.create_el(analysis)
+        if not result.acknowledged:
+            raise APIStorageException('Analysis not created for container {} {}'.format(cont_name, cid))
 
         # Prepare job
         tags = job.get('tags', [])
         if 'analysis' not in tags:
             tags.append('analysis')
 
-        gear_id = job['gear_id']
-
         # Config manifest check
-        gear = get_gear(gear_id)
+        gear = get_gear(job['gear_id'])
         if gear.get('gear', {}).get('custom', {}).get('flywheel', {}).get('invalid', False):
             raise APIConflictException('Gear marked as invalid, will not run!')
         validate_gear_config(gear, job.get('config'))
 
-        destination = containerutil.create_containerreference_from_dictionary({'type': 'analysis', 'id': analysis['_id']})
+        destination = containerutil.create_containerreference_from_dictionary(
+            {'type': 'analysis', 'id': str(analysis['_id'])})
 
-        job = Job(gear_id, inputs, destination=destination, tags=tags, config_=job.get('config'), origin=origin)
+        job = Job(job['gear_id'], inputs,
+            destination=destination, tags=tags, config_=job.get('config'), origin=origin)
         job_id = job.insert()
 
         if not job_id:
-            raise APIStorageException(500, 'Job not created for analysis {} of container {} {}'.format(analysis['_id'], cont_name, cid))
+            # NOTE #775 remove unusable analysis - until jobs have a 'hold' state
+            self.delete_el(analysis['_id'])
+            raise APIStorageException(500, 'Job not created for analysis of container {} {}'.format(cont_name, cid))
 
-        result = self._update_el(cid, {'_id': analysis['_id']}, {'job': job_id}, None)
-        return { 'analysis': analysis, 'job_id':job_id, 'job': job}
+        result = self.update_el(analysis['_id'], {'job': job_id}, None)
+        return {'analysis': analysis, 'job_id': job_id, 'job': job}
 
 
     @staticmethod
diff --git a/api/handlers/refererhandler.py b/api/handlers/refererhandler.py
index eeff1063..d1675f8f 100644
--- a/api/handlers/refererhandler.py
+++ b/api/handlers/refererhandler.py
@@ -50,7 +50,8 @@ class RefererHandler(base.RequestHandler):
         elif self.public_request:
             return containerauth.public_request(self, container=parent_container)
         else:
-            return self.permchecker(self, parent_container=parent_container)
+            # NOTE The handler (self) is passed implicitly
+            return self.permchecker(parent_container=parent_container)
 
 
 class AnalysesHandler(RefererHandler):
@@ -69,26 +70,26 @@ class AnalysesHandler(RefererHandler):
             analyses are only allowed at the session level.
         """
         parent = self.storage.get_parent(cont_name, cid)
-        permchecker = self._get_permchecker(parent)
+        permchecker = self.get_permchecker(parent)
         permchecker(noop)('POST')
 
         if self.is_true('job'):
-            if cont_name == 'sessions':
-                payload = self.request.json_body
-                self.input_validator(payload.get('analysis', {}), 'POST')
-                analysis = payload.get('analysis')
-                job = payload.get('job')
-                if job is None or analysis is None:
-                    self.abort(400, 'JSON body must contain map for "analysis" and "job"')
-                result = self.storage.create_job_and_analysis(cont_name, cid, analysis, job, self.origin)
-                return {'_id': result['analysis']['_id']}
-            else:
+            if cont_name != 'sessions':
                 self.abort(400, 'Analysis created via a job must be at the session level')
 
-        payload = upload.process_upload(self.request, upload.Strategy.analysis, origin=self.origin)
-        analysis = self.storage.default_analysis(self.origin)
-        analysis.update(payload)
-        result = self.storage.exec_op('POST', payload=analysis)
+            payload = self.request.json_body
+            analysis = payload.get('analysis')
+            job = payload.get('job')
+            if not analysis or not job:
+                self.abort(400, 'JSON body must contain map for "analysis" and "job"')
+            self.input_validator(analysis, 'POST')
+            result = self.storage.create_job_and_analysis(cont_name, cid, analysis, job, self.origin)
+            return {'_id': result['analysis']['_id']}
+
+        analysis = upload.process_upload(self.request, upload.Strategy.analysis, origin=self.origin)
+        self.storage.fill_values(analysis, cont_name, cid, self.origin)
+        self.input_validator(analysis, 'POST')
+        result = self.storage.create_el(analysis)
 
         if result.acknowledged:
             return {'_id': result.inserted_id}
diff --git a/bin/database.py b/bin/database.py
index c912fcc0..30e7005b 100755
--- a/bin/database.py
+++ b/bin/database.py
@@ -20,7 +20,7 @@ from api.jobs.jobs import Job
 from api.jobs import gears
 from api.types import Origin
 
-CURRENT_DATABASE_VERSION = 31 # An int that is bumped when a new schema change is made
+CURRENT_DATABASE_VERSION = 32 # An int that is bumped when a new schema change is made
 
 def get_db_version():
 
-- 
GitLab