autotest: Support paygen tests in trampoline.

BUG=chromium:951872
TEST=site_utils/run_suite.py --board=coral --build coral-release/R75-12074.0.0 --model=astronaut --suite_name paygen_au_dev --file_bugs True --pool bvt --no_wait True --priority CQ --timeout_mins 600 --suite_min_duts 3

Change-Id: I917f7331a7d2983f4acab310d57928808eaf57b2
Reviewed-on: https://chromium-review.googlesource.com/1565284
Commit-Ready: ChromeOS CL Exonerator Bot <chromiumos-cl-exonerator@appspot.gserviceaccount.com>
Tested-by: Xixuan Wu <xixuan@chromium.org>
Reviewed-by: Xixuan Wu <xixuan@chromium.org>
diff --git a/client/common_lib/control_data.py b/client/common_lib/control_data.py
index 94c78e9..517dd10 100644
--- a/client/common_lib/control_data.py
+++ b/client/common_lib/control_data.py
@@ -293,6 +293,24 @@
     def set_fast(self, val):
         self._set_bool('fast', val)
 
+    def set_update_type(self, val):
+        self._set_string('update_type', val)
+
+    def set_source_release(self, val):
+        self._set_string('source_release', val)
+
+    def set_target_release(self, val):
+        self._set_string('target_release', val)
+
+    def set_target_payload_uri(self, val):
+        self._set_string('target_payload_uri', val)
+
+    def set_source_payload_uri(self, val):
+        self._set_string('source_payload_uri', val)
+
+    def set_source_archive_uri(self, val):
+        self._set_string('source_archive_uri', val)
+
     def set_attributes(self, val):
         # Add subsystem:default if subsystem is not specified.
         self._set_set('attributes', val)
diff --git a/server/cros/dynamic_suite/suite_common.py b/server/cros/dynamic_suite/suite_common.py
index 1430039..2e2fd51 100644
--- a/server/cros/dynamic_suite/suite_common.py
+++ b/server/cros/dynamic_suite/suite_common.py
@@ -109,13 +109,14 @@
     return test_source_build
 
 
-def stage_build_artifacts(build, hostname=None):
+def stage_build_artifacts(build, hostname=None, artifacts=[]):
     """
     Ensure components of |build| necessary for installing images are staged.
 
     @param build image we want to stage.
     @param hostname hostname of a dut may run test on. This is to help to locate
         a devserver closer to duts if needed. Default is None.
+    @param artifacts A list of string artifact name to be staged.
 
     @raises StageControlFileFailure: if the dev server throws 500 while staging
         suite control files.
@@ -132,6 +133,8 @@
     timings[constants.DOWNLOAD_STARTED_TIME] = _formatted_now()
     try:
         ds.stage_artifacts(image=build, artifacts=['test_suites'])
+        if artifacts:
+          ds.stage_artifacts(image=build, artifacts=artifacts)
     except dev_server.DevServerException as e:
         raise error.StageControlFileFailure(
                 "Failed to stage %s on %s: %s" % (build, ds_name, e))
diff --git a/site_utils/paygen.py b/site_utils/paygen.py
new file mode 100644
index 0000000..ba5b0f6
--- /dev/null
+++ b/site_utils/paygen.py
@@ -0,0 +1,81 @@
+# Copyright 2019 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""This is a temporary module to help scheduling paygen suites in trampoline.
+
+In trampoline, paygen suites are scheduled via skylab create-test, to schedule
+every paygen test independently, instead of creating a paygen suite via
+skylab create-suite.
+"""
+
+import re
+
+import common
+
+from autotest_lib.server.cros.dynamic_suite import control_file_getter
+from autotest_lib.server.cros.dynamic_suite import suite_common
+
+
+def is_paygen_suite(suite_name):
+    """Check if it's to run a paygen suite in trampoline."""
+    paygen_au_regexp = 'paygen_au_*'
+    return re.match(paygen_au_regexp, suite_name) is not None
+
+
+def get_paygen_tests(build, suite_name):
+    """Parse paygen tests from au control files."""
+    if not is_paygen_suite(suite_name):
+        raise ValueError('Cannot download paygen test control files for '
+                         'non-paygen suite %s' % suite_name)
+
+    ds, _ = suite_common.stage_build_artifacts(
+        build, artifacts=['%s_suite' % suite_name])
+    cf_getter = control_file_getter.DevServerGetter(build, ds)
+    tests = suite_common.retrieve_for_suite(cf_getter, suite_name)
+    return suite_common.filter_tests(
+            tests, suite_common.name_in_tag_predicate(suite_name))
+
+
+def paygen_skylab_args(test, suite_name, image, pool, board, model,
+                       timeout_mins, qs_account, service_account):
+    """Form args for requesting paygen tests in skylab."""
+    args = ['-image', image]
+    args += ['-pool', pool]
+    if board is not None:
+        args += ['-board', board]
+
+    if model is not None:
+        args += ['-model', model]
+
+    args += ['-timeout-mins', str(timeout_mins)]
+
+    tags = ['skylab:run_suite_trampoline',
+            'build:%s' % image,
+            'suite:%s' % suite_name]
+    for t in tags:
+        args += ['-tag', t]
+
+    keyvals = ['build:%s' % image,
+               'suite:%s' % suite_name,
+               'label:%s/%s/%s' % (image, suite_name, test.name)]
+    for k in keyvals:
+        args += ['-keyval', k]
+
+    # Paygen test expects a space-separated string of name=value pairs.
+    # See http://shortn/_C8r3rC0rOP.
+    test_args = ['name=%s' % test.suite,
+                 'update_type=%s' % test.update_type,
+                 'source_release=%s' % test.source_release,
+                 'target_release=%s' % test.target_release,
+                 'target_payload_uri=%s' % test.target_payload_uri,
+                 'source_payload_uri=%s' % test.source_payload_uri,
+                 'suite=%s' % test.suite,
+                 'source_archive_uri=%s' % test.source_archive_uri]
+    args += ['-test-args', ' '.join(test_args)]
+
+    if qs_account:
+        args += ['-qs-account', qs_account]
+    args += ['-service-account-json', service_account]
+
+    return args + ['autoupdate_EndToEndTest']
diff --git a/site_utils/run_suite.py b/site_utils/run_suite.py
index ec90fb4..4d2a6f7 100755
--- a/site_utils/run_suite.py
+++ b/site_utils/run_suite.py
@@ -96,6 +96,8 @@
                       'Please re-run utils/build_externals.py inside[outside] '
                       'of the chroot accordingly.')
     raise
