157 lines
6.2 KiB
Python
Executable File
157 lines
6.2 KiB
Python
Executable File
#!/usr/bin/python2
|
|
# Copyright (c) 2012 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.
|
|
|
|
import argparse
|
|
import logging
|
|
import sys
|
|
import xmlrpclib
|
|
|
|
import common
|
|
|
|
from config import rpm_config
|
|
from autotest_lib.client.common_lib import global_config
|
|
from autotest_lib.client.common_lib.cros import retry
|
|
|
|
try:
|
|
from chromite.lib import metrics
|
|
except ImportError:
|
|
from autotest_lib.client.bin.utils import metrics_mock as metrics
|
|
|
|
RPM_FRONTEND_URI = global_config.global_config.get_config_value('CROS',
|
|
'rpm_frontend_uri', type=str, default='')
|
|
RPM_CALL_TIMEOUT_MINS = rpm_config.getint('RPM_INFRASTRUCTURE',
|
|
'call_timeout_mins')
|
|
|
|
POWERUNIT_HOSTNAME_KEY = 'powerunit_hostname'
|
|
POWERUNIT_OUTLET_KEY = 'powerunit_outlet'
|
|
HYDRA_HOSTNAME_KEY = 'hydra_hostname'
|
|
|
|
|
|
class RemotePowerException(Exception):
|
|
"""This is raised when we fail to set the state of the device's outlet."""
|
|
pass
|
|
|
|
|
|
def set_power(host, new_state, timeout_mins=RPM_CALL_TIMEOUT_MINS):
|
|
"""Sends the power state change request to the RPM Infrastructure.
|
|
|
|
@param host: A CrosHost or ServoHost instance.
|
|
@param new_state: State we want to set the power outlet to.
|
|
"""
|
|
# servo V3 is handled differently from the rest.
|
|
# The best heuristic we have to determine servo V3 is the hostname.
|
|
if host.hostname.endswith('servo'):
|
|
args_tuple = (host.hostname, new_state)
|
|
else:
|
|
info = host.host_info_store.get()
|
|
try:
|
|
args_tuple = (host.hostname,
|
|
info.attributes[POWERUNIT_HOSTNAME_KEY],
|
|
info.attributes[POWERUNIT_OUTLET_KEY],
|
|
info.attributes.get(HYDRA_HOSTNAME_KEY),
|
|
new_state)
|
|
except KeyError as e:
|
|
logging.warning('Powerunit information not found. Missing:'
|
|
' %s in host_info_store.', e)
|
|
raise RemotePowerException('Remote power control is not applicable'
|
|
' for %s, it could be either RPM is not'
|
|
' supported on the rack or powerunit'
|
|
' attributes is not configured in'
|
|
' inventory.' % host.hostname)
|
|
_set_power(args_tuple, timeout_mins)
|
|
|
|
|
|
def _set_power(args_tuple, timeout_mins=RPM_CALL_TIMEOUT_MINS):
|
|
"""Sends the power state change request to the RPM Infrastructure.
|
|
|
|
@param args_tuple: A args tuple for rpc call. See example below:
|
|
(hostname, powerunit_hostname, outlet, hydra_hostname, new_state)
|
|
"""
|
|
client = xmlrpclib.ServerProxy(RPM_FRONTEND_URI,
|
|
verbose=False,
|
|
allow_none=True)
|
|
timeout = None
|
|
result = None
|
|
endpoint = (client.set_power_via_poe if len(args_tuple) == 2
|
|
else client.set_power_via_rpm)
|
|
try:
|
|
timeout, result = retry.timeout(endpoint,
|
|
args=args_tuple,
|
|
timeout_sec=timeout_mins * 60,
|
|
default_result=False)
|
|
except Exception as e:
|
|
logging.exception(e)
|
|
raise RemotePowerException(
|
|
'Client call exception (%s): %s' % (RPM_FRONTEND_URI, e))
|
|
if timeout:
|
|
raise RemotePowerException(
|
|
'Call to RPM Infrastructure timed out (%s).' % RPM_FRONTEND_URI)
|
|
if not result:
|
|
error_msg = ('Failed to change outlet status for host: %s to '
|
|
'state: %s.' % (args_tuple[0], args_tuple[-1]))
|
|
logging.error(error_msg)
|
|
if len(args_tuple) > 2:
|
|
# Collect failure metrics if we set power via rpm.
|
|
_send_rpm_failure_metrics(args_tuple[0], args_tuple[1],
|
|
args_tuple[2])
|
|
raise RemotePowerException(error_msg)
|
|
|
|
|
|
def _send_rpm_failure_metrics(hostname, rpm_host, outlet):
|
|
metrics_fields = {
|
|
'hostname': hostname,
|
|
'rpm_host': rpm_host,
|
|
'outlet': outlet
|
|
}
|
|
metrics.Counter('chromeos/autotest/rpm/rpm_failure2').increment(
|
|
fields=metrics_fields)
|
|
|
|
|
|
def parse_options():
|
|
"""Parse the user supplied options."""
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('-m', '--machine', dest='machine', required=True,
|
|
help='Machine hostname to change outlet state.')
|
|
parser.add_argument('-s', '--state', dest='state', required=True,
|
|
choices=['ON', 'OFF', 'CYCLE'],
|
|
help='Power state to set outlet: ON, OFF, CYCLE')
|
|
parser.add_argument('-p', '--powerunit_hostname', dest='p_hostname',
|
|
help='Powerunit hostname of the host.')
|
|
parser.add_argument('-o', '--outlet', dest='outlet',
|
|
help='Outlet of the host.')
|
|
parser.add_argument('-y', '--hydra_hostname', dest='hydra', default='',
|
|
help='Hydra hostname of the host.')
|
|
parser.add_argument('-d', '--disable_emails', dest='disable_emails',
|
|
help='Hours to suspend RPM email notifications.')
|
|
parser.add_argument('-e', '--enable_emails', dest='enable_emails',
|
|
action='store_true',
|
|
help='Resume RPM email notifications.')
|
|
return parser.parse_args()
|
|
|
|
|
|
def main():
|
|
"""Entry point for rpm_client script."""
|
|
options = parse_options()
|
|
if options.machine.endswith('servo'):
|
|
args_tuple = (options.machine, options.state)
|
|
elif not options.p_hostname or not options.outlet:
|
|
print "Powerunit hostname and outlet info are required for DUT."
|
|
return
|
|
else:
|
|
args_tuple = (options.machine, options.p_hostname, options.outlet,
|
|
options.hydra, options.state)
|
|
_set_power(args_tuple)
|
|
|
|
if options.disable_emails is not None:
|
|
client = xmlrpclib.ServerProxy(RPM_FRONTEND_URI, verbose=False)
|
|
client.suspend_emails(options.disable_emails)
|
|
if options.enable_emails:
|
|
client = xmlrpclib.ServerProxy(RPM_FRONTEND_URI, verbose=False)
|
|
client.resume_emails()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|