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