Merge "Fix the exception of no cuttlefish runtime config."
diff --git a/create/avd_spec.py b/create/avd_spec.py
index 7be4ece..b1bdc40 100644
--- a/create/avd_spec.py
+++ b/create/avd_spec.py
@@ -106,6 +106,7 @@
self._instance_name_to_reuse = None
self._unlock_screen = None
self._report_internal_ip = None
+ self._disable_external_ip = None
self._avd_type = None
self._flavor = None
self._image_source = None
@@ -304,6 +305,7 @@
self._autoconnect = args.autoconnect
self._unlock_screen = args.unlock_screen
self._report_internal_ip = args.report_internal_ip
+ self._disable_external_ip = args.disable_external_ip
self._avd_type = args.avd_type
self._flavor = args.flavor or constants.FLAVOR_PHONE
if args.remote_host:
@@ -821,6 +823,11 @@
return self._report_internal_ip
@property
+ def disable_external_ip(self):
+ """Return disable_external_ip."""
+ return self._disable_external_ip
+
+ @property
def kernel_build_info(self):
"""Return kernel build info."""
return self._kernel_build_info
diff --git a/create/create_args.py b/create/create_args.py
index f4a7c4f..3a869a3 100644
--- a/create/create_args.py
+++ b/create/create_args.py
@@ -91,6 +91,12 @@
"ip. Using the internal ip is used when connecting from another "
"GCE instance.")
parser.add_argument(
+ "--disable-external-ip",
+ action="store_true",
+ dest="disable_external_ip",
+ required=False,
+ help="Disable the external ip of the created instance.")
+ parser.add_argument(
"--network",
type=str,
dest="network",
diff --git a/internal/constants.py b/internal/constants.py
index 91f47d1..eb304e9 100755
--- a/internal/constants.py
+++ b/internal/constants.py
@@ -177,6 +177,7 @@
ENV_CUTTLEFISH_CONFIG_FILE = "CUTTLEFISH_CONFIG_FILE"
ENV_CUTTLEFISH_INSTANCE = "CUTTLEFISH_INSTANCE"
ENV_CVD_HOME = "HOME"
+ANDROID_INFO_FILE = "android-info.txt"
CUTTLEFISH_CONFIG_FILE = "cuttlefish_config.json"
TEMP_ARTIFACTS_FOLDER = "acloud_image_artifacts"
diff --git a/internal/lib/cvd_compute_client_multi_stage.py b/internal/lib/cvd_compute_client_multi_stage.py
index 5f7f4ab..7d12673 100644
--- a/internal/lib/cvd_compute_client_multi_stage.py
+++ b/internal/lib/cvd_compute_client_multi_stage.py
@@ -37,6 +37,7 @@
import logging
import os
+import re
import subprocess
import tempfile
import time
@@ -75,6 +76,7 @@
_NO_RETRY = 0
# Launch cvd command for acloud report
_LAUNCH_CVD_COMMAND = "launch_cvd_command"
+_CONFIG_RE = re.compile(r"^config=(?P<config>.+)")
class CvdComputeClient(android_compute_client.AndroidComputeClient):
@@ -270,6 +272,22 @@
self._metadata.update({"kernel_build_id": kernel_build_id})
self._metadata.update({"kernel_build_target": kernel_build_target})
+ def _GetConfigFromAndroidInfo(self):
+ """Get config value from android-info.txt.
+
+ The config in android-info.txt would like "config=phone".
+
+ Returns:
+ Strings of config value.
+ """
+ android_info = self._ssh.GetCmdOutput(
+ "cat %s" % constants.ANDROID_INFO_FILE)
+ logger.debug("Android info: %s", android_info)
+ config_match = _CONFIG_RE.match(android_info)
+ if config_match:
+ return config_match.group("config")
+ return None
+
# pylint: disable=too-many-branches
def _GetLaunchCvdArgs(self, avd_spec=None, blank_data_disk_size_gb=None,
decompress_kernel=None, instance=None):
@@ -293,8 +311,10 @@
launch_cvd_args.append(
"-blank_data_image_mb=%d" % (blank_data_disk_size_gb * 1024))
if avd_spec:
- launch_cvd_args.append("-config=%s" % avd_spec.flavor)
- if avd_spec.hw_customize or not self._ArgSupportInLaunchCVD(_CONFIG_ARG):
+ config = self._GetConfigFromAndroidInfo()
+ if config:
+ launch_cvd_args.append("-config=%s" % config)
+ if avd_spec.hw_customize or not config:
launch_cvd_args.append(
"-x_res=" + avd_spec.hw_property[constants.HW_X_RES])
launch_cvd_args.append(
@@ -339,19 +359,6 @@
launch_cvd_args.append(_AGREEMENT_PROMPT_ARG)
return launch_cvd_args
- def _ArgSupportInLaunchCVD(self, arg):
- """Check if the arg is supported in launch_cvd.
-
- Args:
- arg: String of the arg. e.g. "-config".
-
- Returns:
- True if this arg is supported. Otherwise False.
- """
- if arg in self._ssh.GetCmdOutput("./bin/launch_cvd --help"):
- return True
- return False
-
def StopCvd(self):
"""Stop CVD.
@@ -492,6 +499,7 @@
disk_args = self._GetDiskArgs(
instance, image_name, image_project, boot_disk_size_gb)
+ disable_external_ip = avd_spec.disable_external_ip if avd_spec else False
gcompute_client.ComputeClient.CreateInstance(
self,
instance=instance,
@@ -505,7 +513,8 @@
gpu=self._gpu,
extra_scopes=extra_scopes,
tags=["appstreaming"] if (
- avd_spec and avd_spec.connect_webrtc) else None)
+ avd_spec and avd_spec.connect_webrtc) else None,
+ disable_external_ip=disable_external_ip)
ip = gcompute_client.ComputeClient.GetInstanceIP(
self, instance=instance, zone=self._zone)
logger.debug("'instance_ip': %s", ip.internal
diff --git a/internal/lib/cvd_compute_client_multi_stage_test.py b/internal/lib/cvd_compute_client_multi_stage_test.py
index 0b485dd..d0f63d0 100644
--- a/internal/lib/cvd_compute_client_multi_stage_test.py
+++ b/internal/lib/cvd_compute_client_multi_stage_test.py
@@ -31,6 +31,7 @@
from acloud.internal.lib import gcompute_client
from acloud.internal.lib import utils
from acloud.internal.lib.ssh import Ssh
+from acloud.internal.lib.ssh import IP
from acloud.list import list as list_instances
@@ -59,6 +60,7 @@
LAUNCH_ARGS = "--setupwizard_mode=REQUIRED"
EXTRA_SCOPES = ["scope1"]
GPU = "fake-gpu"
+ FAKE_IP = IP(external="1.1.1.1", internal="10.1.1.1")
def _GetFakeConfig(self):
"""Create a fake configuration object.
@@ -79,15 +81,13 @@
fake_cfg.extra_scopes = self.EXTRA_SCOPES
return fake_cfg
+ # pylint: disable=protected-access
def setUp(self):
"""Set up the test."""
super().setUp()
self.Patch(cvd_compute_client_multi_stage.CvdComputeClient, "InitResourceHandle")
self.Patch(cvd_compute_client_multi_stage.CvdComputeClient, "_VerifyZoneByQuota",
return_value=True)
- self.Patch(cvd_compute_client_multi_stage.CvdComputeClient,
- "_ArgSupportInLaunchCVD",
- return_value=True)
self.Patch(android_build_client.AndroidBuildClient, "InitResourceHandle")
self.Patch(android_build_client.AndroidBuildClient, "DownloadArtifact")
self.Patch(list_instances, "GetInstancesFromInstanceNames", return_value=mock.MagicMock())
@@ -97,6 +97,10 @@
self.Patch(Ssh, "GetBaseCmd")
self.cvd_compute_client_multi_stage = cvd_compute_client_multi_stage.CvdComputeClient(
self._GetFakeConfig(), mock.MagicMock(), gpu=self.GPU)
+ self.cvd_compute_client_multi_stage._ssh = Ssh(
+ ip=self.FAKE_IP,
+ user=constants.GCE_USER,
+ ssh_private_key_path="/fake/acloud_rea")
self.args = mock.MagicMock()
self.args.local_image = constants.FIND_IN_BUILD_ENV
self.args.local_system_image = None
@@ -108,6 +112,7 @@
self.args.num_avds_per_instance = 2
self.args.remote_host = False
self.args.launch_args = self.LAUNCH_ARGS
+ self.args.disable_external_ip = False
# pylint: disable=protected-access
@mock.patch.object(utils, "GetBuildEnvironmentVariable", return_value="fake_env_cf_x86")
@@ -115,6 +120,8 @@
def testGetLaunchCvdArgs(self, _mock_check_img, _mock_env):
"""test GetLaunchCvdArgs."""
# test GetLaunchCvdArgs with avd_spec
+ self.Patch(cvd_compute_client_multi_stage.CvdComputeClient,
+ "_GetConfigFromAndroidInfo", return_value="phone")
fake_avd_spec = avd_spec.AVDSpec(self.args)
expeted_args = ["-config=phone", "-x_res=1080", "-y_res=1920", "-dpi=240",
"-data_policy=always_create", "-blank_data_image_mb=10240",
@@ -188,7 +195,8 @@
zone=self.ZONE,
extra_scopes=self.EXTRA_SCOPES,
gpu=self.GPU,
- tags=None)
+ tags=None,
+ disable_external_ip=False)
mock_check_img.return_value = True
#test use local image in the remote instance.
@@ -222,7 +230,8 @@
zone=self.ZONE,
extra_scopes=self.EXTRA_SCOPES,
gpu=self.GPU,
- tags=None)
+ tags=None,
+ disable_external_ip=False)
def testRecordBuildInfo(self):
"""Test RecordBuildInfo"""
@@ -268,12 +277,13 @@
self.assertEqual(self.cvd_compute_client_multi_stage.stage,
device_stage)
- def testArgSupportInLaunchCVD(self):
- """Test ArgSupportInLaunchCVD"""
- self.Patch(Ssh, "GetCmdOutput", return_value="-config (Config)")
- self.assertTrue(
- self.cvd_compute_client_multi_stage._ArgSupportInLaunchCVD(
- "-config"))
+ def testGetConfigFromAndroidInfo(self):
+ """Test GetConfigFromAndroidInfo"""
+ self.Patch(Ssh, "GetCmdOutput", return_value="config=phone")
+ expected = "phone"
+ self.assertEqual(
+ self.cvd_compute_client_multi_stage._GetConfigFromAndroidInfo(),
+ expected)
if __name__ == "__main__":
diff --git a/internal/lib/gcompute_client.py b/internal/lib/gcompute_client.py
index a9bb8a9..c5fe845 100755
--- a/internal/lib/gcompute_client.py
+++ b/internal/lib/gcompute_client.py
@@ -1144,19 +1144,24 @@
api = self.service.regions().list(project=self._project)
return self.Execute(api)
- def _GetNetworkArgs(self, network, zone):
+ def _GetNetworkArgs(self, network, zone, disable_external_ip):
"""Helper to generate network args that is used to create an instance.
+ See: https://cloud.google.com/compute/docs/reference/rest/v1/instances
+ for more information on the specific fields.
+
Args:
network: A string, e.g. "default".
zone: String, representing zone name, e.g. "us-central1-f"
+ disable_external_ip: Boolean, true if the external ip should be
+ disabled.
Returns:
A dictionary representing network args.
"""
network_args = {
"network": self.GetNetworkUrl(network),
- "accessConfigs": [{
+ "accessConfigs": [] if disable_external_ip else [{
"name": "External NAT",
"type": "ONE_TO_ONE_NAT"
}]
@@ -1229,7 +1234,8 @@
gpu=None,
extra_disk_name=None,
extra_scopes=None,
- tags=None):
+ tags=None,
+ disable_external_ip=False):
"""Create a gce instance with a gce image.
Args:
@@ -1252,6 +1258,8 @@
extra_scopes: A list of extra scopes to be provided to the instance.
tags: A list of tags to associate with the instance. e.g.
["http-server", "https-server"]
+ disable_external_ip: Boolean, true if instance external ip should be
+ disabled.
"""
disk_args = (disk_args
or self._GetDiskArgs(instance, image_name, image_project))
@@ -1268,7 +1276,9 @@
body = {
"machineType": self.GetMachineType(machine_type, zone)["selfLink"],
"name": instance,
- "networkInterfaces": [self._GetNetworkArgs(network, zone)],
+ "networkInterfaces": [
+ self._GetNetworkArgs(network, zone, disable_external_ip)
+ ],
"disks": disk_args,
"labels": {constants.LABEL_CREATE_BY: getpass.getuser()},
"serviceAccounts": [{
@@ -1503,8 +1513,8 @@
ip_name_map = dict.fromkeys(ips)
for instance in self.ListInstances():
try:
- ip = instance["networkInterfaces"][0]["accessConfigs"][0][
- "natIP"]
+ instance_ip = GetInstanceIP(instance)
+ ip = instance_ip.external or instance_ip.internal
if ip in ips:
ip_name_map[ip] = instance["name"]
except (IndexError, KeyError) as e:
@@ -1522,9 +1532,7 @@
ssh.IP object, that stores internal and external ip of the instance.
"""
instance = self.GetInstance(instance, zone)
- internal_ip = instance["networkInterfaces"][0]["networkIP"]
- external_ip = instance["networkInterfaces"][0]["accessConfigs"][0]["natIP"]
- return IP(internal=internal_ip, external=external_ip)
+ return GetInstanceIP(instance)
@utils.TimeExecute(function_description="Updating instance metadata: ")
def SetInstanceMetadata(self, zone, instance, body):
@@ -1755,3 +1763,18 @@
rsa = rsa.strip() if rsa else rsa
utils.VerifyRsaPubKey(rsa)
return rsa
+
+def GetInstanceIP(instance):
+ """Get the internal and external IP for a given instance
+
+ Args:
+ instance: A dict, representing a gce instance
+
+ Returns:
+ A populated IP object
+ """
+ network_interface = instance["networkInterfaces"][0]
+ access_configs = network_interface.get("accessConfigs", [{}])[0]
+ external_ip = access_configs.get("natIP", "")
+ internal_ip = network_interface.get("networkIP", "")
+ return IP(internal=internal_ip, external=external_ip)
diff --git a/list/instance.py b/list/instance.py
index 0da880d..afcaa7c 100644
--- a/list/instance.py
+++ b/list/instance.py
@@ -44,6 +44,7 @@
from acloud.internal.lib import utils
from acloud.internal.lib.adb_tools import AdbTools
from acloud.internal.lib.local_instance_lock import LocalInstanceLock
+from acloud.internal.lib.gcompute_client import GetInstanceIP
logger = logging.getLogger(__name__)
@@ -694,10 +695,8 @@
status = gce_instance.get(constants.INS_KEY_STATUS)
zone = self._GetZoneName(gce_instance.get(constants.INS_KEY_ZONE))
- ip = None
- for network_interface in gce_instance.get("networkInterfaces"):
- for access_config in network_interface.get("accessConfigs"):
- ip = access_config.get("natIP")
+ instance_ip = GetInstanceIP(gce_instance)
+ ip = instance_ip.external or instance_ip.internal
# Get metadata
display = None
diff --git a/public/acloud_main.py b/public/acloud_main.py
index b8d89d5..8102136 100644
--- a/public/acloud_main.py
+++ b/public/acloud_main.py
@@ -332,7 +332,11 @@
if args.which == CMD_CREATE_CUTTLEFISH:
missing_fields.extend(cfg.GetMissingFields(_CREATE_CF_REQUIRE_FIELDS))
if missing_fields:
- return "Missing required configuration fields: %s" % missing_fields
+ return (
+ "Config file (%s) missing required fields: %s, please add these "
+ "fields or reset config file. For reset config information: "
+ "go/acloud-googler-setup#reset-configuration" %
+ (config.GetUserConfigPath(args.config_file), missing_fields))
return None
diff --git a/public/actions/remote_instance_cf_device_factory.py b/public/actions/remote_instance_cf_device_factory.py
index c08df62..8daa2e4 100644
--- a/public/actions/remote_instance_cf_device_factory.py
+++ b/public/actions/remote_instance_cf_device_factory.py
@@ -278,6 +278,8 @@
artifact_files.extend(
os.path.basename(image) for image in glob.glob(
os.path.join(images_dir, file_name)))
+ # Upload android-info.txt to parse config value.
+ artifact_files.append(constants.ANDROID_INFO_FILE)
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 1e0aa0b..3bbbc15 100644
--- a/public/actions/remote_instance_cf_device_factory_test.py
+++ b/public/actions/remote_instance_cf_device_factory_test.py
@@ -301,7 +301,7 @@
fake_host_package,
fake_local_image_dir)
expected_cmd = (
- "tar -cf - --lzop -S -C %s fake.img bootloader kernel | "
+ "tar -cf - --lzop -S -C %s fake.img bootloader kernel android-info.txt | "
"%s -- tar -xf - --lzop -S" %
(fake_local_image_dir, factory._ssh.GetBaseCmd(constants.SSH_BIN)))
mock_shell.assert_called_once_with(expected_cmd)
@@ -318,8 +318,8 @@
fake_host_package,
fake_local_image_dir)
expected_cmd = (
- "tar -cf - --lzop -S -C %s boot.img cache.img super.img userdata.img bootloader | "
- "%s -- tar -xf - --lzop -S" %
+ "tar -cf - --lzop -S -C %s boot.img cache.img super.img userdata.img "
+ "bootloader android-info.txt | %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 e7eb8e1..2c8b2f8 100755
--- a/public/config.py
+++ b/public/config.py
@@ -96,6 +96,22 @@
return os.path.join(config_path, _DEFAULT_CONFIG_FILE)
+def GetUserConfigPath(config_path):
+ """Get Acloud user config file path.
+
+ If there is no config provided, Acloud would use default config path.
+
+ Args:
+ config_path: String, path of Acloud config file.
+
+ Returns:
+ Path (string) of the Acloud config.
+ """
+ if config_path:
+ return config_path
+ return GetDefaultConfigFile()
+
+
def GetAcloudConfig(args):
"""Helper function to initialize Config object.