• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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