Autotest: Update site remote power to use RPM Infrastructure
Updated site_host to include RPM power on, off, and cycle commands,
that implement the client side code of interacting with the RPM
Infrastructure.
Removed all the RPM specific classes from site_remote_power.py and
replaced them with one generic class that utilizes the new methods
in site_host.
Also updated hardreset in SerialHost to utilize these new methods
that it will inherit from site_host.
BUG=chromium-os:34664
TEST=Ran power_IdleServer against a machine in the lab.
Change-Id: I88627416ba001ecad08d96aea1e98d8073aef068
Reviewed-on: https://gerrit.chromium.org/gerrit/34001
Commit-Ready: Simran Basi <sbasi@chromium.org>
Reviewed-by: Simran Basi <sbasi@chromium.org>
Tested-by: Simran Basi <sbasi@chromium.org>
diff --git a/global_config.ini b/global_config.ini
index c33cccb..9232307 100644
--- a/global_config.ini
+++ b/global_config.ini
@@ -150,3 +150,4 @@
# the "Sentry Switched CDU" type
rpm_sentry_username: fake_user
rpm_sentry_password: fake_password
+rpm_frontend_uri: http://chromeos-rpmserver1.cbf.corp.google.com:9999
diff --git a/server/hosts/base_classes.py b/server/hosts/base_classes.py
index 0759d5f..5316865 100644
--- a/server/hosts/base_classes.py
+++ b/server/hosts/base_classes.py
@@ -78,3 +78,9 @@
if self.job:
self.job.hosts.discard(self)
+
+
+ @staticmethod
+ def check_for_rpm_support(hostname):
+ """Stub method. RPM support is implemented in site_host."""
+ return False
\ No newline at end of file
diff --git a/server/hosts/serial.py b/server/hosts/serial.py
index d363cb7..e89bff9 100644
--- a/server/hosts/serial.py
+++ b/server/hosts/serial.py
@@ -1,10 +1,14 @@
import os, sys, subprocess, logging
-from autotest_lib.client.common_lib import utils, error
+from autotest_lib.client.common_lib import global_config, utils, error
from autotest_lib.server import utils as server_utils
from autotest_lib.server.hosts import remote
+RPM_FRONTEND_URI = global_config.global_config.get_config_value('CROS',
+ 'rpm_frontend_uri', type=str, default='')
+
+
SiteHost = utils.import_site_class(
__file__, "autotest_lib.server.hosts.site_host", "SiteHost",
remote.RemoteHost)
@@ -12,6 +16,8 @@
class SerialHost(SiteHost):
DEFAULT_REBOOT_TIMEOUT = SiteHost.DEFAULT_REBOOT_TIMEOUT
+ HARD_RESET_CMD = 'hardreset'
+
def _initialize(self, conmux_server=None, conmux_attach=None,
console_log="console.log", *args, **dargs):
@@ -53,16 +59,18 @@
conmux_attach=None):
""" Returns a boolean indicating if the remote host with "hostname"
supports use as a SerialHost """
+ return_value = False
conmux_attach = cls._get_conmux_attach(conmux_attach)
conmux_hostname = cls._get_conmux_hostname(hostname, conmux_server)
cmd = "%s %s echo 2> /dev/null" % (conmux_attach, conmux_hostname)
try:
result = utils.run(cmd, ignore_status=True, timeout=10)
- return result.exit_status == 0
+ return_value = result.exit_status == 0
except error.CmdError:
logging.warning("Timed out while trying to attach to conmux")
+ return_value = False
- return False
+ return return_value or SiteHost.check_for_rpm_support(hostname)
def start_loggers(self):
@@ -114,7 +122,7 @@
def hardreset(self, timeout=DEFAULT_REBOOT_TIMEOUT, wait=True,
- conmux_command='hardreset', num_attempts=1, halt=False,
+ conmux_command=HARD_RESET_CMD, num_attempts=1, halt=False,
**wait_for_restart_kwargs):
"""
Reach out and slap the box in the power switch.
@@ -141,7 +149,9 @@
def reboot():
if halt:
self.halt()
- if not self.run_conmux(conmux_command):
+ if self.HARD_RESET_CMD in conmux_command and RPM_FRONTEND_URI:
+ self.power_cycle()
+ elif not self.run_conmux(conmux_command):
self.record("ABORT", None, "reboot.start",
"hard reset unavailable")
raise error.AutoservUnsupportedError(
@@ -158,7 +168,11 @@
except error.AutoservShutdownError:
logging.warning(warning_msg, attempt+1, num_attempts)
# re-send the hard reset command
- self.run_conmux(conmux_command)
+ if (self.HARD_RESET_CMD in conmux_command and
+ RPM_FRONTEND_URI):
+ self.power_cycle()
+ else:
+ self.run_conmux(conmux_command)
else:
break
else:
diff --git a/server/hosts/site_host.py b/server/hosts/site_host.py
index 1c222e6..492379b 100644
--- a/server/hosts/site_host.py
+++ b/server/hosts/site_host.py
@@ -3,6 +3,7 @@
# found in the LICENSE file.
import logging
+import re
import subprocess
import time
import xmlrpclib
@@ -12,11 +13,19 @@
from autotest_lib.client.common_lib.cros import autoupdater
from autotest_lib.server import autoserv_parser
from autotest_lib.server import site_host_attributes
-from autotest_lib.server import site_remote_power
from autotest_lib.server.cros import servo
from autotest_lib.server.hosts import remote
+RPM_FRONTEND_URI = global_config.global_config.get_config_value('CROS',
+ 'rpm_frontend_uri', type=str, default='')
+
+
+class RemotePowerException(Exception):
+ """This is raised when we fail to set the state of the device's outlet."""
+ pass
+
+
def make_ssh_command(user='root', port=22, opts='', hosts_file=None,
connect_timeout=None, alive_interval=None):
"""Override default make_ssh_command to use options tuned for Chrome OS.
@@ -87,6 +96,8 @@
SHUTDOWN_TIMEOUT = 5
REBOOT_TIMEOUT = SHUTDOWN_TIMEOUT + BOOT_TIMEOUT
LAB_MACHINE_FILE = '/mnt/stateful_partition/.labmachine'
+ RPM_HOSTNAME_REGEX = ('chromeos[0-9]+(-row[0-9]+)?-rack[0-9]+[a-z]*-'
+ 'host[0-9]+')
def _initialize(self, hostname, servo_host=None, servo_port=None,
@@ -210,9 +221,8 @@
def cleanup(self):
"""Special cleanup method to make sure hosts always get power back."""
super(SiteHost, self).cleanup()
- remote_power = site_remote_power.RemotePower(self.hostname)
- if remote_power:
- remote_power.set_power_on()
+ if self.has_power():
+ self.power_on()
def reboot(self, **dargs):
@@ -330,7 +340,7 @@
#
# 'pkill' helpfully exits with status 1 if no target
# process is found, for which run() will throw an
- # exception. We don't want that, so we ignore the
+ # exception. We don't want that, so we the ignore
# status.
self.run("pkill -f '%s'" % remote_name, ignore_status=True)
@@ -499,3 +509,43 @@
raise error.TestFail(
'client is back up, but did not reboot'
' (boot %s)' % old_boot_id)
+
+
+ @staticmethod
+ def check_for_rpm_support(hostname):
+ """For a given hostname, return whether or not it is powered by an RPM.
+
+ @return None if this host does not follows the defined naming format
+ for RPM powered DUT's in the lab. If it does follow the format,
+ it returns a regular expression MatchObject instead.
+ """
+ return re.match(SiteHost.RPM_HOSTNAME_REGEX, hostname)
+
+
+ def has_power(self):
+ """For this host, return whether or not it is powered by an RPM.
+
+ @return True if this host is in the CROS lab and follows the defined
+ naming format.
+ """
+ return SiteHost.check_for_rpm_support(self.hostname)
+
+
+ def _set_power(self, new_state):
+ client = xmlrpclib.ServerProxy(RPM_FRONTEND_URI, verbose=False)
+ if not client.queue_request(self.hostname, new_state):
+ raise RemotePowerException('Failed to change outlet status for'
+ 'host: %s to state: %s', self.hostname,
+ new_state)
+
+
+ def power_off(self):
+ self._set_power('OFF')
+
+
+ def power_on(self):
+ self._set_power('ON')
+
+
+ def power_cycle(self):
+ self._set_power('CYCLE')
diff --git a/server/site_remote_power.py b/server/site_remote_power.py
deleted file mode 100755
index 7c90843..0000000
--- a/server/site_remote_power.py
+++ /dev/null
@@ -1,221 +0,0 @@
-# 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 logging, os, re, urllib
-
-# If you create site_remote_power_config.py and remote_power_switch_machines
-# there, we will use it to configure the power switch.
-#
-# Example configuration:
-# remote_power_switch_machines = {
-# "myserver": "rps,cyclades-acs,powerswitch1,device1,password"
-# }
-try:
- from site_remote_power_config import remote_power_switch_machines
-except:
- remote_power_switch_machines = {}
-
-# If remote_power_switch_machines was not defined in site_remote_power_config,
-# we look in the AFE database for a label which defines how we should behave.
-#
-# Here is an example label:
-# "rps,cyclades-acs,powerswitch1,device1,password"
-#
-# If you attach the above label to device1, then we will use the "cyclades-acs"
-# powerswitch to turn the power to device1 on and off, and pass in the above
-# configuration to the CycladesACSRemotePowerSwitch class.
-if not remote_power_switch_machines:
- try:
- settings = 'autotest_lib.frontend.settings'
- os.environ['DJANGO_SETTINGS_MODULE'] = settings
- from autotest_lib.frontend.afe import models
- has_models = True
- except ImportError, e:
- has_models = False
-
-# factory function for choosing which remote power class to return
-
-def ParseConfig(config):
- type = config.split(",", 3)[1]
- cls = power_switch_types[type]
- if cls:
- return cls(config)
- else:
- raise AssertionError
-
-def RemotePower(host):
- if remote_power_switch_machines and host in remote_power_switch_machines:
- return ParseConfig(remote_power_switch_machines[host])
- elif has_models:
- host_obj = models.Host.valid_objects.get(hostname=host)
- for label in host_obj.labels.all():
- name = label.name
- if name.startswith("rps,"):
- return ParseConfig(name)
-
- return None
-
-class SentrySwitchedCDU(object):
- """
- This class implements power control for Sentry Switched CDU
- http://www.servertech.com/products/switched-pdus/
-
- It assumes that machine
- chromeos-rackX-hostY
- is controlled by an RPM at
- chromeos-rackX-rpm1.
-
- Example usage:
- switch = SentrySwitchedCDU('chromeos-rack7-host3')
- switch.set_power_off()
- switch.set_power_on()
- """
-
-
- def __init__(self, machine):
- self.machine = machine
- self.rpm_host = re.sub('host[^.]*', 'rpm1', machine, count=1)
-
-
- def set_power_on(self):
- self._set_power('on')
-
-
- def set_power_off(self):
- self._set_power('off')
-
-
- def _set_power(self, command):
- from autotest_lib.client.common_lib import pexpect
- from autotest_lib.client.common_lib import global_config
- password = global_config.global_config.get_config_value(
- 'CROS', 'rpm_sentry_password', type=str)
- username = global_config.global_config.get_config_value(
- 'CROS', 'rpm_sentry_username', type=str)
- rpm_host = self.rpm_host
- # In case machine comes with a full domain name, cut it off
- machine_name = self.machine.split('.', 1)[0]
- prompt = 'Switched CDU:'
- cmd = ('ssh -l %s '
- '-o StrictHostKeyChecking=no '
- '-o UserKnownHostsFile=/dev/null '
- '%s' % (username, rpm_host))
- ssh = pexpect.spawn(cmd)
- ssh.expect('Password:', timeout=30)
- ssh.sendline(password)
- ssh.expect(prompt, timeout=30)
- logging.info('Connecting to power switch (%s@%s)', username, rpm_host)
- # Command looks like: off chromeos-rack7-host3
- ssh.sendline('%s %s' %(command, machine_name))
- try:
- ssh.expect('Command successful', timeout=30)
- except pexpect.TIMEOUT:
- logging.error('Timed out while switching AC power %s for host %s',
- command, machine_name)
- raise
- finally:
- ssh.sendline('logout')
- logging.info('Power turned %s for %s', command, machine_name)
-
-
-class CycladesACSRemotePowerSwitch(object):
- """
- This class implements power control for Cyclades ACS boxes.
-
- The config string contains five components, separated by commas:
- 1) prefix ("rps")
- 2) type ("cyclades-acs")
- 3) hostname of power switch box
- 4) port to use on power switch box
- 5) password to enter when connecting to power switch box
-
- Example usage:
- config = "rps,cyclades-acs,powerswitch1,device1,password"
- switch = CycladesACSRemotePowerSwitch(config)
- switch.set_power_off()
- switch.set_power_on()
- """
-
-
- def __init__(self, config):
- self.power_ip, self.power_port, self.password = config.split(",")[2:]
-
-
- def _set_power(self, state):
- from autotest_lib.client.common_lib import pexpect
- hostname = self.power_ip
- username = 'root:%s' % self.power_port
- cmd = ('ssh -l %s '
- '-o StrictHostKeyChecking=no '
- '-o UserKnownHostsFile=/dev/null '
- '%s' % (username, hostname))
- ssh = pexpect.spawn(cmd)
- ssh.expect('password:', timeout=30)
- ssh.sendline(self.password)
- ssh.expect('\n', timeout=30)
- ssh.send('\020')
- logging.info('Connecting to power switch (%s@%s)' % (username,
- hostname))
- ssh.expect('Please choose an option:', timeout=30)
-
- if state:
- ssh.sendline('5')
- ssh.expect('Outlet turned on', timeout=30)
- logging.info('Power turned on for %s' % self.dict['power_port'])
- else:
- ssh.sendline('4')
- ssh.expect('Outlet turned off', timeout=30)
- logging.info('Power turned off for %s' % self.dict['power_port'])
-
-
- def set_power_on(self):
- self._set_power(1)
-
-
- def set_power_off(self):
- self._set_power(0)
-
-
-class HTTPPowerSwitch(object):
- """
- This class implements power control via standard HTTP GET requests.
-
- The config string contains four components, separated by commas:
- 1) prefix ("rps")
- 2) type ("cyclades-acs")
- 3) URL to turn power on
- 4) URL to turn power off
-
- Example usage:
- url_prefix = "http://user:password@powerswitch1/Set.cmd?CMD=SetPower&P"
- config = "rps,http,%s1=1,%s1=0" % (url_prefix, url_prefix)
- switch = HTTPPowerSwitch(config)
- switch.set_power_off()
- switch.set_power_on()
-
- """
-
-
- def __init__(self, config):
- self.on_url, self.off_url = config.split(",")[2:]
-
-
- def _get_url(self, url):
- logging.info(url)
- f = urllib.urlopen(url)
- f.read()
-
-
- def set_power_on(self):
- self._get_url(self.on_url)
-
-
- def set_power_off(self):
- self._get_url(self.off_url)
-
-
-power_switch_types = {
- 'cyclades-acs': CycladesACSRemotePowerSwitch,
- 'http': HTTPPowerSwitch
-}
diff --git a/server/site_tests/power_IdleServer/control b/server/site_tests/power_IdleServer/control
index 52154d3..ced7c1c 100644
--- a/server/site_tests/power_IdleServer/control
+++ b/server/site_tests/power_IdleServer/control
@@ -14,22 +14,21 @@
draw at idle.
"""
-from autotest_lib.server import site_remote_power, utils
+from autotest_lib.server import utils
def run_system_power_idle(machine):
- remote_power = site_remote_power.RemotePower(machine)
- if remote_power:
- remote_power.set_power_off()
-
host = hosts.create_host(machine)
+ if host.has_power():
+ host.power_off()
+
host_at = autotest.Autotest(host)
host_test = 'power_Idle'
host_at.run_test(host_test)
- if remote_power:
- remote_power.set_power_on()
+ if host.has_power()):
+ host.power_on()
(tuple, failures) = utils.form_ntuples_from_machines(machines, 1)
diff --git a/server/site_tests/suites/control.network_wifi b/server/site_tests/suites/control.network_wifi
index ca8d148..dfa481f 100644
--- a/server/site_tests/suites/control.network_wifi
+++ b/server/site_tests/suites/control.network_wifi
@@ -30,7 +30,7 @@
import time
from autotest_lib.client.common_lib import error
-from autotest_lib.server import site_host_attributes, site_remote_power
+from autotest_lib.server import site_host_attributes
from autotest_lib.server import site_wifitest
from autotest_lib.server.hosts import ssh_host
@@ -47,20 +47,19 @@
def run_server_tests(machine):
client_attributes = site_host_attributes.HostAttributes(machine)
- remote_power = site_remote_power.RemotePower(machine)
-
- if remote_power:
- remote_power.set_power_on()
+ client = hosts.create_host(machine)
+ if client.has_power():
+ client.power_on()
else:
raise error.TestNAError('No power switch configured.')
# Power cycle the entire unit prior to testing. Not expecting any exceptions,
# but shouldn't leave power off no matter what happens.
try:
- remote_power.set_power_off()
+ client.power_off()
time.sleep(POWER_CYCLE_TIME_SECS)
finally:
- remote_power.set_power_on()
+ client.power_on()
# Wait for devices to come back.
router = ssh_host.SSHHost(client_attributes.router_addr)
diff --git a/server/site_tests/suites/control.power b/server/site_tests/suites/control.power
index 28691f8..8f41140 100644
--- a/server/site_tests/suites/control.power
+++ b/server/site_tests/suites/control.power
@@ -11,11 +11,10 @@
DOC = """
This test suite runs automated power tests that should all pass and that
-require the power to be connected with a remote power control.
+require the power to be connected with a remote power manager.
"""
from autotest_lib.server import site_host_attributes
-from autotest_lib.server import site_remote_power
from autotest_lib.client.common_lib import error
# Run power tests that don't take a long time
@@ -37,9 +36,8 @@
client_attributes = site_host_attributes.HostAttributes(machine)
client_at = autotest.Autotest(client)
- remote_power = site_remote_power.RemotePower(machine)
- if remote_power:
- remote_power.set_power_on()
+ if client.has_power():
+ client.power_on()
else:
raise error.TestNAError("No power switch configured")
@@ -50,7 +48,7 @@
client_at.run_test('power_BatteryCharge', percent_target_charge=50,
max_run_time=60*60*4, tag='CHARGE_50')
- remote_power.set_power_off()
+ client.power_off()
try:
for test in TESTS:
client_at.run_test(test)
@@ -59,7 +57,7 @@
client_at.run_test('power_Resume')
finally:
- remote_power.set_power_on()
+ client.power_on()
# Run the 60/20/10/10 load test
# Charge the battery to at least 99% in preparation for the load
@@ -73,7 +71,7 @@
# 1. Make the test run over wifi instead of ethernet
# 2. Make the test login automatically to facebook and gmail
# 3. Add audiovideo_V4L2 webcam test
- remote_power.set_power_off()
+ client.power_off()
try:
if hasattr(client_attributes, 'wifi'):
@@ -86,7 +84,7 @@
client_at.run_test('power_LoadTest', loop_count=9, loop_time=3600,
check_network=False, tag='WIRED')
finally:
- remote_power.set_power_on()
+ client.power_on()
job.parallel_on_machines(run_client_test, machines)
diff --git a/server/site_tests/suites/control.power_build b/server/site_tests/suites/control.power_build
index 343a1c1..6dece44 100644
--- a/server/site_tests/suites/control.power_build
+++ b/server/site_tests/suites/control.power_build
@@ -17,7 +17,6 @@
"""
from autotest_lib.server import site_host_attributes
-from autotest_lib.server import site_remote_power
# Tests that should run both on AC and battery power
TESTS = [
@@ -39,8 +38,7 @@
]
-def _iterate_tests(machine, on_ac=True):
- client = hosts.create_host(machine)
+def _iterate_tests(client, machine, on_ac=True):
client_attributes = site_host_attributes.HostAttributes(machine)
client_at = autotest.Autotest(client)
@@ -62,18 +60,18 @@
def _run_client_test(machine):
- remote_power = site_remote_power.RemotePower(machine)
- if remote_power:
- remote_power.set_power_on()
+ client = hosts.create_host(machine)
+ if client.has_power():
+ client.power_on()
- _iterate_tests(machine, on_ac=True)
+ _iterate_tests(client, machine, on_ac=True)
- if remote_power:
- remote_power.set_power_off()
+ if client.has_power():
+ client.power_off()
try:
- _iterate_tests(machine, on_ac=False)
+ _iterate_tests(client, machine, on_ac=False)
finally:
- remote_power.set_power_on()
+ client.power_on()
job.parallel_on_machines(_run_client_test, machines)
diff --git a/server/site_tests/suites/control.power_daily b/server/site_tests/suites/control.power_daily
index 51a6ac6..69affda 100644
--- a/server/site_tests/suites/control.power_daily
+++ b/server/site_tests/suites/control.power_daily
@@ -16,7 +16,6 @@
"""
from autotest_lib.server import site_host_attributes
-from autotest_lib.server import site_remote_power
from autotest_lib.client.common_lib import error
def _run_client_test(machine):
@@ -24,9 +23,8 @@
client_attributes = site_host_attributes.HostAttributes(machine)
client_at = autotest.Autotest(client)
- remote_power = site_remote_power.RemotePower(machine)
- if remote_power:
- remote_power.set_power_on()
+ if client.has_power():
+ client.power_on()
else:
raise error.TestNAError("No power switch configured")
@@ -36,12 +34,12 @@
max_run_time=60*60*4, tag='CHARGE_50')
# Turn off power and run power_Consumption test.
- remote_power.set_power_off()
+ client.power_off()
try:
client_at.run_test('power_Consumption')
finally:
- remote_power.set_power_on()
+ client.power_on()
# Charge the battery to at least 99% in preparation for the load
# test. Charging the battery from empty to full can take up to 4 hours.
@@ -53,7 +51,7 @@
# TODO (snanda):
# 1. Make the test login automatically to facebook and gmail
# 2. Add audiovideo_V4L2 webcam test
- remote_power.set_power_off()
+ client.power_off()
try:
if hasattr(client_attributes, 'wifi'):
@@ -65,7 +63,7 @@
client_at.run_test('power_LoadTest', loop_count=9, loop_time=3600,
check_network=False, tag='WIRED')
finally:
- remote_power.set_power_on()
+ client.power_on()
job.parallel_on_machines(_run_client_test, machines)
diff --git a/server/site_tests/suites/control.power_weekly b/server/site_tests/suites/control.power_weekly
index a98d021..4379fdc 100644
--- a/server/site_tests/suites/control.power_weekly
+++ b/server/site_tests/suites/control.power_weekly
@@ -24,17 +24,14 @@
# https://sites.google.com/a/chromium.org/dev/chromium-os/testing/test-suites
# has ability to assure power states pre/post conditions.
from autotest_lib.server import site_host_attributes
-from autotest_lib.server import site_remote_power
from autotest_lib.client.common_lib import error
def _run_client_test(machine):
client = hosts.create_host(machine)
client_attributes = site_host_attributes.HostAttributes(machine)
client_at = autotest.Autotest(client)
-
- remote_power = site_remote_power.RemotePower(machine)
- if remote_power:
- remote_power.set_power_on()
+ if client.has_power():
+ client.power_on()
else:
raise error.TestNAError("No power switch configured")
@@ -42,12 +39,12 @@
client_at.run_test('power_BatteryCharge', percent_target_charge=50,
max_run_time=60*60*3, tag='CHARGE_50')
- remote_power.set_power_off()
+ client.power_off()
try:
client_at.run_test('power_Standby', test_hours=12.0, sample_hours=1.0,
constraints=['milliwatts_standby_power <= 500.0'],
max_milliwatts_standby=500.0)
finally:
- remote_power.set_power_on()
+ client.power_on()
job.parallel_on_machines(_run_client_test, machines)