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/rpc_utils.py b/frontend/planner/rpc_utils.py
index 8d359bd..79a6dcc 100644
--- a/frontend/planner/rpc_utils.py
+++ b/frontend/planner/rpc_utils.py
@@ -1,8 +1,7 @@
 import common
 import os
 from autotest_lib.frontend.afe import models as afe_models, model_logic
-from autotest_lib.frontend.planner import models
-from autotest_lib.frontend.shared import rest_client
+from autotest_lib.frontend.planner import models, model_attributes
 from autotest_lib.client.common_lib import global_config, utils
 
 
@@ -36,23 +35,22 @@
     """
     Takes the necessary steps to start a test plan in Autotest
     """
-    afe_rest = rest_client.Resource.load(
-            'http://%s/afe/server/resources' % SERVER)
-
     keyvals = {'server': SERVER,
                'plan_id': plan.id,
                'label_name': label.name}
-
-    info = afe_rest.execution_info.get().execution_info
-    info['control_file'] = _get_execution_engine_control()
-    info['machines_per_execution'] = None
-
-    job_req = {'name': plan.name + '_execution_engine',
-               'execution_info': info,
-               'queue_entries': (),
+    options = {'name': plan.name + '_execution_engine',
+               'priority': afe_models.Job.Priority.MEDIUM,
+               'control_file': _get_execution_engine_control(),
+               'control_type': afe_models.Job.ControlType.SERVER,
+               'synch_count': None,
+               'run_verify': False,
+               'reboot_before': False,
+               'reboot_after': False,
+               'dependencies': (),
                'keyvals': keyvals}
-
-    afe_rest.jobs.post(job_req)
+    job = afe_models.Job.create(owner=afe_models.User.current_user().login,
+                                options=options, hosts=())
+    job.queue(hosts=())
 
 
 def _get_execution_engine_control():
@@ -71,3 +69,93 @@
         LAZY_LOADED_FILES[path] = utils.read_file(path)
 
     return LAZY_LOADED_FILES[path]
+
+
+def update_hosts_table(plan):
+    """
+    Resolves the host labels into host objects
+
+    Adds or removes hosts from the planner Hosts model based on changes to the
+    host label
+    """
+    label_hosts = set()
+
+    for label in plan.host_labels.all():
+        for afe_host in label.host_set.all():
+            host, created = models.Host.objects.get_or_create(plan=plan,
+                                                              host=afe_host)
+            if created:
+                host.added_by_label = True
+                host.save()
+
+            label_hosts.add(host.host.id)
+
+    deleted_hosts = models.Host.objects.filter(
+            plan=plan, added_by_label=True).exclude(host__id__in=label_hosts)
+    deleted_hosts.delete()
+
+
+def compute_next_test_config(plan, host):
+    """
+    Gets the next test config that should be run for this plan and host
+
+    Returns None if the host is already running a job. Also sets the host's
+    complete bit if the host is finished running tests.
+    """
+    if host.blocked:
+        return None
+
+    test_configs = plan.testconfig_set.order_by('execution_order')
+    for test_config in test_configs:
+        afe_jobs = plan.job_set.filter(test_config=test_config)
+        afe_job_ids = afe_jobs.values_list('afe_job', flat=True)
+        hqes = afe_models.HostQueueEntry.objects.filter(job__id__in=afe_job_ids,
+                                                        host=host.host)
+        if not hqes:
+            return test_config.id
+        for hqe in hqes:
+            if not hqe.complete:
+                # HostQueueEntry still active for this host,
+                # should not run another test
+                return None
+
+    # All HQEs related to this host are complete
+    host.complete = True
+    host.save()
+    return None
+
+
+def check_for_completion(plan):
+    """
+    Checks if a plan is actually complete. Sets complete=True if so
+    """
+    if not models.Host.objects.filter(plan=plan, complete=False):
+        plan.complete = True
+        plan.save()
+
+
+def compute_test_run_status(status):
+    """
+    Converts a TKO test status to a Planner test run status
+    """
+    Status = model_attributes.TestRunStatus
+    if status == 'GOOD':
+        return Status.PASSED
+    if status == 'RUNNING':
+        return Status.ACTIVE
+    return Status.FAILED
+
+
+def add_test_run(plan, planner_job, tko_test, hostname, status):
+    """
+    Adds a TKO test to the Planner Test Run tables
+    """
+    host = afe_models.Host.objects.get(hostname=hostname)
+
+    planner_host = models.Host.objects.get(plan=plan, host=host)
+    test_run, _ = models.TestRun.objects.get_or_create(plan=plan,
+                                                       test_job=planner_job,
+                                                       tko_test=tko_test,
+                                                       host=planner_host)
+    test_run.status = status
+    test_run.save()