170 lines
6.4 KiB
Python
170 lines
6.4 KiB
Python
#!/usr/bin/env python3
|
|
# 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 common
|
|
from autotest_lib.site_utils.admin_audit import constants
|
|
|
|
|
|
class BatteryValidator(object):
|
|
"""Battery validator provides capacity verification of battery on the host.
|
|
|
|
The state detection and set state as:
|
|
- NORMAL - battery capacity >= 70%
|
|
- ACCEPTABLE - battery capacity >= 40%
|
|
- NEED_REPLACEMENT - battery capacity < 40%
|
|
- UNKNOWN - logic cannot read data to specify the state
|
|
- NOT_DETECTED - battery is not present on the host
|
|
"""
|
|
# Battery capacity levels
|
|
BATTER_NORMAL_LEVEL = 70
|
|
BATTER_ACCEPTABLE_LEVEL = 40
|
|
|
|
# Attempts to try read battery data
|
|
READ_DATA_RETRY_COUNT = 3
|
|
|
|
def __init__(self, host):
|
|
"""Initialize the battery validator.
|
|
|
|
@params host CrosHost instance.
|
|
"""
|
|
self._host = host
|
|
self._battery_path = None
|
|
self.charge_full = 0
|
|
self.charge_full_design = 0
|
|
|
|
def _read_battery_path(self):
|
|
"""Detect path to battery properties on the host."""
|
|
self._battery_path = None
|
|
info = self._host.get_power_supply_info()
|
|
if 'Battery' not in info:
|
|
logging.debug('Battery is not presented but expected!'
|
|
' In some cases it possible.')
|
|
return None
|
|
self._battery_path = info['Battery']['path']
|
|
logging.info('Battery path: %s', self._battery_path)
|
|
return self._battery_path
|
|
|
|
def is_battery_expected(self):
|
|
"""Verify if battery expected on the host based on host info."""
|
|
host_info = self._host.host_info_store.get()
|
|
return host_info.get_label_value('power') == 'battery'
|
|
|
|
def _read_data_from_host(self):
|
|
"""Read data from the host."""
|
|
|
|
def read_val(file_name, field_type):
|
|
"""Read a value from file."""
|
|
try:
|
|
path = os.path.join(self._battery_path, file_name)
|
|
out = self._host.run('cat %s' % path,
|
|
ignore_status=True).stdout.strip()
|
|
return field_type(out)
|
|
except:
|
|
return field_type(0)
|
|
|
|
self.charge_full = read_val('charge_full', float)
|
|
self.charge_full_design = read_val('charge_full_design', float)
|
|
cycle_count = read_val('cycle_count', int)
|
|
logging.debug('Battery cycle_count: %d', cycle_count)
|
|
|
|
def _validate_by_host(self):
|
|
"""Validate battery by reading data from the host."""
|
|
logging.debug('Try to validate from host side.')
|
|
if self._host.is_up():
|
|
for _ in range(self.READ_DATA_RETRY_COUNT):
|
|
try:
|
|
self._read_battery_path()
|
|
if not self._battery_path:
|
|
logging.info('Battery is not present/found on host')
|
|
return self._update_host_info(
|
|
constants.HW_STATE_NOT_DETECTED)
|
|
self._read_data_from_host()
|
|
return self._update_battery_state()
|
|
except Exception as e:
|
|
logging.debug('(Not critical) %s', e)
|
|
return None
|
|
|
|
def _validate_by_servo(self):
|
|
"""Validate battery by servo access."""
|
|
servo = self._host.servo
|
|
logging.debug('Try to validate from servo side.')
|
|
if servo:
|
|
for _ in range(self.READ_DATA_RETRY_COUNT):
|
|
try:
|
|
if not servo.has_control('battery_full_charge_mah'):
|
|
break
|
|
self.charge_full = servo.get('battery_full_charge_mah')
|
|
self.charge_full_design = servo.get(
|
|
'battery_full_design_mah')
|
|
return self._update_battery_state()
|
|
except Exception as e:
|
|
logging.debug('(Not critical) %s', e)
|
|
return None
|
|
|
|
def validate(self):
|
|
"""Validate battery and update state.
|
|
|
|
Try to validate from host if device is sshable if not then try
|
|
read battery info by servo.
|
|
"""
|
|
logging.info('Starting battery validation.')
|
|
state = None
|
|
if not self.is_battery_expected():
|
|
state = self._update_host_info(constants.HW_STATE_NOT_DETECTED)
|
|
if not state:
|
|
state = self._validate_by_host()
|
|
if not state:
|
|
state = self._validate_by_servo()
|
|
if not state:
|
|
state = self._update_host_info(constants.HW_STATE_UNKNOWN)
|
|
return state
|
|
|
|
def _update_battery_state(self):
|
|
"""Update battery state based on batter charging capacity
|
|
|
|
The logic will update state based on:
|
|
if capacity >= 70% then NORMAL
|
|
if capacity >= 40% then ACCEPTABLE
|
|
if capacity < 40% then NEED_REPLACEMENT
|
|
"""
|
|
if self.charge_full == 0:
|
|
logging.debug('charge_full is 0. Skip update battery_state!')
|
|
return
|
|
if self.charge_full_design == 0:
|
|
logging.debug('charge_full_design is 0.'
|
|
' Skip update battery_state!')
|
|
return
|
|
capacity = (100.0 * self.charge_full / self.charge_full_design)
|
|
logging.debug('Battery capacity: %d', capacity)
|
|
|
|
if capacity >= self.BATTER_NORMAL_LEVEL:
|
|
return self._update_host_info(constants.HW_STATE_NORMAL)
|
|
if capacity >= self.BATTER_ACCEPTABLE_LEVEL:
|
|
return self._update_host_info(constants.HW_STATE_ACCEPTABLE)
|
|
return self._update_host_info(constants.HW_STATE_NEED_REPLACEMENT)
|
|
|
|
def _update_host_info(self, state):
|
|
"""Update state value to the battery_state in the host_info
|
|
|
|
@param state: new state value for the label
|
|
"""
|
|
if self._host:
|
|
state_prefix = constants.BATTERY_STATE_PREFIX
|
|
host_info = self._host.host_info_store.get()
|
|
old_state = host_info.get_label_value(state_prefix)
|
|
host_info.set_version_label(state_prefix, state)
|
|
logging.info('Set %s as `%s` (previous: `%s`)', state_prefix,
|
|
state, old_state)
|
|
self._host.host_info_store.commit(host_info)
|
|
return state
|