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 os 20 import socket 21 import threading 22 import time 23 24 from googleapiclient import errors 25 from google.protobuf import text_format 26 27 from host_controller import common 28 from host_controller.command_processor import base_command_processor 29 from host_controller.console_argument_parser import ConsoleArgumentError 30 from host_controller.tradefed import remote_operation 31 32 from vti.test_serving.proto import TestLabConfigMessage_pb2 as LabCfgMsg 33 from vti.test_serving.proto import TestScheduleConfigMessage_pb2 as SchedCfgMsg 34 35 36 class CommandConfig(base_command_processor.BaseCommandProcessor): 37 """Command processor for config command. 38 39 Attributes: 40 arg_parser: ConsoleArgumentParser object, argument parser. 41 console: cmd.Cmd console object. 42 command: string, command name which this processor will handle. 43 command_detail: string, detailed explanation for the command. 44 schedule_thread: dict containing threading.Thread instances(s) that 45 update schedule info regularly. 46 """ 47 48 command = "config" 49 command_detail = "Specifies a global config type to monitor." 50 51 def UpdateConfig(self, account_id, branch, targets, config_type, method): 52 """Updates the global configuration data. 53 54 Args: 55 account_id: string, Partner Android Build account_id to use. 56 branch: string, branch to grab the artifact from. 57 targets: string, a comma-separate list of build target product(s). 58 config_type: string, config type (`prod` or `test'). 59 method: string, HTTP method for fetching. 60 """ 61 62 self.console._build_provider["pab"].Authenticate() 63 for target in targets.split(","): 64 listed_builds = self.console._build_provider["pab"].GetBuildList( 65 account_id=account_id, 66 branch=branch, 67 target=target, 68 page_token="", 69 max_results=1, 70 method="GET") 71 72 if listed_builds and len(listed_builds) > 0: 73 listed_build = listed_builds[0] 74 if listed_build["successful"]: 75 device_images, test_suites, artifacts, configs = self.console._build_provider[ 76 "pab"].GetArtifact( 77 account_id=account_id, 78 branch=branch, 79 target=target, 80 artifact_name=( 81 "vti-global-config-%s.zip" % config_type), 82 build_id=listed_build["build_id"], 83 method=method) 84 base_path = os.path.dirname(configs[config_type]) 85 schedules_pbs = [] 86 lab_pbs = [] 87 for root, dirs, files in os.walk(base_path): 88 for config_file in files: 89 full_path = os.path.join(root, config_file) 90 try: 91 if config_file.endswith(".schedule_config"): 92 with open(full_path, "r") as fd: 93 context = fd.read() 94 sched_cfg_msg = SchedCfgMsg.ScheduleConfigMessage( 95 ) 96 text_format.Merge( 97 context, sched_cfg_msg) 98 schedules_pbs.append(sched_cfg_msg) 99 print sched_cfg_msg.manifest_branch 100 elif config_file.endswith(".lab_config"): 101 with open(full_path, "r") as fd: 102 context = fd.read() 103 lab_cfg_msg = LabCfgMsg.LabConfigMessage( 104 ) 105 text_format.Merge(context, lab_cfg_msg) 106 lab_pbs.append(lab_cfg_msg) 107 except text_format.ParseError as e: 108 print("ERROR: Config parsing error %s" % e) 109 self.console._vti_endpoint_client.UploadScheduleInfo( 110 schedules_pbs) 111 self.console._vti_endpoint_client.UploadLabInfo(lab_pbs) 112 113 def UpdateConfigLoop(self, account_id, branch, target, config_type, method, 114 update_interval): 115 """Regularly updates the global configuration. 116 117 Args: 118 account_id: string, Partner Android Build account_id to use. 119 branch: string, branch to grab the artifact from. 120 targets: string, a comma-separate list of build target product(s). 121 config_type: string, config type (`prod` or `test'). 122 method: string, HTTP method for fetching. 123 update_interval: int, number of seconds before repeating 124 """ 125 thread = threading.currentThread() 126 while getattr(thread, 'keep_running', True): 127 try: 128 self.UpdateConfig(account_id, branch, target, config_type, 129 method) 130 except (socket.error, remote_operation.RemoteOperationException, 131 httplib2.HttpLib2Error, errors.HttpError) as e: 132 logging.exception(e) 133 time.sleep(update_interval) 134 135 # @Override 136 def SetUp(self): 137 """Initializes the parser for config command.""" 138 self.schedule_thread = {} 139 self.arg_parser.add_argument( 140 "--update", 141 choices=("single", "start", "stop", "list"), 142 default="start", 143 help="Update build info") 144 self.arg_parser.add_argument( 145 "--id", 146 default=None, 147 help="session ID only required for 'stop' update command") 148 self.arg_parser.add_argument( 149 "--interval", 150 type=int, 151 default=60, 152 help="Interval (seconds) to repeat build update.") 153 self.arg_parser.add_argument( 154 "--config-type", 155 choices=("prod", "test"), 156 default="prod", 157 help="Whether it's for prod") 158 self.arg_parser.add_argument( 159 "--branch", 160 required=True, 161 help="Branch to grab the artifact from.") 162 self.arg_parser.add_argument( 163 "--target", 164 required=True, 165 help="a comma-separate list of build target product(s).") 166 self.arg_parser.add_argument( 167 "--account_id", 168 default=common._DEFAULT_ACCOUNT_ID, 169 help="Partner Android Build account_id to use.") 170 self.arg_parser.add_argument( 171 '--method', 172 default='GET', 173 choices=('GET', 'POST'), 174 help='Method for fetching') 175 176 # @Override 177 def Run(self, arg_line): 178 """Updates global config.""" 179 args = self.arg_parser.ParseLine(arg_line) 180 if args.update == "single": 181 self.UpdateConfig(args.account_id, args.branch, args.target, 182 args.config_type, args.method) 183 elif args.update == "list": 184 print("Running config update sessions:") 185 for id in self.schedule_thread: 186 print(" ID %d", id) 187 elif args.update == "start": 188 if args.interval <= 0: 189 raise ConsoleArgumentError("update interval must be positive") 190 # do not allow user to create new 191 # thread if one is currently running 192 if args.id is None: 193 if not self.schedule_thread: 194 args.id = 1 195 else: 196 args.id = max(self.schedule_thread) + 1 197 else: 198 args.id = int(args.id) 199 if args.id in self.schedule_thread and not hasattr( 200 self.schedule_thread[args.id], 'keep_running'): 201 print('config update already running. ' 202 'run config --update=stop --id=%s first.' % args.id) 203 return 204 self.schedule_thread[args.id] = threading.Thread( 205 target=self.UpdateConfigLoop, 206 args=( 207 args.account_id, 208 args.branch, 209 args.target, 210 args.config_type, 211 args.method, 212 args.interval, 213 )) 214 self.schedule_thread[args.id].daemon = True 215 self.schedule_thread[args.id].start() 216 elif args.update == "stop": 217 if args.id is None: 218 print("--id must be set for stop") 219 else: 220 self.schedule_thread[int(args.id)].keep_running = False 221 222 def Help(self): 223 base_command_processor.BaseCommandProcessor.Help(self) 224 print("Sample: schedule --target=aosp_sailfish-userdebug " 225 "--branch=git_oc-release") 226