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