blob: b412666f08756561ba3f843abbaf910cc6c14812 [file] [log] [blame]
Kevin Chengb5963882018-05-09 00:06:27 -07001#!/usr/bin/env python
2#
3# Copyright 2018 - The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
Kevin Chengb5963882018-05-09 00:06:27 -070016"""Common operations between managing GCE and Cuttlefish devices.
17
18This module provides the common operations between managing GCE (device_driver)
19and Cuttlefish (create_cuttlefish_action) devices. Should not be called
20directly.
21"""
22
herbertxuedf01c422018-09-06 19:52:52 +080023from __future__ import print_function
cylan66713722018-10-06 01:38:26 +080024import getpass
Kevin Chengb5963882018-05-09 00:06:27 -070025import logging
Kevin Chengb5963882018-05-09 00:06:27 -070026
27from acloud.public import avd
28from acloud.public import errors
29from acloud.public import report
cylan66713722018-10-06 01:38:26 +080030from acloud.internal import constants
Kevin Chengb5963882018-05-09 00:06:27 -070031from acloud.internal.lib import utils
32
33logger = logging.getLogger(__name__)
34
cylan66713722018-10-06 01:38:26 +080035#For the cuttlefish remote instances: adb port is 6520 and vnc is 6444.
36_CF_TARGET_ADB_PORT = 6520
37_CF_TARGET_VNC_PORT = 6444
Kevin Chengb5963882018-05-09 00:06:27 -070038
39def CreateSshKeyPairIfNecessary(cfg):
Kevin Cheng3031f8a2018-05-16 13:21:51 -070040 """Create ssh key pair if necessary.
Kevin Chengb5963882018-05-09 00:06:27 -070041
Kevin Cheng3031f8a2018-05-16 13:21:51 -070042 Args:
43 cfg: An Acloudconfig instance.
Kevin Chengb5963882018-05-09 00:06:27 -070044
Kevin Cheng3031f8a2018-05-16 13:21:51 -070045 Raises:
46 error.DriverError: If it falls into an unexpected condition.
47 """
48 if not cfg.ssh_public_key_path:
49 logger.warning(
50 "ssh_public_key_path is not specified in acloud config. "
51 "Project-wide public key will "
52 "be used when creating AVD instances. "
53 "Please ensure you have the correct private half of "
54 "a project-wide public key if you want to ssh into the "
55 "instances after creation.")
56 elif cfg.ssh_public_key_path and not cfg.ssh_private_key_path:
57 logger.warning(
58 "Only ssh_public_key_path is specified in acloud config, "
59 "but ssh_private_key_path is missing. "
60 "Please ensure you have the correct private half "
61 "if you want to ssh into the instances after creation.")
62 elif cfg.ssh_public_key_path and cfg.ssh_private_key_path:
63 utils.CreateSshKeyPairIfNotExist(cfg.ssh_private_key_path,
64 cfg.ssh_public_key_path)
65 else:
66 # Should never reach here.
67 raise errors.DriverError(
68 "Unexpected error in CreateSshKeyPairIfNecessary")
Kevin Chengb5963882018-05-09 00:06:27 -070069
70
71class DevicePool(object):
Kevin Cheng3031f8a2018-05-16 13:21:51 -070072 """A class that manages a pool of virtual devices.
Kevin Chengb5963882018-05-09 00:06:27 -070073
Kevin Cheng3031f8a2018-05-16 13:21:51 -070074 Attributes:
75 devices: A list of devices in the pool.
Kevin Chengb5963882018-05-09 00:06:27 -070076 """
77
Kevin Cheng3031f8a2018-05-16 13:21:51 -070078 def __init__(self, device_factory, devices=None):
79 """Constructs a new DevicePool.
Kevin Chengb5963882018-05-09 00:06:27 -070080
Kevin Cheng3031f8a2018-05-16 13:21:51 -070081 Args:
82 device_factory: A device factory capable of producing a goldfish or
83 cuttlefish device. The device factory must expose an attribute with
84 the credentials that can be used to retrieve information from the
85 constructed device.
86 devices: List of devices managed by this pool.
87 """
88 self._devices = devices or []
89 self._device_factory = device_factory
90 self._compute_client = device_factory.GetComputeClient()
Kevin Chengb5963882018-05-09 00:06:27 -070091
Kevin Cheng3031f8a2018-05-16 13:21:51 -070092 def CreateDevices(self, num):
93 """Creates |num| devices for given build_target and build_id.
Kevin Chengb5963882018-05-09 00:06:27 -070094
Kevin Cheng3031f8a2018-05-16 13:21:51 -070095 Args:
96 num: Number of devices to create.
97 """
Kevin Cheng3031f8a2018-05-16 13:21:51 -070098 # Create host instances for cuttlefish/goldfish device.
99 # Currently one instance supports only 1 device.
100 for _ in range(num):
101 instance = self._device_factory.CreateInstance()
102 ip = self._compute_client.GetInstanceIP(instance)
103 self.devices.append(
104 avd.AndroidVirtualDevice(ip=ip, instance_name=instance))
105
cylan31fc5332018-09-17 22:12:08 +0800106 @utils.TimeExecute(function_description="Waiting for AVD(s) to boot up")
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700107 def WaitForBoot(self):
108 """Waits for all devices to boot up.
109
110 Returns:
111 A dictionary that contains all the failures.
112 The key is the name of the instance that fails to boot,
113 and the value is an errors.DeviceBootError object.
114 """
115 failures = {}
116 for device in self._devices:
117 try:
118 self._compute_client.WaitForBoot(device.instance_name)
119 except errors.DeviceBootError as e:
120 failures[device.instance_name] = e
121 return failures
122
123 @property
124 def devices(self):
125 """Returns a list of devices in the pool.
126
127 Returns:
128 A list of devices in the pool.
129 """
130 return self._devices
Kevin Chengb5963882018-05-09 00:06:27 -0700131
132
cylan66713722018-10-06 01:38:26 +0800133# pylint: disable=too-many-locals
134def CreateDevices(command, cfg, device_factory, num, report_internal_ip=False,
135 autoconnect=False):
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700136 """Create a set of devices using the given factory.
Kevin Chengb5963882018-05-09 00:06:27 -0700137
herbertxuedf01c422018-09-06 19:52:52 +0800138 Main jobs in create devices.
139 1. Create GCE instance: Launch instance in GCP(Google Cloud Platform).
140 2. Starting up AVD: Wait device boot up.
141
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700142 Args:
143 command: The name of the command, used for reporting.
144 cfg: An AcloudConfig instance.
145 device_factory: A factory capable of producing a single device.
146 num: The number of devices to create.
Kevin Cheng86d43c72018-08-30 10:59:14 -0700147 report_internal_ip: Boolean to report the internal ip instead of
148 external ip.
Kevin Chengb5963882018-05-09 00:06:27 -0700149
herbertxuedf01c422018-09-06 19:52:52 +0800150 Raises:
151 errors: Create instance fail.
152
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700153 Returns:
154 A Report instance.
155 """
156 reporter = report.Report(command=command)
157 try:
158 CreateSshKeyPairIfNecessary(cfg)
159 device_pool = DevicePool(device_factory)
cylan31fc5332018-09-17 22:12:08 +0800160 device_pool.CreateDevices(num)
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700161 failures = device_pool.WaitForBoot()
herbertxuedf01c422018-09-06 19:52:52 +0800162 if failures:
163 reporter.SetStatus(report.Status.BOOT_FAIL)
herbertxuedf01c422018-09-06 19:52:52 +0800164 else:
165 reporter.SetStatus(report.Status.SUCCESS)
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700166 # Write result to report.
167 for device in device_pool.devices:
cylan66713722018-10-06 01:38:26 +0800168 ip = (device.ip.internal if report_internal_ip
169 else device.ip.external)
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700170 device_dict = {
cylan66713722018-10-06 01:38:26 +0800171 "ip": ip,
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700172 "instance_name": device.instance_name
173 }
cylan66713722018-10-06 01:38:26 +0800174 if autoconnect:
175 forwarded_ports = utils.AutoConnect(ip,
176 cfg.ssh_private_key_path,
177 _CF_TARGET_VNC_PORT,
178 _CF_TARGET_ADB_PORT,
179 getpass.getuser())
180 device_dict[constants.VNC_PORT] = forwarded_ports.vnc_port
181 device_dict[constants.ADB_PORT] = forwarded_ports.adb_port
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700182 if device.instance_name in failures:
183 reporter.AddData(key="devices_failing_boot", value=device_dict)
184 reporter.AddError(str(failures[device.instance_name]))
185 else:
186 reporter.AddData(key="devices", value=device_dict)
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700187 except errors.DriverError as e:
188 reporter.AddError(str(e))
189 reporter.SetStatus(report.Status.FAIL)
190 return reporter