acloud: Cherrypick cl/203969178

Bug: None
Test: ./run_tests.sh
Test: m acloud_test && acloud_test
Test: m acloud && acloud create_gf
Change-Id: I28e292f0406b631a20ce9fa95b5a51cd7822f59f
diff --git a/public/acloud_main.py b/public/acloud_main.py
index 196d404..1b0da84 100644
--- a/public/acloud_main.py
+++ b/public/acloud_main.py
@@ -310,14 +310,17 @@
             raise errors.CommandArgError(
                 "Must specify --build_id and --build_target at the same time.")
 
-    if parsed_args.which in [CMD_CREATE_CUTTLEFISH, CMD_CREATE_GOLDFISH]:
+    if parsed_args.which == CMD_CREATE_CUTTLEFISH:
         if not parsed_args.build_id or not parsed_args.build_target:
             raise errors.CommandArgError(
                 "Must specify --build_id and --build_target")
 
     if parsed_args.which == CMD_CREATE_GOLDFISH:
-        if not parsed_args.emulator_build_id:
-            raise errors.CommandArgError("Must specify --emulator_build_id")
+        if not parsed_args.emulator_build_id and not parsed_args.build_id:
+            raise errors.CommandArgError("Must specify either "
+                                         "--emulator_build_id or --build_id")
+        if not parsed_args.build_target:
+            raise errors.CommandArgError("Must specify --build_target")
 
     if parsed_args.which in [
             create_args.CMD_CREATE, CMD_CREATE_CUTTLEFISH, CMD_CREATE_GOLDFISH
@@ -414,7 +417,8 @@
             num=args.num,
             serial_log_file=args.serial_log_file,
             logcat_file=args.logcat_file,
-            autoconnect=args.autoconnect)
+            autoconnect=args.autoconnect,
+            branch=args.branch)
     elif args.which == CMD_DELETE:
         report = device_driver.DeleteAndroidVirtualDevices(
             cfg, args.instance_names)
diff --git a/public/actions/create_goldfish_action.py b/public/actions/create_goldfish_action.py
index 688440a..55b089c 100644
--- a/public/actions/create_goldfish_action.py
+++ b/public/actions/create_goldfish_action.py
@@ -19,12 +19,15 @@
 emulator.
 """
 import logging
+import os
 
+from acloud.public import errors
 from acloud.public.actions import common_operations
 from acloud.public.actions import base_device_factory
 from acloud.internal.lib import android_build_client
 from acloud.internal.lib import auth
 from acloud.internal.lib import goldfish_compute_client
+from acloud.internal.lib import utils
 
 logger = logging.getLogger(__name__)
 
@@ -33,6 +36,11 @@
     goldfish_compute_client.GoldfishComputeClient.SCOPE
 ])
 
+_EMULATOR_INFO_FILENAME = "emulator-info.txt"
+_EMULATOR_VERSION_PATTERN = "version-emulator"
+_SYSIMAGE_INFO_FILENAME = "android-info.txt"
+_SYSIMAGE_VERSION_PATTERN = "version-sysimage-{}-{}"
+
 
 class GoldfishDeviceFactory(base_device_factory.BaseDeviceFactory):
     """A class that can produce a goldfish device.
@@ -118,6 +126,59 @@
         return instance
 
 
+def ParseBuildInfo(filename, pattern):
+    """Parse build id based on a substring.
+
+    This will parse a file which contains build information to be used. For an
+    emulator build, the file will contain the information about the corresponding
+    stable system image build id. Similarly, for a system image build, the file
+    will contain the information about the corresponding stable emulator build id.
+    Pattern is a substring being used as a key to parse the build info. For
+    example, "version-sysimage-git_pi-dev-sdk_gphone_x86_64-userdebug".
+
+    Args:
+        filename: Name of file to parse.
+        pattern: Substring to look for in file
+
+    Returns:
+        Build id parsed from the file based on pattern
+        Returns None if pattern not found in file
+    """
+    with open(filename) as build_info_file:
+        for line in build_info_file:
+            if pattern in line:
+                return line.rstrip().split("=")[1]
+    return None
+
+
+def _FetchBuildIdFromFile(cfg, build_target, build_id, pattern, filename):
+    """Parse and fetch build id from a file based on a pattern.
+
+    Verify if one of the system image or emulator binary build id is missing.
+    If found missing, then update according to the resource file.
+
+    Args:
+        cfg: An AcloudConfig instance.
+        build_target: Target name.
+        build_id: Build id, a string, e.g. "2263051", "P2804227"
+        pattern: A string to parse build info file.
+        filename: Name of file containing the build info.
+
+    Returns:
+        A build id or None
+    """
+    build_client = android_build_client.AndroidBuildClient(
+        auth.CreateCredentials(cfg, ALL_SCOPES))
+
+    with utils.TempDir() as tempdir:
+        temp_filename = os.path.join(tempdir, filename)
+        build_client.DownloadArtifact(build_target,
+                                      build_id,
+                                      filename,
+                                      temp_filename)
+
+        return ParseBuildInfo(temp_filename, pattern)
+
 
 def CreateDevices(cfg,
                   build_target=None,
@@ -127,7 +188,8 @@
                   num=1,
                   serial_log_file=None,
                   logcat_file=None,
-                  autoconnect=False):
+                  autoconnect=False,
+                  branch=None):
     """Create one or multiple Goldfish devices.
 
     Args:
@@ -141,10 +203,33 @@
                         be saved to.
         logcat_file: String, A path to a file where logcat logs should be saved.
         autoconnect: Boolean, Create ssh tunnel(s) and adb connect after device creation.
+        branch: String, Branch name for system image.
 
     Returns:
         A Report instance.
     """
