[autotest] Include links to failed test logs in suite output

When tests driven by the buildbot fail, we want to provide
some way for sheriffs to check out the logs.
Emit annotated log links for buildbot to consume and print
inline in the waterfall.

BUG=chromium-os:26857
TEST=./site_utils/run_suite.py -b x86-mario -i x86-mario-release/R19-1865.0.0-a1-b1702 -s dummy
TEST=The output from the above command should have several @@@STEP_LINK lines.

Change-Id: Ie73b3393fab328cb9f0ed8c4dcdf5926968f1e74
Reviewed-on: https://gerrit.chromium.org/gerrit/17090
Tested-by: Chris Masone <cmasone@chromium.org>
Reviewed-by: Chris Sosa <sosa@chromium.org>
Commit-Ready: Chris Masone <cmasone@chromium.org>
diff --git a/global_config.ini b/global_config.ini
index 8c0cf45..de34360 100644
--- a/global_config.ini
+++ b/global_config.ini
@@ -138,3 +138,4 @@
 # name field to be populated later.  Sadly, that doesn't parse.
 image_url_pattern: %(dev_server)s/update/%%s
 package_url_pattern: %(dev_server)s/static/archive/%%s/autotest/packages
+log_url_pattern: http://%s/tko/retrieve_logs.cgi?job=/results/%s/
diff --git a/site_utils/run_suite.py b/site_utils/run_suite.py
index fab1265..900df05 100755
--- a/site_utils/run_suite.py
+++ b/site_utils/run_suite.py
@@ -16,8 +16,11 @@
 
 import optparse, time, sys
 import common
+from autotest_lib.client.common_lib import global_config
 from autotest_lib.server.cros import frontend_wrappers
 
+CONFIG = global_config.global_config
+
 def parse_options():
     usage = "usage: %prog [options] control_file"
     parser = optparse.OptionParser(usage=usage)
@@ -48,6 +51,19 @@
                 status['test_name'].startswith('CLIENT_JOB'))
 
 
+def generate_log_link(anchor, job_string):
+    """
+    Generate a link to this job's logs, for consumption by buildbot.
+
+    @param anchor: Link anchor text.
+    @param job_id: the job whose logs we'd like to link to.
+    @return A link formatted for the buildbot log annotator.
+    """
+    host = CONFIG.get_config_value('SERVER', 'hostname', type=str)
+    pattern = CONFIG.get_config_value('CROS', 'log_url_pattern', type=str)
+    return "@@@STEP_LINK@%s@%s@@@" % (anchor, pattern % (host, job_string))
+
+
 def main():
     parser, options, args = parse_options()
     if args or not options.build or not options.board or not options.name:
@@ -69,8 +85,9 @@
             time.sleep(1)
             continue
         views = filter(status_is_relevant,
-                       TKO.run('get_test_views', afe_job_id=job_id))
+                       TKO.run('get_detailed_test_views', afe_job_id=job_id))
         width = len(max(map(lambda x: x['test_name'], views), key=len)) + 3
+        log_links = []
         for entry in views:
             test_entry = entry['test_name'].ljust(width)
             print "%s%s" % (test_entry, get_pretty_status(entry['status']))
@@ -78,7 +95,14 @@
                 print "%s  %s: %s" % (test_entry,
                                       entry['status'],
                                       entry['reason'])
+                job_name = entry['test_name'].split('.')[0]
+                if 'job_keyvals' in entry and job_name in entry['job_keyvals']:
+                    log_links.append(
+                        generate_log_link(entry['test_name'],
+                                          entry['job_keyvals'][job_name]))
                 code = 1
+        for link in log_links:
+            print link
         break
     return code