Refactor RemoteInstanceDeviceFactory out of local_image_remote_instance.py
We're extracting the factory so that it can be utilized by moving
RemoteInstanceDeviceFactory to remote_instance_cf_device_factory.py
Bug:134919522
Test: acloud-dev create --local-image
Change-Id: Ife131c6570566dbe69c8ce6296b366078b0c5104
diff --git a/create/local_image_remote_instance.py b/create/local_image_remote_instance.py
index e8c8afa..9e00751 100644
--- a/create/local_image_remote_instance.py
+++ b/create/local_image_remote_instance.py
@@ -19,233 +19,20 @@
local image.
"""
-from distutils.spawn import find_executable
-import getpass
-import glob
import logging
import os
-import subprocess
from acloud import errors
from acloud.create import base_avd_create
from acloud.internal import constants
-from acloud.internal.lib import auth
-from acloud.internal.lib import cvd_compute_client
from acloud.internal.lib import utils
-from acloud.public.actions import base_device_factory
from acloud.public.actions import common_operations
+from acloud.public.actions import remote_instance_cf_device_factory
logger = logging.getLogger(__name__)
_CVD_HOST_PACKAGE = "cvd-host_package.tar.gz"
-_CVD_USER = getpass.getuser()
-_CMD_LAUNCH_CVD_ARGS = ("-cpus %s -x_res %s -y_res %s -dpi %s "
- "-memory_mb %s ")
-_CMD_LAUNCH_CVD_DISK_ARGS = ("-blank_data_image_mb %s "
- "-data_policy always_create ")
-
-#Output to Serial port 1 (console) group in the instance
-_OUTPUT_CONSOLE_GROUPS = "tty"
-SSH_BIN = "ssh"
-_SSH_CMD = (" -i %(rsa_key_file)s "
- "-q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "
- "-l %(login_user)s %(ip_addr)s ")
-_SSH_CMD_MAX_RETRY = 2
-_SSH_CMD_RETRY_SLEEP = 3
-_USER_BUILD = "userbuild"
-
-
-class RemoteInstanceDeviceFactory(base_device_factory.BaseDeviceFactory):
- """A class that can produce a cuttlefish device.
-
- Attributes:
- avd_spec: AVDSpec object that tells us what we're going to create.
- cfg: An AcloudConfig instance.
- local_image_artifact: A string, path to local image.
- cvd_host_package_artifact: A string, path to cvd host package.
- report_internal_ip: Boolean, True for the internal ip is used when
- connecting from another GCE instance.
- credentials: An oauth2client.OAuth2Credentials instance.
- compute_client: An object of cvd_compute_client.CvdComputeClient.
- ssh_cmd: Sting, ssh command to connect GCE instance.
- """
- def __init__(self, avd_spec, local_image_artifact, cvd_host_package_artifact):
- """Constructs a new remote instance device factory."""
- self._avd_spec = avd_spec
- self._cfg = avd_spec.cfg
- self._local_image_artifact = local_image_artifact
- self._cvd_host_package_artifact = cvd_host_package_artifact
- self._report_internal_ip = avd_spec.report_internal_ip
- self.credentials = auth.CreateCredentials(avd_spec.cfg)
- compute_client = cvd_compute_client.CvdComputeClient(
- avd_spec.cfg, self.credentials)
- super(RemoteInstanceDeviceFactory, self).__init__(compute_client)
- # Private creation parameters
- self._ssh_cmd = None
-
- def CreateInstance(self):
- """Create a single configured cuttlefish device.
-
- 1. Create gcp instance.
- 2. setup the AVD env in the instance.
- 3. upload the artifacts to instance.
- 4. Launch CVD.
-
- Returns:
- A string, representing instance name.
- """
- instance = self._CreateGceInstance()
- self._SetAVDenv(_CVD_USER)
- self._UploadArtifacts(_CVD_USER,
- self._local_image_artifact,
- self._cvd_host_package_artifact,
- self._avd_spec.local_image_dir)
- self._LaunchCvd(_CVD_USER, self._avd_spec.hw_property)
- return instance
-
- @staticmethod
- def _ShellCmdWithRetry(remote_cmd):
- """Runs a shell command on remote device.
-
- If the network is unstable and causes SSH connect fail, it will retry.
- When it retry in a short time, you may encounter unstable network. We
- will use the mechanism of RETRY_BACKOFF_FACTOR. The retry time for each
- failure is times * retries.
-
- Args:
- remote_cmd: A string, shell command to be run on remote.
-
- Raises:
- subprocess.CalledProcessError: For any non-zero return code of
- remote_cmd.
-
- Returns:
- Boolean, True if the command was successfully executed. False otherwise.
- """
- return utils.RetryExceptionType(
- exception_types=subprocess.CalledProcessError,
- max_retries=_SSH_CMD_MAX_RETRY,
- functor=lambda cmd: subprocess.check_call(cmd, shell=True),
- sleep_multiplier=_SSH_CMD_RETRY_SLEEP,
- retry_backoff_factor=utils.DEFAULT_RETRY_BACKOFF_FACTOR,
- cmd=remote_cmd)
-
- def _CreateGceInstance(self):
- """Create a single configured cuttlefish device.
-
- Override method from parent class.
- build_target: The format is like "aosp_cf_x86_phone". We only get info
- from the user build image file name. If the file name is
- not custom format (no "-"), we will use $TARGET_PRODUCT
- from environment variable as build_target.
-
- Returns:
- A string, representing instance name.
- """
- image_name = os.path.basename(
- self._local_image_artifact) if self._local_image_artifact else ""
- build_target = (os.environ.get(constants.ENV_BUILD_TARGET) if "-" not
- in image_name else image_name.split("-")[0])
- instance = self._compute_client.GenerateInstanceName(
- build_target=build_target, build_id=_USER_BUILD)
- # Create an instance from Stable Host Image
- self._compute_client.CreateInstance(
- instance=instance,
- image_name=self._cfg.stable_host_image_name,
- image_project=self._cfg.stable_host_image_project,
- blank_data_disk_size_gb=self._cfg.extra_data_disk_size_gb,
- avd_spec=self._avd_spec)
- ip = self._compute_client.GetInstanceIP(instance)
- self._ssh_cmd = find_executable(SSH_BIN) + _SSH_CMD % {
- "login_user": getpass.getuser(),
- "rsa_key_file": self._cfg.ssh_private_key_path,
- "ip_addr": (ip.internal if self._report_internal_ip
- else ip.external)}
- return instance
-
- @utils.TimeExecute(function_description="Setting up GCE environment")
- def _SetAVDenv(self, cvd_user):
- """set the user to run AVD in the instance.
-
- Args:
- cvd_user: A string, user run the cvd in the instance.
- """
- avd_list_of_groups = []
- avd_list_of_groups.extend(constants.LIST_CF_USER_GROUPS)
- avd_list_of_groups.append(_OUTPUT_CONSOLE_GROUPS)
- remote_cmd = ""
- for group in avd_list_of_groups:
- remote_cmd += "\"sudo usermod -aG %s %s;\"" %(group, cvd_user)
- logger.debug("remote_cmd:\n %s", remote_cmd)
- self._ShellCmdWithRetry(self._ssh_cmd + remote_cmd)
-
- @utils.TimeExecute(function_description="Processing and uploading local images")
- def _UploadArtifacts(self,
- cvd_user,
- local_image_zip,
- cvd_host_package_artifact,
- images_dir):
- """Upload local images and avd local host package to instance.
-
- There are two ways to upload local images.
- 1. Using local image zip, it would be decompressed by install_zip.sh.
- 2. Using local image directory, this directory contains all images.
- Images are compressed/decompressed by lzop during upload process.
-
- Args:
- cvd_user: String, user upload the artifacts to instance.
- local_image_zip: String, path to zip of local images which
- build from 'm dist'.
- cvd_host_package_artifact: String, path to cvd host package.
- images_dir: String, directory of local images which build
- from 'm'.
- """
- # TODO(b/133461252) Deprecate acloud create with local image zip.
- # Upload local image zip file
- if local_image_zip:
- remote_cmd = ("\"sudo su -c '/usr/bin/install_zip.sh .' - '%s'\" < %s"
- % (cvd_user, local_image_zip))
- logger.debug("remote_cmd:\n %s", remote_cmd)
- self._ShellCmdWithRetry(self._ssh_cmd + remote_cmd)
- else:
- # Compress image files for faster upload.
- artifact_files = [os.path.basename(image) for image in glob.glob(
- os.path.join(images_dir, "*.img"))]
- cmd = ("tar -cf - --lzop -S -C {images_dir} {artifact_files} | "
- "{ssh_cmd} -- tar -xf - --lzop -S".format(
- images_dir=images_dir,
- artifact_files=" ".join(artifact_files),
- ssh_cmd=self._ssh_cmd))
- logger.debug("cmd:\n %s", cmd)
- self._ShellCmdWithRetry(cmd)
-
- # host_package
- remote_cmd = ("\"sudo su -c 'tar -x -z -f -' - '%s'\" < %s" %
- (cvd_user, cvd_host_package_artifact))
- logger.debug("remote_cmd:\n %s", remote_cmd)
- self._ShellCmdWithRetry(self._ssh_cmd + remote_cmd)
-
- def _LaunchCvd(self, cvd_user, hw_property):
- """Launch CVD.
-
- Args:
- cvd_user: A string, user run the cvd in the instance.
- hw_property: dict object of hw property.
- """
- launch_cvd_args = _CMD_LAUNCH_CVD_ARGS % (
- hw_property["cpu"],
- hw_property["x_res"],
- hw_property["y_res"],
- hw_property["dpi"],
- hw_property["memory"])
- if constants.HW_ALIAS_DISK in hw_property:
- launch_cvd_args = (launch_cvd_args + _CMD_LAUNCH_CVD_DISK_ARGS %
- hw_property[constants.HW_ALIAS_DISK])
- remote_cmd = ("\"sudo su -c 'bin/launch_cvd %s>&/dev/ttyS0&' - '%s'\"" %
- (launch_cvd_args, cvd_user))
- logger.debug("remote_cmd:\n %s", remote_cmd)
- subprocess.Popen(self._ssh_cmd + remote_cmd, shell=True)
class LocalImageRemoteInstance(base_avd_create.BaseAVDCreate):
@@ -312,7 +99,7 @@
A Report instance.
"""
self.cvd_host_package_artifact = self.VerifyHostPackageArtifactsExist()
- device_factory = RemoteInstanceDeviceFactory(
+ device_factory = remote_instance_cf_device_factory.RemoteInstanceDeviceFactory(
avd_spec,
avd_spec.local_image_artifact,
self.cvd_host_package_artifact)
diff --git a/create/local_image_remote_instance_test.py b/create/local_image_remote_instance_test.py
index 821be68..8764ff6 100644
--- a/create/local_image_remote_instance_test.py
+++ b/create/local_image_remote_instance_test.py
@@ -18,22 +18,14 @@
Create class that is responsible for creating a remote instance AVD with a
local image.
"""
-import uuid
-import glob
import os
-import subprocess
-import time
import unittest
import mock
from acloud import errors
-from acloud.create import avd_spec
from acloud.create import local_image_remote_instance
-from acloud.internal import constants
-from acloud.internal.lib import auth
-from acloud.internal.lib import cvd_compute_client
from acloud.internal.lib import driver_test_lib
from acloud.internal.lib import utils
@@ -76,75 +68,5 @@
"/fake_dir2/cvd-host_package.tar.gz")
-class RemoteInstanceDeviceFactoryTest(driver_test_lib.BaseDriverTest):
- """Test RemoteInstanceDeviceFactory method."""
-
- def setUp(self):
- """Set up the test."""
- super(RemoteInstanceDeviceFactoryTest, self).setUp()
- self.Patch(auth, "CreateCredentials", return_value=mock.MagicMock())
- self.Patch(cvd_compute_client.CvdComputeClient, "InitResourceHandle")
-
- # pylint: disable=protected-access
- def testSSHExecuteWithRetry(self):
- """test SSHExecuteWithRetry method."""
- self.Patch(time, "sleep")
- factory = local_image_remote_instance.RemoteInstanceDeviceFactory
- self.Patch(subprocess, "check_call",
- side_effect=subprocess.CalledProcessError(
- None, "ssh command fail."))
- self.assertRaises(subprocess.CalledProcessError,
- factory._ShellCmdWithRetry,
- "fake cmd")
- self.assertEqual(subprocess.check_call.call_count, #pylint: disable=no-member
- local_image_remote_instance._SSH_CMD_MAX_RETRY + 1)
- self.Patch(subprocess, "check_call", return_value=True)
- self.assertEqual(factory._ShellCmdWithRetry("fake cmd"), True)
-
- # pylint: disable=protected-access
- @mock.patch.dict(os.environ, {constants.ENV_BUILD_TARGET:'fake-target'})
- def testCreateGceInstanceName(self):
- """test create gce instance."""
- self.Patch(utils, "GetBuildEnvironmentVariable",
- return_value="test_environ")
- self.Patch(glob, "glob", return_vale=["fake.img"])
- # Mock uuid
- args = mock.MagicMock()
- args.config_file = ""
- args.avd_type = constants.TYPE_CF
- args.flavor = "phone"
- args.local_image = None
- args.adb_port = None
- fake_avd_spec = avd_spec.AVDSpec(args)
-
- fake_uuid = mock.MagicMock(hex="1234")
- self.Patch(uuid, "uuid4", return_value=fake_uuid)
- self.Patch(cvd_compute_client.CvdComputeClient, "CreateInstance")
- fake_host_package_name = "/fake/host_package.tar.gz"
- fake_image_name = "/fake/aosp_cf_x86_phone-img-eng.username.zip"
-
- factory = local_image_remote_instance.RemoteInstanceDeviceFactory(
- fake_avd_spec,
- fake_image_name,
- fake_host_package_name)
- self.assertEqual(factory._CreateGceInstance(), "ins-1234-userbuild-aosp-cf-x86-phone")
-
- # Can't get target name from zip file name.
- fake_image_name = "/fake/aosp_cf_x86_phone.username.zip"
- factory = local_image_remote_instance.RemoteInstanceDeviceFactory(
- fake_avd_spec,
- fake_image_name,
- fake_host_package_name)
- self.assertEqual(factory._CreateGceInstance(), "ins-1234-userbuild-fake-target")
-
- # No image zip path, it uses local build images.
- fake_image_name = ""
- factory = local_image_remote_instance.RemoteInstanceDeviceFactory(
- fake_avd_spec,
- fake_image_name,
- fake_host_package_name)
- self.assertEqual(factory._CreateGceInstance(), "ins-1234-userbuild-fake-target")
-
-
if __name__ == "__main__":
unittest.main()
diff --git a/public/actions/remote_instance_cf_device_factory.py b/public/actions/remote_instance_cf_device_factory.py
new file mode 100644
index 0000000..a4ab76f
--- /dev/null
+++ b/public/actions/remote_instance_cf_device_factory.py
@@ -0,0 +1,240 @@
+# Copyright 2019 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""RemoteInstanceDeviceFactory provides basic interface to create a cuttlefish
+device factory."""
+
+from distutils.spawn import find_executable
+import getpass
+import glob
+import logging
+import os
+import subprocess
+
+from acloud.internal import constants
+from acloud.internal.lib import auth
+from acloud.internal.lib import cvd_compute_client
+from acloud.internal.lib import utils
+from acloud.public.actions import base_device_factory
+
+
+logger = logging.getLogger(__name__)
+
+_CVD_USER = getpass.getuser()
+_CMD_LAUNCH_CVD_ARGS = ("-cpus %s -x_res %s -y_res %s -dpi %s "
+ "-memory_mb %s ")
+_CMD_LAUNCH_CVD_DISK_ARGS = ("-blank_data_image_mb %s "
+ "-data_policy always_create ")
+
+#Output to Serial port 1 (console) group in the instance
+_OUTPUT_CONSOLE_GROUPS = "tty"
+SSH_BIN = "ssh"
+_SSH_CMD = (" -i %(rsa_key_file)s "
+ "-q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "
+ "-l %(login_user)s %(ip_addr)s ")
+_SSH_CMD_MAX_RETRY = 2
+_SSH_CMD_RETRY_SLEEP = 3
+_USER_BUILD = "userbuild"
+
+
+class RemoteInstanceDeviceFactory(base_device_factory.BaseDeviceFactory):
+ """A class that can produce a cuttlefish device.
+
+ Attributes:
+ avd_spec: AVDSpec object that tells us what we're going to create.
+ cfg: An AcloudConfig instance.
+ local_image_artifact: A string, path to local image.
+ cvd_host_package_artifact: A string, path to cvd host package.
+ report_internal_ip: Boolean, True for the internal ip is used when
+ connecting from another GCE instance.
+ credentials: An oauth2client.OAuth2Credentials instance.
+ compute_client: An object of cvd_compute_client.CvdComputeClient.
+ ssh_cmd: Sting, ssh command to connect GCE instance.
+ """
+ def __init__(self, avd_spec, local_image_artifact, cvd_host_package_artifact):
+ """Constructs a new remote instance device factory."""
+ self._avd_spec = avd_spec
+ self._cfg = avd_spec.cfg
+ self._local_image_artifact = local_image_artifact
+ self._cvd_host_package_artifact = cvd_host_package_artifact
+ self._report_internal_ip = avd_spec.report_internal_ip
+ self.credentials = auth.CreateCredentials(avd_spec.cfg)
+ compute_client = cvd_compute_client.CvdComputeClient(
+ avd_spec.cfg, self.credentials)
+ super(RemoteInstanceDeviceFactory, self).__init__(compute_client)
+ # Private creation parameters
+ self._ssh_cmd = None
+
+ def CreateInstance(self):
+ """Create a single configured cuttlefish device.
+
+ 1. Create gcp instance.
+ 2. setup the AVD env in the instance.
+ 3. upload the artifacts to instance.
+ 4. Launch CVD.
+
+ Returns:
+ A string, representing instance name.
+ """
+ instance = self._CreateGceInstance()
+ self._SetAVDenv(_CVD_USER)
+ self._UploadArtifacts(_CVD_USER,
+ self._local_image_artifact,
+ self._cvd_host_package_artifact,
+ self._avd_spec.local_image_dir)
+ self._LaunchCvd(_CVD_USER, self._avd_spec.hw_property)
+ return instance
+
+ @staticmethod
+ def _ShellCmdWithRetry(remote_cmd):
+ """Runs a shell command on remote device.
+
+ If the network is unstable and causes SSH connect fail, it will retry.
+ When it retry in a short time, you may encounter unstable network. We
+ will use the mechanism of RETRY_BACKOFF_FACTOR. The retry time for each
+ failure is times * retries.
+
+ Args:
+ remote_cmd: A string, shell command to be run on remote.
+
+ Raises:
+ subprocess.CalledProcessError: For any non-zero return code of
+ remote_cmd.
+
+ Returns:
+ Boolean, True if the command was successfully executed. False otherwise.
+ """
+ return utils.RetryExceptionType(
+ exception_types=subprocess.CalledProcessError,
+ max_retries=_SSH_CMD_MAX_RETRY,
+ functor=lambda cmd: subprocess.check_call(cmd, shell=True),
+ sleep_multiplier=_SSH_CMD_RETRY_SLEEP,
+ retry_backoff_factor=utils.DEFAULT_RETRY_BACKOFF_FACTOR,
+ cmd=remote_cmd)
+
+ def _CreateGceInstance(self):
+ """Create a single configured cuttlefish device.
+
+ Override method from parent class.
+ build_target: The format is like "aosp_cf_x86_phone". We only get info
+ from the user build image file name. If the file name is
+ not custom format (no "-"), we will use $TARGET_PRODUCT
+ from environment variable as build_target.
+
+ Returns:
+ A string, representing instance name.
+ """
+ image_name = os.path.basename(
+ self._local_image_artifact) if self._local_image_artifact else ""
+ build_target = (os.environ.get(constants.ENV_BUILD_TARGET) if "-" not
+ in image_name else image_name.split("-")[0])
+ instance = self._compute_client.GenerateInstanceName(
+ build_target=build_target, build_id=_USER_BUILD)
+ # Create an instance from Stable Host Image
+ self._compute_client.CreateInstance(
+ instance=instance,
+ image_name=self._cfg.stable_host_image_name,
+ image_project=self._cfg.stable_host_image_project,
+ blank_data_disk_size_gb=self._cfg.extra_data_disk_size_gb,
+ avd_spec=self._avd_spec)
+ ip = self._compute_client.GetInstanceIP(instance)
+ self._ssh_cmd = find_executable(SSH_BIN) + _SSH_CMD % {
+ "login_user": getpass.getuser(),
+ "rsa_key_file": self._cfg.ssh_private_key_path,
+ "ip_addr": (ip.internal if self._report_internal_ip
+ else ip.external)}
+ return instance
+
+ @utils.TimeExecute(function_description="Setting up GCE environment")
+ def _SetAVDenv(self, cvd_user):
+ """set the user to run AVD in the instance.
+
+ Args:
+ cvd_user: A string, user run the cvd in the instance.
+ """
+ avd_list_of_groups = []
+ avd_list_of_groups.extend(constants.LIST_CF_USER_GROUPS)
+ avd_list_of_groups.append(_OUTPUT_CONSOLE_GROUPS)
+ remote_cmd = ""
+ for group in avd_list_of_groups:
+ remote_cmd += "\"sudo usermod -aG %s %s;\"" %(group, cvd_user)
+ logger.debug("remote_cmd:\n %s", remote_cmd)
+ self._ShellCmdWithRetry(self._ssh_cmd + remote_cmd)
+
+ @utils.TimeExecute(function_description="Processing and uploading local images")
+ def _UploadArtifacts(self,
+ cvd_user,
+ local_image_zip,
+ cvd_host_package_artifact,
+ images_dir):
+ """Upload local images and avd local host package to instance.
+
+ There are two ways to upload local images.
+ 1. Using local image zip, it would be decompressed by install_zip.sh.
+ 2. Using local image directory, this directory contains all images.
+ Images are compressed/decompressed by lzop during upload process.
+
+ Args:
+ cvd_user: String, user upload the artifacts to instance.
+ local_image_zip: String, path to zip of local images which
+ build from 'm dist'.
+ cvd_host_package_artifact: String, path to cvd host package.
+ images_dir: String, directory of local images which build
+ from 'm'.
+ """
+ # TODO(b/133461252) Deprecate acloud create with local image zip.
+ # Upload local image zip file
+ if local_image_zip:
+ remote_cmd = ("\"sudo su -c '/usr/bin/install_zip.sh .' - '%s'\" < %s"
+ % (cvd_user, local_image_zip))
+ logger.debug("remote_cmd:\n %s", remote_cmd)
+ self._ShellCmdWithRetry(self._ssh_cmd + remote_cmd)
+ else:
+ # Compress image files for faster upload.
+ artifact_files = [os.path.basename(image) for image in glob.glob(
+ os.path.join(images_dir, "*.img"))]
+ cmd = ("tar -cf - --lzop -S -C {images_dir} {artifact_files} | "
+ "{ssh_cmd} -- tar -xf - --lzop -S".format(
+ images_dir=images_dir,
+ artifact_files=" ".join(artifact_files),
+ ssh_cmd=self._ssh_cmd))
+ logger.debug("cmd:\n %s", cmd)
+ self._ShellCmdWithRetry(cmd)
+
+ # host_package
+ remote_cmd = ("\"sudo su -c 'tar -x -z -f -' - '%s'\" < %s" %
+ (cvd_user, cvd_host_package_artifact))
+ logger.debug("remote_cmd:\n %s", remote_cmd)
+ self._ShellCmdWithRetry(self._ssh_cmd + remote_cmd)
+
+ def _LaunchCvd(self, cvd_user, hw_property):
+ """Launch CVD.
+
+ Args:
+ cvd_user: A string, user run the cvd in the instance.
+ hw_property: dict object of hw property.
+ """
+ launch_cvd_args = _CMD_LAUNCH_CVD_ARGS % (
+ hw_property["cpu"],
+ hw_property["x_res"],
+ hw_property["y_res"],
+ hw_property["dpi"],
+ hw_property["memory"])
+ if constants.HW_ALIAS_DISK in hw_property:
+ launch_cvd_args = (launch_cvd_args + _CMD_LAUNCH_CVD_DISK_ARGS %
+ hw_property[constants.HW_ALIAS_DISK])
+ remote_cmd = ("\"sudo su -c 'bin/launch_cvd %s>&/dev/ttyS0&' - '%s'\"" %
+ (launch_cvd_args, cvd_user))
+ logger.debug("remote_cmd:\n %s", remote_cmd)
+ subprocess.Popen(self._ssh_cmd + remote_cmd, shell=True)
diff --git a/public/actions/remote_instance_cf_device_factory_test.py b/public/actions/remote_instance_cf_device_factory_test.py
new file mode 100644
index 0000000..2a68a39
--- /dev/null
+++ b/public/actions/remote_instance_cf_device_factory_test.py
@@ -0,0 +1,105 @@
+# Copyright 2019 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Tests for remote_instance_cf_device_factory."""
+
+import glob
+import os
+import subprocess
+import time
+import unittest
+import uuid
+
+import mock
+
+from acloud.create import avd_spec
+from acloud.internal import constants
+from acloud.internal.lib import auth
+from acloud.internal.lib import cvd_compute_client
+from acloud.internal.lib import driver_test_lib
+from acloud.internal.lib import utils
+from acloud.public.actions import remote_instance_cf_device_factory
+
+
+class RemoteInstanceDeviceFactoryTest(driver_test_lib.BaseDriverTest):
+ """Test RemoteInstanceDeviceFactory method."""
+
+ def setUp(self):
+ """Set up the test."""
+ super(RemoteInstanceDeviceFactoryTest, self).setUp()
+ self.Patch(auth, "CreateCredentials", return_value=mock.MagicMock())
+ self.Patch(cvd_compute_client.CvdComputeClient, "InitResourceHandle")
+
+ # pylint: disable=protected-access
+ def testSSHExecuteWithRetry(self):
+ """test SSHExecuteWithRetry method."""
+ self.Patch(time, "sleep")
+ factory = remote_instance_cf_device_factory.RemoteInstanceDeviceFactory
+ self.Patch(subprocess, "check_call",
+ side_effect=subprocess.CalledProcessError(
+ None, "ssh command fail."))
+ self.assertRaises(subprocess.CalledProcessError,
+ factory._ShellCmdWithRetry,
+ "fake cmd")
+ self.assertEqual(subprocess.check_call.call_count, #pylint: disable=no-member
+ remote_instance_cf_device_factory._SSH_CMD_MAX_RETRY + 1)
+ self.Patch(subprocess, "check_call", return_value=True)
+ self.assertEqual(factory._ShellCmdWithRetry("fake cmd"), True)
+
+ # pylint: disable=protected-access
+ @mock.patch.dict(os.environ, {constants.ENV_BUILD_TARGET:'fake-target'})
+ def testCreateGceInstanceName(self):
+ """test create gce instance."""
+ self.Patch(utils, "GetBuildEnvironmentVariable",
+ return_value="test_environ")
+ self.Patch(glob, "glob", return_vale=["fake.img"])
+ # Mock uuid
+ args = mock.MagicMock()
+ args.config_file = ""
+ args.avd_type = constants.TYPE_CF
+ args.flavor = "phone"
+ args.local_image = None
+ args.adb_port = None
+ fake_avd_spec = avd_spec.AVDSpec(args)
+
+ fake_uuid = mock.MagicMock(hex="1234")
+ self.Patch(uuid, "uuid4", return_value=fake_uuid)
+ self.Patch(cvd_compute_client.CvdComputeClient, "CreateInstance")
+ fake_host_package_name = "/fake/host_package.tar.gz"
+ fake_image_name = "/fake/aosp_cf_x86_phone-img-eng.username.zip"
+
+ factory = remote_instance_cf_device_factory.RemoteInstanceDeviceFactory(
+ fake_avd_spec,
+ fake_image_name,
+ fake_host_package_name)
+ self.assertEqual(factory._CreateGceInstance(), "ins-1234-userbuild-aosp-cf-x86-phone")
+
+ # Can't get target name from zip file name.
+ fake_image_name = "/fake/aosp_cf_x86_phone.username.zip"
+ factory = remote_instance_cf_device_factory.RemoteInstanceDeviceFactory(
+ fake_avd_spec,
+ fake_image_name,
+ fake_host_package_name)
+ self.assertEqual(factory._CreateGceInstance(), "ins-1234-userbuild-fake-target")
+
+ # No image zip path, it uses local build images.
+ fake_image_name = ""
+ factory = remote_instance_cf_device_factory.RemoteInstanceDeviceFactory(
+ fake_avd_spec,
+ fake_image_name,
+ fake_host_package_name)
+ self.assertEqual(factory._CreateGceInstance(), "ins-1234-userbuild-fake-target")
+
+
+if __name__ == "__main__":
+ unittest.main()