xref: /aosp_15_r20/tools/acloud/create/remote_image_local_instance.py (revision 800a58d989c669b8eb8a71d8df53b1ba3d411444)
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"""RemoteImageLocalInstance class.
17
18Create class that is responsible for creating a local instance AVD with a
19remote image.
20"""
21import logging
22import os
23import shutil
24import subprocess
25import sys
26
27from acloud import errors
28from acloud.create import create_common
29from acloud.create import local_image_local_instance
30from acloud.internal import constants
31from acloud.internal.lib import android_build_client
32from acloud.internal.lib import auth
33from acloud.internal.lib import cvd_utils
34from acloud.internal.lib import ota_tools
35from acloud.internal.lib import utils
36from acloud.setup import setup_common
37
38
39logger = logging.getLogger(__name__)
40
41# Download remote image variables.
42_CUTTLEFISH_COMMON_BIN_PATH = "/usr/lib/cuttlefish-common/bin/"
43_CONFIRM_DOWNLOAD_DIR = ("Download dir %(download_dir)s does not have enough "
44                         "space (available space %(available_space)sGB, "
45                         "require %(required_space)sGB).\nPlease enter "
46                         "alternate path or 'q' to exit: ")
47_HOME_FOLDER = os.path.expanduser("~")
48# The downloaded image artifacts will take up ~8G:
49#   $du -lh --time $ANDROID_PRODUCT_OUT/aosp_cf_x86_phone-img-eng.XXX.zip
50#   422M
51# And decompressed becomes 7.2G (as of 11/2018).
52# Let's add an extra buffer (~2G) to make sure user has enough disk space
53# for the downloaded image artifacts.
54_REQUIRED_SPACE = 10
55
56_SYSTEM_MIX_IMAGE_DIR = "mix_image_{build_id}"
57
58
59def _ShouldClearFetchDir(fetch_dir, avd_spec, fetch_cvd_args_str,
60                         fetch_cvd_args_file):
61    """Check if the previous fetch directory should be removed.
62
63    The fetch directory would be removed when the user explicitly sets the
64    --force-sync flag, or when the fetch_cvd_args_str changed.
65
66    Args:
67        fetch_dir: String, path to the fetch directory.
68        avd_spec: AVDSpec object that tells us what we're going to create.
69        fetch_cvd_args_str: String, args for current fetch_cvd command.
70        fetch_cvd_args_file: String, path to file of previous fetch_cvd args.
71
72    Returns:
73        Boolean. True if the fetch directory should be removed.
74    """
75    if not os.path.exists(fetch_dir):
76        return False
77    if avd_spec.force_sync:
78        return True
79
80    if not os.path.exists(fetch_cvd_args_file):
81        return True
82    with open(fetch_cvd_args_file, "r") as f:
83        return fetch_cvd_args_str != f.read()
84
85
86@utils.TimeExecute(function_description="Downloading Android Build image")
87def DownloadAndProcessImageFiles(avd_spec):
88    """Download the CF image artifacts and process them.
89
90    To download rom images, Acloud would download the tool fetch_cvd that can
91    help process mixed build images, and store the fetch_cvd_build_args in
92    FETCH_CVD_ARGS_FILE when the fetch command executes successfully. Next
93    time when this function is called with the same image_download_dir, the
94    FETCH_CVD_ARGS_FILE would be used to check whether this image_download_dir
95    can be reused or not.
96
97    Args:
98        avd_spec: AVDSpec object that tells us what we're going to create.
99
100    Returns:
101        extract_path: String, path to image folder.
102
103    Raises:
104        errors.GetRemoteImageError: Fails to download rom images.
105    """
106    cfg = avd_spec.cfg
107    build_id = avd_spec.remote_image[constants.BUILD_ID]
108    build_target = avd_spec.remote_image[constants.BUILD_TARGET]
109
110    extract_path = os.path.join(
111        avd_spec.image_download_dir,
112        constants.TEMP_ARTIFACTS_FOLDER,
113        build_id + build_target)
114
115    logger.debug("Extract path: %s", extract_path)
116
117    build_api = (
118        android_build_client.AndroidBuildClient(auth.CreateCredentials(cfg)))
119    fetch_cvd_build_args = build_api.GetFetchBuildArgs(
120        avd_spec.remote_image,
121        avd_spec.system_build_info,
122        avd_spec.kernel_build_info,
123        avd_spec.boot_build_info,
124        avd_spec.bootloader_build_info,
125        avd_spec.android_efi_loader_build_info,
126        avd_spec.ota_build_info,
127        avd_spec.host_package_build_info)
128
129    fetch_cvd_args_str = " ".join(fetch_cvd_build_args)
130    fetch_cvd_args_file = os.path.join(extract_path,
131                                       constants.FETCH_CVD_ARGS_FILE)
132    if _ShouldClearFetchDir(extract_path, avd_spec, fetch_cvd_args_str,
133                            fetch_cvd_args_file):
134        shutil.rmtree(extract_path)
135
136    if not os.path.exists(extract_path):
137        os.makedirs(extract_path)
138
139        # Download rom images via cvd fetch
140        fetch_cvd_args = list(constants.CMD_CVD_FETCH)
141        creds_cache_file = os.path.join(_HOME_FOLDER, cfg.creds_cache_file)
142        fetch_cvd_cert_arg = build_api.GetFetchCertArg(creds_cache_file)
143        fetch_cvd_args.extend([f"-directory={extract_path}",
144                          fetch_cvd_cert_arg])
145        fetch_cvd_args.extend(fetch_cvd_build_args)
146        logger.debug("Download images command: %s", fetch_cvd_args)
147        if not setup_common.PackageInstalled(constants.CUTTLEFISH_COMMOM_PKG):
148            raise errors.NoCuttlefishCommonInstalled(
149                "cuttlefish-common package is required to run cvd fetch")
150        try:
151            subprocess.check_call(fetch_cvd_args)
152        except subprocess.CalledProcessError as e:
153            raise errors.GetRemoteImageError(f"Fails to download images: {e}")
154
155        # Save the fetch cvd build args when the fetch command succeeds
156        with open(fetch_cvd_args_file, "w") as output:
157            output.write(fetch_cvd_args_str)
158
159    return extract_path
160
161
162def ConfirmDownloadRemoteImageDir(download_dir):
163    """Confirm download remote image directory.
164
165    If available space of download_dir is less than _REQUIRED_SPACE, ask
166    the user to choose a different download dir or to exit out since acloud will
167    fail to download the artifacts due to insufficient disk space.
168
169    Args:
170        download_dir: String, a directory for download and decompress.
171
172    Returns:
173        String, Specific download directory when user confirm to change.
174    """
175    while True:
176        download_dir = os.path.expanduser(download_dir)
177        if not os.path.exists(download_dir):
178            answer = utils.InteractWithQuestion(
179                "No such directory %s.\nEnter 'y' to create it, enter "
180                "anything else to exit out[y/N]: " % download_dir)
181            if answer.lower() == "y":
182                os.makedirs(download_dir)
183            else:
184                sys.exit(constants.EXIT_BY_USER)
185
186        stat = os.statvfs(download_dir)
187        available_space = stat.f_bavail*stat.f_bsize/(1024)**3
188        if available_space < _REQUIRED_SPACE:
189            download_dir = utils.InteractWithQuestion(
190                _CONFIRM_DOWNLOAD_DIR % {"download_dir":download_dir,
191                                         "available_space":available_space,
192                                         "required_space":_REQUIRED_SPACE})
193            if download_dir.lower() == "q":
194                sys.exit(constants.EXIT_BY_USER)
195        else:
196            return download_dir
197
198
199class RemoteImageLocalInstance(local_image_local_instance.LocalImageLocalInstance):
200    """Create class for a remote image local instance AVD.
201
202    RemoteImageLocalInstance just defines logic in downloading the remote image
203    artifacts and leverages the existing logic to launch a local instance in
204    LocalImageLocalInstance.
205    """
206
207    # pylint: disable=too-many-locals
208    def GetImageArtifactsPath(self, avd_spec):
209        """Download the image artifacts and return the paths to them.
210
211        Args:
212            avd_spec: AVDSpec object that tells us what we're going to create.
213
214        Raises:
215            errors.NoCuttlefishCommonInstalled: cuttlefish-common doesn't install.
216
217        Returns:
218            local_image_local_instance.ArtifactPaths object.
219        """
220        if not setup_common.PackageInstalled("cuttlefish-common"):
221            raise errors.NoCuttlefishCommonInstalled(
222                "Package [cuttlefish-common] is not installed!\n"
223                "Please run 'acloud setup --host' to install.")
224
225        avd_spec.image_download_dir = ConfirmDownloadRemoteImageDir(
226            avd_spec.image_download_dir)
227
228        image_dir = DownloadAndProcessImageFiles(avd_spec)
229        launch_cvd_path = os.path.join(image_dir, "bin",
230                                       constants.CMD_LAUNCH_CVD)
231        if not os.path.exists(launch_cvd_path):
232            raise errors.GetCvdLocalHostPackageError(
233                "No launch_cvd found. Please check downloaded artifacts dir: %s"
234                % image_dir)
235
236        mix_image_dir = None
237        misc_info_path = None
238        ota_tools_dir = None
239        system_image_path = None
240        system_ext_image_path = None
241        product_image_path = None
242        boot_image_path = None
243        vendor_boot_image_path = None
244        kernel_image_path = None
245        initramfs_image_path = None
246        vendor_image_path = None
247        vendor_dlkm_image_path = None
248        odm_image_path = None
249        odm_dlkm_image_path = None
250        host_bins_path = image_dir
251        if avd_spec.local_system_image or avd_spec.local_vendor_image:
252            build_id = avd_spec.remote_image[constants.BUILD_ID]
253            build_target = avd_spec.remote_image[constants.BUILD_TARGET]
254            mix_image_dir = os.path.join(
255                image_dir, _SYSTEM_MIX_IMAGE_DIR.format(build_id=build_id))
256            if not os.path.exists(mix_image_dir):
257                os.makedirs(mix_image_dir)
258                create_common.DownloadRemoteArtifact(
259                    avd_spec.cfg, build_target, build_id,
260                    cvd_utils.GetMixBuildTargetFilename(
261                        build_target, build_id),
262                    mix_image_dir, decompress=True)
263            misc_info_path = cvd_utils.FindMiscInfo(mix_image_dir)
264            mix_image_dir = cvd_utils.FindImageDir(mix_image_dir)
265            tool_dirs = (avd_spec.local_tool_dirs +
266                         create_common.GetNonEmptyEnvVars(
267                             constants.ENV_ANDROID_SOONG_HOST_OUT,
268                             constants.ENV_ANDROID_HOST_OUT))
269            ota_tools_dir = os.path.abspath(
270                ota_tools.FindOtaToolsDir(tool_dirs))
271
272            # When using local vendor image, use cvd in local-tool if it
273            # exists. Fall back to downloaded tools in case it's missing
274
275            if avd_spec.local_vendor_image and avd_spec.local_tool_dirs:
276                try:
277                    host_bins_path = self._FindCvdHostBinaries(tool_dirs)
278                except errors.GetCvdLocalHostPackageError:
279                    logger.debug("fall back to downloaded cvd host binaries")
280
281        if avd_spec.local_system_image:
282            (
283                system_image_path,
284                system_ext_image_path,
285                product_image_path,
286            ) = create_common.FindSystemImages(avd_spec.local_system_image)
287
288        if avd_spec.local_kernel_image:
289            (
290                boot_image_path,
291                vendor_boot_image_path,
292                kernel_image_path,
293                initramfs_image_path,
294            ) = self.FindBootOrKernelImages(
295                os.path.abspath(avd_spec.local_kernel_image))
296
297        if avd_spec.local_vendor_boot_image:
298            vendor_boot_image_path = create_common.FindVendorBootImage(
299                avd_spec.local_vendor_boot_image)
300
301        if avd_spec.local_vendor_image:
302            vendor_image_paths = cvd_utils.FindVendorImages(
303                avd_spec.local_vendor_image)
304            vendor_image_path = vendor_image_paths.vendor
305            vendor_dlkm_image_path = vendor_image_paths.vendor_dlkm
306            odm_image_path = vendor_image_paths.odm
307            odm_dlkm_image_path = vendor_image_paths.odm_dlkm
308
309        return local_image_local_instance.ArtifactPaths(
310            image_dir=mix_image_dir or image_dir,
311            host_bins=host_bins_path,
312            host_artifacts=image_dir,
313            misc_info=misc_info_path,
314            ota_tools_dir=ota_tools_dir,
315            system_image=system_image_path,
316            system_ext_image=system_ext_image_path,
317            product_image=product_image_path,
318            vendor_image=vendor_image_path,
319            vendor_dlkm_image=vendor_dlkm_image_path,
320            odm_image=odm_image_path,
321            odm_dlkm_image=odm_dlkm_image_path,
322            boot_image=boot_image_path,
323            vendor_boot_image=vendor_boot_image_path,
324            kernel_image=kernel_image_path,
325            initramfs_image=initramfs_image_path)
326