1#!/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"""A client that talks to Android Build APIs.""" 18 19import io 20import logging 21import os 22 23import apiclient 24 25from acloud.internal.lib import base_cloud_client 26from acloud.public import errors 27 28logger = logging.getLogger(__name__) 29 30 31class AndroidBuildClient(base_cloud_client.BaseCloudApiClient): 32 """Client that manages Android Build.""" 33 34 # API settings, used by BaseCloudApiClient. 35 API_NAME = "androidbuildinternal" 36 API_VERSION = "v2beta1" 37 SCOPE = "https://www.googleapis.com/auth/androidbuild.internal" 38 39 # other variables. 40 DEFAULT_RESOURCE_ID = "0" 41 # TODO(fdeng): We should use "latest". 42 DEFAULT_ATTEMPT_ID = "0" 43 DEFAULT_CHUNK_SIZE = 20 * 1024 * 1024 44 NO_ACCESS_ERROR_PATTERN = "does not have storage.objects.create access" 45 46 # Message constant 47 COPY_TO_MSG = ("build artifact (target: %s, build_id: %s, " 48 "artifact: %s, attempt_id: %s) to " 49 "google storage (bucket: %s, path: %s)") 50 51 def DownloadArtifact(self, 52 build_target, 53 build_id, 54 resource_id, 55 local_dest, 56 attempt_id=None): 57 """Get Android build attempt information. 58 59 Args: 60 build_target: Target name, e.g. "gce_x86-userdebug" 61 build_id: Build id, a string, e.g. "2263051", "P2804227" 62 resource_id: Id of the resource, e.g "avd-system.tar.gz". 63 local_dest: A local path where the artifact should be stored. 64 e.g. "/tmp/avd-system.tar.gz" 65 attempt_id: String, attempt id, will default to DEFAULT_ATTEMPT_ID. 66 """ 67 attempt_id = attempt_id or self.DEFAULT_ATTEMPT_ID 68 api = self.service.buildartifact().get_media( 69 buildId=build_id, 70 target=build_target, 71 attemptId=attempt_id, 72 resourceId=resource_id) 73 logger.info("Downloading artifact: target: %s, build_id: %s, " 74 "resource_id: %s, dest: %s", build_target, build_id, 75 resource_id, local_dest) 76 try: 77 with io.FileIO(local_dest, mode="wb") as fh: 78 downloader = apiclient.http.MediaIoBaseDownload( 79 fh, api, chunksize=self.DEFAULT_CHUNK_SIZE) 80 done = False 81 while not done: 82 _, done = downloader.next_chunk() 83 logger.info("Downloaded artifact: %s", local_dest) 84 except OSError as e: 85 logger.error("Downloading artifact failed: %s", str(e)) 86 raise errors.DriverError(str(e)) 87 88 def CopyTo(self, 89 build_target, 90 build_id, 91 artifact_name, 92 destination_bucket, 93 destination_path, 94 attempt_id=None): 95 """Copy an Android Build artifact to a storage bucket. 96 97 Args: 98 build_target: Target name, e.g. "gce_x86-userdebug" 99 build_id: Build id, a string, e.g. "2263051", "P2804227" 100 artifact_name: Name of the artifact, e.g "avd-system.tar.gz". 101 destination_bucket: String, a google storage bucket name. 102 destination_path: String, "path/inside/bucket" 103 attempt_id: String, attempt id, will default to DEFAULT_ATTEMPT_ID. 104 """ 105 attempt_id = attempt_id or self.DEFAULT_ATTEMPT_ID 106 copy_msg = "Copying %s" % self.COPY_TO_MSG 107 logger.info(copy_msg, build_target, build_id, artifact_name, 108 attempt_id, destination_bucket, destination_path) 109 api = self.service.buildartifact().copyTo( 110 buildId=build_id, 111 target=build_target, 112 attemptId=attempt_id, 113 artifactName=artifact_name, 114 destinationBucket=destination_bucket, 115 destinationPath=destination_path) 116 try: 117 self.Execute(api) 118 finish_msg = "Finished copying %s" % self.COPY_TO_MSG 119 logger.info(finish_msg, build_target, build_id, artifact_name, 120 attempt_id, destination_bucket, destination_path) 121 except errors.HttpError as e: 122 if e.code == 503: 123 if self.NO_ACCESS_ERROR_PATTERN in str(e): 124 error_msg = "Please grant android build team's service account " 125 error_msg += "write access to bucket %s. Original error: %s" 126 error_msg %= (destination_bucket, str(e)) 127 raise errors.HttpError(e.code, message=error_msg) 128 raise 129