Mark ab/7061308 as merged in stage.
Bug: 180401296
Merged-In: Ib7eabae2e55bddf387d486b1258ab638c77ff9cf
Change-Id: I7b4c436f3704a86e6f35eb50cf5d87f714fd44e3
diff --git a/Android.bp b/Android.bp
index 89bc8aa..c9191c5 100644
--- a/Android.bp
+++ b/Android.bp
@@ -12,6 +12,23 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+package {
+ default_applicable_licenses: ["tools_acloud_license"],
+}
+
+// Added automatically by a large-scale-change
+// http://go/android-license-faq
+license {
+ name: "tools_acloud_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "LICENSE",
+ ],
+}
+
python_defaults {
name: "acloud_default",
pkg_path: "acloud",
@@ -55,6 +72,7 @@
"acloud_metrics",
"acloud_proto",
"acloud_public",
+ "acloud_restart",
"acloud_setup",
"py-apitools",
"py-dateutil",
@@ -91,6 +109,7 @@
"acloud_pull",
"acloud_proto",
"acloud_public",
+ "acloud_restart",
"acloud_setup",
"asuite_cc_client",
"py-apitools",
@@ -205,6 +224,14 @@
}
python_library_host{
+ name: "acloud_restart",
+ defaults: ["acloud_default"],
+ srcs: [
+ "restart/*.py",
+ ],
+}
+
+python_library_host{
name: "acloud_metrics",
defaults: ["acloud_default"],
srcs: [
diff --git a/README.md b/README.md
index 60d8796..f7fea24 100755
--- a/README.md
+++ b/README.md
@@ -221,4 +221,4 @@
If you have any questions or feedback, contact [acloud@google.com](mailto:acloud@google.com).
-If you have any bugs or feature requests, [go/acloud-bug](http://go/acloud-bug).
+If you have any bugs or feature requests email them to [buganizer-system+419709@google.com](mailto:buganizer-system+419709@google.com)
\ No newline at end of file
diff --git a/create/avd_spec.py b/create/avd_spec.py
index f0ac5ad..af61f4b 100644
--- a/create/avd_spec.py
+++ b/create/avd_spec.py
@@ -125,6 +125,7 @@
self._bootloader_build_info = None
self._hw_property = None
self._remote_host = None
+ self._gce_metadata = None
self._host_user = None
self._host_ssh_private_key_path = None
# Create config instance for android_build_client to query build api.
@@ -230,7 +231,7 @@
Raises:
error.MalformedHWPropertyError: If hw_property_str is malformed.
"""
- hw_dict = create_common.ParseHWPropertyArgs(hw_property_str)
+ hw_dict = create_common.ParseKeyValuePairArgs(hw_property_str)
arg_hw_properties = {}
for key, value in hw_dict.items():
# Parsing HW properties int to avdspec.
@@ -317,6 +318,7 @@
self._serial_log_file = args.serial_log_file
self._emulator_build_id = args.emulator_build_id
self._gpu = args.gpu
+ self._gce_metadata = create_common.ParseKeyValuePairArgs(args.gce_metadata)
self._stable_cheeps_host_image_name = args.stable_cheeps_host_image_name
self._stable_cheeps_host_image_project = args.stable_cheeps_host_image_project
@@ -467,6 +469,13 @@
self._CheckCFBuildTarget(self._instance_type)
local_image_path = utils.GetBuildEnvironmentVariable(
_ENV_ANDROID_PRODUCT_OUT)
+ # Since dir is provided, check that any images exist to ensure user
+ # didn't forget to 'make' before launch AVD.
+ image_list = glob.glob(os.path.join(local_image_path, "*.img"))
+ if not image_list:
+ raise errors.GetLocalImageError(
+ "No image found(Did you choose a lunch target and run `m`?)"
+ ": %s.\n " % local_image_path)
else:
local_image_path = local_image_arg
@@ -480,14 +489,6 @@
utils.TextColors.WARNING)
else:
self._local_image_dir = local_image_path
- # Since dir is provided, so checking that any images exist to ensure
- # user didn't forget to 'make' before launch AVD.
- image_list = glob.glob(os.path.join(self.local_image_dir, "*.img"))
- if not image_list:
- raise errors.GetLocalImageError(
- "No image found(Did you choose a lunch target and run `m`?)"
- ": %s.\n " % self.local_image_dir)
-
try:
flavor_from_build_string = self._GetFlavorFromString(
utils.GetBuildEnvironmentVariable(constants.ENV_BUILD_TARGET))
@@ -909,3 +910,8 @@
def no_pull_log(self):
"""Return no_pull_log."""
return self._no_pull_log
+
+ @property
+ def gce_metadata(self):
+ """Return gce_metadata."""
+ return self._gce_metadata
diff --git a/create/avd_spec_test.py b/create/avd_spec_test.py
index 836c3ae..c608344 100644
--- a/create/avd_spec_test.py
+++ b/create/avd_spec_test.py
@@ -92,15 +92,37 @@
self.AvdSpec._ProcessLocalImageArgs(self.args)
self.assertEqual(self.AvdSpec._local_image_dir, expected_image_dir)
- # Specified --avd-type=goldfish --local_image without arg
- self.Patch(utils, "GetBuildEnvironmentVariable",
- return_value="test_environ")
- self.Patch(os.path, "isdir", return_value=True)
+ # Specified --local-image and --local-system-image with dirs
+ self.args.local_image = expected_image_dir
+ self.args.local_system_image = expected_image_dir
+ self.AvdSpec._avd_type = constants.TYPE_CF
+ self.AvdSpec._instance_type = constants.INSTANCE_TYPE_LOCAL
+ with mock.patch("os.path.isfile", return_value=False), \
+ mock.patch("os.path.isdir",
+ side_effect=lambda path: path == expected_image_dir), \
+ mock.patch("acloud.create.avd_spec.utils."
+ "GetBuildEnvironmentVariable",
+ return_value="cf_x86_phone"):
+ self.AvdSpec._ProcessLocalImageArgs(self.args)
+ self.assertEqual(self.AvdSpec._local_image_dir, expected_image_dir)
+ self.assertEqual(self.AvdSpec._local_system_image_dir,
+ expected_image_dir)
+
+ # Specified --avd-type=goldfish, --local_image, and
+ # --local-system-image without args
self.args.local_image = constants.FIND_IN_BUILD_ENV
+ self.args.local_system_image = constants.FIND_IN_BUILD_ENV
self.AvdSpec._avd_type = constants.TYPE_GF
self.AvdSpec._instance_type = constants.INSTANCE_TYPE_LOCAL
- self.AvdSpec._ProcessLocalImageArgs(self.args)
- self.assertEqual(self.AvdSpec._local_image_dir, "test_environ")
+ with mock.patch("os.path.isdir",
+ side_effect=lambda path: path == expected_image_dir), \
+ mock.patch("acloud.create.avd_spec.utils."
+ "GetBuildEnvironmentVariable",
+ return_value=expected_image_dir):
+ self.AvdSpec._ProcessLocalImageArgs(self.args)
+ self.assertEqual(self.AvdSpec._local_image_dir, expected_image_dir)
+ self.assertEqual(self.AvdSpec._local_system_image_dir,
+ expected_image_dir)
def testProcessImageArgs(self):
"""Test process image source."""
diff --git a/create/create_args.py b/create/create_args.py
index 63eaeea..ac359a9 100644
--- a/create/create_args.py
+++ b/create/create_args.py
@@ -429,6 +429,13 @@
"provided. Select one gce instance to reuse if --reuse-gce is "
"provided.")
create_parser.add_argument(
+ "--gce-metadata",
+ type=str,
+ dest="gce_metadata",
+ default=None,
+ help="'GCE instance only' Record data into GCE instance metadata with "
+ "key-value pair format. e.g. id:12,name:unknown.")
+ create_parser.add_argument(
"--host",
type=str,
dest="remote_host",
@@ -555,9 +562,8 @@
raise errors.CheckPathError(
"Specified path doesn't exist: %s" % args.local_instance_dir)
- # TODO(b/133211308): Support TYPE_CF.
if not (args.local_system_image is None or
- args.avd_type == constants.TYPE_GF):
+ args.avd_type in (constants.TYPE_CF, constants.TYPE_GF)):
raise errors.UnsupportedCreateArgs("%s instance does not support "
"--local-system-image" %
args.avd_type)
@@ -647,7 +653,7 @@
if args.adb_port:
utils.CheckPortFree(args.adb_port)
- hw_properties = create_common.ParseHWPropertyArgs(args.hw_property)
+ hw_properties = create_common.ParseKeyValuePairArgs(args.hw_property)
for key in hw_properties:
if key not in constants.HW_PROPERTIES:
raise errors.InvalidHWPropertyError(
diff --git a/create/create_common.py b/create/create_common.py
index ff56965..e18e58b 100644
--- a/create/create_common.py
+++ b/create/create_common.py
@@ -29,7 +29,7 @@
logger = logging.getLogger(__name__)
-def ParseHWPropertyArgs(dict_str, item_separator=",", key_value_separator=":"):
+def ParseKeyValuePairArgs(dict_str, item_separator=",", key_value_separator=":"):
"""Helper function to initialize a dict object from string.
e.g.
@@ -47,9 +47,9 @@
Raises:
error.MalformedDictStringError: If dict_str is malformed.
"""
- hw_dict = {}
+ args_dict = {}
if not dict_str:
- return hw_dict
+ return args_dict
for item in dict_str.split(item_separator):
if key_value_separator not in item:
@@ -59,9 +59,9 @@
if not value or not key:
raise errors.MalformedDictStringError(
"Missing key or value in %s, expecting form of 'a:b'" % item)
- hw_dict[key.strip()] = value.strip()
+ args_dict[key.strip()] = value.strip()
- return hw_dict
+ return args_dict
def GetCvdHostPackage():
diff --git a/create/create_common_test.py b/create/create_common_test.py
index a18f561..0cfaea7 100644
--- a/create/create_common_test.py
+++ b/create/create_common_test.py
@@ -48,23 +48,23 @@
# pylint: disable=protected-access
def testProcessHWPropertyWithInvalidArgs(self):
- """Test ParseHWPropertyArgs with invalid args."""
+ """Test ParseKeyValuePairArgs with invalid args."""
# Checking wrong property value.
args_str = "cpu:3,disk:"
with self.assertRaises(errors.MalformedDictStringError):
- create_common.ParseHWPropertyArgs(args_str)
+ create_common.ParseKeyValuePairArgs(args_str)
# Checking wrong property format.
args_str = "cpu:3,disk"
with self.assertRaises(errors.MalformedDictStringError):
- create_common.ParseHWPropertyArgs(args_str)
+ create_common.ParseKeyValuePairArgs(args_str)
def testParseHWPropertyStr(self):
- """Test ParseHWPropertyArgs."""
+ """Test ParseKeyValuePairArgs."""
expected_dict = {"cpu": "2", "resolution": "1080x1920", "dpi": "240",
"memory": "4g", "disk": "4g"}
args_str = "cpu:2,resolution:1080x1920,dpi:240,memory:4g,disk:4g"
- result_dict = create_common.ParseHWPropertyArgs(args_str)
+ result_dict = create_common.ParseKeyValuePairArgs(args_str)
self.assertTrue(expected_dict == result_dict)
def testGetCvdHostPackage(self):
diff --git a/create/local_image_local_instance.py b/create/local_image_local_instance.py
index 322284d..e40d4b6 100644
--- a/create/local_image_local_instance.py
+++ b/create/local_image_local_instance.py
@@ -33,9 +33,23 @@
To delete the local instance, we will call stop_cvd with the environment variable
[CUTTLEFISH_CONFIG_FILE] which is pointing to the runtime cuttlefish json.
+
+To run this program outside of a build environment, the following setup is
+required.
+- One of the local tool directories is a decompressed cvd host package,
+ i.e., cvd-host_package.tar.gz.
+- If the instance doesn't require mixed images, the local image directory
+ should be an unzipped update package, i.e., <target>-img-<build>.zip,
+ which contains a super image.
+- If the instance requires mixed images, the local image directory should
+ be an unzipped target files package, i.e., <target>-target_files-<build>.zip,
+ which contains misc info and images not packed into a super image.
+- If the instance requires mixed images, one of the local tool directories
+ should be an unzipped OTA tools package, i.e., otatools.zip.
"""
import collections
+import glob
import logging
import os
import subprocess
@@ -46,6 +60,7 @@
from acloud.create import base_avd_create
from acloud.create import create_common
from acloud.internal import constants
+from acloud.internal.lib import ota_tools
from acloud.internal.lib import utils
from acloud.internal.lib.adb_tools import AdbTools
from acloud.list import list as list_instance
@@ -55,6 +70,11 @@
logger = logging.getLogger(__name__)
+_SYSTEM_IMAGE_NAME = "system.img"
+_MISC_INFO_FILE_NAME = "misc_info.txt"
+_TARGET_FILES_IMAGES_DIR_NAME = "IMAGES"
+_TARGET_FILES_META_DIR_NAME = "META"
+_MIXED_SUPER_IMAGE_NAME = "mixed_super.img"
_CMD_LAUNCH_CVD_ARGS = (" -daemon -cpus %s -x_res %s -y_res %s -dpi %s "
"-memory_mb %s -run_adb_connector=%s "
"-system_image_dir %s -instance_dir %s "
@@ -69,6 +89,7 @@
"-start_webrtc=true "
"-webrtc_public_ip=%s" % constants.LOCALHOST)
_CMD_LAUNCH_CVD_VNC_ARG = " -start_vnc_server=true"
+_CMD_LAUNCH_CVD_SUPER_IMAGE_ARG = " -super_image=%s"
# In accordance with the number of network interfaces in
# /etc/init.d/cuttlefish-common
@@ -81,10 +102,13 @@
"Enter 'y' to terminate current instance and launch a new "
"instance, enter anything else to exit out[y/N]: ")
-# The collection of image folder and CVD host package folder for local
-# instances.
+# The first two fields of this named tuple are image folder and CVD host
+# package folder which are essential for local instances. The following fields
+# are optional. They are set when the AVD spec requires to mix images.
ArtifactPaths = collections.namedtuple(
- "ArtifactPaths", ["image_dir", "host_bins"])
+ "ArtifactPaths",
+ ["image_dir", "host_bins", "misc_info", "ota_tools_dir", "system_image"],
+ defaults=[None, None, None])
class LocalImageLocalInstance(base_avd_create.BaseAVDCreate):
@@ -181,6 +205,10 @@
cvd_home_dir = instance.GetLocalInstanceHomeDir(local_instance_id)
create_common.PrepareLocalInstanceDir(cvd_home_dir, avd_spec)
+ super_image_path = None
+ if artifact_paths.system_image:
+ super_image_path = self._MixSuperImage(cvd_home_dir,
+ artifact_paths)
runtime_dir = instance.GetLocalInstanceRuntimeDir(local_instance_id)
# TODO(b/168171781): cvd_status of list/delete via the symbolic.
self.PrepareLocalCvdToolsLink(cvd_home_dir, artifact_paths.host_bins)
@@ -194,6 +222,7 @@
avd_spec.connect_webrtc,
avd_spec.connect_vnc,
avd_spec.gpu,
+ super_image_path,
avd_spec.cfg.launch_args)
result_report = report.Report(command="create")
@@ -251,6 +280,54 @@
"CVD host binaries are not found. Please run `make hosttar`, or "
"set --local-tool to an extracted CVD host package.")
+ @staticmethod
+ def _FindMiscInfo(image_dir):
+ """Find misc info in build output dir or extracted target files.
+
+ Args:
+ image_dir: The directory to search for misc info.
+
+ Returns:
+ image_dir if the directory structure looks like an output directory
+ in build environment.
+ image_dir/META if it looks like extracted target files.
+
+ Raises:
+ errors.CheckPathError if this method cannot find misc info.
+ """
+ misc_info_path = os.path.join(image_dir, _MISC_INFO_FILE_NAME)
+ if os.path.isfile(misc_info_path):
+ return misc_info_path
+ misc_info_path = os.path.join(image_dir, _TARGET_FILES_META_DIR_NAME,
+ _MISC_INFO_FILE_NAME)
+ if os.path.isfile(misc_info_path):
+ return misc_info_path
+ raise errors.CheckPathError(
+ "Cannot find %s in %s." % (_MISC_INFO_FILE_NAME, image_dir))
+
+ @staticmethod
+ def _FindImageDir(image_dir):
+ """Find images in build output dir or extracted target files.
+
+ Args:
+ image_dir: The directory to search for images.
+
+ Returns:
+ image_dir if the directory structure looks like an output directory
+ in build environment.
+ image_dir/IMAGES if it looks like extracted target files.
+
+ Raises:
+ errors.GetLocalImageError if this method cannot find images.
+ """
+ if glob.glob(os.path.join(image_dir, "*.img")):
+ return image_dir
+ subdir = os.path.join(image_dir, _TARGET_FILES_IMAGES_DIR_NAME)
+ if glob.glob(os.path.join(subdir, "*.img")):
+ return subdir
+ raise errors.GetLocalImageError(
+ "Cannot find images in %s." % image_dir)
+
def GetImageArtifactsPath(self, avd_spec):
"""Get image artifacts path.
@@ -265,15 +342,57 @@
Returns:
ArtifactPaths object consisting of image directory and host bins
package.
+
+ Raises:
+ errors.GetCvdLocalHostPackageError, errors.GetLocalImageError, or
+ errors.CheckPathError if any artifact is not found.
"""
- return ArtifactPaths(
- avd_spec.local_image_dir,
- self._FindCvdHostBinaries(avd_spec.local_tool_dirs))
+ image_dir = os.path.abspath(avd_spec.local_image_dir)
+ host_bins_path = self._FindCvdHostBinaries(avd_spec.local_tool_dirs)
+
+ if not avd_spec.local_system_image_dir:
+ return ArtifactPaths(image_dir, host_bins_path)
+
+ misc_info_path = self._FindMiscInfo(image_dir)
+ image_dir = self._FindImageDir(image_dir)
+ ota_tools_dir = os.path.abspath(
+ ota_tools.FindOtaTools(avd_spec.local_tool_dirs))
+ system_image_path = os.path.abspath(
+ os.path.join(avd_spec.local_system_image_dir, _SYSTEM_IMAGE_NAME))
+ if not os.path.isfile(system_image_path):
+ raise errors.GetLocalImageError(
+ "Cannot find %s." % system_image_path)
+
+ return ArtifactPaths(image_dir, host_bins_path,
+ misc_info=misc_info_path,
+ ota_tools_dir=ota_tools_dir,
+ system_image=system_image_path)
+
+ @staticmethod
+ def _MixSuperImage(output_dir, artifact_paths):
+ """Mix cuttlefish images and a system image into a super image.
+
+ Args:
+ output_dir: The path to the output directory.
+ artifact_paths: ArtifactPaths object.
+
+ Returns:
+ The path to the super image in output_dir.
+ """
+ ota = ota_tools.OtaTools(artifact_paths.ota_tools_dir)
+ super_image_path = os.path.join(output_dir, _MIXED_SUPER_IMAGE_NAME)
+ ota.BuildSuperImage(
+ super_image_path, artifact_paths.misc_info,
+ lambda partition: ota_tools.GetImageForPartition(
+ partition, artifact_paths.image_dir,
+ system=artifact_paths.system_image))
+ return super_image_path
@staticmethod
def PrepareLaunchCVDCmd(launch_cvd_path, hw_property, connect_adb,
- system_image_dir, runtime_dir, connect_webrtc,
- connect_vnc, gpu, launch_args):
+ image_dir, runtime_dir, connect_webrtc,
+ connect_vnc, gpu, super_image_path,
+ launch_args):
"""Prepare launch_cvd command.
Create the launch_cvd commands with all the required args and add
@@ -282,13 +401,14 @@
Args:
launch_cvd_path: String of launch_cvd path.
hw_property: dict object of hw property.
- system_image_dir: String of local images path.
+ image_dir: String of local images path.
connect_adb: Boolean flag that enables adb_connector.
runtime_dir: String of runtime directory path.
connect_webrtc: Boolean of connect_webrtc.
connect_vnc: Boolean of connect_vnc.
gpu: String of gpu name, the gpu name of local instance should be
"default" if gpu is enabled.
+ super_image_path: String of non-default super image path.
launch_args: String of launch args.
Returns:
@@ -297,8 +417,7 @@
launch_cvd_w_args = launch_cvd_path + _CMD_LAUNCH_CVD_ARGS % (
hw_property["cpu"], hw_property["x_res"], hw_property["y_res"],
hw_property["dpi"], hw_property["memory"],
- ("true" if connect_adb else "false"), system_image_dir,
- runtime_dir)
+ ("true" if connect_adb else "false"), image_dir, runtime_dir)
if constants.HW_ALIAS_DISK in hw_property:
launch_cvd_w_args = (launch_cvd_w_args + _CMD_LAUNCH_CVD_DISK_ARGS %
hw_property[constants.HW_ALIAS_DISK])
@@ -311,6 +430,11 @@
if gpu:
launch_cvd_w_args = launch_cvd_w_args + _CMD_LAUNCH_CVD_GPU_ARG
+ if super_image_path:
+ launch_cvd_w_args = (launch_cvd_w_args +
+ _CMD_LAUNCH_CVD_SUPER_IMAGE_ARG %
+ super_image_path)
+
if launch_args:
launch_cvd_w_args = launch_cvd_w_args + " " + launch_args
diff --git a/create/local_image_local_instance_test.py b/create/local_image_local_instance_test.py
index 8a2ac9e..ab7d103 100644
--- a/create/local_image_local_instance_test.py
+++ b/create/local_image_local_instance_test.py
@@ -17,6 +17,7 @@
import os
import subprocess
+import tempfile
import unittest
import mock
@@ -51,6 +52,12 @@
sg group2
launch_cvd -daemon -cpus fake -x_res fake -y_res fake -dpi fake -memory_mb fake -run_adb_connector=true -system_image_dir fake_image_dir -instance_dir fake_cvd_dir -undefok=report_anonymous_usage_stats,enable_sandbox -report_anonymous_usage_stats=y -enable_sandbox=false -guest_enforce_security=false -vm_manager=crosvm -start_webrtc=true -webrtc_public_ip=0.0.0.0
EOF"""
+
+ LAUNCH_CVD_CMD_WITH_SUPER_IMAGE = """sg group1 <<EOF
+sg group2
+launch_cvd -daemon -cpus fake -x_res fake -y_res fake -dpi fake -memory_mb fake -run_adb_connector=true -system_image_dir fake_image_dir -instance_dir fake_cvd_dir -undefok=report_anonymous_usage_stats,enable_sandbox -report_anonymous_usage_stats=y -enable_sandbox=false -start_vnc_server=true -super_image=fake_super_image
+EOF"""
+
LAUNCH_CVD_CMD_WITH_ARGS = """sg group1 <<EOF
sg group2
launch_cvd -daemon -cpus fake -x_res fake -y_res fake -dpi fake -memory_mb fake -run_adb_connector=true -system_image_dir fake_image_dir -instance_dir fake_cvd_dir -undefok=report_anonymous_usage_stats,enable_sandbox -report_anonymous_usage_stats=y -enable_sandbox=false -start_vnc_server=true -setupwizard_mode=REQUIRED
@@ -151,6 +158,7 @@
mock_lock.LockIfNotInUse.assert_not_called()
@mock.patch("acloud.create.local_image_local_instance.utils")
+ @mock.patch("acloud.create.local_image_local_instance.ota_tools")
@mock.patch("acloud.create.local_image_local_instance.create_common")
@mock.patch.object(local_image_local_instance.LocalImageLocalInstance,
"_LaunchCvd")
@@ -158,14 +166,18 @@
"PrepareLaunchCVDCmd")
@mock.patch.object(instance, "GetLocalInstanceRuntimeDir")
@mock.patch.object(instance, "GetLocalInstanceHomeDir")
- def testCreateInstance(self, _mock_home_dir, _mock_runtime_dir,
+ def testCreateInstance(self, mock_home_dir, _mock_runtime_dir,
_mock_prepare_cmd, mock_launch_cvd,
- _mock_create_common, _mock_utils):
+ _mock_create_common, mock_ota_tools, _mock_utils):
"""Test the report returned by _CreateInstance."""
self.Patch(instance, "GetLocalInstanceName",
return_value="local-instance-1")
+ mock_home_dir.return_value = "/local-instance-1"
artifact_paths = local_image_local_instance.ArtifactPaths(
- "/image/path", "/host/bin/path")
+ "/image/path", "/host/bin/path", "/misc/info/path",
+ "/ota/tools/dir", "/system/image/path")
+ mock_ota_tools_object = mock.Mock()
+ mock_ota_tools.OtaTools.return_value = mock_ota_tools_object
mock_avd_spec = mock.Mock(unlock_screen=False)
local_ins = mock.Mock(
adb_port=6520,
@@ -184,6 +196,9 @@
self.assertEqual(report.data.get("devices"),
self._EXPECTED_DEVICES_IN_REPORT)
+ mock_ota_tools.OtaTools.assert_called_with("/ota/tools/dir")
+ mock_ota_tools_object.BuildSuperImage.assert_called_with(
+ "/local-instance-1/mixed_super.img", "/misc/info/path", mock.ANY)
# Failure
mock_launch_cvd.side_effect = errors.LaunchCVDFail("unit test")
@@ -224,6 +239,94 @@
[cvd_host_dir])
self.assertEqual(path, cvd_host_dir)
+ @staticmethod
+ def _CreateEmptyFile(path):
+ os.makedirs(os.path.dirname(path), exist_ok=True)
+ with open(path, "w"):
+ pass
+
+ @mock.patch("acloud.create.local_image_local_instance.ota_tools")
+ def testGetImageArtifactsPath(self, mock_ota_tools):
+ """Test GetImageArtifactsPath without system image dir."""
+ with tempfile.TemporaryDirectory() as temp_dir:
+ image_dir = "/unit/test"
+ cvd_dir = os.path.join(temp_dir, "cvd-host_package")
+ self._CreateEmptyFile(os.path.join(cvd_dir, "bin", "launch_cvd"))
+
+ mock_avd_spec = mock.Mock(
+ local_image_dir=image_dir,
+ local_system_image_dir=None,
+ local_tool_dirs=[cvd_dir])
+
+ paths = self.local_image_local_instance.GetImageArtifactsPath(
+ mock_avd_spec)
+
+ mock_ota_tools.FindOtaTools.assert_not_called()
+ self.assertEqual(paths, (image_dir, cvd_dir, None, None, None))
+
+ @mock.patch("acloud.create.local_image_local_instance.ota_tools")
+ def testGetImageFromBuildEnvironment(self, mock_ota_tools):
+ """Test GetImageArtifactsPath with files in build environment."""
+ with tempfile.TemporaryDirectory() as temp_dir:
+ image_dir = os.path.join(temp_dir, "image")
+ cvd_dir = os.path.join(temp_dir, "cvd-host_package")
+ mock_ota_tools.FindOtaTools.return_value = cvd_dir
+ system_image_dir = os.path.join(temp_dir, "system_image")
+ system_image_path = os.path.join(system_image_dir, "system.img")
+ misc_info_path = os.path.join(image_dir, "misc_info.txt")
+ self._CreateEmptyFile(os.path.join(image_dir, "boot.img"))
+ self._CreateEmptyFile(os.path.join(cvd_dir, "bin", "launch_cvd"))
+ self._CreateEmptyFile(system_image_path)
+ self._CreateEmptyFile(misc_info_path)
+
+ mock_avd_spec = mock.Mock(
+ local_image_dir=image_dir,
+ local_system_image_dir=system_image_dir,
+ local_tool_dirs=[])
+
+ with mock.patch.dict("acloud.create.local_image_local_instance."
+ "os.environ",
+ {"ANDROID_SOONG_HOST_OUT": cvd_dir},
+ clear=True):
+ paths = self.local_image_local_instance.GetImageArtifactsPath(
+ mock_avd_spec)
+
+ mock_ota_tools.FindOtaTools.assert_called_once()
+ self.assertEqual(paths,
+ (image_dir, cvd_dir, misc_info_path, cvd_dir,
+ system_image_path))
+
+ @mock.patch("acloud.create.local_image_local_instance.ota_tools")
+ def testGetImageFromTargetFiles(self, mock_ota_tools):
+ """Test GetImageArtifactsPath with extracted target files."""
+ ota_tools_dir = "/mock_ota_tools"
+ mock_ota_tools.FindOtaTools.return_value = ota_tools_dir
+
+ with tempfile.TemporaryDirectory() as temp_dir:
+ image_dir = os.path.join(temp_dir, "image")
+ cvd_dir = os.path.join(temp_dir, "cvd-host_package")
+ system_image_dir = os.path.join(temp_dir, "system_image")
+ system_image_path = os.path.join(system_image_dir, "system.img")
+ misc_info_path = os.path.join(image_dir, "META", "misc_info.txt")
+ self._CreateEmptyFile(os.path.join(image_dir, "IMAGES",
+ "boot.img"))
+ self._CreateEmptyFile(os.path.join(cvd_dir, "bin", "launch_cvd"))
+ self._CreateEmptyFile(system_image_path)
+ self._CreateEmptyFile(misc_info_path)
+
+ mock_avd_spec = mock.Mock(
+ local_image_dir=image_dir,
+ local_system_image_dir=system_image_dir,
+ local_tool_dirs=[ota_tools_dir, cvd_dir])
+
+ paths = self.local_image_local_instance.GetImageArtifactsPath(
+ mock_avd_spec)
+
+ mock_ota_tools.FindOtaTools.assert_called_once()
+ self.assertEqual(paths,
+ (os.path.join(image_dir, "IMAGES"), cvd_dir,
+ misc_info_path, ota_tools_dir, system_image_path))
+
@mock.patch.object(utils, "CheckUserInGroups")
def testPrepareLaunchCVDCmd(self, mock_usergroups):
"""test PrepareLaunchCVDCmd."""
@@ -234,7 +337,7 @@
launch_cmd = self.local_image_local_instance.PrepareLaunchCVDCmd(
constants.CMD_LAUNCH_CVD, hw_property, True, "fake_image_dir",
- "fake_cvd_dir", False, True, None, None)
+ "fake_cvd_dir", False, True, None, None, None)
self.assertEqual(launch_cmd, self.LAUNCH_CVD_CMD_WITH_DISK)
# "disk" doesn't exist in hw_property.
@@ -242,24 +345,30 @@
"dpi": "fake", "memory": "fake"}
launch_cmd = self.local_image_local_instance.PrepareLaunchCVDCmd(
constants.CMD_LAUNCH_CVD, hw_property, True, "fake_image_dir",
- "fake_cvd_dir", False, True, None, None)
+ "fake_cvd_dir", False, True, None, None, None)
self.assertEqual(launch_cmd, self.LAUNCH_CVD_CMD_NO_DISK)
# "gpu" is enabled with "default"
launch_cmd = self.local_image_local_instance.PrepareLaunchCVDCmd(
constants.CMD_LAUNCH_CVD, hw_property, True, "fake_image_dir",
- "fake_cvd_dir", False, True, "default", None)
+ "fake_cvd_dir", False, True, "default", None, None)
self.assertEqual(launch_cmd, self.LAUNCH_CVD_CMD_NO_DISK_WITH_GPU)
launch_cmd = self.local_image_local_instance.PrepareLaunchCVDCmd(
constants.CMD_LAUNCH_CVD, hw_property, True, "fake_image_dir",
- "fake_cvd_dir", True, False, None, None)
+ "fake_cvd_dir", True, False, None, None, None)
self.assertEqual(launch_cmd, self.LAUNCH_CVD_CMD_WITH_WEBRTC)
+ launch_cmd = self.local_image_local_instance.PrepareLaunchCVDCmd(
+ constants.CMD_LAUNCH_CVD, hw_property, True, "fake_image_dir",
+ "fake_cvd_dir", False, True, None, "fake_super_image", None)
+ self.assertEqual(launch_cmd, self.LAUNCH_CVD_CMD_WITH_SUPER_IMAGE)
+
# Add args into launch command with "-setupwizard_mode=REQUIRED"
launch_cmd = self.local_image_local_instance.PrepareLaunchCVDCmd(
constants.CMD_LAUNCH_CVD, hw_property, True, "fake_image_dir",
- "fake_cvd_dir", False, True, None, "-setupwizard_mode=REQUIRED")
+ "fake_cvd_dir", False, True, None, None,
+ "-setupwizard_mode=REQUIRED")
self.assertEqual(launch_cmd, self.LAUNCH_CVD_CMD_WITH_ARGS)
@mock.patch.object(utils, "GetUserAnswerYes")
diff --git a/create/remote_image_local_instance.py b/create/remote_image_local_instance.py
index caaf27b..5166efa 100644
--- a/create/remote_image_local_instance.py
+++ b/create/remote_image_local_instance.py
@@ -184,4 +184,6 @@
raise errors.GetCvdLocalHostPackageError(
"No launch_cvd found. Please check downloaded artifacts dir: %s"
% image_dir)
+ # This method does not set the optional fields because launch_cvd loads
+ # the paths from the fetcher config in image_dir.
return local_image_local_instance.ArtifactPaths(image_dir, image_dir)
diff --git a/errors.py b/errors.py
index fc6a084..5b6d249 100644
--- a/errors.py
+++ b/errors.py
@@ -41,7 +41,7 @@
def __init__(self, code, message):
self.code = code
- super(HttpError, self).__init__(message)
+ super().__init__(message)
@staticmethod
def CreateFromHttpError(http_error):
@@ -83,6 +83,10 @@
"""To catch device boot errors."""
+class DownloadArtifactError(DriverError):
+ """To catch download artifact errors."""
+
+
class NoSubnetwork(DriverError):
"""When there is no subnetwork for the GCE."""
diff --git a/internal/lib/auth.py b/internal/lib/auth.py
index 1d1cc09..ad03d6b 100644
--- a/internal/lib/auth.py
+++ b/internal/lib/auth.py
@@ -86,14 +86,20 @@
" error message: %s" % (private_key_path, str(e)))
return credentials
+
# pylint: disable=invalid-name
-def _CreateOauthServiceAccountCredsWithJsonKey(json_private_key_path, scopes):
+def _CreateOauthServiceAccountCredsWithJsonKey(json_private_key_path, scopes,
+ creds_cache_file, user_agent):
"""Create credentials with a normal service account from json key file.
Args:
json_private_key_path: Path to the service account json key file.
scopes: string, multiple scopes should be saperated by space.
Api scopes to request for the oauth token.
+ creds_cache_file: String, file name for the credential cache.
+ e.g. .acloud_oauth2.dat
+ Will be created at home folder.
+ user_agent: String, the user agent for the credential, e.g. "acloud"
Returns:
An oauth2client.OAuth2Credentials instance.
@@ -102,16 +108,22 @@
errors.AuthenticationError: if failed to authenticate.
"""
try:
- return (
- oauth2_service_account.ServiceAccountCredentials
- .from_json_keyfile_name(
- json_private_key_path, scopes=scopes))
+ credentials = oauth2_service_account.ServiceAccountCredentials.from_json_keyfile_name(
+ json_private_key_path, scopes=scopes)
+ storage = multistore_file.get_credential_storage(
+ filename=os.path.abspath(creds_cache_file),
+ client_id=credentials.client_id,
+ user_agent=user_agent,
+ scope=scopes)
+ credentials.set_store(storage)
except EnvironmentError as e:
raise errors.AuthenticationError(
"Could not authenticate using json private key file (%s) "
" error message: %s" % (json_private_key_path, str(e)))
-# pylint: disable=old-style-class
+ return credentials
+
+
class RunFlowFlags():
"""Flags for oauth2client.tools.run_flow."""
@@ -199,21 +211,24 @@
Returns:
An oauth2client.OAuth2Credentials instance.
"""
+ if os.path.isabs(acloud_config.creds_cache_file):
+ creds_cache_file = acloud_config.creds_cache_file
+ else:
+ creds_cache_file = os.path.join(HOME_FOLDER,
+ acloud_config.creds_cache_file)
+
if acloud_config.service_account_json_private_key_path:
return _CreateOauthServiceAccountCredsWithJsonKey(
acloud_config.service_account_json_private_key_path,
- scopes=scopes)
+ scopes=scopes,
+ creds_cache_file=creds_cache_file,
+ user_agent=acloud_config.user_agent)
if acloud_config.service_account_private_key_path:
return _CreateOauthServiceAccountCreds(
acloud_config.service_account_name,
acloud_config.service_account_private_key_path,
scopes=scopes)
- if os.path.isabs(acloud_config.creds_cache_file):
- creds_cache_file = acloud_config.creds_cache_file
- else:
- creds_cache_file = os.path.join(HOME_FOLDER,
- acloud_config.creds_cache_file)
return _CreateOauthUserCreds(
creds_cache_file=creds_cache_file,
client_id=acloud_config.client_id,
diff --git a/internal/lib/cvd_compute_client_multi_stage.py b/internal/lib/cvd_compute_client_multi_stage.py
index e42b29e..3a79bac 100644
--- a/internal/lib/cvd_compute_client_multi_stage.py
+++ b/internal/lib/cvd_compute_client_multi_stage.py
@@ -191,10 +191,10 @@
int(self.GetImage(image_name, image_project)["diskSizeGb"]) +
blank_data_disk_size_gb)
- # Record the system build and kernel build into metadata.
- self._RecordSystemAndKernelInfo(avd_spec, system_build_id,
- system_build_target, kernel_build_id,
- kernel_build_target)
+ # Record the build info into metadata.
+ self._RecordBuildInfo(avd_spec, build_id, build_target,
+ system_build_id, system_build_target,
+ kernel_build_id, kernel_build_target)
if avd_spec and avd_spec.instance_name_to_reuse:
self._ip = self._ReusingGceInstance(avd_spec)
@@ -236,24 +236,35 @@
self._all_failures[instance] = e
return instance
- def _RecordSystemAndKernelInfo(self, avd_spec, system_build_id,
- system_build_target, kernel_build_id,
- kernel_build_target):
- """Rocord the system build info and kernel build info into metadata.
+ def _RecordBuildInfo(self, avd_spec, build_id, build_target,
+ system_build_id, system_build_target,
+ kernel_build_id, kernel_build_target):
+ """Rocord the build information into metadata.
+
+ The build information includes build id and build target of base image,
+ system image, and kernel image.
Args:
avd_spec: An AVDSpec instance.
- system_build_id: A string, build id for the system image.
- system_build_target: Target name for the system image,
- e.g. "cf_x86_phone-userdebug"
- kernel_build_id: Kernel build id, a string, e.g. "223051", "P280427"
- kernel_build_target: String, Kernel build target name.
+ build_id: String, build id for the base image.
+ build_target: String, target name for the base image,
+ e.g. "cf_x86_phone-userdebug"
+ system_build_id: String, build id for the system image.
+ system_build_target: String, system build target name,
+ e.g. "cf_x86_phone-userdebug"
+ kernel_build_id: String, kernel build id, e.g. "223051", "P280427"
+ kernel_build_target: String, kernel build target name.
"""
if avd_spec and avd_spec.image_source == constants.IMAGE_SRC_REMOTE:
+ build_id = avd_spec.remote_image.get(constants.BUILD_ID)
+ build_target = avd_spec.remote_image.get(constants.BUILD_TARGET)
system_build_id = avd_spec.system_build_info.get(constants.BUILD_ID)
system_build_target = avd_spec.system_build_info.get(constants.BUILD_TARGET)
kernel_build_id = avd_spec.kernel_build_info.get(constants.BUILD_ID)
kernel_build_target = avd_spec.kernel_build_info.get(constants.BUILD_TARGET)
+ if build_id and build_target:
+ self._metadata.update({"build_id": build_id})
+ self._metadata.update({"build_target": build_target})
if system_build_id and system_build_target:
self._metadata.update({"system_build_id": system_build_id})
self._metadata.update({"system_build_target": system_build_target})
@@ -469,6 +480,9 @@
avd_spec.hw_property[constants.HW_X_RES],
avd_spec.hw_property[constants.HW_Y_RES],
avd_spec.hw_property[constants.HW_ALIAS_DPI]))
+ if avd_spec.gce_metadata:
+ for key, value in avd_spec.gce_metadata.items():
+ metadata[key] = value
disk_args = self._GetDiskArgs(
instance, image_name, image_project, boot_disk_size_gb)
diff --git a/internal/lib/cvd_compute_client_multi_stage_test.py b/internal/lib/cvd_compute_client_multi_stage_test.py
index 3077945..34ea323 100644
--- a/internal/lib/cvd_compute_client_multi_stage_test.py
+++ b/internal/lib/cvd_compute_client_multi_stage_test.py
@@ -159,7 +159,7 @@
created_subprocess.returncode = 0
created_subprocess.communicate = mock.MagicMock(return_value=('', ''))
self.Patch(cvd_compute_client_multi_stage.CvdComputeClient,
- "_RecordSystemAndKernelInfo")
+ "_RecordBuildInfo")
self.Patch(subprocess, "Popen", return_value=created_subprocess)
self.Patch(subprocess, "check_call")
self.Patch(os, "chmod")
@@ -219,35 +219,40 @@
gpu=self.GPU,
tags=None)
- def testRecordSystemAndKernelInfo(self):
- """Test RecordSystemAndKernelInfo"""
+ def testRecordBuildInfo(self):
+ """Test RecordBuildInfo"""
+ build_id = "build_id"
+ build_target = "build_target"
system_build_id = "system_id"
system_build_target = "system_target"
kernel_build_id = "kernel_id"
kernel_build_target = "kernel_target"
fake_avd_spec = mock.MagicMock()
fake_avd_spec.image_source = constants.IMAGE_SRC_REMOTE
+ fake_avd_spec.remote_image = {constants.BUILD_ID: build_id,
+ constants.BUILD_TARGET: build_target}
fake_avd_spec.system_build_info = {constants.BUILD_ID: system_build_id,
constants.BUILD_TARGET: system_build_target}
fake_avd_spec.kernel_build_info = {constants.BUILD_ID: kernel_build_id,
constants.BUILD_TARGET: kernel_build_target}
expected_metadata = dict()
expected_metadata.update(self.METADATA)
+ expected_metadata.update({"build_id": build_id})
+ expected_metadata.update({"build_target": build_target})
expected_metadata.update({"system_build_id": system_build_id})
expected_metadata.update({"system_build_target": system_build_target})
expected_metadata.update({"kernel_build_id": kernel_build_id})
expected_metadata.update({"kernel_build_target": kernel_build_target})
# Test record metadata with avd_spec for acloud create
- self.cvd_compute_client_multi_stage._RecordSystemAndKernelInfo(
- fake_avd_spec, system_build_id=None, system_build_target=None,
- kernel_build_id=None, kernel_build_target=None)
+ self.cvd_compute_client_multi_stage._RecordBuildInfo(
+ fake_avd_spec, build_id=None, build_target=None, system_build_id=None,
+ system_build_target=None, kernel_build_id=None, kernel_build_target=None)
self.assertEqual(self.cvd_compute_client_multi_stage._metadata, expected_metadata)
- # Test record metadata with build info of system image and kerenel image for
- # acloud create_cf
- self.cvd_compute_client_multi_stage._RecordSystemAndKernelInfo(
- None, system_build_id, system_build_target,
+ # Test record metadata with build info for acloud create_cf
+ self.cvd_compute_client_multi_stage._RecordBuildInfo(
+ None, build_id, build_target, system_build_id, system_build_target,
kernel_build_id, kernel_build_target)
self.assertEqual(self.cvd_compute_client_multi_stage._metadata, expected_metadata)
diff --git a/internal/lib/goldfish_compute_client.py b/internal/lib/goldfish_compute_client.py
index d868403..33de884 100644
--- a/internal/lib/goldfish_compute_client.py
+++ b/internal/lib/goldfish_compute_client.py
@@ -107,15 +107,16 @@
instance: String
Raises:
- Raises an errors.DeviceBootError exception if a failure is detected.
+ errors.DownloadArtifactError: If it fails to download artifact.
+ errors.DeviceBootError: If it fails to boot up.
"""
if self.BOOT_FAILED_MSG in serial_out:
if self.EMULATOR_FETCH_FAILED_MSG in serial_out:
- raise errors.DeviceBootError(
+ raise errors.DownloadArtifactError(
"Failed to download emulator build. Re-run with a newer build."
)
if self.ANDROID_FETCH_FAILED_MSG in serial_out:
- raise errors.DeviceBootError(
+ raise errors.DownloadArtifactError(
"Failed to download system image build. Re-run with a newer build."
)
if self.BOOT_TIMEOUT_MSG in serial_out:
@@ -188,6 +189,7 @@
["http-server", "https-server"]
launch_args: String of args for launch command.
"""
+ self._VerifyZoneByQuota()
self._CheckMachineSize()
# Add space for possible data partition.
diff --git a/internal/lib/goldfish_compute_client_test.py b/internal/lib/goldfish_compute_client_test.py
index 90e4499..c627f6d 100644
--- a/internal/lib/goldfish_compute_client_test.py
+++ b/internal/lib/goldfish_compute_client_test.py
@@ -73,7 +73,7 @@
def setUp(self):
"""Set up the test."""
- super(GoldfishComputeClientTest, self).setUp()
+ super().setUp()
self.Patch(goldfish_compute_client.GoldfishComputeClient,
"InitResourceHandle")
self.goldfish_compute_client = (
@@ -94,6 +94,9 @@
return_value=[{
"fake_arg": "fake_value"
}])
+ self.Patch(goldfish_compute_client.GoldfishComputeClient,
+ "_VerifyZoneByQuota",
+ return_value=True)
@mock.patch("getpass.getuser", return_value="fake_user")
def testCreateInstance(self, _mock_user):
diff --git a/list/instance.py b/list/instance.py
index 6c7ed7f..2a864c5 100644
--- a/list/instance.py
+++ b/list/instance.py
@@ -52,6 +52,7 @@
_CVD_STATUS_BIN = "cvd_status"
_LOCAL_INSTANCE_NAME_FORMAT = "local-instance-%(id)d"
_LOCAL_INSTANCE_NAME_PATTERN = re.compile(r"^local-instance-(?P<id>\d+)$")
+_ACLOUDWEB_INSTANCE_START_STRING = "cf-"
_MSG_UNABLE_TO_CALCULATE = "Unable to calculate"
_NO_ANDROID_ENV = "android source not available"
_RE_GROUP_ADB = "local_adb_port"
@@ -700,6 +701,9 @@
avd_type = value
elif key == constants.INS_KEY_AVD_FLAVOR:
avd_flavor = value
+ # TODO(176884236): Insert avd information into metadata of instance.
+ if not avd_type and name.startswith(_ACLOUDWEB_INSTANCE_START_STRING):
+ avd_type = constants.TYPE_CF
# Find ssl tunnel info.
adb_port = None
diff --git a/public/acloud_main.py b/public/acloud_main.py
index 31118f5..6f01906 100644
--- a/public/acloud_main.py
+++ b/public/acloud_main.py
@@ -131,6 +131,8 @@
from acloud.public.actions import create_goldfish_action
from acloud.pull import pull
from acloud.pull import pull_args
+from acloud.restart import restart
+from acloud.restart import restart_args
from acloud.setup import setup
from acloud.setup import setup_args
@@ -169,6 +171,7 @@
delete_args.CMD_DELETE,
reconnect_args.CMD_RECONNECT,
pull_args.CMD_PULL,
+ restart_args.CMD_RESTART,
])
parser = argparse.ArgumentParser(
description=__doc__,
@@ -243,6 +246,9 @@
# Command "reconnect"
subparser_list.append(reconnect_args.GetReconnectArgParser(subparsers))
+ # Command "restart"
+ subparser_list.append(restart_args.GetRestartArgParser(subparsers))
+
# Command "powerwash"
subparser_list.append(powerwash_args.GetPowerwashArgParser(subparsers))
@@ -429,6 +435,8 @@
list_instances.Run(args)
elif args.which == reconnect_args.CMD_RECONNECT:
reconnect.Run(args)
+ elif args.which == restart_args.CMD_RESTART:
+ report = restart.Run(args)
elif args.which == powerwash_args.CMD_POWERWASH:
report = powerwash.Run(args)
elif args.which == pull_args.CMD_PULL:
diff --git a/public/actions/common_operations.py b/public/actions/common_operations.py
index 6f60dca..9c50646 100644
--- a/public/actions/common_operations.py
+++ b/public/actions/common_operations.py
@@ -32,14 +32,16 @@
logger = logging.getLogger(__name__)
+_ACLOUD_BOOT_UP_ERROR = "ACLOUD_BOOT_UP_ERROR"
+_ACLOUD_DOWNLOAD_ARTIFACT_ERROR = "ACLOUD_DOWNLOAD_ARTIFACT_ERROR"
+# Error type of GCE quota error.
+_GCE_QUOTA_ERROR = "GCE_QUOTA_ERROR"
_DICT_ERROR_TYPE = {
constants.STAGE_INIT: "ACLOUD_INIT_ERROR",
constants.STAGE_GCE: "ACLOUD_CREATE_GCE_ERROR",
- constants.STAGE_ARTIFACT: "ACLOUD_DOWNLOAD_ARTIFACT_ERROR",
- constants.STAGE_BOOT_UP: "ACLOUD_BOOT_UP_ERROR",
+ constants.STAGE_ARTIFACT: _ACLOUD_DOWNLOAD_ARTIFACT_ERROR,
+ constants.STAGE_BOOT_UP: _ACLOUD_BOOT_UP_ERROR,
}
-# Error type of GCE quota error.
-_GCE_QUOTA_ERROR = "GCE_QUOTA_ERROR"
def CreateSshKeyPairIfNecessary(cfg):
@@ -274,7 +276,9 @@
ssh_user=constants.GCE_USER,
extra_args_ssh_tunnel=cfg.extra_args_ssh_tunnel)
if device.instance_name in failures:
- reporter.SetErrorType(_DICT_ERROR_TYPE[device.stage])
+ reporter.SetErrorType(_ACLOUD_BOOT_UP_ERROR)
+ if device.stage:
+ reporter.SetErrorType(_DICT_ERROR_TYPE[device.stage])
reporter.AddData(key="devices_failing_boot", value=device_dict)
reporter.AddError(str(failures[device.instance_name]))
else:
@@ -282,6 +286,8 @@
except (errors.DriverError, errors.CheckGCEZonesQuotaError) as e:
if isinstance(e, errors.CheckGCEZonesQuotaError):
reporter.SetErrorType(_GCE_QUOTA_ERROR)
+ if isinstance(e, errors.DownloadArtifactError):
+ reporter.SetErrorType(_ACLOUD_DOWNLOAD_ARTIFACT_ERROR)
reporter.AddError(str(e))
reporter.SetStatus(report.Status.FAIL)
return reporter
diff --git a/public/actions/remote_instance_cf_device_factory.py b/public/actions/remote_instance_cf_device_factory.py
index 786a49f..cfd36aa 100644
--- a/public/actions/remote_instance_cf_device_factory.py
+++ b/public/actions/remote_instance_cf_device_factory.py
@@ -31,8 +31,10 @@
logger = logging.getLogger(__name__)
-# bootloader is one file required to launch AVD.
+# bootloader and kernel are files required to launch AVD.
_BOOTLOADER = "bootloader"
+_KERNEL = "kernel"
+_ARTIFACT_FILES = ["*.img", _BOOTLOADER, _KERNEL]
_HOME_FOLDER = os.path.expanduser("~")
@@ -52,7 +54,7 @@
"""
def __init__(self, avd_spec, local_image_artifact=None,
cvd_host_package_artifact=None):
- super(RemoteInstanceDeviceFactory, self).__init__(avd_spec, local_image_artifact)
+ super().__init__(avd_spec, local_image_artifact)
self._cvd_host_package_artifact = cvd_host_package_artifact
# pylint: disable=broad-except
@@ -279,10 +281,11 @@
except IOError:
# Older builds may not have a required_images file. In this case
# we fall back to *.img.
- artifact_files = [
- os.path.basename(image) for image in
- glob.glob(os.path.join(images_dir, "*.img"))]
- artifact_files.append(_BOOTLOADER)
+ artifact_files = []
+ for file_name in _ARTIFACT_FILES:
+ artifact_files.extend(
+ os.path.basename(image) for image in glob.glob(
+ os.path.join(images_dir, file_name)))
cmd = ("tar -cf - --lzop -S -C {images_dir} {artifact_files} | "
"{ssh_cmd} -- tar -xf - --lzop -S".format(
images_dir=images_dir,
diff --git a/public/actions/remote_instance_cf_device_factory_test.py b/public/actions/remote_instance_cf_device_factory_test.py
index 12a9e30..34dd875 100644
--- a/public/actions/remote_instance_cf_device_factory_test.py
+++ b/public/actions/remote_instance_cf_device_factory_test.py
@@ -288,10 +288,10 @@
# Test local image get from local folder case.
fake_image = None
- self.Patch(glob, "glob", return_value=["fake.img"])
+ self.Patch(glob, "glob", side_effect=[["fake.img"], ["bootloader"], ["kernel"]])
factory._UploadArtifacts(fake_image, fake_host_package, fake_local_image_dir)
expected_cmd = (
- "tar -cf - --lzop -S -C %s fake.img bootloader | "
+ "tar -cf - --lzop -S -C %s fake.img bootloader kernel | "
"%s -- tar -xf - --lzop -S" %
(fake_local_image_dir, factory._ssh.GetBaseCmd(constants.SSH_BIN)))
mock_shell.assert_called_once_with(expected_cmd)
diff --git a/public/config.py b/public/config.py
index 85d7d43..02ab4f0 100755
--- a/public/config.py
+++ b/public/config.py
@@ -269,9 +269,6 @@
parsed_args.service_account_json_private_key_path)
if parsed_args.which == "create_gf" and parsed_args.base_image:
self.stable_goldfish_host_image_name = parsed_args.base_image
- if parsed_args.which == create_args.CMD_CREATE and not self.hw_property:
- flavor = parsed_args.flavor or constants.FLAVOR_PHONE
- self.hw_property = self.common_hw_property_map.get(flavor, "")
if parsed_args.which in [create_args.CMD_CREATE, "create_cf"]:
if parsed_args.network:
self.network = parsed_args.network
diff --git a/public/config_test.py b/public/config_test.py
index cbe7a4b..be57870 100644
--- a/public/config_test.py
+++ b/public/config_test.py
@@ -276,25 +276,8 @@
config.AcloudConfigManager.LoadConfigFromProtocolBuffer(
self.config_file, internal_config_pb2.InternalConfig)
- def testOverrideWithHWProperty(self):
- """Test override hw property by flavor type."""
- # test override with an exist flavor.
- self.cfg.hw_property = None
- args = mock.MagicMock()
- args.flavor = "phone"
- args.which = "create"
- self.cfg.OverrideWithArgs(args)
- self.assertEqual(self.cfg.hw_property,
- "cpu:2,resolution:1080x1920,dpi:420,memory:4g,disk:8g")
-
- # test override with a nonexistent flavor.
- self.cfg.hw_property = None
- args = mock.MagicMock()
- args.flavor = "non-exist-flavor"
- args.which = "create"
- self.cfg.OverrideWithArgs(args)
- self.assertEqual(self.cfg.hw_property, "")
-
+ def testOverrideWithArgs(self):
+ """Test OverrideWithArgs."""
# test override zone.
self.cfg.zone = "us-central1-f"
args = mock.MagicMock()
diff --git a/restart/__init__.py b/restart/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/restart/__init__.py
diff --git a/restart/restart.py b/restart/restart.py
new file mode 100644
index 0000000..43253d5
--- /dev/null
+++ b/restart/restart.py
@@ -0,0 +1,62 @@
+# Copyright 2021 - 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"""Restart entry point.
+
+This command will restart the CF AVD from a remote instance.
+"""
+
+from __future__ import print_function
+
+from acloud import errors
+from acloud.list import list as list_instances
+from acloud.public import config
+from acloud.public import report
+
+
+def RestartFromInstance(instance, instance_id):
+ """Restart AVD from remote CF instance.
+
+ Args:
+ instance: list.Instance() object.
+ instance_id: Integer of the instance id.
+
+ Returns:
+ A Report instance.
+ """
+ # TODO(162382338): rewrite this function to restart AVD from the remote instance.
+ print("We will restart AVD id (%s) from the instance: %s."
+ % (instance_id, instance.name))
+ return report.Report(command="restart")
+
+
+def Run(args):
+ """Run restart.
+
+ After restart command executed, tool will return one Report instance.
+
+ Args:
+ args: Namespace object from argparse.parse_args.
+
+ Returns:
+ A Report instance.
+
+ Raises:
+ errors.CommandArgError: Lack the instance_name in args.
+ """
+ cfg = config.GetAcloudConfig(args)
+ if args.instance_name:
+ instance = list_instances.GetInstancesFromInstanceNames(
+ cfg, [args.instance_name])
+ return RestartFromInstance(instance[0], args.instance_id)
+ raise errors.CommandArgError("Please assign the '--instance-name' in your command.")
diff --git a/restart/restart_args.py b/restart/restart_args.py
new file mode 100644
index 0000000..bc114d3
--- /dev/null
+++ b/restart/restart_args.py
@@ -0,0 +1,59 @@
+# Copyright 2021 - 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"""Restart args.
+
+Defines the restart arg parser that holds restart specific args.
+"""
+import argparse
+
+
+CMD_RESTART = "restart"
+
+
+def GetRestartArgParser(subparser):
+ """Return the restart arg parser.
+
+ Args:
+ subparser: argparse.ArgumentParser that is attached to main acloud cmd.
+
+ Returns:
+ argparse.ArgumentParser with restart options defined.
+ """
+ restart_parser = subparser.add_parser(CMD_RESTART)
+ restart_parser.required = False
+ restart_parser.set_defaults(which=CMD_RESTART)
+ restart_group = restart_parser.add_mutually_exclusive_group()
+ restart_group.add_argument(
+ "--instance-name",
+ dest="instance_name",
+ type=str,
+ required=False,
+ help="The name of the remote instance that need to restart the AVDs.")
+ # TODO(b/118439885): Old arg formats to support transition, delete when
+ # transistion is done.
+ restart_group.add_argument(
+ "--instance_name",
+ dest="instance_name",
+ type=str,
+ required=False,
+ help=argparse.SUPPRESS)
+ restart_parser.add_argument(
+ "--instance-id",
+ dest="instance_id",
+ type=int,
+ required=False,
+ default=1,
+ help="The instance id of the remote instance that need to be restart.")
+
+ return restart_parser