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 21 22import apiclient 23 24from acloud import errors 25from acloud.internal.lib import base_cloud_client 26 27logger = logging.getLogger(__name__) 28 29 30class AndroidBuildClient(base_cloud_client.BaseCloudApiClient): 31 """Client that manages Android Build.""" 32 33 # API settings, used by BaseCloudApiClient. 34 API_NAME = "androidbuildinternal" 35 API_VERSION = "v2beta1" 36 SCOPE = "https://www.googleapis.com/auth/androidbuild.internal" 37 38 # other variables. 39 DEFAULT_RESOURCE_ID = "0" 40 # TODO(b/27269552): We should use "latest". 41 DEFAULT_ATTEMPT_ID = "0" 42 DEFAULT_CHUNK_SIZE = 20 * 1024 * 1024 43 NO_ACCESS_ERROR_PATTERN = "does not have storage.objects.create access" 44 # LKGB variables. 45 BUILD_STATUS_COMPLETE = "complete" 46 BUILD_TYPE_SUBMITTED = "submitted" 47 ONE_RESULT = 1 48 BUILD_SUCCESSFUL = True 49 50 # Message constant 51 COPY_TO_MSG = ("build artifact (target: %s, build_id: %s, " 52 "artifact: %s, attempt_id: %s) to " 53 "google storage (bucket: %s, path: %s)") 54 # pylint: disable=invalid-name 55 def DownloadArtifact(self, 56 build_target, 57 build_id, 58 resource_id, 59 local_dest, 60 attempt_id=None): 61 """Get Android build attempt information. 62 63 Args: 64 build_target: Target name, e.g. "aosp_cf_x86_phone-userdebug" 65 build_id: Build id, a string, e.g. "2263051", "P2804227" 66 resource_id: Id of the resource, e.g "avd-system.tar.gz". 67 local_dest: A local path where the artifact should be stored. 68 e.g. "/tmp/avd-system.tar.gz" 69 attempt_id: String, attempt id, will default to DEFAULT_ATTEMPT_ID. 70 """ 71 attempt_id = attempt_id or self.DEFAULT_ATTEMPT_ID 72 api = self.service.buildartifact().get_media( 73 buildId=build_id, 74 target=build_target, 75 attemptId=attempt_id, 76 resourceId=resource_id) 77 logger.info("Downloading artifact: target: %s, build_id: %s, " 78 "resource_id: %s, dest: %s", build_target, build_id, 79 resource_id, local_dest) 80 try: 81 with io.FileIO(local_dest, mode="wb") as fh: 82 downloader = apiclient.http.MediaIoBaseDownload( 83 fh, api, chunksize=self.DEFAULT_CHUNK_SIZE) 84 done = False 85 while not done: 86 _, done = downloader.next_chunk() 87 logger.info("Downloaded artifact: %s", local_dest) 88 except (OSError, apiclient.errors.HttpError) as e: 89 logger.error("Downloading artifact failed: %s", str(e)) 90 raise errors.DriverError(str(e)) 91 92 def CopyTo(self, 93 build_target, 94 build_id, 95 artifact_name, 96 destination_bucket, 97 destination_path, 98 attempt_id=None): 99 """Copy an Android Build artifact to a storage bucket. 100 101 Args: 102 build_target: Target name, e.g. "aosp_cf_x86_phone-userdebug" 103 build_id: Build id, a string, e.g. "2263051", "P2804227" 104 artifact_name: Name of the artifact, e.g "avd-system.tar.gz". 105 destination_bucket: String, a google storage bucket name. 106 destination_path: String, "path/inside/bucket" 107 attempt_id: String, attempt id, will default to DEFAULT_ATTEMPT_ID. 108 """ 109 attempt_id = attempt_id or self.DEFAULT_ATTEMPT_ID 110 copy_msg = "Copying %s" % self.COPY_TO_MSG 111 logger.info(copy_msg, build_target, build_id, artifact_name, 112 attempt_id, destination_bucket, destination_path) 113 api = self.service.buildartifact().copyTo( 114 buildId=build_id, 115 target=build_target, 116 attemptId=attempt_id, 117 artifactName=artifact_name, 118 destinationBucket=destination_bucket, 119 destinationPath=destination_path) 120 try: 121 self.Execute(api) 122 finish_msg = "Finished copying %s" % self.COPY_TO_MSG 123 logger.info(finish_msg, build_target, build_id, artifact_name, 124 attempt_id, destination_bucket, destination_path) 125 except errors.HttpError as e: 126 if e.code == 503: 127 if self.NO_ACCESS_ERROR_PATTERN in str(e): 128 error_msg = "Please grant android build team's service account " 129 error_msg += "write access to bucket %s. Original error: %s" 130 error_msg %= (destination_bucket, str(e)) 131 raise errors.HttpError(e.code, message=error_msg) 132 raise 133 134 def GetBranch(self, build_target, build_id): 135 """Derives branch name. 136 137 Args: 138 build_target: Target name, e.g. "aosp_cf_x86_phone-userdebug" 139 build_id: Build ID, a string, e.g. "2263051", "P2804227" 140 141 Returns: 142 A string, the name of the branch 143 """ 144 api = self.service.build().get(buildId=build_id, target=build_target) 145 build = self.Execute(api) 146 return build.get("branch", "") 147 148 def GetLKGB(self, build_target, build_branch): 149 """Get latest successful build id. 150 151 From branch and target, we can use api to query latest successful build id. 152 e.g. {u'nextPageToken':..., u'builds': [{u'completionTimestamp':u'1534157869286', 153 ... u'buildId': u'4949805', u'machineName'...}]} 154 155 Args: 156 build_target: String, target name, e.g. "aosp_cf_x86_phone-userdebug" 157 build_branch: String, git branch name, e.g. "aosp-master" 158 159 Returns: 160 A string, string of build id number. 161 162 Raises: 163 errors.CreateError: Can't get build id. 164 """ 165 api = self.service.build().list( 166 branch=build_branch, 167 target=build_target, 168 buildAttemptStatus=self.BUILD_STATUS_COMPLETE, 169 buildType=self.BUILD_TYPE_SUBMITTED, 170 maxResults=self.ONE_RESULT, 171 successful=self.BUILD_SUCCESSFUL) 172 build = self.Execute(api) 173 logger.info("GetLKGB build API response: %s", build) 174 if build: 175 return str(build.get("builds")[0].get("buildId")) 176 raise errors.GetBuildIDError( 177 "No available good builds for branch: %s target: %s" 178 % (build_branch, build_target) 179 ) 180