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