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 sys 24 25from acloud import errors 26from acloud.create import create_common 27from acloud.create import local_image_local_instance 28from acloud.internal import constants 29from acloud.internal.lib import utils 30from acloud.setup import setup_common 31 32 33logger = logging.getLogger(__name__) 34 35# Download remote image variables. 36_CUTTLEFISH_COMMON_BIN_PATH = "/usr/lib/cuttlefish-common/bin/" 37_CONFIRM_DOWNLOAD_DIR = ("Download dir %(download_dir)s does not have enough " 38 "space (available space %(available_space)sGB, " 39 "require %(required_space)sGB).\nPlease enter " 40 "alternate path or 'q' to exit: ") 41# The downloaded image artifacts will take up ~8G: 42# $du -lh --time $ANDROID_PRODUCT_OUT/aosp_cf_x86_phone-img-eng.XXX.zip 43# 422M 44# And decompressed becomes 7.2G (as of 11/2018). 45# Let's add an extra buffer (~2G) to make sure user has enough disk space 46# for the downloaded image artifacts. 47_REQUIRED_SPACE = 10 48 49 50@utils.TimeExecute(function_description="Downloading Android Build image") 51def DownloadAndProcessImageFiles(avd_spec): 52 """Download the CF image artifacts and process them. 53 54 It will download two artifacts and process them in this function. One is 55 cvd_host_package.tar.gz, the other is rom image zip. If the build_id is 56 "1234" and build_target is "aosp_cf_x86_phone-userdebug", 57 the image zip name is "aosp_cf_x86_phone-img-1234.zip". 58 59 Args: 60 avd_spec: AVDSpec object that tells us what we're going to create. 61 62 Returns: 63 extract_path: String, path to image folder. 64 """ 65 cfg = avd_spec.cfg 66 build_id = avd_spec.remote_image[constants.BUILD_ID] 67 build_target = avd_spec.remote_image[constants.BUILD_TARGET] 68 69 extract_path = os.path.join( 70 avd_spec.image_download_dir, 71 constants.TEMP_ARTIFACTS_FOLDER, 72 build_id) 73 74 logger.debug("Extract path: %s", extract_path) 75 # TODO(b/117189191): If extract folder exists, check if the files are 76 # already downloaded and skip this step if they are. 77 if not os.path.exists(extract_path): 78 os.makedirs(extract_path) 79 remote_image = "%s-img-%s.zip" % (build_target.split('-')[0], 80 build_id) 81 artifacts = [constants.CVD_HOST_PACKAGE, remote_image] 82 for artifact in artifacts: 83 create_common.DownloadRemoteArtifact( 84 cfg, build_target, build_id, artifact, extract_path, decompress=True) 85 return extract_path 86 87 88def ConfirmDownloadRemoteImageDir(download_dir): 89 """Confirm download remote image directory. 90 91 If available space of download_dir is less than _REQUIRED_SPACE, ask 92 the user to choose a different download dir or to exit out since acloud will 93 fail to download the artifacts due to insufficient disk space. 94 95 Args: 96 download_dir: String, a directory for download and decompress. 97 98 Returns: 99 String, Specific download directory when user confirm to change. 100 """ 101 while True: 102 download_dir = os.path.expanduser(download_dir) 103 if not os.path.exists(download_dir): 104 answer = utils.InteractWithQuestion( 105 "No such directory %s.\nEnter 'y' to create it, enter " 106 "anything else to exit out[y/N]: " % download_dir) 107 if answer.lower() == "y": 108 os.makedirs(download_dir) 109 else: 110 sys.exit(constants.EXIT_BY_USER) 111 112 stat = os.statvfs(download_dir) 113 available_space = stat.f_bavail*stat.f_bsize/(1024)**3 114 if available_space < _REQUIRED_SPACE: 115 download_dir = utils.InteractWithQuestion( 116 _CONFIRM_DOWNLOAD_DIR % {"download_dir":download_dir, 117 "available_space":available_space, 118 "required_space":_REQUIRED_SPACE}) 119 if download_dir.lower() == "q": 120 sys.exit(constants.EXIT_BY_USER) 121 else: 122 return download_dir 123 124 125class RemoteImageLocalInstance(local_image_local_instance.LocalImageLocalInstance): 126 """Create class for a remote image local instance AVD. 127 128 RemoteImageLocalInstance just defines logic in downloading the remote image 129 artifacts and leverages the existing logic to launch a local instance in 130 LocalImageLocalInstance. 131 """ 132 133 def GetImageArtifactsPath(self, avd_spec): 134 """Download the image artifacts and return the paths to them. 135 136 Args: 137 avd_spec: AVDSpec object that tells us what we're going to create. 138 139 Raises: 140 errors.NoCuttlefishCommonInstalled: cuttlefish-common doesn't install. 141 142 Returns: 143 Tuple of (local image file, host bins package) paths. 144 """ 145 if not setup_common.PackageInstalled("cuttlefish-common"): 146 raise errors.NoCuttlefishCommonInstalled( 147 "Package [cuttlefish-common] is not installed!\n" 148 "Please run 'acloud setup --host' to install.") 149 150 avd_spec.image_download_dir = ConfirmDownloadRemoteImageDir( 151 avd_spec.image_download_dir) 152 153 image_dir = DownloadAndProcessImageFiles(avd_spec) 154 launch_cvd_path = os.path.join(image_dir, "bin", 155 constants.CMD_LAUNCH_CVD) 156 if not os.path.exists(launch_cvd_path): 157 raise errors.GetCvdLocalHostPackageError( 158 "No launch_cvd found. Please check downloaded artifacts dir: %s" 159 % image_dir) 160 return image_dir, image_dir 161