blob: 5a6d1d25b8c0a272bd2536c80ee8940ad53a2eed [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
23import logging
Kevin Chengb5963882018-05-09 00:06:27 -070024
25from acloud.public import avd
26from acloud.public import errors
27from acloud.public import report
28from acloud.internal.lib import utils
29
30logger = logging.getLogger(__name__)
31
32
33def CreateSshKeyPairIfNecessary(cfg):
Kevin Cheng3031f8a2018-05-16 13:21:51 -070034 """Create ssh key pair if necessary.
Kevin Chengb5963882018-05-09 00:06:27 -070035
Kevin Cheng3031f8a2018-05-16 13:21:51 -070036 Args:
37 cfg: An Acloudconfig instance.
Kevin Chengb5963882018-05-09 00:06:27 -070038
Kevin Cheng3031f8a2018-05-16 13:21:51 -070039 Raises:
40 error.DriverError: If it falls into an unexpected condition.
41 """
42 if not cfg.ssh_public_key_path:
43 logger.warning(
44 "ssh_public_key_path is not specified in acloud config. "
45 "Project-wide public key will "
46 "be used when creating AVD instances. "
47 "Please ensure you have the correct private half of "
48 "a project-wide public key if you want to ssh into the "
49 "instances after creation.")
50 elif cfg.ssh_public_key_path and not cfg.ssh_private_key_path:
51 logger.warning(
52 "Only ssh_public_key_path is specified in acloud config, "
53 "but ssh_private_key_path is missing. "
54 "Please ensure you have the correct private half "
55 "if you want to ssh into the instances after creation.")
56 elif cfg.ssh_public_key_path and cfg.ssh_private_key_path:
57 utils.CreateSshKeyPairIfNotExist(cfg.ssh_private_key_path,
58 cfg.ssh_public_key_path)
59 else:
60 # Should never reach here.
61 raise errors.DriverError(
62 "Unexpected error in CreateSshKeyPairIfNecessary")
Kevin Chengb5963882018-05-09 00:06:27 -070063
64
65class DevicePool(object):
Kevin Cheng3031f8a2018-05-16 13:21:51 -070066 """A class that manages a pool of virtual devices.
Kevin Chengb5963882018-05-09 00:06:27 -070067
Kevin Cheng3031f8a2018-05-16 13:21:51 -070068 Attributes:
69 devices: A list of devices in the pool.
Kevin Chengb5963882018-05-09 00:06:27 -070070 """
71
Kevin Cheng3031f8a2018-05-16 13:21:51 -070072 def __init__(self, device_factory, devices=None):
73 """Constructs a new DevicePool.
Kevin Chengb5963882018-05-09 00:06:27 -070074
Kevin Cheng3031f8a2018-05-16 13:21:51 -070075 Args:
76 device_factory: A device factory capable of producing a goldfish or
77 cuttlefish device. The device factory must expose an attribute with
78 the credentials that can be used to retrieve information from the
79 constructed device.
80 devices: List of devices managed by this pool.
81 """
82 self._devices = devices or []
83 self._device_factory = device_factory
84 self._compute_client = device_factory.GetComputeClient()
Kevin Chengb5963882018-05-09 00:06:27 -070085
Kevin Cheng3031f8a2018-05-16 13:21:51 -070086 def CreateDevices(self, num):
87 """Creates |num| devices for given build_target and build_id.
Kevin Chengb5963882018-05-09 00:06:27 -070088
Kevin Cheng3031f8a2018-05-16 13:21:51 -070089 Args:
90 num: Number of devices to create.
91 """
Kevin Chengb5963882018-05-09 00:06:27 -070092
Kevin Cheng3031f8a2018-05-16 13:21:51 -070093 # Create host instances for cuttlefish/goldfish device.
94 # Currently one instance supports only 1 device.
95 for _ in range(num):
96 instance = self._device_factory.CreateInstance()
97 ip = self._compute_client.GetInstanceIP(instance)
98 self.devices.append(
99 avd.AndroidVirtualDevice(ip=ip, instance_name=instance))
100
101 def WaitForBoot(self):
102 """Waits for all devices to boot up.
103
104 Returns:
105 A dictionary that contains all the failures.
106 The key is the name of the instance that fails to boot,
107 and the value is an errors.DeviceBootError object.
108 """
109 failures = {}
110 for device in self._devices:
111 try:
112 self._compute_client.WaitForBoot(device.instance_name)
113 except errors.DeviceBootError as e:
114 failures[device.instance_name] = e
115 return failures
116
117 @property
118 def devices(self):
119 """Returns a list of devices in the pool.
120
121 Returns:
122 A list of devices in the pool.
123 """
124 return self._devices
Kevin Chengb5963882018-05-09 00:06:27 -0700125
126
127def CreateDevices(command, cfg, device_factory, num):
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700128 """Create a set of devices using the given factory.
Kevin Chengb5963882018-05-09 00:06:27 -0700129
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700130 Args:
131 command: The name of the command, used for reporting.
132 cfg: An AcloudConfig instance.
133 device_factory: A factory capable of producing a single device.
134 num: The number of devices to create.
Kevin Chengb5963882018-05-09 00:06:27 -0700135
Kevin Cheng3031f8a2018-05-16 13:21:51 -0700136 Returns:
137 A Report instance.
138 """
139 reporter = report.Report(command=command)
140 try:
141 CreateSshKeyPairIfNecessary(cfg)
142 device_pool = DevicePool(device_factory)
143 device_pool.CreateDevices(num)
144 failures = device_pool.WaitForBoot()
145 # Write result to report.
146 for device in device_pool.devices:
147 device_dict = {
148 "ip": device.ip,
149 "instance_name": device.instance_name
150 }
151 if device.instance_name in failures:
152 reporter.AddData(key="devices_failing_boot", value=device_dict)
153 reporter.AddError(str(failures[device.instance_name]))
154 else:
155 reporter.AddData(key="devices", value=device_dict)
156 if failures:
157 reporter.SetStatus(report.Status.BOOT_FAIL)
158 else:
159 reporter.SetStatus(report.Status.SUCCESS)
160 except errors.DriverError as e:
161 reporter.AddError(str(e))
162 reporter.SetStatus(report.Status.FAIL)
163 return reporter