Implementation of Test Planner execution engine. Is currently able to
schedule single-host tests and place the results in the proper planner
tables for analysis.

TODO: global support object, execution_engine.py unit tests

Signed-off-by: James Ren <jamesren@google.com>


git-svn-id: http://test.kernel.org/svn/autotest/trunk@4301 592f7852-d20e-0410-864c-8624ca9c26a4
diff --git a/frontend/planner/models.py b/frontend/planner/models.py
index 52ff1bc..f69aa28 100644
--- a/frontend/planner/models.py
+++ b/frontend/planner/models.py
@@ -3,7 +3,8 @@
 from autotest_lib.frontend.afe import models as afe_models
 from autotest_lib.frontend.afe import model_logic, rpc_utils
 from autotest_lib.frontend.tko import models as tko_models
-from autotest_lib.client.common_lib import enum, utils
+from autotest_lib.frontend.planner import model_attributes
+from autotest_lib.client.common_lib import utils
 
 
 class Plan(dbmodels.Model, model_logic.ModelExtensions):
@@ -75,12 +76,13 @@
         host: The AFE host
         complete: True if and only if this host is finished in the test plan
         blocked: True if and only if the host is blocked (not executing tests)
+        added_by_label: True if and only if the host was added because of a host
+                        label (as opposed to being explicitly added)
     """
     host = dbmodels.ForeignKey(afe_models.Host)
     complete = dbmodels.BooleanField(default=False)
     blocked = dbmodels.BooleanField(default=False)
-
-    Status = enum.Enum('Finished', 'Running', 'Blocked', string_values=True)
+    added_by_label = dbmodels.BooleanField(default=False)
 
     class Meta:
         db_table = 'planner_hosts'
@@ -88,30 +90,26 @@
 
     def status(self):
         if self.complete:
-            return Host.Status.FINISHED
+            return model_attributes.HostStatus.FINISHED
         if self.blocked:
-            return Host.Status.BLOCKED
-        return Host.Status.RUNNING
+            return model_attributes.HostStatus.BLOCKED
+        return model_attributes.HostStatus.RUNNING
 
 
     def _get_details_unicode(self):
         return 'Host: %s' % self.host.hostname
 
 
-    @classmethod
-    def smart_get(cls, id):
-        raise NotImplementedError('Planner hosts do not support smart_get()')
-
-
-class ControlFile(model_logic.ModelWithHash):
+class ControlFile(model_logic.ModelWithHash,
+                  model_logic.ModelExtensions):
     """A control file. Immutable once added to the table
 
     Required:
         contents: The text of the control file
 
     Others:
-        control_hash: The SHA1 hash of the control file, for duplicate detection
-                      and fast search
+        the_hash: The SHA1 hash of the control file, for duplicate detection
+                  and fast search
     """
     contents = dbmodels.TextField()
 
@@ -128,12 +126,13 @@
         return u'Control file id %s (SHA1: %s)' % (self.id, self.control_hash)
 
 
-class Test(ModelWithPlan):
+class TestConfig(ModelWithPlan, model_logic.ModelExtensions):
     """A planned test
 
     Required:
         alias: The name to give this test within the plan. Unique with plan id
         test_control_file: The control file to run
+        is_server: True if this control file is a server-side test
         execution_order: An integer describing when this test should be run in
                          the test plan
         estimated_runtime: Time in hours that the test is expected to run. Will
@@ -142,27 +141,28 @@
     """
     alias = dbmodels.CharField(max_length=255)
     control_file = dbmodels.ForeignKey(ControlFile)
+    is_server = dbmodels.BooleanField(default=True)
     execution_order = dbmodels.IntegerField(blank=True)
     estimated_runtime = dbmodels.IntegerField()
 
     class Meta:
-        db_table = 'planner_tests'
+        db_table = 'planner_test_configs'
         ordering = ('execution_order',)
         unique_together = (('plan', 'alias'),)
 
 
     def _get_details_unicode(self):
-        return 'Planned test - Control file id %s' % self.control_file.id
+        return 'Planned test config - Control file id %s' % self.control_file.id
 
 
-class Job(ModelWithPlan):
+class Job(ModelWithPlan, model_logic.ModelExtensions):
     """Represents an Autotest job initiated for a test plan
 
     Required:
-        test: The Test associated with this Job
+        test: The TestConfig associated with this Job
         afe_job: The Autotest job
     """
-    test = dbmodels.ForeignKey(Test)
+    test_config = dbmodels.ForeignKey(TestConfig)
     afe_job = dbmodels.ForeignKey(afe_models.Job)
 
     class Meta:
@@ -189,7 +189,7 @@
         return u'Bug external ID %s' % self.external_uid
 
 
-class TestRun(ModelWithPlan):
+class TestRun(ModelWithPlan, model_logic.ModelExtensions):
     """An individual test run from an Autotest job for the test plan.
 
     Each Job object may have multiple TestRun objects associated with it.
@@ -209,12 +209,13 @@
     Optional:
         bugs: Bugs filed that a relevant to this run
     """
-    Status = enum.Enum('Active', 'Passed', 'Failed', string_values=True)
-
     test_job = dbmodels.ForeignKey(Job)
     tko_test = dbmodels.ForeignKey(tko_models.Test)
     host = dbmodels.ForeignKey(Host)
-    status = dbmodels.CharField(max_length=16, choices=Status.choices())
+    status = dbmodels.CharField(
+            max_length=16,
+            choices=model_attributes.TestRunStatus.choices(),
+            default=model_attributes.TestRunStatus.ACTIVE)
     finalized = dbmodels.BooleanField(default=False)
     seen = dbmodels.BooleanField(default=False)
     triaged = dbmodels.BooleanField(default=False)
@@ -224,6 +225,7 @@
 
     class Meta:
         db_table = 'planner_test_runs'
+        unique_together = (('plan', 'test_job', 'tko_test', 'host'),)
 
 
     def _get_details_unicode(self):
@@ -294,12 +296,11 @@
         name: The name given to the object
         encoded_object: The actual object
     """
-    Type = enum.Enum('support', 'triage', 'autoprocess', 'custom_query',
-                     string_values=True)
-
     user = dbmodels.ForeignKey(afe_models.User)
-    object_type = dbmodels.CharField(max_length=16,
-                                     choices=Type.choices(), db_column='type')
+    object_type = dbmodels.CharField(
+            max_length=16,
+            choices=model_attributes.SavedObjectType.choices(),
+            db_column='type')
     name = dbmodels.CharField(max_length=255)
     encoded_object = dbmodels.TextField()
 
@@ -337,8 +338,8 @@
         value: The value
 
     Others:
-        keyval_hash: The result of SHA1(SHA1(key) ++ value), for duplicate
-                     detection and fast search.
+        the_hash: The result of SHA1(SHA1(key) ++ value), for duplicate
+                  detection and fast search.
     """
     key = dbmodels.CharField(max_length=1024)
     value = dbmodels.CharField(max_length=1024)