blob: d83b151db9fa2f597bb9f7c380a4347d4f7dc27b [file] [log] [blame]
Keun Soo Yimb293fdb2016-09-21 16:03:44 -07001#!/usr/bin/env python
2#
3# Copyright 2016 - 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"""Public Device Driver APIs.
18
19This module provides public device driver APIs that can be called
20as a Python library.
21
Kevin Chengb5963882018-05-09 00:06:27 -070022TODO: The following APIs have not been implemented
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070023 - RebootAVD(ip):
24 - RegisterSshPubKey(username, key):
25 - UnregisterSshPubKey(username, key):
26 - CleanupStaleImages():
27 - CleanupStaleDevices():
28"""
29
Kevin Chengd9d5f0f2018-06-19 14:54:17 -070030from __future__ import print_function
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070031import datetime
32import logging
33import os
Kevin Chengb5963882018-05-09 00:06:27 -070034import socket
35import subprocess
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070036
Kevin Chengd9d5f0f2018-06-19 14:54:17 -070037# pylint: disable=import-error
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070038import dateutil.parser
39import dateutil.tz
40
41from acloud.public import avd
42from acloud.public import errors
43from acloud.public import report
Kevin Chengb5963882018-05-09 00:06:27 -070044from acloud.public.actions import common_operations
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070045from acloud.internal import constants
46from acloud.internal.lib import auth
47from acloud.internal.lib import android_build_client
48from acloud.internal.lib import android_compute_client
49from acloud.internal.lib import gstorage_client
50from acloud.internal.lib import utils
51
52logger = logging.getLogger(__name__)
53
54ALL_SCOPES = " ".join([android_build_client.AndroidBuildClient.SCOPE,
55 gstorage_client.StorageClient.SCOPE,
56 android_compute_client.AndroidComputeClient.SCOPE])
57
58MAX_BATCH_CLEANUP_COUNT = 100
59
Kevin Chengb5963882018-05-09 00:06:27 -070060SSH_TUNNEL_CMD = ("/usr/bin/ssh -i %(rsa_key_file)s -o "
61 "UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -L "
62 "%(vnc_port)d:127.0.0.1:6444 -L %(adb_port)d:127.0.0.1:5555 "
63 "-N -f -l root %(ip_addr)s")
64ADB_CONNECT_CMD = "adb connect 127.0.0.1:%(adb_port)d"
65
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070066
Kevin Cheng5c124ec2018-05-16 13:28:51 -070067# pylint: disable=invalid-name
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070068class AndroidVirtualDevicePool(object):
69 """A class that manages a pool of devices."""
70
71 def __init__(self, cfg, devices=None):
72 self._devices = devices or []
73 self._cfg = cfg
74 credentials = auth.CreateCredentials(cfg, ALL_SCOPES)
75 self._build_client = android_build_client.AndroidBuildClient(
76 credentials)
77 self._storage_client = gstorage_client.StorageClient(credentials)
78 self._compute_client = android_compute_client.AndroidComputeClient(
79 cfg, credentials)
80
81 def _CreateGceImageWithBuildInfo(self, build_target, build_id):
82 """Creates a Gce image using build from Launch Control.
83
84 Clone avd-system.tar.gz of a build to a cache storage bucket
85 using launch control api. And then create a Gce image.
86
87 Args:
Kevin Chengb5963882018-05-09 00:06:27 -070088 build_target: Target name, e.g. "aosp_cf_x86_phone-userdebug"
Keun Soo Yimb293fdb2016-09-21 16:03:44 -070089 build_id: Build id, a string, e.g. "2263051", "P2804227"
90
91 Returns:
92 String, name of the Gce image that has been created.
93 """
94 logger.info("Creating a new gce image using build: build_id %s, "
95 "build_target %s", build_id, build_target)
96 disk_image_id = utils.GenerateUniqueName(
97 suffix=self._cfg.disk_image_name)
98 self._build_client.CopyTo(
99 build_target,
100 build_id,
101 artifact_name=self._cfg.disk_image_name,
102 destination_bucket=self._cfg.storage_bucket_name,
103 destination_path=disk_image_id)
104 disk_image_url = self._storage_client.GetUrl(
105 self._cfg.storage_bucket_name, disk_image_id)
106 try:
107 image_name = self._compute_client.GenerateImageName(build_target,
108 build_id)
109 self._compute_client.CreateImage(image_name=image_name,
110 source_uri=disk_image_url)
111 finally:
112 self._storage_client.Delete(self._cfg.storage_bucket_name,
113 disk_image_id)
114 return image_name
115
116 def _CreateGceImageWithLocalFile(self, local_disk_image):
117 """Create a Gce image with a local image file.
118
119 The local disk image can be either a tar.gz file or a
120 raw vmlinux image.
121 e.g. /tmp/avd-system.tar.gz or /tmp/android_system_disk_syslinux.img
122 If a raw vmlinux image is provided, it will be archived into a tar.gz file.
123
124 The final tar.gz file will be uploaded to a cache bucket in storage.
125
126 Args:
127 local_disk_image: string, path to a local disk image,
128
129 Returns:
130 String, name of the Gce image that has been created.
131
132 Raises:
133 DriverError: if a file with an unexpected extension is given.
134 """
135 logger.info("Creating a new gce image from a local file %s",
136 local_disk_image)
137 with utils.TempDir() as tempdir:
138 if local_disk_image.endswith(self._cfg.disk_raw_image_extension):
139 dest_tar_file = os.path.join(tempdir,
140 self._cfg.disk_image_name)
141 utils.MakeTarFile(
142 src_dict={local_disk_image: self._cfg.disk_raw_image_name},
143 dest=dest_tar_file)
144 local_disk_image = dest_tar_file
145 elif not local_disk_image.endswith(self._cfg.disk_image_extension):
146 raise errors.DriverError(
147 "Wrong local_disk_image type, must be a *%s file or *%s file"
148 % (self._cfg.disk_raw_image_extension,
149 self._cfg.disk_image_extension))
150
151 disk_image_id = utils.GenerateUniqueName(
152 suffix=self._cfg.disk_image_name)
153 self._storage_client.Upload(
154 local_src=local_disk_image,
155 bucket_name=self._cfg.storage_bucket_name,
156 object_name=disk_image_id,
157 mime_type=self._cfg.disk_image_mime_type)
158 disk_image_url = self._storage_client.GetUrl(
159 self._cfg.storage_bucket_name, disk_image_id)
160 try:
161 image_name = self._compute_client.GenerateImageName()
162 self._compute_client.CreateImage(image_name=image_name,
163 source_uri=disk_image_url)
164 finally:
165 self._storage_client.Delete(self._cfg.storage_bucket_name,
166 disk_image_id)
167 return image_name
168
169 def CreateDevices(self,
170 num,
171 build_target=None,
172 build_id=None,
173 gce_image=None,
174 local_disk_image=None,
175 cleanup=True,
176 extra_data_disk_size_gb=None,
177 precreated_data_image=None):
178 """Creates |num| devices for given build_target and build_id.
179
180 - If gce_image is provided, will use it to create an instance.
181 - If local_disk_image is provided, will upload it to a temporary
182 caching storage bucket which is defined by user as |storage_bucket_name|
183 And then create an gce image with it; and then create an instance.
184 - If build_target and build_id are provided, will clone the disk image
185 via launch control to the temporary caching storage bucket.
186 And then create an gce image with it; and then create an instance.
187
188 Args:
189 num: Number of devices to create.
Kevin Chengb5963882018-05-09 00:06:27 -0700190 build_target: Target name, e.g. "aosp_cf_x86_phone-userdebug"
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700191 build_id: Build id, a string, e.g. "2263051", "P2804227"
192 gce_image: string, if given, will use this image
193 instead of creating a new one.
194 implies cleanup=False.
195 local_disk_image: string, path to a local disk image, e.g.
196 /tmp/avd-system.tar.gz
197 cleanup: boolean, if True clean up compute engine image after creating
198 the instance.
199 extra_data_disk_size_gb: Integer, size of extra disk, or None.
200 precreated_data_image: A string, the image to use for the extra disk.
201
202 Raises:
203 errors.DriverError: If no source is specified for image creation.
204 """
205 if gce_image:
206 # GCE image is provided, we can directly move to instance creation.
207 logger.info("Using existing gce image %s", gce_image)
208 image_name = gce_image
209 cleanup = False
210 elif local_disk_image:
211 image_name = self._CreateGceImageWithLocalFile(local_disk_image)
212 elif build_target and build_id:
213 image_name = self._CreateGceImageWithBuildInfo(build_target,
214 build_id)
215 else:
216 raise errors.DriverError(
217 "Invalid image source, must specify one of the following: gce_image, "
218 "local_disk_image, or build_target and build id.")
219
220 # Create GCE instances.
221 try:
222 for _ in range(num):
223 instance = self._compute_client.GenerateInstanceName(
224 build_target, build_id)
225 extra_disk_name = None
226 if extra_data_disk_size_gb > 0:
227 extra_disk_name = self._compute_client.GetDataDiskName(
228 instance)
229 self._compute_client.CreateDisk(extra_disk_name,
230 precreated_data_image,
231 extra_data_disk_size_gb)
Kevin Chengb5963882018-05-09 00:06:27 -0700232 self._compute_client.CreateInstance(
233 instance=instance,
234 image_name=image_name,
235 extra_disk_name=extra_disk_name)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700236 ip = self._compute_client.GetInstanceIP(instance)
237 self.devices.append(avd.AndroidVirtualDevice(
238 ip=ip, instance_name=instance))
239 finally:
240 if cleanup:
241 self._compute_client.DeleteImage(image_name)
242
243 def DeleteDevices(self):
244 """Deletes devices.
245
246 Returns:
247 A tuple, (deleted, failed, error_msgs)
248 deleted: A list of names of instances that have been deleted.
249 faild: A list of names of instances that we fail to delete.
250 error_msgs: A list of failure messages.
251 """
252 instance_names = [device.instance_name for device in self._devices]
253 return self._compute_client.DeleteInstances(instance_names,
254 self._cfg.zone)
255
256 def WaitForBoot(self):
257 """Waits for all devices to boot up.
258
259 Returns:
260 A dictionary that contains all the failures.
261 The key is the name of the instance that fails to boot,
Kevin Chengb5963882018-05-09 00:06:27 -0700262 the value is an errors.DeviceBoottError object.
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700263 """
264 failures = {}
265 for device in self._devices:
266 try:
267 self._compute_client.WaitForBoot(device.instance_name)
Kevin Chengb5963882018-05-09 00:06:27 -0700268 except errors.DeviceBootError as e:
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700269 failures[device.instance_name] = e
270 return failures
271
272 @property
273 def devices(self):
274 """Returns a list of devices in the pool.
275
276 Returns:
277 A list of devices in the pool.
278 """
279 return self._devices
280
281
282def _AddDeletionResultToReport(report_obj, deleted, failed, error_msgs,
283 resource_name):
284 """Adds deletion result to a Report object.
285
286 This function will add the following to report.data.
287 "deleted": [
288 {"name": "resource_name", "type": "resource_name"},
289 ],
290 "failed": [
291 {"name": "resource_name", "type": "resource_name"},
292 ],
293 This function will append error_msgs to report.errors.
294
295 Args:
296 report_obj: A Report object.
297 deleted: A list of names of the resources that have been deleted.
298 failed: A list of names of the resources that we fail to delete.
299 error_msgs: A list of error message strings to be added to the report.
300 resource_name: A string, representing the name of the resource.
301 """
302 for name in deleted:
303 report_obj.AddData(key="deleted",
304 value={"name": name,
305 "type": resource_name})
306 for name in failed:
307 report_obj.AddData(key="failed",
308 value={"name": name,
309 "type": resource_name})
310 report_obj.AddErrors(error_msgs)
311 if failed or error_msgs:
312 report_obj.SetStatus(report.Status.FAIL)
313
314
315def _FetchSerialLogsFromDevices(compute_client, instance_names, output_file,
316 port):
317 """Fetch serial logs from a port for a list of devices to a local file.
318
319 Args:
320 compute_client: An object of android_compute_client.AndroidComputeClient
321 instance_names: A list of instance names.
322 output_file: A path to a file ending with "tar.gz"
323 port: The number of serial port to read from, 0 for serial output, 1 for
324 logcat.
325 """
326 with utils.TempDir() as tempdir:
327 src_dict = {}
328 for instance_name in instance_names:
329 serial_log = compute_client.GetSerialPortOutput(
330 instance=instance_name, port=port)
331 file_name = "%s.log" % instance_name
332 file_path = os.path.join(tempdir, file_name)
333 src_dict[file_path] = file_name
334 with open(file_path, "w") as f:
335 f.write(serial_log.encode("utf-8"))
336 utils.MakeTarFile(src_dict, output_file)
337
338
Kevin Chengb5963882018-05-09 00:06:27 -0700339def _PickFreePort():
340 """Helper to pick a free port.
341
342 Returns:
343 A free port number.
344 """
345 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
346 s.bind(("", 0))
347 port = s.getsockname()[1]
348 s.close()
349 return port
350
351
352def _AutoConnect(device_dict, rsa_key_file):
353 """Autoconnect to an AVD instance.
Fang Deng69498c32017-03-02 14:29:30 -0800354
355 Args:
Kevin Chengb5963882018-05-09 00:06:27 -0700356 device_dict: device_dict representing the device we are autoconnecting
357 to. This dict will be updated with the adb & vnc tunnel
358 ports.
359 rsa_key_file: Private key file to use when creating the ssh tunnels.
Fang Deng69498c32017-03-02 14:29:30 -0800360 """
Kevin Chengb5963882018-05-09 00:06:27 -0700361 try:
362 adb_port = _PickFreePort()
363 vnc_port = _PickFreePort()
364 tunnel_cmd = SSH_TUNNEL_CMD % {"rsa_key_file": rsa_key_file,
365 "vnc_port": vnc_port,
366 "adb_port": adb_port,
367 "ip_addr": device_dict["ip"]}
368 logging.debug("Running '%s'", tunnel_cmd)
369 subprocess.check_call([tunnel_cmd], shell=True)
370 adb_connect_cmd = ADB_CONNECT_CMD % {"adb_port": adb_port}
371 logging.debug("Running '%s'", adb_connect_cmd)
372 device_dict["adb_tunnel_port"] = adb_port
373 device_dict["vnc_tunnel_port"] = vnc_port
374 subprocess.check_call([adb_connect_cmd], shell=True)
375 except subprocess.CalledProcessError:
376 logging.error("Failed to autoconnect %s through local adb tunnel port"
377 " %d and vnc tunnel port %d", device_dict["ip"], adb_port,
378 vnc_port)
Fang Deng69498c32017-03-02 14:29:30 -0800379
380
Kevin Cheng5c124ec2018-05-16 13:28:51 -0700381# pylint: disable=too-many-locals
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700382def CreateAndroidVirtualDevices(cfg,
383 build_target=None,
384 build_id=None,
385 num=1,
386 gce_image=None,
387 local_disk_image=None,
388 cleanup=True,
389 serial_log_file=None,
Kevin Chengb5963882018-05-09 00:06:27 -0700390 logcat_file=None,
Kevin Cheng86d43c72018-08-30 10:59:14 -0700391 autoconnect=False,
392 report_internal_ip=False):
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700393 """Creates one or multiple android devices.
394
395 Args:
396 cfg: An AcloudConfig instance.
Kevin Chengb5963882018-05-09 00:06:27 -0700397 build_target: Target name, e.g. "aosp_cf_x86_phone-userdebug"
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700398 build_id: Build id, a string, e.g. "2263051", "P2804227"
399 num: Number of devices to create.
400 gce_image: string, if given, will use this gce image
401 instead of creating a new one.
402 implies cleanup=False.
403 local_disk_image: string, path to a local disk image, e.g.
404 /tmp/avd-system.tar.gz
405 cleanup: boolean, if True clean up compute engine image and
406 disk image in storage after creating the instance.
407 serial_log_file: A path to a file where serial output should
Fang Dengfbef7c92017-02-08 14:09:34 -0800408 be saved to.
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700409 logcat_file: A path to a file where logcat logs should be saved.
Kevin Chengb5963882018-05-09 00:06:27 -0700410 autoconnect: Create ssh tunnel(s) and adb connect after device creation.
Kevin Cheng86d43c72018-08-30 10:59:14 -0700411 report_internal_ip: Boolean to report the internal ip instead of
412 external ip.
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700413
414 Returns:
415 A Report instance.
416 """
417 r = report.Report(command="create")
418 credentials = auth.CreateCredentials(cfg, ALL_SCOPES)
419 compute_client = android_compute_client.AndroidComputeClient(cfg,
420 credentials)
421 try:
Kevin Chengb5963882018-05-09 00:06:27 -0700422 common_operations.CreateSshKeyPairIfNecessary(cfg)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700423 device_pool = AndroidVirtualDevicePool(cfg)
424 device_pool.CreateDevices(
425 num,
426 build_target,
427 build_id,
428 gce_image,
429 local_disk_image,
430 cleanup,
431 extra_data_disk_size_gb=cfg.extra_data_disk_size_gb,
432 precreated_data_image=cfg.precreated_data_image_map.get(
433 cfg.extra_data_disk_size_gb))
434 failures = device_pool.WaitForBoot()
435 # Write result to report.
436 for device in device_pool.devices:
Kevin Cheng86d43c72018-08-30 10:59:14 -0700437 device_dict = {"ip": (device.ip.internal if report_internal_ip
438 else device.ip.external),
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700439 "instance_name": device.instance_name}
Kevin Chengb5963882018-05-09 00:06:27 -0700440 if autoconnect:
441 _AutoConnect(device_dict, cfg.ssh_private_key_path)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700442 if device.instance_name in failures:
443 r.AddData(key="devices_failing_boot", value=device_dict)
444 r.AddError(str(failures[device.instance_name]))
445 else:
446 r.AddData(key="devices", value=device_dict)
447 if failures:
448 r.SetStatus(report.Status.BOOT_FAIL)
449 else:
450 r.SetStatus(report.Status.SUCCESS)
451
452 # Dump serial and logcat logs.
453 if serial_log_file:
Fang Dengfbef7c92017-02-08 14:09:34 -0800454 _FetchSerialLogsFromDevices(
455 compute_client,
456 instance_names=[d.instance_name for d in device_pool.devices],
457 port=constants.DEFAULT_SERIAL_PORT,
458 output_file=serial_log_file)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700459 if logcat_file:
Fang Dengfbef7c92017-02-08 14:09:34 -0800460 _FetchSerialLogsFromDevices(
461 compute_client,
462 instance_names=[d.instance_name for d in device_pool.devices],
463 port=constants.LOGCAT_SERIAL_PORT,
464 output_file=logcat_file)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700465 except errors.DriverError as e:
466 r.AddError(str(e))
467 r.SetStatus(report.Status.FAIL)
468 return r
469
470
471def DeleteAndroidVirtualDevices(cfg, instance_names):
472 """Deletes android devices.
473
474 Args:
475 cfg: An AcloudConfig instance.
476 instance_names: A list of names of the instances to delete.
477
478 Returns:
479 A Report instance.
480 """
481 r = report.Report(command="delete")
482 credentials = auth.CreateCredentials(cfg, ALL_SCOPES)
483 compute_client = android_compute_client.AndroidComputeClient(cfg,
484 credentials)
485 try:
486 deleted, failed, error_msgs = compute_client.DeleteInstances(
487 instance_names, cfg.zone)
488 _AddDeletionResultToReport(
489 r, deleted,
490 failed, error_msgs,
491 resource_name="instance")
492 if r.status == report.Status.UNKNOWN:
493 r.SetStatus(report.Status.SUCCESS)
494 except errors.DriverError as e:
495 r.AddError(str(e))
496 r.SetStatus(report.Status.FAIL)
497 return r
498
499
500def _FindOldItems(items, cut_time, time_key):
501 """Finds items from |items| whose timestamp is earlier than |cut_time|.
502
503 Args:
504 items: A list of items. Each item is a dictionary represent
505 the properties of the item. It should has a key as noted
506 by time_key.
507 cut_time: A datetime.datatime object.
508 time_key: String, key for the timestamp.
509
510 Returns:
511 A list of those from |items| whose timestamp is earlier than cut_time.
512 """
513 cleanup_list = []
514 for item in items:
515 t = dateutil.parser.parse(item[time_key])
516 if t < cut_time:
517 cleanup_list.append(item)
518 return cleanup_list
519
520
521def Cleanup(cfg, expiration_mins):
522 """Cleans up stale gce images, gce instances, and disk images in storage.
523
524 Args:
525 cfg: An AcloudConfig instance.
526 expiration_mins: Integer, resources older than |expiration_mins| will
527 be cleaned up.
528
529 Returns:
530 A Report instance.
531 """
532 r = report.Report(command="cleanup")
533 try:
534 cut_time = (datetime.datetime.now(dateutil.tz.tzlocal()) -
535 datetime.timedelta(minutes=expiration_mins))
536 logger.info(
537 "Cleaning up any gce images/instances and cached build artifacts."
538 "in google storage that are older than %s", cut_time)
539 credentials = auth.CreateCredentials(cfg, ALL_SCOPES)
540 compute_client = android_compute_client.AndroidComputeClient(
541 cfg, credentials)
542 storage_client = gstorage_client.StorageClient(credentials)
543
544 # Cleanup expired instances
545 items = compute_client.ListInstances(zone=cfg.zone)
546 cleanup_list = [
547 item["name"]
548 for item in _FindOldItems(items, cut_time, "creationTimestamp")
549 ]
550 logger.info("Found expired instances: %s", cleanup_list)
551 for i in range(0, len(cleanup_list), MAX_BATCH_CLEANUP_COUNT):
552 result = compute_client.DeleteInstances(
553 instances=cleanup_list[i:i + MAX_BATCH_CLEANUP_COUNT],
554 zone=cfg.zone)
555 _AddDeletionResultToReport(r, *result, resource_name="instance")
556
557 # Cleanup expired images
558 items = compute_client.ListImages()
559 skip_list = cfg.precreated_data_image_map.viewvalues()
560 cleanup_list = [
561 item["name"]
562 for item in _FindOldItems(items, cut_time, "creationTimestamp")
563 if item["name"] not in skip_list
564 ]
565 logger.info("Found expired images: %s", cleanup_list)
566 for i in range(0, len(cleanup_list), MAX_BATCH_CLEANUP_COUNT):
567 result = compute_client.DeleteImages(
568 image_names=cleanup_list[i:i + MAX_BATCH_CLEANUP_COUNT])
569 _AddDeletionResultToReport(r, *result, resource_name="image")
570
571 # Cleanup expired disks
572 # Disks should have been attached to instances with autoDelete=True.
573 # However, sometimes disks may not be auto deleted successfully.
574 items = compute_client.ListDisks(zone=cfg.zone)
575 cleanup_list = [
576 item["name"]
577 for item in _FindOldItems(items, cut_time, "creationTimestamp")
578 if not item.get("users")
579 ]
580 logger.info("Found expired disks: %s", cleanup_list)
581 for i in range(0, len(cleanup_list), MAX_BATCH_CLEANUP_COUNT):
582 result = compute_client.DeleteDisks(
583 disk_names=cleanup_list[i:i + MAX_BATCH_CLEANUP_COUNT],
584 zone=cfg.zone)
585 _AddDeletionResultToReport(r, *result, resource_name="disk")
586
587 # Cleanup expired google storage
588 items = storage_client.List(bucket_name=cfg.storage_bucket_name)
589 cleanup_list = [
590 item["name"]
591 for item in _FindOldItems(items, cut_time, "timeCreated")
592 ]
593 logger.info("Found expired cached artifacts: %s", cleanup_list)
594 for i in range(0, len(cleanup_list), MAX_BATCH_CLEANUP_COUNT):
595 result = storage_client.DeleteFiles(
596 bucket_name=cfg.storage_bucket_name,
597 object_names=cleanup_list[i:i + MAX_BATCH_CLEANUP_COUNT])
598 _AddDeletionResultToReport(
599 r, *result, resource_name="cached_build_artifact")
600
601 # Everything succeeded, write status to report.
602 if r.status == report.Status.UNKNOWN:
603 r.SetStatus(report.Status.SUCCESS)
604 except errors.DriverError as e:
605 r.AddError(str(e))
606 r.SetStatus(report.Status.FAIL)
607 return r
608
609
610def AddSshRsa(cfg, user, ssh_rsa_path):
611 """Add public ssh rsa key to the project.
612
613 Args:
614 cfg: An AcloudConfig instance.
615 user: the name of the user which the key belongs to.
616 ssh_rsa_path: The absolute path to public rsa key.
617
618 Returns:
619 A Report instance.
620 """
621 r = report.Report(command="sshkey")
622 try:
623 credentials = auth.CreateCredentials(cfg, ALL_SCOPES)
624 compute_client = android_compute_client.AndroidComputeClient(
625 cfg, credentials)
626 compute_client.AddSshRsa(user, ssh_rsa_path)
627 r.SetStatus(report.Status.SUCCESS)
628 except errors.DriverError as e:
629 r.AddError(str(e))
630 r.SetStatus(report.Status.FAIL)
631 return r
Fang Dengcef4b112017-03-02 11:20:17 -0800632
633
634def CheckAccess(cfg):
635 """Check if user has access.
636
637 Args:
638 cfg: An AcloudConfig instance.
639 """
640 credentials = auth.CreateCredentials(cfg, ALL_SCOPES)
641 compute_client = android_compute_client.AndroidComputeClient(
Kevin Cheng5c124ec2018-05-16 13:28:51 -0700642 cfg, credentials)
Fang Dengcef4b112017-03-02 11:20:17 -0800643 logger.info("Checking if user has access to project %s", cfg.project)
644 if not compute_client.CheckAccess():
645 logger.error("User does not have access to project %s", cfg.project)
646 # Print here so that command line user can see it.
Kevin Chengd9d5f0f2018-06-19 14:54:17 -0700647 print("Looks like you do not have access to %s. " % cfg.project)
Fang Dengcef4b112017-03-02 11:20:17 -0800648 if cfg.project in cfg.no_project_access_msg_map:
Kevin Chengd9d5f0f2018-06-19 14:54:17 -0700649 print(cfg.no_project_access_msg_map[cfg.project])