• 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 subprocess
24import sys
25
26from acloud import errors
27from acloud.create import local_image_local_instance
28from acloud.internal import constants
29from acloud.internal.lib import android_build_client
30from acloud.internal.lib import auth
31from acloud.internal.lib import utils
32from acloud.setup import setup_common
33
34
35logger = logging.getLogger(__name__)
36
37# Download remote image variables.
38_CUTTLEFISH_COMMON_BIN_PATH = "/usr/lib/cuttlefish-common/bin/"
39_CONFIRM_DOWNLOAD_DIR = ("Download dir %(download_dir)s does not have enough "
40                         "space (available space %(available_space)sGB, "
41                         "require %(required_space)sGB).\nPlease enter "
42                         "alternate path or 'q' to exit: ")
43_HOME_FOLDER = os.path.expanduser("~")
44# The downloaded image artifacts will take up ~8G:
45#   $du -lh --time $ANDROID_PRODUCT_OUT/aosp_cf_x86_phone-img-eng.XXX.zip
46#   422M
47# And decompressed becomes 7.2G (as of 11/2018).
48# Let's add an extra buffer (~2G) to make sure user has enough disk space
49# for the downloaded image artifacts.
50_REQUIRED_SPACE = 10
51
52
53@utils.TimeExecute(function_description="Downloading Android Build image")
54def DownloadAndProcessImageFiles(avd_spec):
55    """Download the CF image artifacts and process them.
56
57    To download rom images, Acloud would download the tool fetch_cvd that can
58    help process mixed build images.
59
60    Args:
61        avd_spec: AVDSpec object that tells us what we're going to create.
62
63    Returns:
64        extract_path: String, path to image folder.
65
66    Raises:
67        errors.GetRemoteImageError: Fails to download rom images.
68    """
69    cfg = avd_spec.cfg
70    build_id = avd_spec.remote_image[constants.BUILD_ID]
71    build_branch = avd_spec.remote_image[constants.BUILD_BRANCH]
72    build_target = avd_spec.remote_image[constants.BUILD_TARGET]
73
74    extract_path = os.path.join(
75        avd_spec.image_download_dir,
76        constants.TEMP_ARTIFACTS_FOLDER,
77        build_id + build_target)
78
79    logger.debug("Extract path: %s", extract_path)
80    # TODO(b/117189191): If extract folder exists, check if the files are
81    # already downloaded and skip this step if they are.
82    if not os.path.exists(extract_path):
83        os.makedirs(extract_path)
84        build_api = (
85            android_build_client.AndroidBuildClient(auth.CreateCredentials(cfg)))
86
87        # Download rom images via fetch_cvd
88        fetch_cvd = os.path.join(extract_path, constants.FETCH_CVD)
89        build_api.DownloadFetchcvd(fetch_cvd, cfg.fetch_cvd_version)
90        fetch_cvd_build_args = build_api.GetFetchBuildArgs(
91            build_id, build_branch, build_target,
92            avd_spec.system_build_info.get(constants.BUILD_ID),
93            avd_spec.system_build_info.get(constants.BUILD_BRANCH),
94            avd_spec.system_build_info.get(constants.BUILD_TARGET),
95            avd_spec.kernel_build_info.get(constants.BUILD_ID),
96            avd_spec.kernel_build_info.get(constants.BUILD_BRANCH),
97            avd_spec.kernel_build_info.get(constants.BUILD_TARGET),
98            avd_spec.bootloader_build_info.get(constants.BUILD_ID),
99            avd_spec.bootloader_build_info.get(constants.BUILD_BRANCH),
100            avd_spec.bootloader_build_info.get(constants.BUILD_TARGET))
101        creds_cache_file = os.path.join(_HOME_FOLDER, cfg.creds_cache_file)
102        fetch_cvd_cert_arg = build_api.GetFetchCertArg(creds_cache_file)
103        fetch_cvd_args = [fetch_cvd, "-directory=%s" % extract_path,
104                          fetch_cvd_cert_arg]
105        fetch_cvd_args.extend(fetch_cvd_build_args)
106        logger.debug("Download images command: %s", fetch_cvd_args)
107        try:
108            subprocess.check_call(fetch_cvd_args)
109        except subprocess.CalledProcessError as e:
110            raise errors.GetRemoteImageError("Fails to download images: %s" % e)
111
112    return extract_path
113
114
115def ConfirmDownloadRemoteImageDir(download_dir):
116    """Confirm download remote image directory.
117
118    If available space of download_dir is less than _REQUIRED_SPACE, ask
119    the user to choose a different download dir or to exit out since acloud will
120    fail to download the artifacts due to insufficient disk space.
121
122    Args:
123        download_dir: String, a directory for download and decompress.
124
125    Returns:
126        String, Specific download directory when user confirm to change.
127    """
128    while True:
129        download_dir = os.path.expanduser(download_dir)
130        if not os.path.exists(download_dir):
131            answer = utils.InteractWithQuestion(
132                "No such directory %s.\nEnter 'y' to create it, enter "
133                "anything else to exit out[y/N]: " % download_dir)
134            if answer.lower() == "y":
135                os.makedirs(download_dir)
136            else:
137                sys.exit(constants.EXIT_BY_USER)
138
139        stat = os.statvfs(download_dir)
140        available_space = stat.f_bavail*stat.f_bsize/(1024)**3
141        if available_space < _REQUIRED_SPACE:
142            download_dir = utils.InteractWithQuestion(
143                _CONFIRM_DOWNLOAD_DIR % {"download_dir":download_dir,
144                                         "available_space":available_space,
145                                         "required_space":_REQUIRED_SPACE})
146            if download_dir.lower() == "q":
147                sys.exit(constants.EXIT_BY_USER)
148        else:
149            return download_dir
150
151
152class RemoteImageLocalInstance(local_image_local_instance.LocalImageLocalInstance):
153    """Create class for a remote image local instance AVD.
154
155    RemoteImageLocalInstance just defines logic in downloading the remote image
156    artifacts and leverages the existing logic to launch a local instance in
157    LocalImageLocalInstance.
158    """
159
160    def GetImageArtifactsPath(self, avd_spec):
161        """Download the image artifacts and return the paths to them.
162
163        Args:
164            avd_spec: AVDSpec object that tells us what we're going to create.
165
166        Raises:
167            errors.NoCuttlefishCommonInstalled: cuttlefish-common doesn't install.
168
169        Returns:
170            local_image_local_instance.ArtifactPaths object.
171        """
172        if not setup_common.PackageInstalled("cuttlefish-common"):
173            raise errors.NoCuttlefishCommonInstalled(
174                "Package [cuttlefish-common] is not installed!\n"
175                "Please run 'acloud setup --host' to install.")
176
177        avd_spec.image_download_dir = ConfirmDownloadRemoteImageDir(
178            avd_spec.image_download_dir)
179
180        image_dir = DownloadAndProcessImageFiles(avd_spec)
181        launch_cvd_path = os.path.join(image_dir, "bin",
182                                       constants.CMD_LAUNCH_CVD)
183        if not os.path.exists(launch_cvd_path):
184            raise errors.GetCvdLocalHostPackageError(
185                "No launch_cvd found. Please check downloaded artifacts dir: %s"
186                % image_dir)
187        # This method does not set the optional fields because launch_cvd loads
188        # the paths from the fetcher config in image_dir.
189        return local_image_local_instance.ArtifactPaths(
190            image_dir, image_dir, None, None, None, None)
191