Snap for 6001391 from 10b111c8044faf0acf0be504fc630c921c758134 to qt-aml-resolv-release
Change-Id: I6501f8ae18b374eeaf2a0eece0ca4176264b79df
diff --git a/README.md b/README.md
index 8fcbb7c..e9e9c06 100755
--- a/README.md
+++ b/README.md
@@ -29,9 +29,6 @@
You should now be able to call acloud from anywhere.
-> If you're a googler, please check https://goto.google.com/acloud-googler-setup
-> to ensure a smooth setup experience.
-
### **Basic Usage**
Acloud commands take the following form:
@@ -45,6 +42,7 @@
* [delete](#delete)
* [reconnect](#reconnect)
* [setup](#setup)
+* [pull](#pull)
#### **create**
@@ -107,6 +105,10 @@
disk in a key:value format like so:
`cpu:2,resolution:1280x700,dpi:160,memory:2g,disk:2g`
+* `--reuse-gce`: 'cuttlefish only' This can help users use their own instance.
+Reusing specific gce instance if `--reuse-gce` [instance-name] is provided.
+Select one gce instance to reuse if `--reuse-gce` is provided.
+
The full list of options are available via `--help`
> $ acloud create --help
@@ -170,6 +172,27 @@
> $ acloud reconnect --instance-names [instance-name]
+### **pull**
+
+Pull will provide all log files to download or show in screen. It is helpful
+to debug about AVD boot up fail or AVD has abnromal behaviors.
+
+Cheatsheet:
+
+* Pull logs from a sole instance or prompt user to choose one to pull if where
+are more than one active instances.
+
+> $ acloud pull
+
+* Pull logs from the specific instance.
+
+> $ acloud pull --instance-name "instance-name"
+
+* Pull a specific log file from a specific instance
+
+> $ acloud pull --instance-name "instance-name" --file-name "file-name"
+
+
#### **setup**
Setup will walk you through the steps needed to set up your local host to
diff --git a/create/local_image_local_instance.py b/create/local_image_local_instance.py
index 2b9f96e..25871ed 100644
--- a/create/local_image_local_instance.py
+++ b/create/local_image_local_instance.py
@@ -38,11 +38,9 @@
import json
import logging
import os
-import re
import shutil
import subprocess
import sys
-import tempfile
from acloud import errors
from acloud.create import base_avd_create
@@ -50,13 +48,13 @@
from acloud.internal import constants
from acloud.internal.lib import utils
from acloud.internal.lib.adb_tools import AdbTools
+from acloud.list import list as list_instance
from acloud.list import instance
from acloud.public import report
logger = logging.getLogger(__name__)
-_ACLOUD_CVD_TEMP = os.path.join(tempfile.gettempdir(), "acloud_cvd_temp")
_CMD_LAUNCH_CVD_ARGS = (" -daemon -cpus %s -x_res %s -y_res %s -dpi %s "
"-memory_mb %s -system_image_dir %s "
"-instance_dir %s")
@@ -65,16 +63,12 @@
_CONFIRM_RELAUNCH = ("\nCuttlefish AVD[id:%d] is already running. \n"
"Enter 'y' to terminate current instance and launch a new "
"instance, enter anything else to exit out[y/N]: ")
-_CVD_RUNTIME_FOLDER_NAME = "cuttlefish_runtime"
-_CVD_CONFIG_NAME = "cuttlefish_config.json"
_ENV_ANDROID_HOST_OUT = "ANDROID_HOST_OUT"
_ENV_CVD_HOME = "HOME"
_ENV_CUTTLEFISH_INSTANCE = "CUTTLEFISH_INSTANCE"
_LAUNCH_CVD_TIMEOUT_SECS = 60 # default timeout as 60 seconds
_LAUNCH_CVD_TIMEOUT_ERROR = ("Cuttlefish AVD launch timeout, did not complete "
"within %d secs.")
-_LOCAL_INSTANCE_HOME = "instance_home_%s"
-_RE_LOCAL_CVD_PORT = re.compile(r"^127\.0\.0\.1:65(?P<cvd_port_suffix>\d{2})\s+")
_VIRTUAL_DISK_PATHS = "virtual_disk_paths"
@@ -157,7 +151,8 @@
return avd_spec.local_image_dir, host_bins_path
- def PrepareLaunchCVDCmd(self, launch_cvd_path, hw_property, system_image_dir,
+ @staticmethod
+ def PrepareLaunchCVDCmd(launch_cvd_path, hw_property, system_image_dir,
local_instance_id):
"""Prepare launch_cvd command.
@@ -173,7 +168,7 @@
Returns:
String, launch_cvd cmd.
"""
- instance_dir = self.GetLocalInstanceRuntimeDir(local_instance_id)
+ instance_dir = instance.GetLocalInstanceRuntimeDir(local_instance_id)
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"], system_image_dir,
@@ -231,7 +226,8 @@
_LAUNCH_CVD_TIMEOUT_ERROR % timeout_secs)
timeout_exception(self._LaunchCvd)(cmd, local_instance_id)
- def _StopCvd(self, host_bins_path, local_instance_id):
+ @staticmethod
+ def _StopCvd(host_bins_path, local_instance_id):
"""Execute stop_cvd to stop cuttlefish instance.
Args:
@@ -244,7 +240,7 @@
with open(os.devnull, "w") as dev_null:
cvd_env = os.environ.copy()
cvd_env[constants.ENV_CUTTLEFISH_CONFIG_FILE] = os.path.join(
- self.GetLocalInstanceRuntimeDir(local_instance_id),
+ instance.GetLocalInstanceRuntimeDir(local_instance_id),
constants.CUTTLEFISH_CONFIG_FILE)
subprocess.check_call(
utils.AddUserGroupsToCmd(
@@ -260,8 +256,9 @@
# sure adb device is completely gone since it will use the same adb port
adb_cmd.DisconnectAdb(retry=True)
+ @staticmethod
@utils.TimeExecute(function_description="Waiting for AVD(s) to boot up")
- def _LaunchCvd(self, cmd, local_instance_id):
+ def _LaunchCvd(cmd, local_instance_id):
"""Execute Launch CVD.
Kick off the launch_cvd command and log the output.
@@ -275,8 +272,8 @@
"""
# Delete the cvd home/runtime temp if exist. The runtime folder is
# under the cvd home dir, so we only delete them from home dir.
- cvd_home_dir = self.GetLocalInstanceHomeDir(local_instance_id)
- cvd_runtime_dir = self.GetLocalInstanceRuntimeDir(local_instance_id)
+ cvd_home_dir = instance.GetLocalInstanceHomeDir(local_instance_id)
+ cvd_runtime_dir = instance.GetLocalInstanceRuntimeDir(local_instance_id)
shutil.rmtree(cvd_home_dir, ignore_errors=True)
os.makedirs(cvd_runtime_dir)
@@ -303,31 +300,6 @@
utils.TextColors.WARNING)
@staticmethod
- def GetLocalInstanceHomeDir(local_instance_id):
- """Get instance home dir
-
- Args:
- local_instance_id: Integer of instance id.
-
- Return:
- String, path of instance home dir.
- """
- return os.path.join(_ACLOUD_CVD_TEMP,
- _LOCAL_INSTANCE_HOME % local_instance_id)
-
- def GetLocalInstanceRuntimeDir(self, local_instance_id):
- """Get instance runtime dir
-
- Args:
- local_instance_id: Integer of instance id.
-
- Return:
- String, path of instance runtime dir.
- """
- return os.path.join(self.GetLocalInstanceHomeDir(local_instance_id),
- _CVD_RUNTIME_FOLDER_NAME)
-
- @staticmethod
def IsLocalCVDRunning(local_instance_id):
"""Check if the AVD with specific instance id is running
@@ -340,7 +312,8 @@
local_ports = instance.GetLocalPortsbyInsId(local_instance_id)
return AdbTools(local_ports.adb_port).IsAdbConnected()
- def IsLocalImageOccupied(self, local_image_dir):
+ @staticmethod
+ def IsLocalImageOccupied(local_image_dir):
"""Check if the given image path is being used by a running CVD process.
Args:
@@ -349,10 +322,10 @@
Return:
Integer of instance id which using the same image path.
"""
- local_cvd_ids = self._GetActiveCVDIds()
+ local_cvd_ids = list_instance.GetActiveCVDIds()
for cvd_id in local_cvd_ids:
- cvd_config_path = os.path.join(self.GetLocalInstanceRuntimeDir(
- cvd_id), _CVD_CONFIG_NAME)
+ cvd_config_path = os.path.join(instance.GetLocalInstanceRuntimeDir(
+ cvd_id), constants.CUTTLEFISH_CONFIG_FILE)
if not os.path.isfile(cvd_config_path):
continue
with open(cvd_config_path, "r") as config_file:
@@ -361,24 +334,3 @@
if local_image_dir in disk_path:
return cvd_id
return None
-
- @staticmethod
- def _GetActiveCVDIds():
- """Get active cvd ids from adb devices.
-
- The adb port of local instance will be decided according to instance id.
- The rule of adb port will be '6520 + [instance id] - 1'. So we grep last
- two digits of port and calculate the instance id.
-
- Return:
- List of cvd id.
- """
- local_cvd_ids = []
- adb_cmd = [constants.ADB_BIN, "devices"]
- device_info = subprocess.check_output(adb_cmd)
- for device in device_info.splitlines():
- match = _RE_LOCAL_CVD_PORT.match(device)
- if match:
- cvd_serial = match.group("cvd_port_suffix")
- local_cvd_ids.append(int(cvd_serial) - 19)
- return local_cvd_ids
diff --git a/create/local_image_local_instance_test.py b/create/local_image_local_instance_test.py
index fdc7618..1007739 100644
--- a/create/local_image_local_instance_test.py
+++ b/create/local_image_local_instance_test.py
@@ -19,6 +19,7 @@
import mock
from acloud.create import local_image_local_instance
+from acloud.list import instance
from acloud.internal import constants
from acloud.internal.lib import utils
@@ -41,8 +42,7 @@
self.local_image_local_instance = local_image_local_instance.LocalImageLocalInstance()
# pylint: disable=protected-access
- @mock.patch.object(local_image_local_instance.LocalImageLocalInstance,
- "GetLocalInstanceRuntimeDir")
+ @mock.patch.object(instance, "GetLocalInstanceRuntimeDir")
@mock.patch.object(utils, "CheckUserInGroups")
def testPrepareLaunchCVDCmd(self, mock_usergroups, mock_cvd_dir):
"""test PrepareLaunchCVDCmd."""
diff --git a/create/local_image_remote_instance.py b/create/local_image_remote_instance.py
index d741a4f..20d03aa 100644
--- a/create/local_image_remote_instance.py
+++ b/create/local_image_remote_instance.py
@@ -54,8 +54,7 @@
Return:
A string, the path to the host package.
"""
- dirs_to_check = filter(None,
- [os.environ.get(constants.ENV_ANDROID_HOST_OUT)])
+ dirs_to_check = list(filter(None, [os.environ.get(constants.ENV_ANDROID_HOST_OUT)]))
dist_dir = utils.GetDistDir()
if dist_dir:
dirs_to_check.append(dist_dir)
diff --git a/delete/delete.py b/delete/delete.py
index 8318c37..64ca254 100644
--- a/delete/delete.py
+++ b/delete/delete.py
@@ -28,7 +28,6 @@
from acloud.internal import constants
from acloud.internal.lib import utils
from acloud.list import list as list_instances
-from acloud.list import instance as instance_class
from acloud.public import config
from acloud.public import device_driver
from acloud.public import report
@@ -49,11 +48,11 @@
Try to get directory of "run_cvd" by "ps -o command -p <pid>." command.
For example: "/tmp/bin/run_cvd"
- Raises:
- errors.NoExecuteCmd: Can't find stop_cvd.
-
Returns:
String of stop_cvd file path.
+
+ Raises:
+ errors.NoExecuteCmd: Can't find stop_cvd.
"""
process_id = subprocess.check_output(_COMMAND_GET_PROCESS_ID)
process_info = subprocess.check_output(
@@ -103,7 +102,7 @@
remote_instance_list = []
for instance in instances_to_delete:
if instance.islocal:
- delete_report = DeleteLocalInstance()
+ delete_report = DeleteLocalInstance(instance, delete_report)
else:
remote_instance_list.append(instance.name)
# Delete ssvnc viewer
@@ -150,32 +149,42 @@
@utils.TimeExecute(function_description="Deleting local instances",
result_evaluator=utils.ReportEvaluator)
-def DeleteLocalInstance():
+def DeleteLocalInstance(instance, delete_report=None):
"""Delete local instance.
Delete local instance with stop_cvd command and write delete instance
information to report.
+ Args:
+ instance: instance.LocalInstance object.
+ delete_report: Report object.
+
Returns:
A Report instance.
"""
- delete_report = report.Report(command="delete")
+ if not delete_report:
+ delete_report = report.Report(command="delete")
+
try:
with open(os.devnull, "w") as dev_null:
+ cvd_env = os.environ.copy()
+ if instance.instance_dir:
+ cvd_env[constants.ENV_CUTTLEFISH_CONFIG_FILE] = os.path.join(
+ instance.instance_dir, constants.CUTTLEFISH_CONFIG_FILE)
subprocess.check_call(
utils.AddUserGroupsToCmd(_GetStopCvd(),
constants.LIST_CF_USER_GROUPS),
- stderr=dev_null, stdout=dev_null, shell=True)
+ stderr=dev_null, stdout=dev_null, shell=True, env=cvd_env)
delete_report.SetStatus(report.Status.SUCCESS)
device_driver.AddDeletionResultToReport(
- delete_report, [constants.LOCAL_INS_NAME], failed=[],
+ delete_report, [instance.name], failed=[],
error_msgs=[],
resource_name="instance")
+ CleanupSSVncviewer(instance.vnc_port)
except subprocess.CalledProcessError as e:
delete_report.AddError(str(e))
delete_report.SetStatus(report.Status.FAIL)
- # Only CF supports local instances so assume it's a CF VNC port.
- CleanupSSVncviewer(constants.CF_VNC_PORT)
+
return delete_report
@@ -192,16 +201,12 @@
A Report instance.
"""
cfg = config.GetAcloudConfig(args)
- remote_instances_to_delete = args.instance_names
+ instances_to_delete = args.instance_names
- if remote_instances_to_delete:
- return DeleteRemoteInstances(cfg, remote_instances_to_delete)
-
- if args.local_instance:
- if instance_class.LocalInstance():
- return DeleteLocalInstance()
- print("There is no local instance AVD to delete.")
- return report.Report(command="delete")
+ if instances_to_delete:
+ return DeleteInstances(cfg,
+ list_instances.GetInstancesFromInstanceNames(
+ cfg, instances_to_delete))
if args.adb_port:
return DeleteInstances(
diff --git a/delete/delete_args.py b/delete/delete_args.py
index dc9c106..9058d28 100644
--- a/delete/delete_args.py
+++ b/delete/delete_args.py
@@ -40,8 +40,8 @@
dest="instance_names",
nargs="+",
required=False,
- help="The names of the remote instances that need to delete, "
- "separated by spaces, e.g. --instance-names instance-1 instance-2")
+ help="The names of the instances that need to delete, "
+ "separated by spaces, e.g. --instance-names instance-1 local-instance-1")
delete_group.add_argument(
"--all",
action="store_true",
@@ -49,12 +49,6 @@
required=False,
help="If more than 1 AVD instance is found, delete them all.")
delete_group.add_argument(
- "--local-instance",
- action="store_true",
- dest="local_instance",
- required=False,
- help="Only delete the local instance.")
- delete_group.add_argument(
"--adb-port", "-p",
type=int,
dest="adb_port",
diff --git a/delete/delete_test.py b/delete/delete_test.py
index 916cd22..bb7c10e 100644
--- a/delete/delete_test.py
+++ b/delete/delete_test.py
@@ -31,7 +31,7 @@
@mock.patch("subprocess.check_output")
def testGetStopcvd(self, mock_subprocess, mock_path_exist):
"""Test _GetStopCvd."""
- mock_subprocess.side_effect = ["fack_id",
+ mock_subprocess.side_effect = ["fake_id",
"/tmp/bin/run_cvd"]
expected_value = "/tmp/bin/stop_cvd"
self.assertEqual(expected_value, delete._GetStopCvd())
@@ -41,7 +41,10 @@
def testDeleteLocalInstance(self, mock_subprocess, mock_get_stopcvd):
"""Test DeleteLocalInstance."""
mock_subprocess.return_value = True
- delete_report = delete.DeleteLocalInstance()
+ instance_object = mock.MagicMock()
+ instance_object.instance_dir = "fake_instance_dir"
+ instance_object.name = "local-instance"
+ delete_report = delete.DeleteLocalInstance(instance_object)
self.assertEqual(delete_report.data, {
"deleted": [
{
diff --git a/errors.py b/errors.py
index d87bf83..d77ed28 100644
--- a/errors.py
+++ b/errors.py
@@ -32,6 +32,10 @@
"""Error raised when a GCE operation timedout."""
+class GetGceZoneError(DriverError):
+ """Can't get GCE zones info."""
+
+
class HttpError(DriverError):
"""Error related to http requests."""
@@ -241,3 +245,7 @@
class UnsupportedLocalInstanceId(Exception):
"""Unsupported local instance id."""
+
+
+class InvalidInstanceDir(Exception):
+ """Invalid instance dir."""
diff --git a/internal/constants.py b/internal/constants.py
index 895a312..9d91677 100755
--- a/internal/constants.py
+++ b/internal/constants.py
@@ -113,6 +113,7 @@
COMMAND_PS = ["ps", "aux"]
CMD_LAUNCH_CVD = "launch_cvd"
+CMD_PGREP = "pgrep"
CMD_STOP_CVD = "stop_cvd"
CMD_RUN_CVD = "run_cvd"
ENV_ANDROID_BUILD_TOP = "ANDROID_BUILD_TOP"
@@ -144,6 +145,7 @@
INS_KEY_IS_LOCAL = "remote"
INS_STATUS_RUNNING = "RUNNING"
LOCAL_INS_NAME = "local-instance"
+LOCAL_INS_HOME_PREFIX = "instance_home_"
ENV_CUTTLEFISH_CONFIG_FILE = "CUTTLEFISH_CONFIG_FILE"
CUTTLEFISH_CONFIG_FILE = "cuttlefish_config.json"
@@ -151,7 +153,6 @@
TOOL_NAME = "acloud"
EXIT_BY_USER = 1
EXIT_BY_ERROR = -99
-RE_LAUNCH_CVD_PATTERN = "launch_cvd.*%(arg_name)s %(arg_value)s"
# For reuse gce instance
SELECT_ONE_GCE_INSTANCE = "select_one_gce_instance"
diff --git a/internal/lib/cvd_compute_client_multi_stage.py b/internal/lib/cvd_compute_client_multi_stage.py
index 44c0e90..98783a4 100644
--- a/internal/lib/cvd_compute_client_multi_stage.py
+++ b/internal/lib/cvd_compute_client_multi_stage.py
@@ -175,7 +175,7 @@
if avd_spec:
if avd_spec.instance_name_to_reuse:
self.StopCvd()
- self.CleanUpImages()
+ self.CleanUp()
return instance
# TODO: Remove following code after create_cf deprecated.
@@ -277,17 +277,18 @@
except subprocess.CalledProcessError as e:
logger.debug("Failed to stop_cvd (possibly no running device): %s", e)
- def CleanUpImages(self):
- """Clean up the images on the existing instance.
+ def CleanUp(self):
+ """Clean up the files/folders on the existing instance.
- If previous AVD have these images, reusing the instance may have
- side effects if didn't clean it.
+ If previous AVD have these files/folders, reusing the instance may have
+ side effects if not cleaned. The path in the instance is /home/vsoc-01/*
+ if the GCE user is vsoc-01.
"""
- ssh_command = "/bin/rm ./*.img"
+ ssh_command = "'/bin/rm -rf /home/%s/*'" % constants.GCE_USER
try:
self._ssh.Run(ssh_command)
except subprocess.CalledProcessError as e:
- logger.debug("Failed to clean up the images failed: %s", e)
+ logger.debug("Failed to clean up the files/folders: %s", e)
@utils.TimeExecute(function_description="Launching AVD(s) and waiting for boot up",
result_evaluator=utils.BootEvaluator)
diff --git a/internal/lib/gcompute_client.py b/internal/lib/gcompute_client.py
index 8c56ea7..bde5ac2 100755
--- a/internal/lib/gcompute_client.py
+++ b/internal/lib/gcompute_client.py
@@ -31,6 +31,7 @@
import getpass
import logging
import os
+import re
from acloud import errors
from acloud.internal import constants
@@ -47,6 +48,7 @@
_SSH_KEYS_NAME = "sshKeys"
_ITEMS = "items"
_METADATA = "metadata"
+_ZONE_RE = re.compile(r"^zones/(?P<zone>.+)")
BASE_DISK_ARGS = {
"type": "PERSISTENT",
@@ -801,11 +803,41 @@
" onHostMaintenance: %s\n",
str(automatic_restart).lower(), on_host_maintenance)
- def ListInstances(self, zone, instance_filter=None):
- """List instances.
+ def ListInstances(self, instance_filter=None):
+ """List instances cross all zones.
+
+ Gcompute response instance. For example:
+ {
+ 'items':
+ {
+ 'zones/europe-west3-b':
+ {
+ 'warning':
+ {
+ 'message': "There are no results for scope
+ 'zones/europe-west3-b' on this page.",
+ 'code': 'NO_RESULTS_ON_PAGE',
+ 'data': [{'value': u'zones/europe-west3-b',
+ 'key': u'scope'}]
+ }
+ },
+ 'zones/asia-east1-b':
+ {
+ 'instances': [
+ {
+ 'name': 'ins-bc212dc8-userbuild-aosp-cf-x86-64-phone'
+ 'status': 'RUNNING',
+ 'cpuPlatform': 'Intel Broadwell',
+ 'startRestricted': False,
+ 'labels': {u'created_by': u'herbertxue'},
+ 'name': 'ins-bc212dc8-userbuild-aosp-cf-x86-64-phone',
+ ...
+ }]
+ }
+ }
+ }
Args:
- zone: A string, representing zone name. e.g. "us-central1-f"
instance_filter: A string representing a filter in format of
FIELD_NAME COMPARISON_STRING LITERAL_STRING
e.g. "name ne example-instance"
@@ -814,11 +846,17 @@
Returns:
A list of instances.
"""
- return self.ListWithMultiPages(
- api_resource=self.service.instances().list,
+ api = self.service.instances().aggregatedList(
project=self._project,
- zone=zone,
filter=instance_filter)
+ response = self.Execute(api)
+ instances_list = []
+ for instances_data in response["items"].values():
+ if "instances" in instances_data:
+ for instance in instances_data.get("instances"):
+ instances_list.append(instance)
+
+ return instances_list
def SetSchedulingInstances(self,
instances,
@@ -1346,7 +1384,7 @@
"Malformed response for GetSerialPortOutput: %s" % result)
return result["contents"]
- def GetInstanceNamesByIPs(self, ips, zone):
+ def GetInstanceNamesByIPs(self, ips):
"""Get Instance names by IPs.
This function will go through all instances, which
@@ -1355,14 +1393,13 @@
Args:
ips: A set of IPs.
- zone: String, name of the zone.
Returns:
A dictionary where key is IP and value is instance name or None
if instance is not found for the given IP.
"""
ip_name_map = dict.fromkeys(ips)
- for instance in self.ListInstances(zone):
+ for instance in self.ListInstances():
try:
ip = instance["networkInterfaces"][0]["accessConfigs"][0][
"natIP"]
@@ -1413,14 +1450,13 @@
self.WaitOnOperation(
operation, operation_scope=OperationScope.ZONE, scope_name=zone)
- def AddSshRsaInstanceMetadata(self, zone, user, ssh_rsa_path, instance):
+ def AddSshRsaInstanceMetadata(self, user, ssh_rsa_path, instance):
"""Add the public rsa key to the instance's metadata.
Confirm that the instance has this public key in the instance's
metadata, if not we will add this public key.
Args:
- zone: String, name of zone.
user: String, name of the user which the key belongs to.
ssh_rsa_path: String, The absolute path to public rsa key.
instance: String, representing instance name.
@@ -1430,11 +1466,87 @@
entry = "%s:%s" % (user, rsa)
logger.debug("New RSA entry: %s", entry)
+ zone = self.GetZoneByInstance(instance)
gce_instance = self.GetInstance(instance, zone)
metadata = gce_instance.get(_METADATA)
if RsaNotInMetadata(metadata, entry):
self.UpdateRsaInMetadata(zone, instance, metadata, entry)
+ def GetZoneByInstance(self, instance):
+ """Get the zone from instance name.
+
+ Gcompute response instance. For example:
+ {
+ 'items':
+ {
+ 'zones/europe-west3-b':
+ {
+ 'warning':
+ {
+ 'message': "There are no results for scope
+ 'zones/europe-west3-b' on this page.",
+ 'code': 'NO_RESULTS_ON_PAGE',
+ 'data': [{'value': u'zones/europe-west3-b',
+ 'key': u'scope'}]
+ }
+ },
+ 'zones/asia-east1-b':
+ {
+ 'instances': [
+ {
+ 'name': 'ins-bc212dc8-userbuild-aosp-cf-x86-64-phone'
+ 'status': 'RUNNING',
+ 'cpuPlatform': 'Intel Broadwell',
+ 'startRestricted': False,
+ 'labels': {u'created_by': u'herbertxue'},
+ 'name': 'ins-bc212dc8-userbuild-aosp-cf-x86-64-phone',
+ ...
+ }]
+ }
+ }
+ }
+
+ Args:
+ instance: String, representing instance name.
+
+ Raises:
+ errors.GetGceZoneError: Can't get zone from instance name.
+
+ Returns:
+ String of zone name.
+ """
+ api = self.service.instances().aggregatedList(
+ project=self._project,
+ filter="name=%s" % instance)
+ response = self.Execute(api)
+ for zone, instance_data in response["items"].items():
+ if "instances" in instance_data:
+ zone_match = _ZONE_RE.match(zone)
+ if zone_match:
+ return zone_match.group("zone")
+ raise errors.GetGceZoneError("Can't get zone from the instance name %s"
+ % instance)
+
+ def GetZonesByInstances(self, instances):
+ """Get the zone from instance name.
+
+ Args:
+ instances: List of strings, representing instance names.
+
+ Returns:
+ A dictionary that contains the name of all instances in the zone.
+ The key is the name of the zone, and the value is a list contains
+ the name of the instances.
+ """
+ zone_instances = {}
+ for instance in instances:
+ zone = self.GetZoneByInstance(instance)
+ if zone in zone_instances:
+ zone_instances[zone].append(instance)
+ else:
+ zone_instances[zone] = [instance]
+ return zone_instances
+
def CheckAccess(self):
"""Check if the user has read access to the cloud project.
@@ -1472,7 +1584,7 @@
# in the metadata. There may not be an actual ssh key value so
# that's why we filter for None to avoid an empty line in front.
ssh_key_item[_METADATA_KEY_VALUE] = "\n".join(
- filter(None, [ssh_key_item[_METADATA_KEY_VALUE], entry]))
+ list(filter(None, [ssh_key_item[_METADATA_KEY_VALUE], entry])))
else:
# Since there is no ssh key item in the metadata, we need to add it in.
ssh_key_item = {_METADATA_KEY: _SSH_KEYS_NAME,
diff --git a/internal/lib/gcompute_client_test.py b/internal/lib/gcompute_client_test.py
index 5f14f7e..d4f225d 100644
--- a/internal/lib/gcompute_client_test.py
+++ b/internal/lib/gcompute_client_test.py
@@ -482,35 +482,69 @@
def testListInstances(self):
"""Test ListInstances."""
- fake_token = "fake_next_page_token"
instance_1 = "instance_1"
instance_2 = "instance_2"
- response_1 = {"items": [instance_1], "nextPageToken": fake_token}
- response_2 = {"items": [instance_2]}
+ response = {"items": {'zones/fake_zone': {"instances": [instance_1, instance_2]}}}
self.Patch(
gcompute_client.ComputeClient,
"Execute",
- side_effect=[response_1, response_2])
+ side_effect=[response])
resource_mock = mock.MagicMock()
self.compute_client._service.instances = mock.MagicMock(
return_value=resource_mock)
- resource_mock.list = mock.MagicMock()
- instances = self.compute_client.ListInstances(self.ZONE)
+ resource_mock.aggregatedList = mock.MagicMock()
+ instances = self.compute_client.ListInstances()
calls = [
mock.call(
project=PROJECT,
- zone=self.ZONE,
- filter=None,
- pageToken=None),
- mock.call(
- project=PROJECT,
- zone=self.ZONE,
- filter=None,
- pageToken=fake_token),
+ filter=None),
]
- resource_mock.list.assert_has_calls(calls)
+ resource_mock.aggregatedList.assert_has_calls(calls)
self.assertEqual(instances, [instance_1, instance_2])
+ def testGetZoneByInstance(self):
+ """Test GetZoneByInstance."""
+ instance_1 = "instance_1"
+ response = {"items": {'zones/fake_zone': {"instances": [instance_1]}}}
+ self.Patch(
+ gcompute_client.ComputeClient,
+ "Execute",
+ side_effect=[response])
+ expected_zone = "fake_zone"
+ self.assertEqual(self.compute_client.GetZoneByInstance(instance_1),
+ expected_zone)
+
+ # Test unable to find 'zone' from instance name.
+ response = {"items": {'zones/fake_zone': {"warning": "No instances."}}}
+ self.Patch(
+ gcompute_client.ComputeClient,
+ "Execute",
+ side_effect=[response])
+ with self.assertRaises(errors.GetGceZoneError):
+ self.compute_client.GetZoneByInstance(instance_1)
+
+ def testGetZonesByInstances(self):
+ """Test GetZonesByInstances."""
+ instances = ["instance_1", "instance_2"]
+ # Test instances in the same zone.
+ self.Patch(
+ gcompute_client.ComputeClient,
+ "GetZoneByInstance",
+ side_effect=["zone_1", "zone_1"])
+ expected_result = {"zone_1": ["instance_1", "instance_2"]}
+ self.assertEqual(self.compute_client.GetZonesByInstances(instances),
+ expected_result)
+
+ # Test instances in different zones.
+ self.Patch(
+ gcompute_client.ComputeClient,
+ "GetZoneByInstance",
+ side_effect=["zone_1", "zone_2"])
+ expected_result = {"zone_1": ["instance_1"],
+ "zone_2": ["instance_2"]}
+ self.assertEqual(self.compute_client.GetZonesByInstances(instances),
+ expected_result)
+
@mock.patch.object(gcompute_client.ComputeClient, "GetImage")
@mock.patch.object(gcompute_client.ComputeClient, "GetNetworkUrl")
@mock.patch.object(gcompute_client.ComputeClient, "GetSubnetworkUrl")
@@ -1122,7 +1156,7 @@
"ListInstances",
return_value=[good_instance, bad_instance])
ip_name_map = self.compute_client.GetInstanceNamesByIPs(
- ips=["172.22.22.22", "172.22.22.23"], zone=self.ZONE)
+ ips=["172.22.22.22", "172.22.22.23"])
self.assertEqual(ip_name_map, {"172.22.22.22": "instance_1",
"172.22.22.23": None})
@@ -1330,6 +1364,8 @@
self.Patch(os.path, "exists", return_value=True)
m = mock.mock_open(read_data=self.SSHKEY)
self.Patch(gcompute_client.ComputeClient, "WaitOnOperation")
+ self.Patch(gcompute_client.ComputeClient, "GetZoneByInstance",
+ return_value="fake_zone")
resource_mock = mock.MagicMock()
self.compute_client._service.instances = mock.MagicMock(
return_value=resource_mock)
@@ -1341,7 +1377,6 @@
return_value=instance_metadata_key_not_exist)
with mock.patch("__builtin__.open", m):
self.compute_client.AddSshRsaInstanceMetadata(
- "fake_zone",
fake_user,
"/path/to/test_rsa.pub",
"fake_instance")
@@ -1358,7 +1393,6 @@
return_value=instance_metadata_key_exist)
with mock.patch("__builtin__.open", m):
self.compute_client.AddSshRsaInstanceMetadata(
- "fake_zone",
fake_user,
"/path/to/test_rsa.pub",
"fake_instance")
diff --git a/internal/lib/goldfish_compute_client.py b/internal/lib/goldfish_compute_client.py
index 926cdac..d9d1d20 100644
--- a/internal/lib/goldfish_compute_client.py
+++ b/internal/lib/goldfish_compute_client.py
@@ -122,6 +122,17 @@
raise errors.DeviceBootError(
"Emulator timed out while booting.")
+ @staticmethod
+ def GetKernelBuildArtifact(target):
+ if target == "kernel":
+ return "bzImage"
+ if target == "kernel_x86_64":
+ return "bzImage"
+ if target == "kernel_aarch64":
+ return "Image.gz"
+ raise errors.DeviceBootError(
+ "Don't know the artifact name for '%s' target" % target)
+
# pylint: disable=too-many-locals,arguments-differ
# TODO: Refactor CreateInstance to pass in an object instead of all these args.
def CreateInstance(self,
@@ -133,6 +144,7 @@
build_id,
kernel_branch=None,
kernel_build_id=None,
+ kernel_build_target=None,
emulator_branch=None,
emulator_build_id=None,
blank_data_disk_size_gb=None,
@@ -152,6 +164,7 @@
build_id: String, build id, a string, e.g. "2263051", "P2804227"
kernel_branch: String, kernel branch name.
kernel_build_id: String, kernel build id.
+ kernel_build_target: kernel target, e.g. "kernel_x86_64"
emulator_branch: String, emulator branch name, e.g."aosp-emu-master-dev"
emulator_build_id: String, emulator build id, a string, e.g. "2263051", "P2804227"
blank_data_disk_size_gb: Integer, size of the blank data disk in GB.
@@ -181,9 +194,12 @@
metadata["cvd_01_fetch_android_build_target"] = build_target
metadata["cvd_01_fetch_android_bid"] = "{branch}/{build_id}".format(
branch=branch, build_id=build_id)
- if kernel_branch and kernel_build_id:
+ if kernel_branch and kernel_build_id and kernel_build_target:
metadata["cvd_01_fetch_kernel_bid"] = "{branch}/{build_id}".format(
branch=kernel_branch, build_id=kernel_build_id)
+ metadata["cvd_01_fetch_kernel_build_target"] = kernel_build_target
+ metadata["cvd_01_fetch_kernel_build_artifact"] = (
+ self.GetKernelBuildArtifact(kernel_build_target))
metadata["cvd_01_use_custom_kernel"] = "true"
if emulator_branch and emulator_build_id:
metadata[
diff --git a/internal/lib/goldfish_compute_client_test.py b/internal/lib/goldfish_compute_client_test.py
index 8b67088..cfee21f 100644
--- a/internal/lib/goldfish_compute_client_test.py
+++ b/internal/lib/goldfish_compute_client_test.py
@@ -37,6 +37,8 @@
BUILD_ID = "2263051"
KERNEL_BRANCH = "kernel-p-dev-android-goldfish-4.14-x86-64"
KERNEL_BUILD_ID = "112233"
+ KERNEL_BUILD_TARGET = "kernel_x86_64"
+ KERNEL_BUILD_ARTIFACT = "bzImage"
EMULATOR_BRANCH = "aosp-emu-master-dev"
EMULATOR_BUILD_ID = "1234567"
DPI = 160
@@ -105,6 +107,8 @@
"cvd_01_fetch_kernel_bid":
"{branch}/{build_id}".format(
branch=self.KERNEL_BRANCH, build_id=self.KERNEL_BUILD_ID),
+ "cvd_01_fetch_kernel_build_target": self.KERNEL_BUILD_TARGET,
+ "cvd_01_fetch_kernel_build_artifact": self.KERNEL_BUILD_ARTIFACT,
"cvd_01_use_custom_kernel": "true",
"cvd_01_fetch_emulator_bid":
"{branch}/{build_id}".format(
@@ -120,8 +124,11 @@
self.goldfish_compute_client.CreateInstance(
self.INSTANCE, self.IMAGE, self.IMAGE_PROJECT, self.TARGET,
- self.BRANCH, self.BUILD_ID, self.KERNEL_BRANCH,
- self.KERNEL_BUILD_ID, self.EMULATOR_BRANCH,
+ self.BRANCH, self.BUILD_ID,
+ self.KERNEL_BRANCH,
+ self.KERNEL_BUILD_ID,
+ self.KERNEL_BUILD_TARGET,
+ self.EMULATOR_BRANCH,
self.EMULATOR_BUILD_ID, self.EXTRA_DATA_DISK_SIZE_GB, self.GPU,
extra_scopes=self.EXTRA_SCOPES,
tags=self.TAGS)
diff --git a/internal/lib/ssh.py b/internal/lib/ssh.py
index 92d94ab..590f1de 100755
--- a/internal/lib/ssh.py
+++ b/internal/lib/ssh.py
@@ -30,6 +30,7 @@
_SSH_IDENTITY = "-l %(login_user)s %(ip_addr)s"
_SSH_CMD_MAX_RETRY = 4
_SSH_CMD_RETRY_SLEEP = 3
+_WAIT_FOR_SSH_MAX_TIMEOUT = 20
def _SshCall(cmd, timeout=None):
@@ -217,9 +218,8 @@
raise errors.UnknownType("Don't support the execute bin %s." % execute_bin)
- @utils.TimeExecute(function_description="Waiting for SSH server")
- def WaitForSsh(self, timeout=20, max_retry=_SSH_CMD_MAX_RETRY):
- """Wait until the remote instance is ready to accept commands over SSH.
+ def CheckSshConnection(self, timeout):
+ """Run remote 'uptime' ssh command to check ssh connection.
Args:
timeout: Integer, the maximum time to wait for the command to respond.
@@ -229,18 +229,41 @@
"""
remote_cmd = [self.GetBaseCmd(constants.SSH_BIN)]
remote_cmd.append("uptime")
- for _ in range(max_retry):
- if _SshCall(" ".join(remote_cmd), timeout) == 0:
- return
+
+ if _SshCall(" ".join(remote_cmd), timeout) == 0:
+ return
raise errors.DeviceConnectionError(
"Ssh isn't ready in the remote instance.")
+ @utils.TimeExecute(function_description="Waiting for SSH server")
+ def WaitForSsh(self, timeout=_WAIT_FOR_SSH_MAX_TIMEOUT,
+ sleep_for_retry=_SSH_CMD_RETRY_SLEEP,
+ max_retry=_SSH_CMD_MAX_RETRY):
+ """Wait until the remote instance is ready to accept commands over SSH.
+
+ Args:
+ timeout: Integer, the maximum time in seconds to wait for the
+ command to respond.
+ sleep_for_retry: Integer, the sleep time in seconds for retry.
+ max_retry: Integer, the maximum number of retry.
+
+ Raises:
+ errors.DeviceConnectionError: Ssh isn't ready in the remote instance.
+ """
+ utils.RetryExceptionType(
+ exception_types=errors.DeviceConnectionError,
+ max_retries=max_retry,
+ functor=self.CheckSshConnection,
+ sleep_multiplier=sleep_for_retry,
+ retry_backoff_factor=utils.DEFAULT_RETRY_BACKOFF_FACTOR,
+ timeout=timeout)
+
def ScpPushFile(self, src_file, dst_file):
"""Scp push file to remote.
Args:
src_file: The source file path to be pulled.
- dst_file: The destiation file path the file is pulled to.
+ dst_file: The destination file path the file is pulled to.
"""
scp_command = [self.GetBaseCmd(constants.SCP_BIN)]
scp_command.append(src_file)
@@ -252,7 +275,7 @@
Args:
src_file: The source file path to be pulled.
- dst_file: The destiation file path the file is pulled to.
+ dst_file: The destination file path the file is pulled to.
"""
scp_command = [self.GetBaseCmd(constants.SCP_BIN)]
scp_command.append("%s@%s:%s" %(self._gce_user, self._ip, src_file))
diff --git a/internal/lib/ssh_test.py b/internal/lib/ssh_test.py
index 5ec6a3a..bddefd5 100644
--- a/internal/lib/ssh_test.py
+++ b/internal/lib/ssh_test.py
@@ -193,6 +193,19 @@
expected_ip = "1.1.1.1"
self.assertEqual(ssh_object._ip, expected_ip)
+ def testWaitForSsh(self):
+ """Test WaitForSsh."""
+ ssh_object = ssh.Ssh(ip=self.FAKE_IP,
+ gce_user=self.FAKE_SSH_USER,
+ ssh_private_key_path=self.FAKE_SSH_PRIVATE_KEY_PATH,
+ report_internal_ip=self.FAKE_REPORT_INTERNAL_IP)
+ self.Patch(ssh, "_SshCall", return_value=-1)
+ self.assertRaises(errors.DeviceConnectionError,
+ ssh_object.WaitForSsh,
+ timeout=1,
+ sleep_for_retry=1,
+ max_retry=1)
+
if __name__ == "__main__":
unittest.main()
diff --git a/internal/lib/utils.py b/internal/lib/utils.py
index af0d234..ff7e3a8 100755
--- a/internal/lib/utils.py
+++ b/internal/lib/utils.py
@@ -78,7 +78,6 @@
_VNC_BIN = "ssvnc"
_CMD_KILL = ["pkill", "-9", "-f"]
-_CMD_PGREP = "pgrep"
_CMD_SG = "sg "
_CMD_START_VNC = "%(bin)s vnc://127.0.0.1:%(port)d"
_CMD_INSTALL_SSVNC = "sudo apt-get --assume-yes install ssvnc"
@@ -1028,7 +1027,7 @@
"""
try:
with open(os.devnull, "w") as dev_null:
- subprocess.check_call([_CMD_PGREP, "-af", command],
+ subprocess.check_call([constants.CMD_PGREP, "-af", command],
stderr=dev_null, stdout=dev_null)
return True
except subprocess.CalledProcessError:
diff --git a/list/instance.py b/list/instance.py
index 005abcf..7b58792 100644
--- a/list/instance.py
+++ b/list/instance.py
@@ -33,6 +33,7 @@
import os
import re
import subprocess
+import tempfile
# pylint: disable=import-error
import dateutil.parser
@@ -46,6 +47,9 @@
logger = logging.getLogger(__name__)
+_ACLOUD_CVD_TEMP = os.path.join(tempfile.gettempdir(), "acloud_cvd_temp")
+_CVD_RUNTIME_FOLDER_NAME = "cuttlefish_runtime"
+_LOCAL_INSTANCE_HOME = "instance_home_%s"
_MSG_UNABLE_TO_CALCULATE = "Unable to calculate"
_RE_GROUP_ADB = "local_adb_port"
_RE_GROUP_VNC = "local_vnc_port"
@@ -62,6 +66,53 @@
constants.ADB_PORT])
+def GetLocalInstanceHomeDir(local_instance_id):
+ """Get local instance home dir accroding to instance id.
+
+ Args:
+ local_instance_id: Integer of instance id.
+
+ Return:
+ String, path of instance home dir.
+ """
+ return os.path.join(_ACLOUD_CVD_TEMP,
+ _LOCAL_INSTANCE_HOME % local_instance_id)
+
+
+def GetLocalInstanceRuntimeDir(local_instance_id):
+ """Get instance runtime dir
+
+ Args:
+ local_instance_id: Integer of instance id.
+
+ Return:
+ String, path of instance runtime dir.
+ """
+ return os.path.join(GetLocalInstanceHomeDir(local_instance_id),
+ _CVD_RUNTIME_FOLDER_NAME)
+
+
+def GetCuttlefishRuntimeConfig(local_instance_id):
+ """Get and parse cuttlefish_config.json.
+
+ Args:
+ local_instance_id: Integer of instance id.
+
+ Returns:
+ A dictionary that parsed from cuttlefish runtime config.
+
+ Raises:
+ errors.ConfigError: if file not found or config load failed.
+ """
+ runtime_cf_config_path = os.path.join(GetLocalInstanceRuntimeDir(
+ local_instance_id), constants.CUTTLEFISH_CONFIG_FILE)
+ if not os.path.exists(runtime_cf_config_path):
+ raise errors.ConfigError(
+ "file does not exist: %s" % runtime_cf_config_path)
+ with open(runtime_cf_config_path, "r") as cf_config:
+ return json.load(cf_config)
+
+
def GetLocalPortsbyInsId(local_instance_id):
"""Get vnc and adb port by local instance id.
@@ -105,21 +156,25 @@
class Instance(object):
"""Class to store data of instance."""
- def __init__(self):
- self._name = None
- self._fullname = None
- self._status = None
- self._display = None # Resolution and dpi
- self._ip = None
- self._adb_port = None # adb port which is forwarding to remote
- self._vnc_port = None # vnc port which is forwarding to remote
- self._ssh_tunnel_is_connected = None # True if ssh tunnel is still connected
- self._createtime = None
- self._elapsed_time = None
- self._avd_type = None
- self._avd_flavor = None
- self._is_local = None # True if this is a local instance
- self._device_information = None
+ def __init__(self, name, fullname, display, ip, status=None, adb_port=None,
+ vnc_port=None, ssh_tunnel_is_connected=None, createtime=None,
+ elapsed_time=None, avd_type=None, avd_flavor=None,
+ is_local=False, device_information=None):
+ self._name = name
+ self._fullname = fullname
+ self._status = status
+ self._display = display # Resolution and dpi
+ self._ip = ip
+ self._adb_port = adb_port # adb port which is forwarding to remote
+ self._vnc_port = vnc_port # vnc port which is forwarding to remote
+ # True if ssh tunnel is still connected
+ self._ssh_tunnel_is_connected = ssh_tunnel_is_connected
+ self._createtime = createtime
+ self._elapsed_time = elapsed_time
+ self._avd_type = avd_type
+ self._avd_flavor = avd_flavor
+ self._is_local = is_local # True if this is a local instance
+ self._device_information = device_information
def __repr__(self):
"""Return full name property for print."""
@@ -214,95 +269,73 @@
"""Return if it is a local instance."""
return self._is_local
+ @property
+ def adb_port(self):
+ """Return adb_port."""
+ return self._adb_port
+
+ @property
+ def vnc_port(self):
+ """Return vnc_port."""
+ return self._vnc_port
+
class LocalInstance(Instance):
"""Class to store data of local instance."""
- # pylint: disable=protected-access
- def __new__(cls):
+ def __init__(self, local_instance_id, x_res, y_res, dpi, create_time,
+ ins_dir=None):
"""Initialize a localInstance object.
- Gather local instance information from launch_cvd process.
-
- returns:
- Instance object if launch_cvd process is found otherwise return None.
+ Args:
+ local_instance_id: Integer of instance id.
+ x_res: Integer of x dimension.
+ y_res: Integer of y dimension.
+ dpi: Integer of dpi.
+ date_str: String of create time.
+ ins_dir: String, path of instance idr.
"""
- # Running instances on local is not supported on all OS.
- if not utils.IsSupportedPlatform():
- return None
+ display = ("%sx%s (%s)" % (x_res, y_res, dpi))
+ elapsed_time = _GetElapsedTime(create_time) if create_time else None
+ name = "%s-%d" % (constants.LOCAL_INS_NAME, local_instance_id)
+ local_ports = GetLocalPortsbyInsId(local_instance_id)
+ fullname = (_FULL_NAME_STRING %
+ {"device_serial": "127.0.0.1:%d" % local_ports.adb_port,
+ "instance_name": name,
+ "elapsed_time": elapsed_time})
+ adb_device = AdbTools(local_ports.adb_port)
+ device_information = None
+ if adb_device.IsAdbConnected():
+ device_information = adb_device.device_information
- process_output = subprocess.check_output(_COMMAND_PS_LAUNCH_CVD)
- for line in process_output.splitlines():
- match = _RE_RUN_CVD.match(line)
- if match:
- local_instance = Instance()
- cf_runtime_config_dict = GetCuttlefishRuntimeConfig()
- x_res = cf_runtime_config_dict["x_res"]
- y_res = cf_runtime_config_dict["y_res"]
- dpi = cf_runtime_config_dict["dpi"]
- date_str = match.group("date_str").strip()
- local_instance._name = constants.LOCAL_INS_NAME
- local_instance._createtime = date_str
- local_instance._elapsed_time = _GetElapsedTime(date_str)
- local_instance._fullname = (_FULL_NAME_STRING %
- {"device_serial": "127.0.0.1:%d" %
- constants.CF_ADB_PORT,
- "instance_name": local_instance._name,
- "elapsed_time": local_instance._elapsed_time})
- local_instance._avd_type = constants.TYPE_CF
- local_instance._ip = "127.0.0.1"
- local_instance._status = constants.INS_STATUS_RUNNING
- local_instance._adb_port = constants.CF_ADB_PORT
- local_instance._vnc_port = constants.CF_VNC_PORT
- local_instance._display = ("%sx%s (%s)" % (x_res, y_res, dpi))
- local_instance._is_local = True
- local_instance._ssh_tunnel_is_connected = True
+ super(LocalInstance, self).__init__(
+ name=name, fullname=fullname, display=display, ip="127.0.0.1",
+ status=constants.INS_STATUS_RUNNING, adb_port=local_ports.adb_port,
+ vnc_port=local_ports.vnc_port, createtime=create_time,
+ elapsed_time=elapsed_time, avd_type=constants.TYPE_CF,
+ is_local=True, device_information=device_information)
- adb_device = AdbTools(constants.CF_ADB_PORT)
- if adb_device.IsAdbConnected():
- local_instance._device_information = adb_device.device_information
- return local_instance
- return None
+ # LocalInstance class properties
+ self._instance_dir = ins_dir
-def GetCuttlefishRuntimeConfig():
- """Get and parse cuttlefish_config.json.
+ @property
+ def instance_dir(self):
+ """Return _instance_dir."""
+ return self._instance_dir
- Returns:
- Dict parsing json file from cuttlefish runtime config.
-
- Raises:
- errors.ConfigError: if file not found or config load failed.
- """
- runtime_cf_config_path = os.path.join(os.path.expanduser("~"),
- "cuttlefish_runtime",
- "cuttlefish_config.json")
- if os.path.exists(runtime_cf_config_path):
- with open(runtime_cf_config_path, "r") as cf_config:
- return json.load(cf_config)
- else:
- raise errors.ConfigError("file does not exist: %s" % runtime_cf_config_path)
- raise errors.ConfigError("Could not load cuttlefish_config.json.")
class RemoteInstance(Instance):
"""Class to store data of remote instance."""
+ # pylint: disable=too-many-locals
def __init__(self, gce_instance):
"""Process the args into class vars.
- RemoteInstace initialized by gce dict object.
+ RemoteInstace initialized by gce dict object. We parse the required data
+ from gce_instance to local variables.
Reference:
https://cloud.google.com/compute/docs/reference/rest/v1/instances/get
- Args:
- gce_instance: dict object queried from gce.
- """
- super(RemoteInstance, self).__init__()
- self._ProcessGceInstance(gce_instance)
- self._is_local = False
-
- def _ProcessGceInstance(self, gce_instance):
- """Parse the required data from gce_instance to local variables.
-
We also gather more details on client side including the forwarding adb
port and vnc port which will be used to determine the status of ssh
tunnel connection.
@@ -314,13 +347,13 @@
- Terminated: If we can't retrieve the public ip from gce instance.
Args:
- gce_instance: dict object queried from gce.
+ gce_instance: dict object queried from gce.
"""
- self._name = gce_instance.get(constants.INS_KEY_NAME)
+ name = gce_instance.get(constants.INS_KEY_NAME)
- self._createtime = gce_instance.get(constants.INS_KEY_CREATETIME)
- self._elapsed_time = _GetElapsedTime(self._createtime)
- self._status = gce_instance.get(constants.INS_KEY_STATUS)
+ create_time = gce_instance.get(constants.INS_KEY_CREATETIME)
+ elapsed_time = _GetElapsedTime(create_time)
+ status = gce_instance.get(constants.INS_KEY_STATUS)
ip = None
for network_interface in gce_instance.get("networkInterfaces"):
@@ -328,44 +361,56 @@
ip = access_config.get("natIP")
# Get metadata
+ display = None
+ avd_type = None
+ avd_flavor = None
for metadata in gce_instance.get("metadata", {}).get("items", []):
key = metadata["key"]
value = metadata["value"]
if key == constants.INS_KEY_DISPLAY:
- self._display = value
+ display = value
elif key == constants.INS_KEY_AVD_TYPE:
- self._avd_type = value
+ avd_type = value
elif key == constants.INS_KEY_AVD_FLAVOR:
- self._avd_flavor = value
+ avd_flavor = value
# Find ssl tunnel info.
+ adb_port = None
+ vnc_port = None
+ device_information = None
if ip:
- forwarded_ports = self.GetAdbVncPortFromSSHTunnel(ip,
- self._avd_type)
- self._ip = ip
- self._adb_port = forwarded_ports.adb_port
- self._vnc_port = forwarded_ports.vnc_port
- self._ssh_tunnel_is_connected = self._adb_port is not None
+ forwarded_ports = self.GetAdbVncPortFromSSHTunnel(ip, avd_type)
+ adb_port = forwarded_ports.adb_port
+ vnc_port = forwarded_ports.vnc_port
+ ssh_tunnel_is_connected = adb_port is not None
- adb_device = AdbTools(self._adb_port)
+ adb_device = AdbTools(adb_port)
if adb_device.IsAdbConnected():
- self._device_information = adb_device.device_information
- self._fullname = (_FULL_NAME_STRING %
- {"device_serial": "127.0.0.1:%d" % self._adb_port,
- "instance_name": self._name,
- "elapsed_time": self._elapsed_time})
+ device_information = adb_device.device_information
+ fullname = (_FULL_NAME_STRING %
+ {"device_serial": "127.0.0.1:%d" % adb_port,
+ "instance_name": name,
+ "elapsed_time": elapsed_time})
else:
- self._fullname = (_FULL_NAME_STRING %
- {"device_serial": "not connected",
- "instance_name": self._name,
- "elapsed_time": self._elapsed_time})
+ fullname = (_FULL_NAME_STRING %
+ {"device_serial": "not connected",
+ "instance_name": name,
+ "elapsed_time": elapsed_time})
# If instance is terminated, its ip is None.
else:
- self._ssh_tunnel_is_connected = False
- self._fullname = (_FULL_NAME_STRING %
- {"device_serial": "terminated",
- "instance_name": self._name,
- "elapsed_time": self._elapsed_time})
+ ssh_tunnel_is_connected = False
+ fullname = (_FULL_NAME_STRING %
+ {"device_serial": "terminated",
+ "instance_name": name,
+ "elapsed_time": elapsed_time})
+
+ super(RemoteInstance, self).__init__(
+ name=name, fullname=fullname, display=display, ip=ip, status=status,
+ adb_port=adb_port, vnc_port=vnc_port,
+ ssh_tunnel_is_connected=ssh_tunnel_is_connected,
+ createtime=create_time, elapsed_time=elapsed_time, avd_type=avd_type,
+ avd_flavor=avd_flavor, is_local=False,
+ device_information=device_information)
@staticmethod
def GetAdbVncPortFromSSHTunnel(ip, avd_type):
diff --git a/list/instance_test.py b/list/instance_test.py
index af1960b..8c9e1ca 100644
--- a/list/instance_test.py
+++ b/list/instance_test.py
@@ -61,21 +61,23 @@
""""Test get local instance info from launch_cvd process."""
self.Patch(subprocess, "check_output", return_value=self.PS_LAUNCH_CVD)
self.Patch(instance, "_GetElapsedTime", return_value="fake_time")
- self.Patch(instance, "GetCuttlefishRuntimeConfig",
- return_value=self.PS_RUNTIME_CF_CONFIG)
- local_instance = instance.LocalInstance()
- self.assertEqual(constants.LOCAL_INS_NAME, local_instance.name)
+ local_instance = instance.LocalInstance(2,
+ "1080",
+ "1920",
+ "480",
+ "Sat Nov 10 21:55:10 2018",
+ "fake_instance_dir")
+ self.assertEqual(constants.LOCAL_INS_NAME + "-2", local_instance.name)
self.assertEqual(True, local_instance.islocal)
self.assertEqual("1080x1920 (480)", local_instance.display)
self.assertEqual("Sat Nov 10 21:55:10 2018", local_instance.createtime)
- expected_full_name = "device serial: 127.0.0.1:%s (%s) elapsed time: %s" % (
- constants.CF_ADB_PORT, constants.LOCAL_INS_NAME, "fake_time")
+ expected_full_name = ("device serial: 127.0.0.1:%s (%s) elapsed time: %s"
+ % ("6521",
+ constants.LOCAL_INS_NAME + "-2",
+ "fake_time"))
self.assertEqual(expected_full_name, local_instance.fullname)
-
- # test return None if no launch_cvd process found
- self.Patch(subprocess, "check_output", return_value="no launch_cvd "
- "found")
- self.assertEqual(None, instance.LocalInstance())
+ self.assertEqual(6521, local_instance.forwarding_adb_port)
+ self.assertEqual(6445, local_instance.forwarding_vnc_port)
def testGetElapsedTime(self):
"""Test _GetElapsedTime"""
diff --git a/list/list.py b/list/list.py
index 4034171..05596df 100644
--- a/list/list.py
+++ b/list/list.py
@@ -20,6 +20,8 @@
from __future__ import print_function
import getpass
import logging
+import re
+import subprocess
from acloud import errors
from acloud.internal import constants
@@ -32,6 +34,31 @@
logger = logging.getLogger(__name__)
+_COMMAND_PS_LAUNCH_CVD = ["ps", "-wweo", "lstart,cmd"]
+_RE_LOCAL_INSTANCE_ID = re.compile(r".+instance_home_(?P<ins_id>\d+).+")
+_RE_LOCAL_CVD_PORT = re.compile(r"^127\.0\.0\.1:65(?P<cvd_port_suffix>\d{2})\s+")
+
+
+def GetActiveCVDIds():
+ """Get active local cvd ids from adb devices.
+
+ The adb port of local instance will be decided according to instance id.
+ The rule of adb port will be '6520 + [instance id] - 1'. So we grep last
+ two digits of port and calculate the instance id.
+
+ Return:
+ List of cvd id.
+ """
+ local_cvd_ids = []
+ adb_cmd = [constants.ADB_BIN, "devices"]
+ device_info = subprocess.check_output(adb_cmd)
+ for device in device_info.splitlines():
+ match = _RE_LOCAL_CVD_PORT.match(device)
+ if match:
+ cvd_serial = match.group("cvd_port_suffix")
+ local_cvd_ids.append(int(cvd_serial) - 19)
+ return local_cvd_ids
+
def _ProcessInstances(instance_list):
"""Get more details of remote instances.
@@ -102,14 +129,72 @@
credentials = auth.CreateCredentials(cfg)
compute_client = gcompute_client.ComputeClient(cfg, credentials)
filter_item = "labels.%s=%s" % (constants.LABEL_CREATE_BY, getpass.getuser())
- all_instances = compute_client.ListInstances(cfg.zone,
- instance_filter=filter_item)
- logger.debug("Instance list from: %s (filter: %s\n%s):",
- cfg.zone, filter_item, all_instances)
+ all_instances = compute_client.ListInstances(instance_filter=filter_item)
+
+ logger.debug("Instance list from: (filter: %s\n%s):",
+ filter_item, all_instances)
return _ProcessInstances(all_instances)
+def GetLocalInstances():
+ """Look for local instances.
+
+ Gather local instances information from cuttlefish runtime config.
+
+ Returns:
+ instance_list: List of local instances.
+ """
+ # Running instances on local is not supported on all OS.
+ if not utils.IsSupportedPlatform():
+ return None
+
+ local_cvd_ids = GetActiveCVDIds()
+ local_instance_list = []
+ for cvd_id in local_cvd_ids:
+ ins_dir = x_res = y_res = dpi = cf_runtime_config_dict = None
+ try:
+ cf_runtime_config_dict = instance.GetCuttlefishRuntimeConfig(cvd_id)
+ except errors.ConfigError:
+ logger.error("Instance[id:%d] dir not found!", cvd_id)
+
+ if cf_runtime_config_dict:
+ ins_dir = instance.GetLocalInstanceRuntimeDir(cvd_id)
+ x_res = cf_runtime_config_dict["x_res"]
+ y_res = cf_runtime_config_dict["y_res"]
+ dpi = cf_runtime_config_dict["dpi"]
+ # TODO(143063678), there's no createtime info in
+ # cuttlefish_config.json so far.
+ local_instance_list.append(instance.LocalInstance(cvd_id,
+ x_res,
+ y_res,
+ dpi,
+ None,
+ ins_dir))
+ return local_instance_list
+
+
+def _GetIdFromInstanceDirStr(instance_dir):
+ """Look for instance id from the path of instance dir.
+
+ Args:
+ instance_dir: String, path of instance_dir.
+
+ Returns:
+ Integer of instance id.
+
+ Raises:
+ errors.InvalidInstanceDir: Invalid instance idr.
+ """
+ match = _RE_LOCAL_INSTANCE_ID.match(instance_dir)
+ if match:
+ return int(match.group("ins_id"))
+
+ raise errors.InvalidInstanceDir("Instance dir is invalid:%s. local AVD "
+ "launched outside acloud is not supported"
+ % instance_dir)
+
+
def GetInstances(cfg):
"""Look for remote/local instances.
@@ -120,9 +205,9 @@
instance_list: List of instances.
"""
instances_list = GetRemoteInstances(cfg)
- local_instance = instance.LocalInstance()
- if local_instance:
- instances_list.append(local_instance)
+ local_instances = GetLocalInstances()
+ if local_instances:
+ instances_list.extend(local_instances)
return instances_list
diff --git a/public/acloud_main.py b/public/acloud_main.py
index fb69e5e..c80b995 100644
--- a/public/acloud_main.py
+++ b/public/acloud_main.py
@@ -56,6 +56,13 @@
To show more detail info on the list.
$ acloud list -vv
+- Pull:
+ Pull will download log files or show the log file in screen from one remote
+ cuttlefish instance:
+ $ acloud pull
+ Pull from a specified instance:
+ $ acloud pull --instance-name "your_instance_name"
+
Try $acloud [cmd] --help for further details.
"""
@@ -396,6 +403,7 @@
emulator_branch=args.emulator_branch,
kernel_build_id=args.kernel_build_id,
kernel_branch=args.kernel_branch,
+ kernel_build_target=args.kernel_build_target,
gpu=args.gpu,
num=args.num,
serial_log_file=args.serial_log_file,
diff --git a/public/actions/create_goldfish_action.py b/public/actions/create_goldfish_action.py
index 36124f8..c5b942a 100644
--- a/public/actions/create_goldfish_action.py
+++ b/public/actions/create_goldfish_action.py
@@ -67,6 +67,7 @@
emulator_build_id,
kernel_build_id=None,
kernel_branch=None,
+ kernel_build_target=None,
gpu=None,
avd_spec=None,
tags=None,
@@ -113,7 +114,8 @@
self.emulator_build_info = self._build_client.GetBuildInfo(
emulator_build_target, emulator_build_id, emulator_branch)
self.kernel_build_info = self._build_client.GetBuildInfo(
- cfg.kernel_build_target, kernel_build_id, kernel_branch)
+ kernel_build_target or cfg.kernel_build_target, kernel_build_id,
+ kernel_branch)
def GetBuildInfoDict(self):
"""Get build info dictionary.
@@ -159,6 +161,7 @@
emulator_build_id=self.emulator_build_info.build_id,
kernel_branch=self.kernel_build_info.branch,
kernel_build_id=self.kernel_build_info.build_id,
+ kernel_build_target=self.kernel_build_info.build_target,
gpu=self._gpu,
blank_data_disk_size_gb=self._blank_data_disk_size_gb,
avd_spec=self._avd_spec,
@@ -232,6 +235,7 @@
emulator_branch=None,
kernel_build_id=None,
kernel_branch=None,
+ kernel_build_target=None,
gpu=None,
num=1,
serial_log_file=None,
@@ -252,6 +256,7 @@
gpu: String, GPU to attach to the device or None. e.g. "nvidia-k80"
kernel_build_id: Kernel build id, a string.
kernel_branch: Kernel branch name, a string.
+ kernel_build_target: Kernel build artifact, a string.
num: Integer, Number of devices to create.
serial_log_file: String, A path to a file where serial output should
be saved to.
@@ -313,20 +318,22 @@
logger.info(
"Creating a goldfish device in project %s, build_target: %s, "
"build_id: %s, emulator_bid: %s, kernel_build_id: %s, "
- "kernel_branh: %s, GPU: %s, num: %s, "
+ "kernel_branch: %s, kernel_build_target: %s, GPU: %s, num: %s, "
"serial_log_file: %s, "
"autoconnect: %s", cfg.project, build_target, build_id,
- emulator_build_id, kernel_build_id, kernel_branch, gpu, num,
- serial_log_file, autoconnect)
+ emulator_build_id, kernel_build_id, kernel_branch, kernel_build_target,
+ gpu, num, serial_log_file, autoconnect)
- device_factory = GoldfishDeviceFactory(cfg, build_target, build_id,
- cfg.emulator_build_target,
- emulator_build_id, gpu=gpu,
- avd_spec=avd_spec, tags=tags,
- branch=branch,
- emulator_branch=emulator_branch,
- kernel_build_id=kernel_build_id,
- kernel_branch=kernel_branch)
+ device_factory = GoldfishDeviceFactory(
+ cfg, build_target, build_id,
+ cfg.emulator_build_target,
+ emulator_build_id, gpu=gpu,
+ avd_spec=avd_spec, tags=tags,
+ branch=branch,
+ emulator_branch=emulator_branch,
+ kernel_build_id=kernel_build_id,
+ kernel_branch=kernel_branch,
+ kernel_build_target=kernel_build_target)
return common_operations.CreateDevices("create_gf", cfg, device_factory,
num, constants.TYPE_GF,
diff --git a/public/actions/create_goldfish_action_test.py b/public/actions/create_goldfish_action_test.py
index fa04ee2..d031167 100644
--- a/public/actions/create_goldfish_action_test.py
+++ b/public/actions/create_goldfish_action_test.py
@@ -123,7 +123,10 @@
report = create_goldfish_action.CreateDevices(
none_avd_spec, cfg, build_target=self.BUILD_TARGET,
build_id=self.BUILD_ID, emulator_build_id=self.EMULATOR_BUILD_ID,
- gpu=self.GPU, kernel_build_id=self.KERNEL_BUILD_ID)
+ gpu=self.GPU,
+ kernel_branch=self.KERNEL_BRANCH,
+ kernel_build_id=self.KERNEL_BUILD_ID,
+ kernel_build_target=self.KERNEL_BUILD_TARGET)
# Verify
self.compute_client.CreateInstance.assert_called_with(
@@ -138,6 +141,7 @@
emulator_build_id=self.EMULATOR_BUILD_ID,
kernel_branch=self.KERNEL_BRANCH,
kernel_build_id=self.KERNEL_BUILD_ID,
+ kernel_build_target=self.KERNEL_BUILD_TARGET,
gpu=self.GPU,
avd_spec=none_avd_spec,
extra_scopes=self.EXTRA_SCOPES,
@@ -193,6 +197,7 @@
emulator_build_id=self.EMULATOR_BUILD_ID,
kernel_branch=self.KERNEL_BRANCH,
kernel_build_id=self.KERNEL_BUILD_ID,
+ kernel_build_target=self.KERNEL_BUILD_TARGET,
gpu=self.GPU,
avd_spec=self.avd_spec,
extra_scopes=self.EXTRA_SCOPES,
@@ -239,7 +244,9 @@
emulator_branch=None,
gpu=self.GPU,
branch=None,
- kernel_build_id=self.KERNEL_BUILD_ID)
+ kernel_branch=self.KERNEL_BRANCH,
+ kernel_build_id=self.KERNEL_BUILD_ID,
+ kernel_build_target=self.KERNEL_BUILD_TARGET)
# Verify
self.compute_client.CreateInstance.assert_called_with(
@@ -254,6 +261,7 @@
emulator_build_id=self.EMULATOR_BUILD_ID,
kernel_branch=self.KERNEL_BRANCH,
kernel_build_id=self.KERNEL_BUILD_ID,
+ kernel_build_target=self.KERNEL_BUILD_TARGET,
gpu=self.GPU,
avd_spec=none_avd_spec,
extra_scopes=self.EXTRA_SCOPES,
@@ -307,6 +315,7 @@
emulator_build_id=self.EMULATOR_BUILD_ID,
kernel_branch=self.KERNEL_BRANCH,
kernel_build_id=self.KERNEL_BUILD_ID,
+ kernel_build_target=self.KERNEL_BUILD_TARGET,
gpu=self.GPU,
avd_spec=self.avd_spec,
extra_scopes=self.EXTRA_SCOPES,
@@ -363,6 +372,7 @@
emulator_build_id=self.EMULATOR_BUILD_ID,
kernel_branch=self.KERNEL_BRANCH,
kernel_build_id=self.KERNEL_BUILD_ID,
+ kernel_build_target=self.KERNEL_BUILD_TARGET,
gpu=self.GPU,
avd_spec=none_avd_spec,
extra_scopes=self.EXTRA_SCOPES,
@@ -416,6 +426,7 @@
emulator_build_id=self.EMULATOR_BUILD_ID,
kernel_branch=self.KERNEL_BRANCH,
kernel_build_id=self.KERNEL_BUILD_ID,
+ kernel_build_target=self.KERNEL_BUILD_TARGET,
gpu=self.GPU,
avd_spec=self.avd_spec,
extra_scopes=self.EXTRA_SCOPES,
diff --git a/public/actions/remote_instance_cf_device_factory.py b/public/actions/remote_instance_cf_device_factory.py
index 8a539c1..74f5ca7 100644
--- a/public/actions/remote_instance_cf_device_factory.py
+++ b/public/actions/remote_instance_cf_device_factory.py
@@ -250,3 +250,26 @@
and the value is an errors.DeviceBootError object.
"""
return self._compute_client.all_failures
+
+ def GetBuildInfoDict(self):
+ """Get build info dictionary.
+
+ Returns:
+ A build info dictionary.
+ """
+ build_info_dict = {
+ key: val for key, val in self._avd_spec.remote_image.items() if val}
+
+ # kernel_target have default value "kernel". If user provide kernel_build_id
+ # or kernel_branch, then start to process kernel image.
+ if (self._avd_spec.kernel_build_info[constants.BUILD_ID]
+ or self._avd_spec.kernel_build_info[constants.BUILD_BRANCH]):
+ build_info_dict.update(
+ {"kernel_%s" % key: val
+ for key, val in self._avd_spec.kernel_build_info.items() if val}
+ )
+ build_info_dict.update(
+ {"system_%s" % key: val
+ for key, val in self._avd_spec.system_build_info.items() if val}
+ )
+ return build_info_dict
diff --git a/public/device_driver.py b/public/device_driver.py
index a18dcc7..8e37a59 100755
--- a/public/device_driver.py
+++ b/public/device_driver.py
@@ -449,13 +449,24 @@
Returns:
A Report instance.
"""
+ # delete, failed, error_msgs are used to record result.
+ deleted = []
+ failed = []
+ error_msgs = []
+
r = default_report if default_report else report.Report(command="delete")
credentials = auth.CreateCredentials(cfg)
compute_client = android_compute_client.AndroidComputeClient(cfg,
credentials)
+ zone_instances = compute_client.GetZonesByInstances(instance_names)
+
try:
- deleted, failed, error_msgs = compute_client.DeleteInstances(
- instance_names, cfg.zone)
+ for zone, instances in zone_instances.items():
+ deleted_ins, failed_ins, error_ins = compute_client.DeleteInstances(
+ instances, zone)
+ deleted.extend(deleted_ins)
+ failed.extend(failed_ins)
+ error_msgs.extend(error_ins)
AddDeletionResultToReport(
r, deleted,
failed, error_msgs,
@@ -513,7 +524,7 @@
storage_client = gstorage_client.StorageClient(credentials)
# Cleanup expired instances
- items = compute_client.ListInstances(zone=cfg.zone)
+ items = compute_client.ListInstances()
cleanup_list = [
item["name"]
for item in _FindOldItems(items, cut_time, "creationTimestamp")
diff --git a/public/device_driver_test.py b/public/device_driver_test.py
index 237d3ea..560c26b 100644
--- a/public/device_driver_test.py
+++ b/public/device_driver_test.py
@@ -162,10 +162,12 @@
def testDeleteAndroidVirtualDevices(self):
"""Test DeleteAndroidVirtualDevices."""
+ cfg = _CreateCfg()
instance_names = ["fake-instance-1", "fake-instance-2"]
+ self.compute_client.GetZonesByInstances.return_value = (
+ {cfg.zone: instance_names})
self.compute_client.DeleteInstances.return_value = (instance_names, [],
[])
- cfg = _CreateCfg()
report = device_driver.DeleteAndroidVirtualDevices(cfg, instance_names)
self.compute_client.DeleteInstances.assert_called_once_with(
instance_names, cfg.zone)
@@ -267,8 +269,7 @@
}
self.assertEqual(report.data, expected_report_data)
- self.compute_client.ListInstances.assert_called_once_with(
- zone=cfg.zone)
+ self.compute_client.ListInstances.assert_called_once_with()
self.compute_client.DeleteInstances.assert_called_once_with(
instances=["fake_instance_1"], zone=cfg.zone)
diff --git a/reconnect/reconnect.py b/reconnect/reconnect.py
index 3420081..2c653ec 100644
--- a/reconnect/reconnect.py
+++ b/reconnect/reconnect.py
@@ -76,7 +76,6 @@
compute_client = android_compute_client.AndroidComputeClient(
cfg, credentials)
compute_client.AddSshRsaInstanceMetadata(
- cfg.zone,
user,
cfg.ssh_public_key_path,
instance_name)