blob: b20236cf49cffc4adeabfd005491377411c5e38f [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,
391 autoconnect=False):
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700392 """Creates one or multiple android devices.
393
394 Args:
395 cfg: An AcloudConfig instance.
Kevin Chengb5963882018-05-09 00:06:27 -0700396 build_target: Target name, e.g. "aosp_cf_x86_phone-userdebug"
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700397 build_id: Build id, a string, e.g. "2263051", "P2804227"
398 num: Number of devices to create.
399 gce_image: string, if given, will use this gce image
400 instead of creating a new one.
401 implies cleanup=False.
402 local_disk_image: string, path to a local disk image, e.g.
403 /tmp/avd-system.tar.gz
404 cleanup: boolean, if True clean up compute engine image and
405 disk image in storage after creating the instance.
406 serial_log_file: A path to a file where serial output should
Fang Dengfbef7c92017-02-08 14:09:34 -0800407 be saved to.
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700408 logcat_file: A path to a file where logcat logs should be saved.
Kevin Chengb5963882018-05-09 00:06:27 -0700409 autoconnect: Create ssh tunnel(s) and adb connect after device creation.
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700410
411 Returns:
412 A Report instance.
413 """
414 r = report.Report(command="create")
415 credentials = auth.CreateCredentials(cfg, ALL_SCOPES)
416 compute_client = android_compute_client.AndroidComputeClient(cfg,
417 credentials)
418 try:
Kevin Chengb5963882018-05-09 00:06:27 -0700419 common_operations.CreateSshKeyPairIfNecessary(cfg)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700420 device_pool = AndroidVirtualDevicePool(cfg)
421 device_pool.CreateDevices(
422 num,
423 build_target,
424 build_id,
425 gce_image,
426 local_disk_image,
427 cleanup,
428 extra_data_disk_size_gb=cfg.extra_data_disk_size_gb,
429 precreated_data_image=cfg.precreated_data_image_map.get(
430 cfg.extra_data_disk_size_gb))
431 failures = device_pool.WaitForBoot()
432 # Write result to report.
433 for device in device_pool.devices:
434 device_dict = {"ip": device.ip,
435 "instance_name": device.instance_name}
Kevin Chengb5963882018-05-09 00:06:27 -0700436 if autoconnect:
437 _AutoConnect(device_dict, cfg.ssh_private_key_path)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700438 if device.instance_name in failures:
439 r.AddData(key="devices_failing_boot", value=device_dict)
440 r.AddError(str(failures[device.instance_name]))
441 else:
442 r.AddData(key="devices", value=device_dict)
443 if failures:
444 r.SetStatus(report.Status.BOOT_FAIL)
445 else:
446 r.SetStatus(report.Status.SUCCESS)
447
448 # Dump serial and logcat logs.
449 if serial_log_file:
Fang Dengfbef7c92017-02-08 14:09:34 -0800450 _FetchSerialLogsFromDevices(
451 compute_client,
452 instance_names=[d.instance_name for d in device_pool.devices],
453 port=constants.DEFAULT_SERIAL_PORT,
454 output_file=serial_log_file)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700455 if logcat_file:
Fang Dengfbef7c92017-02-08 14:09:34 -0800456 _FetchSerialLogsFromDevices(
457 compute_client,
458 instance_names=[d.instance_name for d in device_pool.devices],
459 port=constants.LOGCAT_SERIAL_PORT,
460 output_file=logcat_file)
Keun Soo Yimb293fdb2016-09-21 16:03:44 -0700461 except errors.DriverError as e:
462 r.AddError(str(e))
463 r.SetStatus(report.Status.FAIL)
464 return r
465
466
467def DeleteAndroidVirtualDevices(cfg, instance_names):
468 """Deletes android devices.
469
470 Args:
471 cfg: An AcloudConfig instance.
472 instance_names: A list of names of the instances to delete.
473
474 Returns:
475 A Report instance.
476 """
477 r = report.Report(command="delete")
478 credentials = auth.CreateCredentials(cfg, ALL_SCOPES)
479 compute_client = android_compute_client.AndroidComputeClient(cfg,
480 credentials)
481 try:
482 deleted, failed, error_msgs = compute_client.DeleteInstances(
483 instance_names, cfg.zone)
484 _AddDeletionResultToReport(
485 r, deleted,
486 failed, error_msgs,
487 resource_name="instance")
488 if r.status == report.Status.UNKNOWN:
489 r.SetStatus(report.Status.SUCCESS)
490 except errors.DriverError as e:
491 r.AddError(str(e))
492 r.SetStatus(report.Status.FAIL)
493 return r
494
495
496def _FindOldItems(items, cut_time, time_key):
497 """Finds items from |items| whose timestamp is earlier than |cut_time|.
498
499 Args:
500 items: A list of items. Each item is a dictionary represent
501 the properties of the item. It should has a key as noted
502 by time_key.
503 cut_time: A datetime.datatime object.
504 time_key: String, key for the timestamp.
505
506 Returns:
507 A list of those from |items| whose timestamp is earlier than cut_time.
508 """
509 cleanup_list = []
510 for item in items:
511 t = dateutil.parser.parse(item[time_key])
512 if t < cut_time:
513 cleanup_list.append(item)
514 return cleanup_list
515
516
517def Cleanup(cfg, expiration_mins):
518 """Cleans up stale gce images, gce instances, and disk images in storage.
519
520 Args:
521 cfg: An AcloudConfig instance.
522 expiration_mins: Integer, resources older than |expiration_mins| will
523 be cleaned up.
524
525 Returns:
526 A Report instance.
527 """
528 r = report.Report(command="cleanup")
529 try:
530 cut_time = (datetime.datetime.now(dateutil.tz.tzlocal()) -
531 datetime.timedelta(minutes=expiration_mins))
532 logger.info(
533 "Cleaning up any gce images/instances and cached build artifacts."
534 "in google storage that are older than %s", cut_time)
535 credentials = auth.CreateCredentials(cfg, ALL_SCOPES)
536 compute_client = android_compute_client.AndroidComputeClient(
537 cfg, credentials)
538 storage_client = gstorage_client.StorageClient(credentials)
539
540 # Cleanup expired instances
541 items = compute_client.ListInstances(zone=cfg.zone)
542 cleanup_list = [
543 item["name"]
544 for item in _FindOldItems(items, cut_time, "creationTimestamp")
545 ]
546 logger.info("Found expired instances: %s", cleanup_list)
547 for i in range(0, len(cleanup_list), MAX_BATCH_CLEANUP_COUNT):
548 result = compute_client.DeleteInstances(
549 instances=cleanup_list[i:i + MAX_BATCH_CLEANUP_COUNT],
550 zone=cfg.zone)
551 _AddDeletionResultToReport(r, *result, resource_name="instance")
552
553 # Cleanup expired images
554 items = compute_client.ListImages()
555 skip_list = cfg.precreated_data_image_map.viewvalues()
556 cleanup_list = [
557 item["name"]
558 for item in _FindOldItems(items, cut_time, "creationTimestamp")
559 if item["name"] not in skip_list
560 ]
561 logger.info("Found expired images: %s", cleanup_list)
562 for i in range(0, len(cleanup_list), MAX_BATCH_CLEANUP_COUNT):
563 result = compute_client.DeleteImages(
564 image_names=cleanup_list[i:i + MAX_BATCH_CLEANUP_COUNT])
565 _AddDeletionResultToReport(r, *result, resource_name="image")
566
567 # Cleanup expired disks
568 # Disks should have been attached to instances with autoDelete=True.
569 # However, sometimes disks may not be auto deleted successfully.
570 items = compute_client.ListDisks(zone=cfg.zone)
571 cleanup_list = [
572 item["name"]
573 for item in _FindOldItems(items, cut_time, "creationTimestamp")
574 if not item.get("users")
575 ]
576 logger.info("Found expired disks: %s", cleanup_list)
577 for i in range(0, len(cleanup_list), MAX_BATCH_CLEANUP_COUNT):
578 result = compute_client.DeleteDisks(
579 disk_names=cleanup_list[i:i + MAX_BATCH_CLEANUP_COUNT],
580 zone=cfg.zone)
581 _AddDeletionResultToReport(r, *result, resource_name="disk")
582
583 # Cleanup expired google storage
584 items = storage_client.List(bucket_name=cfg.storage_bucket_name)
585 cleanup_list = [
586 item["name"]
587 for item in _FindOldItems(items, cut_time, "timeCreated")
588 ]
589 logger.info("Found expired cached artifacts: %s", cleanup_list)
590 for i in range(0, len(cleanup_list), MAX_BATCH_CLEANUP_COUNT):
591 result = storage_client.DeleteFiles(
592 bucket_name=cfg.storage_bucket_name,
593 object_names=cleanup_list[i:i + MAX_BATCH_CLEANUP_COUNT])
594 _AddDeletionResultToReport(
595 r, *result, resource_name="cached_build_artifact")
596
597 # Everything succeeded, write status to report.
598 if r.status == report.Status.UNKNOWN:
599 r.SetStatus(report.Status.SUCCESS)
600 except errors.DriverError as e:
601 r.AddError(str(e))
602 r.SetStatus(report.Status.FAIL)
603 return r
604
605
606def AddSshRsa(cfg, user, ssh_rsa_path):
607 """Add public ssh rsa key to the project.
608
609 Args:
610 cfg: An AcloudConfig instance.
611 user: the name of the user which the key belongs to.
612 ssh_rsa_path: The absolute path to public rsa key.
613
614 Returns:
615 A Report instance.
616 """
617 r = report.Report(command="sshkey")
618 try:
619 credentials = auth.CreateCredentials(cfg, ALL_SCOPES)
620 compute_client = android_compute_client.AndroidComputeClient(
621 cfg, credentials)
622 compute_client.AddSshRsa(user, ssh_rsa_path)
623 r.SetStatus(report.Status.SUCCESS)
624 except errors.DriverError as e:
625 r.AddError(str(e))
626 r.SetStatus(report.Status.FAIL)
627 return r
Fang Dengcef4b112017-03-02 11:20:17 -0800628
629
630def CheckAccess(cfg):
631 """Check if user has access.
632
633 Args:
634 cfg: An AcloudConfig instance.
635 """
636 credentials = auth.CreateCredentials(cfg, ALL_SCOPES)
637 compute_client = android_compute_client.AndroidComputeClient(
Kevin Cheng5c124ec2018-05-16 13:28:51 -0700638 cfg, credentials)
Fang Dengcef4b112017-03-02 11:20:17 -0800639 logger.info("Checking if user has access to project %s", cfg.project)
640 if not compute_client.CheckAccess():
641 logger.error("User does not have access to project %s", cfg.project)
642 # Print here so that command line user can see it.
Kevin Chengd9d5f0f2018-06-19 14:54:17 -0700643 print("Looks like you do not have access to %s. " % cfg.project)
Fang Dengcef4b112017-03-02 11:20:17 -0800644 if cfg.project in cfg.no_project_access_msg_map:
Kevin Chengd9d5f0f2018-06-19 14:54:17 -0700645 print(cfg.no_project_access_msg_map[cfg.project])