blob: 74ee50c710a8f69f409eee4f68d46ffe9b8939bb [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.
16
17"""Common operations between managing GCE and Cuttlefish devices.
18
19This module provides the common operations between managing GCE (device_driver)
20and Cuttlefish (create_cuttlefish_action) devices. Should not be called
21directly.
22"""
23
24import logging
25import os
26
27from acloud.public import avd
28from acloud.public import errors
29from acloud.public import report
30from acloud.internal.lib import utils
31
32logger = logging.getLogger(__name__)
33
34
35def CreateSshKeyPairIfNecessary(cfg):
36 """Create ssh key pair if necessary.
37
38 Args:
39 cfg: An Acloudconfig instance.
40
41 Raises:
42 error.DriverError: If it falls into an unexpected condition.
43 """
44 if not cfg.ssh_public_key_path:
45 logger.warning("ssh_public_key_path is not specified in acloud config. "
46 "Project-wide public key will "
47 "be used when creating AVD instances. "
48 "Please ensure you have the correct private half of "
49 "a project-wide public key if you want to ssh into the "
50 "instances after creation.")
51 elif cfg.ssh_public_key_path and not cfg.ssh_private_key_path:
52 logger.warning("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("Unexpected error in CreateSshKeyPairIfNecessary")
62
63
64class DevicePool(object):
65 """A class that manages a pool of virtual devices.
66
67 Attributes:
68 devices: A list of devices in the pool.
69 """
70
71 def __init__(self, device_factory, devices=None):
72 """Constructs a new DevicePool.
73
74 Args:
75 device_factory: A device factory capable of producing a goldfish or
76 cuttlefish device. The device factory must expose an attribute with
77 the credentials that can be used to retrieve information from the
78 constructed device.
79 devices: List of devices managed by this pool.
80 """
81 self._devices = devices or []
82 self._device_factory = device_factory
83 self._compute_client = device_factory.GetComputeClient()
84
85 def CreateDevices(self, num):
86 """Creates |num| devices for given build_target and build_id.
87
88 Args:
89 num: Number of devices to create.
90 """
91
92 # Create host instances for cuttlefish/goldfish device.
93 # Currently one instance supports only 1 device.
94 for _ in range(num):
95 instance = self._device_factory.CreateInstance()
96 ip = self._compute_client.GetInstanceIP(instance)
97 self.devices.append(
98 avd.AndroidVirtualDevice(ip=ip, instance_name=instance))
99
100 def WaitForBoot(self):
101 """Waits for all devices to boot up.
102
103 Returns:
104 A dictionary that contains all the failures.
105 The key is the name of the instance that fails to boot,
106 and the value is an errors.DeviceBootError object.
107 """
108 failures = {}
109 for device in self._devices:
110 try:
111 self._compute_client.WaitForBoot(device.instance_name)
112 except errors.DeviceBootError as e:
113 failures[device.instance_name] = e
114 return failures
115
116 @property
117 def devices(self):
118 """Returns a list of devices in the pool.
119
120 Returns:
121 A list of devices in the pool.
122 """
123 return self._devices
124
125
126def CreateDevices(command, cfg, device_factory, num):
127 """Create a set of devices using the given factory.
128
129 Args:
130 command: The name of the command, used for reporting.
131 cfg: An AcloudConfig instance.
132 device_factory: A factory capable of producing a single device.
133 num: The number of devices to create.
134
135 Returns:
136 A Report instance.
137 """
138 reporter = report.Report(command=command)
139 try:
140 CreateSshKeyPairIfNecessary(cfg)
141 device_pool = DevicePool(device_factory)
142 device_pool.CreateDevices(num)
143 failures = device_pool.WaitForBoot()
144 # Write result to report.
145 for device in device_pool.devices:
146 device_dict = {"ip": device.ip, "instance_name": device.instance_name}
147 if device.instance_name in failures:
148 reporter.AddData(key="devices_failing_boot", value=device_dict)
149 reporter.AddError(str(failures[device.instance_name]))
150 else:
151 reporter.AddData(key="devices", value=device_dict)
152 if failures:
153 reporter.SetStatus(report.Status.BOOT_FAIL)
154 else:
155 reporter.SetStatus(report.Status.SUCCESS)
156 except errors.DriverError as e:
157 reporter.AddError(str(e))
158 reporter.SetStatus(report.Status.FAIL)
159 return reporter