220 lines
7.8 KiB
Python
220 lines
7.8 KiB
Python
#!/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)
|