1#!/usr/bin/env python2 2# Copyright 2020 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5"""Functional to validate RPM configs in the lab.""" 6 7from __future__ import absolute_import 8from __future__ import division 9from __future__ import print_function 10 11import os 12import logging 13import six 14import time 15 16import common 17from autotest_lib.client.common_lib import error 18from autotest_lib.site_utils.rpm_control_system import rpm_client 19from autotest_lib.site_utils.admin_audit import constants 20 21 22def _is_rpm_config_present(host): 23 """Check if RPM config data present. 24 25 @param host: any host with has host_info_store field 26 27 @raises: error.AutoservError if config present partially. 28 """ 29 host_info = host.host_info_store.get() 30 powerunit_hostname = host_info.attributes.get('powerunit_hostname') 31 powerunit_outlet = host_info.attributes.get('powerunit_outlet') 32 33 powerunit_hasinfo = (bool(powerunit_hostname), bool(powerunit_outlet)) 34 35 if powerunit_hasinfo == (True, True): 36 return True 37 elif powerunit_hasinfo == (False, False): 38 set_rpm_state(host, constants.RPM_STATE_MISSING_CONFIG) 39 return False 40 else: 41 set_rpm_state(host, constants.RPM_STATE_WRONG_CONFIG) 42 msg = "inconsistent power info: %s %s" % (powerunit_hostname, 43 powerunit_outlet) 44 logging.error(msg) 45 raise error.AutoservError(msg) 46 47 48def _set_power_off(host, quite=False): 49 try: 50 rpm_client.set_power(host, "OFF") 51 except Exception as e: 52 # We do not want to leave RPM outlets in off state 53 _set_power_on(host, quite=True) 54 if not quite: 55 logging.debug('Fail to set state OFF for RPM; %s', str(e)) 56 six.reraise(e.__class__, e) 57 58 59def _set_power_on(host, quite=False): 60 try: 61 rpm_client.set_power(host, "ON") 62 except Exception as e: 63 # We do not want to leave RPM outlets in off state 64 if not quite: 65 logging.debug('Fail to set state ON for RPM; %s', str(e)) 66 six.reraise(e.__class__, e) 67 68 69def _check_rpm_power_delivery_with_battery(host): 70 """Verify RPM for device which has battery. 71 72 Verification based on check if device can charging. 73 @param host: any host with has host_info_store field 74 """ 75 76 def validate_power_state(is_on, wait_time): 77 deadline = time.time() + wait_time 78 while time.time() < deadline: 79 if not host.is_up(): 80 # DUT is not available by ssh will try again. 81 continue 82 power_info = host.get_power_supply_info() 83 try: 84 is_online = power_info['Line Power']['online'] == 'yes' 85 if is_on == is_online: 86 break 87 except KeyError: 88 logging.debug('(Not critical) Fail check online power') 89 time.sleep(5) 90 else: 91 expected_state = 'ON' if is_on else 'OFF' 92 msg = "%s didn't enter %s state in %s seconds" % ( 93 host.hostname, 94 expected_state, 95 wait_time, 96 ) 97 raise Exception(msg) 98 99 logging.info("Cutting down wall power for %s...", host.hostname) 100 _set_power_off(host) 101 validate_power_state(False, host.WAIT_DOWN_REBOOT_TIMEOUT) 102 103 logging.info("Re-enable wall power for %s...", host.hostname) 104 _set_power_on(host) 105 validate_power_state(True, host.BOOT_TIMEOUT) 106 logging.info("RPM Check Successful") 107 108 109def _check_rpm_power_delivery_without_battery(host): 110 """Verify RPM for device which has battery. 111 112 Verification based on check if device online or offline. 113 @param host: any host with has host_info_store field 114 """ 115 logging.info("Cutting down wall power for %s...", host.hostname) 116 _set_power_off(host) 117 if not host.wait_down(timeout=host.WAIT_DOWN_REBOOT_TIMEOUT): 118 msg = "%s didn't enter OFF state in %s seconds" % ( 119 host.hostname, 120 host.WAIT_DOWN_REBOOT_TIMEOUT, 121 ) 122 raise Exception(msg) 123 124 logging.info("Re-enable wall power for %s...", host.hostname) 125 _set_power_on(host) 126 if not host.wait_up(timeout=host.BOOT_TIMEOUT): 127 msg = "%s didn't enter ON state in %s seconds" % ( 128 host.hostname, 129 host.BOOT_TIMEOUT, 130 ) 131 raise Exception(msg) 132 133 134def verify_unsafe(host): 135 """Verify that we can power cycle a host with its RPM information. 136 Any host without RPM information will be safely skipped. 137 138 This procedure is intended to catch inaccurate RPM info when the 139 host is deployed. 140 141 @param host: any host with has host_info_store field 142 143 @raises: error.AutoservError if config present partially or wrong. 144 error.AutoservError if device does not specify power label. 145 error.AutoservError if device has mismatch between host-info 146 and device. 147 """ 148 logging.info("Start RPM check for: %s", host.hostname) 149 if not hasattr(host, 'host_info_store'): 150 logging.info('Host:%s does not have host_info_store attribute', 151 host.hostname) 152 return 153 if not _is_rpm_config_present(host): 154 logging.info("RPM config is not present. Skipping check.") 155 return 156 157 # In the lab we need trust that host-info will be correct. 158 power_info = host.host_info_store.get().get_label_value('power') 159 if not power_info: 160 raise error.AutoservError( 161 'Could not detect power-info in host-info. The information' 162 ' has to be provided by manufacture configs. Please file' 163 ' the bug agains Fleet Inventory') 164 has_battery = power_info == 'battery' 165 166 # Verify host-info against manufactory configs 167 try: 168 info = host.get_power_supply_info() 169 except Exception as e: 170 logging.debug('(Not critical) %s', e) 171 raise error.AutoservError('Could not detect power supply info') 172 if 'Battery' in info: 173 if not has_battery: 174 raise error.AutoservError( 175 'Unexpected detected battery on the device') 176 elif has_battery: 177 raise error.AutoservError( 178 'Battery is not detected on the device. But expected') 179 # Try to use RPM config to confirm tha information is correct. 180 try: 181 if has_battery: 182 _check_rpm_power_delivery_with_battery(host) 183 else: 184 _check_rpm_power_delivery_without_battery(host) 185 except Exception as e: 186 set_rpm_state(host, constants.RPM_STATE_WRONG_CONFIG) 187 logging.debug('(Not critical) %s', e) 188 msg = getattr(e, 'message') if hasattr(e, 'message') else str(e) 189 logging.info('RPM check fails! %s', msg) 190 six.reraise(error.AutoservError, e) 191 else: 192 set_rpm_state(host, constants.RPM_STATE_WORKING) 193 logging.info("The host passed RPM config check!") 194 195 196def set_rpm_state(host, new_state): 197 """Set RPM state info labels to dut host_info. 198 199 @param host: Any host with has host_info_store field 200 @param new_state: New RPM state. Possible values are 201 listed in RPM_STATES_SUPPORTED. 202 """ 203 if not new_state: 204 logging.debug('RPM state is not specified.') 205 return 206 if new_state not in constants.RPM_STATES_SUPPORTED: 207 logging.debug('Not supported Incorrect RPM state.') 208 return 209 host_info = host.host_info_store.get() 210 prefix = constants.RPM_STATE_LABEL_PREFIX 211 old_state = host_info.get_label_value(prefix) 212 if old_state == new_state: 213 # do not need update 214 logging.debug('RPM state not changed. Skiping update') 215 return 216 host_info.set_version_label(prefix, new_state) 217 host.host_info_store.commit(host_info) 218 logging.info('Rpm state updated to %s (previous: %s)', new_state, 219 old_state) 220