# Copyright (c) 2011 The Chromium 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
import re
import telnetlib
class PowerStrip(object):
"""Controls Server Technology CW-16V1-C20M switched CDUs.
(Cabinet Power Distribution Unit)
This class is used to control the CW-16V1-C20M unit which
is a 16 port remote power strip. The strip supports AC devices
using 100-120V 50/60Hz input voltages. The commands in this
class are supported by switches that use Sentry Switched CDU Version 6.0g.
Opens a new connection for every command.
"""
TIMEOUT = 10
def __init__(self, host, user='admn', password='admn'):
self._host = host
self._user = user
self._password = password
def PowerOff(self, outlet):
"""Powers off the device that is plugged into the specified outlet.
Args:
outlet: The outlet ID defined on the switch (eg. .a14).
"""
self._DoCommand('off', outlet)
def PowerOn(self, outlet):
"""Powers on the device that is plugged into the specified outlet.
Args:
outlet: The outlet ID defined on the switch (eg. .a14).
"""
self._DoCommand('on', outlet)
def _DoCommand(self, command, outlet):
"""Performs power strip commands on the specified outlet.
Sample telnet interaction:
Escape character is '^]'.
Sentry Switched CDU Version 6.0g
Username: admn
Password: < password hidden from view >
Location:
Switched CDU: on .a1
Outlet Outlet Outlet Control
ID Name Status State
.A1 TowerA_Outlet1 On On
Command successful
Switched CDU: < cdu cmd >
Args:
command: A valid CW-16V1-C20M command that follows the format
<command> <outlet>.
outlet: The outlet ID defined on the switch (eg. .a14).
"""
tn = telnetlib.Telnet()
# To avoid 'Connection Reset by Peer: 104' exceptions when rapid calls
# are made to the telnet server on the power strip, we retry executing
# a command.
retry = range(5)
for attempt in retry:
try:
tn.open(self._host, timeout=PowerStrip.TIMEOUT)
resp = tn.read_until('Username:', timeout=PowerStrip.TIMEOUT)
assert 'Username' in resp, 'Username not found in response. (%s)' % resp
tn.write(self._user + '\n')
resp = tn.read_until('Password:', timeout=PowerStrip.TIMEOUT)
assert 'Password' in resp, 'Password not found in response. (%s)' % resp
tn.write(self._password + '\n')
resp = tn.read_until('Switched CDU:', timeout=PowerStrip.TIMEOUT)
assert 'Switched CDU' in resp, 'Standard prompt not found in ' \
'response. (%s)' % resp
tn.write('%s %s\n' % (command, outlet))
# Obtain the output of command and make sure it matches with the action
# we performed.
# Sample valid output:
# .A1 TowerA_Outlet1 On On
resp = tn.read_until('Switched CDU:', timeout=PowerStrip.TIMEOUT)
if not re.search('%s\s+\S+\s+%s\s+%s' % (outlet, command, command),
resp, re.I):
raise Exception('Command \'%s\' execution failed. (%s)' %
(command, resp))
# Exiting the telnet session cleanly significantly reduces the chance of
# connection error on initiating the following telnet session.
tn.write('exit\n')
tn.read_all()
# If we've gotten this far, there is no need to retry.
break
except Exception as e:
logging.debug('Power strip retry on cmd "%s". Reason: %s'
% (command, str(e)))
if attempt == retry[-1]:
raise Exception('Sentry Command "%s" failed. '
'Reason: %s' % (command, str(e)))
finally:
tn.close()