blob: 4b71723378a1af508b604eced1ffa75e594a2b67 [file] [log] [blame]
#!/usr/bin/env python
#
# Copyright 2018 - The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
r"""RemoteImageLocalInstance class.
Create class that is responsible for creating a local instance AVD with a
remote image.
"""
from __future__ import print_function
import logging
import os
import subprocess
import tempfile
from acloud import errors
from acloud.create import local_image_local_instance
from acloud.internal import constants
from acloud.internal.lib import android_build_client
from acloud.internal.lib import auth
from acloud.internal.lib import utils
from acloud.setup import setup_common
# Download remote image variables.
_CVD_HOST_PACKAGE = "cvd-host_package.tar.gz"
_CUTTLEFISH_COMMON_BIN_PATH = "/usr/lib/cuttlefish-common/bin/"
_TEMP_IMAGE_FOLDER = os.path.join(tempfile.gettempdir(),
"acloud_image_artifacts", "cuttlefish")
_CF_IMAGES = ["cache.img", "cmdline", "kernel", "ramdisk.img", "system.img",
"userdata.img", "vendor.img"]
_BOOT_IMAGE = "boot.img"
UNPACK_BOOTIMG_CMD = "%s -boot_img %s" % (
os.path.join(_CUTTLEFISH_COMMON_BIN_PATH, "unpack_boot_image.py"),
"%s -dest %s")
ACL_CMD = "setfacl -m g:libvirt-qemu:rw %s"
logger = logging.getLogger(__name__)
class RemoteImageLocalInstance(local_image_local_instance.LocalImageLocalInstance):
"""Create class for a remote image local instance AVD.
RemoteImageLocalInstance just defines logic in downloading the remote image
artifacts and leverages the existing logic to launch a local instance in
LocalImageLocalInstance.
"""
@utils.TimeExecute(function_description="Downloading Android Build image")
def GetImageArtifactsPath(self, avd_spec):
"""Download the image artifacts and return the paths to them.
Args:
avd_spec: AVDSpec object that tells us what we're going to create.
Raises:
errors.NoCuttlefishCommonInstalled: cuttlefish-common doesn't install.
Returns:
Tuple of (local image file, launch_cvd package) paths.
"""
if not setup_common.PackageInstalled("cuttlefish-common"):
raise errors.NoCuttlefishCommonInstalled(
"Package [cuttlefish-common] is not installed!\n"
"Please run 'acloud setup --host' to install.")
image_dir = self._DownloadAndProcessImageFiles(avd_spec)
launch_cvd_path = os.path.join(image_dir, "bin", constants.CMD_LAUNCH_CVD)
return image_dir, launch_cvd_path
def _DownloadAndProcessImageFiles(self, avd_spec):
"""Download the CF image artifacts and process them.
Download from the Android Build system, unpack the boot img file,
and ACL the image files.
Args:
avd_spec: AVDSpec object that tells us what we're going to create.
Returns:
extract_path: String, path to image folder.
"""
cfg = avd_spec.cfg
build_id = avd_spec.remote_image[constants.BUILD_ID]
build_target = avd_spec.remote_image[constants.BUILD_TARGET]
extract_path = os.path.join(_TEMP_IMAGE_FOLDER, build_id)
logger.debug("Extract path: %s", extract_path)
# TODO(b/117189191): If extract folder exists, check if the files are
# already downloaded and skip this step if they are.
if not os.path.exists(extract_path):
os.makedirs(extract_path)
self._DownloadRemoteImage(cfg, build_target, build_id, extract_path)
self._UnpackBootImage(extract_path)
self._AclCfImageFiles(extract_path)
return extract_path
@staticmethod
def _DownloadRemoteImage(cfg, build_target, build_id, extract_path):
"""Download cuttlefish package and remote image then extract them.
Args:
cfg: An AcloudConfig instance.
build_target: String, the build target, e.g. cf_x86_phone-userdebug.
build_id: String, Build id, e.g. "2263051", "P2804227"
extract_path: String, a path include extracted files.
"""
remote_image = "%s-img-%s.zip" % (build_target.split('-')[0],
build_id)
artifacts = [_CVD_HOST_PACKAGE, remote_image]
build_client = android_build_client.AndroidBuildClient(
auth.CreateCredentials(cfg))
for artifact in artifacts:
with utils.TempDir() as tempdir:
temp_filename = os.path.join(tempdir, artifact)
build_client.DownloadArtifact(
build_target,
build_id,
artifact,
temp_filename)
utils.Decompress(temp_filename, extract_path)
@staticmethod
def _UnpackBootImage(extract_path):
"""Unpack Boot.img.
Args:
extract_path: String, a path include extracted files.
Raises:
errors.BootImgDoesNotExist: boot.img doesn't exist.
errors.UnpackBootImageError: Unpack boot.img fail.
"""
bootimg_path = os.path.join(extract_path, _BOOT_IMAGE)
if not os.path.exists(bootimg_path):
raise errors.BootImgDoesNotExist(
"%s does not exist in %s" % (_BOOT_IMAGE, bootimg_path))
logger.info("Start to unpack boot.img.")
try:
subprocess.check_call(
UNPACK_BOOTIMG_CMD % (bootimg_path, extract_path),
shell=True)
except subprocess.CalledProcessError as e:
raise errors.UnpackBootImageError(
"Failed to unpack boot.img: %s" % str(e))
logger.info("Unpack boot.img complete!")
@staticmethod
def _AclCfImageFiles(extract_path):
"""ACL related files.
Use setfacl so that libvirt does not lose access to this file if user
does anything to this file at any point.
Args:
extract_path: String, a path include extracted files.
Raises:
errors.CheckPathError: Path doesn't exist.
"""
logger.info("Start to acl files: %s", ",".join(_CF_IMAGES))
for image in _CF_IMAGES:
image_path = os.path.join(extract_path, image)
if not os.path.exists(image_path):
raise errors.CheckPathError(
"Specified file doesn't exist: %s" % image_path)
subprocess.check_call(ACL_CMD % image_path, shell=True)
logger.info("ACL files completed!")