1# Copyright (c) 2013 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4# 5# Expects to be run in an environment with sudo and no interactive password 6# prompt, such as within the Chromium OS development chroot. 7 8 9"""This file provides core logic for servo verify/repair process.""" 10 11 12import httplib 13import logging 14import socket 15import time 16import xmlrpclib 17 18from autotest_lib.client.bin import utils 19from autotest_lib.client.common_lib import error 20from autotest_lib.client.common_lib import global_config 21from autotest_lib.client.common_lib import lsbrelease_utils 22from autotest_lib.client.common_lib.cros import autoupdater 23from autotest_lib.client.common_lib.cros import dev_server 24from autotest_lib.client.common_lib.cros import retry 25from autotest_lib.client.common_lib.cros.graphite import autotest_stats 26from autotest_lib.client.common_lib.cros.network import ping_runner 27from autotest_lib.client.cros import constants as client_constants 28from autotest_lib.server import site_utils as server_site_utils 29from autotest_lib.server.cros import dnsname_mangler 30from autotest_lib.server.cros.servo import servo 31from autotest_lib.server.cros.dynamic_suite import frontend_wrappers 32from autotest_lib.server.hosts import ssh_host 33from autotest_lib.site_utils.rpm_control_system import rpm_client 34 35 36# Names of the host attributes in the database that represent the values for 37# the servo_host and servo_port for a servo connected to the DUT. 38SERVO_HOST_ATTR = 'servo_host' 39SERVO_PORT_ATTR = 'servo_port' 40 41_CONFIG = global_config.global_config 42 43class ServoHostException(error.AutoservError): 44 """This is the base class for exceptions raised by ServoHost.""" 45 pass 46 47 48class ServoHostVerifyFailure(ServoHostException): 49 """Raised when servo verification fails.""" 50 pass 51 52 53class ServoHostRepairFailure(ServoHostException): 54 """Raised when a repair method fails to repair a servo host.""" 55 pass 56 57 58class ServoHostRepairMethodNA(ServoHostException): 59 """Raised when a repair method is not applicable.""" 60 pass 61 62 63class ServoHostRepairTotalFailure(ServoHostException): 64 """Raised if all attempts to repair a servo host fail.""" 65 pass 66 67 68def make_servo_hostname(dut_hostname): 69 """Given a DUT's hostname, return the hostname of its servo. 70 71 @param dut_hostname: hostname of a DUT. 72 73 @return hostname of the DUT's servo. 74 75 """ 76 host_parts = dut_hostname.split('.') 77 host_parts[0] = host_parts[0] + '-servo' 78 return '.'.join(host_parts) 79 80 81class ServoHost(ssh_host.SSHHost): 82 """Host class for a host that controls a servo, e.g. beaglebone.""" 83 84 # Timeout for getting the value of 'pwr_button'. 85 PWR_BUTTON_CMD_TIMEOUT_SECS = 15 86 # Timeout for rebooting servo host. 87 REBOOT_TIMEOUT_SECS = 90 88 HOST_DOWN_TIMEOUT_SECS = 60 89 # Delay after rebooting for servod to become fully functional. 90 REBOOT_DELAY_SECS = 20 91 # Servod process name. 92 SERVOD_PROCESS = 'servod' 93 # Timeout for initializing servo signals. 94 INITIALIZE_SERVO_TIMEOUT_SECS = 30 95 96 _MAX_POWER_CYCLE_ATTEMPTS = 3 97 _timer = autotest_stats.Timer('servo_host') 98 99 100 def _initialize(self, servo_host='localhost', servo_port=9999, 101 required_by_test=True, is_in_lab=None, *args, **dargs): 102 """Initialize a ServoHost instance. 103 104 A ServoHost instance represents a host that controls a servo. 105 106 @param servo_host: Name of the host where the servod process 107 is running. 108 @param servo_port: Port the servod process is listening on. 109 @param required_by_test: True if servo is required by test. 110 @param is_in_lab: True if the servo host is in Cros Lab. Default is set 111 to None, for which utils.host_is_in_lab_zone will be 112 called to check if the servo host is in Cros lab. 113 114 """ 115 super(ServoHost, self)._initialize(hostname=servo_host, 116 *args, **dargs) 117 if is_in_lab is None: 118 self._is_in_lab = utils.host_is_in_lab_zone(self.hostname) 119 else: 120 self._is_in_lab = is_in_lab 121 self._is_localhost = (self.hostname == 'localhost') 122 remote = 'http://%s:%s' % (self.hostname, servo_port) 123 self._servod_server = xmlrpclib.ServerProxy(remote) 124 # Commands on the servo host must be run by the superuser. Our account 125 # on Beaglebone is root, but locally we might be running as a 126 # different user. If so - `sudo ' will have to be added to the 127 # commands. 128 if self._is_localhost: 129 self._sudo_required = utils.system_output('id -u') != '0' 130 else: 131 self._sudo_required = False 132 # Create a cache of Servo object. This must be called at the end of 133 # _initialize to make sure all attributes are set. 134 self._servo = None 135 self.required_by_test = required_by_test 136 try: 137 self.verify() 138 except Exception: 139 if required_by_test: 140 if not self.is_in_lab(): 141 raise 142 else: 143 self.repair() 144 145 146 def is_in_lab(self): 147 """Check whether the servo host is a lab device. 148 149 @returns: True if the servo host is in Cros Lab, otherwise False. 150 151 """ 152 return self._is_in_lab 153 154 155 def is_localhost(self): 156 """Checks whether the servo host points to localhost. 157 158 @returns: True if it points to localhost, otherwise False. 159 160 """ 161 return self._is_localhost 162 163 164 def get_servod_server_proxy(self): 165 """Return a proxy that can be used to communicate with servod server. 166 167 @returns: An xmlrpclib.ServerProxy that is connected to the servod 168 server on the host. 169 170 """ 171 return self._servod_server 172 173 174 def get_wait_up_processes(self): 175 """Get the list of local processes to wait for in wait_up. 176 177 Override get_wait_up_processes in 178 autotest_lib.client.common_lib.hosts.base_classes.Host. 179 Wait for servod process to go up. Called by base class when 180 rebooting the device. 181 182 """ 183 processes = [self.SERVOD_PROCESS] 184 return processes 185 186 187 def _is_cros_host(self): 188 """Check if a servo host is running chromeos. 189 190 @return: True if the servo host is running chromeos. 191 False if it isn't, or we don't have enough information. 192 """ 193 try: 194 result = self.run('grep -q CHROMEOS /etc/lsb-release', 195 ignore_status=True, timeout=10) 196 except (error.AutoservRunError, error.AutoservSSHTimeout): 197 return False 198 return result.exit_status == 0 199 200 201 def make_ssh_command(self, user='root', port=22, opts='', hosts_file=None, 202 connect_timeout=None, alive_interval=None): 203 """Override default make_ssh_command to use tuned options. 204 205 Tuning changes: 206 - ConnectTimeout=30; maximum of 30 seconds allowed for an SSH 207 connection failure. Consistency with remote_access.py. 208 209 - ServerAliveInterval=180; which causes SSH to ping connection every 210 180 seconds. In conjunction with ServerAliveCountMax ensures 211 that if the connection dies, Autotest will bail out quickly. 212 213 - ServerAliveCountMax=3; consistency with remote_access.py. 214 215 - ConnectAttempts=4; reduce flakiness in connection errors; 216 consistency with remote_access.py. 217 218 - UserKnownHostsFile=/dev/null; we don't care about the keys. 219 220 - SSH protocol forced to 2; needed for ServerAliveInterval. 221 222 @param user User name to use for the ssh connection. 223 @param port Port on the target host to use for ssh connection. 224 @param opts Additional options to the ssh command. 225 @param hosts_file Ignored. 226 @param connect_timeout Ignored. 227 @param alive_interval Ignored. 228 229 @returns: An ssh command with the requested settings. 230 231 """ 232 base_command = ('/usr/bin/ssh -a -x %s -o StrictHostKeyChecking=no' 233 ' -o UserKnownHostsFile=/dev/null -o BatchMode=yes' 234 ' -o ConnectTimeout=30 -o ServerAliveInterval=180' 235 ' -o ServerAliveCountMax=3 -o ConnectionAttempts=4' 236 ' -o Protocol=2 -l %s -p %d') 237 return base_command % (opts, user, port) 238 239 240 def _make_scp_cmd(self, sources, dest): 241 """Format scp command. 242 243 Given a list of source paths and a destination path, produces the 244 appropriate scp command for encoding it. Remote paths must be 245 pre-encoded. Overrides _make_scp_cmd in AbstractSSHHost 246 to allow additional ssh options. 247 248 @param sources: A list of source paths to copy from. 249 @param dest: Destination path to copy to. 250 251 @returns: An scp command that copies |sources| on local machine to 252 |dest| on the remote servo host. 253 254 """ 255 command = ('scp -rq %s -o BatchMode=yes -o StrictHostKeyChecking=no ' 256 '-o UserKnownHostsFile=/dev/null -P %d %s "%s"') 257 return command % (self.master_ssh_option, 258 self.port, ' '.join(sources), dest) 259 260 261 def run(self, command, timeout=3600, ignore_status=False, 262 stdout_tee=utils.TEE_TO_LOGS, stderr_tee=utils.TEE_TO_LOGS, 263 connect_timeout=30, options='', stdin=None, verbose=True, args=()): 264 """Run a command on the servo host. 265 266 Extends method `run` in SSHHost. If the servo host is a remote device, 267 it will call `run` in SSHost without changing anything. 268 If the servo host is 'localhost', it will call utils.system_output. 269 270 @param command: The command line string. 271 @param timeout: Time limit in seconds before attempting to 272 kill the running process. The run() function 273 will take a few seconds longer than 'timeout' 274 to complete if it has to kill the process. 275 @param ignore_status: Do not raise an exception, no matter 276 what the exit code of the command is. 277 @param stdout_tee/stderr_tee: Where to tee the stdout/stderr. 278 @param connect_timeout: SSH connection timeout (in seconds) 279 Ignored if host is 'localhost'. 280 @param options: String with additional ssh command options 281 Ignored if host is 'localhost'. 282 @param stdin: Stdin to pass (a string) to the executed command. 283 @param verbose: Log the commands. 284 @param args: Sequence of strings to pass as arguments to command by 285 quoting them in " and escaping their contents if necessary. 286 287 @returns: A utils.CmdResult object. 288 289 @raises AutoservRunError if the command failed. 290 @raises AutoservSSHTimeout SSH connection has timed out. Only applies 291 when servo host is not 'localhost'. 292 293 """ 294 run_args = {'command': command, 'timeout': timeout, 295 'ignore_status': ignore_status, 'stdout_tee': stdout_tee, 296 'stderr_tee': stderr_tee, 'stdin': stdin, 297 'verbose': verbose, 'args': args} 298 if self.is_localhost(): 299 if self._sudo_required: 300 run_args['command'] = 'sudo -n %s' % command 301 try: 302 return utils.run(**run_args) 303 except error.CmdError as e: 304 logging.error(e) 305 raise error.AutoservRunError('command execution error', 306 e.result_obj) 307 else: 308 run_args['connect_timeout'] = connect_timeout 309 run_args['options'] = options 310 return super(ServoHost, self).run(**run_args) 311 312 313 @_timer.decorate 314 def _check_servod(self): 315 """A sanity check of the servod state.""" 316 msg_prefix = 'Servod error: %s' 317 error_msg = None 318 try: 319 timeout, _ = retry.timeout( 320 self._servod_server.get, args=('pwr_button', ), 321 timeout_sec=self.PWR_BUTTON_CMD_TIMEOUT_SECS) 322 if timeout: 323 error_msg = msg_prefix % 'Request timed out.' 324 except (socket.error, xmlrpclib.Error, httplib.BadStatusLine) as e: 325 error_msg = msg_prefix % e 326 if error_msg: 327 raise ServoHostVerifyFailure(error_msg) 328 329 330 def _check_servo_config(self): 331 """Check if config file exists for servod. 332 333 If servod config file does not exist, there is no need to verify if 334 servo is working. The servo could be attached to a board not supported 335 yet. 336 337 @raises ServoHostVerifyFailure if /var/lib/servod/config does not exist. 338 339 """ 340 if self._is_localhost: 341 return 342 try: 343 self.run('test -f /var/lib/servod/config') 344 except (error.AutoservRunError, error.AutoservSSHTimeout) as e: 345 if not self._is_cros_host(): 346 logging.info('Ignoring servo config check failure, either %s ' 347 'is not running chromeos or we cannot find enough ' 348 'information about the host.', self.hostname) 349 return 350 raise ServoHostVerifyFailure( 351 'Servo config file check failed for %s: %s' % 352 (self.hostname, e)) 353 354 355 def _check_servod_status(self): 356 """Check if servod process is running. 357 358 If servod is not running, there is no need to verify if servo is 359 working. Check the process before making any servod call can avoid 360 long timeout that eventually fail any servod call. 361 If the servo host is set to localhost, failure of servod status check 362 will be ignored, as servo call may use ssh tunnel. 363 364 @raises ServoHostVerifyFailure if servod process does not exist. 365 366 """ 367 try: 368 pids = [str(int(s)) for s in 369 self.run('pgrep servod').stdout.strip().split('\n')] 370 logging.info('servod is running, PID=%s', ','.join(pids)) 371 except (error.AutoservRunError, error.AutoservSSHTimeout) as e: 372 if self._is_localhost: 373 logging.info('Ignoring servod status check failure. servo host ' 374 'is set to localhost, servo call may use ssh ' 375 'tunnel to go through.') 376 else: 377 raise ServoHostVerifyFailure( 378 'Servod status check failed for %s: %s' % 379 (self.hostname, e)) 380 381 382 def get_release_version(self): 383 """Get the value of attribute CHROMEOS_RELEASE_VERSION from lsb-release. 384 385 @returns The version string in lsb-release, under attribute 386 CHROMEOS_RELEASE_VERSION. 387 """ 388 lsb_release_content = self.run( 389 'cat "%s"' % client_constants.LSB_RELEASE).stdout.strip() 390 return lsbrelease_utils.get_chromeos_release_version( 391 lsb_release_content=lsb_release_content) 392 393 394 @_timer.decorate 395 def _update_image(self): 396 """Update the image on the servo host, if needed. 397 398 This method recognizes the following cases: 399 * If the Host is not running Chrome OS, do nothing. 400 * If a previously triggered update is now complete, reboot 401 to the new version. 402 * If the host is processing a previously triggered update, 403 do nothing. 404 * If the host is running a version of Chrome OS different 405 from the default for servo Hosts, trigger an update, but 406 don't wait for it to complete. 407 408 @raises dev_server.DevServerException: If all the devservers are down. 409 @raises site_utils.ParseBuildNameException: If the devserver returns 410 an invalid build name. 411 @raises autoupdater.ChromiumOSError: If something goes wrong in the 412 checking update engine client status or applying an update. 413 @raises AutoservRunError: If the update_engine_client isn't present on 414 the host, and the host is a cros_host. 415 416 """ 417 # servod could be running in a Ubuntu workstation. 418 if not self._is_cros_host(): 419 logging.info('Not attempting an update, either %s is not running ' 420 'chromeos or we cannot find enough information about ' 421 'the host.', self.hostname) 422 return 423 424 if lsbrelease_utils.is_moblab(): 425 logging.info('Not attempting an update, %s is running moblab.', 426 self.hostname) 427 return 428 429 board = _CONFIG.get_config_value( 430 'CROS', 'servo_board') 431 afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10) 432 target_version = afe.run('get_stable_version', board=board) 433 build_pattern = _CONFIG.get_config_value( 434 'CROS', 'stable_build_pattern') 435 target_build = build_pattern % (board, target_version) 436 target_build_number = server_site_utils.ParseBuildName( 437 target_build)[3] 438 ds = dev_server.ImageServer.resolve(self.hostname) 439 url = ds.get_update_url(target_build) 440 441 updater = autoupdater.ChromiumOSUpdater(update_url=url, host=self) 442 current_build_number = self.get_release_version() 443 status = updater.check_update_status() 444 445 if status == autoupdater.UPDATER_NEED_REBOOT: 446 logging.info('Rebooting beaglebone host %s with build %s', 447 self.hostname, current_build_number) 448 kwargs = { 449 'reboot_cmd': 'sleep 1 ; reboot & sleep 10; reboot -f', 450 'fastsync': True, 451 'label': None, 452 'wait': False, 453 } 454 # Do not wait for reboot to complete. Otherwise, self.reboot call 455 # will log reboot failure if servo does not come back. The logged 456 # reboot failure will lead to test job failure. If the test does not 457 # require servo, we don't want servo failure to fail the test with 458 # error: `Host did not return from reboot` in status.log 459 # If servo does not come back after reboot, exception needs to be 460 # raised, so test requires servo should fail. 461 self.reboot(**kwargs) 462 if self.wait_up(timeout=120): 463 current_build_number = self.get_release_version() 464 logging.info('servo host %s back from reboot, with build %s', 465 self.hostname, current_build_number) 466 else: 467 raise error.AutoservHostError( 468 'servo host %s failed to come back from reboot.' % 469 self.hostname) 470 471 if status in autoupdater.UPDATER_PROCESSING_UPDATE: 472 logging.info('servo host %s already processing an update, update ' 473 'engine client status=%s', self.hostname, status) 474 elif current_build_number != target_build_number: 475 logging.info('Using devserver url: %s to trigger update on ' 476 'servo host %s, from %s to %s', url, self.hostname, 477 current_build_number, target_build_number) 478 try: 479 ds.stage_artifacts(target_build, 480 artifacts=['full_payload']) 481 except Exception as e: 482 logging.error('Staging artifacts failed: %s', str(e)) 483 logging.error('Abandoning update for this cycle.') 484 else: 485 try: 486 updater.trigger_update() 487 except autoupdater.RootFSUpdateError as e: 488 trigger_download_status = 'failed with %s' % str(e) 489 autotest_stats.Counter( 490 'servo_host.RootFSUpdateError').increment() 491 else: 492 trigger_download_status = 'passed' 493 logging.info('Triggered download and update %s for %s, ' 494 'update engine currently in status %s', 495 trigger_download_status, self.hostname, 496 updater.check_update_status()) 497 else: 498 logging.info('servo host %s does not require an update.', 499 self.hostname) 500 501 502 def verify_software(self): 503 """Update the servo host and verify it's in a good state. 504 505 It overrides the base class function for verify_software. 506 If an update is available, downloads and applies it. Then verifies: 507 1) Whether basic servo command can run successfully. 508 2) Whether USB is in a good state. crbug.com/225932 509 510 @raises ServoHostVerifyFailure if servo host does not pass the checks. 511 512 """ 513 logging.info('Applying an update to the servo host, if necessary.') 514 self._update_image() 515 self._check_servo_config() 516 self._check_servod_status() 517 518 # If servo is already initialized, we don't need to do it again, call 519 # _check_servod should be enough. 520 if self._servo: 521 self._check_servod() 522 else: 523 self._servo = servo.Servo(servo_host=self) 524 timeout, _ = retry.timeout( 525 self._servo.initialize_dut, 526 timeout_sec=self.INITIALIZE_SERVO_TIMEOUT_SECS) 527 if timeout: 528 raise ServoHostVerifyFailure('Servo initialize timed out.') 529 logging.info('Sanity checks pass on servo host %s', self.hostname) 530 531 532 def _repair_with_sysrq_reboot(self): 533 """Reboot with magic SysRq key.""" 534 self.reboot(timeout=self.REBOOT_TIMEOUT_SECS, 535 label=None, 536 down_timeout=self.HOST_DOWN_TIMEOUT_SECS, 537 reboot_cmd='echo "b" > /proc/sysrq-trigger &', 538 fastsync=True) 539 time.sleep(self.REBOOT_DELAY_SECS) 540 541 542 def has_power(self): 543 """Return whether or not the servo host is powered by PoE.""" 544 # TODO(fdeng): See crbug.com/302791 545 # For now, assume all servo hosts in the lab have power. 546 return self.is_in_lab() 547 548 549 def power_cycle(self): 550 """Cycle power to this host via PoE if it is a lab device. 551 552 @raises ServoHostRepairFailure if it fails to power cycle the 553 servo host. 554 555 """ 556 if self.has_power(): 557 try: 558 rpm_client.set_power(self.hostname, 'CYCLE') 559 except (socket.error, xmlrpclib.Error, 560 httplib.BadStatusLine, 561 rpm_client.RemotePowerException) as e: 562 raise ServoHostRepairFailure( 563 'Power cycling %s failed: %s' % (self.hostname, e)) 564 else: 565 logging.info('Skipping power cycling, not a lab device.') 566 567 568 def _powercycle_to_repair(self): 569 """Power cycle the servo host using PoE. 570 571 @raises ServoHostRepairFailure if it fails to fix the servo host. 572 @raises ServoHostRepairMethodNA if it does not support power. 573 574 """ 575 if not self.has_power(): 576 raise ServoHostRepairMethodNA('%s does not support power.' % 577 self.hostname) 578 logging.info('Attempting repair via PoE powercycle.') 579 failed_cycles = 0 580 self.power_cycle() 581 while not self.wait_up(timeout=self.REBOOT_TIMEOUT_SECS): 582 failed_cycles += 1 583 if failed_cycles >= self._MAX_POWER_CYCLE_ATTEMPTS: 584 raise ServoHostRepairFailure( 585 'Powercycled host %s %d times; device did not come back' 586 ' online.' % (self.hostname, failed_cycles)) 587 self.power_cycle() 588 logging.info('Powercycling was successful after %d failures.', 589 failed_cycles) 590 # Allow some time for servod to get started. 591 time.sleep(self.REBOOT_DELAY_SECS) 592 593 594 def repair(self): 595 """Attempt to repair servo host. 596 597 This overrides the base class function for repair. 598 Note if the host is not in Cros Lab, the repair procedure 599 will be skipped. 600 601 @raises ServoHostRepairTotalFailure if all attempts fail. 602 603 """ 604 if not self.is_in_lab(): 605 logging.warning('Skip repairing servo host %s: Not a lab device.', 606 self.hostname) 607 return 608 logging.info('Attempting to repair servo host %s.', self.hostname) 609 # Reset the cache to guarantee servo initialization being called later. 610 self._servo = None 611 # TODO(dshi): add self._powercycle_to_repair back to repair_funcs 612 # after crbug.com/336606 is fixed. 613 repair_funcs = [self._repair_with_sysrq_reboot,] 614 errors = [] 615 for repair_func in repair_funcs: 616 counter_prefix = 'servo_host_repair.%s.' % repair_func.__name__ 617 try: 618 repair_func() 619 self.verify() 620 autotest_stats.Counter(counter_prefix + 'SUCCEEDED').increment() 621 return 622 except ServoHostRepairMethodNA as e: 623 logging.warning('Repair method NA: %s', e) 624 autotest_stats.Counter(counter_prefix + 'RepairNA').increment() 625 errors.append(str(e)) 626 except Exception as e: 627 logging.warning('Failed to repair servo: %s', e) 628 autotest_stats.Counter(counter_prefix + 'FAILED').increment() 629 errors.append(str(e)) 630 autotest_stats.Counter('servo_host_repair.Full_Repair_Failed'). \ 631 increment() 632 raise ServoHostRepairTotalFailure( 633 'All attempts at repairing the servo failed:\n%s' % 634 '\n'.join(errors)) 635 636 637 def get_servo(self): 638 """Get the cached servo.Servo object. 639 640 @return: a servo.Servo object. 641 """ 642 return self._servo 643 644 645def create_servo_host(dut, servo_args, try_lab_servo=False): 646 """Create a ServoHost object. 647 648 The `servo_args` parameter is a dictionary specifying optional 649 Servo client parameter overrides (i.e. a specific host or port). 650 When specified, the caller requires that an exception be raised 651 unless both the ServoHost and the Servo are successfully 652 created. 653 654 There are three possible cases: 655 1. If the DUT is in the Cros test lab then the ServoHost object 656 is only created for the host in the lab. Alternate host or 657 port settings in `servo_host` will be ignored. 658 2. When not case 1., but `servo_args` is not `None`, then create 659 a ServoHost object using `servo_args`. 660 3. Otherwise, return `None`. 661 662 When the `try_lab_servo` parameter is false, it indicates that a 663 ServoHost should not be created for a device in the Cros test 664 lab. The setting of `servo_args` takes precedence over the 665 setting of `try_lab_servo`. 666 667 @param dut: host name of the host that servo connects. It can be used to 668 lookup the servo in test lab using naming convention. 669 @param servo_args: A dictionary that contains args for creating 670 a ServoHost object, 671 e.g. {'servo_host': '172.11.11.111', 672 'servo_port': 9999}. 673 See comments above. 674 @param try_lab_servo: Boolean. Whether to create ServoHost for a device 675 in test lab. See above. 676 677 @returns: A ServoHost object or None. See comments above. 678 679 """ 680 required_by_test = servo_args is not None 681 if not utils.is_in_container(): 682 is_moblab = utils.is_moblab() 683 else: 684 is_moblab = _CONFIG.get_config_value( 685 'SSP', 'is_moblab', type=bool, default=False) 686 if not is_moblab: 687 dut_is_hostname = not dnsname_mangler.is_ip_address(dut) 688 if dut_is_hostname: 689 lab_servo_hostname = make_servo_hostname(dut) 690 is_in_lab = utils.host_is_in_lab_zone(lab_servo_hostname) 691 else: 692 is_in_lab = False 693 else: 694 # Servos on Moblab are not in the actual lab. 695 is_in_lab = False 696 afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10) 697 hosts = afe.get_hosts(hostname=dut) 698 if hosts and SERVO_HOST_ATTR in hosts[0].attributes: 699 servo_args = {} 700 servo_args[SERVO_HOST_ATTR] = hosts[0].attributes[SERVO_HOST_ATTR] 701 servo_args[SERVO_PORT_ATTR] = hosts[0].attributes.get( 702 SERVO_PORT_ATTR, 9999) 703 if (utils.is_in_container() and 704 servo_args[SERVO_HOST_ATTR] in ['localhost', '127.0.0.1']): 705 servo_args[SERVO_HOST_ATTR] = _CONFIG.get_config_value( 706 'SSP', 'host_container_ip', type=str, default=None) 707 708 if not is_in_lab: 709 if not required_by_test: 710 return None 711 return ServoHost(required_by_test=True, is_in_lab=False, **servo_args) 712 elif servo_args is not None or try_lab_servo: 713 # Technically, this duplicates the SSH ping done early in the servo 714 # proxy initialization code. However, this ping ends in a couple 715 # seconds when if fails, rather than the 60 seconds it takes to decide 716 # that an SSH ping has timed out. Specifically, that timeout happens 717 # when our servo DNS name resolves, but there is no host at that IP. 718 # TODO(dshi): crbug.com/380773 Remove this ping check once the bug is 719 # fixed. Autotest should not try to verify servo if servo is 720 # not required for the test. 721 ping_config = ping_runner.PingConfig( 722 lab_servo_hostname, count=3, 723 ignore_result=True, ignore_status=True) 724 logging.info('Pinging servo at %s', lab_servo_hostname) 725 host_is_up = ping_runner.PingRunner().ping(ping_config).received > 0 726 if host_is_up: 727 return ServoHost(servo_host=lab_servo_hostname, is_in_lab=is_in_lab, 728 required_by_test=required_by_test) 729 else: 730 return None 731