+
+from autotest_lib.site_utils import paygen
 from autotest_lib.site_utils import run_suite_common
 
 CONFIG = global_config.global_config
@@ -2141,8 +2143,44 @@
     logging.info(stdout)
 
 
+def _run_paygen_with_skylab(options, override_pool, override_qs_account):
+    """Run paygen suites with skylab."""
+    builds = suite_common.make_builds_from_options(options)
+    skylab_tool = os.environ.get('SKYLAB_TOOL') or _SKYLAB_TOOL
+    test_source_build = suite_common.get_test_source_build(builds)
+    pool = ('DUT_POOL_%s' % options.pool.upper()
+            if not override_pool else override_pool)
+    paygen_tests = paygen.get_paygen_tests(test_source_build, options.name)
+    for test in paygen_tests:
+        cmd = [skylab_tool, 'create-test']
+        cmd += paygen.paygen_skylab_args(
+                test, options.name, test_source_build, pool, options.board,
+                options.model, options.timeout_mins,
+                override_qs_account, _SKYLAB_SERVICE_ACCOUNT)
+        job_created_on = time.time()
+        try:
+            res = cros_build_lib.RunCommand(cmd, capture_output=True)
+        except cros_build_lib.RunCommandError as e:
+            logging.error(str(e))
+            return run_suite_common.SuiteResult(
+                    run_suite_common.RETURN_CODES.INFRA_FAILURE)
+
+        logging.info(res.output)
+        job_url = res.output.split()[-1]
+        job_id = job_url.split('id=')[-1]
+        job_timer = diagnosis_utils.JobTimer(
+                job_created_on, float(options.timeout_mins))
+        _log_create_task(job_timer, job_url, job_id)
+
+    return run_suite_common.SuiteResult(run_suite_common.RETURN_CODES.OK)
+
+
 def _run_with_skylab(options, override_pool, override_qs_account):
     """Run suite inside skylab."""
+    if paygen.is_paygen_suite(options.name):
+        return _run_paygen_with_skylab(options, override_pool,
+                                       override_qs_account)
+
     builds = suite_common.make_builds_from_options(options)
     skylab_tool = os.environ.get('SKYLAB_TOOL') or _SKYLAB_TOOL
     pool = override_pool or options.pool