blob: 8c3a3ca1f09cb3000d7ace42d2f05b7afe16e6f9 [file] [log] [blame]
#!/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: ' + str(e))
if timeout:
raise RemotePowerException(
'Call to RPM Infrastructure timed out.')
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[1], args_tuple[2])
raise RemotePowerException(error_msg)
def _send_rpm_failure_metrics(rpm_host, outlet):
metrics_fields = {'rpm_host': rpm_host, 'outlet': outlet}
metrics.Counter('chromeos/autotest/repair/rpm_failure').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())