+    if emulator_build_id is None:
+        emulator_build_id = _FetchBuildIdFromFile(cfg,
+                                                  build_target,
+                                                  build_id,
+                                                  _EMULATOR_VERSION_PATTERN,
+                                                  _EMULATOR_INFO_FILENAME)
+
+    if emulator_build_id is None:
+        raise errors.CommandArgError("Emulator build id not found "
+                                     "in %s" % _EMULATOR_INFO_FILENAME)
+
+    if build_id is None:
+        pattern = _SYSIMAGE_VERSION_PATTERN.format(branch, build_target)
+        build_id = _FetchBuildIdFromFile(cfg,
+                                         cfg.emulator_build_target,
+                                         emulator_build_id,
+                                         pattern,
+                                         _SYSIMAGE_INFO_FILENAME)
+
+    if build_id is None:
+        raise errors.CommandArgError("Emulator system image build id not found "
+                                     "in %s" % _SYSIMAGE_INFO_FILENAME)
     # TODO: Implement copying files from the instance, including
     # the serial log (kernel log), and logcat log files.
     # TODO: Implement autoconnect.
diff --git a/public/actions/create_goldfish_action_test.py b/public/actions/create_goldfish_action_test.py
index 70474a0..3b76209 100644
--- a/public/actions/create_goldfish_action_test.py
+++ b/public/actions/create_goldfish_action_test.py
@@ -123,5 +123,112 @@
         self.assertEquals(report.command, "create_gf")
         self.assertEquals(report.status, "SUCCESS")
 
+    def testCreateDevicesWithoutBuildId(self):
+        """Test CreateDevices when emulator sys image build id is not provided."""
+        cfg = self._CreateCfg()
+
+        # Mock uuid
+        fake_uuid = mock.MagicMock(hex="1234")
+        self.Patch(uuid, "uuid4", return_value=fake_uuid)
+
+        # Mock compute client methods
+        self.compute_client.GetInstanceIP.return_value = self.IP
+        self.compute_client.GenerateImageName.return_value = self.IMAGE
+        self.compute_client.GenerateInstanceName.return_value = self.INSTANCE
+
+        # Mock build client method
+        self.build_client.GetBranch.side_effect = [
+            self.BRANCH, self.EMULATOR_BRANCH
+        ]
+
+        # Mock _FetchBuildIdFromFile method
+        self.Patch(
+            create_goldfish_action,
+            "_FetchBuildIdFromFile",
+            return_value=self.BUILD_ID)
+
+        # Call CreateDevices
+        report = create_goldfish_action.CreateDevices(
+            cfg,
+            self.BUILD_TARGET,
+            None,
+            self.EMULATOR_BUILD_ID,
+            self.GPU,
+            branch=self.BRANCH)
+
+        # Verify
+        self.compute_client.CreateInstance.assert_called_with(
+            instance=self.INSTANCE,
+            blank_data_disk_size_gb=self.EXTRA_DATA_DISK_GB,
+            image_name=self.GOLDFISH_HOST_IMAGE_NAME,
+            image_project=self.GOLDFISH_HOST_IMAGE_PROJECT,
+            build_target=self.BUILD_TARGET,
+            branch=self.BRANCH,
+            build_id=self.BUILD_ID,
+            emulator_branch=self.EMULATOR_BRANCH,
+            emulator_build_id=self.EMULATOR_BUILD_ID,
+            gpu=self.GPU)
+
+        self.assertEquals(report.data, {
+            "devices": [{
+                "instance_name": self.INSTANCE,
+                "ip": self.IP,
+            },],
+        })
+        self.assertEquals(report.command, "create_gf")
+        self.assertEquals(report.status, "SUCCESS")
+
+    #pylint: disable=invalid-name
+    def testCreateDevicesWithoutEmulatorBuildId(self):
+        """Test CreateDevices when emulator build id is not provided."""
+        cfg = self._CreateCfg()
+
+        # Mock uuid
+        fake_uuid = mock.MagicMock(hex="1234")
+        self.Patch(uuid, "uuid4", return_value=fake_uuid)
+
+        # Mock compute client methods
+        self.compute_client.GetInstanceIP.return_value = self.IP
+        self.compute_client.GenerateImageName.return_value = self.IMAGE
+        self.compute_client.GenerateInstanceName.return_value = self.INSTANCE
+
+        # Mock build client method
+        self.build_client.GetBranch.side_effect = [
+            self.BRANCH, self.EMULATOR_BRANCH
+        ]
+
+        # Mock _FetchBuildIdFromFile method
+        self.Patch(
+            create_goldfish_action,
+            "_FetchBuildIdFromFile",
+            return_value=self.EMULATOR_BUILD_ID)
+
+        # Call CreateDevices
+        report = create_goldfish_action.CreateDevices(
+            cfg, self.BUILD_TARGET, self.BUILD_ID, None, self.GPU)
+
+        # Verify
+        self.compute_client.CreateInstance.assert_called_with(
+            instance=self.INSTANCE,
+            blank_data_disk_size_gb=self.EXTRA_DATA_DISK_GB,
+            image_name=self.GOLDFISH_HOST_IMAGE_NAME,
+            image_project=self.GOLDFISH_HOST_IMAGE_PROJECT,
+            build_target=self.BUILD_TARGET,
+            branch=self.BRANCH,
+            build_id=self.BUILD_ID,
+            emulator_branch=self.EMULATOR_BRANCH,
+            emulator_build_id=self.EMULATOR_BUILD_ID,
+            gpu=self.GPU)
+
+        self.assertEquals(report.data, {
+            "devices": [{
+                "instance_name": self.INSTANCE,
+                "ip": self.IP,
+            },],
+        })
+        self.assertEquals(report.command, "create_gf")
+        self.assertEquals(report.status, "SUCCESS")
+
+
 if __name__ == "__main__":
     unittest.main()