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