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. 16r"""LocalImageLocalInstance class. 17 18Create class that is responsible for creating a local instance AVD with a 19local image. For launching multiple local instances under the same user, 20The cuttlefish tool requires 3 variables: 21- ANDROID_HOST_OUT: To locate the launch_cvd tool. 22- HOME: To specify the temporary folder of launch_cvd. 23- CUTTLEFISH_INSTANCE: To specify the instance id. 24Acloud user must either set ANDROID_HOST_OUT or run acloud with --local-tool. 25Acloud sets the other 2 variables for each local instance. 26 27The adb port and vnc port of local instance will be decided according to 28instance id. The rule of adb port will be '6520 + [instance id] - 1' and the vnc 29port will be '6444 + [instance id] - 1'. 30e.g: 31If instance id = 3 the adb port will be 6522 and vnc port will be 6446. 32 33To delete the local instance, we will call stop_cvd with the environment variable 34[CUTTLEFISH_CONFIG_FILE] which is pointing to the runtime cuttlefish json. 35""" 36 37import logging 38import os 39import shutil 40import subprocess 41import threading 42import sys 43 44from acloud import errors 45from acloud.create import base_avd_create 46from acloud.internal import constants 47from acloud.internal.lib import utils 48from acloud.internal.lib.adb_tools import AdbTools 49from acloud.list import list as list_instance 50from acloud.list import instance 51from acloud.public import report 52 53 54logger = logging.getLogger(__name__) 55 56_CMD_LAUNCH_CVD_ARGS = (" -daemon -cpus %s -x_res %s -y_res %s -dpi %s " 57 "-memory_mb %s -run_adb_connector=%s " 58 "-system_image_dir %s -instance_dir %s " 59 "-undefok=report_anonymous_usage_stats,enable_sandbox " 60 "-report_anonymous_usage_stats=y " 61 "-enable_sandbox=false") 62_CMD_LAUNCH_CVD_GPU_ARG = " -gpu_mode=drm_virgl" 63_CMD_LAUNCH_CVD_DISK_ARGS = (" -blank_data_image_mb %s " 64 "-data_policy always_create") 65_CMD_LAUNCH_CVD_WEBRTC_ARGS = (" -guest_enforce_security=false " 66 "-vm_manager=crosvm " 67 "-start_webrtc=true " 68 "-webrtc_public_ip=%s" % constants.LOCALHOST) 69_CONFIRM_RELAUNCH = ("\nCuttlefish AVD[id:%d] is already running. \n" 70 "Enter 'y' to terminate current instance and launch a new " 71 "instance, enter anything else to exit out[y/N]: ") 72_LAUNCH_CVD_TIMEOUT_ERROR = ("Cuttlefish AVD launch timeout, did not complete " 73 "within %d secs.") 74_VIRTUAL_DISK_PATHS = "virtual_disk_paths" 75 76 77class LocalImageLocalInstance(base_avd_create.BaseAVDCreate): 78 """Create class for a local image local instance AVD.""" 79 80 @utils.TimeExecute(function_description="Total time: ", 81 print_before_call=False, print_status=False) 82 def _CreateAVD(self, avd_spec, no_prompts): 83 """Create the AVD. 84 85 Args: 86 avd_spec: AVDSpec object that tells us what we're going to create. 87 no_prompts: Boolean, True to skip all prompts. 88 89 Raises: 90 errors.LaunchCVDFail: Launch AVD failed. 91 92 Returns: 93 A Report instance. 94 """ 95 # Running instances on local is not supported on all OS. 96 if not utils.IsSupportedPlatform(print_warning=True): 97 result_report = report.Report(command="create") 98 result_report.SetStatus(report.Status.FAIL) 99 return result_report 100 101 local_image_path, host_bins_path = self.GetImageArtifactsPath(avd_spec) 102 103 launch_cvd_path = os.path.join(host_bins_path, "bin", 104 constants.CMD_LAUNCH_CVD) 105 cmd = self.PrepareLaunchCVDCmd(launch_cvd_path, 106 avd_spec.hw_property, 107 avd_spec.connect_adb, 108 local_image_path, 109 avd_spec.local_instance_id, 110 avd_spec.connect_webrtc, 111 avd_spec.gpu) 112 113 result_report = report.Report(command="create") 114 instance_name = instance.GetLocalInstanceName( 115 avd_spec.local_instance_id) 116 try: 117 self.CheckLaunchCVD( 118 cmd, host_bins_path, avd_spec.local_instance_id, 119 local_image_path, avd_spec.connect_webrtc, no_prompts, 120 avd_spec.boot_timeout_secs or constants.DEFAULT_CF_BOOT_TIMEOUT) 121 except errors.LaunchCVDFail as launch_error: 122 result_report.SetStatus(report.Status.BOOT_FAIL) 123 result_report.AddDeviceBootFailure( 124 instance_name, constants.LOCALHOST, None, None, 125 error=str(launch_error)) 126 return result_report 127 128 active_ins = list_instance.GetActiveCVD(avd_spec.local_instance_id) 129 if active_ins: 130 result_report.SetStatus(report.Status.SUCCESS) 131 result_report.AddDevice(instance_name, constants.LOCALHOST, 132 active_ins.adb_port, active_ins.vnc_port) 133 # Launch vnc client if we're auto-connecting. 134 if avd_spec.connect_vnc: 135 utils.LaunchVNCFromReport(result_report, avd_spec, no_prompts) 136 if avd_spec.connect_webrtc: 137 utils.LaunchBrowserFromReport(result_report) 138 if avd_spec.unlock_screen: 139 AdbTools(active_ins.adb_port).AutoUnlockScreen() 140 else: 141 err_msg = "cvd_status return non-zero after launch_cvd" 142 logger.error(err_msg) 143 result_report.SetStatus(report.Status.BOOT_FAIL) 144 result_report.AddDeviceBootFailure( 145 instance_name, constants.LOCALHOST, None, None, error=err_msg) 146 return result_report 147 148 @staticmethod 149 def _FindCvdHostBinaries(search_paths): 150 """Return the directory that contains CVD host binaries.""" 151 for search_path in search_paths: 152 if os.path.isfile(os.path.join(search_path, "bin", 153 constants.CMD_LAUNCH_CVD)): 154 return search_path 155 156 host_out_dir = os.environ.get(constants.ENV_ANDROID_HOST_OUT) 157 if (host_out_dir and 158 os.path.isfile(os.path.join(host_out_dir, "bin", 159 constants.CMD_LAUNCH_CVD))): 160 return host_out_dir 161 162 raise errors.GetCvdLocalHostPackageError( 163 "CVD host binaries are not found. Please run `make hosttar`, or " 164 "set --local-tool to an extracted CVD host package.") 165 166 def GetImageArtifactsPath(self, avd_spec): 167 """Get image artifacts path. 168 169 This method will check if launch_cvd is exist and return the tuple path 170 (image path and host bins path) where they are located respectively. 171 For remote image, RemoteImageLocalInstance will override this method and 172 return the artifacts path which is extracted and downloaded from remote. 173 174 Args: 175 avd_spec: AVDSpec object that tells us what we're going to create. 176 177 Returns: 178 Tuple of (local image file, host bins package) paths. 179 """ 180 return (avd_spec.local_image_dir, 181 self._FindCvdHostBinaries(avd_spec.local_tool_dirs)) 182 183 @staticmethod 184 def PrepareLaunchCVDCmd(launch_cvd_path, hw_property, connect_adb, 185 system_image_dir, local_instance_id, connect_webrtc, 186 gpu): 187 """Prepare launch_cvd command. 188 189 Create the launch_cvd commands with all the required args and add 190 in the user groups to it if necessary. 191 192 Args: 193 launch_cvd_path: String of launch_cvd path. 194 hw_property: dict object of hw property. 195 system_image_dir: String of local images path. 196 connect_adb: Boolean flag that enables adb_connector. 197 local_instance_id: Integer of instance id. 198 connect_webrtc: Boolean of connect_webrtc. 199 gpu: String of gpu name, the gpu name of local instance should be 200 "default" if gpu is enabled. 201 202 Returns: 203 String, launch_cvd cmd. 204 """ 205 instance_dir = instance.GetLocalInstanceRuntimeDir(local_instance_id) 206 launch_cvd_w_args = launch_cvd_path + _CMD_LAUNCH_CVD_ARGS % ( 207 hw_property["cpu"], hw_property["x_res"], hw_property["y_res"], 208 hw_property["dpi"], hw_property["memory"], 209 ("true" if connect_adb else "false"), system_image_dir, 210 instance_dir) 211 if constants.HW_ALIAS_DISK in hw_property: 212 launch_cvd_w_args = (launch_cvd_w_args + _CMD_LAUNCH_CVD_DISK_ARGS % 213 hw_property[constants.HW_ALIAS_DISK]) 214 if connect_webrtc: 215 launch_cvd_w_args = launch_cvd_w_args + _CMD_LAUNCH_CVD_WEBRTC_ARGS 216 217 if gpu: 218 launch_cvd_w_args = launch_cvd_w_args + _CMD_LAUNCH_CVD_GPU_ARG 219 220 launch_cmd = utils.AddUserGroupsToCmd(launch_cvd_w_args, 221 constants.LIST_CF_USER_GROUPS) 222 logger.debug("launch_cvd cmd:\n %s", launch_cmd) 223 return launch_cmd 224 225 def CheckLaunchCVD(self, cmd, host_bins_path, local_instance_id, 226 local_image_path, connect_webrtc=False, no_prompts=False, 227 timeout_secs=constants.DEFAULT_CF_BOOT_TIMEOUT): 228 """Execute launch_cvd command and wait for boot up completed. 229 230 1. Check if the provided image files are in use by any launch_cvd process. 231 2. Check if launch_cvd with the same instance id is running. 232 3. Launch local AVD. 233 234 Args: 235 cmd: String, launch_cvd command. 236 host_bins_path: String of host package directory. 237 local_instance_id: Integer of instance id. 238 local_image_path: String of local image directory. 239 connect_webrtc: Boolean, whether to auto connect webrtc to device. 240 no_prompts: Boolean, True to skip all prompts. 241 timeout_secs: Integer, the number of seconds to wait for the AVD to boot up. 242 """ 243 # launch_cvd assumes host bins are in $ANDROID_HOST_OUT, let's overwrite 244 # it to wherever we're running launch_cvd since they could be in a 245 # different dir (e.g. downloaded image). 246 os.environ[constants.ENV_ANDROID_HOST_OUT] = host_bins_path 247 # Check if the instance with same id is running. 248 existing_ins = list_instance.GetActiveCVD(local_instance_id) 249 if existing_ins: 250 if no_prompts or utils.GetUserAnswerYes(_CONFIRM_RELAUNCH % 251 local_instance_id): 252 existing_ins.Delete() 253 else: 254 sys.exit(constants.EXIT_BY_USER) 255 else: 256 # Image files can't be shared among instances, so check if any running 257 # launch_cvd process is using this path. 258 occupied_ins_id = self.IsLocalImageOccupied(local_image_path) 259 if occupied_ins_id: 260 utils.PrintColorString( 261 "The image path[%s] is already used by current running AVD" 262 "[id:%d]\nPlease choose another path to launch local " 263 "instance." % (local_image_path, occupied_ins_id), 264 utils.TextColors.FAIL) 265 sys.exit(constants.EXIT_BY_USER) 266 if connect_webrtc: 267 utils.ReleasePort(constants.WEBRTC_LOCAL_PORT) 268 self._LaunchCvd(cmd, local_instance_id, timeout=timeout_secs) 269 270 @staticmethod 271 @utils.TimeExecute(function_description="Waiting for AVD(s) to boot up") 272 def _LaunchCvd(cmd, local_instance_id, timeout=None): 273 """Execute Launch CVD. 274 275 Kick off the launch_cvd command and log the output. 276 277 Args: 278 cmd: String, launch_cvd command. 279 local_instance_id: Integer of instance id. 280 timeout: Integer, the number of seconds to wait for the AVD to boot up. 281 282 Raises: 283 errors.LaunchCVDFail when any CalledProcessError. 284 """ 285 # Delete the cvd home/runtime temp if exist. The runtime folder is 286 # under the cvd home dir, so we only delete them from home dir. 287 cvd_home_dir = instance.GetLocalInstanceHomeDir(local_instance_id) 288 cvd_runtime_dir = instance.GetLocalInstanceRuntimeDir(local_instance_id) 289 shutil.rmtree(cvd_home_dir, ignore_errors=True) 290 os.makedirs(cvd_runtime_dir) 291 292 cvd_env = os.environ.copy() 293 cvd_env[constants.ENV_CVD_HOME] = cvd_home_dir 294 cvd_env[constants.ENV_CUTTLEFISH_INSTANCE] = str(local_instance_id) 295 # Check the result of launch_cvd command. 296 # An exit code of 0 is equivalent to VIRTUAL_DEVICE_BOOT_COMPLETED 297 process = subprocess.Popen(cmd, shell=True, stderr=subprocess.STDOUT, 298 env=cvd_env) 299 if timeout: 300 timer = threading.Timer(timeout, process.kill) 301 timer.start() 302 process.wait() 303 if timeout: 304 timer.cancel() 305 if process.returncode == 0: 306 return 307 raise errors.LaunchCVDFail( 308 "Can't launch cuttlefish AVD. Return code:%s. \nFor more detail: " 309 "%s/launcher.log" % (str(process.returncode), cvd_runtime_dir)) 310 311 @staticmethod 312 def IsLocalImageOccupied(local_image_dir): 313 """Check if the given image path is being used by a running CVD process. 314 315 Args: 316 local_image_dir: String, path of local image. 317 318 Return: 319 Integer of instance id which using the same image path. 320 """ 321 # TODO(149602560): Remove occupied image checking after after cf disk 322 # overlay is stable 323 for cf_runtime_config_path in instance.GetAllLocalInstanceConfigs(): 324 ins = instance.LocalInstance(cf_runtime_config_path) 325 if ins.CvdStatus(): 326 for disk_path in ins.virtual_disk_paths: 327 if local_image_dir in disk_path: 328 return ins.instance_id 329 return None 330