• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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