Home | History | Annotate | Download | only in chromeos
      1 # Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import logging
      6 import re
      7 import telnetlib
      8 
      9 
     10 class PowerStrip(object):
     11   """Controls Server Technology CW-16V1-C20M switched CDUs.
     12   (Cabinet Power Distribution Unit)
     13 
     14   This class is used to control the CW-16V1-C20M unit which
     15   is a 16 port remote power strip.  The strip supports AC devices
     16   using 100-120V 50/60Hz input voltages.  The commands in this
     17   class are supported by switches that use Sentry Switched CDU Version 6.0g.
     18 
     19   Opens a new connection for every command.
     20   """
     21 
     22   TIMEOUT = 10
     23 
     24   def __init__(self, host, user='admn', password='admn'):
     25     self._host = host
     26     self._user = user
     27     self._password = password
     28 
     29   def PowerOff(self, outlet):
     30     """Powers off the device that is plugged into the specified outlet.
     31 
     32     Args:
     33       outlet: The outlet ID defined on the switch (eg. .a14).
     34     """
     35     self._DoCommand('off', outlet)
     36 
     37   def PowerOn(self, outlet):
     38     """Powers on the device that is plugged into the specified outlet.
     39 
     40     Args:
     41       outlet: The outlet ID defined on the switch (eg. .a14).
     42     """
     43     self._DoCommand('on', outlet)
     44 
     45   def _DoCommand(self, command, outlet):
     46     """Performs power strip commands on the specified outlet.
     47 
     48     Sample telnet interaction:
     49       Escape character is '^]'.
     50 
     51       Sentry Switched CDU Version 6.0g
     52 
     53       Username: admn
     54       Password: < password hidden from view >
     55 
     56       Location:
     57 
     58       Switched CDU: on .a1
     59 
     60          Outlet   Outlet                     Outlet      Control
     61          ID       Name                       Status      State
     62 
     63          .A1      TowerA_Outlet1             On          On
     64 
     65          Command successful
     66 
     67       Switched CDU: < cdu cmd >
     68 
     69     Args:
     70       command: A valid CW-16V1-C20M command that follows the format
     71                <command> <outlet>.
     72       outlet: The outlet ID defined on the switch (eg. .a14).
     73     """
     74     tn = telnetlib.Telnet()
     75     # To avoid 'Connection Reset by Peer: 104' exceptions when rapid calls
     76     # are made to the telnet server on the power strip, we retry executing
     77     # a command.
     78     retry = range(5)
     79     for attempt in retry:
     80       try:
     81         tn.open(self._host, timeout=PowerStrip.TIMEOUT)
     82         resp = tn.read_until('Username:', timeout=PowerStrip.TIMEOUT)
     83         assert 'Username' in resp, 'Username not found in response. (%s)' % resp
     84         tn.write(self._user + '\n')
     85 
     86         resp = tn.read_until('Password:', timeout=PowerStrip.TIMEOUT)
     87         assert 'Password' in resp, 'Password not found in response. (%s)' % resp
     88         tn.write(self._password + '\n')
     89 
     90         resp = tn.read_until('Switched CDU:', timeout=PowerStrip.TIMEOUT)
     91         assert 'Switched CDU' in resp, 'Standard prompt not found in ' \
     92                                          'response. (%s)' % resp
     93         tn.write('%s %s\n' % (command, outlet))
     94 
     95         # Obtain the output of command and make sure it matches with the action
     96         # we performed.
     97         # Sample valid output:
     98         #   .A1      TowerA_Outlet1             On          On
     99         resp = tn.read_until('Switched CDU:', timeout=PowerStrip.TIMEOUT)
    100         if not re.search('%s\s+\S+\s+%s\s+%s' % (outlet, command, command),
    101                                                  resp, re.I):
    102           raise Exception('Command \'%s\' execution failed. (%s)' %
    103                           (command, resp))
    104 
    105         # Exiting the telnet session cleanly significantly reduces the chance of
    106         # connection error on initiating the following telnet session.
    107         tn.write('exit\n')
    108         tn.read_all()
    109 
    110         # If we've gotten this far, there is no need to retry.
    111         break
    112       except Exception as e:
    113         logging.debug('Power strip retry on cmd "%s".  Reason: %s'
    114                        % (command, str(e)))
    115         if attempt == retry[-1]:
    116           raise Exception('Sentry Command "%s" failed.  '
    117                           'Reason: %s' % (command, str(e)))
    118       finally:
    119         tn.close()
    120