• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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"-target_directory={extract_path}",
144                          fetch_cvd_cert_arg])
145        # Android boolean parsing does not recognize capitalized True/False as valid
146        lowercase_enable_value = str(avd_spec.enable_fetch_local_caching).lower()
147        fetch_cvd_args.extend([f"-enable_caching={lowercase_enable_value}"])
148        fetch_cvd_args.extend(fetch_cvd_build_args)
149        logger.debug("Download images command: %s", fetch_cvd_args)
150        if not setup_common.PackageInstalled(constants.CUTTLEFISH_COMMOM_PKG):
151            raise errors.NoCuttlefishCommonInstalled(
152                "cuttlefish-common package is required to run cvd fetch")
153        try:
154            subprocess.check_call(fetch_cvd_args)
155        except subprocess.CalledProcessError as e:
156            raise errors.GetRemoteImageError(f"Fails to download images: {e}")
157
158        # Save the fetch cvd build args when the fetch command succeeds
159        with open(fetch_cvd_args_file, "w") as output:
160            output.write(fetch_cvd_args_str)
161
162    return extract_path
163
164
165def ConfirmDownloadRemoteImageDir(download_dir):
166    """Confirm download remote image directory.
167
168    If available space of download_dir is less than _REQUIRED_SPACE, ask
169    the user to choose a different download dir or to exit out since acloud will
170    fail to download the artifacts due to insufficient disk space.
171
172    Args:
173        download_dir: String, a directory for download and decompress.
174
175    Returns:
176        String, Specific download directory when user confirm to change.
177    """
178    while True:
179        download_dir = os.path.expanduser(download_dir)
180        if not os.path.exists(download_dir):
181            answer = utils.InteractWithQuestion(
182                "No such directory %s.\nEnter 'y' to create it, enter "
183                "anything else to exit out[y/N]: " % download_dir)
184            if answer.lower() == "y":
185                os.makedirs(download_dir)
186            else:
187                sys.exit(constants.EXIT_BY_USER)
188
189        stat = os.statvfs(download_dir)
190        available_space = stat.f_bavail*stat.f_bsize/(1024)**3
191        if available_space < _REQUIRED_SPACE:
192            download_dir = utils.InteractWithQuestion(
193                _CONFIRM_DOWNLOAD_DIR % {"download_dir":download_dir,
194                                         "available_space":available_space,
195                                         "required_space":_REQUIRED_SPACE})
196            if download_dir.lower() == "q":
197                sys.exit(constants.EXIT_BY_USER)
198        else:
199            return download_dir
200
201
202class RemoteImageLocalInstance(local_image_local_instance.LocalImageLocalInstance):
203    """Create class for a remote image local instance AVD.
204
205    RemoteImageLocalInstance just defines logic in downloading the remote image
206    artifacts and leverages the existing logic to launch a local instance in
207    LocalImageLocalInstance.
208    """
209
210    # pylint: disable=too-many-locals
211    def GetImageArtifactsPath(self, avd_spec):
212        """Download the image artifacts and return the paths to them.
213
214        Args:
215            avd_spec: AVDSpec object that tells us what we're going to create.
216
217        Raises:
218            errors.NoCuttlefishCommonInstalled: cuttlefish-common doesn't install.
219
220        Returns:
221            local_image_local_instance.ArtifactPaths object.
222        """
223        if not setup_common.PackageInstalled("cuttlefish-common"):
224            raise errors.NoCuttlefishCommonInstalled(
225                "Package [cuttlefish-common] is not installed!\n"
226                "Please run 'acloud setup --host' to install.")
227
228        avd_spec.image_download_dir = ConfirmDownloadRemoteImageDir(
229            avd_spec.image_download_dir)
230
231        image_dir = DownloadAndProcessImageFiles(avd_spec)
232        launch_cvd_path = os.path.join(image_dir, "bin",
233                                       constants.CMD_LAUNCH_CVD)
234        if not os.path.exists(launch_cvd_path):
235            raise errors.GetCvdLocalHostPackageError(
236                "No launch_cvd found. Please check downloaded artifacts dir: %s"
237                % image_dir)
238
239        mix_image_dir = None
240        misc_info_path = None
241        ota_tools_dir = None
242        system_image_path = None
243        system_ext_image_path = None
244        product_image_path = None
245        boot_image_path = None
246        vendor_boot_image_path = None
247        kernel_image_path = None
248        initramfs_image_path = None
249        vendor_image_path = None
250        vendor_dlkm_image_path = None
251        odm_image_path = None
252        odm_dlkm_image_path = None
253        host_bins_path = image_dir
254        if avd_spec.local_system_image or avd_spec.local_vendor_image:
255            build_id = avd_spec.remote_image[constants.BUILD_ID]
256            build_target = avd_spec.remote_image[constants.BUILD_TARGET]
257            mix_image_dir = os.path.join(
258                image_dir, _SYSTEM_MIX_IMAGE_DIR.format(build_id=build_id))
259            if not os.path.exists(mix_image_dir):
260                os.makedirs(mix_image_dir)
261                create_common.DownloadRemoteArtifact(
262                    avd_spec.cfg, build_target, build_id,
263                    cvd_utils.GetMixBuildTargetFilename(
264                        build_target, build_id),
265                    mix_image_dir, decompress=True)
266            misc_info_path = cvd_utils.FindMiscInfo(mix_image_dir)
267            mix_image_dir = cvd_utils.FindImageDir(mix_image_dir)
268            tool_dirs = (avd_spec.local_tool_dirs +
269                         create_common.GetNonEmptyEnvVars(
270                             constants.ENV_ANDROID_SOONG_HOST_OUT,
271                             constants.ENV_ANDROID_HOST_OUT))
272            ota_tools_dir = os.path.abspath(
273                ota_tools.FindOtaToolsDir(tool_dirs))
274
275            # When using local vendor image, use cvd in local-tool if it
276            # exists. Fall back to downloaded tools in case it's missing
277
278            if avd_spec.local_vendor_image and avd_spec.local_tool_dirs:
279                try:
280                    host_bins_path = self._FindCvdHostBinaries(tool_dirs)
281                except errors.GetCvdLocalHostPackageError:
282                    logger.debug("fall back to downloaded cvd host binaries")
283
284        if avd_spec.local_system_image:
285            (
286                system_image_path,
287                system_ext_image_path,
288                product_image_path,
289            ) = create_common.FindSystemImages(avd_spec.local_system_image)
290
291        if avd_spec.local_kernel_image:
292            (
293                boot_image_path,
294                vendor_boot_image_path,
295                kernel_image_path,
296                initramfs_image_path,
297            ) = self.FindBootOrKernelImages(
298                os.path.abspath(avd_spec.local_kernel_image))
299
300        if avd_spec.local_vendor_boot_image:
301            vendor_boot_image_path = create_common.FindVendorBootImage(
302                avd_spec.local_vendor_boot_image)
303
304        if avd_spec.local_vendor_image:
305            vendor_image_paths = cvd_utils.FindVendorImages(
306                avd_spec.local_vendor_image)
307            vendor_image_path = vendor_image_paths.vendor
308            vendor_dlkm_image_path = vendor_image_paths.vendor_dlkm
309            odm_image_path = vendor_image_paths.odm
310            odm_dlkm_image_path = vendor_image_paths.odm_dlkm
311
312        return local_image_local_instance.ArtifactPaths(
313            image_dir=mix_image_dir or image_dir,
314            host_bins=host_bins_path,
315            host_artifacts=image_dir,
316            misc_info=misc_info_path,
317            ota_tools_dir=ota_tools_dir,
318            system_image=system_image_path,
319            system_ext_image=system_ext_image_path,
320            product_image=product_image_path,
321            vendor_image=vendor_image_path,
322            vendor_dlkm_image=vendor_dlkm_image_path,
323            odm_image=odm_image_path,
324            odm_dlkm_image=odm_dlkm_image_path,
325            boot_image=boot_image_path,
326            vendor_boot_image=vendor_boot_image_path,
327            kernel_image=kernel_image_path,
328            initramfs_image=initramfs_image_path)
329