[autotest] rpm dispatcher/controller get power info from frontend server
This is the 3rd CL for making the rpm infra read rpm/outlet/hydra
info from AFE.
Previously, rpm dispatcher/controler uses regular expression pattern
to determine the hydra hostname. And outlet(ports) must be labeled
with hostname beforehand.
This cl makes rpm dispatcher/controler receive a PowerUnitInfo instance
from frontend server, which includes the rpm(poe)/outlet(port)/hydra
information for the device.
CQ-DEPEND=CL:212346
BUG=chromium:392548
TEST=unittest; Integration tests with other cls in this series, set
up a local rpm server and power cycle devices.
Change-Id: I492531c8dc3f134d32f73d0a0560bf54f8a28b70
Reviewed-on: https://chromium-review.googlesource.com/212357
Tested-by: Fang Deng <fdeng@chromium.org>
Reviewed-by: Simran Basi <sbasi@chromium.org>
Commit-Queue: Fang Deng <fdeng@chromium.org>
diff --git a/site_utils/rpm_control_system/rpm_config.ini b/site_utils/rpm_control_system/rpm_config.ini
index a5aed76..9557a7e 100644
--- a/site_utils/rpm_control_system/rpm_config.ini
+++ b/site_utils/rpm_control_system/rpm_config.ini
@@ -45,15 +45,7 @@
password: google
servo_interface_mapping_file: servo_interface_mapping.csv
-[hydra1]
-hostname: chromeos-197-hydra1.mtv
-username: autotest
-password: s3rialsw
-admin_username: root
-admin_password: tslinux
-
-[hydra2]
-hostname: chromeos-197-hydra2.mtv
+[HYDRA]
username: autotest
password: s3rialsw
admin_username: root
diff --git a/site_utils/rpm_control_system/rpm_controller.py b/site_utils/rpm_control_system/rpm_controller.py
index e306033..825d874 100644
--- a/site_utils/rpm_control_system/rpm_controller.py
+++ b/site_utils/rpm_control_system/rpm_controller.py
@@ -10,7 +10,6 @@
import pexpect
import Queue
import re
-import subprocess
import threading
import time
@@ -29,34 +28,9 @@
'call_timeout_mins')
SET_POWER_STATE_TIMEOUT_SECONDS = rpm_config.getint(
'RPM_INFRASTRUCTURE', 'set_power_state_timeout_seconds')
-HYDRA_HOST_REGX = 'chromeos2-row\d+-rack(\d+)-rpm1$'
PROCESS_TIMEOUT_BUFFER = 30
-def get_hydra_name(hostname):
- """Return hydra name given rpm hostname.
-
- @param hostname: the hostname of rpm,
- e.g. chromeos2-row2-rack1-rpm1, chromeos-rack1-rpm1
-
- @returns: The corresponding hydra name or None if rpm is not
- behind a known hydra.
-
- """
- # TODO(fdeng): Replace the harded coded mapping with something
- # dynamic. crbug.com/376538
- m = re.match(HYDRA_HOST_REGX, hostname)
- if m:
- rack = int(m.group(1))
- # chromeos2-row{..}-rack{1..7} --> Hydra1
- if rack >= 1 and rack <= 7:
- return 'hydra1'
- # chromeos2-row{..}-rack{8..11} --> Hydra2
- elif rack >= 8 and rack <= 11:
- return 'hydra2'
- return None
-
-
class RPMController(object):
"""
This abstract class implements RPM request queueing and
@@ -65,7 +39,7 @@
The actual interaction with the RPM device will be implemented
by the RPM specific subclasses.
- It assumes that you know the RPM hostname and that the DUT is on
+ It assumes that you know the RPM hostname and that the device is on
the specified RPM.
This class also allows support for RPM devices that can be accessed
@@ -109,8 +83,8 @@
DEVICE_PROMPT = '$'
PASSWORD_PROMPT = 'Password:'
# The state change command can be any string format but must accept 2 vars:
- # state followed by DUT/Plug name.
- STATE_CMD = '%s %s'
+ # state followed by device/Plug name.
+ SET_STATE_CMD = '%s %s'
SUCCESS_MSG = None # Some RPM's may not return a success msg.
NEW_STATE_ON = 'ON'
@@ -119,7 +93,7 @@
TYPE = 'Should set TYPE in subclass.'
- def __init__(self, rpm_hostname, hydra_name=None):
+ def __init__(self, rpm_hostname, hydra_hostname=None):
"""
RPMController Constructor.
To be called by subclasses.
@@ -131,13 +105,10 @@
self.request_queue = Queue.Queue()
self._running = False
self.is_running_lock = threading.Lock()
- self.behind_hydra = False
# If a hydra name is provided by the subclass then we know we are
# talking to an rpm behind a hydra device.
- if hydra_name:
- self.behind_hydra = True
- self.hydra_name = hydra_name
- self._hydra_hostname = rpm_config.get(hydra_name,'hostname')
+ self.hydra_hostname = hydra_hostname if hydra_hostname else None
+ self.behind_hydra = hydra_hostname is not None
def _start_processing_requests(self):
@@ -180,13 +151,14 @@
threading.Thread(target=rpm_controller.run).start()
Requests are in the format of:
- [dut_hostname, new_state, condition_var, result]
+ [powerunit_info, new_state, condition_var, result]
Run will set the result with the correct value.
"""
while not self.request_queue.empty():
try:
result = multiprocessing.Value(ctypes.c_bool, False)
request = self.request_queue.get()
+ device_hostname = request['powerunit_info'].device_hostname
if (datetime.datetime.utcnow() > (request['start_time'] +
datetime.timedelta(minutes=RPM_CALL_TIMEOUT_MINS))):
logging.error('The request was waited for too long to be '
@@ -204,7 +176,7 @@
PROCESS_TIMEOUT_BUFFER)
if process.is_alive():
logging.debug('%s: process (%s) still running, will be '
- 'terminated!', request['dut'], process.pid)
+ 'terminated!', device_hostname, process.pid)
process.terminate()
is_timeout.value = True
@@ -214,10 +186,10 @@
'seconds.' % SET_POWER_STATE_TIMEOUT_SECONDS)
if not result.value:
logging.error('Request to change %s to state %s failed.',
- request['dut'], request['new_state'])
+ device_hostname, request['new_state'])
except Exception as e:
logging.error('Request to change %s to state %s failed: '
- 'Raised exception: %s', request['dut'],
+ 'Raised exception: %s', device_hostname,
request['new_state'], e)
result.value = False
@@ -227,13 +199,13 @@
def _process_request(self, request, result, is_timeout):
- """Process the request to change a DUT's outlet state.
+ """Process the request to change a device's outlet state.
The call of set_power_state is made in a new running process. If it
takes longer than SET_POWER_STATE_TIMEOUT_SECONDS, the request will be
timed out.
- @param request: A request to change a DUT's outlet state.
+ @param request: A request to change a device's outlet state.
@param result: A Value object passed to the new process for the caller
thread to retrieve the result.
@param is_timeout: A Value object passed to the new process for the
@@ -261,7 +233,7 @@
log_filename_format=log_filename_format,
use_log_server=False)
logging.info('Failed to set up logging through log server: %s', e)
- kwargs = {'dut_hostname':request['dut'],
+ kwargs = {'powerunit_info':request['powerunit_info'],
'new_state':request['new_state']}
is_timeout_value, result_value = retry.timeout(
self.set_power_state,
@@ -272,20 +244,20 @@
is_timeout.value = is_timeout_value
- def queue_request(self, dut_hostname, new_state):
+ def queue_request(self, powerunit_info, new_state):
"""
- Queues up a requested state change for a DUT's outlet.
+ Queues up a requested state change for a device's outlet.
Requests are in the format of:
- [dut_hostname, new_state, condition_var, result]
+ [powerunit_info, new_state, condition_var, result]
Run will set the result with the correct value.
- @param dut_hostname: hostname of DUT whose outlet we want to change.
+ @param powerunit_info: And PowerUnitInfo instance.
@param new_state: ON/OFF/CYCLE - state or action we want to perform on
the outlet.
"""
request = {}
- request['dut'] = dut_hostname
+ request['powerunit_info'] = powerunit_info
request['new_state'] = new_state
request['start_time'] = datetime.datetime.utcnow()
# Reserve a spot for the result to be stored.
@@ -310,7 +282,7 @@
if not ssh:
return
ssh.expect(RPMController.PASSWORD_PROMPT, timeout=60)
- ssh.sendline(rpm_config.get(self.hydra_name, 'admin_password'))
+ ssh.sendline(rpm_config.get('HYDRA', 'admin_password'))
ssh.expect(RPMController.HYDRA_PROMPT)
ssh.sendline(RPMController.CLI_CMD)
cli_prompt_re = re.compile(RPMController.CLI_PROMPT)
@@ -362,7 +334,7 @@
return False
if response == 0:
try:
- ssh.sendline(rpm_config.get(self.hydra_name,'password'))
+ ssh.sendline(rpm_config.get('HYDRA','password'))
ssh.sendline('')
response = ssh.expect_list(
[re.compile(RPMController.USERNAME_PROMPT),
@@ -409,11 +381,11 @@
would be if another user is logged into the device.
"""
if admin_override:
- username = rpm_config.get(self.hydra_name, 'admin_username')
+ username = rpm_config.get('HYDRA', 'admin_username')
else:
- username = '%s:%s' % (rpm_config.get(self.hydra_name,'username'),
+ username = '%s:%s' % (rpm_config.get('HYDRA','username'),
self.hostname)
- cmd = RPMController.SSH_LOGIN_CMD % (username, self._hydra_hostname)
+ cmd = RPMController.SSH_LOGIN_CMD % (username, self.hydra_hostname)
num_attempts = 0
while num_attempts < RPMController.HYDRA_MAX_CONNECT_RETRIES:
try:
@@ -490,7 +462,7 @@
time.sleep(5)
- def set_power_state(self, dut_hostname, new_state):
+ def set_power_state(self, powerunit_info, new_state):
"""
Set the state of the dut's outlet on this RPM.
@@ -502,7 +474,7 @@
proper connection and state change code. And the subclass will handle
accessing the RPM devices.
- @param dut_hostname: hostname of DUT whose outlet we want to change.
+ @param powerunit_info: An instance of PowerUnitInfo.
@param new_state: ON/OFF/CYCLE - state or action we want to perform on
the outlet.
@@ -513,16 +485,16 @@
if not ssh:
return False
if new_state == self.NEW_STATE_CYCLE:
- logging.debug('Beginning Power Cycle for DUT: %s',
- dut_hostname)
- result = self._change_state(dut_hostname, self.NEW_STATE_OFF, ssh)
+ logging.debug('Beginning Power Cycle for device: %s',
+ powerunit_info.device_hostname)
+ result = self._change_state(powerunit_info, self.NEW_STATE_OFF, ssh)
if not result:
return result
time.sleep(RPMController.CYCLE_SLEEP_TIME)
- result = self._change_state(dut_hostname, self.NEW_STATE_ON, ssh)
+ result = self._change_state(powerunit_info, self.NEW_STATE_ON, ssh)
else:
- # Try to change the state of the DUT's power outlet.
- result = self._change_state(dut_hostname, new_state, ssh)
+ # Try to change the state of the device's power outlet.
+ result = self._change_state(powerunit_info, new_state, ssh)
# Terminate hydra connection if necessary.
self._logout(ssh)
@@ -530,14 +502,14 @@
return result
- def _change_state(self, dut_hostname, new_state, ssh):
+ def _change_state(self, powerunit_info, new_state, ssh):
"""
Perform the actual state change operation.
Once we have established communication with the RPM this method is
responsible for changing the state of the RPM outlet.
- @param dut_hostname: hostname of DUT whose outlet we want to change.
+ @param powerunit_info: An instance of PowerUnitInfo.
@param new_state: ON/OFF - state or action we want to perform on
the outlet.
@param ssh: The ssh connection used to execute the state change commands
@@ -546,17 +518,25 @@
@return: True if the attempt to change power state was successful,
False otherwise.
"""
- ssh.sendline(self.SET_STATE_CMD % (new_state, dut_hostname))
+ outlet = powerunit_info.outlet
+ device_hostname = powerunit_info.device_hostname
+ if not outlet:
+ logging.error('Request to change outlet for device: %s to new '
+ 'state %s failed: outlet is unknown, please '
+ 'make sure POWERUNIT_OUTLET exist in the host\'s '
+ 'attributes in afe.', device_hostname, new_state)
+ ssh.sendline(self.SET_STATE_CMD % (new_state, outlet))
if self.SUCCESS_MSG:
# If this RPM device returns a success message check for it before
# continuing.
try:
ssh.expect(self.SUCCESS_MSG, timeout=60)
except pexpect.ExceptionPexpect:
- logging.error('Request to change outlet for DUT: %s to new '
- 'state %s failed.', dut_hostname, new_state)
+ logging.error('Request to change outlet for device: %s to new '
+ 'state %s failed.', device_hostname, new_state)
return False
- logging.debug('Outlet for DUT: %s set to %s', dut_hostname, new_state)
+ logging.debug('Outlet for device: %s set to %s', device_hostname,
+ new_state)
return True
@@ -570,37 +550,6 @@
return self.TYPE
- def get_next_rpm_hostname(self):
- """Return the hostname of the next RPM in the same location if it
- exists.
-
- For example chromeos3-rack2-row3 may have rpm1 and rpm2.
-
- @returns Hostname of the next rpm or None.
- """
- if self.behind_hydra:
- # For now lets not do the hydra-case. It would require us to log
- # into the admin console, and see if an entry exists for the next
- # RPM hostname. This would impact the run time of any failure that
- # occurs with an RPM behind the hydra, as well as constantly
- # disconnecting the lab admins from the admin console.
- return None
- # Determine the RPM location and number.
- hostname_regex = '(?P<LOCATION>.*)-rpm(?P<RPM_NUM>[\d]+)(.)*'
- match = re.match(hostname_regex, self.hostname)
- location = match.group('LOCATION')
- rpm_number = int(match.group('RPM_NUM'))
- next_rpm = '%s-rpm%d' % (location, int(rpm_number) + 1)
- try:
- # Ping it to see if it exists.
- subprocess.check_call(
- ['ping', '%s.%s' % (next_rpm, self._dns_zone), '-w 3'],
- stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- return next_rpm
- except subprocess.CalledProcessError:
- return None
-
-
class SentryRPMController(RPMController):
"""
This class implements power control for Sentry Switched CDU
@@ -618,41 +567,16 @@
DEVICE_PROMPT = 'Switched CDU:'
SET_STATE_CMD = '%s %s'
SUCCESS_MSG = 'Command successful'
- SET_OUTLET_NAME_CMD = 'set outlet name .A%d %s'
NUM_OF_OUTLETS = 17
TYPE = 'Sentry'
- def __init__(self, hostname, hydra_name=None):
- hydra_name = hydra_name or get_hydra_name(hostname)
- super(SentryRPMController, self).__init__(hostname, hydra_name)
+ def __init__(self, hostname, hydra_hostname=None):
+ super(SentryRPMController, self).__init__(hostname, hydra_hostname)
self._username = rpm_config.get('SENTRY', 'username')
self._password = rpm_config.get('SENTRY', 'password')
- def set_power_state(self, dut_hostname, new_state):
- """
- Set the state of the dut's outlet on this RPM.
-
- Overload set_power_state in RPMController.
- @param dut_hostname: hostname of DUT whose outlet we want to change.
- @param new_state: ON/OFF/CYCLE - state or action we want to perform on
- the outlet.
-
- @return: True if the attempt to change power state was successful,
- False otherwise.
- """
- if 'row' in dut_hostname:
- # Because the devices with a row and a rack all have long
- # hostnames, we can't store their full names in the rpm, therefore
- # for these devices we drop the 'chromeosX' part of their name.
- # For example: chromeos2-rack2-row1-host1 is just stored as
- # rack2-row1-host1 inside the RPM.
- dut_hostname = dut_hostname.split('-', 1)[1]
- return super(SentryRPMController, self).set_power_state(
- dut_hostname, new_state)
-
-
def _setup_test_user(self, ssh):
"""Configure the test user for the RPM
@@ -695,6 +619,12 @@
Configure the RPM by adding the test user and setting up the outlet
names.
+ Note the rpm infrastructure does not rely on the outlet name to map a
+ device to its outlet any more. We keep this method in case there is
+ a need to label outlets for other reasons. We may deprecate
+ this method if it has been proved the outlet names will not be used
+ in any scenario.
+
@param outlet_naming_map: Dictionary used to map the outlet numbers to
host names. Keys must be ints. And names are
in the format of 'hostX'.
@@ -709,12 +639,12 @@
self._setup_test_user(ssh)
# Set up the outlet names.
# Hosts have the same name format as the RPM hostname except they
- # end in hostX instead of rpm1.
- dut_name_format = self.hostname.replace('-rpm1', '')
+ # end in hostX instead of rpmX.
+ dut_name_format = re.sub('-rpm[0-9]*', '', self.hostname)
if self.behind_hydra:
- # Remove "chromeos2" from DUTs behind the hydra due to a length
+ # Remove "chromeosX" from DUTs behind the hydra due to a length
# constraint on the names we can store inside the RPM.
- dut_name_format = dut_name_format.replace('chromeos2-', '')
+ dut_name_format = re.sub('chromeos[0-9]*-', '', dut_name_format)
dut_name_format = dut_name_format + '-%s'
self._clear_outlet_names(ssh)
for outlet, name in outlet_naming_map.items():
@@ -759,59 +689,62 @@
self._rpm = powerswitch
- def _get_outlet_value_and_state(self, dut_hostname):
+ def _get_outlet_state(self, outlet):
"""
- Look up the outlet and state for a given hostname on the RPM.
+ Look up the state for a given outlet on the RPM.
- @param dut_hostname: hostname of DUT whose outlet we want to lookup.
+ @param outlet: the outlet to look up.
- @return [outlet, state]: the outlet number as well as its current state.
+ @return state: the outlet's current state.
"""
status_list = self._rpm.statuslist()
- for outlet, hostname, state in status_list:
- if hostname == dut_hostname:
- return outlet, state
+ for outlet_name, hostname, state in status_list:
+ if outlet_name == outlet:
+ return state
return None
- def set_power_state(self, dut_hostname, new_state):
+ def set_power_state(self, powerunit_info, new_state):
"""
Since this does not utilize SSH in any manner, this will overload the
set_power_state in RPMController and completes all steps of changing
- the DUT's outlet state.
+ the device's outlet state.
"""
- outlet_and_state = self._get_outlet_value_and_state(dut_hostname)
- if not outlet_and_state:
- logging.error('DUT %s is not on rpm %s',
- dut_hostname, self.hostname)
+ device_hostname = powerunit_info.device_hostname
+ outlet = powerunit_info.outlet
+ if not outlet:
+ logging.error('Request to change outlet for device %s to '
+ 'new state %s failed: outlet is unknown. Make sure '
+ 'POWERUNIT_OUTLET exists in the host\'s '
+ 'attributes in afe' , device_hostname, new_state)
return False
- outlet, state = outlet_and_state
+ state = self._get_outlet_state(outlet)
expected_state = new_state
if new_state == self.NEW_STATE_CYCLE:
- logging.debug('Beginning Power Cycle for DUT: %s',
- dut_hostname)
+ logging.debug('Beginning Power Cycle for device: %s',
+ device_hostname)
self._rpm.off(outlet)
- logging.debug('Outlet for DUT: %s set to OFF', dut_hostname)
+ logging.debug('Outlet for device: %s set to OFF', device_hostname)
# Pause for 5 seconds before restoring power.
time.sleep(RPMController.CYCLE_SLEEP_TIME)
self._rpm.on(outlet)
- logging.debug('Outlet for DUT: %s set to ON', dut_hostname)
+ logging.debug('Outlet for device: %s set to ON', device_hostname)
expected_state = self.NEW_STATE_ON
if new_state == self.NEW_STATE_OFF:
self._rpm.off(outlet)
- logging.debug('Outlet for DUT: %s set to OFF', dut_hostname)
+ logging.debug('Outlet for device: %s set to OFF', device_hostname)
if new_state == self.NEW_STATE_ON:
self._rpm.on(outlet)
- logging.debug('Outlet for DUT: %s set to ON', dut_hostname)
+ logging.debug('Outlet for device: %s set to ON', device_hostname)
# Lookup the final state of the outlet
- return self._is_plug_state(dut_hostname, expected_state)
+ return self._is_plug_state(powerunit_info, expected_state)
- def _is_plug_state(self, dut_hostname, expected_state):
- outlet, state = self._get_outlet_value_and_state(dut_hostname)
+ def _is_plug_state(self, powerunit_info, expected_state):
+ state = self._get_outlet_state(powerunit_info.outlet)
if expected_state not in state:
- logging.error('Outlet for DUT: %s did not change to new state'
- ' %s', dut_hostname, expected_state)
+ logging.error('Outlet for device: %s did not change to new state'
+ ' %s', powerunit_info.device_hostname, expected_state)
return False
return True
@@ -848,13 +781,11 @@
TYPE = 'CiscoPOE'
- def __init__(self, hostname, servo_interface):
+ def __init__(self, hostname):
"""
Initialize controller class for a Cisco POE switch.
@param hostname: the Cisco POE switch host name.
- @param servo_interface: a dictionary that maps servo hostname
- to (switch_hostname, interface).
"""
super(CiscoPOEController, self).__init__(hostname)
self._username = rpm_config.get('CiscoPOE', 'username')
@@ -865,7 +796,6 @@
self.poe_prompt = self.POE_PROMPT % short_hostname
self.config_prompt = self.CONFIG_PROMPT % short_hostname
self.config_if_prompt = self.CONFIG_IF_PROMPT % short_hostname
- self._servo_interface = servo_interface
def _login(self):
@@ -999,13 +929,13 @@
ssh.sendline(self.EXIT_CMD)
- def _change_state(self, dut_hostname, new_state, ssh):
+ def _change_state(self, powerunit_info, new_state, ssh):
"""
Perform the actual state change operation.
Overload _change_state in RPMController.
- @param dut_hostname: hostname of servo, whose outlet we want to change.
+ @param powerunit_info: An PowerUnitInfo instance.
@param new_state: ON/OFF - state or action we want to perform on
the outlet.
@param ssh: The ssh connection used to execute the state change commands
@@ -1014,14 +944,13 @@
@return: True if the attempt to change power state was successful,
False otherwise.
"""
- switch_if_tuple = self._servo_interface.get(dut_hostname)
- if not switch_if_tuple:
- logging.error('Could not find the interface for %s on switch %s. '
- 'Maybe the mapping file is out of date?',
- dut_hostname, self.hostname)
+ interface = powerunit_info.outlet
+ device_hostname = powerunit_info.device_hostname
+ if not interface:
+ logging.error('Could not change state: the interface on %s for %s '
+ 'was not given.', self.hostname, device_hostname)
return False
- else:
- interface = switch_if_tuple[1]
+
# Enter configuration terminal.
if not self._enter_configuration_terminal(interface, ssh):
logging.error('Could not enter configuration terminal for %s',
@@ -1037,28 +966,20 @@
return False
# Exit configuraiton terminal.
if not self._exit_configuration_terminal(ssh):
- logging.error('Skipping verifying outlet state for DUT: %s, '
+ logging.error('Skipping verifying outlet state for device: %s, '
'because could not exit configuration terminal.',
- dut_hostname)
+ device_hostname)
return False
# Verify if the state has changed successfully.
if not self._verify_state(interface, new_state, ssh):
logging.error('Could not verify state on interface %s', interface)
return False
- logging.debug('Outlet for DUT: %s set to %s',
- dut_hostname, new_state)
+ logging.debug('Outlet for device: %s set to %s',
+ device_hostname, new_state)
return True
- def get_next_rpm_hostname(self):
- """Override from RPMController. Not applicable to POE Controller.
-
- @returns None.
- """
- return None
-
-
def test_in_order_requests():
"""Simple integration testing."""
rpm = WebPoweredRPMController('chromeos-rack8e-rpm1')
diff --git a/site_utils/rpm_control_system/rpm_controller_unittest.py b/site_utils/rpm_control_system/rpm_controller_unittest.py
index f88f1c1..dd6bb2f 100755
--- a/site_utils/rpm_control_system/rpm_controller_unittest.py
+++ b/site_utils/rpm_control_system/rpm_controller_unittest.py
@@ -11,6 +11,9 @@
import rpm_controller
+import common
+from autotest_lib.site_utils.rpm_control_system import utils
+
class TestRPMControllerQueue(mox.MoxTestBase):
"""Test request can be queued and processed in controller.
@@ -19,11 +22,16 @@
def setUp(self):
super(TestRPMControllerQueue, self).setUp()
self.rpm = rpm_controller.SentryRPMController('chromeos-rack1-host8')
+ self.powerunit_info = utils.PowerUnitInfo(
+ device_hostname='chromos-rack1-host8',
+ powerunit_hostname='chromeos-rack1-rpm1',
+ powerunit_type=utils.PowerUnitInfo.POWERUNIT_TYPES.RPM,
+ outlet='.A100',
+ hydra_hostname=None)
def testQueueRequest(self):
"""Should create a new process to handle request."""
- dut_hostname = 'chromos-rack1-host8'
new_state = 'ON'
process = self.mox.CreateMockAnything()
rpm_controller.multiprocessing.Process = self.mox.CreateMockAnything()
@@ -32,7 +40,7 @@
process.start()
process.join()
self.mox.ReplayAll()
- self.assertFalse(self.rpm.queue_request(dut_hostname, new_state))
+ self.assertFalse(self.rpm.queue_request(self.powerunit_info, new_state))
self.mox.VerifyAll()
@@ -46,23 +54,29 @@
rpm_controller.pexpect.spawn = self.mox.CreateMockAnything()
rpm_controller.pexpect.spawn(mox.IgnoreArg()).AndReturn(self.ssh)
self.rpm = rpm_controller.SentryRPMController('chromeos-rack1-host8')
+ self.powerunit_info = utils.PowerUnitInfo(
+ device_hostname='chromos-rack1-host8',
+ powerunit_hostname='chromeos-rack1-rpm1',
+ powerunit_type=utils.PowerUnitInfo.POWERUNIT_TYPES.RPM,
+ outlet='.A100',
+ hydra_hostname=None)
def testSuccessfullyChangeOutlet(self):
"""Should return True if change was successful."""
prompt = 'Switched CDU:'
password = 'admn'
- dut_hostname = 'chromos-rack1-host8'
new_state = 'ON'
self.ssh.expect('Password:', timeout=60)
self.ssh.sendline(password)
self.ssh.expect(prompt, timeout=60)
- self.ssh.sendline('%s %s' % (new_state, dut_hostname))
+ self.ssh.sendline('%s %s' % (new_state, self.powerunit_info.outlet))
self.ssh.expect('Command successful', timeout=60)
self.ssh.sendline('logout')
self.ssh.close(force=True)
self.mox.ReplayAll()
- self.assertTrue(self.rpm.set_power_state(dut_hostname, new_state))
+ self.assertTrue(self.rpm.set_power_state(
+ self.powerunit_info, new_state))
self.mox.VerifyAll()
@@ -70,18 +84,17 @@
"""Should return False if change was unsuccessful."""
prompt = 'Switched CDU:'
password = 'admn'
- dut_hostname = 'chromos-rack1-host8'
new_state = 'ON'
self.ssh.expect('Password:', timeout=60)
self.ssh.sendline(password)
self.ssh.expect(prompt, timeout=60)
- self.ssh.sendline('%s %s' % (new_state, dut_hostname))
+ self.ssh.sendline('%s %s' % (new_state, self.powerunit_info.outlet))
self.ssh.expect('Command successful',
timeout=60).AndRaise(pexpect.TIMEOUT('Timed Out'))
self.ssh.sendline('logout')
self.ssh.close(force=True)
self.mox.ReplayAll()
- self.assertFalse(self.rpm.set_power_state(dut_hostname, new_state))
+ self.assertFalse(self.rpm.set_power_state(self.powerunit_info, new_state))
self.mox.VerifyAll()
@@ -100,6 +113,12 @@
# Outlet statuses are in the format "u'ON'"
initial_state = 'u\'ON\''
self.test_status_list_initial = [[outlet, dut, initial_state]]
+ self.powerunit_info = utils.PowerUnitInfo(
+ device_hostname=dut,
+ powerunit_hostname=hostname,
+ powerunit_type=utils.PowerUnitInfo.POWERUNIT_TYPES.RPM,
+ outlet=outlet,
+ hydra_hostname=None)
def testSuccessfullyChangeOutlet(self):
@@ -109,8 +128,8 @@
self.dli_ps.off(8)
self.dli_ps.statuslist().AndReturn(test_status_list_final)
self.mox.ReplayAll()
- self.assertTrue(self.web_rpm.set_power_state('chromeos-rack8a-host8',
- 'OFF'))
+ self.assertTrue(self.web_rpm.set_power_state(
+ self.powerunit_info, 'OFF'))
self.mox.VerifyAll()
@@ -121,18 +140,16 @@
self.dli_ps.off(8)
self.dli_ps.statuslist().AndReturn(test_status_list_final)
self.mox.ReplayAll()
- self.assertFalse(self.web_rpm.set_power_state('chromeos-rack8a-host8',
- 'OFF'))
+ self.assertFalse(self.web_rpm.set_power_state(
+ self.powerunit_info, 'OFF'))
self.mox.VerifyAll()
- def testDutNotOnRPM(self):
+ def testNoOutlet(self):
"""Should return False if DUT hostname is not on the RPM device."""
- self.dli_ps.statuslist().AndReturn(self.test_status_list_initial)
- self.mox.ReplayAll()
- self.assertFalse(self.web_rpm.set_power_state('chromeos-rack8a-host1',
- 'OFF'))
- self.mox.VerifyAll()
+ self.powerunit_info.outlet=None
+ self.assertFalse(self.web_rpm.set_power_state(
+ self.powerunit_info, 'OFF'))
class TestCiscoPOEController(mox.MoxTestBase):
@@ -155,6 +172,12 @@
SERVO = 'chromeos1-rack3-host12-servo'
SWITCH = 'chromeos2-poe-switch8'
PORT = 'fa32'
+ POWERUNIT_INFO = utils.PowerUnitInfo(
+ device_hostname=PORT,
+ powerunit_hostname=SERVO,
+ powerunit_type=utils.PowerUnitInfo.POWERUNIT_TYPES.POE,
+ outlet=PORT,
+ hydra_hostname=None)
def setUp(self):
@@ -162,9 +185,7 @@
self.mox.StubOutWithMock(pexpect.spawn, '_spawn')
self.mox.StubOutWithMock(pexpect.spawn, 'read_nonblocking')
self.mox.StubOutWithMock(pexpect.spawn, 'sendline')
- servo_interface = {self.SERVO:(self.SWITCH, self.PORT)}
- self.poe = rpm_controller.CiscoPOEController(
- self.SWITCH, servo_interface)
+ self.poe = rpm_controller.CiscoPOEController(self.SWITCH)
pexpect.spawn._spawn(mox.IgnoreArg(), mox.IgnoreArg())
pexpect.spawn.read_nonblocking(
mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(self.STREAM_WELCOME)
@@ -222,7 +243,7 @@
mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(self.STREAM_DEVICE)
pexpect.spawn.sendline('exit')
self.mox.ReplayAll()
- self.assertTrue(self.poe.set_power_state(self.SERVO, 'ON'))
+ self.assertTrue(self.poe.set_power_state(self.POWERUNIT_INFO, 'ON'))
self.mox.VerifyAll()
@@ -231,7 +252,7 @@
self._EnterConfigurationHelper(success=False)
pexpect.spawn.sendline('exit')
self.mox.ReplayAll()
- self.assertFalse(self.poe.set_power_state(self.SERVO, 'ON'))
+ self.assertFalse(self.poe.set_power_state(self.POWERUNIT_INFO, 'ON'))
self.mox.VerifyAll()
@@ -251,7 +272,7 @@
pexpect.spawn.__str__().AndReturn('A pexpect.spawn object.')
pexpect.spawn.sendline('exit')
self.mox.ReplayAll()
- self.assertFalse(self.poe.set_power_state(self.SERVO, 'ON'))
+ self.assertFalse(self.poe.set_power_state(self.POWERUNIT_INFO, 'ON'))
self.mox.VerifyAll()
@@ -274,7 +295,7 @@
pexpect.spawn.__str__().AndReturn('A pexpect.spawn object.')
pexpect.spawn.sendline('exit')
self.mox.ReplayAll()
- self.assertFalse(self.poe.set_power_state(self.SERVO, 'ON'))
+ self.assertFalse(self.poe.set_power_state(self.POWERUNIT_INFO, 'ON'))
self.mox.VerifyAll()
diff --git a/site_utils/rpm_control_system/rpm_dispatcher.py b/site_utils/rpm_control_system/rpm_dispatcher.py
index f050a3f..25fca6d 100755
--- a/site_utils/rpm_control_system/rpm_dispatcher.py
+++ b/site_utils/rpm_control_system/rpm_dispatcher.py
@@ -6,7 +6,6 @@
import atexit
import errno
import logging
-import os
import re
import sys
import socket
@@ -25,11 +24,6 @@
LOG_FILENAME_FORMAT = rpm_config.get('GENERAL','dispatcher_logname_format')
-# Servo-interface mapping file
-MAPPING_FILE = os.path.join(
- os.path.dirname(__file__),
- rpm_config.get('CiscoPOE', 'servo_interface_mapping_file'))
-
class RPMDispatcher(object):
"""
@@ -72,8 +66,6 @@
self._worker_dict = {}
self._frontend_server = rpm_config.get('RPM_INFRASTRUCTURE',
'frontend_uri')
- self._mapping_last_modified = os.path.getmtime(MAPPING_FILE)
- self._servo_interface = utils.load_servo_interface_mapping()
logging.info('Registering this rpm dispatcher with the frontend '
'server at %s.', self._frontend_server)
client = xmlrpclib.ServerProxy(self._frontend_server)
@@ -121,67 +113,31 @@
return True
- def queue_request(self, dut_hostname, new_state):
+ def queue_request(self, powerunit_info_dict, new_state):
"""
- Looks up the appropriate RPMController instance for this DUT and queues
+ Looks up the appropriate RPMController instance for the device and queues
up the request.
- @param dut_hostname: hostname of the DUT whose outlet we are trying to
- change.
+ @param powerunit_info_dict: A dictionary, containing the attribute/values
+ of an unmarshalled PowerUnitInfo instance.
@param new_state: [ON, OFF, CYCLE] state we want to the change the
outlet to.
@return: True if the attempt to change power state was successful,
False otherwise.
"""
- logging.info('Received request to set DUT: %s to state: %s',
- dut_hostname, new_state)
- rpm_hostname = self._get_rpm_hostname_for_dut(dut_hostname)
+ powerunit_info = utils.PowerUnitInfo(**powerunit_info_dict)
+ logging.info('Received request to set device: %s to state: %s',
+ powerunit_info.device_hostname, new_state)
result = False
- while not result and rpm_hostname:
- rpm_controller = self._get_rpm_controller(rpm_hostname)
- result = rpm_controller.queue_request(dut_hostname, new_state)
- if not result:
- # If the request failed, check to see if there is another RPM
- # at this location.
- rpm_hostname = rpm_controller.get_next_rpm_hostname()
+ while not result:
+ rpm_controller = self._get_rpm_controller(
+ powerunit_info.powerunit_hostname,
+ powerunit_info.hydra_hostname)
+ result = rpm_controller.queue_request(powerunit_info, new_state)
return result
- def _get_rpm_hostname_for_dut(self, dut_hostname):
- """
- Private method that retreives the appropriate RPMController instance
- for this DUT.
-
- @param dut_hostname: hostname of the DUT whose RPMController we want.
-
- @return: RPM Hostname responsible for this DUT.
- Return None on failure.
- """
- if dut_hostname.endswith('servo'):
- # Servos are managed by Cisco POE switches.
- reload_info = utils.reload_servo_interface_mapping_if_necessary(
- self._mapping_last_modified)
- if reload_info:
- self._mapping_last_modified, self._servo_interface = reload_info
- switch_if_tuple = self._servo_interface.get(dut_hostname)
- if not switch_if_tuple:
- logging.error('Could not determine POE hostname for %s. '
- 'Please check the servo-interface mapping file.',
- dut_hostname)
- return None
- else:
- rpm_hostname = switch_if_tuple[0]
- logging.info('POE hostname for DUT %s is %s', dut_hostname,
- rpm_hostname)
- else:
- # Regular DUTs are managed by RPMs.
- rpm_hostname = re.sub('host[^.]*', 'rpm1', dut_hostname, count=1)
- logging.info('RPM hostname for DUT %s is %s', dut_hostname,
- rpm_hostname)
- return rpm_hostname
-
-
- def _get_rpm_controller(self, rpm_hostname):
+ def _get_rpm_controller(self, rpm_hostname, hydra_hostname=None):
"""
Private method that retreives the appropriate RPMController instance
for this RPM Hostname or calls _create_rpm_controller it if it does not
@@ -195,12 +151,13 @@
return None
rpm_controller = self._worker_dict_get(rpm_hostname)
if not rpm_controller:
- rpm_controller = self._create_rpm_controller(rpm_hostname)
+ rpm_controller = self._create_rpm_controller(
+ rpm_hostname, hydra_hostname)
self._worker_dict_put(rpm_hostname, rpm_controller)
return rpm_controller
- def _create_rpm_controller(self, rpm_hostname):
+ def _create_rpm_controller(self, rpm_hostname, hydra_hostname):
"""
Determines the type of RPMController required and initializes it.
@@ -212,8 +169,7 @@
if hostname_elements[-2] == 'poe':
# POE switch hostname looks like 'chromeos2-poe-switch1'.
logging.info('The controller is a Cisco POE switch.')
- return rpm_controller.CiscoPOEController(
- rpm_hostname, self._servo_interface)
+ return rpm_controller.CiscoPOEController(rpm_hostname)
else:
# The device is an RPM.
rack_id = hostname_elements[-2]
@@ -223,7 +179,9 @@
return rpm_controller.WebPoweredRPMController(rpm_hostname)
else:
logging.info('RPM is a Sentry CDU device.')
- return rpm_controller.SentryRPMController(rpm_hostname)
+ return rpm_controller.SentryRPMController(
+ hostname=rpm_hostname,
+ hydra_hostname=hydra_hostname)
def _get_serveruri(self):
diff --git a/site_utils/rpm_control_system/rpm_dispatcher_unittest.py b/site_utils/rpm_control_system/rpm_dispatcher_unittest.py
index aaa6298..b7e0162 100755
--- a/site_utils/rpm_control_system/rpm_dispatcher_unittest.py
+++ b/site_utils/rpm_control_system/rpm_dispatcher_unittest.py
@@ -11,6 +11,7 @@
DUT_SAME_RPM1 = 'chromeos-rack8e-hostbs1'
DUT_SAME_RPM2 = 'chromeos-rack8e-hostbs2'
+RPM_HOSTNAME = 'chromeos-rack8e-rpm1'
DUT_DIFFERENT_RPM = 'chromeos-rack1-hostbs1'
FAKE_DISPATCHER_URI = 'fake-dispatcher'
FAKE_DISPATCHER_PORT = 9999
@@ -60,10 +61,8 @@
belong to the same RPM device create and retrieve the same RPMController
instance.
"""
- controller1 = self.dispatcher._get_rpm_controller(
- self.dispatcher._get_rpm_hostname_for_dut(DUT_SAME_RPM1))
- controller2 = self.dispatcher._get_rpm_controller(
- self.dispatcher._get_rpm_hostname_for_dut(DUT_SAME_RPM2))
+ controller1 = self.dispatcher._get_rpm_controller(RPM_HOSTNAME)
+ controller2 = self.dispatcher._get_rpm_controller(RPM_HOSTNAME)
self.assertEquals(controller1, controller2)