# 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()