Tri Vo | e6638de | 2016-10-03 19:39:03 -0700 | [diff] [blame^] | 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 | |
| 19 | import io |
| 20 | import logging |
| 21 | import os |
| 22 | |
| 23 | import apiclient |
| 24 | |
| 25 | from acloud.internal.lib import base_cloud_client |
| 26 | from acloud.public import errors |
| 27 | |
| 28 | logger = logging.getLogger(__name__) |
| 29 | |
| 30 | |
| 31 | class 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 |