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