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 29from host_controller.utils.usb import usb_utils 30 31from vts.utils.python.common import cmd_utils 32 33 34class CommandDevice(base_command_processor.BaseCommandProcessor): 35 """Command processor for Device command. 36 37 Attributes: 38 arg_parser: ConsoleArgumentParser object, argument parser. 39 console: cmd.Cmd console object. 40 command: string, command name which this processor will handle. 41 command_detail: string, detailed explanation for the command. 42 update_thread: threading.Thread that updates device state regularly. 43 """ 44 45 command = "device" 46 command_detail = "Selects device(s) under test." 47 48 def UpdateDevice(self, 49 server_type, 50 host, 51 lease, 52 suppress_lock_warning=True, 53 from_job_pool=False): 54 """Updates the device state of all devices on a given host. 55 56 Args: 57 server_type: string, the type of a test secheduling server. 58 host: HostController object 59 lease: boolean, True to lease and execute jobs. 60 suppress_lock_warning: bool, True to suppress the warning msg from 61 file_lock. 62 from_job_pool: bool, True if the 'device' command is executed from 63 one of the job pool processes. Checks only 64 the availability of the devices when set. 65 """ 66 if server_type == "vti": 67 devices = [] 68 69 if from_job_pool: 70 devices_dict = {} 71 for serial in self.console.GetSerials(): 72 device = {} 73 device["serial"] = serial 74 device["status"] = common._DEVICE_STATUS_DICT[ 75 "no-response"] 76 device["product"] = "error" 77 devices_dict[serial] = device 78 79 stdout, stderr, returncode = cmd_utils.ExecuteOneShellCommand( 80 "adb devices") 81 lines_adb = stdout.split("\n") 82 stdout, stderr, returncode = cmd_utils.ExecuteOneShellCommand( 83 "fastboot devices") 84 lines_fastboot = stdout.split("\n") 85 86 for line in lines_adb: 87 if (len(line.strip()) and not (line.startswith("* ") 88 or line.startswith("List "))): 89 device = {} 90 device["serial"] = line.split()[0] 91 serial = device["serial"] 92 93 if from_job_pool: 94 if (serial in devices_dict 95 and line.split()[1] == "device"): 96 devices_dict[serial][ 97 "status"] = common._DEVICE_STATUS_DICT[ 98 "online"] 99 product = (self.console._vti_endpoint_client. 100 GetJobDeviceProductName()) 101 if product: 102 devices_dict[serial]["product"] = product 103 continue 104 105 if self.console.file_lock.LockDevice( 106 serial, suppress_lock_warning) == False: 107 self.console.device_status[ 108 serial] = common._DEVICE_STATUS_DICT["use"] 109 if not suppress_lock_warning: 110 logging.info("Device %s already locked." % serial) 111 continue 112 113 stdout, _, retcode = cmd_utils.ExecuteOneShellCommand( 114 "adb -s %s reboot bootloader" % device["serial"], 115 common.DEFAULT_DEVICE_TIMEOUT_SECS, 116 usb_utils.ResetUsbDeviceOfSerial_Callback, 117 device["serial"]) 118 if retcode == 0: 119 lines_fastboot.append(line) 120 121 self.console.file_lock.UnlockDevice(serial) 122 123 for line in lines_fastboot: 124 if len(line.strip()): 125 device = {} 126 device["serial"] = line.split()[0] 127 serial = device["serial"] 128 129 if from_job_pool: 130 if serial in devices_dict: 131 devices_dict[serial][ 132 "status"] = common._DEVICE_STATUS_DICT[ 133 "fastboot"] 134 product = (self.console._vti_endpoint_client. 135 GetJobDeviceProductName()) 136 if product: 137 devices_dict[serial]["product"] = product 138 continue 139 140 if self.console.file_lock.LockDevice( 141 serial, suppress_lock_warning) == False: 142 self.console.device_status[ 143 serial] = common._DEVICE_STATUS_DICT["use"] 144 if not suppress_lock_warning: 145 logging.info("Device %s already locked." % serial) 146 continue 147 148 _, stderr, retcode = cmd_utils.ExecuteOneShellCommand( 149 "fastboot -s %s getvar product" % device["serial"], 150 common.DEFAULT_DEVICE_TIMEOUT_SECS, 151 usb_utils.ResetUsbDeviceOfSerial_Callback, 152 device["serial"]) 153 if retcode == 0: 154 res = stderr.splitlines()[0].rstrip() 155 if ":" in res: 156 device["product"] = res.split(":")[1].strip() 157 elif "waiting for %s" % serial in res: 158 res = stderr.splitlines()[1].rstrip() 159 device["product"] = res.split(":")[1].strip() 160 else: 161 device["product"] = "error" 162 self.console.device_status[ 163 serial] = common._DEVICE_STATUS_DICT["fastboot"] 164 else: 165 device["product"] = "error" 166 self.console.device_status[ 167 serial] = common._DEVICE_STATUS_DICT["no-response"] 168 169 device["status"] = self.console.device_status[serial] 170 devices.append(device) 171 172 self.console.file_lock.UnlockDevice(serial) 173 174 if from_job_pool: 175 devices = devices_dict.values() 176 if devices: 177 self.console._vti_endpoint_client.UploadDeviceInfo( 178 host.hostname, devices) 179 return 180 181 self.console._vti_endpoint_client.UploadDeviceInfo( 182 host.hostname, devices) 183 184 if lease: 185 self.console._job_in_queue.put("lease") 186 187 if self.console.vtslab_version: 188 self.console._vti_endpoint_client.UploadHostVersion( 189 host.hostname, self.console.vtslab_version) 190 elif server_type == "tfc": 191 devices = host.ListDevices() 192 for device in devices: 193 device.Extend(['sim_state', 'sim_operator', 'mac_address']) 194 snapshots = self.console._tfc_client.CreateDeviceSnapshot( 195 host._cluster_ids[0], host.hostname, devices) 196 self.console._tfc_client.SubmitHostEvents([snapshots]) 197 else: 198 logging.error("Error: unknown server_type %s for UpdateDevice", 199 server_type) 200 201 def UpdateDeviceRepeat(self, 202 server_type, 203 host, 204 lease, 205 update_interval, 206 suppress_lock_warning=True, 207 from_job_pool=False): 208 """Regularly updates the device state of devices on a given host. 209 210 Args: 211 server_type: string, the type of a test secheduling server. 212 host: HostController object 213 lease: boolean, True to lease and execute jobs. 214 update_interval: int, number of seconds before repeating 215 suppress_lock_warning: bool, True to suppress the warning msg from 216 file_lock. 217 from_job_pool: bool, True if the 'device' command is executed form 218 one of the job pool processes. 219 """ 220 thread = threading.currentThread() 221 while getattr(thread, 'keep_running', True): 222 try: 223 self.UpdateDevice(server_type, host, lease, 224 suppress_lock_warning, from_job_pool) 225 except (socket.error, remote_operation.RemoteOperationException, 226 httplib2.HttpLib2Error, errors.HttpError) as e: 227 logging.exception(e) 228 time.sleep(update_interval) 229 230 def RunUSBResetTimer(self, serial, interval): 231 """Sets up a timer to run the target function after 'interval' secs. 232 233 Args: 234 serial: string, serial number of the device whose USB device file 235 will reset when the timeout happens. 236 interval: int, sets up the timer for the target function to be 237 executed after 'interval' seconds, if not canceled. 238 239 Returns: 240 threading.Timer, set to reset USB port corresponding the device 241 with the given serial number. 242 """ 243 usb_reset_timer = threading.Timer(interval, self.USBResetCallback, 244 (serial, )) 245 usb_reset_timer.daemon = True 246 usb_reset_timer.start() 247 248 return usb_reset_timer 249 250 def USBResetCallback(self, serial): 251 """Resets USB device file corresponding to the given device serial. 252 253 Args: 254 serial: string, serial number of the device whose USB device file 255 will reset. 256 """ 257 device_file_path = usb_utils.GetDevicesUSBFilePath() 258 if serial in device_file_path: 259 logging.error( 260 "Device %s not responding. Resetting device file %s.", serial, 261 device_file_path[serial]) 262 usb_utils.ResetDeviceUsb(device_file_path[serial]) 263 264 # @Override 265 def SetUp(self): 266 """Initializes the parser for device command.""" 267 self.update_thread = None 268 self.arg_parser.add_argument( 269 "--set_serial", 270 default="", 271 help="Serial number for device. Can be a comma-separated list.") 272 self.arg_parser.add_argument( 273 "--update", 274 choices=("single", "start", "stop"), 275 default="start", 276 help="Update device info on cloud scheduler") 277 self.arg_parser.add_argument( 278 "--interval", 279 type=int, 280 default=30, 281 help="Interval (seconds) to repeat device update.") 282 self.arg_parser.add_argument( 283 "--host", type=int, help="The index of the host.") 284 self.arg_parser.add_argument( 285 "--server_type", 286 choices=("vti", "tfc"), 287 default="vti", 288 help="The type of a cloud-based test scheduler server.") 289 self.arg_parser.add_argument( 290 "--lease", 291 default=False, 292 type=bool, 293 help="Whether to lease jobs and execute them.") 294 self.arg_parser.add_argument( 295 "--suppress_lock_warning", 296 default=True, 297 help="Whether to suppress device lock warning messages.") 298 self.arg_parser.add_argument( 299 "--from_job_pool", 300 action="store_true", 301 help="Whether the command is executed from the job pool. " 302 "Check only the availability of the devices when set.") 303 304 # @Override 305 def Run(self, arg_line): 306 """Sets device info such as serial number.""" 307 args = self.arg_parser.ParseLine(arg_line) 308 if args.set_serial: 309 self.console.SetSerials(args.set_serial.split(",")) 310 logging.info("serials: %s", self.console._serials) 311 if args.update: 312 if args.host is None: 313 if len(self.console._hosts) > 1: 314 raise ConsoleArgumentError("More than one host.") 315 args.host = 0 316 host = self.console._hosts[args.host] 317 318 if args.suppress_lock_warning: 319 if (type(args.suppress_lock_warning) != str 320 or args.suppress_lock_warning.lower() == "true"): 321 suppress_lock_warning = True 322 else: 323 suppress_lock_warning = False 324 325 if args.update == "single": 326 self.UpdateDevice(args.server_type, host, args.lease, 327 suppress_lock_warning, args.from_job_pool) 328 elif args.update == "start": 329 if args.interval <= 0: 330 raise ConsoleArgumentError( 331 "update interval must be positive") 332 # do not allow user to create new 333 # thread if one is currently running 334 if self.update_thread is not None and not hasattr( 335 self.update_thread, 'keep_running'): 336 logging.warning('device update already running. ' 337 'run device --update stop first.') 338 return 339 self.update_thread = threading.Thread( 340 target=self.UpdateDeviceRepeat, 341 args=( 342 args.server_type, 343 host, 344 args.lease, 345 args.interval, 346 suppress_lock_warning, 347 args.from_job_pool, 348 )) 349 self.update_thread.daemon = True 350 self.update_thread.start() 351 elif args.update == "stop": 352 self.update_thread.keep_running = False 353 if self.console.GetSerials(): 354 self.console.ResetSerials() 355