[autotest] Support updating both firmware and CrOS from run_suite

Add firmware_build option for run_suite to pass in firmware build.
Save the build containing server-side test to job_keyvals.

Design doc:
https://docs.google.com/a/google.com/document/d/115aAHyZaatFDzuKWm61Sj2fImH4NDWikXqifvWKbbW0/edit#

BUG=chromium:270258
TEST=local run_suite:
make sure it works with older build:
./run_suite.py -b veyron_jerry -i trybot-veyron_jerry-paladin/R45-7086.0.0-b8  -p suites -s dummy_server

Call failed as --firmware_ro_build not supported yet.
./run_suite.py -b veyron_jerry -i trybot-veyron_jerry-paladin/R45-7122.0.0-b11 --firmware_build veyron_jerry-firmware/R41-6588.9.0 --test_source_build  trybot-veyron_jerry-paladin/R45-7122.0.0-b11 --firmware_ro_build veyron_jerry-firmware/R41-6588.9.0  -p suites -s dummy_server

Call failed as the firmware build does not have test_suites package built:
./run_suite.py -b veyron_jerry -i trybot-veyron_jerry-paladin/R45-7122.0.0-b11 --firmware_build veyron_jerry-firmware/R41-6588.9.0 --test_source_build  veyron_jerry-firmware/R41-6588.9.0 -p suites -s dummy_server

make sure it works with new build:
./run_suite.py -b veyron_jerry -i trybot-veyron_jerry-paladin/R45-7122.0.0-b11 --firmware_build veyron_jerry-firmware/R41-6588.9.0 --test_source_build  trybot-veyron_jerry-paladin/R45-7122.0.0-b11 -p suites -s dummy_server

DEPLOY=apache,scheduler

Change-Id: I50d23a7e81e4d6224b3483111110f910eb407074
Reviewed-on: https://chromium-review.googlesource.com/272793
Reviewed-by: Dan Shi <dshi@chromium.org>
Commit-Queue: Dan Shi <dshi@chromium.org>
Trybot-Ready: Dan Shi <dshi@chromium.org>
Tested-by: Dan Shi <dshi@chromium.org>
diff --git a/frontend/afe/site_rpc_interface.py b/frontend/afe/site_rpc_interface.py
index 5885e88..beb69f9 100644
--- a/frontend/afe/site_rpc_interface.py
+++ b/frontend/afe/site_rpc_interface.py
@@ -23,9 +23,11 @@
 from autotest_lib.client.common_lib.cros.graphite import autotest_stats
 from autotest_lib.frontend.afe import rpc_utils
 from autotest_lib.server import utils
+from autotest_lib.server.cros import provision
 from autotest_lib.server.cros.dynamic_suite import constants
 from autotest_lib.server.cros.dynamic_suite import control_file_getter
 from autotest_lib.server.cros.dynamic_suite import tools
+from autotest_lib.server.cros.dynamic_suite.suite import Suite
 from autotest_lib.server.hosts import moblab_host
 from autotest_lib.site_utils import host_history
 from autotest_lib.site_utils import job_history
@@ -122,7 +124,8 @@
                      timeout_mins=None, priority=priorities.Priority.DEFAULT,
                      suite_args=None, wait_for_results=True, job_retry=False,
                      max_retries=None, max_runtime_mins=None, suite_min_duts=0,
-                     offload_failures_only=False, **kwargs):
+                     offload_failures_only=False, builds={},
+                     test_source_build=None, **kwargs):
     """
     Create a job to run a test suite on the given device with the given image.
 
@@ -133,6 +136,12 @@
                  of the test suite to run, e.g. 'bvt'.
     @param board: the kind of device to run the tests on.
     @param build: unique name by which to refer to the image from now on.
+    @param builds: the builds to install e.g.
+                   {'cros-version:': 'x86-alex-release/R18-1655.0.0',
+                    'fw-version:':  'x86-alex-firmware/R36-5771.50.0',
+                    'fwro-version:':  'x86-alex-firmware/R36-5771.49.0'}
+                   If builds is given a value, it overrides argument build.
+    @param test_source_build: Build that contains the server-side test code.
     @param pool: Specify the pool of machines to use for scheduling
             purposes.
     @param check_hosts: require appropriate live hosts to exist in the lab.
@@ -175,14 +184,29 @@
     if num == 0:
         logging.warning("Can't run on 0 hosts; using default.")
         num = None
-    (ds, keyvals) = _stage_build_artifacts(build)
+
+    # TODO(dshi): crbug.com/496782 Remove argument build and its reference after
+    # R45 falls out of stable channel.
+    if build and not builds:
+        builds = {provision.CROS_VERSION_PREFIX: build}
+    # TODO(dshi): crbug.com/497236 Remove this check after firmware ro provision
+    # is supported in Autotest.
+    if provision.FW_RO_VERSION_PREFIX in builds:
+        raise error.SuiteArgumentException(
+                'Updating RO firmware is not supported yet.')
+    # Default test source build to CrOS build if it's not specified.
+    test_source_build = Suite.get_test_source_build(
+            builds, test_source_build=test_source_build)
+
+    (ds, keyvals) = _stage_build_artifacts(test_source_build)
     keyvals[constants.SUITE_MIN_DUTS_KEY] = suite_min_duts
 
     if not control_file:
-      # No control file was supplied so look it up from the build artifacts.
-      suite_name = canonicalize_suite_name(name)
-      control_file = _get_control_file_contents_by_name(build, ds, suite_name)
-      name = '%s-%s' % (build, suite_name)
+        # No control file was supplied so look it up from the build artifacts.
+        suite_name = canonicalize_suite_name(name)
+        control_file = _get_control_file_contents_by_name(test_source_build,
+                                                          ds, suite_name)
+        name = '%s-%s' % (test_source_build, suite_name)
 
     timeout_mins = timeout_mins or timeout * 60
     max_runtime_mins = max_runtime_mins or timeout * 60
@@ -190,9 +214,12 @@
     if not board:
         board = utils.ParseBuildName(build)[0]
 
+    # TODO(dshi): crbug.com/496782 Remove argument build and its reference after
+    # R45 falls out of stable channel.
     # Prepend build and board to the control file.
     inject_dict = {'board': board,
-                   'build': build,
+                   'build': test_source_build,
+                   'builds': builds,
                    'check_hosts': check_hosts,
                    'pool': pool,
                    'num': num,
@@ -206,7 +233,8 @@
                    'job_retry': job_retry,
                    'max_retries': max_retries,
                    'max_runtime_mins': max_runtime_mins,
-                   'offload_failures_only': offload_failures_only
+                   'offload_failures_only': offload_failures_only,
+                   'test_source_build': test_source_build
                    }
 
     control_file = tools.inject_vars(inject_dict, control_file)
@@ -277,7 +305,7 @@
 @moblab_only
 def reset_config_settings():
     with open(_CONFIG.shadow_file, 'w') as config_file:
-      pass
+        pass
     os.system('sudo reboot')