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