1# Lint as: python2, python3 2# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6from __future__ import absolute_import 7from __future__ import division 8from __future__ import print_function 9from io import StringIO 10import json 11 12import logging 13import os 14import re 15import sys 16import six 17import time 18 19import common 20from autotest_lib.client.bin import utils 21from autotest_lib.client.common_lib import autotemp 22from autotest_lib.client.common_lib import error 23from autotest_lib.client.common_lib import global_config 24from autotest_lib.client.common_lib import hosts 25from autotest_lib.client.common_lib import lsbrelease_utils 26from autotest_lib.client.common_lib import utils as common_utils 27from autotest_lib.client.common_lib.cros import cros_config 28from autotest_lib.client.common_lib.cros import dev_server 29from autotest_lib.client.common_lib.cros import retry 30from autotest_lib.client.cros import constants as client_constants 31from autotest_lib.client.cros import cros_ui 32from autotest_lib.server import afe_utils 33from autotest_lib.server import utils as server_utils 34from autotest_lib.server.cros import provision 35from autotest_lib.server.cros.dynamic_suite import constants as ds_constants 36from autotest_lib.server.cros.dynamic_suite import tools, frontend_wrappers 37from autotest_lib.server.cros.device_health_profile import device_health_profile 38from autotest_lib.server.cros.device_health_profile import profile_constants 39from autotest_lib.server.cros.servo import pdtester 40from autotest_lib.server.hosts import abstract_ssh 41from autotest_lib.server.hosts import base_label 42from autotest_lib.server.hosts import chameleon_host 43from autotest_lib.server.hosts import cros_constants 44from autotest_lib.server.hosts import cros_label 45from autotest_lib.server.hosts import cros_repair 46from autotest_lib.server.hosts import pdtester_host 47from autotest_lib.server.hosts import servo_host 48from autotest_lib.server.hosts import servo_constants 49from autotest_lib.site_utils.rpm_control_system import rpm_client 50from autotest_lib.site_utils.admin_audit import constants as audit_const 51from autotest_lib.site_utils.admin_audit import verifiers as audit_verify 52from six.moves import zip 53 54 55# In case cros_host is being ran via SSP on an older Moblab version with an 56# older chromite version. 57try: 58 from autotest_lib.utils.frozen_chromite.lib import metrics 59except ImportError: 60 metrics = utils.metrics_mock 61 62 63CONFIG = global_config.global_config 64 65class FactoryImageCheckerException(error.AutoservError): 66 """Exception raised when an image is a factory image.""" 67 pass 68 69 70class CrosHost(abstract_ssh.AbstractSSHHost): 71 """Chromium OS specific subclass of Host.""" 72 73 VERSION_PREFIX = provision.CROS_VERSION_PREFIX 74 75 _AFE = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10) 76 77 # Timeout values (in seconds) associated with various ChromeOS 78 # state changes. 79 # 80 # In general, a good rule of thumb is that the timeout can be up 81 # to twice the typical measured value on the slowest platform. 82 # The times here have not necessarily been empirically tested to 83 # meet this criterion. 84 # 85 # SLEEP_TIMEOUT: Time to allow for suspend to memory. 86 # RESUME_TIMEOUT: Time to allow for resume after suspend, plus 87 # time to restart the netwowrk. 88 # SHUTDOWN_TIMEOUT: Time to allow for shut down. 89 # BOOT_TIMEOUT: Time to allow for boot from power off. Among 90 # other things, this must account for the 30 second dev-mode 91 # screen delay, time to start the network on the DUT, and the 92 # ssh timeout of 120 seconds. 93 # USB_BOOT_TIMEOUT: Time to allow for boot from a USB device, 94 # including the 30 second dev-mode delay and time to start the 95 # network. 96 # INSTALL_TIMEOUT: Time to allow for chromeos-install. 97 # ADMIN_INSTALL_TIMEOUT: Time to allow for chromeos-install 98 # used by admin tasks. 99 # POWERWASH_BOOT_TIMEOUT: Time to allow for a reboot that 100 # includes powerwash. 101 102 SLEEP_TIMEOUT = 2 103 RESUME_TIMEOUT = 10 104 SHUTDOWN_TIMEOUT = 10 105 BOOT_TIMEOUT = 150 106 USB_BOOT_TIMEOUT = 300 107 INSTALL_TIMEOUT = 480 108 ADMIN_INSTALL_TIMEOUT = 600 109 POWERWASH_BOOT_TIMEOUT = 60 110 DEVSERVER_DOWNLOAD_TIMEOUT = 600 111 112 # Minimum OS version that supports server side packaging. Older builds may 113 # not have server side package built or with Autotest code change to support 114 # server-side packaging. 115 MIN_VERSION_SUPPORT_SSP = CONFIG.get_config_value( 116 'AUTOSERV', 'min_version_support_ssp', type=int) 117 118 USE_FSFREEZE = CONFIG.get_config_value( 119 'CROS', 'enable_fs_freeze', type=bool, default=False) 120 121 # REBOOT_TIMEOUT: How long to wait for a reboot. 122 # 123 # We have a long timeout to ensure we don't flakily fail due to other 124 # issues. Shorter timeouts are vetted in platform_RebootAfterUpdate. 125 # TODO(sbasi - crbug.com/276094) Restore to 5 mins once the 'host did not 126 # return from reboot' bug is solved. 127 REBOOT_TIMEOUT = 480 128 129 # _USB_POWER_TIMEOUT: Time to allow for USB to power toggle ON and OFF. 130 # _POWER_CYCLE_TIMEOUT: Time to allow for manual power cycle. 131 # _CHANGE_SERVO_ROLE_TIMEOUT: Time to allow DUT regain network connection 132 # since changing servo role will reset USB state 133 # and causes temporary ethernet drop. 134 _USB_POWER_TIMEOUT = 5 135 _POWER_CYCLE_TIMEOUT = 10 136 _CHANGE_SERVO_ROLE_TIMEOUT = 180 137 138 _RPM_HOSTNAME_REGEX = ('chromeos(\d+)(-row(\d+))?-rack(\d+[a-z]*)' 139 '-host(\d+)') 140 141 # Constants used in ping_wait_up() and ping_wait_down(). 142 # 143 # _PING_WAIT_COUNT is the approximate number of polling 144 # cycles to use when waiting for a host state change. 145 # 146 # _PING_STATUS_DOWN and _PING_STATUS_UP are names used 147 # for arguments to the internal _ping_wait_for_status() 148 # method. 149 _PING_WAIT_COUNT = 40 150 _PING_STATUS_DOWN = False 151 _PING_STATUS_UP = True 152 153 # Allowed values for the power_method argument. 154 155 # POWER_CONTROL_RPM: Used in power_off/on/cycle() methods, default for all 156 # DUTs except those with servo_v4 CCD. 157 # POWER_CONTROL_CCD: Used in power_off/on/cycle() methods, default for all 158 # DUTs with servo_v4 CCD. 159 # POWER_CONTROL_SERVO: Used in set_power() and power_cycle() methods. 160 # POWER_CONTROL_MANUAL: Used in set_power() and power_cycle() methods. 161 POWER_CONTROL_RPM = 'RPM' 162 POWER_CONTROL_CCD = 'CCD' 163 POWER_CONTROL_SERVO = 'servoj10' 164 POWER_CONTROL_MANUAL = 'manual' 165 166 POWER_CONTROL_VALID_ARGS = (POWER_CONTROL_RPM, 167 POWER_CONTROL_CCD, 168 POWER_CONTROL_SERVO, 169 POWER_CONTROL_MANUAL) 170 171 _RPM_OUTLET_CHANGED = 'outlet_changed' 172 173 # URL pattern to download firmware image. 174 _FW_IMAGE_URL_PATTERN = CONFIG.get_config_value( 175 'CROS', 'firmware_url_pattern', type=str) 176 177 # Regular expression for extracting EC version string 178 _EC_REGEX = '(%s_\w*[-\.]\w*[-\.]\w*[-\.]\w*)' 179 180 # Regular expression for extracting BIOS version string 181 _BIOS_REGEX = '(%s\.\w*\.\w*\.\w*)' 182 183 # Command to update firmware located on DUT 184 _FW_UPDATE_CMD = 'chromeos-firmwareupdate --mode=recovery %s' 185 186 @staticmethod 187 def check_host(host, timeout=10): 188 """ 189 Check if the given host is a chrome-os host. 190 191 @param host: An ssh host representing a device. 192 @param timeout: The timeout for the run command. 193 194 @return: True if the host device is chromeos. 195 196 """ 197 try: 198 result = host.run( 199 'grep -q CHROMEOS /etc/lsb-release && ' 200 '! grep -q moblab /etc/lsb-release && ' 201 '! grep -q labstation /etc/lsb-release &&' 202 ' grep CHROMEOS_RELEASE_BOARD /etc/lsb-release', 203 ignore_status=True, 204 timeout=timeout).stdout 205 if result: 206 return not ( 207 lsbrelease_utils.is_jetstream( 208 lsb_release_content=result) or 209 lsbrelease_utils.is_gce_board( 210 lsb_release_content=result)) 211 212 except (error.AutoservRunError, error.AutoservSSHTimeout): 213 return False 214 215 return False 216 217 218 @staticmethod 219 def get_chameleon_arguments(args_dict): 220 """Extract chameleon options from `args_dict` and return the result. 221 222 Recommended usage: 223 ~~~~~~~~ 224 args_dict = utils.args_to_dict(args) 225 chameleon_args = hosts.CrosHost.get_chameleon_arguments(args_dict) 226 host = hosts.create_host(machine, chameleon_args=chameleon_args) 227 ~~~~~~~~ 228 229 @param args_dict Dictionary from which to extract the chameleon 230 arguments. 231 """ 232 chameleon_args = {key: args_dict[key] 233 for key in ('chameleon_host', 'chameleon_port') 234 if key in args_dict} 235 if 'chameleon_ssh_port' in args_dict: 236 chameleon_args['port'] = int(args_dict['chameleon_ssh_port']) 237 return chameleon_args 238 239 @staticmethod 240 def get_btattenuator_arguments(args_dict): 241 """Extract btattenuator options from `args_dict` and return the result. 242 243 @param args_dict Dictionary from which to extract the btattenuator 244 arguments. 245 """ 246 logging.debug("args dict in croshost is %s", args_dict) 247 btattenuator_args = { 248 key: args_dict[key] 249 for key in ('btatten_addr', ) if key in args_dict 250 } 251 252 return btattenuator_args 253 254 @staticmethod 255 def get_btpeer_arguments(args_dict): 256 """Extract btpeer options from `args_dict` and return the result. 257 258 This is used to parse details of Bluetooth peer. 259 Recommended usage: 260 ~~~~~~~~ 261 args_dict = utils.args_to_dict(args) 262 btpeer_args = hosts.CrosHost.get_btpeer_arguments(args_dict) 263 host = hosts.create_host(machine, btpeer_args=btpeer_args) 264 ~~~~~~~~ 265 266 If btpeer_host_list is given, it should be a comma delimited list of 267 host:ssh_port/chameleon_port 268 127.0.0.1:22/9992 269 270 When using ipv6, wrap the host portion in square brackets: 271 [::1]:22/9992 272 273 Note: Only the host name is required. Both ports are optional. 274 If providing the chameleon port, note that you should provide an 275 unforwarded port (i.e. the port exposed on the actual dut). 276 277 @param args_dict: Dictionary from which to extract the btpeer 278 arguments. 279 """ 280 if 'btpeer_host_list' in args_dict: 281 result = [] 282 for btpeer in args_dict['btpeer_host_list'].split(','): 283 # IPv6 addresses including a port number should be enclosed in 284 # square brackets. 285 delimiter = ']:' if re.search(r':.*:', btpeer) else ':' 286 287 # Split into ip + ports 288 split = btpeer.strip('[]').split(delimiter) 289 290 # If ports are given, split into ssh + chameleon ports 291 if len(split) > 1: 292 ports = split[1].split('/') 293 split = [split[0]] + ports 294 295 result.append({ 296 key: value 297 for key, value in zip(('btpeer_host', 298 'btpeer_ssh_port', 299 'btpeer_port'), split) 300 }) 301 return result 302 else: 303 return {key: args_dict[key] 304 for key in ('btpeer_host', 'btpeer_port', 'btpeer_ssh_port') 305 if key in args_dict} 306 307 308 @staticmethod 309 def get_local_host_ip(args_dict): 310 """Ip address of DUT in the local LAN. 311 312 When using port forwarding during testing, the host ip is 127.0.0.1 and 313 can't be used by any peer devices (for example to scp). A local IP 314 should be given in this case so peripherals can access the DUT in the 315 local LAN. 316 317 The argument should be given with the key |local_host_ip|. 318 319 @params args_dict: Dictionary from which to extract the local host ip. 320 """ 321 return { 322 key: args_dict[key] 323 for key in ('local_host_ip', ) if key in args_dict 324 } 325 326 @staticmethod 327 def get_pdtester_arguments(args_dict): 328 """Extract chameleon options from `args_dict` and return the result. 329 330 Recommended usage: 331 ~~~~~~~~ 332 args_dict = utils.args_to_dict(args) 333 pdtester_args = hosts.CrosHost.get_pdtester_arguments(args_dict) 334 host = hosts.create_host(machine, pdtester_args=pdtester_args) 335 ~~~~~~~~ 336 337 @param args_dict Dictionary from which to extract the pdtester 338 arguments. 339 """ 340 return {key: args_dict[key] 341 for key in ('pdtester_host', 'pdtester_port') 342 if key in args_dict} 343 344 345 @staticmethod 346 def get_servo_arguments(args_dict): 347 """Extract servo options from `args_dict` and return the result. 348 349 Recommended usage: 350 ~~~~~~~~ 351 args_dict = utils.args_to_dict(args) 352 servo_args = hosts.CrosHost.get_servo_arguments(args_dict) 353 host = hosts.create_host(machine, servo_args=servo_args) 354 ~~~~~~~~ 355 356 @param args_dict Dictionary from which to extract the servo 357 arguments. 358 """ 359 servo_attrs = (servo_constants.SERVO_HOST_ATTR, 360 servo_constants.SERVO_HOST_SSH_PORT_ATTR, 361 servo_constants.SERVO_PORT_ATTR, 362 servo_constants.SERVOD_DOCKER_ATTR, 363 servo_constants.SERVO_SERIAL_ATTR, 364 servo_constants.SERVO_BOARD_ATTR, 365 servo_constants.SERVO_MODEL_ATTR) 366 servo_args = {key: args_dict[key] 367 for key in servo_attrs 368 if key in args_dict} 369 return ( 370 None 371 if servo_constants.SERVO_HOST_ATTR in servo_args 372 and not servo_args[servo_constants.SERVO_HOST_ATTR] 373 else servo_args) 374 375 376 def _initialize(self, 377 hostname, 378 chameleon_args=None, 379 servo_args=None, 380 pdtester_args=None, 381 try_lab_servo=False, 382 try_servo_repair=False, 383 ssh_verbosity_flag='', 384 ssh_options='', 385 try_servo_recovery=False, 386 *args, 387 **dargs): 388 """Initialize superclasses, |self.chameleon|, and |self.servo|. 389 390 This method will attempt to create the test-assistant object 391 (chameleon/servo) when it is needed by the test. Check 392 the docstring of chameleon_host.create_chameleon_host and 393 servo_host.create_servo_host for how this is determined. 394 395 @param hostname: Hostname of the dut. 396 @param chameleon_args: A dictionary that contains args for creating 397 a ChameleonHost. See chameleon_host for details. 398 @param servo_args: A dictionary that contains args for creating 399 a ServoHost object. See servo_host for details. 400 @param try_lab_servo: When true, indicates that an attempt should 401 be made to create a ServoHost for a DUT in 402 the test lab, even if not required by 403 `servo_args`. See servo_host for details. 404 @param try_servo_repair: If a servo host is created, check it 405 with `repair()` rather than `verify()`. 406 See servo_host for details. 407 @param ssh_verbosity_flag: String, to pass to the ssh command to control 408 verbosity. 409 @param ssh_options: String, other ssh options to pass to the ssh 410 command. 411 @param try_servo_recovery: When True, start servod in recovery mode. 412 See servo_host for details. 413 """ 414 super(CrosHost, self)._initialize(hostname=hostname, *args, **dargs) 415 self._repair_strategy = cros_repair.create_cros_repair_strategy() 416 # hold special dut_state for repair process 417 self._device_repair_state = None 418 self.labels = base_label.LabelRetriever(cros_label.CROS_LABELS) 419 # self.env is a dictionary of environment variable settings 420 # to be exported for commands run on the host. 421 # LIBC_FATAL_STDERR_ can be useful for diagnosing certain 422 # errors that might happen. 423 self.env['LIBC_FATAL_STDERR_'] = '1' 424 self._ssh_verbosity_flag = ssh_verbosity_flag 425 self._ssh_options = ssh_options 426 self.health_profile = None 427 self._default_power_method = None 428 dut_health_profile = device_health_profile.DeviceHealthProfile( 429 hostname=self.hostname, 430 host_info=self.host_info_store.get(), 431 result_dir=self.get_result_dir()) 432 433 # TODO(otabek@): remove when b/171414073 closed 434 if self.use_icmp: 435 pingable_before_servo = self.is_up_fast(count=1) 436 if pingable_before_servo: 437 logging.info('DUT is pingable before init Servo.') 438 else: 439 logging.info('Skipping ping to DUT before init Servo.') 440 _servo_host, servo_state = servo_host.create_servo_host( 441 dut=self, 442 servo_args=servo_args, 443 try_lab_servo=try_lab_servo, 444 try_servo_repair=try_servo_repair, 445 try_servo_recovery=try_servo_recovery, 446 dut_host_info=self.host_info_store.get(), 447 dut_health_profile=dut_health_profile) 448 if dut_health_profile.is_loaded(): 449 logging.info('Device health profile loaded.') 450 # The device profile is located in the servo_host which make it 451 # dependency. If profile is not loaded yet then we do not have it 452 # TODO(otabek@) persist device provide out of servo-host. 453 self.health_profile = dut_health_profile 454 self.set_servo_host(_servo_host, servo_state) 455 456 # TODO(waihong): Do the simplication on Chameleon too. 457 self._chameleon_host = chameleon_host.create_chameleon_host( 458 dut=self.hostname, 459 chameleon_args=chameleon_args) 460 if self._chameleon_host: 461 self.chameleon = self._chameleon_host.create_chameleon_board() 462 else: 463 self.chameleon = None 464 465 # Bluetooth peers will be populated by the test if needed 466 self._btpeer_host_list = [] 467 self.btpeer_list = [] 468 self.btpeer = None 469 470 # Add pdtester host if pdtester args were added on command line 471 self._pdtester_host = pdtester_host.create_pdtester_host( 472 pdtester_args, self._servo_host) 473 474 if self._pdtester_host: 475 self.pdtester_servo = self._pdtester_host.get_servo() 476 logging.info('pdtester_servo: %r', self.pdtester_servo) 477 # Create the pdtester object used to access the ec uart 478 self.pdtester = pdtester.PDTester(self.pdtester_servo, 479 self._pdtester_host.get_servod_server_proxy()) 480 else: 481 self.pdtester = None 482 483 def initialize_btpeer(self, btpeer_args=[]): 484 """ Initialize the Bluetooth peers 485 486 Initialize Bluetooth peer devices given in the arguments. Bluetooth peer 487 is chameleon host on Raspberry Pi. 488 @param btpeer_args: A dictionary that contains args for creating 489 a ChameleonHost. See chameleon_host for details. 490 491 """ 492 logging.debug('Attempting to initialize bluetooth peers if available') 493 try: 494 if type(btpeer_args) is list: 495 btpeer_args_list = btpeer_args 496 else: 497 btpeer_args_list = [btpeer_args] 498 499 self._btpeer_host_list = chameleon_host.create_btpeer_host( 500 dut=self.hostname, btpeer_args_list=btpeer_args_list) 501 logging.debug('Bluetooth peer hosts are %s', 502 self._btpeer_host_list) 503 self.btpeer_list = [_host.create_chameleon_board() for _host in 504 self._btpeer_host_list if _host is not None] 505 506 if len(self.btpeer_list) > 0: 507 self.btpeer = self.btpeer_list[0] 508 509 logging.debug('After initialize_btpeer btpeer_list %s ' 510 'btpeer_host_list is %s and btpeer is %s', 511 self.btpeer_list, self._btpeer_host_list, 512 self.btpeer) 513 except Exception as e: 514 logging.error('Exception %s in initialize_btpeer', str(e)) 515 516 517 def get_cros_repair_image_name(self): 518 """Get latest stable cros image name from AFE. 519 520 Use the board name from the info store. Should that fail, try to 521 retrieve the board name from the host's installed image itself. 522 523 @returns: current stable cros image name for this host. 524 """ 525 info = self.host_info_store.get() 526 if not info.board: 527 logging.warning('No board label value found. Trying to infer ' 528 'from the host itself.') 529 try: 530 info.labels.append(self.get_board()) 531 except (error.AutoservRunError, error.AutoservSSHTimeout) as e: 532 logging.error('Also failed to get the board name from the DUT ' 533 'itself. %s.', str(e)) 534 raise error.AutoservError('Cannot determine board of the DUT' 535 ' while getting repair image name.') 536 return afe_utils.get_stable_cros_image_name_v2(info) 537 538 539 def host_version_prefix(self, image): 540 """Return version label prefix. 541 542 In case the CrOS provisioning version is something other than the 543 standard CrOS version e.g. CrOS TH version, this function will 544 find the prefix from provision.py. 545 546 @param image: The image name to find its version prefix. 547 @returns: A prefix string for the image type. 548 """ 549 return provision.get_version_label_prefix(image) 550 551 def stage_build_to_usb(self, build): 552 """Stage the current ChromeOS image on the USB stick connected to the 553 servo. 554 555 @param build: The build to download and send to USB. 556 """ 557 if not self.servo: 558 raise error.TestError('Host %s does not have servo.' % 559 self.hostname) 560 561 _, update_url = self.stage_image_for_servo(build) 562 563 try: 564 self.servo.image_to_servo_usb(update_url) 565 finally: 566 # servo.image_to_servo_usb turned the DUT off, so turn it back on 567 logging.debug('Turn DUT power back on.') 568 self.servo.get_power_state_controller().power_on() 569 570 logging.debug('ChromeOS image %s is staged on the USB stick.', 571 build) 572 573 def verify_job_repo_url(self, tag=''): 574 """ 575 Make sure job_repo_url of this host is valid. 576 577 Eg: The job_repo_url "http://lmn.cd.ab.xyx:8080/static/\ 578 lumpy-release/R29-4279.0.0/autotest/packages" claims to have the 579 autotest package for lumpy-release/R29-4279.0.0. If this isn't the case, 580 download and extract it. If the devserver embedded in the url is 581 unresponsive, update the job_repo_url of the host after staging it on 582 another devserver. 583 584 @param job_repo_url: A url pointing to the devserver where the autotest 585 package for this build should be staged. 586 @param tag: The tag from the server job, in the format 587 <job_id>-<user>/<hostname>, or <hostless> for a server job. 588 589 @raises DevServerException: If we could not resolve a devserver. 590 @raises AutoservError: If we're unable to save the new job_repo_url as 591 a result of choosing a new devserver because the old one failed to 592 respond to a health check. 593 @raises urllib2.URLError: If the devserver embedded in job_repo_url 594 doesn't respond within the timeout. 595 """ 596 info = self.host_info_store.get() 597 job_repo_url = info.attributes.get(ds_constants.JOB_REPO_URL, '') 598 if not job_repo_url: 599 logging.warning('No job repo url set on host %s', self.hostname) 600 return 601 602 logging.info('Verifying job repo url %s', job_repo_url) 603 devserver_url, image_name = tools.get_devserver_build_from_package_url( 604 job_repo_url) 605 606 ds = dev_server.ImageServer(devserver_url) 607 608 logging.info('Staging autotest artifacts for %s on devserver %s', 609 image_name, ds.url()) 610 611 start_time = time.time() 612 ds.stage_artifacts(image_name, ['autotest_packages']) 613 stage_time = time.time() - start_time 614 615 # Record how much of the verification time comes from a devserver 616 # restage. If we're doing things right we should not see multiple 617 # devservers for a given board/build/branch path. 618 try: 619 board, build_type, branch = server_utils.ParseBuildName( 620 image_name)[:3] 621 except server_utils.ParseBuildNameException: 622 pass 623 else: 624 devserver = devserver_url[ 625 devserver_url.find('/') + 2:devserver_url.rfind(':')] 626 stats_key = { 627 'board': board, 628 'build_type': build_type, 629 'branch': branch, 630 'devserver': devserver.replace('.', '_'), 631 } 632 633 monarch_fields = { 634 'board': board, 635 'build_type': build_type, 636 'branch': branch, 637 'dev_server': devserver, 638 } 639 metrics.Counter( 640 'chromeos/autotest/provision/verify_url' 641 ).increment(fields=monarch_fields) 642 metrics.SecondsDistribution( 643 'chromeos/autotest/provision/verify_url_duration' 644 ).add(stage_time, fields=monarch_fields) 645 646 647 def stage_server_side_package(self, image=None): 648 """Stage autotest server-side package on devserver. 649 650 @param image: Full path of an OS image to install or a build name. 651 652 @return: A url to the autotest server-side package. 653 654 @raise: error.AutoservError if fail to locate the build to test with, or 655 fail to stage server-side package. 656 """ 657 # If enable_drone_in_restricted_subnet is False, do not set hostname 658 # in devserver.resolve call, so a devserver in non-restricted subnet 659 # is picked to stage autotest server package for drone to download. 660 hostname = self.hostname 661 if not server_utils.ENABLE_DRONE_IN_RESTRICTED_SUBNET: 662 hostname = None 663 if image: 664 image_name = tools.get_build_from_image(image) 665 if not image_name: 666 raise error.AutoservError( 667 'Failed to parse build name from %s' % image) 668 ds = dev_server.ImageServer.resolve(image_name, hostname) 669 else: 670 info = self.host_info_store.get() 671 job_repo_url = info.attributes.get(ds_constants.JOB_REPO_URL, '') 672 if job_repo_url: 673 devserver_url, image_name = ( 674 tools.get_devserver_build_from_package_url(job_repo_url)) 675 # If enable_drone_in_restricted_subnet is True, use the 676 # existing devserver. Otherwise, resolve a new one in 677 # non-restricted subnet. 678 if server_utils.ENABLE_DRONE_IN_RESTRICTED_SUBNET: 679 ds = dev_server.ImageServer(devserver_url) 680 else: 681 ds = dev_server.ImageServer.resolve(image_name) 682 elif info.build is not None: 683 ds = dev_server.ImageServer.resolve(info.build, hostname) 684 image_name = info.build 685 else: 686 raise error.AutoservError( 687 'Failed to stage server-side package. The host has ' 688 'no job_repo_url attribute or cros-version label.') 689 690 # Get the OS version of the build, for any build older than 691 # MIN_VERSION_SUPPORT_SSP, server side packaging is not supported. 692 match = re.match('.*/R\d+-(\d+)\.', image_name) 693 if match and int(match.group(1)) < self.MIN_VERSION_SUPPORT_SSP: 694 raise error.AutoservError( 695 'Build %s is older than %s. Server side packaging is ' 696 'disabled.' % (image_name, self.MIN_VERSION_SUPPORT_SSP)) 697 698 ds.stage_artifacts(image_name, ['autotest_server_package']) 699 return '%s/static/%s/%s' % (ds.url(), image_name, 700 'autotest_server_package.tar.bz2') 701 702 703 def stage_image_for_servo(self, image_name=None, artifact='test_image'): 704 """Stage a build on a devserver and return the update_url. 705 706 @param image_name: a name like lumpy-release/R27-3837.0.0 707 @param artifact: a string like 'test_image'. Requests 708 appropriate image to be staged. 709 @returns a tuple of (image_name, URL) like 710 (lumpy-release/R27-3837.0.0, 711 http://172.22.50.205:8082/update/lumpy-release/R27-3837.0.0) 712 """ 713 if not image_name: 714 image_name = self.get_cros_repair_image_name() 715 logging.info('Staging build for servo install: %s', image_name) 716 devserver = dev_server.ImageServer.resolve(image_name, self.hostname) 717 devserver.stage_artifacts(image_name, [artifact]) 718 if artifact == 'test_image': 719 return image_name, devserver.get_test_image_url(image_name) 720 elif artifact == 'recovery_image': 721 return image_name, devserver.get_recovery_image_url(image_name) 722 else: 723 raise error.AutoservError("Bad artifact!") 724 725 726 def stage_factory_image_for_servo(self, image_name): 727 """Stage a build on a devserver and return the update_url. 728 729 @param image_name: a name like <baord>/4262.204.0 730 731 @return: An update URL, eg: 732 http://<devserver>/static/canary-channel/\ 733 <board>/4262.204.0/factory_test/chromiumos_factory_image.bin 734 735 @raises: ValueError if the factory artifact name is missing from 736 the config. 737 738 """ 739 if not image_name: 740 logging.error('Need an image_name to stage a factory image.') 741 return 742 743 factory_artifact = CONFIG.get_config_value( 744 'CROS', 'factory_artifact', type=str, default='') 745 if not factory_artifact: 746 raise ValueError('Cannot retrieve the factory artifact name from ' 747 'autotest config, and hence cannot stage factory ' 748 'artifacts.') 749 750 logging.info('Staging build for servo install: %s', image_name) 751 devserver = dev_server.ImageServer.resolve(image_name, self.hostname) 752 devserver.stage_artifacts( 753 image_name, 754 [factory_artifact], 755 archive_url=None) 756 757 return tools.factory_image_url_pattern() % (devserver.url(), image_name) 758 759 760 def prepare_for_update(self): 761 """Prepares the DUT for an update. 762 763 Subclasses may override this to perform any special actions 764 required before updating. 765 """ 766 pass 767 768 769 def _clear_fw_version_labels(self, rw_only): 770 """Clear firmware version labels from the machine. 771 772 @param rw_only: True to only clear fwrw_version; otherewise, clear 773 both fwro_version and fwrw_version. 774 """ 775 info = self.host_info_store.get() 776 info.clear_version_labels(provision.FW_RW_VERSION_PREFIX) 777 if not rw_only: 778 info.clear_version_labels(provision.FW_RO_VERSION_PREFIX) 779 self.host_info_store.commit(info) 780 781 782 def _add_fw_version_label(self, build, rw_only): 783 """Add firmware version label to the machine. 784 785 @param build: Build of firmware. 786 @param rw_only: True to only add fwrw_version; otherwise, add both 787 fwro_version and fwrw_version. 788 789 """ 790 info = self.host_info_store.get() 791 info.set_version_label(provision.FW_RW_VERSION_PREFIX, build) 792 if not rw_only: 793 info.set_version_label(provision.FW_RO_VERSION_PREFIX, build) 794 self.host_info_store.commit(info) 795 796 797 def get_latest_release_version(self, platform, ref_board=None): 798 """Search for the latest package release version from the image archive, 799 and return it. 800 801 @param platform: platform name, a.k.a. board or model 802 @param ref_board: reference board name, a.k.a. baseboard, parent 803 804 @return 'firmware-{platform}-{branch}-firmwarebranch/{release-version}/' 805 '{platform}' 806 or None if LATEST release file does not exist. 807 """ 808 809 platforms = [ platform ] 810 811 # Search the image path in reference board archive as well. 812 # For example, bob has its binary image under its reference board (gru) 813 # image archive. 814 if ref_board: 815 platforms.append(ref_board) 816 817 for board in platforms: 818 # Read 'LATEST-1.0.0' file 819 branch_dir = provision.FW_BRANCH_GLOB % board 820 latest_file = os.path.join(provision.CROS_IMAGE_ARCHIVE, branch_dir, 821 'LATEST-1.0.0') 822 823 try: 824 # The result could be one or more. 825 result = utils.system_output('gsutil ls -d ' + latest_file) 826 827 candidates = re.findall('gs://.*', result) 828 829 # Found the directory candidates. No need to check the other 830 # board name cadidates. Let's break the loop. 831 break 832 except error.CmdError: 833 # It doesn't exist. Let's move on to the next item. 834 pass 835 else: 836 logging.error('No LATEST release info is available.') 837 return None 838 839 for cand_dir in candidates: 840 result = utils.system_output('gsutil cat ' + cand_dir) 841 842 release_path = cand_dir.replace('LATEST-1.0.0', result) 843 release_path = os.path.join(release_path, platform) 844 try: 845 # Check if release_path does exist. 846 release = utils.system_output('gsutil ls -d ' + release_path) 847 # Now 'release' has a full directory path: e.g. 848 # gs://chromeos-image-archive/firmware-octopus-11297.B- 849 # firmwarebranch/RNone-1.0.0-b4395530/octopus/ 850 851 # Remove "gs://chromeos-image-archive". 852 release = release.replace(provision.CROS_IMAGE_ARCHIVE, '') 853 854 # Remove CROS_IMAGE_ARCHIVE and any surrounding '/'s. 855 return release.strip('/') 856 except error.CmdError: 857 # The directory might not exist. Let's try next candidate. 858 pass 859 else: 860 raise error.AutoservError('Cannot find the latest firmware') 861 862 @staticmethod 863 def get_version_from_image(host, bios_image, ec_image): 864 """Get version string from binary image using regular expression. 865 866 @param host: An instance of hosts.Host. 867 @param bios_image: Filename of AP BIOS image on the DUT/labstation. 868 @param ec_image: Filename of EC image on the DUT/labstation. 869 870 @return Tuple of bios version and ec version 871 """ 872 if not host: 873 return None 874 cmd_args = ['futility', 'update', '--manifest'] 875 if bios_image: 876 cmd_args.append('-i') 877 cmd_args.append(bios_image) 878 if ec_image: 879 cmd_args.append('-e') 880 cmd_args.append(ec_image) 881 cmd = ' '.join([utils.sh_quote_word(arg) for arg in cmd_args]) 882 stdout = host.run(cmd).stdout 883 if not isinstance(stdout, six.text_type): 884 stdout = stdout.decode('utf-8') 885 io = StringIO(stdout) 886 data = json.load(io) 887 return ( 888 data.get('default', {}).get('host', {}).get('versions', 889 {}).get('rw'), 890 data.get('default', {}).get('ec', {}).get('versions', 891 {}).get('rw'), 892 ) 893 894 895 def firmware_install(self, 896 build, 897 rw_only=False, 898 dest=None, 899 local_tarball=None, 900 verify_version=False, 901 try_scp=False, 902 install_ec=True, 903 install_bios=True, 904 corrupt_ec=False): 905 """Install firmware to the DUT. 906 907 Use stateful update if the DUT is already running the same build. 908 Stateful update does not update kernel and tends to run much faster 909 than a full reimage. If the DUT is running a different build, or it 910 failed to do a stateful update, full update, including kernel update, 911 will be applied to the DUT. 912 913 Once a host enters firmware_install its fw[ro|rw]_version label will 914 be removed. After the firmware is updated successfully, a new 915 fw[ro|rw]_version label will be added to the host. 916 917 @param build: The build version to which we want to provision the 918 firmware of the machine, 919 e.g. 'link-firmware/R22-2695.1.144'. 920 @param rw_only: True to only install firmware to its RW portions. Keep 921 the RO portions unchanged. 922 @param dest: Directory to store the firmware in. 923 @param local_tarball: Path to local firmware image for installing 924 without devserver. 925 @param verify_version: True to verify EC and BIOS versions after 926 programming firmware, default is False. 927 @param try_scp: False to always program using servo, true to try copying 928 the firmware and programming from the DUT. 929 @param install_ec: True to install EC FW, and False to skip it. 930 @param install_bios: True to install BIOS, and False to skip it. 931 @param corrupt_ec: True to flash EC with a false image (for test purpose). 932 933 TODO(dshi): After bug 381718 is fixed, update here with corresponding 934 exceptions that could be raised. 935 936 """ 937 if not self.servo: 938 raise error.TestError('Host %s does not have servo.' % 939 self.hostname) 940 941 # Get the DUT board name from AFE. 942 info = self.host_info_store.get() 943 board = info.board 944 model = info.model 945 946 if board is None or board == '': 947 board = self.servo.get_board() 948 949 if model is None or model == '': 950 try: 951 model = self.get_platform() 952 except Exception as e: 953 logging.warning('Dut is unresponsive: %s', str(e)) 954 955 # If local firmware path not provided fetch it from the dev server 956 tmpd = None 957 if not local_tarball: 958 logging.info('Will install firmware from build %s.', build) 959 960 try: 961 ds = dev_server.ImageServer.resolve(build, self.hostname) 962 ds.stage_artifacts(build, ['firmware']) 963 964 if not dest: 965 tmpd = autotemp.tempdir(unique_id='fwimage') 966 dest = tmpd.name 967 968 # Download firmware image 969 fwurl = self._FW_IMAGE_URL_PATTERN % (ds.url(), build) 970 local_tarball = os.path.join(dest, os.path.basename(fwurl)) 971 logging.info('Downloading file from %s to %s.', fwurl, 972 local_tarball) 973 ds.download_file(fwurl, 974 local_tarball, 975 timeout=self.DEVSERVER_DOWNLOAD_TIMEOUT) 976 logging.info('Done downloading') 977 except Exception as e: 978 raise error.TestError('Failed to download firmware package: %s' 979 % str(e)) 980 981 ec_image = None 982 if install_ec: 983 # Extract EC image from tarball 984 logging.info('Extracting EC image.') 985 ec_image = self.servo.extract_ec_image(board, model, local_tarball, 986 corrupt_ec) 987 logging.info('Extracted: %s', ec_image) 988 989 bios_image = None 990 if install_bios: 991 # Extract BIOS image from tarball 992 logging.info('Extracting BIOS image.') 993 bios_image = self.servo.extract_bios_image(board, model, 994 local_tarball) 995 logging.info('Extracted: %s', bios_image) 996 997 if not bios_image and not ec_image: 998 raise error.TestError('No firmware installation was processed.') 999 1000 # Clear firmware version labels 1001 self._clear_fw_version_labels(rw_only) 1002 1003 # Install firmware from local tarball 1004 try: 1005 image_ec_version = None 1006 image_bios_version = None 1007 1008 # Check if copying to DUT is enabled and DUT is available 1009 if try_scp and self.is_up(): 1010 # DUT is available, make temp firmware directory to store images 1011 logging.info('Making temp folder.') 1012 dest_folder = '/tmp/firmware' 1013 self.run('mkdir -p ' + dest_folder) 1014 dest_bios_path = None 1015 dest_ec_path = None 1016 1017 fw_cmd = self._FW_UPDATE_CMD % ('--wp=1' if rw_only else '') 1018 1019 if bios_image: 1020 # Send BIOS firmware image to DUT 1021 logging.info('Sending BIOS firmware.') 1022 dest_bios_path = os.path.join(dest_folder, 1023 os.path.basename(bios_image)) 1024 self.send_file(bios_image, dest_bios_path) 1025 1026 # Initialize firmware update command for BIOS image 1027 fw_cmd += ' -i %s' % dest_bios_path 1028 1029 # Send EC firmware image to DUT when EC image was found 1030 if ec_image: 1031 logging.info('Sending EC firmware.') 1032 dest_ec_path = os.path.join(dest_folder, 1033 os.path.basename(ec_image)) 1034 self.send_file(ec_image, dest_ec_path) 1035 1036 # Add EC image to firmware update command 1037 fw_cmd += ' -e %s' % dest_ec_path 1038 1039 # Make sure command is allowed to finish even if ssh fails. 1040 fw_cmd = "trap '' SIGHUP; %s" % fw_cmd 1041 1042 # Update firmware on DUT 1043 logging.info('Updating firmware.') 1044 try: 1045 self.run(fw_cmd, options="-o LogLevel=verbose") 1046 except error.AutoservRunError as e: 1047 if e.result_obj.exit_status != 255: 1048 raise 1049 elif ec_image: 1050 logging.warning("DUT network dropped during update" 1051 " (often caused by EC resetting USB)") 1052 else: 1053 logging.error("DUT network dropped during update" 1054 " (unexpected, since no EC image)") 1055 raise 1056 image_bios_version, image_ec_version = self.get_version_from_image( 1057 self, dest_bios_path, dest_ec_path) 1058 else: 1059 # Host is not available, program firmware using servo 1060 dest_bios_path = None 1061 dest_ec_path = None 1062 if ec_image: 1063 dest_ec_path = self.servo.program_ec(ec_image, rw_only) 1064 if bios_image: 1065 dest_bios_path = self.servo.program_bios( 1066 bios_image, rw_only) 1067 if utils.host_is_in_lab_zone(self.hostname): 1068 self._add_fw_version_label(build, rw_only) 1069 image_bios_version, image_ec_version = self.get_version_from_image( 1070 self._servo_host, dest_bios_path, dest_ec_path) 1071 1072 # Reboot and wait for DUT after installing firmware 1073 logging.info('Rebooting DUT.') 1074 self.servo.get_power_state_controller().reset() 1075 time.sleep(self.servo.BOOT_DELAY) 1076 self.test_wait_for_boot() 1077 1078 # When enabled verify EC and BIOS firmware version after programming 1079 if verify_version: 1080 # Check programmed EC firmware when EC image was found 1081 if ec_image: 1082 logging.info('Checking EC firmware version.') 1083 if image_ec_version is None: 1084 raise error.TestFail( 1085 'Could not find EC version in %s' % ec_image) 1086 dest_ec_version = self.get_ec_version() 1087 if dest_ec_version != image_ec_version: 1088 raise error.TestFail( 1089 'Failed to update EC firmware, version %s ' 1090 '(expected %s)' % (dest_ec_version, 1091 image_ec_version)) 1092 1093 if bios_image: 1094 # Check programmed BIOS firmware against expected version 1095 logging.info('Checking BIOS firmware version.') 1096 if image_bios_version is None: 1097 raise error.TestFail( 1098 'Could not find BIOS version in %s' % 1099 bios_image) 1100 dest_bios_version = self.get_firmware_version() 1101 if dest_bios_version != image_bios_version: 1102 raise error.TestFail( 1103 'Failed to update BIOS, version %s ' 1104 '(expected %s)' % (dest_bios_version, 1105 image_bios_version)) 1106 finally: 1107 if tmpd: 1108 tmpd.clean() 1109 1110 1111 def install_image_to_servo_usb(self, image_url=None): 1112 """Installing a test image on a USB storage device. 1113 1114 Download image to USB-storage attached to the Servo board. 1115 1116 @param image_url: If specified use as the url to download to 1117 USB-storage. 1118 1119 @raises AutoservError if the image fails to download. 1120 1121 """ 1122 if not image_url: 1123 logging.debug('Skip download as image_url not provided!') 1124 return 1125 1126 logging.info('Downloading image to USB') 1127 metrics_field = {'download': bool(image_url)} 1128 metrics.Counter( 1129 'chromeos/autotest/provision/servo_install/download_image' 1130 ).increment(fields=metrics_field) 1131 with metrics.SecondsTimer( 1132 'chromeos/autotest/servo_install/download_image_time'): 1133 try: 1134 self.servo.image_to_servo_usb(image_path=image_url, 1135 power_off_dut=False) 1136 except error.AutotestError as e: 1137 metrics.Counter('chromeos/autotest/repair/image_to_usb_error' 1138 ).increment( 1139 fields={'host': self.hostname or ''}) 1140 six.reraise(error.AutotestError, str(e), sys.exc_info()[2]) 1141 1142 def boot_in_recovery_mode(self, 1143 usb_boot_timeout=USB_BOOT_TIMEOUT, 1144 need_snk=False): 1145 """Booting host in recovery mode. 1146 1147 Boot device in recovery mode and verify that device booted from 1148 external storage as expected. 1149 1150 @param usb_boot_timeout: The usb_boot_timeout to use wait the host 1151 to boot. Factory images need a longer 1152 usb_boot_timeout than regular cros images. 1153 @param snk_mode: If True, switch servo_v4 role to 'snk' 1154 mode before boot DUT into recovery mode. 1155 1156 @raises AutoservError if the image fails to boot. 1157 1158 """ 1159 logging.info('Booting from USB directly. Usb boot timeout: %s', 1160 usb_boot_timeout) 1161 with metrics.SecondsTimer( 1162 'chromeos/autotest/provision/servo_install/boot_duration'): 1163 self.servo.boot_in_recovery_mode(snk_mode=need_snk) 1164 if not self.wait_up(timeout=usb_boot_timeout): 1165 if need_snk: 1166 # Attempt to restore servo_v4 role to 'src' mode. 1167 self.servo.set_servo_v4_role('src') 1168 raise hosts.AutoservRepairError( 1169 'DUT failed to boot from USB after %d seconds' % 1170 usb_boot_timeout, 'failed_to_boot_pre_install') 1171 1172 # Make sure the DUT is boot from an external device. 1173 if not self.is_boot_from_external_device(): 1174 raise hosts.AutoservRepairError( 1175 'DUT is expected to boot from an external device(e.g. ' 1176 'a usb stick), however it seems still boot from an' 1177 ' internal storage.', 'boot_from_internal_storage') 1178 1179 def run_install_image(self, 1180 install_timeout=INSTALL_TIMEOUT, 1181 need_snk=False, 1182 is_repair=False): 1183 """Installing the image with chromeos-install. 1184 1185 Steps included: 1186 1) Recover TPM on the device 1187 2) Run chromeos-install 1188 2.a) if success: power off/on the device 1189 2.b) if fail: 1190 2.b.1) Mark for replacement if fail with hardware issue 1191 2.b.2) Run internal storage check. (Only if is_repair=True) 1192 3) Wait the device to boot as verifier of success install 1193 1194 Device has to booted from external storage. 1195 1196 @param install_timeout: The timeout to use when installing the 1197 chromeos image. Factory images need a 1198 longer install_timeout. 1199 @param snk_mode: If True, switch servo_v4 role to 'snk' 1200 mode before boot DUT into recovery mode. 1201 @param is_repair: Indicates if the method is called from a 1202 repair task. 1203 1204 @raises AutoservError if the fail in process of install image. 1205 @raises AutoservRepairError if fail to boot after install image. 1206 1207 """ 1208 # The new chromeos-tpm-recovery has been merged since R44-7073.0.0. 1209 # In old CrOS images, this command fails. Skip the error. 1210 logging.info('Resetting the TPM status') 1211 try: 1212 self.run('chromeos-tpm-recovery') 1213 except error.AutoservRunError: 1214 logging.warning('chromeos-tpm-recovery is too old.') 1215 1216 with metrics.SecondsTimer( 1217 'chromeos/autotest/provision/servo_install/install_duration'): 1218 logging.info('Installing image through chromeos-install.') 1219 try: 1220 self.run('chromeos-install --yes',timeout=install_timeout) 1221 self.halt() 1222 except Exception as e: 1223 storage_errors = [ 1224 'No space left on device', 1225 'I/O error when trying to write primary GPT', 1226 'Input/output error while writing out', 1227 'cannot read GPT header', 1228 'can not determine destination device', 1229 'wrong fs type', 1230 'bad superblock on', 1231 ] 1232 has_error = [msg for msg in storage_errors if(msg in str(e))] 1233 if has_error: 1234 info = self.host_info_store.get() 1235 info.set_version_label( 1236 audit_const.DUT_STORAGE_STATE_PREFIX, 1237 audit_const.HW_STATE_NEED_REPLACEMENT) 1238 self.host_info_store.commit(info) 1239 self.set_device_repair_state( 1240 cros_constants.DEVICE_STATE_NEEDS_REPLACEMENT) 1241 logging.debug( 1242 'Fail install image from USB; Storage error; %s', e) 1243 raise error.AutoservError( 1244 'Failed to install image from USB due to a suspect ' 1245 'disk failure, DUT storage state changed to ' 1246 'need_replacement, please check debug log ' 1247 'for details.') 1248 else: 1249 if is_repair: 1250 # DUT will be marked for replacement if storage is bad. 1251 audit_verify.VerifyDutStorage(self).verify() 1252 1253 logging.debug('Fail install image from USB; %s', e) 1254 raise error.AutoservError( 1255 'Failed to install image from USB due to unexpected ' 1256 'error, please check debug log for details.') 1257 finally: 1258 # We need reset the DUT no matter re-install success or not, 1259 # as we don't want leave the DUT in boot from usb state. 1260 logging.info('Power cycling DUT through servo.') 1261 self.servo.get_power_state_controller().power_off() 1262 self.servo.switch_usbkey('off') 1263 if need_snk: 1264 # Attempt to restore servo_v4 role to 'src' mode. 1265 self.servo.set_servo_v4_role('src') 1266 # N.B. The Servo API requires that we use power_on() here 1267 # for two reasons: 1268 # 1) After turning on a DUT in recovery mode, you must turn 1269 # it off and then on with power_on() once more to 1270 # disable recovery mode (this is a Parrot specific 1271 # requirement). 1272 # 2) After power_off(), the only way to turn on is with 1273 # power_on() (this is a Storm specific requirement). 1274 self.servo.get_power_state_controller().power_on() 1275 1276 logging.info('Waiting for DUT to come back up.') 1277 if not self.wait_up(timeout=self.BOOT_TIMEOUT): 1278 raise hosts.AutoservRepairError('DUT failed to reboot installed ' 1279 'test image after %d seconds' % 1280 self.BOOT_TIMEOUT, 1281 'failed_to_boot_post_install') 1282 1283 def servo_install(self, 1284 image_url=None, 1285 usb_boot_timeout=USB_BOOT_TIMEOUT, 1286 install_timeout=INSTALL_TIMEOUT, 1287 is_repair=False): 1288 """Re-install the OS on the DUT by: 1289 1290 Steps: 1291 1) Power off the host 1292 2) Installing an image on a USB-storage attached to the Servo board 1293 3) Booting that image in recovery mode 1294 4) Installing the image with chromeos-install. 1295 1296 @param image_url: If specified use as the url to install on 1297 the DUT otherwise boot the currently 1298 staged image on the USB stick. 1299 @param usb_boot_timeout: The usb_boot_timeout to use during 1300 re-image. Factory images need a longer 1301 usb_boot_timeout than regular cros images. 1302 @param install_timeout: The timeout to use when installing the 1303 chromeos image. Factory images need a 1304 longer install_timeout. 1305 @param is_repair: Indicates if the method is called from a 1306 repair task. 1307 1308 @raises AutoservError if the image fails to boot. 1309 1310 """ 1311 self.servo.get_power_state_controller().power_off() 1312 if image_url: 1313 self.install_image_to_servo_usb(image_url=image_url) 1314 else: 1315 # Give the DUT some time to power_off if we skip 1316 # download image to usb. (crbug.com/982993) 1317 time.sleep(10) 1318 1319 need_snk = self.require_snk_mode_in_recovery() 1320 1321 self.boot_in_recovery_mode(usb_boot_timeout=usb_boot_timeout, 1322 need_snk=need_snk) 1323 1324 self.run_install_image(install_timeout=install_timeout, 1325 need_snk=need_snk, 1326 is_repair=is_repair) 1327 1328 def set_servo_host(self, host, servo_state=None): 1329 """Set our servo host member, and associated servo. 1330 1331 @param host Our new `ServoHost`. 1332 """ 1333 self._servo_host = host 1334 self.servo_pwr_supported = None 1335 if self._servo_host is not None: 1336 self.servo = self._servo_host.get_servo() 1337 servo_state = self._servo_host.get_servo_state() 1338 self._set_smart_usbhub_label(self._servo_host.smart_usbhub) 1339 try: 1340 self.servo_pwr_supported = self.servo.has_control('power_state') 1341 except Exception as e: 1342 logging.debug( 1343 "Could not get servo power state due to {}".format(e)) 1344 else: 1345 self.servo = None 1346 self.servo_pwr_supported = False 1347 self.set_servo_type() 1348 self.set_servo_state(servo_state) 1349 self._set_servo_topology() 1350 1351 1352 def repair_servo(self): 1353 """ 1354 Confirm that servo is initialized and verified. 1355 1356 If the servo object is missing, attempt to repair the servo 1357 host. Repair failures are passed back to the caller. 1358 1359 @raise AutoservError: If there is no servo host for this CrOS 1360 host. 1361 """ 1362 if self.servo: 1363 return 1364 if not self._servo_host: 1365 raise error.AutoservError('No servo host for %s.' % 1366 self.hostname) 1367 try: 1368 self._servo_host.repair() 1369 except: 1370 raise 1371 finally: 1372 self.set_servo_host(self._servo_host) 1373 1374 1375 def set_servo_type(self): 1376 """Set servo info labels to dut host_info""" 1377 if not self.servo: 1378 logging.debug('Servo is not initialized to get servo_type.') 1379 return 1380 if not self.is_servo_in_working_state(): 1381 logging.debug('Servo is not good, skip update servo_type.') 1382 return 1383 servo_type = self.servo.get_servo_type() 1384 if not servo_type: 1385 logging.debug('Cannot collect servo_type from servo' 1386 ' by `dut-control servo_type`! Please file a bug' 1387 ' and inform infra team as we are not expected ' 1388 ' to reach this point.') 1389 return 1390 host_info = self.host_info_store.get() 1391 prefix = servo_constants.SERVO_TYPE_LABEL_PREFIX 1392 old_type = host_info.get_label_value(prefix) 1393 if old_type == servo_type: 1394 # do not need update 1395 return 1396 host_info.set_version_label(prefix, servo_type) 1397 self.host_info_store.commit(host_info) 1398 logging.info('ServoHost: servo_type updated to %s ' 1399 '(previous: %s)', servo_type, old_type) 1400 1401 1402 def set_servo_state(self, servo_state): 1403 """Set servo info labels to dut host_info""" 1404 if servo_state is not None: 1405 host_info = self.host_info_store.get() 1406 servo_state_prefix = servo_constants.SERVO_STATE_LABEL_PREFIX 1407 old_state = host_info.get_label_value(servo_state_prefix) 1408 if old_state == servo_state: 1409 # do not need update 1410 return 1411 host_info.set_version_label(servo_state_prefix, servo_state) 1412 self.host_info_store.commit(host_info) 1413 logging.info('ServoHost: servo_state updated to %s (previous: %s)', 1414 servo_state, old_state) 1415 1416 1417 def get_servo_state(self): 1418 host_info = self.host_info_store.get() 1419 servo_state_prefix = servo_constants.SERVO_STATE_LABEL_PREFIX 1420 return host_info.get_label_value(servo_state_prefix) 1421 1422 def is_servo_in_working_state(self): 1423 """Validate servo is in WORKING state.""" 1424 servo_state = self.get_servo_state() 1425 return servo_state == servo_constants.SERVO_STATE_WORKING 1426 1427 def get_servo_usb_state(self): 1428 """Get the label value indicating the health of the USB drive. 1429 1430 @return: The label value if defined, otherwise '' (empty string). 1431 @rtype: str 1432 """ 1433 host_info = self.host_info_store.get() 1434 servo_usb_state_prefix = audit_const.SERVO_USB_STATE_PREFIX 1435 return host_info.get_label_value(servo_usb_state_prefix) 1436 1437 def is_servo_usb_usable(self): 1438 """Check if the servo USB storage device is usable for FAFT. 1439 1440 @return: False if the label indicates a state that will break FAFT. 1441 True if state is okay, or if state is not defined. 1442 @rtype: bool 1443 """ 1444 usb_state = self.get_servo_usb_state() 1445 return usb_state in ('', audit_const.HW_STATE_ACCEPTABLE, 1446 audit_const.HW_STATE_NORMAL, 1447 audit_const.HW_STATE_UNKNOWN) 1448 1449 def _set_smart_usbhub_label(self, smart_usbhub_detected): 1450 if smart_usbhub_detected is None: 1451 # skip the label update here as this indicate we wasn't able 1452 # to confirm usbhub type. 1453 return 1454 host_info = self.host_info_store.get() 1455 if (smart_usbhub_detected == 1456 (servo_constants.SMART_USBHUB_LABEL in host_info.labels)): 1457 # skip label update if current label match the truth. 1458 return 1459 if smart_usbhub_detected: 1460 logging.info('Adding %s label to host %s', 1461 servo_constants.SMART_USBHUB_LABEL, 1462 self.hostname) 1463 host_info.labels.append(servo_constants.SMART_USBHUB_LABEL) 1464 else: 1465 logging.info('Removing %s label from host %s', 1466 servo_constants.SMART_USBHUB_LABEL, 1467 self.hostname) 1468 host_info.labels.remove(servo_constants.SMART_USBHUB_LABEL) 1469 self.host_info_store.commit(host_info) 1470 1471 def repair(self): 1472 """Attempt to get the DUT to pass `self.verify()`. 1473 1474 This overrides the base class function for repair; it does 1475 not call back to the parent class, but instead relies on 1476 `self._repair_strategy` to coordinate the verification and 1477 repair steps needed to get the DUT working. 1478 """ 1479 message = 'Beginning repair for host %s board %s model %s' 1480 info = self.host_info_store.get() 1481 message %= (self.hostname, info.board, info.model) 1482 self.record('INFO', None, None, message) 1483 profile_state = profile_constants.DUT_STATE_READY 1484 # Initialize bluetooth peers 1485 self.initialize_btpeer() 1486 try: 1487 self._repair_strategy.repair(self) 1488 except hosts.AutoservVerifyDependencyError as e: 1489 # TODO(otabek): remove when finish b/174191325 1490 self._stat_if_pingable_but_not_sshable() 1491 # We don't want flag a DUT as failed if only non-critical 1492 # verifier(s) failed during the repair. 1493 if e.is_critical(): 1494 profile_state = profile_constants.DUT_STATE_REPAIR_FAILED 1495 self._reboot_labstation_if_needed() 1496 self.try_set_device_needs_manual_repair() 1497 raise 1498 finally: 1499 self.set_health_profile_dut_state(profile_state) 1500 1501 def get_verifier_state(self, tag): 1502 """Return the state of host verifier by tag. 1503 1504 @returns: bool or None 1505 """ 1506 return self._repair_strategy.verifier_is_good(tag) 1507 1508 def get_repair_strategy_node(self, tag): 1509 """Return the instance of verifier/repair node for host by tag. 1510 1511 @returns: _DependencyNode or None 1512 """ 1513 return self._repair_strategy.node_by_tag(tag) 1514 1515 def close(self): 1516 """Close connection.""" 1517 super(CrosHost, self).close() 1518 1519 if self._chameleon_host: 1520 self._chameleon_host.close() 1521 1522 if self.health_profile: 1523 try: 1524 self.health_profile.close() 1525 except Exception as e: 1526 logging.warning( 1527 'Failed to finalize device health profile; %s', e) 1528 1529 if self._servo_host: 1530 self._servo_host.close() 1531 1532 def get_power_supply_info(self): 1533 """Get the output of power_supply_info. 1534 1535 power_supply_info outputs the info of each power supply, e.g., 1536 Device: Line Power 1537 online: no 1538 type: Mains 1539 voltage (V): 0 1540 current (A): 0 1541 Device: Battery 1542 state: Discharging 1543 percentage: 95.9276 1544 technology: Li-ion 1545 1546 Above output shows two devices, Line Power and Battery, with details of 1547 each device listed. This function parses the output into a dictionary, 1548 with key being the device name, and value being a dictionary of details 1549 of the device info. 1550 1551 @return: The dictionary of power_supply_info, e.g., 1552 {'Line Power': {'online': 'yes', 'type': 'main'}, 1553 'Battery': {'vendor': 'xyz', 'percentage': '100'}} 1554 @raise error.AutoservRunError if power_supply_info tool is not found in 1555 the DUT. Caller should handle this error to avoid false failure 1556 on verification. 1557 """ 1558 result = self.run('power_supply_info').stdout.strip() 1559 info = {} 1560 device_name = None 1561 device_info = {} 1562 for line in result.split('\n'): 1563 pair = [v.strip() for v in line.split(':')] 1564 if len(pair) != 2: 1565 continue 1566 if pair[0] == 'Device': 1567 if device_name: 1568 info[device_name] = device_info 1569 device_name = pair[1] 1570 device_info = {} 1571 else: 1572 device_info[pair[0]] = pair[1] 1573 if device_name and not device_name in info: 1574 info[device_name] = device_info 1575 return info 1576 1577 1578 def get_battery_percentage(self): 1579 """Get the battery percentage. 1580 1581 @return: The percentage of battery level, value range from 0-100. Return 1582 None if the battery info cannot be retrieved. 1583 """ 1584 try: 1585 info = self.get_power_supply_info() 1586 logging.info(info) 1587 return float(info['Battery']['percentage']) 1588 except (KeyError, ValueError, error.AutoservRunError): 1589 return None 1590 1591 1592 def get_battery_state(self): 1593 """Get the battery charging state. 1594 1595 @return: A string representing the battery charging state. It can be 1596 'Charging', 'Fully charged', or 'Discharging'. 1597 """ 1598 try: 1599 info = self.get_power_supply_info() 1600 logging.info(info) 1601 return info['Battery']['state'] 1602 except (KeyError, ValueError, error.AutoservRunError): 1603 return None 1604 1605 1606 def get_battery_display_percentage(self): 1607 """Get the battery display percentage. 1608 1609 @return: The display percentage of battery level, value range from 1610 0-100. Return None if the battery info cannot be retrieved. 1611 """ 1612 try: 1613 info = self.get_power_supply_info() 1614 logging.info(info) 1615 return float(info['Battery']['display percentage']) 1616 except (KeyError, ValueError, error.AutoservRunError): 1617 return None 1618 1619 1620 def is_ac_connected(self): 1621 """Check if the dut has power adapter connected and charging. 1622 1623 @return: True if power adapter is connected and charging. 1624 """ 1625 try: 1626 info = self.get_power_supply_info() 1627 return info['Line Power']['online'] == 'yes' 1628 except (KeyError, error.AutoservRunError): 1629 return None 1630 1631 1632 def _cleanup_poweron(self): 1633 """Special cleanup method to make sure hosts always get power back.""" 1634 info = self.host_info_store.get() 1635 if self._RPM_OUTLET_CHANGED not in info.attributes: 1636 return 1637 logging.debug('This host has recently interacted with the RPM' 1638 ' Infrastructure. Ensuring power is on.') 1639 try: 1640 self.power_on() 1641 self._remove_rpm_changed_tag() 1642 except rpm_client.RemotePowerException: 1643 logging.error('Failed to turn Power On for this host after ' 1644 'cleanup through the RPM Infrastructure.') 1645 1646 battery_percentage = self.get_battery_percentage() 1647 if ( 1648 battery_percentage 1649 and battery_percentage < cros_constants.MIN_BATTERY_LEVEL): 1650 raise 1651 elif self.is_ac_connected(): 1652 logging.info('The device has power adapter connected and ' 1653 'charging. No need to try to turn RPM on ' 1654 'again.') 1655 self._remove_rpm_changed_tag() 1656 logging.info('Battery level is now at %s%%. The device may ' 1657 'still have enough power to run test, so no ' 1658 'exception will be raised.', battery_percentage) 1659 1660 1661 def _remove_rpm_changed_tag(self): 1662 info = self.host_info_store.get() 1663 del info.attributes[self._RPM_OUTLET_CHANGED] 1664 self.host_info_store.commit(info) 1665 1666 1667 def _add_rpm_changed_tag(self): 1668 info = self.host_info_store.get() 1669 info.attributes[self._RPM_OUTLET_CHANGED] = 'true' 1670 self.host_info_store.commit(info) 1671 1672 1673 1674 def _is_factory_image(self): 1675 """Checks if the image on the DUT is a factory image. 1676 1677 @return: True if the image on the DUT is a factory image. 1678 False otherwise. 1679 """ 1680 result = self.run('[ -f /root/.factory_test ]', ignore_status=True) 1681 return result.exit_status == 0 1682 1683 1684 def _restart_ui(self): 1685 """Restart the Chrome UI. 1686 1687 @raises: FactoryImageCheckerException for factory images, since 1688 we cannot attempt to restart ui on them. 1689 error.AutoservRunError for any other type of error that 1690 occurs while restarting ui. 1691 """ 1692 if self._is_factory_image(): 1693 raise FactoryImageCheckerException('Cannot restart ui on factory ' 1694 'images') 1695 1696 # TODO(jrbarnette): The command to stop/start the ui job 1697 # should live inside cros_ui, too. However that would seem 1698 # to imply interface changes to the existing start()/restart() 1699 # functions, which is a bridge too far (for now). 1700 prompt = cros_ui.get_chrome_session_ident(self) 1701 self.run('stop ui; start ui') 1702 cros_ui.wait_for_chrome_ready(prompt, self) 1703 1704 1705 def _start_powerd_if_needed(self): 1706 """Start powerd if it isn't already running.""" 1707 self.run('start powerd', ignore_status=True) 1708 1709 def _read_arc_prop_file(self, filename): 1710 for path in [ 1711 '/usr/share/arcvm/properties/', '/usr/share/arc/properties/' 1712 ]: 1713 if self.path_exists(path + filename): 1714 return utils.parse_cmd_output('cat ' + path + filename, 1715 run_method=self.run) 1716 return None 1717 1718 def _get_arc_build_info(self): 1719 """Returns a dictionary mapping build properties to their values.""" 1720 build_info = None 1721 for filename in ['build.prop', 'vendor_build.prop']: 1722 properties = self._read_arc_prop_file(filename) 1723 if properties: 1724 if build_info: 1725 build_info.update(properties) 1726 else: 1727 build_info = properties 1728 else: 1729 logging.error('Failed to find %s in device.', filename) 1730 return build_info 1731 1732 def has_arc_hardware_vulkan(self): 1733 """Returns a boolean whether device has hardware vulkan.""" 1734 return self._get_arc_build_info().get('ro.hardware.vulkan') 1735 1736 def get_arc_build_type(self): 1737 """Returns the ARC build type of the host.""" 1738 return self._get_arc_build_info().get('ro.build.type') 1739 1740 def get_arc_primary_abi(self): 1741 """Returns the primary abi of the host.""" 1742 return self._get_arc_build_info().get('ro.product.cpu.abi') 1743 1744 def get_arc_security_patch(self): 1745 """Returns the security patch of the host.""" 1746 return self._get_arc_build_info().get('ro.build.version.security_patch') 1747 1748 def get_arc_first_api_level(self): 1749 """Returns the security patch of the host.""" 1750 return self._get_arc_build_info().get('ro.product.first_api_level') 1751 1752 def _get_lsb_release_content(self): 1753 """Return the content of lsb-release file of host.""" 1754 return self.run( 1755 'cat "%s"' % client_constants.LSB_RELEASE).stdout.strip() 1756 1757 1758 def get_release_version(self): 1759 """Get the value of attribute CHROMEOS_RELEASE_VERSION from lsb-release. 1760 1761 @returns The version string in lsb-release, under attribute 1762 CHROMEOS_RELEASE_VERSION. 1763 """ 1764 return lsbrelease_utils.get_chromeos_release_version( 1765 lsb_release_content=self._get_lsb_release_content()) 1766 1767 1768 def get_release_builder_path(self): 1769 """Get the value of CHROMEOS_RELEASE_BUILDER_PATH from lsb-release. 1770 1771 @returns The version string in lsb-release, under attribute 1772 CHROMEOS_RELEASE_BUILDER_PATH. 1773 """ 1774 return lsbrelease_utils.get_chromeos_release_builder_path( 1775 lsb_release_content=self._get_lsb_release_content()) 1776 1777 1778 def get_chromeos_release_milestone(self): 1779 """Get the value of attribute CHROMEOS_RELEASE_BUILD_TYPE 1780 from lsb-release. 1781 1782 @returns The version string in lsb-release, under attribute 1783 CHROMEOS_RELEASE_BUILD_TYPE. 1784 """ 1785 return lsbrelease_utils.get_chromeos_release_milestone( 1786 lsb_release_content=self._get_lsb_release_content()) 1787 1788 1789 def verify_cros_version_label(self): 1790 """Verify if host's cros-version label match the actual image in dut. 1791 1792 @returns True if the label match with image in dut, otherwise False 1793 """ 1794 os_from_host = self.get_release_builder_path() 1795 info = self.host_info_store.get() 1796 os_from_label = info.get_label_value(self.VERSION_PREFIX) 1797 if not os_from_label: 1798 logging.debug('No existing %s label detected', self.VERSION_PREFIX) 1799 return True 1800 1801 # known cases where the version label will not match the 1802 # original CHROMEOS_RELEASE_BUILDER_PATH setting: 1803 # * Tests for the `arc-presubmit` append "-cheetsth" to the label. 1804 if os_from_label.endswith(provision.CHEETS_SUFFIX): 1805 logging.debug('%s label with %s suffix detected, this suffix will' 1806 ' be ignored when comparing label.', 1807 self.VERSION_PREFIX, provision.CHEETS_SUFFIX) 1808 os_from_label = os_from_label[:-len(provision.CHEETS_SUFFIX)] 1809 logging.debug('OS version from host: %s; OS verision cached in ' 1810 'label: %s', os_from_host, os_from_label) 1811 return os_from_label == os_from_host 1812 1813 1814 def cleanup_services(self): 1815 """Reinitializes the device for cleanup. 1816 1817 Subclasses may override this to customize the cleanup method. 1818 1819 To indicate failure of the reset, the implementation may raise 1820 any of: 1821 error.AutoservRunError 1822 error.AutotestRunError 1823 FactoryImageCheckerException 1824 1825 @raises error.AutoservRunError 1826 @raises error.AutotestRunError 1827 @raises error.FactoryImageCheckerException 1828 """ 1829 self._restart_ui() 1830 self._start_powerd_if_needed() 1831 1832 1833 def cleanup(self): 1834 """Cleanup state on device.""" 1835 self.run('rm -f %s' % client_constants.CLEANUP_LOGS_PAUSED_FILE) 1836 try: 1837 self.cleanup_services() 1838 except (error.AutotestRunError, error.AutoservRunError, 1839 FactoryImageCheckerException): 1840 logging.warning('Unable to restart ui.') 1841 1842 # cleanup routines, i.e. reboot the machine. 1843 super(CrosHost, self).cleanup() 1844 1845 # Check if the rpm outlet was manipulated. 1846 if self.has_power(): 1847 self._cleanup_poweron() 1848 1849 1850 def reboot(self, **dargs): 1851 """ 1852 This function reboots the site host. The more generic 1853 RemoteHost.reboot() performs sync and sleeps for 5 1854 seconds. This is not necessary for ChromeOS devices as the 1855 sync should be finished in a short time during the reboot 1856 command. 1857 """ 1858 if 'reboot_cmd' not in dargs: 1859 reboot_timeout = dargs.get('reboot_timeout', 10) 1860 dargs['reboot_cmd'] = ('sleep 1; ' 1861 'reboot & sleep %d; ' 1862 'reboot -f' % reboot_timeout) 1863 # Enable fastsync to avoid running extra sync commands before reboot. 1864 if 'fastsync' not in dargs: 1865 dargs['fastsync'] = True 1866 1867 dargs['board'] = self.host_info_store.get().board 1868 # Record who called us 1869 orig = sys._getframe(1).f_code 1870 metric_fields = {'board' : dargs['board'], 1871 'dut_host_name' : self.hostname, 1872 'success' : True} 1873 metric_debug_fields = {'board' : dargs['board'], 1874 'caller' : "%s:%s" % (orig.co_filename, 1875 orig.co_name), 1876 'success' : True, 1877 'error' : ''} 1878 1879 t0 = time.time() 1880 logging.debug('Pre reboot lsb-release {}'.format( 1881 self._get_lsb_release_content())) 1882 try: 1883 super(CrosHost, self).reboot(**dargs) 1884 except Exception as e: 1885 metric_fields['success'] = False 1886 metric_debug_fields['success'] = False 1887 metric_debug_fields['error'] = type(e).__name__ 1888 raise 1889 finally: 1890 duration = int(time.time() - t0) 1891 logging.debug('Post reboot lsb-release {}'.format( 1892 self._get_lsb_release_content())) 1893 1894 metrics.Counter( 1895 'chromeos/autotest/autoserv/reboot_count').increment( 1896 fields=metric_fields) 1897 metrics.Counter( 1898 'chromeos/autotest/autoserv/reboot_debug').increment( 1899 fields=metric_debug_fields) 1900 metrics.SecondsDistribution( 1901 'chromeos/autotest/autoserv/reboot_duration').add( 1902 duration, fields=metric_fields) 1903 1904 def _default_suspend_cmd(self, suspend_time=60, delay_seconds=0): 1905 """ 1906 Return the default suspend command 1907 1908 @param suspend_time: How long to suspend as integer seconds. 1909 @param suspend_cmd: Suspend command to execute. 1910 1911 @returns formatted suspend_cmd string to execute 1912 """ 1913 suspend_cmd = ' && '.join([ 1914 'echo 0 > /sys/class/rtc/rtc0/wakealarm', 1915 'echo +%d > /sys/class/rtc/rtc0/wakealarm' % suspend_time, 1916 'powerd_dbus_suspend --delay=%d' % delay_seconds]) 1917 return suspend_cmd 1918 1919 def suspend_bg(self, suspend_time=60, delay_seconds=0, 1920 suspend_cmd=None): 1921 """ 1922 This function suspends the site host and returns right away. 1923 1924 Note: use this when you need to perform work *while* the host is 1925 suspended. 1926 1927 @param suspend_time: How long to suspend as integer seconds. 1928 @param suspend_cmd: Suspend command to execute. 1929 1930 @exception AutoservSuspendError: if |suspend_cmd| fails 1931 """ 1932 if suspend_cmd is None: 1933 suspend_cmd = self._default_suspend_cmd(suspend_time, delay_seconds) 1934 try: 1935 self.run_background(suspend_cmd) 1936 except error.AutoservRunError: 1937 raise error.AutoservSuspendError("suspend command failed") 1938 1939 def suspend(self, suspend_time=60, delay_seconds=0, 1940 suspend_cmd=None, allow_early_resume=False): 1941 """ 1942 This function suspends the site host. 1943 1944 @param suspend_time: How long to suspend as integer seconds. 1945 @param suspend_cmd: Suspend command to execute. 1946 @param allow_early_resume: If False and if device resumes before 1947 |suspend_time|, throw an error. 1948 1949 @exception AutoservSuspendError Host resumed earlier than 1950 |suspend_time|. 1951 """ 1952 1953 if suspend_cmd is None: 1954 suspend_cmd = self._default_suspend_cmd(suspend_time, delay_seconds) 1955 super(CrosHost, self).suspend(suspend_time, suspend_cmd, 1956 allow_early_resume); 1957 1958 1959 def upstart_status(self, service_name): 1960 """Check the status of an upstart init script. 1961 1962 @param service_name: Service to look up. 1963 1964 @returns True if the service is running, False otherwise. 1965 """ 1966 return 'start/running' in self.run('status %s' % service_name, 1967 ignore_status=True).stdout 1968 1969 def upstart_stop(self, service_name): 1970 """Stops an upstart job if it's running. 1971 1972 @param service_name: Service to stop 1973 1974 @returns True if service has been stopped or was already stopped 1975 False otherwise. 1976 """ 1977 if not self.upstart_status(service_name): 1978 return True 1979 1980 result = self.run('stop %s' % service_name, ignore_status=True) 1981 if result.exit_status != 0: 1982 return False 1983 return True 1984 1985 def upstart_restart(self, service_name): 1986 """Restarts (or starts) an upstart job. 1987 1988 @param service_name: Service to start/restart 1989 1990 @returns True if service has been started/restarted, False otherwise. 1991 """ 1992 cmd = 'start' 1993 if self.upstart_status(service_name): 1994 cmd = 'restart' 1995 cmd = cmd + ' %s' % service_name 1996 result = self.run(cmd) 1997 if result.exit_status != 0: 1998 return False 1999 return True 2000 2001 def verify_software(self): 2002 """Verify working software on a ChromeOS system. 2003 2004 Tests for the following conditions: 2005 1. All conditions tested by the parent version of this 2006 function. 2007 2. Sufficient space in /mnt/stateful_partition. 2008 3. Sufficient space in /mnt/stateful_partition/encrypted. 2009 4. update_engine answers a simple status request over DBus. 2010 2011 """ 2012 super(CrosHost, self).verify_software() 2013 default_kilo_inodes_required = CONFIG.get_config_value( 2014 'SERVER', 'kilo_inodes_required', type=int, default=100) 2015 board = self.get_board().replace(ds_constants.BOARD_PREFIX, '') 2016 kilo_inodes_required = CONFIG.get_config_value( 2017 'SERVER', 'kilo_inodes_required_%s' % board, 2018 type=int, default=default_kilo_inodes_required) 2019 self.check_inodes('/mnt/stateful_partition', kilo_inodes_required) 2020 self.check_diskspace( 2021 '/mnt/stateful_partition', 2022 CONFIG.get_config_value( 2023 'SERVER', 'gb_diskspace_required', type=float, 2024 default=20.0)) 2025 encrypted_stateful_path = '/mnt/stateful_partition/encrypted' 2026 # Not all targets build with encrypted stateful support. 2027 if self.path_exists(encrypted_stateful_path): 2028 self.check_diskspace( 2029 encrypted_stateful_path, 2030 CONFIG.get_config_value( 2031 'SERVER', 'gb_encrypted_diskspace_required', type=float, 2032 default=0.1)) 2033 2034 self.wait_for_system_services() 2035 2036 # Factory images don't run update engine, 2037 # goofy controls dbus on these DUTs. 2038 if not self._is_factory_image(): 2039 self.run('update_engine_client --status') 2040 2041 2042 @retry.retry(error.AutoservError, timeout_min=5, delay_sec=10) 2043 def wait_for_service(self, service_name): 2044 """Wait for target status of an upstart init script. 2045 2046 @param service_name: Service to wait for. 2047 """ 2048 if not self.upstart_status(service_name): 2049 raise error.AutoservError('Service %s not running.' % service_name) 2050 2051 def wait_for_system_services(self): 2052 """Waits for system-services to be running. 2053 2054 Sometimes, update_engine will take a while to update firmware, so we 2055 should give this some time to finish. See crbug.com/765686#c38 for 2056 details. 2057 """ 2058 self.wait_for_service('system-services') 2059 2060 2061 def verify(self): 2062 """Verify ChromeOS system is in good state.""" 2063 message = 'Beginning verify for host %s board %s model %s' 2064 info = self.host_info_store.get() 2065 message %= (self.hostname, info.board, info.model) 2066 self.record('INFO', None, None, message) 2067 try: 2068 self._repair_strategy.verify(self) 2069 except hosts.AutoservVerifyDependencyError as e: 2070 # We don't want flag a DUT as failed if only non-critical 2071 # verifier(s) failed during the repair. 2072 if e.is_critical(): 2073 raise 2074 2075 2076 def make_ssh_command(self, 2077 user='root', 2078 port=None, 2079 opts='', 2080 hosts_file=None, 2081 connect_timeout=None, 2082 alive_interval=None, 2083 alive_count_max=None, 2084 connection_attempts=None): 2085 """Override default make_ssh_command to use options tuned for ChromeOS. 2086 2087 Tuning changes: 2088 - ConnectTimeout=30; maximum of 30 seconds allowed for an SSH 2089 connection failure. Consistency with remote_access.sh. 2090 2091 - ServerAliveInterval=900; which causes SSH to ping connection every 2092 900 seconds. In conjunction with ServerAliveCountMax ensures 2093 that if the connection dies, Autotest will bail out. 2094 Originally tried 60 secs, but saw frequent job ABORTS where 2095 the test completed successfully. Later increased from 180 seconds to 2096 900 seconds to account for tests where the DUT is suspended for 2097 longer periods of time. 2098 2099 - ServerAliveCountMax=3; consistency with remote_access.sh. 2100 2101 - ConnectAttempts=4; reduce flakiness in connection errors; 2102 consistency with remote_access.sh. 2103 2104 - UserKnownHostsFile=/dev/null; we don't care about the keys. 2105 Host keys change with every new installation, don't waste 2106 memory/space saving them. 2107 2108 - SSH protocol forced to 2; needed for ServerAliveInterval. 2109 2110 @param user User name to use for the ssh connection. 2111 @param port Port on the target host to use for ssh connection. 2112 @param opts Additional options to the ssh command. 2113 @param hosts_file Ignored. 2114 @param connect_timeout Ignored. 2115 @param alive_interval Ignored. 2116 @param alive_count_max Ignored. 2117 @param connection_attempts Ignored. 2118 """ 2119 options = ' '.join([opts, '-o Protocol=2']) 2120 return super(CrosHost, self).make_ssh_command( 2121 user=user, port=port, opts=options, hosts_file='/dev/null', 2122 connect_timeout=30, alive_interval=900, alive_count_max=3, 2123 connection_attempts=4) 2124 2125 2126 def syslog(self, message, tag='autotest'): 2127 """Logs a message to syslog on host. 2128 2129 @param message String message to log into syslog 2130 @param tag String tag prefix for syslog 2131 2132 """ 2133 self.run('logger -t "%s" "%s"' % (tag, message)) 2134 2135 2136 def _ping_check_status(self, status): 2137 """Ping the host once, and return whether it has a given status. 2138 2139 @param status Check the ping status against this value. 2140 @return True iff `status` and the result of ping are the same 2141 (i.e. both True or both False). 2142 2143 """ 2144 ping_val = utils.ping(self.hostname, 2145 tries=1, 2146 deadline=1, 2147 timeout=2, 2148 ignore_timeout=True) 2149 return not (status ^ (ping_val == 0)) 2150 2151 def _ping_wait_for_status(self, status, timeout): 2152 """Wait for the host to have a given status (UP or DOWN). 2153 2154 Status is checked by polling. Polling will not last longer 2155 than the number of seconds in `timeout`. The polling 2156 interval will be long enough that only approximately 2157 _PING_WAIT_COUNT polling cycles will be executed, subject 2158 to a maximum interval of about one minute. 2159 2160 @param status Waiting will stop immediately if `ping` of the 2161 host returns this status. 2162 @param timeout Poll for at most this many seconds. 2163 @return True iff the host status from `ping` matched the 2164 requested status at the time of return. 2165 2166 """ 2167 # _ping_check_status() takes about 1 second, hence the 2168 # "- 1" in the formula below. 2169 # FIXME: if the ping command errors then _ping_check_status() 2170 # returns instantly. If timeout is also smaller than twice 2171 # _PING_WAIT_COUNT then the while loop below forks many 2172 # thousands of ping commands (see /tmp/test_that_results_XXXXX/ 2173 # /results-1-logging_YYY.ZZZ/debug/autoserv.DEBUG) and hogs one 2174 # CPU core for 60 seconds. 2175 poll_interval = min(int(timeout / self._PING_WAIT_COUNT), 60) - 1 2176 end_time = time.time() + timeout 2177 while time.time() <= end_time: 2178 if self._ping_check_status(status): 2179 return True 2180 if poll_interval > 0: 2181 time.sleep(poll_interval) 2182 2183 # The last thing we did was sleep(poll_interval), so it may 2184 # have been too long since the last `ping`. Check one more 2185 # time, just to be sure. 2186 return self._ping_check_status(status) 2187 2188 def ping_wait_up(self, timeout): 2189 """Wait for the host to respond to `ping`. 2190 2191 N.B. This method is not a reliable substitute for 2192 `wait_up()`, because a host that responds to ping will not 2193 necessarily respond to ssh. This method should only be used 2194 if the target DUT can be considered functional even if it 2195 can't be reached via ssh. 2196 2197 @param timeout Minimum time to allow before declaring the 2198 host to be non-responsive. 2199 @return True iff the host answered to ping before the timeout. 2200 2201 """ 2202 if self.use_icmp: 2203 return self._ping_wait_for_status(self._PING_STATUS_UP, timeout) 2204 else: 2205 logging.debug('Using SSH instead of ICMP for ping_wait_up.') 2206 return self.wait_up(timeout) 2207 2208 def ping_wait_down(self, timeout): 2209 """Wait until the host no longer responds to `ping`. 2210 2211 This function can be used as a slightly faster version of 2212 `wait_down()`, by avoiding potentially long ssh timeouts. 2213 2214 @param timeout Minimum time to allow for the host to become 2215 non-responsive. 2216 @return True iff the host quit answering ping before the 2217 timeout. 2218 2219 """ 2220 if self.use_icmp: 2221 return self._ping_wait_for_status(self._PING_STATUS_DOWN, timeout) 2222 else: 2223 logging.debug('Using SSH instead of ICMP for ping_wait_down.') 2224 return self.wait_down(timeout) 2225 2226 def _is_host_port_forwarded(self): 2227 """Checks if the dut is connected over port forwarding. 2228 2229 N.B. This method does not detect all situations where port forwarding is 2230 occurring. Namely, running autotest on the dut may result in a 2231 false-positive, and port forwarding using a different machine on the 2232 same network will be a false-negative. 2233 2234 @return True if the dut is connected over port forwarding 2235 False otherwise 2236 """ 2237 is_localhost = self.hostname in ['localhost', '127.0.0.1'] 2238 is_forwarded = is_localhost and not self.is_default_port 2239 if is_forwarded: 2240 logging.info('Detected DUT connected by port forwarding') 2241 return is_forwarded 2242 2243 def test_wait_for_sleep(self, sleep_timeout=None): 2244 """Wait for the client to enter low-power sleep mode. 2245 2246 The test for "is asleep" can't distinguish a system that is 2247 powered off; to confirm that the unit was asleep, it is 2248 necessary to force resume, and then call 2249 `test_wait_for_resume()`. 2250 2251 This function is expected to be called from a test as part 2252 of a sequence like the following: 2253 2254 ~~~~~~~~ 2255 boot_id = host.get_boot_id() 2256 # trigger sleep on the host 2257 host.test_wait_for_sleep() 2258 # trigger resume on the host 2259 host.test_wait_for_resume(boot_id) 2260 ~~~~~~~~ 2261 2262 @param sleep_timeout time limit in seconds to allow the host sleep. 2263 2264 @exception TestFail The host did not go to sleep within 2265 the allowed time. 2266 """ 2267 if sleep_timeout is None: 2268 sleep_timeout = self.SLEEP_TIMEOUT 2269 2270 # If the dut is accessed over SSH port-forwarding, `ping` is not useful 2271 # for detecting the dut is down since a ping to localhost will always 2272 # succeed. In this case, fall back to wait_down() which uses SSH. 2273 if self._is_host_port_forwarded(): 2274 success = self.wait_down(timeout=sleep_timeout) 2275 else: 2276 success = self.ping_wait_down(timeout=sleep_timeout) 2277 2278 if not success: 2279 raise error.TestFail( 2280 'client failed to sleep after %d seconds' % sleep_timeout) 2281 2282 2283 def test_wait_for_resume(self, old_boot_id, resume_timeout=None): 2284 """Wait for the client to resume from low-power sleep mode. 2285 2286 The `old_boot_id` parameter should be the value from 2287 `get_boot_id()` obtained prior to entering sleep mode. A 2288 `TestFail` exception is raised if the boot id changes. 2289 2290 See @ref test_wait_for_sleep for more on this function's 2291 usage. 2292 2293 @param old_boot_id A boot id value obtained before the 2294 target host went to sleep. 2295 @param resume_timeout time limit in seconds to allow the host up. 2296 2297 @exception TestFail The host did not respond within the 2298 allowed time. 2299 @exception TestFail The host responded, but the boot id test 2300 indicated a reboot rather than a sleep 2301 cycle. 2302 """ 2303 if resume_timeout is None: 2304 resume_timeout = self.RESUME_TIMEOUT 2305 2306 if not self.wait_up(timeout=resume_timeout): 2307 raise error.TestFail( 2308 'client failed to resume from sleep after %d seconds' % 2309 resume_timeout) 2310 else: 2311 new_boot_id = self.get_boot_id() 2312 if new_boot_id != old_boot_id: 2313 logging.error('client rebooted (old boot %s, new boot %s)', 2314 old_boot_id, new_boot_id) 2315 raise error.TestFail( 2316 'client rebooted, but sleep was expected') 2317 2318 2319 def test_wait_for_shutdown(self, shutdown_timeout=None): 2320 """Wait for the client to shut down. 2321 2322 The test for "has shut down" can't distinguish a system that 2323 is merely asleep; to confirm that the unit was down, it is 2324 necessary to force boot, and then call test_wait_for_boot(). 2325 2326 This function is expected to be called from a test as part 2327 of a sequence like the following: 2328 2329 ~~~~~~~~ 2330 boot_id = host.get_boot_id() 2331 # trigger shutdown on the host 2332 host.test_wait_for_shutdown() 2333 # trigger boot on the host 2334 host.test_wait_for_boot(boot_id) 2335 ~~~~~~~~ 2336 2337 @param shutdown_timeout time limit in seconds to allow the host down. 2338 @exception TestFail The host did not shut down within the 2339 allowed time. 2340 """ 2341 if shutdown_timeout is None: 2342 shutdown_timeout = self.SHUTDOWN_TIMEOUT 2343 2344 if self._is_host_port_forwarded(): 2345 success = self.wait_down(timeout=shutdown_timeout) 2346 else: 2347 success = self.ping_wait_down(timeout=shutdown_timeout) 2348 2349 if not success: 2350 raise error.TestFail( 2351 'client failed to shut down after %d seconds' % 2352 shutdown_timeout) 2353 2354 2355 def test_wait_for_boot(self, old_boot_id=None): 2356 """Wait for the client to boot from cold power. 2357 2358 The `old_boot_id` parameter should be the value from 2359 `get_boot_id()` obtained prior to shutting down. A 2360 `TestFail` exception is raised if the boot id does not 2361 change. The boot id test is omitted if `old_boot_id` is not 2362 specified. 2363 2364 See @ref test_wait_for_shutdown for more on this function's 2365 usage. 2366 2367 @param old_boot_id A boot id value obtained before the 2368 shut down. 2369 2370 @exception TestFail The host did not respond within the 2371 allowed time. 2372 @exception TestFail The host responded, but the boot id test 2373 indicated that there was no reboot. 2374 """ 2375 if not self.wait_up(timeout=self.REBOOT_TIMEOUT): 2376 raise error.TestFail( 2377 'client failed to reboot after %d seconds' % 2378 self.REBOOT_TIMEOUT) 2379 elif old_boot_id: 2380 if self.get_boot_id() == old_boot_id: 2381 logging.error('client not rebooted (boot %s)', 2382 old_boot_id) 2383 raise error.TestFail( 2384 'client is back up, but did not reboot') 2385 2386 2387 @staticmethod 2388 def check_for_rpm_support(hostname): 2389 """For a given hostname, return whether or not it is powered by an RPM. 2390 2391 @param hostname: hostname to check for rpm support. 2392 2393 @return None if this host does not follows the defined naming format 2394 for RPM powered DUT's in the lab. If it does follow the format, 2395 it returns a regular expression MatchObject instead. 2396 """ 2397 return re.match(CrosHost._RPM_HOSTNAME_REGEX, hostname) 2398 2399 2400 def has_power(self): 2401 """For this host, return whether or not it is powered by an RPM. 2402 2403 @return True if this host is in the CROS lab and follows the defined 2404 naming format. 2405 """ 2406 return CrosHost.check_for_rpm_support(self.hostname) 2407 2408 2409 def _set_power(self, state, power_method): 2410 """Sets the power to the host via RPM, CCD, Servo or manual. 2411 2412 @param state Specifies which power state to set to DUT 2413 @param power_method Specifies which method of power control to 2414 use. By default "RPM" or "CCD" will be used based 2415 on servo type. Valid values from 2416 POWER_CONTROL_VALID_ARGS, or None to use default. 2417 2418 """ 2419 ACCEPTABLE_STATES = ['ON', 'OFF'] 2420 2421 if not power_method: 2422 power_method = self.get_default_power_method() 2423 2424 state = state.upper() 2425 if state not in ACCEPTABLE_STATES: 2426 raise error.TestError('State must be one of: %s.' 2427 % (ACCEPTABLE_STATES,)) 2428 2429 if power_method == self.POWER_CONTROL_SERVO: 2430 logging.info('Setting servo port J10 to %s', state) 2431 self.servo.set('prtctl3_pwren', state.lower()) 2432 time.sleep(self._USB_POWER_TIMEOUT) 2433 elif power_method == self.POWER_CONTROL_MANUAL: 2434 logging.info('You have %d seconds to set the AC power to %s.', 2435 self._POWER_CYCLE_TIMEOUT, state) 2436 time.sleep(self._POWER_CYCLE_TIMEOUT) 2437 elif power_method == self.POWER_CONTROL_CCD: 2438 servo_role = 'src' if state == 'ON' else 'snk' 2439 logging.info('servo ccd power pass through detected,' 2440 ' changing servo_role to %s.', servo_role) 2441 self.servo.set_servo_v4_role(servo_role) 2442 if not self.ping_wait_up(timeout=self._CHANGE_SERVO_ROLE_TIMEOUT): 2443 # Make sure we don't leave DUT with no power(servo_role=snk) 2444 # when DUT is not pingable, as we raise a exception here 2445 # that may break a power cycle in the middle. 2446 self.servo.set_servo_v4_role('src') 2447 raise error.AutoservError( 2448 'DUT failed to regain network connection after %d seconds.' 2449 % self._CHANGE_SERVO_ROLE_TIMEOUT) 2450 else: 2451 if not self.has_power(): 2452 raise error.TestFail('DUT does not have RPM connected.') 2453 self._add_rpm_changed_tag() 2454 rpm_client.set_power(self, state, timeout_mins=5) 2455 2456 2457 def power_off(self, power_method=None): 2458 """Turn off power to this host via RPM, CCD, Servo or manual. 2459 2460 @param power_method Specifies which method of power control to 2461 use. By default "RPM" or "CCD" will be used based 2462 on servo type. Valid values from 2463 POWER_CONTROL_VALID_ARGS, or None to use default. 2464 2465 """ 2466 self._sync_if_up() 2467 self._set_power('OFF', power_method) 2468 2469 def _check_supported(self): 2470 """Throw an error if dts mode control is not supported.""" 2471 if not self.servo_pwr_supported: 2472 raise error.TestFail('power_state controls not supported') 2473 2474 def _sync_if_up(self): 2475 """Run sync on the DUT and wait for completion if the DUT is up. 2476 2477 Additionally, try to sync and ignore status if its not up. 2478 2479 Useful prior to reboots to ensure files are written to disc. 2480 2481 """ 2482 if self.is_up_fast(): 2483 self.run("sync") 2484 return 2485 # If it is not up, attempt to sync in the rare event the DUT is up but 2486 # doesn't respond to a ping. Ignore any errors. 2487 try: 2488 self.run("sync", ignore_status=True, timeout=1) 2489 except Exception: 2490 pass 2491 2492 def power_off_via_servo(self): 2493 """Force the DUT to power off. 2494 2495 The DUT is guaranteed to be off at the end of this call, 2496 regardless of its previous state, provided that there is 2497 working EC and boot firmware. There is no requirement for 2498 working OS software. 2499 2500 """ 2501 self._check_supported() 2502 self._sync_if_up() 2503 self.servo.set_nocheck('power_state', 'off') 2504 2505 def power_on_via_servo(self, rec_mode='on'): 2506 """Force the DUT to power on. 2507 2508 Prior to calling this function, the DUT must be powered off, 2509 e.g. with a call to `power_off()`. 2510 2511 At power on, recovery mode is set as specified by the 2512 corresponding argument. When booting with recovery mode on, it 2513 is the caller's responsibility to unplug/plug in a bootable 2514 external storage device. 2515 2516 If the DUT requires a delay after powering on but before 2517 processing inputs such as USB stick insertion, the delay is 2518 handled by this method; the caller is not responsible for such 2519 delays. 2520 2521 @param rec_mode Setting of recovery mode to be applied at 2522 power on. default: REC_OFF aka 'off' 2523 2524 """ 2525 self._check_supported() 2526 self.servo.set_nocheck('power_state', rec_mode) 2527 2528 def reset_via_servo(self): 2529 """Force the DUT to reset. 2530 2531 The DUT is guaranteed to be on at the end of this call, 2532 regardless of its previous state, provided that there is 2533 working OS software. This also guarantees that the EC has 2534 been restarted. 2535 2536 """ 2537 self._check_supported() 2538 self._sync_if_up() 2539 self.servo.set_nocheck('power_state', 'reset') 2540 2541 2542 def power_on(self, power_method=None): 2543 """Turn on power to this host via RPM, CCD, Servo or manual. 2544 2545 @param power_method Specifies which method of power control to 2546 use. By default "RPM" or "CCD" will be used based 2547 on servo type. Valid values from 2548 POWER_CONTROL_VALID_ARGS, or None to use default. 2549 2550 """ 2551 self._set_power('ON', power_method) 2552 2553 2554 def power_cycle(self, power_method=None): 2555 """Cycle power to this host by turning it OFF, then ON. 2556 2557 @param power_method Specifies which method of power control to 2558 use. By default "RPM" or "CCD" will be used based 2559 on servo type. Valid values from 2560 POWER_CONTROL_VALID_ARGS, or None to use default. 2561 2562 """ 2563 if not power_method: 2564 power_method = self.get_default_power_method() 2565 2566 if power_method in (self.POWER_CONTROL_SERVO, 2567 self.POWER_CONTROL_MANUAL, 2568 self.POWER_CONTROL_CCD): 2569 self.power_off(power_method=power_method) 2570 time.sleep(self._POWER_CYCLE_TIMEOUT) 2571 self.power_on(power_method=power_method) 2572 else: 2573 self._add_rpm_changed_tag() 2574 rpm_client.set_power(self, 'CYCLE') 2575 2576 2577 def get_platform_from_fwid(self): 2578 """Determine the platform from the crossystem fwid. 2579 2580 @returns a string representing this host's platform. 2581 """ 2582 # Look at the firmware for non-unibuild cases or if cros_config fails. 2583 crossystem = utils.Crossystem(self) 2584 crossystem.init() 2585 # Extract fwid value and use the leading part as the platform id. 2586 # fwid generally follow the format of {platform}.{firmware version} 2587 # Example: Alex.X.YYY.Z or Google_Alex.X.YYY.Z 2588 platform = crossystem.fwid().split('.')[0].lower() 2589 # Newer platforms start with 'Google_' while the older ones do not. 2590 return platform.replace('google_', '') 2591 2592 2593 def get_platform(self): 2594 """Determine the correct platform label for this host. 2595 2596 @returns a string representing this host's platform. 2597 """ 2598 release_info = utils.parse_cmd_output('cat /etc/lsb-release', 2599 run_method=self.run) 2600 platform = '' 2601 if release_info.get('CHROMEOS_RELEASE_UNIBUILD') == '1': 2602 platform = self.get_model_from_cros_config() 2603 return platform if platform else self.get_platform_from_fwid() 2604 2605 2606 def get_model_from_cros_config(self): 2607 """Get the host model from cros_config command. 2608 2609 @returns a string representing this host's model. 2610 """ 2611 return cros_config.call_cros_config_get_output('/ name', 2612 self.run, ignore_status=True) 2613 2614 2615 def get_architecture(self): 2616 """Determine the correct architecture label for this host. 2617 2618 @returns a string representing this host's architecture. 2619 """ 2620 crossystem = utils.Crossystem(self) 2621 crossystem.init() 2622 return crossystem.arch() 2623 2624 2625 def get_chrome_version(self): 2626 """Gets the Chrome version number and milestone as strings. 2627 2628 Invokes "chrome --version" to get the version number and milestone. 2629 2630 @return A tuple (chrome_ver, milestone) where "chrome_ver" is the 2631 current Chrome version number as a string (in the form "W.X.Y.Z") 2632 and "milestone" is the first component of the version number 2633 (the "W" from "W.X.Y.Z"). If the version number cannot be parsed 2634 in the "W.X.Y.Z" format, the "chrome_ver" will be the full output 2635 of "chrome --version" and the milestone will be the empty string. 2636 2637 """ 2638 version_string = self.run(client_constants.CHROME_VERSION_COMMAND).stdout 2639 return utils.parse_chrome_version(version_string) 2640 2641 2642 def get_ec_version(self): 2643 """Get the ec version as strings. 2644 2645 @returns a string representing this host's ec version. 2646 """ 2647 command = 'mosys ec info -s fw_version' 2648 result = self.run(command, ignore_status=True) 2649 if result.exit_status != 0: 2650 return '' 2651 return result.stdout.strip() 2652 2653 2654 def get_firmware_version(self): 2655 """Get the firmware version as strings. 2656 2657 @returns a string representing this host's firmware version. 2658 """ 2659 crossystem = utils.Crossystem(self) 2660 crossystem.init() 2661 return crossystem.fwid() 2662 2663 2664 def get_hardware_id(self): 2665 """Get hardware id as strings. 2666 2667 @returns a string representing this host's hardware id. 2668 """ 2669 crossystem = utils.Crossystem(self) 2670 crossystem.init() 2671 return crossystem.hwid() 2672 2673 def get_hardware_revision(self): 2674 """Get the hardware revision as strings. 2675 2676 @returns a string representing this host's hardware revision. 2677 """ 2678 command = 'mosys platform version' 2679 result = self.run(command, ignore_status=True) 2680 if result.exit_status != 0: 2681 return '' 2682 return result.stdout.strip() 2683 2684 2685 def get_kernel_version(self): 2686 """Get the kernel version as strings. 2687 2688 @returns a string representing this host's kernel version. 2689 """ 2690 return self.run('uname -r').stdout.strip() 2691 2692 2693 def get_cpu_name(self): 2694 """Get the cpu name as strings. 2695 2696 @returns a string representing this host's cpu name. 2697 """ 2698 2699 # Try get cpu name from device tree first 2700 if self.path_exists('/proc/device-tree/compatible'): 2701 command = ' | '.join( 2702 ["sed -e 's/\\x0/\\n/g' /proc/device-tree/compatible", 2703 'tail -1']) 2704 return self.run(command).stdout.strip().replace(',', ' ') 2705 2706 # Get cpu name from uname -p 2707 command = 'uname -p' 2708 ret = self.run(command).stdout.strip() 2709 2710 # 'uname -p' return variant of unknown or amd64 or x86_64 or i686 2711 # Try get cpu name from /proc/cpuinfo instead 2712 if re.match("unknown|amd64|[ix][0-9]?86(_64)?", ret, re.IGNORECASE): 2713 command = "grep model.name /proc/cpuinfo | cut -f 2 -d: | head -1" 2714 self = self.run(command).stdout.strip() 2715 2716 # Remove bloat from CPU name, for example 2717 # Intel(R) Core(TM) i5-7Y57 CPU @ 1.20GHz -> Intel Core i5-7Y57 2718 # Intel(R) Xeon(R) CPU E5-2690 v4 @ 2.60GHz -> Intel Xeon E5-2690 v4 2719 # AMD A10-7850K APU with Radeon(TM) R7 Graphics -> AMD A10-7850K 2720 # AMD GX-212JC SOC with Radeon(TM) R2E Graphics -> AMD GX-212JC 2721 trim_re = r' (@|processor|apu|soc|radeon).*|\(.*?\)| cpu' 2722 return re.sub(trim_re, '', ret, flags=re.IGNORECASE) 2723 2724 2725 def get_screen_resolution(self): 2726 """Get the screen(s) resolution as strings. 2727 In case of more than 1 monitor, return resolution for each monitor 2728 separate with plus sign. 2729 2730 @returns a string representing this host's screen(s) resolution. 2731 """ 2732 command = 'for f in /sys/class/drm/*/*/modes; do head -1 $f; done' 2733 ret = self.run(command, ignore_status=True) 2734 # We might have Chromebox without a screen 2735 if ret.exit_status != 0: 2736 return '' 2737 return ret.stdout.strip().replace('\n', '+') 2738 2739 2740 def get_mem_total_gb(self): 2741 """Get total memory available in the system in GiB (2^20). 2742 2743 @returns an integer representing total memory 2744 """ 2745 mem_total_kb = self.read_from_meminfo('MemTotal') 2746 kb_in_gb = float(2 ** 20) 2747 return int(round(mem_total_kb / kb_in_gb)) 2748 2749 2750 def get_disk_size_gb(self): 2751 """Get size of disk in GB (10^9) 2752 2753 @returns an integer representing size of disk, 0 in Error Case 2754 """ 2755 command = 'grep $(rootdev -s -d | cut -f3 -d/)$ /proc/partitions' 2756 result = self.run(command, ignore_status=True) 2757 if result.exit_status != 0: 2758 return 0 2759 _, _, block, _ = re.split(r' +', result.stdout.strip()) 2760 byte_per_block = 1024.0 2761 disk_kb_in_gb = 1e9 2762 return int(int(block) * byte_per_block / disk_kb_in_gb + 0.5) 2763 2764 2765 def get_battery_size(self): 2766 """Get size of battery in Watt-hour via sysfs 2767 2768 This method assumes that battery support voltage_min_design and 2769 charge_full_design sysfs. 2770 2771 @returns a float representing Battery size, 0 if error. 2772 """ 2773 # sysfs report data in micro scale 2774 battery_scale = 1e6 2775 2776 command = 'cat /sys/class/power_supply/*/voltage_min_design' 2777 result = self.run(command, ignore_status=True) 2778 if result.exit_status != 0: 2779 return 0 2780 voltage = float(result.stdout.strip()) / battery_scale 2781 2782 command = 'cat /sys/class/power_supply/*/charge_full_design' 2783 result = self.run(command, ignore_status=True) 2784 if result.exit_status != 0: 2785 return 0 2786 amphereHour = float(result.stdout.strip()) / battery_scale 2787 2788 return voltage * amphereHour 2789 2790 2791 def get_low_battery_shutdown_percent(self): 2792 """Get the percent-based low-battery shutdown threshold. 2793 2794 @returns a float representing low-battery shutdown percent, 0 if error. 2795 """ 2796 ret = 0.0 2797 try: 2798 command = 'check_powerd_config --low_battery_shutdown_percent' 2799 ret = float(self.run(command).stdout) 2800 except error.CmdError: 2801 logging.debug("Can't run %s", command) 2802 except ValueError: 2803 logging.debug("Didn't get number from %s", command) 2804 2805 return ret 2806 2807 2808 def has_hammer(self): 2809 """Check whether DUT has hammer device or not. 2810 2811 @returns boolean whether device has hammer or not 2812 """ 2813 command = 'grep Hammer /sys/bus/usb/devices/*/product' 2814 return self.run(command, ignore_status=True).exit_status == 0 2815 2816 2817 def is_chrome_switch_present(self, switch): 2818 """Returns True if the specified switch was provided to Chrome. 2819 2820 @param switch The chrome switch to search for. 2821 """ 2822 2823 command = 'pgrep -x -f -c "/opt/google/chrome/chrome.*%s.*"' % switch 2824 return self.run(command, ignore_status=True).exit_status == 0 2825 2826 2827 def oobe_triggers_update(self): 2828 """Returns True if this host has an OOBE flow during which 2829 it will perform an update check and perhaps an update. 2830 One example of such a flow is Hands-Off Zero-Touch Enrollment. 2831 As more such flows are developed, code handling them needs 2832 to be added here. 2833 2834 @return Boolean indicating whether this host's OOBE triggers an update. 2835 """ 2836 return self.is_chrome_switch_present( 2837 '--enterprise-enable-zero-touch-enrollment=hands-off') 2838 2839 2840 # TODO(kevcheng): change this to just return the board without the 2841 # 'board:' prefix and fix up all the callers. Also look into removing the 2842 # need for this method. 2843 def get_board(self): 2844 """Determine the correct board label for this host. 2845 2846 @returns a string representing this host's board. 2847 """ 2848 release_info = utils.parse_cmd_output('cat /etc/lsb-release', 2849 run_method=self.run) 2850 return (ds_constants.BOARD_PREFIX + 2851 release_info['CHROMEOS_RELEASE_BOARD']) 2852 2853 def get_channel(self): 2854 """Determine the correct channel label for this host. 2855 2856 @returns: a string represeting this host's build channel. 2857 (stable, dev, beta). None on fail. 2858 """ 2859 return lsbrelease_utils.get_chromeos_channel( 2860 lsb_release_content=self._get_lsb_release_content()) 2861 2862 def get_power_supply(self): 2863 """ 2864 Determine what type of power supply the host has 2865 2866 @returns a string representing this host's power supply. 2867 'power:battery' when the device has a battery intended for 2868 extended use 2869 'power:AC_primary' when the device has a battery not intended 2870 for extended use (for moving the machine, etc) 2871 'power:AC_only' when the device has no battery at all. 2872 """ 2873 psu = self.run(command='cros_config /hardware-properties psu-type', 2874 ignore_status=True) 2875 if psu.exit_status: 2876 # Assume battery if unspecified in cros_config. 2877 return 'power:battery' 2878 2879 psu_str = psu.stdout.strip() 2880 if psu_str == 'unknown': 2881 return None 2882 2883 return 'power:%s' % psu_str 2884 2885 2886 def has_battery(self): 2887 """Determine if DUT has a battery. 2888 2889 Returns: 2890 Boolean, False if known not to have battery, True otherwise. 2891 """ 2892 return self.get_power_supply() == 'power:battery' 2893 2894 2895 def get_servo(self): 2896 """Determine if the host has a servo attached. 2897 2898 If the host has a working servo attached, it should have a servo label. 2899 2900 @return: string 'servo' if the host has servo attached. Otherwise, 2901 returns None. 2902 """ 2903 return 'servo' if self._servo_host else None 2904 2905 def _has_display(self, internal): 2906 """ Determine if the device under test is equipped with a display 2907 @params internal: True if checking internal display else checking 2908 external display. 2909 @return: 'internal_display' if internal is true and internal display 2910 present; 2911 'external_display' if internal is false and external display 2912 present; 2913 None otherwise. 2914 """ 2915 from autotest_lib.client.cros.graphics import graphics_utils 2916 from autotest_lib.client.common_lib import utils as common_utils 2917 2918 def __system_output(cmd): 2919 return self.run(cmd).stdout 2920 2921 def __read_file(remote_path): 2922 return self.run('cat %s' % remote_path).stdout 2923 2924 # Hijack the necessary client functions so that we can take advantage 2925 # of the client lib here. 2926 # FIXME: find a less hacky way than this 2927 original_system_output = utils.system_output 2928 original_read_file = common_utils.read_file 2929 utils.system_output = __system_output 2930 common_utils.read_file = __read_file 2931 try: 2932 if internal: 2933 return ('internal_display' 2934 if graphics_utils.has_internal_display() else None) 2935 else: 2936 return ('external_display' 2937 if graphics_utils.has_external_display() else None) 2938 finally: 2939 utils.system_output = original_system_output 2940 common_utils.read_file = original_read_file 2941 2942 2943 def has_internal_display(self): 2944 """Determine if the device under test is equipped with an internal 2945 display. 2946 2947 @return: 'internal_display' if one is present; None otherwise. 2948 """ 2949 return self._has_display(True) 2950 2951 def has_external_display(self): 2952 """Determine if the device under test is equipped with an external 2953 display. 2954 2955 @return: 'external_display' if one is present; None otherwise. 2956 """ 2957 return self._has_display(False) 2958 2959 def is_boot_from_usb(self): 2960 """Check if DUT is boot from USB. 2961 2962 @return: True if DUT is boot from usb. 2963 """ 2964 device = self.run('rootdev -s -d').stdout.strip() 2965 removable = int(self.run('cat /sys/block/%s/removable' % 2966 os.path.basename(device)).stdout.strip()) 2967 return removable == 1 2968 2969 def is_boot_from_external_device(self): 2970 """Check if DUT is boot from external storage. 2971 2972 @return: True if DUT is boot from external storage. 2973 """ 2974 boot_device = self.run('rootdev -s -d', ignore_status=True, 2975 timeout=60).stdout.strip() 2976 if not boot_device: 2977 logging.debug('Boot storage not detected on the host.') 2978 return False 2979 main_storage_cmd = ('. /usr/sbin/write_gpt.sh;' 2980 ' . /usr/share/misc/chromeos-common.sh;' 2981 ' load_base_vars; get_fixed_dst_drive') 2982 main_storage = self.run(main_storage_cmd, 2983 ignore_status=True, 2984 timeout=60).stdout.strip() 2985 if not main_storage or boot_device != main_storage: 2986 logging.debug('Device booted from external storage storage.') 2987 return True 2988 logging.debug('Device booted from main storage.') 2989 return False 2990 2991 def read_from_meminfo(self, key): 2992 """Return the memory info from /proc/meminfo 2993 2994 @param key: meminfo requested 2995 2996 @return the memory value as a string 2997 2998 """ 2999 meminfo = self.run('grep %s /proc/meminfo' % key).stdout.strip() 3000 logging.debug('%s', meminfo) 3001 return int(re.search(r'\d+', meminfo).group(0)) 3002 3003 3004 def get_cpu_arch(self): 3005 """Returns CPU arch of the device. 3006 3007 @return CPU architecture of the DUT. 3008 """ 3009 # Add CPUs by following logic in client/bin/utils.py. 3010 if self.run("grep '^flags.*:.* lm .*' /proc/cpuinfo", 3011 ignore_status=True).stdout: 3012 return 'x86_64' 3013 if self.run("grep -Ei 'ARM|CPU implementer' /proc/cpuinfo", 3014 ignore_status=True).stdout: 3015 return 'arm' 3016 return 'i386' 3017 3018 3019 def get_board_type(self): 3020 """ 3021 Get the DUT's device type / form factor from cros_config. It can be one 3022 of CHROMEBOX, CHROMEBASE, CHROMEBOOK, or CHROMEBIT. 3023 3024 @return form factor value from cros_config. 3025 """ 3026 3027 device_type = self.run('cros_config /hardware-properties form-factor', 3028 ignore_status=True).stdout 3029 if device_type: 3030 return device_type 3031 3032 # TODO: remove lsb-release fallback once cros_config works everywhere 3033 device_type = self.run('grep DEVICETYPE /etc/lsb-release', 3034 ignore_status=True).stdout 3035 if device_type: 3036 return device_type.split('=')[-1].strip() 3037 return '' 3038 3039 3040 def get_arc_version(self): 3041 """Return ARC version installed on the DUT. 3042 3043 @returns ARC version as string if the CrOS build has ARC, else None. 3044 """ 3045 arc_version = self.run('grep CHROMEOS_ARC_VERSION /etc/lsb-release', 3046 ignore_status=True).stdout 3047 if arc_version: 3048 return arc_version.split('=')[-1].strip() 3049 return None 3050 3051 3052 def get_os_type(self): 3053 return 'cros' 3054 3055 3056 def get_labels(self): 3057 """Return the detected labels on the host.""" 3058 return self.labels.get_labels(self) 3059 3060 3061 def get_default_power_method(self): 3062 """ 3063 Get the default power method for power_on/off/cycle() methods. 3064 @return POWER_CONTROL_RPM or POWER_CONTROL_CCD 3065 """ 3066 if not self._default_power_method: 3067 self._default_power_method = self.POWER_CONTROL_RPM 3068 if self.servo and self.servo.supports_built_in_pd_control(): 3069 self._default_power_method = self.POWER_CONTROL_CCD 3070 else: 3071 logging.debug('Either servo is unitialized or the servo ' 3072 'setup does not support pd controls. Falling ' 3073 'back to default RPM method.') 3074 return self._default_power_method 3075 3076 3077 def find_usb_devices(self, idVendor, idProduct): 3078 """ 3079 Get usb device sysfs name for specific device. 3080 3081 @param idVendor Vendor ID to search in sysfs directory. 3082 @param idProduct Product ID to search in sysfs directory. 3083 3084 @return Usb node names in /sys/bus/usb/drivers/usb/ that match. 3085 """ 3086 # Look for matching file and cut at position 7 to get dir name. 3087 grep_cmd = 'grep {} /sys/bus/usb/drivers/usb/*/{} | cut -f 7 -d /' 3088 3089 vendor_cmd = grep_cmd.format(idVendor, 'idVendor') 3090 product_cmd = grep_cmd.format(idProduct, 'idProduct') 3091 3092 # Use uniq -d to print duplicate line from both command 3093 cmd = 'sort <({}) <({}) | uniq -d'.format(vendor_cmd, product_cmd) 3094 3095 return self.run(cmd, ignore_status=True).stdout.strip().split('\n') 3096 3097 3098 def bind_usb_device(self, usb_node): 3099 """ 3100 Bind usb device 3101 3102 @param usb_node Node name in /sys/bus/usb/drivers/usb/ 3103 """ 3104 cmd = 'echo {} > /sys/bus/usb/drivers/usb/bind'.format(usb_node) 3105 self.run(cmd, ignore_status=True) 3106 3107 3108 def unbind_usb_device(self, usb_node): 3109 """ 3110 Unbind usb device 3111 3112 @param usb_node Node name in /sys/bus/usb/drivers/usb/ 3113 """ 3114 cmd = 'echo {} > /sys/bus/usb/drivers/usb/unbind'.format(usb_node) 3115 self.run(cmd, ignore_status=True) 3116 3117 3118 def get_wlan_ip(self): 3119 """ 3120 Get ip address of wlan interface. 3121 3122 @return ip address of wlan or empty string if wlan is not connected. 3123 """ 3124 cmds = [ 3125 'iw dev', # List wlan physical device 3126 'grep Interface', # Grep only interface name 3127 'cut -f 2 -d" "', # Cut the name part 3128 'xargs ifconfig', # Feed it to ifconfig to get ip 3129 'grep -oE "inet [0-9.]+"', # Grep only ipv4 3130 'cut -f 2 -d " "' # Cut the ip part 3131 ] 3132 return self.run(' | '.join(cmds), ignore_status=True).stdout.strip() 3133 3134 def connect_to_wifi(self, ssid, passphrase=None, security=None): 3135 """ 3136 Connect to wifi network 3137 3138 @param ssid SSID of the wifi network. 3139 @param passphrase Passphrase of the wifi network. None if not existed. 3140 @param security Security of the wifi network. Default to "psk" if 3141 passphase is given without security. Possible values 3142 are "none", "psk", "802_1x". 3143 3144 @return True if succeed, False if not. 3145 """ 3146 cmd = '/usr/local/autotest/cros/scripts/wifi connect ' + ssid 3147 if passphrase: 3148 cmd += ' ' + passphrase 3149 if security: 3150 cmd += ' ' + security 3151 return self.run(cmd, ignore_status=True).exit_status == 0 3152 3153 def get_device_repair_state(self): 3154 """Get device repair state""" 3155 return self._device_repair_state 3156 3157 def is_marked_for_replacement(self): 3158 """Verify if device was marked for replacemnet during admin task.""" 3159 expected_state = cros_constants.DEVICE_STATE_NEEDS_REPLACEMENT 3160 return self.get_device_repair_state() == expected_state 3161 3162 def set_device_repair_state(self, state, resultdir=None): 3163 """Set device repair state. 3164 3165 The special device state will be written to the 'dut_state.repair' 3166 file in result directory. The file will be read by Lucifer. The 3167 file will not be created if result directory not specified. 3168 3169 @params state: The new state for the device. 3170 @params resultdir: The path to result directory. If path not provided 3171 will be attempt to get retrieve it from job 3172 if present. 3173 """ 3174 resultdir = resultdir or getattr(self.job, 'resultdir', '') 3175 if resultdir: 3176 target = os.path.join(resultdir, 'dut_state.repair') 3177 common_utils.open_write_close(target, state) 3178 logging.info('Set device state as %s. ' 3179 'Created dut_state.repair file.', state) 3180 else: 3181 logging.debug('Cannot write the device state due missing info ' 3182 'about result dir.') 3183 self._device_repair_state = state 3184 3185 def set_device_needs_replacement(self, resultdir=None): 3186 """Set device as required replacement. 3187 3188 @params resultdir: The path to result directory. If path not provided 3189 will be attempt to get retrieve it from job 3190 if present. 3191 """ 3192 self.set_device_repair_state( 3193 cros_constants.DEVICE_STATE_NEEDS_REPLACEMENT, 3194 resultdir=resultdir) 3195 3196 def _dut_is_accessible_by_verifier(self): 3197 """Check if DUT accessible by SSH or PING verifier. 3198 3199 @returns: bool, True - verifier marked as success. 3200 False - result not reachable, verifier did not success. 3201 """ 3202 if not self._repair_strategy: 3203 return False 3204 dut_ssh = self._repair_strategy.verifier_is_good('ssh') 3205 dut_ping = self._repair_strategy.verifier_is_good('ping') 3206 return dut_ssh == hosts.VERIFY_SUCCESS or dut_ssh == hosts.VERIFY_SUCCESS 3207 3208 def _stat_if_pingable_but_not_sshable(self): 3209 """Check if DUT pingable but failed SSH verifier.""" 3210 if not self._repair_strategy: 3211 return 3212 dut_ssh = self._repair_strategy.verifier_is_good('ssh') 3213 dut_ping = self._repair_strategy.verifier_is_good('ping') 3214 if (dut_ping == hosts.VERIFY_FAILED 3215 and dut_ssh == hosts.VERIFY_FAILED): 3216 metrics.Counter('chromeos/autotest/dut_pingable_no_ssh').increment( 3217 fields={'host': self.hostname}) 3218 3219 def try_set_device_needs_manual_repair(self): 3220 """Check if device require manual attention to be fixed. 3221 3222 The state 'needs_manual_repair' can be set when auto repair cannot 3223 fix the device due hardware or cable issues. 3224 """ 3225 # ignore the logic if state present 3226 # state can be set by any cros repair actions 3227 if self.get_device_repair_state(): 3228 return 3229 if self._dut_is_accessible_by_verifier(): 3230 # DUT is accessible and we still have many options to repair it. 3231 return 3232 needs_manual_repair = False 3233 dhp = self.health_profile 3234 if dhp and dhp.get_repair_fail_count() > 49: 3235 # 42 = 6 times during 7 days. (every 4 hour repair) 3236 # round up to 50 in case somebody will run some attempt on it. 3237 logging.info( 3238 'DUT is not sshable and fail %s times.' 3239 ' Limit to try repair is 50 times', 3240 dhp.get_repair_fail_count()) 3241 needs_manual_repair = True 3242 3243 if not needs_manual_repair: 3244 # We cannot ssh to the DUT and we have hardware or set-up issues 3245 # with servo then we need request manual repair for the DUT. 3246 servo_state_required_manual_fix = [ 3247 servo_constants.SERVO_STATE_DUT_NOT_CONNECTED, 3248 servo_constants.SERVO_STATE_NEED_REPLACEMENT, 3249 ] 3250 if self.get_servo_state() in servo_state_required_manual_fix: 3251 logging.info( 3252 'DUT required manual repair because it is not sshable' 3253 ' and possible have setup issue with Servo. Please' 3254 ' verify all connections and present of devices.') 3255 needs_manual_repair = True 3256 3257 if needs_manual_repair: 3258 self.set_device_repair_state( 3259 cros_constants.DEVICE_STATE_NEEDS_MANUAL_REPAIR) 3260 3261 def _reboot_labstation_if_needed(self): 3262 """Place request to reboot the labstation if DUT is not sshable. 3263 3264 @returns: None 3265 """ 3266 message_prefix = "Don't need to request servo-host reboot" 3267 if self._dut_is_accessible_by_verifier(): 3268 return 3269 if not self._servo_host: 3270 logging.debug('%s as it not initialized', message_prefix) 3271 return 3272 if not self._servo_host.is_up_fast(): 3273 logging.debug('%s as servo-host is not sshable', message_prefix) 3274 return 3275 if not self._servo_host.is_labstation(): 3276 logging.debug('Servo_v3 is not requested to reboot for the DUT') 3277 return 3278 usb_path = self._servo_host.get_main_servo_usb_path() 3279 if usb_path: 3280 connected_port = os.path.basename(os.path.normpath(usb_path)) 3281 # Directly connected servo to the labstation looks like '1-5.3' 3282 # and when connected by hub - '1-5.2.3' or '1-5.2.1.3'. Where: 3283 # - '1-5' - port on labstation 3284 # - '2' or '2.1' - port on the hub or smart-hub 3285 # - '3' - port on servo hub 3286 if len(connected_port.split('.')) > 2: 3287 logging.debug('%s as servo connected by hub', message_prefix) 3288 return 3289 self._servo_host.request_reboot() 3290 logging.info('Requested labstation reboot because DUT is not sshable') 3291 3292 def is_file_system_writable(self, testdirs=None): 3293 """Check is the file systems are writable. 3294 3295 The standard linux response to certain unexpected file system errors 3296 (including hardware errors in block devices) is to change the file 3297 system status to read-only. This checks that that hasn't happened. 3298 3299 @param testdirs: List of directories to check. If no data provided 3300 then '/mnt/stateful_partition' and '/var/tmp' 3301 directories will be checked. 3302 3303 @returns boolean whether file-system writable. 3304 """ 3305 def _check_dir(testdir): 3306 # check if we can create a file 3307 filename = os.path.join(testdir, 'writable_my_test_file') 3308 command = 'touch %s && rm %s' % (filename, filename) 3309 rv = self.run(command=command, 3310 timeout=30, 3311 ignore_status=True) 3312 is_writable = rv.exit_status == 0 3313 if not is_writable: 3314 logging.info('Cannot create a file in "%s"!' 3315 ' Probably the FS is read-only', testdir) 3316 logging.info("FileSystem is not writable!") 3317 return False 3318 return True 3319 3320 if not testdirs or len(testdirs) == 0: 3321 # N.B. Order matters here: Encrypted stateful is loop-mounted 3322 # from a file in unencrypted stateful, so we don't test for 3323 # errors in encrypted stateful if unencrypted fails. 3324 testdirs = ['/mnt/stateful_partition', '/var/tmp'] 3325 3326 for dir in testdirs: 3327 # loop will be stopped if any directory fill fail the check 3328 try: 3329 if not _check_dir(dir): 3330 return False 3331 except Exception as e: 3332 # here expected only timeout error, all other will 3333 # be catch by 'ignore_status=True' 3334 logging.debug('Fail to check %s to write in it', dir) 3335 return False 3336 return True 3337 3338 def blocking_sync(self, freeze_for_reset=False): 3339 """Sync root device and internal device, via script. 3340 3341 The actual calls end up logged by the run() call, since they're printed 3342 to stdout/stderr in the script. 3343 3344 @param freeze_for_reset: if True, prepare for reset by blocking writes 3345 (only if enable_fs_sync_fsfreeze=True) 3346 """ 3347 3348 if freeze_for_reset and self.USE_FSFREEZE: 3349 logging.info('Blocking sync and freeze') 3350 elif freeze_for_reset: 3351 logging.info('Blocking sync for reset') 3352 else: 3353 logging.info('Blocking sync') 3354 3355 # client/bin is installed on the DUT as /usr/local/autotest/bin 3356 sync_cmd = '/usr/local/autotest/bin/fs_sync.py' 3357 if freeze_for_reset and self.USE_FSFREEZE: 3358 sync_cmd += ' --freeze' 3359 return self.run(sync_cmd) 3360 3361 def set_health_profile_dut_state(self, state): 3362 if not self.health_profile: 3363 logging.debug('Device health profile is not initialized, skip' 3364 ' set dut state.') 3365 return 3366 reset_counters = state in profile_constants.STATES_NEED_RESET_COUNTER 3367 self.health_profile.update_dut_state(state, reset_counters) 3368 3369 def require_snk_mode_in_recovery(self): 3370 """Check whether we need to switch servo_v4 role to snk when 3371 booting into recovery mode. (See crbug.com/1129165) 3372 """ 3373 has_battery = True 3374 # Determine if the host has battery based on host_info first. 3375 power_info = self.host_info_store.get().get_label_value('power') 3376 if power_info: 3377 has_battery = power_info == 'battery' 3378 elif self.is_up_fast(): 3379 # when running local tests host_info is not available, so we 3380 # need to determine whether the host has battery by checking 3381 # from host side. 3382 logging.debug('Label `power` is not found in host_info, checking' 3383 ' if the host has battery from host side.') 3384 has_battery = self.has_battery() 3385 3386 if not has_battery: 3387 logging.info( 3388 '%s does not has battery, snk mode is not needed' 3389 ' for recovery.', self.hostname) 3390 return False 3391 3392 if not self.servo.supports_built_in_pd_control(): 3393 logging.info('Power delivery is not supported on this servo, snk' 3394 ' mode is not needed for recovery.') 3395 return False 3396 try: 3397 battery_percent = self.servo.get('battery_charge_percent') 3398 if battery_percent < cros_constants.MIN_BATTERY_LEVEL: 3399 logging.info( 3400 'Current battery level %s%% below %s%% threshold, we' 3401 ' will attempt to boot host in recovery mode without' 3402 ' changing servo to snk mode. Please note the host may' 3403 ' not able to see usb drive in recovery mode later due' 3404 ' to servo not in snk mode.', battery_percent, 3405 cros_constants.MIN_BATTERY_LEVEL) 3406 return False 3407 except Exception as e: 3408 logging.info( 3409 'Unexpected error occurred when getting' 3410 ' battery_charge_percent from servo; %s', str(e)) 3411 return False 3412 return True 3413 3414 def _set_servo_topology(self): 3415 """Set servo-topology info to the host-info.""" 3416 logging.debug('Try to save servo topology to host-info.') 3417 if not self._servo_host: 3418 logging.debug('Servo host is not initialized.') 3419 return 3420 if not self.is_servo_in_working_state(): 3421 logging.debug('Is servo is not in working state then' 3422 ' update topology is not allowed.') 3423 return 3424 if not self._servo_host.is_servo_topology_supported(): 3425 logging.debug('Servo-topology is not supported.') 3426 return 3427 servo_topology = self._servo_host.get_topology() 3428 if not servo_topology or servo_topology.is_empty(): 3429 logging.debug('Servo topology is empty') 3430 return 3431 servo_topology.save(self.host_info_store) 3432