From 116b707413dec9e01f3b785526d603e54080b376 Mon Sep 17 00:00:00 2001
From: Jorrit Schaap <schaap@astron.nl>
Date: Mon, 6 Jul 2020 12:53:06 +0200
Subject: [PATCH] TMSS-189: added tmss_id to RADB task table, and adapted RA
 code to handle either otdb_id or tmss_id. When TMSS is done all references in
 RA to otdb_id/mom_id can be removed simplifying lots of code.

---
 MAC/Services/test/tPipelineControl.py         |  4 +-
 .../Common/lib/specification.py               | 18 ++--
 .../test/t_resourceassigner.py                |  6 +-
 .../ResourceAssigner/test/t_schedulers.py     |  2 +
 .../ResourceAssignmentDatabase/radb.py        | 84 ++++++++++++----
 .../radb/sql/create_database.sql              |  6 +-
 .../tests/t_radb_functionality.py             | 98 ++++++++++---------
 .../tests/t_radb_performance.py               |  2 +-
 .../ResourceAssignmentService/rpc.py          | 26 +++--
 .../ResourceAssignmentService/service.py      | 13 ++-
 10 files changed, 166 insertions(+), 93 deletions(-)

diff --git a/MAC/Services/test/tPipelineControl.py b/MAC/Services/test/tPipelineControl.py
index c4603858fbb..8c2e72031dd 100644
--- a/MAC/Services/test/tPipelineControl.py
+++ b/MAC/Services/test/tPipelineControl.py
@@ -99,7 +99,7 @@ class MockRAService(ServiceMessageHandler):
                            for x in predecessors}
         self.status = status
 
-    def GetTask(self, id, mom_id, otdb_id, specification_id):
+    def GetTask(self, id, mom_id, otdb_id, specification_id, **kwargs):
         logger.info("***** GetTask(%s) *****", otdb_id)
 
         return {
@@ -113,7 +113,7 @@ class MockRAService(ServiceMessageHandler):
         }
 
     def GetTasks(self, lower_bound, upper_bound, task_ids, task_status, task_type, mom_ids,
-                 otdb_ids, cluster):
+                 otdb_ids, cluster, **kwargs):
         logger.info("***** GetTasks(%s) *****", task_ids)
 
         if task_ids is None:
diff --git a/SAS/ResourceAssignment/Common/lib/specification.py b/SAS/ResourceAssignment/Common/lib/specification.py
index ff54d542f02..9ab0a18e7d4 100644
--- a/SAS/ResourceAssignment/Common/lib/specification.py
+++ b/SAS/ResourceAssignment/Common/lib/specification.py
@@ -77,6 +77,7 @@ class Specification:
         self.otdb_id    = None # Task Id in OTDB
         self.mom_id     = None # Task Id in MoM
         self.radb_id    = None # Task Id in RADB
+        self.tmss_id    = None # (Sub)Task Id in TMSS
         self.trigger_id = None # Id of trigger is this was specified in a trigger
         self.type       = None # Task type in RADB
         self.subtype    = None # Task type in RADB
@@ -139,6 +140,7 @@ class Specification:
         result["otdb_id"]       = self.otdb_id
         result["mom_id"]        = self.mom_id
         result["task_id"]       = self.radb_id
+        result["tmss_id"]       = self.tmss_id
         result["trigger_id"]    = self.trigger_id
         result["status"]        = self.status
         result["task_type"]     = self.type
@@ -170,6 +172,7 @@ class Specification:
         self.otdb_id       = input_dict.get("otdb_id")
         self.mom_id        = input_dict.get("mom_id")
         self.radb_id       = input_dict.get("task_id")
+        self.tmss_id       = input_dict.get("tmss_id")
         self.trigger_id    = input_dict.get("trigger_id")
         self.status        = input_dict["status"]
         self.type          = input_dict["task_type"]
@@ -980,6 +983,7 @@ class Specification:
             self.radb_id  = task["id"] # Should be the same as radb_id, but self.radb_id might not yet be set
             self.mom_id   = task["mom_id"]
             self.otdb_id  = task["otdb_id"]
+            self.tmss_id  = task.get("tmss_id")
             self.status   = task["status"]
             self.type     = task["type"]
             self.duration = timedelta(seconds = task["duration"])
@@ -1023,13 +1027,13 @@ class Specification:
 
         assignable_task_states = ['approved', 'prescheduled', 'error']
         if self.status in assignable_task_states:
-            logger.info('Task otdb_id=%s with status \'%s\' is assignable' % (self.otdb_id, self.status))
+            logger.info('Task otdb_id=%s tmss_id=%s with status \'%s\' is assignable' % (self.otdb_id, self.tmss_id, self.status))
         else:
             assignable_task_states_str = ', '.join(assignable_task_states)
-            logger.warn('Task otdb_id=%s with status \'%s\' is not assignable. Allowed statuses are %s' %
-                        (self.otdb_id, self.status, assignable_task_states_str))
+            logger.warn('Task otdb_id=%s tmss_id=%s with status \'%s\' is not assignable. Allowed statuses are %s' %
+                        (self.otdb_id, self.tmss_id, self.status, assignable_task_states_str))
 
-            message = "Unsupported status '%s' of task with OTDB ID: %s" % (self.status, self.otdb_id)
+            message = "Unsupported status '%s' of task with otdb_id=%s tmss_id=%s" % (self.status, self.otdb_id, self.tmss_id)
             raise Exception(message) #TODO more specific exception type?
 
     def set_status(self, new_status):
