Kevin Cheng | b596388 | 2018-05-09 00:06:27 -0700 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # |
| 3 | # Copyright 2018 - The Android Open Source Project |
| 4 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | # you may not use this file except in compliance with the License. |
| 7 | # You may obtain a copy of the License at |
| 8 | # |
| 9 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | # |
| 11 | # Unless required by applicable law or agreed to in writing, software |
| 12 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. |
Kevin Cheng | b596388 | 2018-05-09 00:06:27 -0700 | [diff] [blame] | 16 | """A client that manages Goldfish Virtual Device on compute engine. |
| 17 | |
| 18 | ** GoldfishComputeClient ** |
| 19 | |
| 20 | GoldfishComputeClient derives from AndroidComputeClient. It manges a google |
| 21 | compute engine project that is setup for running Goldfish Virtual Devices. |
| 22 | It knows how to create a host instance from a Goldfish Stable Host Image, fetch |
| 23 | Android build, an emulator build, and start Android within the host instance. |
| 24 | |
| 25 | ** Class hierarchy ** |
| 26 | |
| 27 | base_cloud_client.BaseCloudApiClient |
| 28 | ^ |
| 29 | | |
| 30 | gcompute_client.ComputeClient |
| 31 | ^ |
| 32 | | |
| 33 | android_compute_client.AndroidComputeClient |
| 34 | ^ |
| 35 | | |
| 36 | goldfish_compute_client.GoldfishComputeClient |
| 37 | |
| 38 | |
cylan | 0d77ae1 | 2018-05-18 08:36:48 +0000 | [diff] [blame] | 39 | TODO: This class should likely be merged with CvdComputeClient |
Kevin Cheng | b596388 | 2018-05-09 00:06:27 -0700 | [diff] [blame] | 40 | """ |
| 41 | |
| 42 | import getpass |
| 43 | import logging |
Kevin Cheng | b596388 | 2018-05-09 00:06:27 -0700 | [diff] [blame] | 44 | |
Sam Chiu | 7de3b23 | 2018-12-06 19:45:52 +0800 | [diff] [blame^] | 45 | from acloud import errors |
Kevin Cheng | b596388 | 2018-05-09 00:06:27 -0700 | [diff] [blame] | 46 | from acloud.internal.lib import android_compute_client |
| 47 | from acloud.internal.lib import gcompute_client |
Kevin Cheng | b596388 | 2018-05-09 00:06:27 -0700 | [diff] [blame] | 48 | |
| 49 | logger = logging.getLogger(__name__) |
| 50 | |
| 51 | |
| 52 | class GoldfishComputeClient(android_compute_client.AndroidComputeClient): |
cylan | 0d77ae1 | 2018-05-18 08:36:48 +0000 | [diff] [blame] | 53 | """Client that manages Goldfish based Android Virtual Device. |
Kevin Cheng | b596388 | 2018-05-09 00:06:27 -0700 | [diff] [blame] | 54 | |
cylan | 0d77ae1 | 2018-05-18 08:36:48 +0000 | [diff] [blame] | 55 | Attributes: |
| 56 | acloud_config: An AcloudConfig object. |
| 57 | oauth2_credentials: An oauth2client.OAuth2Credentials instance. |
Kevin Cheng | b596388 | 2018-05-09 00:06:27 -0700 | [diff] [blame] | 58 | """ |
Kevin Cheng | b596388 | 2018-05-09 00:06:27 -0700 | [diff] [blame] | 59 | |
cylan | 0d77ae1 | 2018-05-18 08:36:48 +0000 | [diff] [blame] | 60 | # To determine if the boot failed |
| 61 | BOOT_FAILED_MSG = "VIRTUAL_DEVICE_FAILED" |
Kevin Cheng | b596388 | 2018-05-09 00:06:27 -0700 | [diff] [blame] | 62 | |
cylan | 0d77ae1 | 2018-05-18 08:36:48 +0000 | [diff] [blame] | 63 | # To determine the failure reason |
| 64 | # If the emulator build is not available |
| 65 | EMULATOR_FETCH_FAILED_MSG = "EMULATOR_FETCH_FAILED" |
| 66 | # If the system image build is not available |
| 67 | ANDROID_FETCH_FAILED_MSG = "ANDROID_FETCH_FAILED" |
| 68 | # If the emulator could not boot in time |
| 69 | BOOT_TIMEOUT_MSG = "VIRTUAL_DEVICE_BOOT_FAILED" |
Kevin Cheng | b596388 | 2018-05-09 00:06:27 -0700 | [diff] [blame] | 70 | |
cylan | 0d77ae1 | 2018-05-18 08:36:48 +0000 | [diff] [blame] | 71 | #pylint: disable=signature-differs |
| 72 | def _GetDiskArgs(self, disk_name, image_name, image_project, disk_size_gb): |
| 73 | """Helper to generate disk args that is used to create an instance. |
| 74 | |
| 75 | Args: |
| 76 | disk_name: String, the name of disk. |
| 77 | image_name: String, the name of the system image. |
| 78 | image_project: String, the name of the project where the image. |
| 79 | disk_size_gb: Integer, size of the blank data disk in GB. |
| 80 | |
| 81 | Returns: |
| 82 | A dictionary representing disk args. |
| 83 | """ |
| 84 | return [{ |
| 85 | "type": "PERSISTENT", |
| 86 | "boot": True, |
| 87 | "mode": "READ_WRITE", |
| 88 | "autoDelete": True, |
| 89 | "initializeParams": { |
| 90 | "diskName": |
Kevin Cheng | b596388 | 2018-05-09 00:06:27 -0700 | [diff] [blame] | 91 | disk_name, |
cylan | 0d77ae1 | 2018-05-18 08:36:48 +0000 | [diff] [blame] | 92 | "sourceImage": |
Kevin Cheng | b596388 | 2018-05-09 00:06:27 -0700 | [diff] [blame] | 93 | self.GetImage(image_name, image_project)["selfLink"], |
cylan | 0d77ae1 | 2018-05-18 08:36:48 +0000 | [diff] [blame] | 94 | "diskSizeGb": |
Kevin Cheng | b596388 | 2018-05-09 00:06:27 -0700 | [diff] [blame] | 95 | disk_size_gb |
cylan | 0d77ae1 | 2018-05-18 08:36:48 +0000 | [diff] [blame] | 96 | }, |
| 97 | }] |
| 98 | #pylint: disable=signature-differs |
Kevin Cheng | b596388 | 2018-05-09 00:06:27 -0700 | [diff] [blame] | 99 | |
cylan | 0d77ae1 | 2018-05-18 08:36:48 +0000 | [diff] [blame] | 100 | def CheckBootFailure(self, serial_out, instance): |
| 101 | """Overriding method from the parent class. |
Kevin Cheng | b596388 | 2018-05-09 00:06:27 -0700 | [diff] [blame] | 102 | |
cylan | 0d77ae1 | 2018-05-18 08:36:48 +0000 | [diff] [blame] | 103 | Args: |
| 104 | serial_out: String |
| 105 | instance: String |
Kevin Cheng | b596388 | 2018-05-09 00:06:27 -0700 | [diff] [blame] | 106 | |
cylan | 0d77ae1 | 2018-05-18 08:36:48 +0000 | [diff] [blame] | 107 | Raises: |
| 108 | Raises an errors.DeviceBootError exception if a failure is detected. |
| 109 | """ |
| 110 | if self.BOOT_FAILED_MSG in serial_out: |
| 111 | if self.EMULATOR_FETCH_FAILED_MSG in serial_out: |
| 112 | raise errors.DeviceBootError( |
| 113 | "Failed to download emulator build. Re-run with a newer build." |
| 114 | ) |
| 115 | if self.ANDROID_FETCH_FAILED_MSG in serial_out: |
| 116 | raise errors.DeviceBootError( |
| 117 | "Failed to download system image build. Re-run with a newer build." |
| 118 | ) |
| 119 | if self.BOOT_TIMEOUT_MSG in serial_out: |
| 120 | raise errors.DeviceBootError( |
| 121 | "Emulator timed out while booting.") |
Kevin Cheng | b596388 | 2018-05-09 00:06:27 -0700 | [diff] [blame] | 122 | |
cylan | 0d77ae1 | 2018-05-18 08:36:48 +0000 | [diff] [blame] | 123 | # pylint: disable=too-many-locals,arguments-differ |
| 124 | # TODO: Refactor CreateInstance to pass in an object instead of all these args. |
| 125 | def CreateInstance(self, |
| 126 | instance, |
| 127 | image_name, |
| 128 | image_project, |
| 129 | build_target, |
| 130 | branch, |
| 131 | build_id, |
| 132 | emulator_branch=None, |
| 133 | emulator_build_id=None, |
| 134 | blank_data_disk_size_gb=None, |
| 135 | gpu=None): |
| 136 | """Create a goldfish instance given a stable host image and a build id. |
Kevin Cheng | b596388 | 2018-05-09 00:06:27 -0700 | [diff] [blame] | 137 | |
cylan | 0d77ae1 | 2018-05-18 08:36:48 +0000 | [diff] [blame] | 138 | Args: |
| 139 | instance: String, instance name. |
| 140 | image_name: String, the name of the system image. |
| 141 | image_project: String, name of the project where the image belongs. |
| 142 | Assume the default project if None. |
| 143 | build_target: String, target name, e.g. "sdk_phone_x86_64-sdk" |
| 144 | branch: String, branch name, e.g. "git_pi-dev" |
| 145 | build_id: String, build id, a string, e.g. "2263051", "P2804227" |
| 146 | emulator_branch: String, emulator branch name, e.g."aosp-emu-master-dev" |
| 147 | emulator_build_id: String, emulator build id, a string, e.g. "2263051", "P2804227" |
| 148 | blank_data_disk_size_gb: Integer, size of the blank data disk in GB. |
| 149 | gpu: String, GPU that should be attached to the instance, or None of no |
| 150 | acceleration is needed. e.g. "nvidia-tesla-k80" |
| 151 | """ |
| 152 | self._CheckMachineSize() |
Kevin Cheng | b596388 | 2018-05-09 00:06:27 -0700 | [diff] [blame] | 153 | |
cylan | 0d77ae1 | 2018-05-18 08:36:48 +0000 | [diff] [blame] | 154 | # Add space for possible data partition. |
| 155 | boot_disk_size_gb = ( |
| 156 | int(self.GetImage(image_name, image_project)["diskSizeGb"]) + |
| 157 | blank_data_disk_size_gb) |
| 158 | disk_args = self._GetDiskArgs(instance, image_name, image_project, |
| 159 | boot_disk_size_gb) |
Kevin Cheng | b596388 | 2018-05-09 00:06:27 -0700 | [diff] [blame] | 160 | |
cylan | 0d77ae1 | 2018-05-18 08:36:48 +0000 | [diff] [blame] | 161 | # Goldfish instances are metadata compatible with cuttlefish devices. |
| 162 | # See details goto/goldfish-deployment |
| 163 | metadata = self._metadata.copy() |
| 164 | resolution = self._resolution.split("x") |
Kevin Cheng | b596388 | 2018-05-09 00:06:27 -0700 | [diff] [blame] | 165 | |
cylan | 0d77ae1 | 2018-05-18 08:36:48 +0000 | [diff] [blame] | 166 | # Note that we use the same metadata naming conventions as cuttlefish |
| 167 | metadata["cvd_01_dpi"] = resolution[3] |
| 168 | metadata["cvd_01_fetch_android_build_target"] = build_target |
| 169 | metadata["cvd_01_fetch_android_bid"] = "{branch}/{build_id}".format( |
| 170 | branch=branch, build_id=build_id) |
| 171 | if emulator_branch and emulator_build_id: |
| 172 | metadata[ |
| 173 | "cvd_01_fetch_emulator_bid"] = "{branch}/{build_id}".format( |
| 174 | branch=emulator_branch, build_id=emulator_build_id) |
| 175 | metadata["cvd_01_launch"] = "1" |
| 176 | metadata["cvd_01_x_res"] = resolution[0] |
| 177 | metadata["cvd_01_y_res"] = resolution[1] |
Kevin Cheng | b596388 | 2018-05-09 00:06:27 -0700 | [diff] [blame] | 178 | |
cylan | 0d77ae1 | 2018-05-18 08:36:48 +0000 | [diff] [blame] | 179 | # Add per-instance ssh key |
| 180 | if self._ssh_public_key_path: |
| 181 | rsa = self._LoadSshPublicKey(self._ssh_public_key_path) |
| 182 | logger.info( |
| 183 | "ssh_public_key_path is specified in config: %s, " |
| 184 | "will add the key to the instance.", self._ssh_public_key_path) |
| 185 | metadata["sshKeys"] = "%s:%s" % (getpass.getuser(), rsa) |
| 186 | else: |
| 187 | logger.warning("ssh_public_key_path is not specified in config, " |
| 188 | "only project-wide key will be effective.") |
Kevin Cheng | b596388 | 2018-05-09 00:06:27 -0700 | [diff] [blame] | 189 | |
cylan | 0d77ae1 | 2018-05-18 08:36:48 +0000 | [diff] [blame] | 190 | gcompute_client.ComputeClient.CreateInstance( |
| 191 | self, |
| 192 | instance=instance, |
| 193 | image_name=image_name, |
| 194 | image_project=image_project, |
| 195 | disk_args=disk_args, |
| 196 | metadata=metadata, |
| 197 | machine_type=self._machine_type, |
| 198 | network=self._network, |
| 199 | zone=self._zone, |
| 200 | gpu=gpu) |