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. 16"""A client that manages Goldfish Virtual Device on compute engine. 17 18** GoldfishComputeClient ** 19 20GoldfishComputeClient derives from AndroidComputeClient. It manges a google 21compute engine project that is setup for running Goldfish Virtual Devices. 22It knows how to create a host instance from a Goldfish Stable Host Image, fetch 23Android 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 39TODO: This class should likely be merged with CvdComputeClient 40""" 41 42import getpass 43import logging 44 45from acloud import errors 46from acloud.internal import constants 47from acloud.internal.lib import android_compute_client 48from acloud.internal.lib import gcompute_client 49 50 51logger = logging.getLogger(__name__) 52 53 54class GoldfishComputeClient(android_compute_client.AndroidComputeClient): 55 """Client that manages Goldfish based Android Virtual Device. 56 57 Attributes: 58 acloud_config: An AcloudConfig object. 59 oauth2_credentials: An oauth2client.OAuth2Credentials instance. 60 """ 61 62 # To determine if the boot failed 63 BOOT_FAILED_MSG = "VIRTUAL_DEVICE_FAILED" 64 65 # To determine the failure reason 66 # If the emulator build is not available 67 EMULATOR_FETCH_FAILED_MSG = "EMULATOR_FETCH_FAILED" 68 # If the system image build is not available 69 ANDROID_FETCH_FAILED_MSG = "ANDROID_FETCH_FAILED" 70 # If the emulator could not boot in time 71 BOOT_TIMEOUT_MSG = "VIRTUAL_DEVICE_BOOT_FAILED" 72 73 #pylint: disable=signature-differs 74 def _GetDiskArgs(self, disk_name, image_name, image_project, disk_size_gb): 75 """Helper to generate disk args that is used to create an instance. 76 77 Args: 78 disk_name: String, the name of disk. 79 image_name: String, the name of the system image. 80 image_project: String, the name of the project where the image. 81 disk_size_gb: Integer, size of the blank data disk in GB. 82 83 Returns: 84 A dictionary representing disk args. 85 """ 86 return [{ 87 "type": "PERSISTENT", 88 "boot": True, 89 "mode": "READ_WRITE", 90 "autoDelete": True, 91 "initializeParams": { 92 "diskName": 93 disk_name, 94 "sourceImage": 95 self.GetImage(image_name, image_project)["selfLink"], 96 "diskSizeGb": 97 disk_size_gb 98 }, 99 }] 100 #pylint: disable=signature-differs 101 102 def CheckBootFailure(self, serial_out, instance): 103 """Overriding method from the parent class. 104 105 Args: 106 serial_out: String 107 instance: String 108 109 Raises: 110 errors.DownloadArtifactError: If it fails to download artifact. 111 errors.DeviceBootError: If it fails to boot up. 112 """ 113 if self.BOOT_FAILED_MSG in serial_out: 114 if self.EMULATOR_FETCH_FAILED_MSG in serial_out: 115 raise errors.DownloadArtifactError( 116 "Failed to download emulator build. Re-run with a newer build." 117 ) 118 if self.ANDROID_FETCH_FAILED_MSG in serial_out: 119 raise errors.DownloadArtifactError( 120 "Failed to download system image build. Re-run with a newer build." 121 ) 122 if self.BOOT_TIMEOUT_MSG in serial_out: 123 raise errors.DeviceBootError( 124 "Emulator timed out while booting.") 125 126 @staticmethod 127 def GetKernelBuildArtifact(target): 128 """Get kernel build artifact name. 129 130 Args: 131 target: String, kernel target name. 132 133 Returns: 134 String of artifact name. 135 136 Raises: 137 errors.DeviceBootError: If it fails to get artifact name. 138 """ 139 if target == "kernel": 140 return "bzImage" 141 if target == "kernel_x86_64": 142 return "bzImage" 143 if target == "kernel_aarch64": 144 return "Image.gz" 145 raise errors.DeviceBootError( 146 "Don't know the artifact name for '%s' target" % target) 147 148 # pylint: disable=too-many-locals,arguments-differ 149 # TODO: Refactor CreateInstance to pass in an object instead of all these args. 150 def CreateInstance(self, 151 instance, 152 image_name, 153 image_project, 154 build_target, 155 branch, 156 build_id, 157 kernel_branch=None, 158 kernel_build_id=None, 159 kernel_build_target=None, 160 emulator_branch=None, 161 emulator_build_id=None, 162 emulator_build_target=None, 163 blank_data_disk_size_gb=None, 164 gpu=None, 165 avd_spec=None, 166 extra_scopes=None, 167 tags=None, 168 launch_args=None): 169 """Create a goldfish instance given a stable host image and a build id. 170 171 Args: 172 instance: String, instance name. 173 image_name: String, the name of the system image. 174 image_project: String, name of the project where the image belongs. 175 Assume the default project if None. 176 build_target: String, target name, e.g. "sdk_phone_x86_64-sdk" 177 branch: String, branch name, e.g. "git_pi-dev" 178 build_id: String, build id, a string, e.g. "2263051", "P2804227" 179 kernel_branch: String, kernel branch name. 180 kernel_build_id: String, kernel build id. 181 kernel_build_target: kernel target, e.g. "kernel_x86_64" 182 emulator_branch: String, emulator branch name, e.g."aosp-emu-master-dev" 183 emulator_build_id: String, emulator build id, a string, e.g. "2263051", "P2804227" 184 emulator_build_target: String, emulator build target. 185 blank_data_disk_size_gb: Integer, size of the blank data disk in GB. 186 gpu: String, GPU that should be attached to the instance, or None of no 187 acceleration is needed. e.g. "nvidia-tesla-k80" 188 avd_spec: An AVDSpec instance. 189 extra_scopes: A list of extra scopes to be passed to the instance. 190 tags: A list of tags to associate with the instance. e.g. 191 ["http-server", "https-server"] 192 launch_args: String of args for launch command. 193 """ 194 self._VerifyZoneByQuota() 195 self._CheckMachineSize() 196 197 # Add space for possible data partition. 198 boot_disk_size_gb = ( 199 int(self.GetImage(image_name, image_project)["diskSizeGb"]) + 200 blank_data_disk_size_gb) 201 disk_args = self._GetDiskArgs(instance, image_name, image_project, 202 boot_disk_size_gb) 203 204 # Goldfish instances are metadata compatible with cuttlefish devices. 205 # See details goto/goldfish-deployment 206 metadata = self._metadata.copy() 207 metadata["user"] = getpass.getuser() 208 metadata[constants.INS_KEY_AVD_TYPE] = constants.TYPE_GF 209 210 # Note that we use the same metadata naming conventions as cuttlefish 211 metadata["cvd_01_fetch_android_build_target"] = build_target 212 metadata["cvd_01_fetch_android_bid"] = "{branch}/{build_id}".format( 213 branch=branch, build_id=build_id) 214 if kernel_branch and kernel_build_id and kernel_build_target: 215 metadata["cvd_01_fetch_kernel_bid"] = "{branch}/{build_id}".format( 216 branch=kernel_branch, build_id=kernel_build_id) 217 metadata["cvd_01_fetch_kernel_build_target"] = kernel_build_target 218 metadata["cvd_01_fetch_kernel_build_artifact"] = ( 219 self.GetKernelBuildArtifact(kernel_build_target)) 220 metadata["cvd_01_use_custom_kernel"] = "true" 221 if emulator_branch and emulator_build_id: 222 metadata[ 223 "cvd_01_fetch_emulator_bid"] = "{branch}/{build_id}".format( 224 branch=emulator_branch, build_id=emulator_build_id) 225 if emulator_build_target: 226 metadata["cvd_01_fetch_emulator_build_target"] = emulator_build_target 227 if launch_args: 228 metadata["launch_args"] = launch_args 229 metadata["cvd_01_launch"] = "1" 230 231 # Update metadata by avd_spec 232 # for legacy create_gf cmd, we will keep using resolution. 233 # And always use avd_spec for acloud create cmd. 234 if avd_spec: 235 metadata[constants.INS_KEY_AVD_FLAVOR] = avd_spec.flavor 236 metadata["cvd_01_x_res"] = avd_spec.hw_property[constants.HW_X_RES] 237 metadata["cvd_01_y_res"] = avd_spec.hw_property[constants.HW_Y_RES] 238 metadata["cvd_01_dpi"] = avd_spec.hw_property[constants.HW_ALIAS_DPI] 239 metadata[constants.INS_KEY_DISPLAY] = ("%sx%s (%s)" % ( 240 avd_spec.hw_property[constants.HW_X_RES], 241 avd_spec.hw_property[constants.HW_Y_RES], 242 avd_spec.hw_property[constants.HW_ALIAS_DPI])) 243 else: 244 resolution = self._resolution.split("x") 245 metadata["cvd_01_x_res"] = resolution[0] 246 metadata["cvd_01_y_res"] = resolution[1] 247 metadata["cvd_01_dpi"] = resolution[3] 248 249 gcompute_client.ComputeClient.CreateInstance( 250 self, 251 instance=instance, 252 image_name=image_name, 253 image_project=image_project, 254 disk_args=disk_args, 255 metadata=metadata, 256 machine_type=self._machine_type, 257 network=self._network, 258 zone=self._zone, 259 gpu=gpu, 260 tags=tags, 261 extra_scopes=extra_scopes) 262