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 17 import httplib2 18 import logging 19 import socket 20 import threading 21 import time 22 23 from googleapiclient import errors 24 25 from host_controller import common 26 from host_controller.command_processor import base_command_processor 27 from host_controller.console_argument_parser import ConsoleArgumentError 28 from host_controller.tradefed import remote_operation 29 30 31 class 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): 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 """ 60 builds = [] 61 62 self.console._build_provider["pab"].Authenticate( 63 userinfo_file=userinfo_file, 64 noauth_local_webserver=noauth_local_webserver) 65 for target in targets.split(","): 66 listed_builds = self.console._build_provider["pab"].GetBuildList( 67 account_id=account_id, 68 branch=branch, 69 target=target, 70 page_token="", 71 max_results=100, 72 method=method) 73 74 for listed_build in listed_builds: 75 if method == "GET": 76 if "successful" in listed_build: 77 if listed_build["successful"]: 78 build = {} 79 build["manifest_branch"] = branch 80 build["build_id"] = listed_build["build_id"] 81 if "-" in target: 82 build["build_target"], build[ 83 "build_type"] = target.split("-") 84 else: 85 build["build_target"] = target 86 build["build_type"] = "" 87 build["artifact_type"] = artifact_type 88 build["artifacts"] = [] 89 builds.append(build) 90 else: 91 print("Error: listed_build %s" % listed_build) 92 else: # POST 93 build = {} 94 build["manifest_branch"] = branch 95 build["build_id"] = listed_build[u"1"] 96 if "-" in target: 97 (build["build_target"], 98 build["build_type"]) = target.split("-") 99 else: 100 build["build_target"] = target 101 build["build_type"] = "" 102 build["artifact_type"] = artifact_type 103 build["artifacts"] = [] 104 builds.append(build) 105 self.console._vti_endpoint_client.UploadBuildInfo(builds) 106 107 def UpdateBuildLoop(self, account_id, branch, target, artifact_type, 108 method, userinfo_file, noauth_local_webserver, 109 update_interval): 110 """Regularly updates the build information. 111 112 Args: 113 account_id: string, Partner Android Build account_id to use. 114 branch: string, branch to grab the artifact from. 115 targets: string, a comma-separate list of build target product(s). 116 artifact_type: string, artifcat type (`device`, 'gsi' or `test). 117 method: string, method for getting build information. 118 userinfo_file: string, the path of a file containing email and 119 password (if method == POST). 120 noauth_local_webserver: boolean, True to not use a local websever. 121 update_interval: int, number of seconds before repeating 122 """ 123 thread = threading.currentThread() 124 while getattr(thread, 'keep_running', True): 125 try: 126 self.UpdateBuild(account_id, branch, target, artifact_type, 127 method, userinfo_file, noauth_local_webserver) 128 except (socket.error, remote_operation.RemoteOperationException, 129 httplib2.HttpLib2Error, errors.HttpError) as e: 130 logging.exception(e) 131 time.sleep(update_interval) 132 133 # @Override 134 def SetUp(self): 135 """Initializes the parser for build command.""" 136 self.build_thread = {} 137 self.arg_parser.add_argument( 138 "--update", 139 choices=("single", "start", "stop", "list"), 140 default="start", 141 help="Update build info") 142 self.arg_parser.add_argument( 143 "--id", 144 default=None, 145 help="session ID only required for 'stop' update command") 146 self.arg_parser.add_argument( 147 "--interval", 148 type=int, 149 default=30, 150 help="Interval (seconds) to repeat build update.") 151 self.arg_parser.add_argument( 152 "--artifact-type", 153 choices=("device", "gsi", "test"), 154 default="device", 155 help="The type of an artifact to update") 156 self.arg_parser.add_argument( 157 "--branch", 158 required=True, 159 help="Branch to grab the artifact from.") 160 self.arg_parser.add_argument( 161 "--target", 162 required=True, 163 help="a comma-separate list of build target product(s).") 164 self.arg_parser.add_argument( 165 "--account_id", 166 default=common._DEFAULT_ACCOUNT_ID, 167 help="Partner Android Build account_id to use.") 168 self.arg_parser.add_argument( 169 "--method", 170 default="GET", 171 choices=("GET", "POST"), 172 help="Method for getting build information") 173 self.arg_parser.add_argument( 174 "--userinfo-file", 175 help= 176 "Location of file containing email and password, if using POST.") 177 self.arg_parser.add_argument( 178 "--noauth_local_webserver", 179 default=False, 180 type=bool, 181 help="True to not use a local webserver for authentication.") 182 183 # @Override 184 def Run(self, arg_line): 185 """Updates build info.""" 186 args = self.arg_parser.ParseLine(arg_line) 187 if args.update == "single": 188 self.UpdateBuild(args.account_id, args.branch, args.target, 189 args.artifact_type, args.method, 190 args.userinfo_file, args.noauth_local_webserver) 191 elif args.update == "list": 192 print("Running build update sessions:") 193 for id in self.build_thread: 194 print(" ID %d", id) 195 elif args.update == "start": 196 if args.interval <= 0: 197 raise ConsoleArgumentError("update interval must be positive") 198 # do not allow user to create new 199 # thread if one is currently running 200 if args.id is None: 201 if not self.build_thread: 202 args.id = 1 203 else: 204 args.id = max(self.build_thread) + 1 205 else: 206 args.id = int(args.id) 207 if args.id in self.build_thread and not hasattr( 208 self.build_thread[args.id], 'keep_running'): 209 print('build update (session ID: %s) already running. ' 210 'run build --update stop first.' % args.id) 211 return 212 self.build_thread[args.id] = threading.Thread( 213 target=self.UpdateBuildLoop, 214 args=( 215 args.account_id, 216 args.branch, 217 args.target, 218 args.artifact_type, 219 args.method, 220 args.userinfo_file, 221 args.noauth_local_webserver, 222 args.interval, 223 )) 224 self.build_thread[args.id].daemon = True 225 self.build_thread[args.id].start() 226 elif args.update == "stop": 227 if args.id is None: 228 print("--id must be set for stop") 229 else: 230 self.build_thread[int(args.id)].keep_running = False 231