#!/usr/bin/env python # # Copyright 2018 - The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """A client that manages Cuttlefish Virtual Device on compute engine. ** CvdComputeClient ** CvdComputeClient derives from AndroidComputeClient. It manges a google compute engine project that is setup for running Cuttlefish Virtual Devices. It knows how to create a host instance from Cuttlefish Stable Host Image, fetch Android build, and start Android within the host instance. ** Class hierarchy ** base_cloud_client.BaseCloudApiClient ^ | gcompute_client.ComputeClient ^ | android_compute_client.AndroidComputeClient ^ | cvd_compute_client.CvdComputeClient """ import getpass import logging from acloud.internal import constants from acloud.internal.lib import android_compute_client from acloud.internal.lib import gcompute_client from acloud.internal.lib import utils logger = logging.getLogger(__name__) class CvdComputeClient(android_compute_client.AndroidComputeClient): """Client that manages Anadroid Virtual Device.""" DATA_POLICY_CREATE_IF_MISSING = "create_if_missing" # TODO: refactor CreateInstance to take in an object that contains these # args, this method differs too and holds way too cf-specific args to put in # the parent method. # pylint: disable=arguments-differ,too-many-locals @utils.TimeExecute(function_description="Creating GCE instance") def CreateInstance(self, instance, image_name, image_project, build_target=None, branch=None, build_id=None, kernel_branch=None, kernel_build_id=None, blank_data_disk_size_gb=None, avd_spec=None, extra_scopes=None): """Create a cuttlefish instance given stable host image and build id. Args: instance: instance name. image_name: A string, the name of the GCE image. image_project: A string, name of the project where the image lives. Assume the default project if None. build_target: Target name, e.g. "aosp_cf_x86_phone-userdebug" branch: Branch name, e.g. "aosp-master" build_id: Build id, a string, e.g. "2263051", "P2804227" kernel_branch: Kernel branch name, e.g. "kernel-common-android-4.14" kernel_build_id: Kernel build id, a string, e.g. "223051", "P280427" blank_data_disk_size_gb: Size of the blank data disk in GB. avd_spec: An AVDSpec instance. extra_scopes: A list of extra scopes to be passed to the instance. """ self._CheckMachineSize() # A blank data disk would be created on the host. Make sure the size of # the boot disk is large enough to hold it. boot_disk_size_gb = ( int(self.GetImage(image_name, image_project)["diskSizeGb"]) + blank_data_disk_size_gb) disk_args = self._GetDiskArgs( instance, image_name, image_project, boot_disk_size_gb) # Transitional metadata variable as outlined in go/cuttlefish-deployment # These metadata tell the host instance to fetch and launch one # cuttlefish device (cvd-01). Ideally we should use a separate tool to # manage CVD devices on the host instance and not through metadata. # TODO(b/77626419): Remove these metadata once the # cuttlefish-google.service is turned off on the host instance. metadata = self._metadata.copy() metadata["cvd_01_fetch_android_build_target"] = build_target metadata["cvd_01_fetch_android_bid"] = "{branch}/{build_id}".format( branch=branch, build_id=build_id) if kernel_branch and kernel_build_id: metadata["cvd_01_fetch_kernel_bid"] = "{branch}/{build_id}".format( branch=kernel_branch, build_id=kernel_build_id) metadata["cvd_01_launch"] = (self._launch_args if self._launch_args else "1") # The cuttlefish-google tools changed the usage of this cvd_01_launch # variable. For the local image, we remove the cvd_01_launch from # metadata to tell server not to launch cvd while instance is booted # up. if avd_spec and avd_spec.image_source == constants.IMAGE_SRC_LOCAL: metadata.pop("cvd_01_launch", None) if blank_data_disk_size_gb > 0: # Policy 'create_if_missing' would create a blank userdata disk if # missing. If already exist, reuse the disk. metadata["cvd_01_data_policy"] = self.DATA_POLICY_CREATE_IF_MISSING metadata["cvd_01_blank_data_disk_size"] = str( blank_data_disk_size_gb * 1024) metadata["user"] = getpass.getuser() # Update metadata by avd_spec # for legacy create_cf cmd, we will keep using resolution. # And always use avd_spec for acloud create cmd. # TODO(b/118406018): deprecate resolution config and use hw_proprty for # all create cmds. if avd_spec: metadata[constants.INS_KEY_AVD_TYPE] = avd_spec.avd_type metadata[constants.INS_KEY_AVD_FLAVOR] = avd_spec.flavor metadata["cvd_01_x_res"] = avd_spec.hw_property[constants.HW_X_RES] metadata["cvd_01_y_res"] = avd_spec.hw_property[constants.HW_Y_RES] metadata["cvd_01_dpi"] = avd_spec.hw_property[constants.HW_ALIAS_DPI] metadata["cvd_01_blank_data_disk_size"] = avd_spec.hw_property[ constants.HW_ALIAS_DISK] # Use another METADATA_DISPLAY to record resolution which will be # retrieved in acloud list cmd. We try not to use cvd_01_x_res # since cvd_01_xxx metadata is going to deprecated by cuttlefish. metadata[constants.INS_KEY_DISPLAY] = ("%sx%s (%s)" % ( avd_spec.hw_property[constants.HW_X_RES], avd_spec.hw_property[constants.HW_Y_RES], avd_spec.hw_property[constants.HW_ALIAS_DPI])) else: resolution = self._resolution.split("x") metadata["cvd_01_dpi"] = resolution[3] metadata["cvd_01_x_res"] = resolution[0] metadata["cvd_01_y_res"] = resolution[1] # Add per-instance ssh key if self._ssh_public_key_path: rsa = self._LoadSshPublicKey(self._ssh_public_key_path) logger.info("ssh_public_key_path is specified in config: %s, " "will add the key to the instance.", self._ssh_public_key_path) metadata["sshKeys"] = "%s:%s" % (getpass.getuser(), rsa) else: logger.warning( "ssh_public_key_path is not specified in config, " "only project-wide key will be effective.") # Add labels for giving the instances ability to be filter for # acloud list/delete cmds. labels = {constants.LABEL_CREATE_BY: getpass.getuser()} gcompute_client.ComputeClient.CreateInstance( self, instance=instance, image_name=image_name, image_project=image_project, disk_args=disk_args, metadata=metadata, machine_type=self._machine_type, network=self._network, zone=self._zone, labels=labels, extra_scopes=extra_scopes)