acloud: Create instance and upload local image
- Using host image to create the instance.
- upload the cvd-host-package.tar.gz and local image to instance.
- lanuch the CVD in the instance.
Bug: 112878792
Test: make dist that generate the local image
acloud create --local_image
acloud create --local_image /tmp/image_dir that for specified path
atest acloud_test
Change-Id: Ifbd80fb10b05fd45b1477d68e2472ba0f5357c93
diff --git a/create/avd_spec.py b/create/avd_spec.py
index cce3be0..f062cca 100644
--- a/create/avd_spec.py
+++ b/create/avd_spec.py
@@ -77,6 +77,7 @@
# Let's define the private class vars here and then process the user
# args afterwards.
self._autoconnect = None
+ self._report_internal_ip = None
self._avd_type = None
self._flavor = None
self._image_source = None
@@ -214,6 +215,7 @@
args: Namespace object from argparse.parse_args.
"""
self._autoconnect = args.autoconnect
+ self._report_internal_ip = args.report_internal_ip
self._avd_type = args.avd_type
self._flavor = args.flavor
self._instance_type = (constants.INSTANCE_TYPE_LOCAL
@@ -377,6 +379,11 @@
return self._num_of_instances
@property
+ def report_internal_ip(self):
+ """Return report internal ip."""
+ return self._report_internal_ip
+
+ @property
def kernel_build_id(self):
"""Return kernel build id."""
return self._kernel_build_id
diff --git a/create/create_common.py b/create/create_common.py
index 5744833..c5473ce 100644
--- a/create/create_common.py
+++ b/create/create_common.py
@@ -23,6 +23,7 @@
import sys
from acloud import errors
+from acloud.internal.lib import utils
logger = logging.getLogger(__name__)
@@ -128,3 +129,31 @@
image_path = images[0]
logger.debug("Local image: %s ", image_path)
return image_path
+
+
+def DisplayJobResult(report):
+ """Get job result from report.
+
+ -Display instance name/ip from report.data.
+ report.data example:
+ {'devices':[{'instance_name': 'ins-f6a34397-none-5043363',
+ 'ip': u'35.234.10.162'}]}
+ -Display error message from report.error.
+
+ Args:
+ report: A Report instance.
+ """
+ if report.data.get("devices"):
+ device_data = report.data.get("devices")
+ for device in device_data:
+ utils.PrintColorString("instance name: %s" %
+ device.get("instance_name"),
+ utils.TextColors.OKGREEN)
+ utils.PrintColorString("device IP: %s" % device.get("ip"),
+ utils.TextColors.OKGREEN)
+
+ # TODO(b/117245508): Help user to delete instance if it got created.
+ if report.errors:
+ error_msg = "\n".join(report.errors)
+ utils.PrintColorString("Fail in:\n%s\n" % error_msg,
+ utils.TextColors.FAIL)
diff --git a/create/local_image_remote_instance.py b/create/local_image_remote_instance.py
index eadc2c8..af9d6ea 100644
--- a/create/local_image_remote_instance.py
+++ b/create/local_image_remote_instance.py
@@ -19,17 +19,150 @@
local image.
"""
+from distutils.spawn import find_executable
+import getpass
import logging
import os
+import subprocess
from acloud import errors
from acloud.create import create_common
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
logger = logging.getLogger(__name__)
+_ALL_SCOPES = [cvd_compute_client.CvdComputeClient.SCOPE]
_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 -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 ")
+
+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.
+ image_path: A string, upload image artifact to instance.
+ cvd_host_package: A string, upload host package artifact to instance.
+ credentials: An oauth2client.OAuth2Credentials instance.
+ compute_client: An object of cvd_compute_client.CvdComputeClient.
+ """
+ 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, _ALL_SCOPES)
+ 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._LaunchCvd(_CVD_USER, self._avd_spec.hw_property)
+ return instance
+
+ def _CreateGceInstance(self):
+ """Create a single configured cuttlefish device.
+
+ Override method from parent class.
+
+ Returns:
+ A string, representing instance name.
+ """
+ #TODO(117487673): Grab the build target name from the image name.
+ instance = self._compute_client.GenerateInstanceName(
+ build_target=self._avd_spec.flavor, build_id="local")
+ # 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="Setup GCE environment")
+ def _SetAVDenv(self, cvd_user):
+ """set the user to run AVD 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)
+ 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)
+ subprocess.check_call(self._ssh_cmd + remote_cmd, shell=True)
+
+ @utils.TimeExecute(function_description="Uploading local image")
+ def _UploadArtifacts(self,
+ cvd_user,
+ local_image_artifact,
+ cvd_host_package_artifact):
+ """Upload local image and avd local host package to instance."""
+ # local image
+ remote_cmd = ("\"sudo su -c '/usr/bin/install_zip.sh .' - '%s'\" < %s" %
+ (cvd_user, local_image_artifact))
+ logger.debug("remote_cmd:\n %s", remote_cmd)
+ subprocess.check_call(self._ssh_cmd + remote_cmd, shell=True)
+
+ # 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)
+ subprocess.check_call(self._ssh_cmd + remote_cmd, shell=True)
+
+ def _LaunchCvd(self, cvd_user, hw_property):
+ """Launch CVD."""
+ lunch_cvd_args = _CMD_LAUNCH_CVD_ARGS % (
+ hw_property["cpu"],
+ hw_property["x_res"],
+ hw_property["y_res"],
+ hw_property["dpi"],
+ hw_property["memory"],
+ hw_property["disk"])
+ remote_cmd = ("\"sudo su -c 'bin/launch_cvd %s>&/dev/ttyS0&' - '%s'\"" %
+ (lunch_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):
@@ -93,12 +226,22 @@
"Can't find the cvd host package: \n%s" %
'\n'.join(paths))
+ @utils.TimeExecute(function_description="Total time: ",
+ print_before_call=False, print_status=False)
def Create(self, avd_spec):
"""Create the AVD.
Args:
avd_spec: AVDSpec object that tells us what we're going to create.
"""
- print("We will create a remote instance AVD with a local image: %s" %
- avd_spec)
+ self.PrintAvdDetails(avd_spec)
self.VerifyArtifactsExist(avd_spec.local_image_dir)
+ device_factory = RemoteInstanceDeviceFactory(
+ avd_spec,
+ self.local_image_artifact,
+ self.cvd_host_package_artifact)
+ report = common_operations.CreateDevices("create_cf", avd_spec.cfg,
+ device_factory, avd_spec.num,
+ avd_spec.report_internal_ip)
+ create_common.DisplayJobResult(report)
+ return report
diff --git a/create/remote_image_remote_instance.py b/create/remote_image_remote_instance.py
index f50ad79..0937acd 100644
--- a/create/remote_image_remote_instance.py
+++ b/create/remote_image_remote_instance.py
@@ -19,9 +19,8 @@
remote image.
"""
-import time
-
from acloud.create import base_avd_create
+from acloud.create import create_common
from acloud.internal.lib import utils
from acloud.public.actions import create_cuttlefish_action
@@ -29,6 +28,8 @@
class RemoteImageRemoteInstance(base_avd_create.BaseAVDCreate):
"""Create class for a remote image remote instance AVD."""
+ @utils.TimeExecute(function_description="Total time: ",
+ print_before_call=False, print_status=False)
def Create(self, avd_spec):
"""Create the AVD.
@@ -39,38 +40,6 @@
A Report instance.
"""
self.PrintAvdDetails(avd_spec)
- start = time.time()
report = create_cuttlefish_action.CreateDevices(avd_spec=avd_spec)
- utils.PrintColorString("\n")
- utils.PrintColorString("Total time: %ds" % (time.time() - start),
- utils.TextColors.WARNING)
- self.DisplayJobResult(report)
+ create_common.DisplayJobResult(report)
return report
-
- @staticmethod
- def DisplayJobResult(report):
- """Get job result from report.
-
- -Display instance name/ip from report.data.
- report.data example:
- {'devices':[{'instance_name': 'ins-f6a34397-none-5043363',
- 'ip': u'35.234.10.162'}]}
- -Display error message from report.error.
-
- Args:
- report: A Report instance.
- """
- if report.data.get("devices"):
- device_data = report.data.get("devices")
- for device in device_data:
- utils.PrintColorString("instance name: %s" %
- device.get("instance_name"),
- utils.TextColors.OKGREEN)
- utils.PrintColorString("device IP: %s" % device.get("ip"),
- utils.TextColors.OKGREEN)
-
- # TODO(b/117245508): Help user to delete instance if it got created.
- if report.errors:
- error_msg = "\n".join(report.errors)
- utils.PrintColorString("Fail in:\n%s\n" % error_msg,
- utils.TextColors.FAIL)
diff --git a/internal/lib/cvd_compute_client.py b/internal/lib/cvd_compute_client.py
index 8c17521..fffd859 100644
--- a/internal/lib/cvd_compute_client.py
+++ b/internal/lib/cvd_compute_client.py
@@ -13,7 +13,6 @@
# 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.
-
"""A client that manages Cuttlefish Virtual Device on compute engine.
** CvdComputeClient **
@@ -44,6 +43,7 @@
from acloud.internal import constants
from acloud.internal.lib import android_compute_client
from acloud.internal.lib import gcompute_client
+from acloud.internal.lib import utils
logger = logging.getLogger(__name__)
@@ -57,10 +57,11 @@
# args, this method differs too and holds way too cf-specific args to put in
# the parent method.
# pylint: disable=arguments-differ,too-many-locals
- def CreateInstance(self, instance, image_name, image_project, build_target,
- branch, build_id, kernel_branch=None,
- kernel_build_id=None, blank_data_disk_size_gb=None,
- avd_spec=None):
+ @utils.TimeExecute(function_description="Creating GCE instance")
+ def CreateInstance(self, instance, image_name, image_project,
+ build_target=None, branch=None, build_id=None,
+ kernel_branch=None, kernel_build_id=None,
+ blank_data_disk_size_gb=None, avd_spec=None):
"""Create a cuttlefish instance given stable host image and build id.
Args:
@@ -101,7 +102,9 @@
if kernel_branch and kernel_build_id:
metadata["cvd_01_fetch_kernel_bid"] = "{branch}/{build_id}".format(
branch=kernel_branch, build_id=kernel_build_id)
- metadata["cvd_01_launch"] = "1"
+ metadata["cvd_01_launch"] = "0" if (
+ avd_spec
+ and avd_spec.image_source == constants.IMAGE_SRC_LOCAL) else "1"
metadata["cvd_01_x_res"] = resolution[0]
metadata["cvd_01_y_res"] = resolution[1]
if blank_data_disk_size_gb > 0:
diff --git a/internal/lib/cvd_compute_client_test.py b/internal/lib/cvd_compute_client_test.py
index 96476be..c5afdc3 100644
--- a/internal/lib/cvd_compute_client_test.py
+++ b/internal/lib/cvd_compute_client_test.py
@@ -19,6 +19,8 @@
import unittest
import mock
+from acloud.create import avd_spec
+from acloud.internal import constants
from acloud.internal.lib import cvd_compute_client
from acloud.internal.lib import driver_test_lib
from acloud.internal.lib import gcompute_client
@@ -115,6 +117,35 @@
network=self.NETWORK,
zone=self.ZONE)
+ #test use local image in the remote instance.
+ args = mock.MagicMock()
+ args.local_image = "/tmp/path"
+ args.config_file = ""
+ args.avd_type = "cf"
+ args.flavor = "phone"
+ fake_avd_spec = avd_spec.AVDSpec(args)
+ fake_avd_spec.hw_property[constants.HW_X_RES] = str(self.X_RES)
+ fake_avd_spec.hw_property[constants.HW_Y_RES] = str(self.Y_RES)
+ fake_avd_spec.hw_property[constants.HW_ALIAS_DPI] = str(self.DPI)
+ fake_avd_spec.hw_property[constants.HW_ALIAS_DISK] = str(
+ self.EXTRA_DATA_DISK_SIZE_GB * 1024)
+ expected_metadata["cvd_01_launch"] = "0"
+ expected_metadata["avd_type"] = "cf"
+ expected_metadata["flavor"] = "phone"
+ self.cvd_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.EXTRA_DATA_DISK_SIZE_GB, fake_avd_spec)
+ mock_create.assert_called_with(
+ self.cvd_compute_client,
+ instance=self.INSTANCE,
+ image_name=self.IMAGE,
+ image_project=self.IMAGE_PROJECT,
+ disk_args=expected_disk_args,
+ metadata=expected_metadata,
+ machine_type=self.MACHINE_TYPE,
+ network=self.NETWORK,
+ zone=self.ZONE)
if __name__ == "__main__":
unittest.main()
diff --git a/internal/lib/utils.py b/internal/lib/utils.py
index ae334ac..241c3c8 100755
--- a/internal/lib/utils.py
+++ b/internal/lib/utils.py
@@ -575,3 +575,53 @@
exception is an instance of DriverError or None if no error.
"""
return self._final_results
+
+
+class TimeExecute(object):
+ """Count the function execute time."""
+
+ def __init__(self, function_description=None, print_before_call=True, print_status=True):
+ """Initializes the class.
+
+ Args:
+ function_description: String that describes function (e.g."Creating
+ Instance...")
+ print_before_call: Boolean, print the function description before
+ calling the function, default True.
+ print_status: Boolean, print the status of the function after the
+ function has completed, default True ("OK" or "Fail").
+ """
+ self._function_description = function_description
+ self._print_before_call = print_before_call
+ self._print_status = print_status
+
+ def __call__(self, func):
+ def DecoratorFunction(*args, **kargs):
+ """Decorator function.
+
+ Args:
+ *args: Arguments to pass to the functor.
+ **kwargs: Key-val based arguments to pass to the functor.
+
+ Raises:
+ Exception: The exception that functor(*args, **kwargs) throws.
+ """
+ timestart = time.time()
+ if self._print_before_call:
+ PrintColorString("%s ..."% self._function_description, end="")
+ try:
+ result = func(*args, **kargs)
+ if not self._print_before_call:
+ PrintColorString("%s (%ds)" % (self._function_description,
+ time.time()-timestart),
+ TextColors.OKGREEN)
+ if self._print_status:
+ PrintColorString("OK! (%ds)" % (time.time()-timestart),
+ TextColors.OKGREEN)
+ return result
+ except:
+ if self._print_status:
+ PrintColorString("Fail! (%ds)" % (time.time()-timestart),
+ TextColors.FAIL)
+ raise
+ return DecoratorFunction
diff --git a/public/actions/common_operations.py b/public/actions/common_operations.py
index 09f6495..e3a4035 100644
--- a/public/actions/common_operations.py
+++ b/public/actions/common_operations.py
@@ -22,7 +22,6 @@
from __future__ import print_function
import logging
-import time
from acloud.public import avd
from acloud.public import errors
@@ -100,6 +99,7 @@
self.devices.append(
avd.AndroidVirtualDevice(ip=ip, instance_name=instance))
+ @utils.TimeExecute(function_description="Waiting for AVD(s) to boot up")
def WaitForBoot(self):
"""Waits for all devices to boot up.
@@ -149,32 +149,14 @@
"""
reporter = report.Report(command=command)
try:
- gce_start_time = time.time()
- utils.PrintColorString("Creating GCE instance%s..." %
- ("s" if num > 1 else ""), end="")
CreateSshKeyPairIfNecessary(cfg)
device_pool = DevicePool(device_factory)
- try:
- device_pool.CreateDevices(num)
- except:
- utils.PrintColorString("Fail (%ds)" % (time.time() - gce_start_time),
- utils.TextColors.FAIL)
- raise
- utils.PrintColorString("OK (%ds)" % (time.time() - gce_start_time),
- utils.TextColors.OKGREEN)
-
- utils.PrintColorString("Starting up AVD%s..." %
- ("s" if num > 1 else ""), end="")
- start_boot_time = time.time()
+ device_pool.CreateDevices(num)
failures = device_pool.WaitForBoot()
if failures:
reporter.SetStatus(report.Status.BOOT_FAIL)
- utils.PrintColorString("Fail (%ds)" % (time.time() - start_boot_time),
- utils.TextColors.FAIL)
else:
reporter.SetStatus(report.Status.SUCCESS)
- utils.PrintColorString("OK (%ds)" % (time.time() - start_boot_time),
- utils.TextColors.OKGREEN)
# Write result to report.
for device in device_pool.devices:
device_dict = {