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 Raises an errors.DeviceBootError exception if a failure is detected. 111 """ 112 if self.BOOT_FAILED_MSG in serial_out: 113 if self.EMULATOR_FETCH_FAILED_MSG in serial_out: 114 raise errors.DeviceBootError( 115 "Failed to download emulator build. Re-run with a newer build." 116 ) 117 if self.ANDROID_FETCH_FAILED_MSG in serial_out: 118 raise errors.DeviceBootError( 119 "Failed to download system image build. Re-run with a newer build." 120 ) 121 if self.BOOT_TIMEOUT_MSG in serial_out: 122 raise errors.DeviceBootError( 123 "Emulator timed out while booting.") 124 125 @staticmethod 126 def GetKernelBuildArtifact(target): 127 if target == "kernel": 128 return "bzImage" 129 if target == "kernel_x86_64": 130 return "bzImage" 131 if target == "kernel_aarch64": 132 return "Image.gz" 133 raise errors.DeviceBootError( 134 "Don't know the artifact name for '%s' target" % target) 135 136 # pylint: disable=too-many-locals,arguments-differ 137 # TODO: Refactor CreateInstance to pass in an object instead of all these args. 138 def CreateInstance(self, 139 instance, 140 image_name, 141 image_project, 142 build_target, 143 branch, 144 build_id, 145 kernel_branch=None, 146 kernel_build_id=None, 147 kernel_build_target=None, 148 emulator_branch=None, 149 emulator_build_id=None, 150 blank_data_disk_size_gb=None, 151 gpu=None, 152 avd_spec=None, 153 extra_scopes=None, 154 tags=None): 155 """Create a goldfish instance given a stable host image and a build id. 156 157 Args: 158 instance: String, instance name. 159 image_name: String, the name of the system image. 160 image_project: String, name of the project where the image belongs. 161 Assume the default project if None. 162 build_target: String, target name, e.g. "sdk_phone_x86_64-sdk" 163 branch: String, branch name, e.g. "git_pi-dev" 164 build_id: String, build id, a string, e.g. "2263051", "P2804227" 165 kernel_branch: String, kernel branch name. 166 kernel_build_id: String, kernel build id. 167 kernel_build_target: kernel target, e.g. "kernel_x86_64" 168 emulator_branch: String, emulator branch name, e.g."aosp-emu-master-dev" 169 emulator_build_id: String, emulator build id, a string, e.g. "2263051", "P2804227" 170 blank_data_disk_size_gb: Integer, size of the blank data disk in GB. 171 gpu: String, GPU that should be attached to the instance, or None of no 172 acceleration is needed. e.g. "nvidia-tesla-k80" 173 avd_spec: An AVDSpec instance. 174 extra_scopes: A list of extra scopes to be passed to the instance. 175 tags: A list of tags to associate with the instance. e.g. 176 ["http-server", "https-server"] 177 """ 178 self._CheckMachineSize() 179 180 # Add space for possible data partition. 181 boot_disk_size_gb = ( 182 int(self.GetImage(image_name, image_project)["diskSizeGb"]) + 183 blank_data_disk_size_gb) 184 disk_args = self._GetDiskArgs(instance, image_name, image_project, 185 boot_disk_size_gb) 186 187 # Goldfish instances are metadata compatible with cuttlefish devices. 188 # See details goto/goldfish-deployment 189 metadata = self._metadata.copy() 190 metadata["user"] = getpass.getuser() 191 metadata[constants.INS_KEY_AVD_TYPE] = constants.TYPE_GF 192 193 # Note that we use the same metadata naming conventions as cuttlefish 194 metadata["cvd_01_fetch_android_build_target"] = build_target 195 metadata["cvd_01_fetch_android_bid"] = "{branch}/{build_id}".format( 196 branch=branch, build_id=build_id) 197 if kernel_branch and kernel_build_id and kernel_build_target: 198 metadata["cvd_01_fetch_kernel_bid"] = "{branch}/{build_id}".format( 199 branch=kernel_branch, build_id=kernel_build_id) 200 metadata["cvd_01_fetch_kernel_build_target"] = kernel_build_target 201 metadata["cvd_01_fetch_kernel_build_artifact"] = ( 202 self.GetKernelBuildArtifact(kernel_build_target)) 203 metadata["cvd_01_use_custom_kernel"] = "true" 204 if emulator_branch and emulator_build_id: 205 metadata[ 206 "cvd_01_fetch_emulator_bid"] = "{branch}/{build_id}".format( 207 branch=emulator_branch, build_id=emulator_build_id) 208 metadata["cvd_01_launch"] = "1" 209 210 # Update metadata by avd_spec 211 # for legacy create_gf cmd, we will keep using resolution. 212 # And always use avd_spec for acloud create cmd. 213 if avd_spec: 214 metadata[constants.INS_KEY_AVD_FLAVOR] = avd_spec.flavor 215 metadata["cvd_01_x_res"] = avd_spec.hw_property[constants.HW_X_RES] 216 metadata["cvd_01_y_res"] = avd_spec.hw_property[constants.HW_Y_RES] 217 metadata["cvd_01_dpi"] = avd_spec.hw_property[constants.HW_ALIAS_DPI] 218 metadata[constants.INS_KEY_DISPLAY] = ("%sx%s (%s)" % ( 219 avd_spec.hw_property[constants.HW_X_RES], 220 avd_spec.hw_property[constants.HW_Y_RES], 221 avd_spec.hw_property[constants.HW_ALIAS_DPI])) 222 else: 223 resolution = self._resolution.split("x") 224 metadata["cvd_01_x_res"] = resolution[0] 225 metadata["cvd_01_y_res"] = resolution[1] 226 metadata["cvd_01_dpi"] = resolution[3] 227 228 gcompute_client.ComputeClient.CreateInstance( 229 self, 230 instance=instance, 231 image_name=image_name, 232 image_project=image_project, 233 disk_args=disk_args, 234 metadata=metadata, 235 machine_type=self._machine_type, 236 network=self._network, 237 zone=self._zone, 238 gpu=gpu, 239 tags=tags, 240 extra_scopes=extra_scopes) 241