• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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