[autotest] Create a new get_status_task RPC to improve dut_status.

Previously, status was determined by fetching all jobs in a given
time range, and then searching back through the returned list for
a job that indicated host status.  This had two drawbacks:
  * If the job indicating status was earlier than the start time
    of the query, no status could be determined.
  * The extra data returned in the query slowed it down.

This adds a new RPC that finds the task through a single database
query searching for the specific necessary condition.  This should
largely eliminate the '--' status, and is notably faster (possibly 4
times faster, or more).

This also changes the rules for what constitues a diagnostic job or
task, by changing the diagnosis associated with test jobs.  This
is done because
  * It makes determining status possible with a query to a single
    table.
  * The experience has been that test jobs don't indicate a
    diagnosis anyway.

This is the first of two parts; this change creates the server
side of the new RPC.  This change must be pushed to prod before
the follow-on change for the client side.

BUG=chromium:463632
TEST=Run dut_status using a local instance pointed at the prod database

Change-Id: Ifc61387f0cdc46ceb32062663c6a73c5d4bcfb4f
Reviewed-on: https://chromium-review.googlesource.com/265783
Reviewed-by: Richard Barnette <jrbarnette@chromium.org>
Commit-Queue: Richard Barnette <jrbarnette@chromium.org>
Tested-by: Richard Barnette <jrbarnette@chromium.org>
diff --git a/frontend/afe/rpc_interface.py b/frontend/afe/rpc_interface.py
index 7d30761..352e77f 100644
--- a/frontend/afe/rpc_interface.py
+++ b/frontend/afe/rpc_interface.py
@@ -46,6 +46,7 @@
 from autotest_lib.server import frontend
 from autotest_lib.server import utils
 from autotest_lib.server.cros.dynamic_suite import tools
+from autotest_lib.site_utils import status_history
 
 
 _timer = autotest_stats.Timer('rpc_interface')
@@ -1138,15 +1139,54 @@
     if not host.shard:
         return get_special_tasks(host_id=host_id, **filter_data)
     else:
-        # The return value from AFE.get_special_tasks() is a list of
-        # post-processed objects that aren't JSON-serializable.  So,
-        # we have to call AFE.run() to get the raw, serializable
-        # output from the shard.
+        # The return values from AFE methods are post-processed
+        # objects that aren't JSON-serializable.  So, we have to
+        # call AFE.run() to get the raw, serializable output from
+        # the shard.
         shard_afe = frontend.AFE(server=host.shard.rpc_hostname())
         return shard_afe.run('get_special_tasks',
                              host_id=host_id, **filter_data)
 
 
+def get_status_task(host_id, end_time):
+    """Get the special task identifying a host's status.
+
+    Returns information for a single special task for a host
+    identifying whether the host was working or broken at the given
+    `end_time`.  A successful task indicates a working host; a
+    failed task indicates a broken host.
+
+    If the host is managed by a shard, this call forwards the
+    request.
+
+    @param host_id      Id in the database of the target host.
+    @param end_time     Time reference for the host's status.
+
+    @return A single task; its status (successful or not)
+            corresponds to the status of the host (working or
+            broken) at the given time.  If no task is found, return
+            `None`.
+
+    """
+    host = models.Host.smart_get(host_id)
+    if not host.shard:
+        tasklist = rpc_utils.prepare_rows_as_nested_dicts(
+                status_history.get_status_task(host_id, end_time),
+                ('host', 'queue_entry'))
+        if tasklist:
+            return tasklist[0]
+        else:
+            return None
+    else:
+        # The return values from AFE methods are post-processed
+        # objects that aren't JSON-serializable.  So, we have to
+        # call AFE.run() to get the raw, serializable output from
+        # the shard.
+        shard_afe = frontend.AFE(server=host.shard.rpc_hostname())
+        return shard_afe.run('get_status_task',
+                             host_id=host_id, end_time=end_time)
+
+
 # support for host detail view
 
 def get_host_queue_entries_and_special_tasks(host_id, query_start=None,