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