@@ -1063,10 +1067,10 @@ class Specification:
         """
 
         logger.info(
-            'insertSpecification mom_id=%s, otdb_id=%s, status=%s, task_type=%s, start_time=%s, end_time=%s '
-            'cluster=%s' % (self.mom_id, self.otdb_id, self.status, self.type, self.starttime, self.endtime, self.cluster)
+            'insertSpecification mom_id=%s, otdb_id=%s, tmss_id=%s, status=%s, task_type=%s, start_time=%s, end_time=%s '
+            'cluster=%s' % (self.mom_id, self.otdb_id, self.tmss_id, self.status, self.type, self.starttime, self.endtime, self.cluster)
         )
-        result = self.radb.insertOrUpdateSpecificationAndTask(self.mom_id, self.otdb_id, self.status, self.type, self.starttime,
+        result = self.radb.insertOrUpdateSpecificationAndTask(self.mom_id, self.otdb_id, self.tmss_id, self.status, self.type, self.starttime,
                                                               self.endtime, str(self.as_dict()), self.cluster, commit=True) #TODO use internal_dict?
 
         specification_id = result['specification_id'] # We never seem to need this again
diff --git a/SAS/ResourceAssignment/ResourceAssigner/test/t_resourceassigner.py b/SAS/ResourceAssignment/ResourceAssigner/test/t_resourceassigner.py
index ec65936f4e1..d434a04de0d 100755
--- a/SAS/ResourceAssignment/ResourceAssigner/test/t_resourceassigner.py
+++ b/SAS/ResourceAssignment/ResourceAssigner/test/t_resourceassigner.py
@@ -607,7 +607,7 @@ class ResourceAssignerTest(RADBCommonTestMixin, unittest.TestCase):
     @integration_test
     def test_do_assignment_notifies_bus_when_it_was_unable_to_schedule_Conflict(self):
         # prepare: insert a blocking task with a huge claim on storage (directly via the radb, not via the resource_assigner)
-        task_id = self.radb.insertOrUpdateSpecificationAndTask(9876, 9876, 'prescheduled', 'observation',
+        task_id = self.radb.insertOrUpdateSpecificationAndTask(9876, 9876, None, 'prescheduled', 'observation',
                                                        datetime.datetime.utcnow()-datetime.timedelta(days=1),
                                                        datetime.datetime.utcnow()+datetime.timedelta(days=1),
                                                        "", "CEP4")['task_id']
@@ -911,7 +911,7 @@ class ResourceAssignerTest(RADBCommonTestMixin, unittest.TestCase):
         The resource assigner should be able to handle that, or prevent that.'''
 
         # prepare: insert a blocking task with a huge claim on storage (directly via the radb, not via the resource_assigner)
-        task_id = self.radb.insertOrUpdateSpecificationAndTask(9876, 9876, 'approved', 'observation',
+        task_id = self.radb.insertOrUpdateSpecificationAndTask(9876, 9876, None, 'approved', 'observation',
                                                        datetime.datetime.utcnow()-datetime.timedelta(days=1),
                                                        datetime.datetime.utcnow()+datetime.timedelta(days=1),
                                                        "", "CEP4")['task_id']
@@ -932,7 +932,7 @@ class ResourceAssignerTest(RADBCommonTestMixin, unittest.TestCase):
         self.assertEqual('active', self.radb.getTask(task_id)['status'])
 
         # create a second task (caused by a trigger)
-        task2_id = self.radb.insertOrUpdateSpecificationAndTask(8765, 8765, 'approved', 'observation',
+        task2_id = self.radb.insertOrUpdateSpecificationAndTask(8765, 8765, None, 'approved', 'observation',
                                                        datetime.datetime.utcnow(),
                                                        datetime.datetime.utcnow()+datetime.timedelta(hours=1),
                                                        "", "CEP4")['task_id']
diff --git a/SAS/ResourceAssignment/ResourceAssigner/test/t_schedulers.py b/SAS/ResourceAssignment/ResourceAssigner/test/t_schedulers.py
index b6d87543c97..4a2ba929b7c 100755
--- a/SAS/ResourceAssignment/ResourceAssigner/test/t_schedulers.py
+++ b/SAS/ResourceAssignment/ResourceAssigner/test/t_schedulers.py
@@ -86,6 +86,7 @@ class BasicSchedulerTest(SchedulerTest):
 
         return self.radb.insertOrUpdateSpecificationAndTask(mom_id=mom_otdb_id,
                                                     otdb_id=mom_otdb_id,
+                                                    tmss_id=None,
                                                     task_status='approved',
                                                     task_type='observation',
                                                     starttime=starttime,
@@ -506,6 +507,7 @@ class PrioritySchedulerTest(StationSchedulerTest):
     def new_task_without_momid(self, otdb_id):
         return self.radb.insertOrUpdateSpecificationAndTask(mom_id=None,
                                                     otdb_id=otdb_id,
+                                                    tmss_id=None,
                                                     task_status='approved',
                                                     task_type='observation',
                                                     starttime=datetime.datetime(2017, 1, 1, 1, 0, 0),
diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py
index 43a7c7674b9..caeeb439d07 100644
--- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py
+++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb.py
@@ -134,8 +134,8 @@ class RADatabase(PostgresDatabaseConnection):
 
         raise KeyError('No such status_id: %s. Valid values are: %s' % (status_id, ', '.join([x['id'] for x in self.getResourceClaimStatuses()])))
 
-    def getTasksTimeWindow(self, task_ids=None, mom_ids=None, otdb_ids=None):
-        if len([x for x in [task_ids, mom_ids, otdb_ids] if x != None]) > 1:
+    def getTasksTimeWindow(self, task_ids=None, mom_ids=None, otdb_ids=None, tmss_ids=None):
+        if len([x for x in [task_ids, mom_ids, otdb_ids, tmss_ids] if x != None]) > 1:
             raise KeyError("Provide either task_ids or mom_ids or otdb_ids, not multiple kinds.")
 
         query = '''SELECT min(starttime) as min_starttime, max(endtime) as max_endtime from resource_allocation.task_view'''
@@ -173,6 +173,16 @@ class RADatabase(PostgresDatabaseConnection):
             elif len(otdb_ids) == 0: #assume a list/enumerable of id's, length 0
                 return []
 
+        if tmss_ids is not None:
+            if isinstance(tmss_ids, int): # just a single id
+                conditions.append('tmss_id = %s')
+                qargs.append(tmss_ids)
+            elif len(tmss_ids) > 0: #assume a list/enumerable of id's
+                conditions.append('tmss_id in %s')
+                qargs.append(tuple(tmss_ids))
+            elif len(tmss_ids) == 0: #assume a list/enumerable of id's, length 0
+                return []
+            
         if conditions:
             query += ' WHERE ' + ' AND '.join(conditions)
 
@@ -184,9 +194,9 @@ class RADatabase(PostgresDatabaseConnection):
 
         return result
 
-    def getTasks(self, lower_bound=None, upper_bound=None, task_ids=None, task_status=None, task_type=None, mom_ids=None, otdb_ids=None, cluster=None):
-        if len([x for x in [task_ids, mom_ids, otdb_ids] if x != None]) > 1:
-            raise KeyError("Provide either task_ids or mom_ids or otdb_ids, not multiple kinds.")
+    def getTasks(self, lower_bound=None, upper_bound=None, task_ids=None, task_status=None, task_type=None, mom_ids=None, otdb_ids=None, tmss_ids=None, cluster=None):
+        if len([x for x in [task_ids, mom_ids, otdb_ids, tmss_ids] if x != None]) > 1:
+            raise KeyError("Provide either task_ids or mom_ids or otdb_ids or tmss_ids, not multiple kinds.")
 
         query = '''SELECT * from resource_allocation.task_view'''
 
@@ -231,6 +241,16 @@ class RADatabase(PostgresDatabaseConnection):
             elif len(otdb_ids) == 0: #assume a list/enumerable of id's, length 0
                 return []
 
+        if tmss_ids is not None:
+            if isinstance(tmss_ids, int): # just a single id
+                conditions.append('tmss_id = %s')
+                qargs.append(tmss_ids)
+            elif len(tmss_ids) > 0: #assume a list/enumerable of id's
+                conditions.append('tmss_id in %s')
+                qargs.append(tuple(tmss_ids))
+            elif len(tmss_ids) == 0: #assume a list/enumerable of id's, length 0
+                return []
+            
         task_status, task_type = self._convertTaskTypeAndStatusToIds(task_status, task_type)
 
         if task_status is not None:
@@ -272,9 +292,9 @@ class RADatabase(PostgresDatabaseConnection):
         return tasks
 
 
-    def getTask(self, id=None, mom_id=None, otdb_id=None, specification_id=None):
-        '''get a task for either the given (task)id, or for the given mom_id, or for the given otdb_id, or for the given specification_id'''
-        ids = [id, mom_id, otdb_id, specification_id]
+    def getTask(self, id=None, mom_id=None, otdb_id=None, tmss_id=None, specification_id=None):
+        '''get a task for either the given (task)id, or for the given mom_id, or for the given otdb_id, or for the given tmss_id, or for the given specification_id'''
+        ids = [id, mom_id, otdb_id, tmss_id, specification_id]
         validIds = [x for x in ids if x != None]
 
         if len(validIds) != 1:
@@ -287,6 +307,8 @@ class RADatabase(PostgresDatabaseConnection):
             query += '''where tv.mom_id = (%s);'''
         elif otdb_id is not None:
             query += '''where tv.otdb_id = (%s);'''
+        elif tmss_id is not None:
+            query += '''where tv.tmss_id = (%s);'''
         elif specification_id is not None:
             query += '''where tv.specification_id = (%s);'''
 
@@ -327,24 +349,27 @@ class RADatabase(PostgresDatabaseConnection):
         '''converts task_status and task_type to id's in case one and/or the other are strings'''
         return self._convertTaskStatusToId(task_status), self._convertTaskTypeToId(task_type)
 
-    def insertTask(self, mom_id, otdb_id, task_status, task_type, specification_id, commit=True):
+    def insertTask(self, mom_id, otdb_id, tmss_id, task_status, task_type, specification_id, commit=True):
         if isinstance(mom_id, int) and mom_id < 0:
             mom_id = None
 
         if isinstance(otdb_id, int) and otdb_id < 0:
             otdb_id = None
 
-        logger.info('insertTask mom_id=%s, otdb_id=%s, task_status=%s, task_type=%s, specification_id=%s' %
-                    (mom_id, otdb_id, task_status, task_type, specification_id))
+        if isinstance(tmss_id, int) and tmss_id < 0:
+            tmss_id = None
+
+        logger.info('insertTask mom_id=%s, otdb_id=%s, tmss_id=%s, task_status=%s, task_type=%s, specification_id=%s' %
+                    (mom_id, otdb_id, tmss_id, task_status, task_type, specification_id))
         task_status, task_type = self._convertTaskTypeAndStatusToIds(task_status, task_type)
 
         query = '''LOCK TABLE resource_allocation.resource_claim, resource_allocation.resource_usage, resource_allocation.task IN EXCLUSIVE MODE; '''\
                 '''INSERT INTO resource_allocation.task
-        (mom_id, otdb_id, status_id, type_id, specification_id)
-        VALUES (%s, %s, %s, %s, %s)
+        (mom_id, otdb_id, tmss_id, status_id, type_id, specification_id)
+        VALUES (%s, %s, %s, %s, %s, %s)
         RETURNING id;'''
 
-        id = self.executeQuery(query, (mom_id, otdb_id, task_status, task_type, specification_id), fetch=FETCH_ONE).get('id')
+        id = self.executeQuery(query, (mom_id, otdb_id, tmss_id, task_status, task_type, specification_id), fetch=FETCH_ONE).get('id')
         if commit:
             self.commit()
         return id
@@ -377,7 +402,24 @@ class RADatabase(PostgresDatabaseConnection):
 
         return self._cursor.rowcount > 0
 
-    def updateTask(self, task_id, mom_id=None, otdb_id=None, task_status=None, task_type=None, specification_id=None, commit=True):
+    def updateTaskStatusForTMSSId(self, tmss_id, task_status, commit=True):
+        '''converts task_status and task_type to id's in case one and/or the other are strings'''
+        if task_status is not None and isinstance(task_status, str):
+            #convert task_status string to task_status.id
+            task_status = self.getTaskStatusId(task_status, True)
+
+        query = '''LOCK TABLE resource_allocation.resource_claim, resource_allocation.resource_usage, resource_allocation.task IN EXCLUSIVE MODE; '''\
+                '''UPDATE resource_allocation.task
+        SET status_id = %s
+        WHERE resource_allocation.task.tmss_id = %s;'''
+
+        self.executeQuery(query, [task_status, tmss_id])
+        if commit:
+            self.commit()
+
+        return self._cursor.rowcount > 0
+    
+    def updateTask(self, task_id, mom_id=None, otdb_id=None, tmss_id=None, task_status=None, task_type=None, specification_id=None, commit=True):
         '''Update the given paramenters for the task with given task_id.
         Inside the database consistency checks are made.
         When one or more claims of a task are in conflict status, then its task is set to conflict as well, and hence cannot be scheduled.
@@ -398,6 +440,10 @@ class RADatabase(PostgresDatabaseConnection):
             fields.append('otdb_id')
             values.append(otdb_id)
 
+        if tmss_id is not None :
+            fields.append('tmss_id')
+            values.append(tmss_id)
+
         if task_status is not None :
             fields.append('status_id')
             values.append(task_status)
@@ -1567,13 +1613,15 @@ class RADatabase(PostgresDatabaseConnection):
         else:
             self.executeQuery('SELECT * from resource_allocation.rebuild_resource_usages_from_claims_for_resource_of_status(%s, %s)', (resource_id, claim_status_id), fetch=FETCH_NONE)
 
-    def insertOrUpdateSpecificationAndTask(self, mom_id, otdb_id, task_status, task_type, starttime, endtime, content, cluster, commit=True):
+    def insertOrUpdateSpecificationAndTask(self, mom_id, otdb_id, tmss_id, task_status, task_type, starttime, endtime, content, cluster, commit=True):
         '''
         Insert a new specification and task in one transaction.
         Removes resource_claims for existing task with same otdb_id if present in the same transaction.
         '''
         try:
             existing_task = self.getTask(otdb_id=otdb_id)
+            if existing_task is None and tmss_id is not None:
+                existing_task = self.getTask(tmss_id=tmss_id)
             if existing_task is None and mom_id is not None:
                 existing_task = self.getTask(mom_id=mom_id)
 
@@ -1584,10 +1632,10 @@ class RADatabase(PostgresDatabaseConnection):
                 taskId = existing_task['id']
                 self.deleteResourceClaimForTask(existing_task['id'], False)
                 self.updateSpecification(specId, starttime=starttime, endtime=endtime, content=content, cluster=cluster, commit=False)
-                self.updateTask(taskId, mom_id=mom_id, otdb_id=otdb_id, task_status=task_status, task_type=task_type, commit=False)
+                self.updateTask(taskId, mom_id=mom_id, otdb_id=otdb_id, tmss_id=tmss_id, task_status=task_status, task_type=task_type, commit=False)
             else:
                 specId = self.insertSpecification(starttime, endtime, content, cluster, False)
-                taskId = self.insertTask(mom_id, otdb_id, task_status, task_type, specId, False)
+                taskId = self.insertTask(mom_id, otdb_id, tmss_id, task_status, task_type, specId, False)
 
             if commit:
                 self.commit()
diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb/sql/create_database.sql b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb/sql/create_database.sql
index 489b3f6a4d4..f2d625e128d 100644
--- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb/sql/create_database.sql
+++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radb/sql/create_database.sql
@@ -177,6 +177,7 @@ CREATE TABLE resource_allocation.task (
   id serial NOT NULL,
   mom_id integer UNIQUE,
   otdb_id integer UNIQUE,
+  tmss_id integer UNIQUE,
   status_id integer NOT NULL REFERENCES resource_allocation.task_status DEFERRABLE INITIALLY IMMEDIATE,
   type_id integer NOT NULL REFERENCES resource_allocation.task_type DEFERRABLE INITIALLY IMMEDIATE,
   specification_id integer NOT NULL REFERENCES resource_allocation.specification  ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE,
@@ -191,6 +192,9 @@ CREATE INDEX task_mom_id_idx
 CREATE INDEX task_otdb_id_idx
   ON resource_allocation.task (otdb_id);
 
+CREATE INDEX task_tmss_id_idx
+  ON resource_allocation.task (tmss_id);
+
 CREATE INDEX task_status_id_idx
   ON resource_allocation.task (status_id);
 
@@ -416,7 +420,7 @@ ALTER TABLE resource_allocation.config
 -- VIEWS ----------------------------------------------
 
 CREATE OR REPLACE VIEW resource_allocation.task_view AS
- SELECT t.id, t.mom_id, t.otdb_id, t.status_id, t.type_id, t.specification_id,
+ SELECT t.id, t.mom_id, t.otdb_id, t.tmss_id, t.status_id, t.type_id, t.specification_id,
     ts.name AS status, tt.name AS type, s.starttime, s.endtime, extract(epoch from age(s.endtime, s.starttime)) as duration, s.cluster,
     (SELECT array_agg(tp.predecessor_id) FROM resource_allocation.task_predecessor tp where tp.task_id=t.id) as predecessor_ids,
     (SELECT array_agg(tp.task_id) FROM resource_allocation.task_predecessor tp where tp.predecessor_id=t.id) as successor_ids,
diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/tests/t_radb_functionality.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/tests/t_radb_functionality.py
index 8650940395a..c7f35b9e962 100755
--- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/tests/t_radb_functionality.py
+++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/tests/t_radb_functionality.py
@@ -139,6 +139,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
 
         task = self.radb.insertOrUpdateSpecificationAndTask(mom_id=mom_id,
                                                     otdb_id=otdb_id,
+                                                    tmss_id=None,
                                                     task_status=self.test_task.task_status,
                                                     task_type=self.test_task.task_type,
                                                     starttime=self.test_task.starttime,
@@ -434,7 +435,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
         """ Verify if radb.insertTask() raises an exception when called with non-existing specification ID """
 
         with self.assertRaises(Exception):
-            self.radb.insertTask(0, 0, 'conflict', 'observation', 1)
+            self.radb.insertTask(0, 0, None, 'conflict', 'observation', 1)
 
     @integration_test
     def test_insertTask_with_invalid_id_type_raises_exception(self):
@@ -445,7 +446,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
                                                          content="", cluster="CEP4")
 
         with self.assertRaises(Exception):
-            self.radb.insertTask('monkey see', 'is monkey do', 'conflict', 'observation', spec_id)
+            self.radb.insertTask('monkey see', 'is monkey do', None, 'conflict', 'observation', spec_id)
 
     @integration_test
     def test_insertTask_allows_nonexisting_mom_and_otdb_ids(self):
@@ -456,7 +457,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
                                                 content="", cluster="CEP4")
         mom_id = otdb_id = -1
 
-        task_id = self.radb.insertTask(mom_id, otdb_id, 'conflict', 'observation', spec_id)
+        task_id = self.radb.insertTask(mom_id, otdb_id, None, 'conflict', 'observation', spec_id)
 
         self.assertIsNotNone(task_id)
 
@@ -469,8 +470,8 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
                                                 content="", cluster="CEP4")
 
         with self.assertRaises(Exception):
-            self.radb.insertTask(1, 1, 'conflict', 'observation', spec_id)
-            self.radb.insertTask(1, 2, 'conflict', 'observation', spec_id)
+            self.radb.insertTask(1, 1, None, 'conflict', 'observation', spec_id)
+            self.radb.insertTask(1, 2, None, 'conflict', 'observation', spec_id)
 
     @integration_test
     def test_insertTask_duplicate_otdb_ids_fails(self):
@@ -481,8 +482,8 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
                                                 content="", cluster="CEP4")
 
         with self.assertRaises(Exception):
-            self.radb.insertTask(1, 1, 'conflict', 'observation', spec_id)
-            self.radb.insertTask(2, 1, 'conflict', 'observation', spec_id)
+            self.radb.insertTask(1, 1, None, 'conflict', 'observation', spec_id)
+            self.radb.insertTask(2, 1, None, 'conflict', 'observation', spec_id)
 
     @integration_test
     def test_insertTask_with_invalid_task_status_raises_exception(self):
@@ -494,7 +495,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
                                                          content="", cluster="CEP4")
 
         with self.assertRaises(Exception):
-            self.radb.insertTask(0, 0, 'willywonka', 'observation', specification_id)
+            self.radb.insertTask(0, 0, None, 'willywonka', 'observation', specification_id)
 
     @integration_test
     def test_insertTask_with_invalid_task_type_raises_exception(self):
@@ -506,7 +507,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
                                                          content="", cluster="CEP4")
 
         with self.assertRaises(Exception):
-            self.radb.insertTask(0, 0, 'conflict', 'willywonka', specification_id)
+            self.radb.insertTask(0, 0, None, 'conflict', 'willywonka', specification_id)
 
     @integration_test
     def test_insertTask_normal_use_succeeds(self):
@@ -525,6 +526,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
             'type_id': 0,
             'mom_id': 0,
             'otdb_id': 0,
+            'tmss_id': None,
             'blocked_by_ids': [],
             'predecessor_ids': [],
             'successor_ids': [],
@@ -537,7 +539,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
                                                                         cluster=sample_task['cluster'],
                                                                         content='',)
 
-        task_id = self.radb.insertTask(sample_task['mom_id'], sample_task['otdb_id'], sample_task['status'],
+        task_id = self.radb.insertTask(sample_task['mom_id'], sample_task['otdb_id'], None, sample_task['status'],
                                        sample_task['type'], sample_task['specification_id'])
         sample_task['id'] = task_id
         task = self.radb.getTask(id=task_id)
@@ -940,7 +942,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
         now = datetime.utcnow()
         now -= timedelta(minutes=now.minute, seconds=now.second, microseconds=now.microsecond) # round to full hour
 
-        result = self.radb.insertOrUpdateSpecificationAndTask(0, 0, 'approved', 'observation', now, now+timedelta(hours=1),
+        result = self.radb.insertOrUpdateSpecificationAndTask(0, 0, None, 'approved', 'observation', now, now+timedelta(hours=1),
         'foo', 'CEP4')
         self.assertTrue(result['inserted'])
         spec_id1 = result['specification_id']
@@ -1032,7 +1034,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
         logger.info('-- now test with a 2nd task, and test resource availability, conflicts etc. --')
 
         # another task, fully overlapping with task1
-        result = self.radb.insertOrUpdateSpecificationAndTask(1, 1, 'approved', 'observation', now, now+timedelta(hours=1), 'foo', 'CEP4')
+        result = self.radb.insertOrUpdateSpecificationAndTask(1, 1, None, 'approved', 'observation', now, now+timedelta(hours=1), 'foo', 'CEP4')
         self.assertTrue(result['inserted'])
         spec_id2 = result['specification_id']
         task_id2 = result['task_id']
@@ -1157,7 +1159,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
         task2 = self.radb.getTask(task_id2)
 
         # another task, partially overlapping with both task1 & task3
-        result = self.radb.insertOrUpdateSpecificationAndTask(2, 2, 'approved', 'observation',
+        result = self.radb.insertOrUpdateSpecificationAndTask(2, 2, None, 'approved', 'observation',
                                                       task1['starttime'] + (task1['endtime']-task1['starttime'])/2,
                                                       task2['starttime'] + (task2['endtime']-task2['starttime'])/2,
                                                       'foo', 'CEP4')
@@ -1266,7 +1268,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
         future = now + timedelta(hours=2)
 
         # insert one task, and reuse that for multiple  claims
-        result = self.radb.insertOrUpdateSpecificationAndTask(0, 0, 'approved', 'observation', future, future + timedelta(hours=1),
+        result = self.radb.insertOrUpdateSpecificationAndTask(0, 0, None, 'approved', 'observation', future, future + timedelta(hours=1),
                                                       'content', 'CEP4')
         self.assertTrue(result['inserted'])
         task_id = result['task_id']
@@ -1464,7 +1466,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
         future = now + timedelta(hours=2)
 
         #insert one task, and reuse that for multiple overlapping claims
-        result = self.radb.insertOrUpdateSpecificationAndTask(0, 0, 'approved', 'observation', now, now+timedelta(hours=1),
+        result = self.radb.insertOrUpdateSpecificationAndTask(0, 0, None, 'approved', 'observation', now, now+timedelta(hours=1),
                                                       'foo', 'CEP4')
         self.assertTrue(result['inserted'])
         task_id = result['task_id']
@@ -1587,7 +1589,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
         future = now + timedelta(hours=2)
 
         # insert one task, and reuse that for multiple overlapping claims
-        result = self.radb.insertOrUpdateSpecificationAndTask(0, 0, 'approved', 'observation', future, future + timedelta(hours=1), 'first content',
+        result = self.radb.insertOrUpdateSpecificationAndTask(0, 0, None, 'approved', 'observation', future, future + timedelta(hours=1), 'first content',
                                                       'CEP4')
         self.assertTrue(result['inserted'])
         task_id = result['task_id']
@@ -1598,7 +1600,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
         self.assertEqual('first content', self.radb.getSpecification(task['specification_id'])['content'])
 
         # prove that we can re-insert the spec/task, and that the new task is indeed updated
-        result = self.radb.insertOrUpdateSpecificationAndTask(0, 0, 'approved', 'observation', future, future + timedelta(hours=1), 'second content',
+        result = self.radb.insertOrUpdateSpecificationAndTask(0, 0, None, 'approved', 'observation', future, future + timedelta(hours=1), 'second content',
                                                       'CEP4')
         self.assertTrue(result['inserted'])
         self.assertEqual(task_id, result['task_id']) # as per 20190916 inserting a task again should not yield a new task id.
@@ -1635,7 +1637,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
 
         # prove again that we can re-insert the spec/task (future with claims), and that the new task is indeed inserted and new,
         # and that the claim(s) and usage(s) were actually deleted (via cascading deletes)
-        result = self.radb.insertOrUpdateSpecificationAndTask(0, 0, 'approved', 'observation', future, future + timedelta(hours=1), 'third content',
+        result = self.radb.insertOrUpdateSpecificationAndTask(0, 0, None, 'approved', 'observation', future, future + timedelta(hours=1), 'third content',
                                                       'CEP4')
         self.assertTrue(result['inserted'])
         self.assertEqual(task_id, result['task_id']) # as per 20190916 inserting a task again should not yield a new task id.
@@ -1688,7 +1690,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
         # and prove again that we can re-insert the spec/task (future with claims and a corrupted usage table), and that the new task is indeed inserted and new,
         # and that the claim(s) and usage(s) were actually deleted (via cascading deletes)
         # 2017-08-29: YEAH! the insert fails just like on production. Now we can start making a fix!
-        result = self.radb.insertOrUpdateSpecificationAndTask(0, 0, 'approved', 'observation', future, future + timedelta(hours=1), 'fourth content',
+        result = self.radb.insertOrUpdateSpecificationAndTask(0, 0, None, 'approved', 'observation', future, future + timedelta(hours=1), 'fourth content',
                                                       'CEP4')
         self.assertTrue(result['inserted'])
         self.assertEqual(task_id, result['task_id']) # as per 20190916 inserting a task again should not yield a new task id.
@@ -1730,7 +1732,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
         start = now - timedelta(minutes=now.minute, seconds=now.second, microseconds=now.microsecond) # round to current full hour
 
         #insert a task
-        result = self.radb.insertOrUpdateSpecificationAndTask(0, 0, 'approved', 'observation', start, start+timedelta(hours=2), 'foo', 'CEP4')
+        result = self.radb.insertOrUpdateSpecificationAndTask(0, 0, None, 'approved', 'observation', start, start+timedelta(hours=2), 'foo', 'CEP4')
         self.assertTrue(result['inserted'])
         task_id = result['task_id']
 
@@ -1856,7 +1858,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
         for spec in self.radb.getSpecifications():
             self.radb.deleteSpecification(spec['id']) # cascades into tasks and claims
 
-        result1 = self.radb.insertOrUpdateSpecificationAndTask(0, 0, 'approved', 'observation', start,
+        result1 = self.radb.insertOrUpdateSpecificationAndTask(0, 0, None, 'approved', 'observation', start,
                                                       start + timedelta(hours=2), 'foo', 'CEP4')
         task1_id = result1['task_id']
 
@@ -1872,7 +1874,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
 
         # claim same
 
-        result2 = self.radb.insertOrUpdateSpecificationAndTask(1, 1, 'approved', 'observation', start + timedelta(minutes=5),
+        result2 = self.radb.insertOrUpdateSpecificationAndTask(1, 1, None, 'approved', 'observation', start + timedelta(minutes=5),
                                                       start + timedelta(hours=2, minutes=5), 'foo', 'CEP4')
         task2_id = result2['task_id']
 
@@ -1899,7 +1901,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
         for spec in self.radb.getSpecifications():
             self.radb.deleteSpecification(spec['id']) # cascades into tasks and claims
 
-        result1 = self.radb.insertOrUpdateSpecificationAndTask(0, 0, 'approved', 'observation', start,
+        result1 = self.radb.insertOrUpdateSpecificationAndTask(0, 0, None, 'approved', 'observation', start,
                                                       start + timedelta(hours=2), 'foo', 'CEP4')
         task1_id = result1['task_id']
 
@@ -1915,7 +1917,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
 
         # claim same
 
-        result2 = self.radb.insertOrUpdateSpecificationAndTask(1, 1, 'approved', 'observation', start + timedelta(minutes=5),
+        result2 = self.radb.insertOrUpdateSpecificationAndTask(1, 1, None, 'approved', 'observation', start + timedelta(minutes=5),
                                                       start + timedelta(hours=1, minutes=50), 'foo', 'CEP4')
         task2_id = result2['task_id']
 
@@ -1942,7 +1944,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
         for spec in self.radb.getSpecifications():
             self.radb.deleteSpecification(spec['id']) # cascades into tasks and claims
 
-        result1 = self.radb.insertOrUpdateSpecificationAndTask(0, 0, 'approved', 'observation', start,
+        result1 = self.radb.insertOrUpdateSpecificationAndTask(0, 0, None, 'approved', 'observation', start,
                                                       start + timedelta(hours=2), 'foo', 'CEP4')
         task1_id = result1['task_id']
 
@@ -1958,7 +1960,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
 
         # claim same
 
-        result2 = self.radb.insertOrUpdateSpecificationAndTask(1, 1, 'approved', 'observation', start + timedelta(minutes=-5),
+        result2 = self.radb.insertOrUpdateSpecificationAndTask(1, 1, None, 'approved', 'observation', start + timedelta(minutes=-5),
                                                       start + timedelta(hours=1, minutes=55), 'foo', 'CEP4')
         task2_id = result2['task_id']
 
@@ -1989,7 +1991,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
         for spec in self.radb.getSpecifications():
             self.radb.deleteSpecification(spec['id']) # cascades into tasks and claims
 
-        result1 = self.radb.insertOrUpdateSpecificationAndTask(0, 0, 'approved', 'observation', start,
+        result1 = self.radb.insertOrUpdateSpecificationAndTask(0, 0, None, 'approved', 'observation', start,
                                                       start + timedelta(hours=2), 'foo', 'CEP4')
         task1_id = result1['task_id']
 
@@ -2005,7 +2007,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
 
         # claim same
 
-        result2 = self.radb.insertOrUpdateSpecificationAndTask(1, 1, 'approved', 'observation', start + timedelta(minutes=-5),
+        result2 = self.radb.insertOrUpdateSpecificationAndTask(1, 1, None, 'approved', 'observation', start + timedelta(minutes=-5),
                                                       start + timedelta(hours=2, minutes=5), 'foo', 'CEP4')
         task2_id = result2['task_id']
 
@@ -2032,7 +2034,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
         for spec in self.radb.getSpecifications():
             self.radb.deleteSpecification(spec['id']) # cascades into tasks and claims
 
-        result1 = self.radb.insertOrUpdateSpecificationAndTask(0, 0, 'approved', 'observation', start,
+        result1 = self.radb.insertOrUpdateSpecificationAndTask(0, 0, None, 'approved', 'observation', start,
                                                       start + timedelta(hours=2), 'foo', 'CEP4')
         task1_id = result1['task_id']
 
@@ -2048,7 +2050,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
 
         # claim same
 
-        result2 = self.radb.insertOrUpdateSpecificationAndTask(1, 1, 'approved', 'observation', start,
+        result2 = self.radb.insertOrUpdateSpecificationAndTask(1, 1, None, 'approved', 'observation', start,
                                                       start + timedelta(hours=2), 'foo', 'CEP4')
         task2_id = result2['task_id']
 
@@ -2075,7 +2077,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
         for spec in self.radb.getSpecifications():
             self.radb.deleteSpecification(spec['id']) # cascades into tasks and claims
 
-        result1 = self.radb.insertOrUpdateSpecificationAndTask(0, 0, 'approved', 'observation', start,
+        result1 = self.radb.insertOrUpdateSpecificationAndTask(0, 0, None, 'approved', 'observation', start,
                                                       start + timedelta(hours=2), 'foo', 'CEP4')
         task1_id = result1['task_id']
 
@@ -2091,7 +2093,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
 
         # claim same
 
-        result2 = self.radb.insertOrUpdateSpecificationAndTask(1, 1, 'approved', 'observation', start + timedelta(hours=3),
+        result2 = self.radb.insertOrUpdateSpecificationAndTask(1, 1, None, 'approved', 'observation', start + timedelta(hours=3),
                                                       start + timedelta(hours=5), 'foo', 'CEP4')
         task2_id = result2['task_id']
 
@@ -2116,7 +2118,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
         for spec in self.radb.getSpecifications():
             self.radb.deleteSpecification(spec['id']) # cascades into tasks and claims
 
-        result1 = self.radb.insertOrUpdateSpecificationAndTask(0, 0, 'approved', 'observation', start + timedelta(hours=3),
+        result1 = self.radb.insertOrUpdateSpecificationAndTask(0, 0, None, 'approved', 'observation', start + timedelta(hours=3),
                                                       start + timedelta(hours=5), 'foo', 'CEP4')
         task1_id = result1['task_id']
 
@@ -2132,7 +2134,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
 
         # claim same
 
-        result2 = self.radb.insertOrUpdateSpecificationAndTask(1, 1, 'approved', 'observation', start,
+        result2 = self.radb.insertOrUpdateSpecificationAndTask(1, 1, None, 'approved', 'observation', start,
                                                       start + timedelta(hours=2), 'foo', 'CEP4')
         task2_id = result2['task_id']
 
@@ -2172,7 +2174,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
         resource_max_cap = self.radb.get_resource_claimable_capacity(RESOURCE_ID, base_time, base_time)
 
         # insert the 'low prio' spec, task...
-        spec_task_low = self.radb.insertOrUpdateSpecificationAndTask(1, 1, 'prescheduled', 'observation',
+        spec_task_low = self.radb.insertOrUpdateSpecificationAndTask(1, 1, None, 'prescheduled', 'observation',
                                                              base_time + timedelta(minutes=5),
                                                              base_time + timedelta(minutes=10), 'foo', 'CEP4')
         task_low_id = spec_task_low['task_id']
@@ -2213,7 +2215,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
         # overlapping with the beginning of task_low
         # so, the dwellscheduler finds task_low in task_high's higway
         # so, task_low is aborted by the dwellscheduler (later in the code).
-        spec_task_high1 = self.radb.insertOrUpdateSpecificationAndTask(2, 2, 'approved', 'observation',
+        spec_task_high1 = self.radb.insertOrUpdateSpecificationAndTask(2, 2, None, 'approved', 'observation',
                                                               base_time,
                                                               base_time + timedelta(minutes=7), 'foo', 'CEP4')
         task_high1_id = spec_task_high1['task_id']
@@ -2283,7 +2285,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
         now = datetime.utcnow()
         now -= timedelta(minutes=now.minute, seconds=now.second, microseconds=now.microsecond) # round to full hour
 
-        result = self.radb.insertOrUpdateSpecificationAndTask(0, 0, 'approved', 'observation',
+        result = self.radb.insertOrUpdateSpecificationAndTask(0, 0, None, 'approved', 'observation',
                                                       now, now+timedelta(hours=1), 'foo', 'CEP4')
         self.assertTrue(result['inserted'])
         self.assertIsNotNone(result['task_id'])
@@ -2327,7 +2329,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
         now = datetime.utcnow()
         now -= timedelta(minutes=now.minute, seconds=now.second, microseconds=now.microsecond) # round to full hour
 
-        result = self.radb.insertOrUpdateSpecificationAndTask(0, 0, 'approved', 'observation',
+        result = self.radb.insertOrUpdateSpecificationAndTask(0, 0, None, 'approved', 'observation',
                                                       now, now+timedelta(hours=1), 'foo', 'CEP4')
         self.assertTrue(result['inserted'])
         self.assertIsNotNone(result['task_id'])
@@ -2403,7 +2405,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
         now = now - timedelta(minutes=now.minute, seconds=now.second, microseconds=now.microsecond)
         now = now + timedelta(hours=1)
 
-        spec_task = self.radb.insertOrUpdateSpecificationAndTask(0, 0, 'approved', 'observation',
+        spec_task = self.radb.insertOrUpdateSpecificationAndTask(0, 0, None, 'approved', 'observation',
                                                          now, now + timedelta(minutes=10),
                                                          'foo', 'CEP4')
 
@@ -2477,7 +2479,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
 
         now = datetime.utcnow()
 
-        spec_task = self.radb.insertOrUpdateSpecificationAndTask(0, 0, 'approved', 'observation',
+        spec_task = self.radb.insertOrUpdateSpecificationAndTask(0, 0, None, 'approved', 'observation',
                                                          now, now,  # tasks can have zero duration
                                                          'foo', 'CEP4')
 
@@ -2527,7 +2529,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
         resource_max_cap = self.radb.get_resource_claimable_capacity(RESOURCE_ID, base_time, base_time)
 
         # insert a first task and full claim on a resource...
-        spec_task1 = self.radb.insertOrUpdateSpecificationAndTask(0, 0, 'approved', 'observation',
+        spec_task1 = self.radb.insertOrUpdateSpecificationAndTask(0, 0, None, 'approved', 'observation',
                                                           base_time + timedelta(minutes=+0),
                                                           base_time + timedelta(minutes=+10), 'foo', 'CEP4')
         self.assertTrue(spec_task1['inserted'])
@@ -2544,7 +2546,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
 
         # insert second (partially overlapping) task and claim on same resource, which we expect to get a conflict status
         # because the first claim already claims the resource fully.
-        spec_task2 = self.radb.insertOrUpdateSpecificationAndTask(1, 1, 'approved', 'observation',
+        spec_task2 = self.radb.insertOrUpdateSpecificationAndTask(1, 1, None, 'approved', 'observation',
                                                           base_time + timedelta(minutes=+5),
                                                           base_time + timedelta(minutes=+15), 'foo', 'CEP4')
         self.assertTrue(spec_task2['inserted'])
@@ -2600,7 +2602,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
                                           microseconds=base_time.microsecond)
 
         # insert a first task and full claim on a resource...
-        spec_task = self.radb.insertOrUpdateSpecificationAndTask(0, 0, 'approved', 'observation',
+        spec_task = self.radb.insertOrUpdateSpecificationAndTask(0, 0, None, 'approved', 'observation',
                                                           base_time + timedelta(minutes=-20),
                                                           base_time + timedelta(minutes=-10), 'foo', 'CEP4')
         self.assertTrue(spec_task['inserted'])
@@ -2658,7 +2660,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
         RESOURCE_ID = 0
         resource_max_cap = int(self.radb.get_resource_claimable_capacity(RESOURCE_ID, now, now))
 
-        task1_id = self.radb.insertOrUpdateSpecificationAndTask(1, 1, 'approved', 'observation',
+        task1_id = self.radb.insertOrUpdateSpecificationAndTask(1, 1, None, 'approved', 'observation',
                                                         now+timedelta(hours=1),
                                                         now + timedelta(hours=2),
                                                         'content', 'CEP4')['task_id']
@@ -2704,7 +2706,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
                          self.radb.getResourceUsages(task1['starttime'], task1['endtime'], RESOURCE_ID)[RESOURCE_ID]['claimed'])
 
         # insert second task after the first one (not overlapping)
-        task2_id = self.radb.insertOrUpdateSpecificationAndTask(2, 2, 'approved', 'observation',
+        task2_id = self.radb.insertOrUpdateSpecificationAndTask(2, 2, None, 'approved', 'observation',
                                                         now + timedelta(hours=3),
                                                         now + timedelta(hours=4),
                                                         'content', 'CEP4')['task_id']
@@ -2756,7 +2758,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
         now = datetime.utcnow()
         now -= timedelta(minutes=now.minute, seconds=now.second, microseconds=now.microsecond)  # round to full hour
         for i in [1,2]:
-            task_id = self.radb.insertOrUpdateSpecificationAndTask(i, i, 'approved', 'observation',
+            task_id = self.radb.insertOrUpdateSpecificationAndTask(i, i, None, 'approved', 'observation',
                                                                    now+timedelta(hours=1), now + timedelta(hours=2),
                                                                    'content', 'CEP4')['task_id']
             task = self.radb.getTask(task_id)
@@ -2839,7 +2841,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
         # we need a task....
         now = datetime.utcnow()
         now -= timedelta(minutes=now.minute, seconds=now.second, microseconds=now.microsecond)  # round to full hour
-        task_id = self.radb.insertOrUpdateSpecificationAndTask(0, 0, 'approved', 'reservation',
+        task_id = self.radb.insertOrUpdateSpecificationAndTask(0, 0, None, 'approved', 'reservation',
                                                now + timedelta(hours=1), now + timedelta(hours=2),
                                                'content', 'CEP4')['task_id']
 
@@ -2911,7 +2913,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
         self.assertEqual(0, len(usages))
         self.assertEqual(0, len(usage_deltas))
 
-        task_id = self.radb.insertOrUpdateSpecificationAndTask(mom_id=0, otdb_id=0, task_status='approved', task_type='observation',
+        task_id = self.radb.insertOrUpdateSpecificationAndTask(mom_id=0, otdb_id=0, tmss_id=None, task_status='approved', task_type='observation',
                                                                starttime=now+timedelta(hours=1), endtime=now+timedelta(hours=2),
                                                                content="", cluster="CEP4")['task_id']
         task = self.radb.getTask(task_id)
diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/tests/t_radb_performance.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/tests/t_radb_performance.py
index 6a1758c71f9..54e624c715c 100755
--- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/tests/t_radb_performance.py
+++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/tests/t_radb_performance.py
@@ -75,7 +75,7 @@ class ResourceAssignmentDatabaseTest(RADBCommonTestMixin, unittest.TestCase):
                         logger.info('starting task and claim scheduling: counter=%s num_claims_per_resource=%s num_claims_to_insert=%s oversubscription_factor=%s',
                                     counter, num_claims_per_resource, num_claims_to_insert, oversubscription_factor)
 
-                        result = self.radb.insertOrUpdateSpecificationAndTask(counter, counter, 'approved', 'observation',
+                        result = self.radb.insertOrUpdateSpecificationAndTask(counter, counter, None, 'approved', 'observation',
                                                                       now+timedelta(hours=3*counter),
                                                                       now + timedelta(hours=3*counter + 1),
                                                                       'content', 'CEP4')
diff --git a/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py b/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py
index 2e2f67b91c0..cc8781a70f5 100644
--- a/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py
+++ b/SAS/ResourceAssignment/ResourceAssignmentService/rpc.py
@@ -175,13 +175,14 @@ class RADBRPC(RPCClientContextManagerMixin):
                         available_capacity=available_capacity,
                         total_capacity=total_capacity)
 
-    def getTask(self, id=None, mom_id=None, otdb_id=None, specification_id=None):
+    def getTask(self, id=None, mom_id=None, otdb_id=None, tmss_id=None, specification_id=None):
         '''get a task for either the given (task)id, or for the given mom_id, or for the given otdb_id, or for the given specification_id'''
-        return self._rpc_client.execute('GetTask', id=id, mom_id=mom_id, otdb_id=otdb_id, specification_id=specification_id)
+        return self._rpc_client.execute('GetTask', id=id, mom_id=mom_id, otdb_id=otdb_id, tmss_id=tmss_id, specification_id=specification_id)
 
-    def insertTask(self, mom_id, otdb_id, task_status, task_type, specification_id):
+    def insertTask(self, mom_id, otdb_id, tmss_id, task_status, task_type, specification_id):
         return self._rpc_client.execute('InsertTask', mom_id=mom_id,
                                            otdb_id=otdb_id,
+                                           tmss_id=tmss_id,
                                            task_status=task_status,
                                            task_type=task_type,
                                            specification_id=specification_id)
@@ -189,11 +190,12 @@ class RADBRPC(RPCClientContextManagerMixin):
     def deleteTask(self, id):
         return self._rpc_client.execute('DeleteTask', id=id)
 
-    def updateTask(self, task_id, mom_id=None, otdb_id=None, task_status=None, task_type=None, specification_id=None):
+    def updateTask(self, task_id, mom_id=None, otdb_id=None, tmss_id=None, task_status=None, task_type=None, specification_id=None):
         return self._rpc_client.execute('UpdateTask',
                          id=task_id,
                          mom_id=mom_id,
                          otdb_id=otdb_id,
+                         tmss_id=tmss_id,
                          task_status=task_status,
                          task_type=task_type,
                          specification_id=specification_id)
@@ -203,10 +205,15 @@ class RADBRPC(RPCClientContextManagerMixin):
                          otdb_id=otdb_id,
                          task_status=task_status)
 
-    def getTasksTimeWindow(self, task_ids=None, mom_ids=None, otdb_ids=None):
-        return self._rpc_client.execute('GetTasksTimeWindow', task_ids=task_ids, mom_ids=mom_ids, otdb_ids=otdb_ids)
+    def updateTaskStatusForTMSSId(self, tmss_id, task_status):
+        return self._rpc_client.execute('UpdateTaskStatusForTMSSId',
+                         tmss_id=tmss_id,
+                         task_status=task_status)
+
+    def getTasksTimeWindow(self, task_ids=None, mom_ids=None, otdb_ids=None, tmss_ids=None):
+        return self._rpc_client.execute('GetTasksTimeWindow', task_ids=task_ids, mom_ids=mom_ids, otdb_ids=otdb_ids, tmss_ids=tmss_ids)
 
-    def getTasks(self, lower_bound=None, upper_bound=None, task_ids=None, task_status=None, task_type=None, mom_ids=None, otdb_ids=None, cluster=None):
+    def getTasks(self, lower_bound=None, upper_bound=None, task_ids=None, task_status=None, task_type=None, mom_ids=None, otdb_ids=None, tmss_ids=None, cluster=None):
         '''getTasks let's you query tasks from the radb with many optional filters.
         :param lower_bound: datetime specifies the lower_bound of a time window above which to select tasks
         :param upper_bound: datetime specifies the upper_bound of a time window below which to select tasks
@@ -217,7 +224,7 @@ class RADBRPC(RPCClientContextManagerMixin):
         :param otdb_ids: int/list/tuple specifies one or more otdb_ids to select
         :param cluster: string specifies the cluster to select
         '''
-        return self._rpc_client.execute('GetTasks', lower_bound=lower_bound, upper_bound=upper_bound, task_ids=task_ids, task_status=task_status, task_type=task_type, mom_ids=mom_ids, otdb_ids=otdb_ids, cluster=cluster)
+        return self._rpc_client.execute('GetTasks', lower_bound=lower_bound, upper_bound=upper_bound, task_ids=task_ids, task_status=task_status, task_type=task_type, mom_ids=mom_ids, otdb_ids=otdb_ids, tmss_ids=tmss_ids, cluster=cluster)
 
     def getTaskPredecessorIds(self, id=None):
         return self._rpc_client.execute('GetTaskPredecessorIds', id=id)
@@ -240,10 +247,11 @@ class RADBRPC(RPCClientContextManagerMixin):
     def getSpecification(self, id):
         return self._rpc_client.execute('GetSpecification', id=id)
 
-    def insertOrUpdateSpecificationAndTask(self, mom_id, otdb_id, task_status, task_type, starttime, endtime, content, cluster):
+    def insertOrUpdateSpecificationAndTask(self, mom_id, otdb_id, tmss_id, task_status, task_type, starttime, endtime, content, cluster):
         return self._rpc_client.execute('insertOrUpdateSpecificationAndTask',
                         mom_id=mom_id,
                         otdb_id=otdb_id,
+                        tmss_id=tmss_id,
                         task_status=task_status,
                         task_type=task_type,
                         starttime=starttime,
diff --git a/SAS/ResourceAssignment/ResourceAssignmentService/service.py b/SAS/ResourceAssignment/ResourceAssignmentService/service.py
index ddfd4fad090..732404dbadb 100644
--- a/SAS/ResourceAssignment/ResourceAssignmentService/service.py
+++ b/SAS/ResourceAssignment/ResourceAssignmentService/service.py
@@ -240,7 +240,8 @@ class RADBServiceMessageHandler(ServiceMessageHandler):
         logger.info('GetTasksTimeWindow: %s' % dict({k:v for k,v in list(kwargs.items()) if v != None}))
         return self.radb.getTasksTimeWindow(task_ids=kwargs.get('task_ids'),
                                             mom_ids=kwargs.get('mom_ids'),
-                                            otdb_ids=kwargs.get('otdb_ids'))
+                                            otdb_ids=kwargs.get('otdb_ids'),
+                                            tmss_ids=kwargs.get('tmss_ids'))
 
     def _getTasks(self, **kwargs):
         logger.info('GetTasks: %s' % dict({k:v for k,v in list(kwargs.items()) if v != None}))
@@ -251,17 +252,19 @@ class RADBServiceMessageHandler(ServiceMessageHandler):
                                   task_type=kwargs.get('task_type'),
                                   mom_ids=kwargs.get('mom_ids'),
                                   otdb_ids=kwargs.get('otdb_ids'),
+                                  tmss_ids=kwargs.get('tmss_ids'),
                                   cluster=kwargs.get('cluster'))
 
     def _getTask(self, **kwargs):
         logger.info('GetTask: %s' % dict({k:v for k,v in list(kwargs.items()) if v != None}))
-        task = self.radb.getTask(id=kwargs.get('id'), mom_id=kwargs.get('mom_id'), otdb_id=kwargs.get('otdb_id'), specification_id=kwargs.get('specification_id'))
+        task = self.radb.getTask(id=kwargs.get('id'), mom_id=kwargs.get('mom_id'), otdb_id=kwargs.get('otdb_id'), tmss_id=kwargs.get('tmss_id'), specification_id=kwargs.get('specification_id'))
         return task
 
     def _insertTask(self, **kwargs):
         logger.info('InsertTask: %s' % dict({k:v for k,v in list(kwargs.items()) if v != None}))
-        task_id = self.radb.insertTask(kwargs['mom_id'],
-                                       kwargs['otdb_id'],
+        task_id = self.radb.insertTask(kwargs.get('mom_id'),
+                                       kwargs.get('otdb_id'),
+                                       kwargs.get('tmss_id'),
                                        kwargs.get('status_id', kwargs.get('task_status', 'prepared')),
                                        kwargs.get('type_id', kwargs.get('task_type')),
                                        kwargs['specification_id'])
@@ -286,6 +289,7 @@ class RADBServiceMessageHandler(ServiceMessageHandler):
         updated = self.radb.updateTask(id,
                                        mom_id=kwargs.get('mom_id'),
                                        otdb_id=kwargs.get('otdb_id'),
+                                       tmss_id=kwargs.get('tmss_id'),
                                        task_status=kwargs.get('status_id', kwargs.get('task_status')),
                                        task_type=kwargs.get('type_id', kwargs.get('task_type')),
                                        specification_id=kwargs.get('specification_id'))
@@ -321,6 +325,7 @@ class RADBServiceMessageHandler(ServiceMessageHandler):
         logger.info('insertOrUpdateSpecificationAndTask: %s' % dict({k:v for k,v in list(kwargs.items()) if v != None and k != 'content'}))
         return self.radb.insertOrUpdateSpecificationAndTask(kwargs['mom_id'],
                                                     kwargs['otdb_id'],
+                                                    kwargs.get('tmss_id'),
                                                     kwargs['task_status'],
                                                     kwargs['task_type'],
                                                     kwargs.get('starttime'),
-- 
GitLab