blob: ba618c1ab980a381c6a4bfc96d1382bd7aaccd1d [file] [log] [blame]
# Copyright 2019 - 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.
"""RemoteInstanceDeviceFactory provides basic interface to create a cuttlefish
device factory."""
import logging
import os
import shutil
import tempfile
from acloud.create import create_common
from acloud.internal import constants
from acloud.internal.lib import cvd_utils
from acloud.internal.lib import utils
from acloud.public.actions import gce_device_factory
from acloud.pull import pull
logger = logging.getLogger(__name__)
class RemoteInstanceDeviceFactory(gce_device_factory.GCEDeviceFactory):
"""A class that can produce a cuttlefish device.
Attributes:
avd_spec: AVDSpec object that tells us what we're going to create.
cfg: An AcloudConfig instance.
local_image_artifact: A string, path to local image.
cvd_host_package_artifact: A string, path to cvd host package.
report_internal_ip: Boolean, True for the internal ip is used when
connecting from another GCE instance.
credentials: An oauth2client.OAuth2Credentials instance.
compute_client: An object of cvd_compute_client.CvdComputeClient.
ssh: An Ssh object.
"""
def __init__(self, avd_spec, local_image_artifact=None,
cvd_host_package_artifact=None):
super().__init__(avd_spec, local_image_artifact)
self._all_logs = {}
self._cvd_host_package_artifact = cvd_host_package_artifact
# pylint: disable=broad-except
def CreateInstance(self):
"""Create a single configured cuttlefish device.
Returns:
A string, representing instance name.
"""
instance = self.CreateGceInstance()
# If instance is failed, no need to go next step.
if instance in self.GetFailures():
return instance
try:
image_args = self._ProcessArtifacts()
failures = self._compute_client.LaunchCvd(
instance, self._avd_spec, cvd_utils.GCE_BASE_DIR, image_args)
for failing_instance, error_msg in failures.items():
self._SetFailures(failing_instance, error_msg)
except Exception as e:
self._SetFailures(instance, e)
self._FindLogFiles(
instance,
instance in self.GetFailures() and not self._avd_spec.no_pull_log)
return instance
def _ProcessArtifacts(self):
"""Process artifacts.
- If images source is local, tool will upload images from local site to
remote instance.
- If images source is remote, tool will download images from android
build to remote instance. Before download images, we have to update
fetch_cvd to remote instance.
Returns:
A list of strings, the launch_cvd arguments.
"""
avd_spec = self._avd_spec
launch_cvd_args = []
temp_dir = None
try:
target_files_dir = None
if cvd_utils.AreTargetFilesRequired(avd_spec):
temp_dir = tempfile.mkdtemp(prefix="acloud_remote_ins")
target_files_dir = self._GetLocalTargetFilesDir(temp_dir)
if avd_spec.image_source == constants.IMAGE_SRC_LOCAL:
cvd_utils.UploadArtifacts(
self._ssh, cvd_utils.GCE_BASE_DIR,
(target_files_dir or self._local_image_artifact or
avd_spec.local_image_dir),
self._cvd_host_package_artifact)
elif avd_spec.image_source == constants.IMAGE_SRC_REMOTE:
self._compute_client.UpdateFetchCvd(avd_spec.fetch_cvd_version)
self._compute_client.FetchBuild(
avd_spec.remote_image,
avd_spec.system_build_info,
avd_spec.kernel_build_info,
avd_spec.boot_build_info,
avd_spec.bootloader_build_info,
avd_spec.android_efi_loader_build_info,
avd_spec.ota_build_info,
avd_spec.host_package_build_info)
launch_cvd_args += cvd_utils.UploadExtraImages(
self._ssh, cvd_utils.GCE_BASE_DIR, avd_spec, target_files_dir)
finally:
if temp_dir:
shutil.rmtree(temp_dir)
if avd_spec.mkcert and avd_spec.connect_webrtc:
self._compute_client.UpdateCertificate()
if avd_spec.extra_files:
self._compute_client.UploadExtraFiles(avd_spec.extra_files)
return [arg for arg_pair in launch_cvd_args for arg in arg_pair]
@utils.TimeExecute(function_description="Downloading target_files archive")
def _DownloadTargetFiles(self, download_dir):
"""Download target_files zip to a directory.
Args:
download_dir: The directory to which the zip is downloaded.
Returns:
The path to the target_files zip.
"""
avd_spec = self._avd_spec
build_id = avd_spec.remote_image[constants.BUILD_ID]
build_target = avd_spec.remote_image[constants.BUILD_TARGET]
name = cvd_utils.GetMixBuildTargetFilename(build_target, build_id)
create_common.DownloadRemoteArtifact(avd_spec.cfg, build_target,
build_id, name, download_dir)
return os.path.join(download_dir, name)
def _GetLocalTargetFilesDir(self, temp_dir):
"""Return a directory of extracted target_files or local images.
Args:
temp_dir: Temporary directory to store downloaded build artifacts
and extracted target_files archive.
Returns:
The path to the target_files directory.
"""
avd_spec = self._avd_spec
if avd_spec.image_source == constants.IMAGE_SRC_LOCAL:
if self._local_image_artifact:
target_files_zip = self._local_image_artifact
target_files_dir = os.path.join(temp_dir, "local_images")
else:
return os.path.abspath(avd_spec.local_image_dir)
else: # must be IMAGE_SRC_REMOTE
target_files_zip = self._DownloadTargetFiles(temp_dir)
target_files_dir = os.path.join(temp_dir, "remote_images")
os.makedirs(target_files_dir, exist_ok=True)
cvd_utils.ExtractTargetFilesZip(target_files_zip, target_files_dir)
return target_files_dir
def _FindLogFiles(self, instance, download):
"""Find and pull all log files from instance.
Args:
instance: String, instance name.
download: Whether to download the files to a temporary directory
and show messages to the user.
"""
logs = [cvd_utils.HOST_KERNEL_LOG]
if self._avd_spec.image_source == constants.IMAGE_SRC_REMOTE:
logs.append(
cvd_utils.GetRemoteFetcherConfigJson(cvd_utils.GCE_BASE_DIR))
logs.extend(cvd_utils.FindRemoteLogs(
self._ssh,
cvd_utils.GCE_BASE_DIR,
self._avd_spec.base_instance_num,
self._avd_spec.num_avds_per_instance))
self._all_logs[instance] = logs
if download:
# To avoid long download time, fetch from the first device only.
log_files = pull.GetAllLogFilePaths(self._ssh,
constants.REMOTE_LOG_FOLDER)
error_log_folder = pull.PullLogs(self._ssh, log_files, instance)
self._compute_client.ExtendReportData(constants.ERROR_LOG_FOLDER,
error_log_folder)
def GetOpenWrtInfoDict(self):
"""Get openwrt info dictionary.
Returns:
A openwrt info dictionary. None for the case is not openwrt device.
"""
if not self._avd_spec.openwrt:
return None
return cvd_utils.GetOpenWrtInfoDict(self._ssh, cvd_utils.GCE_BASE_DIR)
def GetAdbPorts(self):
"""Get ADB ports of the created devices.
Returns:
The port numbers as a list of integers.
"""
return cvd_utils.GetAdbPorts(self._avd_spec.base_instance_num,
self._avd_spec.num_avds_per_instance)
def GetVncPorts(self):
"""Get VNC ports of the created devices.
Returns:
The port numbers as a list of integers.
"""
return cvd_utils.GetVncPorts(self._avd_spec.base_instance_num,
self._avd_spec.num_avds_per_instance)
def GetBuildInfoDict(self):
"""Get build info dictionary.
Returns:
A build info dictionary. None for local image case.
"""
if self._avd_spec.image_source == constants.IMAGE_SRC_LOCAL:
return None
return cvd_utils.GetRemoteBuildInfoDict(self._avd_spec)
def GetLogs(self):
"""Get all device logs.
Returns:
A dictionary that maps instance names to lists of report.LogFile.
"""
return self._all_logs