1# Copyright 2024 - 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 Trusty 16device factory.""" 17 18import json 19import logging 20import os 21import posixpath as remote_path 22import shlex 23import tempfile 24import traceback 25 26from acloud import errors 27from acloud.create import create_common 28from acloud.internal import constants 29from acloud.internal.lib import cvd_utils 30from acloud.internal.lib import utils 31from acloud.public import report 32from acloud.public.actions import gce_device_factory 33from acloud.pull import pull 34 35 36logger = logging.getLogger(__name__) 37_CONFIG_JSON_FILENAME = "config.json" 38 39# log files under REMOTE_LOG_FOLDER in order to 40# enable `acloud pull` to retrieve them 41_REMOTE_LOG_FOLDER = constants.REMOTE_LOG_FOLDER 42_REMOTE_STDOUT_PATH = f"{_REMOTE_LOG_FOLDER}/kernel.log" 43_REMOTE_STDERR_PATH = f"{_REMOTE_LOG_FOLDER}/qemu_trusty_err.log" 44 45# below Trusty image archive is generated by 46# branch:aosp-trusty-main / target: qemu_generic_arm64_gicv3* targets 47_TRUSTY_MANIFEST_TRUSTY_IMAGE_PACKAGE = "trusty_image_package.tar.gz" 48 49# below Trusty image archive is generated by: 50# branch: git_main-throttled-nightly / target: qemu_trusty_arm64 51_PLATFORM_MANIFEST_TRUSTY_IMAGE_PACKAGE = "trusty_tee_package_goog.tar.gz" 52 53# below Trusty image archive is generated by: 54# aosp developers for --local-image usage 55_PLATFORM_MANIFEST_TRUSTY_IMAGE_PACKAGE_LOCAL = "trusty_tee_package.tar.gz" 56 57# below Host tools archive is generated by: 58# branch: git_main-throttled-nightly / target: qemu_trusty_arm64 59_TRUSTY_HOST_PACKAGE_DIR = "trusty-host_package" 60_TRUSTY_HOST_TARBALL = "trusty-host_package.tar.gz" 61 62# Default Trusty image build. This does not depend on the android branch. 63_DEFAULT_TRUSTY_BUILD_BRANCH = "aosp-trusty-main" 64_DEFAULT_TRUSTY_BUILD_TARGET = "qemu_generic_arm64_gicv3_test_debug" 65 66 67def _TrustyImagePackageFilename(build_target): 68 trusty_target = build_target.replace("_", "-") 69 return f"{trusty_target}.{_TRUSTY_MANIFEST_TRUSTY_IMAGE_PACKAGE}" 70 71 72def _FindHostPackage(package_path=None): 73 if package_path: 74 # checked in create_args._VerifyTrustyArgs 75 return package_path 76 dirs_to_check = create_common.GetNonEmptyEnvVars( 77 constants.ENV_ANDROID_SOONG_HOST_OUT, constants.ENV_ANDROID_HOST_OUT 78 ) 79 dist_dir = utils.GetDistDir() 80 if dist_dir: 81 dirs_to_check.append(dist_dir) 82 83 for path in dirs_to_check: 84 for name in [_TRUSTY_HOST_TARBALL, _TRUSTY_HOST_PACKAGE_DIR]: 85 trusty_host_package = os.path.join(path, name) 86 if os.path.exists(trusty_host_package): 87 return trusty_host_package 88 raise errors.GetTrustyLocalHostPackageError( 89 "Can't find the trusty host package (Try lunching a trusty target " 90 "like qemu_trusty_arm64-trunk_staging-userdebug and running 'm'): \n" 91 + "\n".join(dirs_to_check) 92 ) 93 94 95def _FindTrustyImagePackage(): 96 dist_dir = utils.GetDistDir() 97 if dist_dir: 98 for name in [ 99 _PLATFORM_MANIFEST_TRUSTY_IMAGE_PACKAGE, 100 _PLATFORM_MANIFEST_TRUSTY_IMAGE_PACKAGE_LOCAL, 101 ]: 102 trusty_image_package = os.path.join(dist_dir, name) 103 if os.path.exists(trusty_image_package): 104 return trusty_image_package 105 raise errors.GetTrustyLocalImagePackageError( 106 "Can't find the trusty image package (Try lunching a trusty target " 107 "like qemu_trusty_arm64-trunk_staging-userdebug and running 'm dist trusty-tee_package')" 108 ) 109 110 111class RemoteInstanceDeviceFactory(gce_device_factory.GCEDeviceFactory): 112 """A class that can produce a Trusty device.""" 113 114 def __init__(self, avd_spec, local_android_image_artifact=None): 115 super().__init__(avd_spec, local_android_image_artifact) 116 self._all_logs = {} 117 118 # pylint: disable=broad-except 119 def CreateInstance(self): 120 """Create and start a single Trusty instance. 121 122 Returns: 123 The instance name as a string. 124 """ 125 instance = self.CreateGceInstance() 126 if instance in self.GetFailures(): 127 return instance 128 129 try: 130 self._ProcessArtifacts() 131 self._StartTrusty() 132 except Exception as e: 133 self._SetFailures(instance, traceback.format_exception(e)) 134 135 self._FindLogFiles( 136 instance, instance in self.GetFailures() and not self._avd_spec.no_pull_log 137 ) 138 return instance 139 140 def _ProcessArtifacts(self): 141 """Process artifacts. 142 143 - If images source is local, tool will upload images from local site to 144 remote instance. 145 - If images source is remote, tool will download images from android 146 build to remote instance. 147 """ 148 avd_spec = self._avd_spec 149 if avd_spec.image_source == constants.IMAGE_SRC_LOCAL: 150 host_package_artifact = _FindHostPackage( 151 avd_spec.trusty_host_package 152 ) 153 cvd_utils.UploadArtifacts( 154 self._ssh, 155 cvd_utils.GCE_BASE_DIR, 156 (self._local_image_artifact or avd_spec.local_image_dir), 157 host_package_artifact, 158 ) 159 elif avd_spec.image_source == constants.IMAGE_SRC_REMOTE: 160 self._FetchBuild() 161 if self._compute_client.build_api.GetKernelBuild( 162 avd_spec.kernel_build_info 163 ): 164 self._ReplaceModules() 165 else: 166 # fetch the kernel image from the android build artifacts 167 self._FetchAndUploadKernelImage() 168 if avd_spec.local_trusty_image: 169 self._UploadBuildArchive(avd_spec.local_trusty_image) 170 elif avd_spec.image_source == constants.IMAGE_SRC_LOCAL: 171 local_trusty_image = _FindTrustyImagePackage() 172 self._UploadBuildArchive(local_trusty_image) 173 else: 174 self._FetchAndUploadTrustyImages() 175 176 config = { 177 "linux": "kernel", 178 "linux_arch": "arm64", 179 "initrd": "ramdisk.img", 180 "atf": "atf/qemu/debug", 181 "qemu": "bin/trusty_qemu_system_aarch64", 182 "extra_qemu_flags": ["-machine", "gic-version=3"], 183 "android_image_dir": ".", 184 "rpmbd": "bin/rpmb_dev", 185 "arch": "arm64", 186 "adb": "bin/adb", 187 } 188 with tempfile.NamedTemporaryFile(mode="w+t") as config_json_file: 189 json.dump(config, config_json_file) 190 config_json_file.flush() 191 remote_config_path = remote_path.join( 192 cvd_utils.GCE_BASE_DIR, _CONFIG_JSON_FILENAME 193 ) 194 self._ssh.ScpPushFile(config_json_file.name, remote_config_path) 195 196 # We are building our own command-line instead of using 197 # self._compute_client.FetchBuild() because we need to use the host cvd 198 # tool rather than `fetch_cvd`. The downloaded fetch_cvd tool is too 199 # old and cannot handle a custom host package filename. This can be 200 # removed when b/298447306 is fixed. 201 @utils.TimeExecute(function_description="Fetching builds") 202 def _FetchBuild(self): 203 """Fetch builds from android build server.""" 204 avd_spec = self._avd_spec 205 build_client = self._compute_client.build_api 206 207 # Provide the default trusty host package artifact filename. We must 208 # explicitly use the default build id/branch and target for the host 209 # package if those values were not set for the host package so that we 210 # can override the artifact filename. 211 host_package = avd_spec.host_package_build_info.copy() 212 if not ( 213 host_package[constants.BUILD_ID] or host_package[constants.BUILD_BRANCH] 214 ): 215 host_package[constants.BUILD_ID] = avd_spec.remote_image[constants.BUILD_ID] 216 host_package[constants.BUILD_BRANCH] = avd_spec.remote_image[ 217 constants.BUILD_BRANCH 218 ] 219 if not host_package[constants.BUILD_TARGET]: 220 host_package[constants.BUILD_TARGET] = avd_spec.remote_image[ 221 constants.BUILD_TARGET 222 ] 223 host_package.setdefault(constants.BUILD_ARTIFACT, _TRUSTY_HOST_TARBALL) 224 225 fetch_args = build_client.GetFetchBuildArgs( 226 avd_spec.remote_image, 227 {}, 228 avd_spec.kernel_build_info, 229 {}, 230 {}, 231 {}, 232 {}, 233 host_package, 234 ) 235 fetch_cmd = constants.CMD_CVD_FETCH + ["-credential_source=gce"] + fetch_args 236 self._ssh.Run(" ".join(fetch_cmd), timeout=constants.DEFAULT_SSH_TIMEOUT) 237 238 def _ReplaceModules(self): 239 """Replace modules in android ramdisk with modules from the kernel build""" 240 android_ramdisk = remote_path.join(cvd_utils.GCE_BASE_DIR, "ramdisk.img") 241 kernel_ramdisk = remote_path.join(cvd_utils.GCE_BASE_DIR, "initramfs.img") 242 # We are switching to the bin/ directory so host tools are in the 243 # current directory for python to find. 244 self._ssh.Run( 245 f"cd {cvd_utils.GCE_BASE_DIR}/bin && ./replace_ramdisk_modules " 246 f"--android-ramdisk={android_ramdisk} " 247 f"--kernel-ramdisk={kernel_ramdisk} " 248 f"--output-ramdisk={android_ramdisk}", 249 timeout=constants.DEFAULT_SSH_TIMEOUT, 250 ) 251 252 @utils.TimeExecute(function_description="Fetching & Uploading Trusty image") 253 def _FetchAndUploadTrustyImages(self): 254 """Fetch Trusty image archive from ab, Upload to GCE""" 255 build_client = self._compute_client.build_api 256 trusty_build_info = self._avd_spec.trusty_build_info 257 if trusty_build_info[constants.BUILD_BRANCH]: 258 build_id = trusty_build_info[constants.BUILD_ID] 259 build_branch = trusty_build_info[constants.BUILD_BRANCH] 260 build_target = ( 261 trusty_build_info[constants.BUILD_TARGET] 262 or _DEFAULT_TRUSTY_BUILD_TARGET 263 ) 264 if not build_id: 265 build_id = build_client.GetLKGB(build_target, build_branch) 266 trusty_image_package = _TrustyImagePackageFilename(build_target) 267 else: 268 # if Trusty build_branch not specified, use the android build branch 269 # get the Trusty image package from the android platform manifest 270 android_build_info = self._avd_spec.remote_image 271 build_id = android_build_info[constants.BUILD_ID] 272 build_branch = android_build_info[constants.BUILD_BRANCH] 273 build_target = android_build_info[constants.BUILD_TARGET] 274 trusty_image_package = _PLATFORM_MANIFEST_TRUSTY_IMAGE_PACKAGE 275 with tempfile.NamedTemporaryFile(suffix=".tar.gz") as image_local_file: 276 image_local_path = image_local_file.name 277 build_client.DownloadArtifact( 278 build_target, 279 build_id, 280 trusty_image_package, 281 image_local_path, 282 ) 283 self._UploadBuildArchive(image_local_path) 284 285 @utils.TimeExecute(function_description="Fetching & Uploading Kernel Image") 286 def _FetchAndUploadKernelImage(self): 287 """Fetch Kernel image from ab, Upload to GCE""" 288 build_client = self._compute_client.build_api 289 android_build_info = self._avd_spec.remote_image 290 build_id = android_build_info[constants.BUILD_ID] 291 build_target = android_build_info[constants.BUILD_TARGET] 292 with tempfile.NamedTemporaryFile(prefix="kernel") as image_local_file: 293 image_local_path = image_local_file.name 294 logger.debug('DownloadArtifact "kernel" to %s\n', image_local_path) 295 ret = build_client.DownloadArtifact( 296 build_target, 297 build_id, 298 "kernel", 299 image_local_path, 300 ) 301 logger.debug("DownloadArtifact to %s Returned %d\n", image_local_path, ret) 302 self._ssh.ScpPushFile(image_local_path, f"{cvd_utils.GCE_BASE_DIR}/kernel") 303 logger.debug( 304 "ScpPushFile from %s to %s\n", 305 image_local_path, 306 f"{cvd_utils.GCE_BASE_DIR}/kernel", 307 ) 308 309 def _UploadBuildArchive(self, archive_path): 310 """Upload Build Artifact (Trusty images archive or Kernel image)""" 311 remote_cmd = f"tar -xzf - -C {cvd_utils.GCE_BASE_DIR} < " + archive_path 312 logger.debug("remote_cmd:\n %s", remote_cmd) 313 self._ssh.Run(remote_cmd) 314 315 @utils.TimeExecute(function_description="Starting Trusty") 316 def _StartTrusty(self): 317 """Start the model on the GCE instance.""" 318 self._ssh.Run(f"mkdir -p {_REMOTE_LOG_FOLDER}") 319 320 # We use an explicit subshell so we can run this command in the 321 # background. 322 cmd = "-- sh -c " + shlex.quote( 323 shlex.quote( 324 f"{cvd_utils.GCE_BASE_DIR}/run.py " 325 f"--verbose --config={_CONFIG_JSON_FILENAME} " 326 f"{self._avd_spec.launch_args} " 327 f"> {_REMOTE_STDOUT_PATH} " 328 f"2> {_REMOTE_STDERR_PATH} &" 329 ) 330 ) 331 self._ssh.Run(cmd, self._avd_spec.boot_timeout_secs or 30, retry=0) 332 333 def _FindLogFiles(self, instance, download): 334 """Find and pull all log files from instance. 335 336 Args: 337 instance: String, instance name. 338 download: Whether to download the files to a temporary directory 339 and show messages to the user. 340 """ 341 logs = [cvd_utils.HOST_KERNEL_LOG] 342 if self._avd_spec.image_source == constants.IMAGE_SRC_REMOTE: 343 logs.append(cvd_utils.GetRemoteFetcherConfigJson(cvd_utils.GCE_BASE_DIR)) 344 logs.append(report.LogFile(_REMOTE_STDOUT_PATH, constants.LOG_TYPE_KERNEL_LOG)) 345 logs.append(report.LogFile(_REMOTE_STDERR_PATH, constants.LOG_TYPE_TEXT)) 346 self._all_logs[instance] = logs 347 348 logger.debug("logs: %s", logs) 349 if download: 350 # To avoid long download time, fetch from the first device only. 351 log_paths = [log["path"] for log in logs] 352 error_log_folder = pull.PullLogs(self._ssh, log_paths, instance) 353 self._compute_client.ExtendReportData( 354 constants.ERROR_LOG_FOLDER, error_log_folder 355 ) 356 357 def GetLogs(self): 358 """Get all device logs. 359 360 Returns: 361 A dictionary that maps instance names to lists of report.LogFile. 362 """ 363 return self._all_logs 364