#!/usr/bin/env python2 # Copyright 2020 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Functional to validate RPM configs in the lab.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function import os import logging import six import time import common from autotest_lib.client.common_lib import error from autotest_lib.site_utils.rpm_control_system import rpm_client from autotest_lib.site_utils.admin_audit import constants def _is_rpm_config_present(host): """Check if RPM config data present. @param host: any host with has host_info_store field @raises: error.AutoservError if config present partially. """ host_info = host.host_info_store.get() powerunit_hostname = host_info.attributes.get('powerunit_hostname') powerunit_outlet = host_info.attributes.get('powerunit_outlet') powerunit_hasinfo = (bool(powerunit_hostname), bool(powerunit_outlet)) if powerunit_hasinfo == (True, True): return True elif powerunit_hasinfo == (False, False): set_rpm_state(host, constants.RPM_STATE_MISSING_CONFIG) return False else: set_rpm_state(host, constants.RPM_STATE_WRONG_CONFIG) msg = "inconsistent power info: %s %s" % (powerunit_hostname, powerunit_outlet) logging.error(msg) raise error.AutoservError(msg) def _set_power_off(host, quite=False): try: rpm_client.set_power(host, "OFF") except Exception as e: # We do not want to leave RPM outlets in off state _set_power_on(host, quite=True) if not quite: logging.debug('Fail to set state OFF for RPM; %s', str(e)) six.reraise(e.__class__, e) def _set_power_on(host, quite=False): try: rpm_client.set_power(host, "ON") except Exception as e: # We do not want to leave RPM outlets in off state if not quite: logging.debug('Fail to set state ON for RPM; %s', str(e)) six.reraise(e.__class__, e) def _check_rpm_power_delivery_with_battery(host): """Verify RPM for device which has battery. Verification based on check if device can charging. @param host: any host with has host_info_store field """ def validate_power_state(is_on, wait_time): deadline = time.time() + wait_time while time.time() < deadline: if not host.is_up(): # DUT is not available by ssh will try again. continue power_info = host.get_power_supply_info() try: is_online = power_info['Line Power']['online'] == 'yes' if is_on == is_online: break except KeyError: logging.debug('(Not critical) Fail check online power') time.sleep(5) else: expected_state = 'ON' if is_on else 'OFF' msg = "%s didn't enter %s state in %s seconds" % ( host.hostname, expected_state, wait_time, ) raise Exception(msg) logging.info("Cutting down wall power for %s...", host.hostname) _set_power_off(host) validate_power_state(False, host.WAIT_DOWN_REBOOT_TIMEOUT) logging.info("Re-enable wall power for %s...", host.hostname) _set_power_on(host) validate_power_state(True, host.BOOT_TIMEOUT) logging.info("RPM Check Successful") def _check_rpm_power_delivery_without_battery(host): """Verify RPM for device which has battery. Verification based on check if device online or offline. @param host: any host with has host_info_store field """ logging.info("Cutting down wall power for %s...", host.hostname) _set_power_off(host) if not host.wait_down(timeout=host.WAIT_DOWN_REBOOT_TIMEOUT): msg = "%s didn't enter OFF state in %s seconds" % ( host.hostname, host.WAIT_DOWN_REBOOT_TIMEOUT, ) raise Exception(msg) logging.info("Re-enable wall power for %s...", host.hostname) _set_power_on(host) if not host.wait_up(timeout=host.BOOT_TIMEOUT): msg = "%s didn't enter ON state in %s seconds" % ( host.hostname, host.BOOT_TIMEOUT, ) raise Exception(msg) def verify_unsafe(host): """Verify that we can power cycle a host with its RPM information. Any host without RPM information will be safely skipped. This procedure is intended to catch inaccurate RPM info when the host is deployed. @param host: any host with has host_info_store field @raises: error.AutoservError if config present partially or wrong. error.AutoservError if device does not specify power label. error.AutoservError if device has mismatch between host-info and device. """ logging.info("Start RPM check for: %s", host.hostname) if not hasattr(host, 'host_info_store'): logging.info('Host:%s does not have host_info_store attribute', host.hostname) return if not _is_rpm_config_present(host): logging.info("RPM config is not present. Skipping check.") return # In the lab we need trust that host-info will be correct. power_info = host.host_info_store.get().get_label_value('power') if not power_info: raise error.AutoservError( 'Could not detect power-info in host-info. The information' ' has to be provided by manufacture configs. Please file' ' the bug agains Fleet Inventory') has_battery = power_info == 'battery' # Verify host-info against manufactory configs try: info = host.get_power_supply_info() except Exception as e: logging.debug('(Not critical) %s', e) raise error.AutoservError('Could not detect power supply info') if 'Battery' in info: if not has_battery: raise error.AutoservError( 'Unexpected detected battery on the device') elif has_battery: raise error.AutoservError( 'Battery is not detected on the device. But expected') # Try to use RPM config to confirm tha information is correct. try: if has_battery: _check_rpm_power_delivery_with_battery(host) else: _check_rpm_power_delivery_without_battery(host) except Exception as e: set_rpm_state(host, constants.RPM_STATE_WRONG_CONFIG) logging.debug('(Not critical) %s', e) msg = getattr(e, 'message') if hasattr(e, 'message') else str(e) logging.info('RPM check fails! %s', msg) six.reraise(error.AutoservError, e) else: set_rpm_state(host, constants.RPM_STATE_WORKING) logging.info("The host passed RPM config check!") def set_rpm_state(host, new_state): """Set RPM state info labels to dut host_info. @param host: Any host with has host_info_store field @param new_state: New RPM state. Possible values are listed in RPM_STATES_SUPPORTED. """ if not new_state: logging.debug('RPM state is not specified.') return if new_state not in constants.RPM_STATES_SUPPORTED: logging.debug('Not supported Incorrect RPM state.') return host_info = host.host_info_store.get() prefix = constants.RPM_STATE_LABEL_PREFIX old_state = host_info.get_label_value(prefix) if old_state == new_state: # do not need update logging.debug('RPM state not changed. Skiping update') return host_info.set_version_label(prefix, new_state) host.host_info_store.commit(host_info) logging.info('Rpm state updated to %s (previous: %s)', new_state, old_state)