Secondary migration from google3 into AOSP.
The 2nd revival of AOSP acloud is upon us!
The cl revision the acloud g3 code was pulled from is 195930083.
Things I did:
- Added AOSP copyright for new files and updated g3 imports to be relative.
- Merged in changes into existing files (easier to see changes here and
for future cls).
- Scrubbed default.config of project and build info.
- Merge acloud.py (from g3) into acloud_main.py (entry point for AOSP
acloud).
- Regenerated internal_config_pb2.py and user_config_pb2.py.
- Removed add_mock from gcomputer_client_test and added TODO in file
where to replace it and updated parameterized to import from
absl.testing.
- Updated references to gce_x86 to aosp_cf_x86_phone and updated branch
references to 'aosp-master'.
Thing to note:
- New files fail pylint (in order to make it easy to check history on new files,
formatting will be done using yapf in another cl).
- pip install acloud.zip seg faults so investigation and fix for that
will happen in another cl.
- User needs to 'pip install absl-py' for parameterized lib in unittests.
Bug: 79684654
Test: ./run_tests.sh
Change-Id: I060641227d7c9162a45557e732686f22b83895e9
diff --git a/public/actions/common_operations.py b/public/actions/common_operations.py
new file mode 100644
index 0000000..74ee50c
--- /dev/null
+++ b/public/actions/common_operations.py
@@ -0,0 +1,159 @@
+#!/usr/bin/env python
+#
+# Copyright 2018 - 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.
+
+"""Common operations between managing GCE and Cuttlefish devices.
+
+This module provides the common operations between managing GCE (device_driver)
+and Cuttlefish (create_cuttlefish_action) devices. Should not be called
+directly.
+"""
+
+import logging
+import os
+
+from acloud.public import avd
+from acloud.public import errors
+from acloud.public import report
+from acloud.internal.lib import utils
+
+logger = logging.getLogger(__name__)
+
+
+def CreateSshKeyPairIfNecessary(cfg):
+ """Create ssh key pair if necessary.
+
+ Args:
+ cfg: An Acloudconfig instance.
+
+ Raises:
+ error.DriverError: If it falls into an unexpected condition.
+ """
+ if not cfg.ssh_public_key_path:
+ logger.warning("ssh_public_key_path is not specified in acloud config. "
+ "Project-wide public key will "
+ "be used when creating AVD instances. "
+ "Please ensure you have the correct private half of "
+ "a project-wide public key if you want to ssh into the "
+ "instances after creation.")
+ elif cfg.ssh_public_key_path and not cfg.ssh_private_key_path:
+ logger.warning("Only ssh_public_key_path is specified in acloud config, "
+ "but ssh_private_key_path is missing. "
+ "Please ensure you have the correct private half "
+ "if you want to ssh into the instances after creation.")
+ elif cfg.ssh_public_key_path and cfg.ssh_private_key_path:
+ utils.CreateSshKeyPairIfNotExist(cfg.ssh_private_key_path,
+ cfg.ssh_public_key_path)
+ else:
+ # Should never reach here.
+ raise errors.DriverError("Unexpected error in CreateSshKeyPairIfNecessary")
+
+
+class DevicePool(object):
+ """A class that manages a pool of virtual devices.
+
+ Attributes:
+ devices: A list of devices in the pool.
+ """
+
+ def __init__(self, device_factory, devices=None):
+ """Constructs a new DevicePool.
+
+ Args:
+ device_factory: A device factory capable of producing a goldfish or
+ cuttlefish device. The device factory must expose an attribute with
+ the credentials that can be used to retrieve information from the
+ constructed device.
+ devices: List of devices managed by this pool.
+ """
+ self._devices = devices or []
+ self._device_factory = device_factory
+ self._compute_client = device_factory.GetComputeClient()
+
+ def CreateDevices(self, num):
+ """Creates |num| devices for given build_target and build_id.
+
+ Args:
+ num: Number of devices to create.
+ """
+
+ # Create host instances for cuttlefish/goldfish device.
+ # Currently one instance supports only 1 device.
+ for _ in range(num):
+ instance = self._device_factory.CreateInstance()
+ ip = self._compute_client.GetInstanceIP(instance)
+ self.devices.append(
+ avd.AndroidVirtualDevice(ip=ip, instance_name=instance))
+
+ def WaitForBoot(self):
+ """Waits for all devices to boot up.
+
+ Returns:
+ A dictionary that contains all the failures.
+ The key is the name of the instance that fails to boot,
+ and the value is an errors.DeviceBootError object.
+ """
+ failures = {}
+ for device in self._devices:
+ try:
+ self._compute_client.WaitForBoot(device.instance_name)
+ except errors.DeviceBootError as e:
+ failures[device.instance_name] = e
+ return failures
+
+ @property
+ def devices(self):
+ """Returns a list of devices in the pool.
+
+ Returns:
+ A list of devices in the pool.
+ """
+ return self._devices
+
+
+def CreateDevices(command, cfg, device_factory, num):
+ """Create a set of devices using the given factory.
+
+ Args:
+ command: The name of the command, used for reporting.
+ cfg: An AcloudConfig instance.
+ device_factory: A factory capable of producing a single device.
+ num: The number of devices to create.
+
+ Returns:
+ A Report instance.
+ """
+ reporter = report.Report(command=command)
+ try:
+ CreateSshKeyPairIfNecessary(cfg)
+ device_pool = DevicePool(device_factory)
+ device_pool.CreateDevices(num)
+ failures = device_pool.WaitForBoot()
+ # Write result to report.
+ for device in device_pool.devices:
+ device_dict = {"ip": device.ip, "instance_name": device.instance_name}
+ if device.instance_name in failures:
+ reporter.AddData(key="devices_failing_boot", value=device_dict)
+ reporter.AddError(str(failures[device.instance_name]))
+ else:
+ reporter.AddData(key="devices", value=device_dict)
+ if failures:
+ reporter.SetStatus(report.Status.BOOT_FAIL)
+ else:
+ reporter.SetStatus(report.Status.SUCCESS)
+ except errors.DriverError as e:
+ reporter.AddError(str(e))
+ reporter.SetStatus(report.Status.FAIL)
+ return reporter