1#
2# Copyright (C) 2018 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the 'License');
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#      http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an 'AS IS' BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16
17import httplib2
18import logging
19import socket
20import threading
21import time
22
23from googleapiclient import errors
24
25from host_controller import common
26from host_controller.command_processor import base_command_processor
27from host_controller.console_argument_parser import ConsoleArgumentError
28from host_controller.tradefed import remote_operation
29
30
31class CommandBuild(base_command_processor.BaseCommandProcessor):
32    """Command processor for build command.
33
34    Attributes:
35        arg_parser: ConsoleArgumentParser object, argument parser.
36        build_thread: dict containing threading.Thread instances(s) that
37                      update build info regularly.
38        console: cmd.Cmd console object.
39        command: string, command name which this processor will handle.
40        command_detail: string, detailed explanation for the command.
41    """
42
43    command = "build"
44    command_detail = "Specifies branches and targets to monitor."
45
46    def UpdateBuild(self, account_id, branch, targets, artifact_type, method,
47                    userinfo_file, noauth_local_webserver, verify_signed):
48        """Updates the build state.
49
50        Args:
51            account_id: string, Partner Android Build account_id to use.
52            branch: string, branch to grab the artifact from.
53            targets: string, a comma-separate list of build target product(s).
54            artifact_type: string, artifact type (`device`, 'gsi' or `test').
55            method: string,  method for getting build information.
56            userinfo_file: string, the path of a file containing email and
57                           password (if method == POST).
58            noauth_local_webserver: boolean, True to not use a local websever.
59            verify_signed: A Boolean indicating whether to verify signed build.
60        """
61        builds = []
62
63        self.console._build_provider["pab"].Authenticate(
64            userinfo_file=userinfo_file,
65            noauth_local_webserver=noauth_local_webserver)
66        for target in targets.split(","):
67            try:
68                listed_builds = self.console._build_provider["pab"].GetBuildList(
69                    account_id=account_id,
70                    branch=branch,
71                    target=target,
72                    page_token="",
73                    max_results=100,
74                    method=method,
75                    verify_signed=verify_signed)
76            except ValueError as e:
77                logging.exception(e)
78                continue
79
80            for listed_build in listed_builds:
81                if method == "GET":
82                    if "successful" in listed_build:
83                        if listed_build["successful"]:
84                            build = {}
85                            build["manifest_branch"] = branch
86                            build["build_id"] = listed_build["build_id"]
87                            if "-" in target:
88                                build["build_target"], build[
89                                    "build_type"] = target.split("-")
90                            else:
91                                build["build_target"] = target
92                                build["build_type"] = ""
93                            build["artifact_type"] = artifact_type
94                            build["artifacts"] = []
95                            if "signed" in listed_build:
96                                build["signed"] = listed_build["signed"]
97                            else:
98                                build["signed"] = False
99                            builds.append(build)
100                    else:
101                        logging.error("Error: listed_build %s", listed_build)
102                else:  # POST
103                    build = {}
104                    build["manifest_branch"] = branch
105                    build["build_id"] = listed_build[u"1"]
106                    if "-" in target:
107                        (build["build_target"],
108                         build["build_type"]) = target.split("-")
109                    else:
110                        build["build_target"] = target
111                        build["build_type"] = ""
112                    build["artifact_type"] = artifact_type
113                    build["artifacts"] = []
114                    build["signed"] = False
115                    builds.append(build)
116        self.console._vti_endpoint_client.UploadBuildInfo(builds)
117
118    def UpdateBuildLoop(self, account_id, branch, target, artifact_type,
119                        method, userinfo_file, noauth_local_webserver,
120                        update_interval, verify_signed):
121        """Regularly updates the build information.
122
123        Args:
124            account_id: string, Partner Android Build account_id to use.
125            branch: string, branch to grab the artifact from.
126            targets: string, a comma-separate list of build target product(s).
127            artifact_type: string, artifcat type (`device`, 'gsi' or `test).
128            method: string,  method for getting build information.
129            userinfo_file: string, the path of a file containing email and
130                           password (if method == POST).
131            noauth_local_webserver: boolean, True to not use a local websever.
132            update_interval: int, number of seconds before repeating
133        """
134        thread = threading.currentThread()
135        while getattr(thread, 'keep_running', True):
136            try:
137                self.UpdateBuild(account_id, branch, target, artifact_type,
138                                 method, userinfo_file, noauth_local_webserver,
139                                 verify_signed)
140            except (socket.error, remote_operation.RemoteOperationException,
141                    httplib2.HttpLib2Error, errors.HttpError) as e:
142                logging.exception(e)
143            time.sleep(update_interval)
144
145    # @Override
146    def SetUp(self):
147        """Initializes the parser for build command."""
148        self.build_thread = {}
149        self.arg_parser.add_argument(
150            "--update",
151            choices=("single", "start", "stop", "list"),
152            default="start",
153            help="Update build info")
154        self.arg_parser.add_argument(
155            "--id",
156            default=None,
157            help="session ID only required for 'stop' update command")
158        self.arg_parser.add_argument(
159            "--interval",
160            type=int,
161            default=30,
162            help="Interval (seconds) to repeat build update.")
163        self.arg_parser.add_argument(
164            "--artifact-type",
165            choices=("device", "gsi", "test"),
166            default="device",
167            help="The type of an artifact to update")
168        self.arg_parser.add_argument(
169            "--branch",
170            required=True,
171            help="Branch to grab the artifact from.")
172        self.arg_parser.add_argument(
173            "--target",
174            required=True,
175            help="a comma-separate list of build target product(s).")
176        self.arg_parser.add_argument(
177            "--account_id",
178            default=common._DEFAULT_ACCOUNT_ID,
179            help="Partner Android Build account_id to use.")
180        self.arg_parser.add_argument(
181            "--method",
182            default="GET",
183            choices=("GET", "POST"),
184            help="Method for getting build information")
185        self.arg_parser.add_argument(
186            "--userinfo-file",
187            help=
188            "Location of file containing email and password, if using POST.")
189        self.arg_parser.add_argument(
190            "--noauth_local_webserver",
191            default=False,
192            type=bool,
193            help="True to not use a local webserver for authentication.")
194        self.arg_parser.add_argument(
195            "--verify-signed-build",
196            default=False,
197            type=bool,
198            help="True to verify whether the build is signed.")
199    # @Override
200    def Run(self, arg_line):
201        """Updates build info."""
202        args = self.arg_parser.ParseLine(arg_line)
203        if args.update == "single":
204            self.UpdateBuild(args.account_id, args.branch, args.target,
205                             args.artifact_type, args.method,
206                             args.userinfo_file, args.noauth_local_webserver,
207                             args.verify_signed_build)
208        elif args.update == "list":
209            logging.info("Running build update sessions:")
210            for id in self.build_thread:
211                logging.info("  ID %d", id)
212        elif args.update == "start":
213            if args.interval <= 0:
214                raise ConsoleArgumentError("update interval must be positive")
215            # do not allow user to create new
216            # thread if one is currently running
217            if args.id is None:
218                if not self.build_thread:
219                    args.id = 1
220                else:
221                    args.id = max(self.build_thread) + 1
222            else:
223                args.id = int(args.id)
224            if args.id in self.build_thread and not hasattr(
225                    self.build_thread[args.id], 'keep_running'):
226                logging.warning(
227                    'build update (session ID: %s) already running. '
228                    'run build --update stop first.', args.id)
229                return
230            self.build_thread[args.id] = threading.Thread(
231                target=self.UpdateBuildLoop,
232                args=(
233                    args.account_id,
234                    args.branch,
235                    args.target,
236                    args.artifact_type,
237                    args.method,
238                    args.userinfo_file,
239                    args.noauth_local_webserver,
240                    args.interval,
241                    args.verify_signed_build,
242                ))
243            self.build_thread[args.id].daemon = True
244            self.build_thread[args.id].start()
245        elif args.update == "stop":
246            if args.id is None:
247                logging.error("--id must be set for stop")
248            else:
249                self.build_thread[int(args.id)].keep_running = False
250