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.command_processor import base_command_processor 26from host_controller.command_processor import command_config 27from host_controller.console_argument_parser import ConsoleArgumentError 28from host_controller.tradefed import remote_operation 29 30 31class CommandConfigLocal(command_config.CommandConfig): 32 """Command processor for config-local command. 33 34 Attributes: 35 arg_parser: ConsoleArgumentParser object, argument parser. 36 console: cmd.Cmd console object. 37 command: string, command name which this processor will handle. 38 command_detail: string, detailed explanation for the command. 39 schedule_thread: dict containing threading.Thread instances(s) that 40 update schedule info regularly. 41 """ 42 43 command = "config_local" 44 command_detail = "Uploads configs from local path." 45 46 # @Override 47 def UpdateConfig(self, path, update_build, clear_schedule, clear_labinfo): 48 """Updates the global configuration data. 49 50 Args: 51 path: string, a path where config files are stored. 52 update_build: boolean, indicating whether to upload build info. 53 """ 54 if path: 55 self.UploadConfig( 56 path=path, 57 update_build=update_build, 58 clear_schedule=clear_schedule, 59 clear_labinfo=clear_labinfo) 60 61 # @Override 62 def UpdateConfigLoop(self, path, update_build, update_interval, 63 clear_schedule, clear_labinfo): 64 """Regularly updates the global configuration. 65 66 Args: 67 path: string, a path where config files are stored. 68 update_build: boolean, indicating whether to upload build info. 69 update_interval: int, number of seconds before repeating 70 """ 71 thread = threading.currentThread() 72 while getattr(thread, 'keep_running', True): 73 try: 74 self.UpdateConfig(path, update_build, clear_schedule, 75 clear_labinfo) 76 except (socket.error, remote_operation.RemoteOperationException, 77 httplib2.HttpLib2Error, errors.HttpError) as e: 78 logging.exception(e) 79 time.sleep(update_interval) 80 81 # @Override 82 def SetUp(self): 83 """Initializes the parser for config command.""" 84 self.schedule_thread = {} 85 self.arg_parser.add_argument( 86 "--update", 87 choices=("single", "start", "stop", "list"), 88 default="single", 89 help="Update build info") 90 self.arg_parser.add_argument( 91 "--id", 92 default=None, 93 help="session ID only required for 'stop' update command") 94 self.arg_parser.add_argument( 95 "--interval", 96 type=int, 97 default=60, 98 help="Interval (seconds) to repeat build update.") 99 self.arg_parser.add_argument( 100 "--path", 101 required=True, 102 help="A path where config files are stored.") 103 self.arg_parser.add_argument( 104 '--update_build', 105 dest='update_build', 106 action='store_true', 107 help='A boolean value indicating whether to upload build info.') 108 self.arg_parser.add_argument( 109 "--clear_schedule", 110 default=False, 111 help="True to clear all schedule data on the scheduler cloud") 112 self.arg_parser.add_argument( 113 "--clear_labinfo", 114 default=False, 115 help="True to clear all lab info data on the scheduler cloud") 116 117 # @Override 118 def Run(self, arg_line): 119 """Updates global config.""" 120 args = self.arg_parser.ParseLine(arg_line) 121 if args.update == "single": 122 self.UpdateConfig(args.path, args.update_build, 123 args.clear_schedule, args.clear_labinfo) 124 elif args.update == "list": 125 logging.info("Running config update sessions:") 126 for id in self.schedule_thread: 127 logging.info(" ID %d", id) 128 elif args.update == "start": 129 if args.interval <= 0: 130 raise ConsoleArgumentError("update interval must be positive") 131 # do not allow user to create new 132 # thread if one is currently running 133 if args.id is None: 134 if not self.schedule_thread: 135 args.id = 1 136 else: 137 args.id = max(self.schedule_thread) + 1 138 else: 139 args.id = int(args.id) 140 if args.id in self.schedule_thread and not hasattr( 141 self.schedule_thread[args.id], 'keep_running'): 142 logging.warning( 143 'config update already running. ' 144 'run config-local --update=stop --id=%s first.', args.id) 145 return 146 self.schedule_thread[args.id] = threading.Thread( 147 target=self.UpdateConfigLoop, 148 args=( 149 args.path, 150 args.update_build, 151 args.interval, 152 args.clear_schedule, 153 args.clear_labinfo, 154 )) 155 self.schedule_thread[args.id].daemon = True 156 self.schedule_thread[args.id].start() 157 elif args.update == "stop": 158 if args.id is None: 159 logging.error("--id must be set for stop") 160 else: 161 self.schedule_thread[int(args.id)].keep_running = False 162 163 def Help(self): 164 base_command_processor.BaseCommandProcessor.Help(self) 165 logging.info("Sample: config-local --path=/usr/local/home/config/prod " 166 "--update=single --update_build") 167