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 Cuttlefish Virtual Device on compute engine. 17 18** CvdComputeClient ** 19 20CvdComputeClient derives from AndroidComputeClient. It manges a google 21compute engine project that is setup for running Cuttlefish Virtual Devices. 22It knows how to create a host instance from Cuttlefish Stable Host Image, fetch 23Android 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 cvd_compute_client.CvdComputeClient 37 38""" 39 40import getpass 41import logging 42 43from acloud.internal import constants 44from acloud.internal.lib import android_compute_client 45from acloud.internal.lib import gcompute_client 46from acloud.internal.lib import utils 47 48 49logger = logging.getLogger(__name__) 50 51_METADATA_TO_UNSET = ["cvd_01_launch", 52 "cvd_01_fetch_android_build_target", 53 "cvd_01_fetch_android_bid", 54 "cvd_01_fetch_system_bid", 55 "cvd_01_fetch_system_build_target", 56 "cvd_01_fetch_kernel_bid", 57 "cvd_01_fetch_kernel_build_target"] 58 59# TODO(228405515): Delete CvdComputeClient class. 60class CvdComputeClient(android_compute_client.AndroidComputeClient): 61 """Client that manages Anadroid Virtual Device.""" 62 63 DATA_POLICY_CREATE_IF_MISSING = "create_if_missing" 64 65 # TODO: refactor CreateInstance to take in an object that contains these 66 # args, this method differs too and holds way too cf-specific args to put in 67 # the parent method. 68 # pylint: disable=arguments-differ,too-many-locals 69 @utils.TimeExecute(function_description="Creating GCE instance") 70 def CreateInstance(self, instance, image_name, image_project, 71 build_target=None, branch=None, build_id=None, 72 kernel_branch=None, kernel_build_id=None, 73 kernel_build_target=None, blank_data_disk_size_gb=None, 74 avd_spec=None, extra_scopes=None, 75 system_build_target=None, system_branch=None, 76 system_build_id=None): 77 """Create a cuttlefish instance given stable host image and build id. 78 79 Args: 80 instance: instance name. 81 image_name: A string, the name of the GCE image. 82 image_project: A string, name of the project where the image lives. 83 Assume the default project if None. 84 build_target: Target name, e.g. "aosp_cf_x86_64_phone-userdebug" 85 branch: Branch name, e.g. "aosp-master" 86 build_id: Build id, a string, e.g. "2263051", "P2804227" 87 kernel_branch: Kernel branch name, e.g. "kernel-common-android-4.14" 88 kernel_build_id: Kernel build id, a string, e.g. "223051", "P280427" 89 kernel_build_target: String, Kernel build target name. 90 blank_data_disk_size_gb: Size of the blank data disk in GB. 91 avd_spec: An AVDSpec instance. 92 extra_scopes: A list of extra scopes to be passed to the instance. 93 system_build_target: Target name for the system image, 94 e.g. "cf_x86_phone-userdebug" 95 system_branch: A String, branch name for the system image. 96 system_build_id: A string, build id for the system image. 97 """ 98 self._CheckMachineSize() 99 100 # A blank data disk would be created on the host. Make sure the size of 101 # the boot disk is large enough to hold it. 102 boot_disk_size_gb = ( 103 int(self.GetImage(image_name, image_project)["diskSizeGb"]) + 104 blank_data_disk_size_gb) 105 disk_args = self._GetDiskArgs( 106 instance, image_name, image_project, boot_disk_size_gb) 107 108 # Transitional metadata variable as outlined in go/cuttlefish-deployment 109 # These metadata tell the host instance to fetch and launch one 110 # cuttlefish device (cvd-01). Ideally we should use a separate tool to 111 # manage CVD devices on the host instance and not through metadata. 112 # TODO(b/77626419): Remove these metadata once the 113 # cuttlefish-google.service is turned off on the host instance. 114 metadata = self._metadata.copy() 115 metadata["cvd_01_fetch_android_build_target"] = build_target 116 metadata["cvd_01_fetch_android_bid"] = "{branch}/{build_id}".format( 117 branch=branch, build_id=build_id) 118 if kernel_branch and kernel_build_id: 119 metadata["cvd_01_fetch_kernel_bid"] = "{branch}/{build_id}".format( 120 branch=kernel_branch, build_id=kernel_build_id) 121 if kernel_build_target: 122 metadata["cvd_01_fetch_kernel_build_target"] = kernel_build_target 123 if system_build_target: 124 metadata["cvd_01_fetch_system_build_target"] = system_build_target 125 if system_branch and system_build_id: 126 metadata["cvd_01_fetch_system_bid"] = "{branch}/{build_id}".format( 127 branch=system_branch, build_id=system_build_id) 128 metadata["cvd_01_launch"] = self._GetLaunchCvdArgs(avd_spec) 129 130 # For the local image, we unset the _METADATA_TO_UNSET from 131 # metadata to tell server not to launch cvd and not to fetch image 132 # while instance is booted up. 133 if avd_spec and avd_spec.image_source == constants.IMAGE_SRC_LOCAL: 134 for meta in _METADATA_TO_UNSET: 135 metadata.pop(meta, None) 136 137 if blank_data_disk_size_gb > 0: 138 # Policy 'create_if_missing' would create a blank userdata disk if 139 # missing. If already exist, reuse the disk. 140 metadata["cvd_01_data_policy"] = self.DATA_POLICY_CREATE_IF_MISSING 141 metadata["cvd_01_blank_data_disk_size"] = str( 142 blank_data_disk_size_gb * 1024) 143 metadata["user"] = getpass.getuser() 144 # Update metadata by avd_spec 145 # for legacy create_cf cmd, we will keep using resolution. 146 # And always use avd_spec for acloud create cmd. 147 # TODO(b/118406018): deprecate resolution config and use hw_proprty for 148 # all create cmds. 149 if avd_spec: 150 metadata[constants.INS_KEY_AVD_TYPE] = avd_spec.avd_type 151 metadata[constants.INS_KEY_AVD_FLAVOR] = avd_spec.flavor 152 metadata["cvd_01_x_res"] = avd_spec.hw_property[constants.HW_X_RES] 153 metadata["cvd_01_y_res"] = avd_spec.hw_property[constants.HW_Y_RES] 154 metadata["cvd_01_dpi"] = avd_spec.hw_property[constants.HW_ALIAS_DPI] 155 if constants.HW_ALIAS_DISK in avd_spec.hw_property: 156 metadata["cvd_01_blank_data_disk_size"] = avd_spec.hw_property[ 157 constants.HW_ALIAS_DISK] 158 # Use another METADATA_DISPLAY to record resolution which will be 159 # retrieved in acloud list cmd. We try not to use cvd_01_x_res 160 # since cvd_01_xxx metadata is going to deprecated by cuttlefish. 161 metadata[constants.INS_KEY_DISPLAY] = ("%sx%s (%s)" % ( 162 avd_spec.hw_property[constants.HW_X_RES], 163 avd_spec.hw_property[constants.HW_Y_RES], 164 avd_spec.hw_property[constants.HW_ALIAS_DPI])) 165 else: 166 resolution = self._resolution.split("x") 167 metadata["cvd_01_dpi"] = resolution[3] 168 metadata["cvd_01_x_res"] = resolution[0] 169 metadata["cvd_01_y_res"] = resolution[1] 170 171 gcompute_client.ComputeClient.CreateInstance( 172 self, 173 instance=instance, 174 image_name=image_name, 175 image_project=image_project, 176 disk_args=disk_args, 177 metadata=metadata, 178 machine_type=self._machine_type, 179 network=self._network, 180 zone=self._zone, 181 disk_type=avd_spec.disk_type if avd_spec else None, 182 extra_scopes=extra_scopes) 183 184 def _GetLaunchCvdArgs(self, avd_spec): 185 """Define the launch_cvd args. 186 187 Set launch_cvd args with following priority. 188 -First: Set args from config. 189 -Second: Set args from cpu and memory settings. 190 -Third: Set args as "1" to don't pass any args. 191 192 Args: 193 avd_spec: An AVDSpec instance. 194 195 Returns: 196 String of launch_cvd args. 197 """ 198 if self._launch_args: 199 return self._launch_args 200 201 if avd_spec: 202 cpu_arg = "" 203 mem_arg = "" 204 if constants.HW_ALIAS_CPUS in avd_spec.hw_property: 205 cpu_arg = ("-cpus=%s" % 206 avd_spec.hw_property[constants.HW_ALIAS_CPUS]) 207 if constants.HW_ALIAS_MEMORY in avd_spec.hw_property: 208 mem_arg = ("-memory_mb=%s" % 209 avd_spec.hw_property[constants.HW_ALIAS_MEMORY]) 210 if cpu_arg or mem_arg: 211 return cpu_arg + " " + mem_arg 212 213 return "1" 214