1# Copyright 2019 - The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""RemoteInstanceDeviceFactory provides basic interface to create a cuttlefish 16device factory.""" 17 18import logging 19import os 20import shutil 21import subprocess 22import tempfile 23 24from acloud import errors 25from acloud.create import create_common 26from acloud.internal import constants 27from acloud.internal.lib import cvd_utils 28from acloud.internal.lib import utils 29from acloud.public.actions import gce_device_factory 30from acloud.pull import pull 31 32 33logger = logging.getLogger(__name__) 34 35 36class RemoteInstanceDeviceFactory(gce_device_factory.GCEDeviceFactory): 37 """A class that can produce a cuttlefish device. 38 39 Attributes: 40 avd_spec: AVDSpec object that tells us what we're going to create. 41 cfg: An AcloudConfig instance. 42 local_image_artifact: A string, path to local image. 43 cvd_host_package_artifact: A string, path to cvd host package. 44 report_internal_ip: Boolean, True for the internal ip is used when 45 connecting from another GCE instance. 46 credentials: An oauth2client.OAuth2Credentials instance. 47 compute_client: An object of cvd_compute_client.CvdComputeClient. 48 ssh: An Ssh object. 49 """ 50 def __init__(self, avd_spec, local_image_artifact=None, 51 cvd_host_package_artifact=None): 52 super().__init__(avd_spec, local_image_artifact) 53 self._all_logs = {} 54 self._cvd_host_package_artifact = cvd_host_package_artifact 55 56 # pylint: disable=broad-except 57 def CreateInstance(self): 58 """Create a single configured cuttlefish device. 59 60 Returns: 61 A string, representing instance name. 62 """ 63 instance = self.CreateGceInstance() 64 # If instance is failed, no need to go next step. 65 if instance in self.GetFailures(): 66 return instance 67 try: 68 image_args = self._ProcessArtifacts() 69 failures = self._compute_client.LaunchCvd( 70 instance, self._avd_spec, cvd_utils.GCE_BASE_DIR, image_args) 71 for failing_instance, error_msg in failures.items(): 72 self._SetFailures(failing_instance, error_msg) 73 except Exception as e: 74 self._SetFailures(instance, e) 75 76 try: 77 self._FindLogFiles( 78 instance, 79 (instance in self.GetFailures() and 80 not self._avd_spec.no_pull_log)) 81 except (errors.SubprocessFail, errors.DeviceConnectionError, 82 subprocess.CalledProcessError, subprocess.TimeoutExpired) as e: 83 logger.error("Fail to find log files: %s", e) 84 85 return instance 86 87 def _ProcessArtifacts(self): 88 """Process artifacts. 89 90 - If images source is local, tool will upload images from local site to 91 remote instance. 92 - If images source is remote, tool will download images from android 93 build to remote instance. Before download images, we have to update 94 fetch_cvd to remote instance. 95 96 Returns: 97 A list of strings, the launch_cvd arguments. 98 """ 99 avd_spec = self._avd_spec 100 launch_cvd_args = [] 101 temp_dir = None 102 try: 103 target_files_dir = None 104 if cvd_utils.AreTargetFilesRequired(avd_spec): 105 temp_dir = tempfile.mkdtemp(prefix="acloud_remote_ins") 106 target_files_dir = self._GetLocalTargetFilesDir(temp_dir) 107 108 if avd_spec.image_source == constants.IMAGE_SRC_LOCAL: 109 cvd_utils.UploadArtifacts( 110 self._ssh, cvd_utils.GCE_BASE_DIR, 111 (target_files_dir or self._local_image_artifact or 112 avd_spec.local_image_dir), 113 self._cvd_host_package_artifact) 114 elif avd_spec.image_source == constants.IMAGE_SRC_REMOTE: 115 self._compute_client.FetchBuild( 116 avd_spec.remote_image, 117 avd_spec.system_build_info, 118 avd_spec.kernel_build_info, 119 avd_spec.boot_build_info, 120 avd_spec.bootloader_build_info, 121 avd_spec.android_efi_loader_build_info, 122 avd_spec.ota_build_info, 123 avd_spec.host_package_build_info) 124 125 launch_cvd_args += cvd_utils.UploadExtraImages( 126 self._ssh, cvd_utils.GCE_BASE_DIR, avd_spec, target_files_dir) 127 finally: 128 if temp_dir: 129 shutil.rmtree(temp_dir) 130 131 if avd_spec.mkcert and avd_spec.connect_webrtc: 132 self._compute_client.UpdateCertificate() 133 134 if avd_spec.extra_files: 135 self._compute_client.UploadExtraFiles(avd_spec.extra_files) 136 137 return [arg for arg_pair in launch_cvd_args for arg in arg_pair] 138 139 @utils.TimeExecute(function_description="Downloading target_files archive") 140 def _DownloadTargetFiles(self, download_dir): 141 """Download target_files zip to a directory. 142 143 Args: 144 download_dir: The directory to which the zip is downloaded. 145 146 Returns: 147 The path to the target_files zip. 148 """ 149 avd_spec = self._avd_spec 150 build_id = avd_spec.remote_image[constants.BUILD_ID] 151 build_target = avd_spec.remote_image[constants.BUILD_TARGET] 152 name = cvd_utils.GetMixBuildTargetFilename(build_target, build_id) 153 create_common.DownloadRemoteArtifact(avd_spec.cfg, build_target, 154 build_id, name, download_dir) 155 return os.path.join(download_dir, name) 156 157 def _GetLocalTargetFilesDir(self, temp_dir): 158 """Return a directory of extracted target_files or local images. 159 160 Args: 161 temp_dir: Temporary directory to store downloaded build artifacts 162 and extracted target_files archive. 163 164 Returns: 165 The path to the target_files directory. 166 """ 167 avd_spec = self._avd_spec 168 if avd_spec.image_source == constants.IMAGE_SRC_LOCAL: 169 if self._local_image_artifact: 170 target_files_zip = self._local_image_artifact 171 target_files_dir = os.path.join(temp_dir, "local_images") 172 else: 173 return os.path.abspath(avd_spec.local_image_dir) 174 else: # must be IMAGE_SRC_REMOTE 175 target_files_zip = self._DownloadTargetFiles(temp_dir) 176 target_files_dir = os.path.join(temp_dir, "remote_images") 177 178 os.makedirs(target_files_dir, exist_ok=True) 179 cvd_utils.ExtractTargetFilesZip(target_files_zip, target_files_dir) 180 return target_files_dir 181 182 def _FindLogFiles(self, instance, download): 183 """Find and pull all log files from instance. 184 185 Args: 186 instance: String, instance name. 187 download: Whether to download the files to a temporary directory 188 and show messages to the user. 189 """ 190 logs = [cvd_utils.HOST_KERNEL_LOG] 191 if self._avd_spec.image_source == constants.IMAGE_SRC_REMOTE: 192 logs.append( 193 cvd_utils.GetRemoteFetcherConfigJson(cvd_utils.GCE_BASE_DIR)) 194 logs.extend(cvd_utils.FindRemoteLogs( 195 self._ssh, 196 cvd_utils.GCE_BASE_DIR, 197 self._avd_spec.base_instance_num, 198 self._avd_spec.num_avds_per_instance)) 199 self._all_logs[instance] = logs 200 201 if download: 202 # To avoid long download time, fetch from the first device only. 203 log_files = pull.GetAllLogFilePaths(self._ssh, 204 constants.REMOTE_LOG_FOLDER) 205 error_log_folder = pull.PullLogs(self._ssh, log_files, instance) 206 self._compute_client.ExtendReportData(constants.ERROR_LOG_FOLDER, 207 error_log_folder) 208 209 def GetOpenWrtInfoDict(self): 210 """Get openwrt info dictionary. 211 212 Returns: 213 A openwrt info dictionary. None for the case is not openwrt device. 214 """ 215 if not self._avd_spec.openwrt: 216 return None 217 return cvd_utils.GetOpenWrtInfoDict(self._ssh, cvd_utils.GCE_BASE_DIR) 218 219 def GetAdbPorts(self): 220 """Get ADB ports of the created devices. 221 222 Returns: 223 The port numbers as a list of integers. 224 """ 225 return cvd_utils.GetAdbPorts(self._avd_spec.base_instance_num, 226 self._avd_spec.num_avds_per_instance) 227 228 def GetVncPorts(self): 229 """Get VNC ports of the created devices. 230 231 Returns: 232 The port numbers as a list of integers. 233 """ 234 return cvd_utils.GetVncPorts(self._avd_spec.base_instance_num, 235 self._avd_spec.num_avds_per_instance) 236 237 def GetBuildInfoDict(self): 238 """Get build info dictionary. 239 240 Returns: 241 A build info dictionary. None for local image case. 242 """ 243 if self._avd_spec.image_source == constants.IMAGE_SRC_LOCAL: 244 return None 245 return cvd_utils.GetRemoteBuildInfoDict(self._avd_spec) 246 247 def GetLogs(self): 248 """Get all device logs. 249 250 Returns: 251 A dictionary that maps instance names to lists of report.LogFile. 252 """ 253 return self._all_logs 254