3 # Reboot specified nodes
9 import errno, time, traceback
12 import threading, popen2
14 from monitor.wrapper import plc
16 from subprocess import PIPE, Popen
17 import pcucontrol.transports.ssh.pxssh as pxssh
18 import pcucontrol.transports.ssh.pexpect as pexpect
20 from monitor.util import command
22 # Use our versions of telnetlib and pyssh
23 sys.path.insert(0, os.path.dirname(sys.argv[0]))
24 import pcucontrol.transports.telnetlib as telnetlib
25 sys.path.insert(0, os.path.dirname(sys.argv[0]) + "/pyssh")
26 import pcucontrol.transports.pyssh as pyssh
27 from monitor import config
29 from monitor.database.info.model import FindbadPCURecord
34 # Event class ID from pcu events
35 #NODE_POWER_CONTROL = 3
38 #MONITOR_USER_ID = 11142
41 logger = logging.getLogger("monitor")
45 class ExceptionNoTransport(Exception): pass
46 class ExceptionNotFound(Exception): pass
47 class ExceptionPassword(Exception): pass
48 class ExceptionTimeout(Exception): pass
49 class ExceptionPrompt(Exception): pass
50 class ExceptionSequence(Exception): pass
51 class ExceptionReset(Exception): pass
52 class ExceptionPort(Exception): pass
53 class ExceptionUsername(Exception): pass
55 def telnet_answer(telnet, expected, buffer):
58 output = telnet.read_until(expected, TELNET_TIMEOUT)
60 # logger.debug(output)
61 if output.find(expected) == -1:
62 raise ExceptionNotFound, "'%s' not found" % expected
64 telnet.write(buffer + "\r\n")
67 # PCU has model, host, preferred-port, user, passwd,
69 # This is an object derived directly form the PLCAPI DB fields
71 def __init__(self, plc_pcu_dict):
72 for field in ['username', 'password', 'site_id',
75 'node_ids', 'ports', ]:
76 if field in plc_pcu_dict:
77 self.__setattr__(field, plc_pcu_dict[field])
79 raise Exception("No such field %s in PCU object" % field)
81 # These are the convenience functions build around the PCU object.
83 def __init__(self, plc_pcu_dict):
84 PCU.__init__(self, plc_pcu_dict)
85 self.host = self.pcu_name()
88 if self.hostname is not None and self.hostname is not "":
90 elif self.ip is not None and self.ip is not "":
95 def nodeidToPort(self, node_id):
96 if node_id in self.node_ids:
97 for i in range(0, len(self.node_ids)):
98 if node_id == self.node_ids[i]:
101 raise Exception("No such Node ID: %d" % node_id)
103 # This class captures the observed pcu records from FindBadPCUs.py
105 def __init__(self, pcu_record_dict):
106 for field in ['port_status',
109 if field in pcu_record_dict:
110 if field == "reboot":
111 self.__setattr__("reboot_str", pcu_record_dict[field])
113 self.__setattr__(field, pcu_record_dict[field])
115 raise Exception("No such field %s in pcu record dict" % field)
126 def __init__(self, type, verbose):
128 self.verbose = verbose
129 self.transport = None
131 def open(self, host, username=None, password=None, prompt="User Name"):
134 if self.type == self.TELNET:
135 transport = telnetlib.Telnet(host, timeout=self.TELNET_TIMEOUT)
136 transport.set_debuglevel(self.verbose)
137 if username is not None:
138 self.transport = transport
139 self.ifThenSend(prompt, username, ExceptionUsername)
141 elif self.type == self.SSH:
142 if username is not None:
143 transport = pyssh.Ssh(username, host)
144 transport.set_debuglevel(self.verbose)
146 # TODO: have an ssh set_debuglevel() also...
148 raise Exception("Username cannot be None for ssh transport.")
149 elif self.type == self.HTTP:
150 self.url = "http://%s:%d/" % (host,80)
151 uri = "%s:%d" % (host,80)
154 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm()
155 authinfo.add_password (None, uri, username, password)
156 authhandler = urllib2.HTTPBasicAuthHandler( authinfo )
158 transport = urllib2.build_opener(authhandler)
161 raise Exception("Unknown transport type: %s" % self.type)
163 self.transport = transport
167 if self.type == self.TELNET:
168 self.transport.close()
169 elif self.type == self.SSH:
170 self.transport.close()
171 elif self.type == self.HTTP:
174 raise Exception("Unknown transport type %s" % self.type)
175 self.transport = None
177 def sendHTTP(self, resource, data):
179 print "POSTing '%s' to %s" % (data,self.url + resource)
182 f = self.transport.open(self.url + resource ,data)
187 except urllib2.URLError,err:
188 logger.info('Could not open http connection', err)
189 return "http transport error"
193 def sendPassword(self, password, prompt=None):
194 if self.type == self.TELNET:
196 self.ifThenSend("Password", password, ExceptionPassword)
198 self.ifThenSend(prompt, password, ExceptionPassword)
199 elif self.type == self.SSH:
200 self.ifThenSend("password:", password, ExceptionPassword)
201 elif self.type == self.HTTP:
204 raise Exception("Unknown transport type: %s" % self.type)
206 def ifThenSend(self, expected, buffer, ErrorClass=ExceptionPrompt):
208 if self.transport != None:
209 output = self.transport.read_until(expected, self.TELNET_TIMEOUT)
210 if output.find(expected) == -1:
211 print "OUTPUT: --%s--" % output
212 raise ErrorClass, "'%s' not found" % expected
214 self.transport.write(buffer + "\r\n")
216 raise ExceptionNoTransport("transport object is type None")
218 def ifElse(self, expected, ErrorClass):
220 self.transport.read_until(expected, self.TELNET_TIMEOUT)
222 raise ErrorClass("Could not find '%s' within timeout" % expected)
225 class PCUControl(Transport,PCUModel,PCURecord):
229 def __init__(self, plc_pcu_record, verbose, supported_ports=[]):
230 PCUModel.__init__(self, plc_pcu_record)
231 PCURecord.__init__(self, plc_pcu_record)
234 if '22' in supported_ports and self.port_status['22'] == "open":
236 elif '23' in supported_ports and self.port_status['23'] == "open":
237 type = Transport.TELNET
238 # NOTE: prefer https over http
239 elif '443' in supported_ports and self.port_status['443'] == "open":
240 type = Transport.HTTPS
241 elif '80' in supported_ports and self.port_status['80'] == "open":
242 type = Transport.HTTP
243 elif '5869' in supported_ports and self.port_status['5869'] == "open":
244 # For DRAC cards. Racadm opens this port.
245 type = Transport.HTTP
246 elif '9100' in supported_ports and self.port_status['9100'] == "open":
247 type = Transport.IPAL
248 elif '16992' in supported_ports and self.port_status['16992'] == "open":
249 type = Transport.HTTP
251 raise ExceptionPort("Unsupported Port: No transport from open ports")
253 raise ExceptionPort("No Portstatus: No transport because no open ports")
254 Transport.__init__(self, type, verbose)
256 def run(self, node_port, dryrun):
257 """ This function is to be defined by the specific PCU instance. """
260 def reboot(self, node_port, dryrun):
262 return self.run(node_port, dryrun)
263 except ExceptionNotFound, err:
264 return "error: " + str(err)
265 except ExceptionPassword, err:
266 return "Password exception: " + str(err)
267 except ExceptionTimeout, err:
268 return "Timeout exception: " + str(err)
269 except ExceptionUsername, err:
270 return "No username prompt: " + str(err)
271 except ExceptionSequence, err:
272 return "Sequence error: " + str(err)
273 except ExceptionPrompt, err:
274 return "Prompt exception: " + str(err)
275 except ExceptionNoTransport, err:
276 return "No Transport: " + str(err)
277 except ExceptionPort, err:
278 return "No ports exception: " + str(err)
279 except socket.error, err:
280 return "socket error: timeout: " + str(err)
281 except urllib2.HTTPError, err:
282 return "HTTPError: " + str(err)
283 except urllib2.URLError, err:
284 return "URLError: " + str(err)
285 except EOFError, err:
287 logger.debug("reboot: EOF")
289 self.transport.close()
291 traceback.print_exc()
292 return "EOF connection reset" + str(err)
294 class IPMI(PCUControl):
296 supported_ports = [80,443,623]
298 # TODO: get exit codes to determine success or failure...
299 def run(self, node_port, dryrun):
302 cmd = "ipmitool -I lanplus -H %s -U %s -P '%s' power cycle"
303 p = os.popen(cmd % ( self.host, self.username, self.password) )
305 print "RESULT: ", result
307 cmd = "ipmitool -I lanplus -H %s -U %s -P '%s' user list"
308 p = os.popen(cmd % ( self.host, self.username, self.password) )
310 print "RESULT: ", result
312 if "Error" in result:
317 class IPAL(PCUControl):
319 This now uses a proprietary format for communicating with the PCU. I
320 prefer it to Telnet, and Web access, since it's much lighter weight
321 and, more importantly, IT WORKS!! HHAHHHAHAHAHAHAHA!
323 supported_ports = [23,80,9100]
325 def format_msg(self, data, cmd):
326 esc = chr(int('1b',16))
327 return "%c%s%c%s%c" % (esc, self.password, esc, data, cmd) # esc, 'q', chr(4))
329 def recv_noblock(self, s, count):
333 # TODO: make sleep backoff, before stopping.
335 ret = s.recv(count, socket.MSG_DONTWAIT)
336 except socket.error, e:
337 if e[0] == errno.EAGAIN:
338 raise Exception(e[1])
340 # TODO: not other exceptions.
344 def run(self, node_port, dryrun):
345 if self.type == Transport.IPAL:
346 return self.run_ipal(node_port, dryrun)
347 elif self.type == Transport.TELNET:
348 return self.run_telnet(node_port, dryrun)
350 raise ExceptionNoTransport("Unimplemented Transport for IPAL")
352 def run_telnet(self, node_port, dryrun):
353 # TELNET version of protocol...
355 ## XXX Some iPals require you to hit Enter a few times first
356 self.ifThenSend("Password >", "\r\n\r\n", ExceptionNotFound)
358 self.ifThenSend("Password >", self.password, ExceptionPassword)
359 self.transport.write("\r\n\r\n")
360 if not dryrun: # P# - Pulse relay
361 print "node_port %s" % node_port
362 self.ifThenSend("Enter >",
365 print "send newlines"
366 self.transport.write("\r\n\r\n")
367 print "after new lines"
368 # Get the next prompt
369 print "wait for enter"
370 self.ifElse("Enter >", ExceptionTimeout)
375 def run_ipal(self, node_port, dryrun):
381 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
384 s.connect((self.host, 9100))
385 except socket.error, e:
387 if e[0] == errno.ECONNREFUSED:
388 # cannot connect to remote host
389 raise Exception(e[1])
391 # TODO: what other conditions are there?
395 print "Checking status"
396 s.send(self.format_msg("", 'O'))
397 ret = self.recv_noblock(s, 8)
398 print "Current status is '%s'" % ret
401 raise Exception("Status returned 'another session already open' %s : %s" % (node_port, ret))
403 if node_port < len(ret):
404 status = ret[node_port]
412 raise Exception("Unknown status for PCU socket %s : %s" % (node_port, ret))
414 raise Exception("Mismatch between configured port and PCU status: %s %s" % (node_port, ret))
419 print "Pulsing %s" % node_port
420 s.send(self.format_msg("%s" % node_port, 'P'))
422 # NOTE: turn power on ; do not pulse the port.
423 print "Power was off, so turning on ..."
424 s.send(self.format_msg("%s" % node_port, 'E'))
425 #s.send(self.format_msg("%s" % node_port, 'P'))
427 print "Receiving response."
428 ret = self.recv_noblock(s, 8)
429 print "Current status is '%s'" % ret
431 if node_port < len(ret):
432 status = ret[node_port]
440 raise Exception("Unknown status for PCU socket %s : %s" % (node_port, ret))
442 raise Exception("Mismatch between configured port and PCU status: %s %s" % (node_port, ret))
447 return "Failed Power On"
452 class APCControl(PCUControl):
453 supported_ports = [22,23,80,443]
456 def run(self, node_port, dryrun):
457 print "RUNNING!!!!!!!!!!!!"
458 if self.type == Transport.HTTPS or self.type == Transport.HTTP:
459 print "APC via http...."
460 return self.run_http_or_https(node_port, dryrun)
462 print "APC via telnet/ssh...."
463 return self.run_telnet_or_ssh(node_port, dryrun)
465 def run_telnet_or_ssh(self, node_port, dryrun):
466 self.open(self.host, self.username)
467 self.sendPassword(self.password)
470 for val in self.reboot_sequence:
472 self.ifThenSend("\r\n> ", val, ExceptionPassword)
475 self.ifThenSend("\r\n> ", val)
478 self.ifThenSend("Enter 'YES' to continue or <ENTER> to cancel",
482 self.ifThenSend("Enter 'YES' to continue or <ENTER> to cancel",
483 "", ExceptionSequence)
484 self.ifThenSend("Press <ENTER> to continue...", "", ExceptionSequence)
489 def run_http_or_https(self, node_port, dryrun):
491 # send reboot signal.
492 # TODO: send a ManualPCU() reboot request for this PCU.
493 # NOTE: this model defies automation because, the port numbering
494 # and the form numbers are not consistent across models. There is
495 # not direct mapping from port# to form#.
496 return "Manual Reboot Required"
499 # TODO: also send message for https, since that doesn't work this way...
500 if self.type == Transport.HTTPS:
501 cmd = self.get_https_cmd()
502 elif self.type == Transport.HTTP:
503 cmd = self.get_http_cmd()
505 raise ExceptionNoTransport("Unsupported transport for http command")
507 cmd = cmd % ( self.username, self.password, self.host)
508 print "CMD: %s" % cmd
512 if len(result.split('\n')) > 2:
516 # NOTE: an error has occurred, so no need to log out.
517 print "RESULT: ", result
520 def get_https_cmd(self):
521 version = self.get_version()
522 print "VERSION: %s" % version
523 if "AP96" in version:
524 cmd = "curl -s --insecure --user '%s:%s' https://%s/outlets.htm " + \
525 " | grep -E '^[^<]+' " + \
526 " | grep -v 'Protected Object' "
528 # NOTE: no other case known right now...
529 cmd = "curl -s --insecure --user '%s:%s' https://%s/outlets.htm " + \
530 " | grep -E '^[^<]+' " + \
531 " | grep -v 'Protected Object' "
535 def get_http_cmd(self):
536 version = self.get_version()
537 print "VERSION: %s" % version
538 if "AP7900" in version:
539 cmd = "curl -s --anyauth --user '%s:%s' http://%s/rPDUout.htm | grep -E '^[^<]+'"
540 elif "AP7920" in version:
541 cmd = "curl -s --anyauth --user '%s:%s' http://%s/ms3out.htm | grep -E '^[^<]+' "
544 print "USING DEFAULT"
545 cmd = "curl -s --anyauth --user '%s:%s' http://%s/ms3out.htm | grep -E '^[^<]+' "
549 def get_version(self):
550 # NOTE: this command returns and formats all data.
551 #cmd = """curl -s --anyauth --user '%s:%s' http://%s/about.htm """ +
552 # """ | sed -e "s/<[^>]*>//g" -e "s/ //g" -e "/^$/d" """ +
553 # """ | awk '{line=$0 ; if ( ! /:/ && length(pline) > 0 ) \
554 # { print pline, line } else { pline=line} }' """ +
555 # """ | grep Model """
557 # NOTE: we may need to return software version, no model version to
558 # know which file to request on the server.
560 if self.type == Transport.HTTP:
561 cmd = """curl -s --anyauth --user '%s:%s' http://%s/about.htm """ + \
562 """ | sed -e "s/<[^>]*>//g" -e "s/ //g" -e "/^$/d" """ + \
563 """ | grep -E "AP[[:digit:]]+" """
564 #""" | grep -E "v[[:digit:]].*" """
565 elif self.type == Transport.HTTPS:
566 cmd = """curl -s --insecure --user '%s:%s' https://%s/about.htm """ + \
567 """ | sed -e "s/<[^>]*>//g" -e "s/ //g" -e "/^$/d" """ + \
568 """ | grep -E "AP[[:digit:]]+" """
569 #""" | grep -E "v[[:digit:]].*" """
571 raise ExceptionNoTransport("Unsupported transport to get version")
573 cmd = cmd % ( self.username, self.password, self.host)
576 return result.strip()
579 # NOTE: log out again, to allow other uses to access the machine.
580 if self.type == Transport.HTTP:
581 cmd = """curl -s --anyauth --user '%s:%s' http://%s/logout.htm """ + \
582 """ | grep -E '^[^<]+' """
583 elif self.type == Transport.HTTPS:
584 cmd = """curl -s --insecure --user '%s:%s' http://%s/logout.htm """ + \
585 """ | grep -E '^[^<]+' """
587 raise ExceptionNoTransport("Unsupported transport to logout")
589 cmd = cmd % ( self.username, self.password, self.host)
593 class APCControl12p3(APCControl):
594 def run(self, node_port, dryrun):
595 self.reboot_sequence = ["1", "2", str(node_port), "3"]
596 return super(APCControl12p3, self).run(node_port, dryrun)
598 class APCControl1p4(APCControl):
599 def run(self, node_port, dryrun):
600 self.reboot_sequence = ["1", str(node_port), "4"]
601 return super(APCControl1p4, self).run(node_port, dryrun)
603 class APCControl121p3(APCControl):
604 def run(self, node_port, dryrun):
605 self.reboot_sequence = ["1", "2", "1", str(node_port), "3"]
606 return super(APCControl121p3, self).run(node_port, dryrun)
608 class APCControl121p1(APCControl):
609 def run(self, node_port, dryrun):
610 self.reboot_sequence = ["1", "2", "1", str(node_port), "1", "3"]
611 return super(APCControl121p1, self).run(node_port, dryrun)
613 class APCControl13p13(APCControl):
614 def run(self, node_port, dryrun):
615 self.reboot_sequence = ["1", "3", str(node_port), "1", "3"]
616 return super(APCControl13p13, self).run(node_port, dryrun)
619 class IntelAMT(PCUControl):
620 supported_ports = [16992]
622 def run(self, node_port, dryrun):
625 # TODO: need to make this path universal; not relative to pwd.
626 cmd_str = config.MONITOR_SCRIPT_ROOT + "/pcucontrol/models/intelamt/remoteControl"
629 # NOTE: -p checks the power state of the host.
630 # TODO: parse the output to find out if it's ok or not.
631 cmd_str += " -p http://%s:16992/RemoteControlService -user admin -pass '%s' " % (self.host, self.password )
633 cmd_str += " -A http://%s:16992/RemoteControlService -user admin -pass '%s' " % (self.host, self.password )
636 return cmd.system(cmd_str, self.TELNET_TIMEOUT)
638 class DRAC(PCUControl):
639 def run(self, node_port, dryrun):
641 print "trying racadm_reboot..."
642 racadm_reboot(self.host, self.username, self.password, node_port, dryrun)
646 class DRACDefault(PCUControl):
647 supported_ports = [22,443,5869]
648 def run(self, node_port, dryrun):
649 self.open(self.host, self.username)
650 self.sendPassword(self.password)
652 print "logging in..."
653 self.transport.write("\r\n")
656 self.ifThenSend("[%s]#" % self.username, "getsysinfo")
659 self.ifThenSend("[%s]#" % self.username, "serveraction powercycle")
661 self.ifThenSend("[%s]#" % self.username, "exit")
666 class HPiLO(PCUControl):
667 supported_ports = [22,443]
668 def run(self, node_port, dryrun):
669 if self.type == Transport.SSH:
670 return self.run_ssh(node_port, dryrun)
671 elif self.type == Transport.HTTP or self.type == Transport.HTTPS:
672 return self.run_https(node_port, dryrun)
674 raise ExceptionNoTransport("Unimplemented Transport for HPiLO %s" % self.type)
676 def run_ssh(self, node_port, dryrun):
678 self.open(self.host, self.username)
679 self.sendPassword(self.password)
682 self.ifThenSend("</>hpiLO->", "cd system1")
684 # Reboot Outlet N (Y/N)?
686 self.ifThenSend("</system1>hpiLO->", "POWER")
689 self.ifThenSend("</system1>hpiLO->", "reset")
691 self.ifThenSend("</system1>hpiLO->", "exit")
696 def run_https(self, node_port, dryrun):
698 locfg = command.CMD()
700 cmd_str = config.MONITOR_SCRIPT_ROOT + "/pcucontrol/models/hpilo/"
702 cmd = cmd_str + "locfg.pl -s %s -f %s -u %s -p '%s' | grep 'MESSAGE' | grep -v 'No error'" % (
703 self.host, cmd_str+"iloxml/Get_Network.xml",
704 self.username, self.password)
705 sout, serr = locfg.run_noexcept(cmd)
707 if sout.strip() != "" or serr.strip() != "":
708 print "sout: %s" % sout.strip()
709 return sout.strip() + serr.strip()
712 locfg = command.CMD()
713 cmd = cmd_str + "locfg.pl -s %s -f %s -u %s -p '%s' | grep 'MESSAGE' | grep -v 'No error'" % (
714 self.host, cmd_str+"iloxml/Reset_Server.xml",
715 self.username, self.password)
716 sout, serr = locfg.run_noexcept(cmd)
718 if sout.strip() != "":
719 print "sout: %s" % sout.strip()
724 class BayTechRPC3NC(PCUControl):
725 def run(self, node_port, dryrun):
726 self.open(self.host, self.username, None, "Enter user name:")
727 self.sendPassword(self.password, "Enter Password:")
729 #self.ifThenSend("RPC-16>", "Status")
730 self.ifThenSend("RPC3-NC>", "Reboot %d" % node_port)
732 # Reboot Outlet N (Y/N)?
734 self.ifThenSend("(Y/N)?", "N")
736 self.ifThenSend("(Y/N)?", "Y")
737 self.ifThenSend("RPC3-NC>", "")
742 class BayTechRPC16(PCUControl):
743 def run(self, node_port, dryrun):
744 self.open(self.host, self.username, None, "Enter user name:")
745 self.sendPassword(self.password, "Enter Password:")
747 #self.ifThenSend("RPC-16>", "Status")
749 self.ifThenSend("RPC-16>", "Reboot %d" % node_port)
751 # Reboot Outlet N (Y/N)?
753 self.ifThenSend("(Y/N)?", "N")
755 self.ifThenSend("(Y/N)?", "Y")
756 self.ifThenSend("RPC-16>", "")
761 class BayTechCtrlCUnibe(PCUControl):
763 For some reason, these units let you log in fine, but they hang
764 indefinitely, unless you send a Ctrl-C after the password. No idea
767 def run(self, node_port, dryrun):
768 print "BayTechCtrlC %s" % self.host
770 ssh_options="-o StrictHostKeyChecking=no -o PasswordAuthentication=yes -o PubkeyAuthentication=no"
772 if not s.login(self.host, self.username, self.password, ssh_options):
773 raise ExceptionPassword("Invalid Password")
774 # Otherwise, the login succeeded.
776 # Send a ctrl-c to the remote process.
777 print "sending ctrl-c"
780 # Control Outlets (5 ,1).........5
782 #index = s.expect("Enter Request")
783 index = s.expect(["Enter Request :"])
788 index = s.expect(["DS-RPC>", "Enter user name:"])
790 s.send(self.username + "\r\n")
791 index = s.expect(["DS-RPC>"])
794 print "Reboot %d" % node_port
796 s.send("Reboot %d\r\n" % node_port)
799 index = s.expect(["\(Y/N\)\?", "Port in use", "DS-RPC>"])
808 raise ExceptionPrompt("PCU Reported 'Port in use.'")
810 raise ExceptionSequence("Issued command 'Reboot' failed.")
813 index = s.expect(["DS-RPC>"])
814 #print "got prompt back"
819 raise ExceptionPrompt("EOF before expected Prompt")
820 except pexpect.TIMEOUT:
821 raise ExceptionPrompt("Timeout before expected Prompt")
825 class BayTechCtrlC(PCUControl):
827 For some reason, these units let you log in fine, but they hang
828 indefinitely, unless you send a Ctrl-C after the password. No idea
831 def run(self, node_port, dryrun):
832 print "BayTechCtrlC %s" % self.host
834 ssh_options="-o StrictHostKeyChecking=no -o PasswordAuthentication=yes -o PubkeyAuthentication=no"
836 if not s.login(self.host, self.username, self.password, ssh_options):
837 raise ExceptionPassword("Invalid Password")
838 # Otherwise, the login succeeded.
840 # Send a ctrl-c to the remote process.
841 print "SENDING ctrl-c"
844 # Control Outlets (5 ,1).........5
846 print "EXPECTING: ", "Enter Request :"
847 index = s.expect(["Enter Request :"])
852 print "EXPECTING: ", "DS-RPC>"
853 index = s.expect(["DS-RPC>", "Enter user name:", "Port in use."])
855 print "sending username"
856 s.send(self.username + "\r\n")
857 index = s.expect(["DS-RPC>"])
859 raise ExceptionPrompt("PCU Reported 'Port in use.'")
862 print "SENDING: Reboot %d" % node_port
863 s.send("Reboot %d\r\n" % node_port)
867 print "EXPECTING: ", "Y/N?"
868 index = s.expect(["\(Y/N\)\?", "Port in use", "DS-RPC>"])
877 raise ExceptionPrompt("PCU Reported 'Port in use.'")
879 raise ExceptionSequence("Issued command 'Reboot' failed.")
881 # NOTE: for some reason, the script times out with the
882 # following line. In manual tests, it works correctly, but
883 # with automated tests, evidently it fails.
886 #print "TOTAL--", s.allstr, "--EOT"
887 index = s.expect(["DS-RPC>"])
888 print "got prompt back"
893 raise ExceptionPrompt("EOF before 'Enter Request' Prompt")
894 except pexpect.TIMEOUT:
895 raise ExceptionPrompt("Timeout before Prompt")
899 class BayTech(PCUControl):
900 supported_ports = [22,23]
901 def run(self, node_port, dryrun):
902 self.open(self.host, self.username)
903 self.sendPassword(self.password)
905 # Control Outlets (5 ,1).........5
906 self.ifThenSend("Enter Request :", "5")
910 self.ifThenSend("DS-RPC>", "Reboot %d" % node_port, ExceptionNotFound)
911 except ExceptionNotFound, msg:
912 # one machine is configured to ask for a username,
913 # even after login...
914 print "msg: %s" % msg
915 self.transport.write(self.username + "\r\n")
917 self.ifThenSend("DS-RPC>", "Reboot %d" % node_port)
919 # Reboot Outlet N (Y/N)?
921 self.ifThenSend("(Y/N)?", "N")
923 self.ifThenSend("(Y/N)?", "Y")
925 self.ifThenSend("DS-RPC>", "")
930 class WTIIPS4(PCUControl):
931 supported_ports = [23]
932 def run(self, node_port, dryrun):
934 self.sendPassword(self.password, "Enter Password:")
936 self.ifThenSend("IPS> ", "/Boot %s" % node_port)
938 self.ifThenSend("Sure? (Y/N): ", "N")
940 self.ifThenSend("Sure? (Y/N): ", "Y")
942 self.ifThenSend("IPS> ", "")
947 class ePowerSwitchNew(PCUControl):
949 # The old code used Python's HTTPPasswordMgrWithDefaultRealm()
950 # For some reason this both doesn't work and in some cases, actually
951 # hangs the PCU. Definitely not what we want.
953 # The code below is much simpler. Just letting things fail first,
954 # and then, trying again with authentication string in the header.
956 def run(self, node_port, dryrun):
957 self.transport = None
958 self.url = "http://%s:%d/" % (self.host,80)
959 uri = "%s:%d" % (self.host,80)
961 req = urllib2.Request(self.url)
963 handle = urllib2.urlopen(req)
965 # NOTE: this is expected to fail initially
972 return "ERROR: not protected by HTTP authentication"
974 if not hasattr(e, 'code') or e.code != 401:
975 return "ERROR: failed for: %s" % str(e)
977 base64data = base64.encodestring("%s:%s" % (self.username, self.password))[:-1]
978 # NOTE: assuming basic realm authentication.
979 authheader = "Basic %s" % base64data
980 req.add_header("Authorization", authheader)
983 f = urllib2.urlopen(req)
985 # failing here means the User/passwd is wrong (hopefully)
986 raise ExceptionPassword("Incorrect username/password")
988 # NOTE: after verifying that the user/password is correct,
989 # actually reboot the given node.
992 data = urllib.urlencode({'P%d' % node_port : "r"})
993 req = urllib2.Request(self.url + "cmd.html")
994 req.add_header("Authorization", authheader)
995 # add data to handler,
996 f = urllib2.urlopen(req, data)
997 if self.verbose: print f.read()
999 import traceback; traceback.print_exc()
1001 # fetch url one more time on cmd.html, econtrol.html or whatever.
1004 if self.verbose: print f.read()
1009 class CustomPCU(PCUControl):
1010 def run(self, node_port, dryrun):
1011 url = "https://www-itec.uni-klu.ac.at/plab-pcu/index.php"
1014 # Turn host off, then on
1015 formstr = "plab%s=off" % node_port
1016 os.system("curl --user %s:%s --form '%s' --insecure %s" % (self.username, self.password, formstr, url))
1018 formstr = "plab%s=on" % node_port
1019 os.system("curl --user %s:%s --form '%s' --insecure %s" % (self.username, self.password, formstr, url))
1021 os.system("curl --user %s:%s --insecure %s" % (self.username, self.password, url))
1024 class ePowerSwitchOld(PCUControl):
1025 def run(self, node_port, dryrun):
1026 self.url = "http://%s:%d/" % (self.host,80)
1027 uri = "%s:%d" % (self.host,80)
1030 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm()
1031 authinfo.add_password (None, uri, self.username, self.password)
1032 authhandler = urllib2.HTTPBasicAuthHandler( authinfo )
1034 # NOTE: it doesn't seem to matter whether this authinfo is here or not.
1035 transport = urllib2.build_opener(authinfo)
1036 f = transport.open(self.url)
1037 if self.verbose: print f.read()
1040 transport = urllib2.build_opener(authhandler)
1041 f = transport.open(self.url + "cmd.html", "P%d=r" % node_port)
1042 if self.verbose: print f.read()
1047 class ePowerSwitchOld(PCUControl):
1048 supported_ports = [80]
1049 def run(self, node_port, dryrun):
1050 self.url = "http://%s:%d/" % (self.host,80)
1051 uri = "%s:%d" % (self.host,80)
1053 # TODO: I'm still not sure what the deal is here.
1054 # two independent calls appear to need to be made before the
1055 # reboot will succeed. It doesn't seem to be possible to do
1056 # this with a single call. I have no idea why.
1059 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm()
1060 authinfo.add_password (None, uri, self.username, self.password)
1061 authhandler = urllib2.HTTPBasicAuthHandler( authinfo )
1063 # NOTE: it doesn't seem to matter whether this authinfo is here or not.
1064 transport = urllib2.build_opener()
1065 f = transport.open(self.url + "elogin.html", "pwd=%s" % self.password)
1066 if self.verbose: print f.read()
1069 transport = urllib2.build_opener(authhandler)
1070 f = transport.open(self.url + "econtrol.html", "P%d=r" % node_port)
1071 if self.verbose: print f.read()
1073 # data= "P%d=r" % node_port
1074 #self.open(self.host, self.username, self.password)
1075 #self.sendHTTP("elogin.html", "pwd=%s" % self.password)
1076 #self.sendHTTP("econtrol.html", data)
1077 #self.sendHTTP("cmd.html", data)
1082 class ManualPCU(PCUControl):
1083 supported_ports = [22,23,80,443,9100,16992]
1085 def run(self, node_port, dryrun):
1087 # TODO: send email message to monitor admin requesting manual
1088 # intervention. This should always be an option for ridiculous,
1093 class PM211MIP(ManualPCU):
1094 supported_ports = [80,443]
1096 ### rebooting european BlackBox PSE boxes
1097 # Thierry Parmentelat - May 11 2005
1098 # tested on 4-ports models known as PSE505-FR
1099 # uses http to POST a data 'P<port>=r'
1100 # relies on basic authentication within http1.0
1101 # first curl-based script was
1102 # curl --http1.0 --basic --user <username>:<password> --data P<port>=r \
1103 # http://<hostname>:<http_port>/cmd.html && echo OK
1108 class BlackBoxPSMaverick(PCUControl):
1109 supported_ports = [80]
1111 def run(self, node_port, dryrun):
1113 # send reboot signal.
1114 cmd = "curl -s --data 'P%s=r' --anyauth --user '%s:%s' http://%s/config/home_f.html" % ( node_port, self.username, self.password, self.host)
1116 # else, just try to log in
1117 cmd = "curl -s --anyauth --user '%s:%s' http://%s/config/home_f.html" % ( self.username, self.password, self.host)
1121 print "RESULT: ", result
1123 if len(result.split()) > 3:
1128 def bbpse_reboot (pcu_ip,username,password,port_in_pcu,http_port, dryrun):
1132 url = "http://%s:%d/cmd.html" % (pcu_ip,http_port)
1133 data= "P%d=r" % port_in_pcu
1135 logger.debug("POSTing '%s' on %s" % (data,url))
1137 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm()
1138 uri = "%s:%d" % (pcu_ip,http_port)
1139 authinfo.add_password (None, uri, username, password)
1140 authhandler = urllib2.HTTPBasicAuthHandler( authinfo )
1142 opener = urllib2.build_opener(authhandler)
1143 urllib2.install_opener(opener)
1149 f = urllib2.urlopen(url,data)
1156 except urllib2.URLError,err:
1157 logger.info('Could not open http connection', err)
1158 return "bbpse error"
1160 ### rebooting x10toggle based systems addressed by port
1161 # Marc E. Fiuczynski - May 31 2005
1162 # tested on 4-ports models known as PSE505-FR
1163 # uses ssh and password to login to an account
1164 # that will cause the system to be powercycled.
1166 def x10toggle_reboot(ip, username, password, port, dryrun):
1171 ssh = pyssh.Ssh(username, ip)
1175 telnet_answer(ssh, "password:", password)
1179 telnet_answer(ssh, "x10toggle>", "A%d" % port)
1182 output = ssh.close()
1184 logger.debug(output)
1187 except Exception, err:
1191 output = ssh.close()
1193 logger.debug(output)
1194 return errno.ETIMEDOUT
1196 ### rebooting Dell systems via RAC card
1197 # Marc E. Fiuczynski - June 01 2005
1198 # tested with David Lowenthal's itchy/scratchy nodes at UGA
1201 def runcmd(command, args, username, password, timeout = None):
1204 result_ready = threading.Condition()
1208 result_ready.acquire()
1212 result_ready.notify()
1213 result_ready.release()
1215 def do_command(command, username, password):
1218 # Popen4 is a popen-type class that combines stdout and stderr
1219 p = popen2.Popen4(command)
1221 # read all output data
1222 p.tochild.write("%s\n" % username)
1223 p.tochild.write("%s\n" % password)
1225 data = p.fromchild.read()
1228 # might get interrupted by a signal in poll() or waitpid()
1231 set_result((retval, data))
1234 if ex.errno == errno.EINTR:
1237 except Exception, ex:
1241 command = " ".join([command] + args)
1243 worker = threading.Thread(target = do_command, args = (command, username, password, ))
1244 worker.setDaemon(True)
1245 result_ready.acquire()
1247 result_ready.wait(timeout)
1249 if result == [None]:
1250 raise Exception, "command timed-out: '%s'" % command
1252 result_ready.release()
1255 if isinstance(result, Exception):
1258 (retval, data) = result
1259 if os.WIFEXITED(retval) and os.WEXITSTATUS(retval) == 0:
1262 out = "system command ('%s') " % command
1263 if os.WIFEXITED(retval):
1264 out += "failed, rc = %d" % os.WEXITSTATUS(retval)
1266 out += "killed by signal %d" % os.WTERMSIG(retval)
1268 out += "; output follows:\n" + data
1269 raise Exception, out
1271 def racadm_reboot(host, username, password, port, dryrun):
1274 ip = socket.gethostbyname(host)
1276 cmd = "/usr/sbin/racadm"
1279 output = runcmd(cmd, ["-r %s -i serveraction powercycle" % ip],
1282 output = runcmd(cmd, ["-r %s -i getsysinfo" % ip],
1285 print "RUNCMD: %s" % output
1287 logger.debug(output)
1290 except Exception, err:
1291 logger.debug("runcmd raised exception %s" % err)
1297 if pcu['hostname'] is not None and pcu['hostname'] is not "":
1298 return pcu['hostname']
1299 elif pcu['ip'] is not None and pcu['ip'] is not "":
1305 def get_pcu_values(pcu_id):
1306 print "pcuid: %s" % pcu_id
1308 pcurec = FindbadPCURecord.get_latest_by(plc_pcuid=pcu_id).first()
1310 values = pcurec.to_dict()
1318 def reboot(nodename):
1319 return reboot_policy(nodename, True, False)
1321 def reboot_str(nodename):
1323 continue_probe = True
1326 pcu = plc.getpcu(nodename)
1328 logger.debug("no pcu for %s" % nodename)
1329 print "no pcu for %s" % nodename
1330 return False # "%s has no pcu" % nodename
1332 values = get_pcu_values(pcu['pcu_id'])
1334 logger.debug("No values for pcu probe %s" % nodename)
1335 print "No values for pcu probe %s" % nodename
1336 return False #"no info for pcu_id %s" % pcu['pcu_id']
1339 logger.debug("Trying PCU %s %s" % (pcu['hostname'], pcu['model']))
1341 ret = reboot_test_new(nodename, values, verbose, dryrun)
1344 def reboot_policy(nodename, continue_probe, dryrun):
1347 pcu = plc.getpcu(nodename)
1349 logger.debug("no pcu for %s" % nodename)
1350 print "no pcu for %s" % nodename
1351 return False # "%s has no pcu" % nodename
1353 values = get_pcu_values(pcu['pcu_id'])
1355 logger.debug("No values for pcu probe %s" % nodename)
1356 print "No values for pcu probe %s" % nodename
1357 return False #"no info for pcu_id %s" % pcu['pcu_id']
1360 logger.debug("Trying PCU %s %s" % (pcu['hostname'], pcu['model']))
1362 ret = reboot_test_new(nodename, values, verbose, dryrun)
1371 class Unknown(PCUControl):
1372 supported_ports = [22,23,80,443,5869,9100,16992]
1374 def model_to_object(modelname):
1375 if "AMT" in modelname:
1377 elif "BayTech" in modelname:
1379 elif "HPiLO" in modelname:
1381 elif "IPAL" in modelname:
1383 elif "APC" in modelname:
1385 elif "DRAC" in modelname:
1387 elif "WTI" in modelname:
1389 elif "ePowerSwitch" in modelname:
1390 return ePowerSwitchNew
1391 elif "IPMI" in modelname:
1393 elif "BlackBoxPSMaverick" in modelname:
1394 return BlackBoxPSMaverick
1395 elif "PM211MIP" in modelname:
1397 elif "ManualPCU" in modelname:
1400 print "UNKNOWN model %s"%modelname
1403 def reboot_test_new(nodename, values, verbose, dryrun):
1405 if 'plc_pcu_stats' in values:
1406 values.update(values['plc_pcu_stats'])
1409 modelname = values['model']
1411 object = eval('%s(values, verbose, ["22", "23", "80", "443", "9100", "16992", "5869"])' % modelname)
1412 rb_ret = object.reboot(values[nodename], dryrun)
1415 # TODO: how to handle the weird, georgetown pcus, the drac faults, and ilo faults
1416 except ExceptionPort, err:
1422 def reboot_test(nodename, values, continue_probe, verbose, dryrun):
1424 if 'plc_pcu_stats' in values:
1425 values.update(values['plc_pcu_stats'])
1428 # DataProbe iPal (many sites)
1429 if continue_probe and values['model'].find("IP-41x_IP-81x") >= 0:
1430 ipal = IPAL(values, verbose, ['23', '80', '9100'])
1431 rb_ret = ipal.reboot(values[nodename], dryrun)
1433 # APC Masterswitch (Berkeley)
1434 elif continue_probe and ( values['model'].find("AP79xx") >= 0 or \
1435 values['model'].find("Masterswitch") >= 0 ):
1438 # TODO: make a more robust version of APC
1439 if values['pcu_id'] in [1102,1163,1055,1111,1231,1113,1127,1128,1148]:
1440 apc = APCControl12p3(values, verbose, ['22', '23'])
1441 rb_ret = apc.reboot(values[nodename], dryrun)
1443 elif values['pcu_id'] in [1110,86]:
1444 apc = APCControl1p4(values, verbose, ['22', '23'])
1445 rb_ret = apc.reboot(values[nodename], dryrun)
1447 elif values['pcu_id'] in [1221,1225,1220,1192]:
1448 apc = APCControl121p3(values, verbose, ['22', '23'])
1449 rb_ret = apc.reboot(values[nodename], dryrun)
1451 elif values['pcu_id'] in [1173,1240,47,1363,1405,1401,1372,1371]:
1452 apc = APCControl121p1(values, verbose, ['22', '23'])
1453 rb_ret = apc.reboot(values[nodename], dryrun)
1456 apc = APCControl13p13(values, verbose, ['22', '23'])
1457 rb_ret = apc.reboot(values[nodename], dryrun)
1460 elif continue_probe and values['model'].find("DS4-RPC") >= 0:
1461 if values['pcu_id'] in [1056,1237,1052,1209,1002,1008,1041,1013,1022]:
1462 # These require a 'ctrl-c' to be sent...
1463 baytech = BayTechCtrlC(values, verbose, ['22', '23'])
1464 rb_ret = baytech.reboot(values[nodename], dryrun)
1466 elif values['pcu_id'] in [93]:
1467 baytech = BayTechRPC3NC(values, verbose, ['22', '23'])
1468 rb_ret = baytech.reboot(values[nodename], dryrun)
1470 elif values['pcu_id'] in [1057]:
1471 # These require a 'ctrl-c' to be sent...
1472 baytech = BayTechCtrlCUnibe(values, verbose, ['22', '23'])
1473 rb_ret = baytech.reboot(values[nodename], dryrun)
1475 elif values['pcu_id'] in [1012]:
1476 # This pcu sometimes doesn't present the 'Username' prompt,
1477 # unless you immediately try again...
1479 baytech = BayTechRPC16(values, verbose, ['22', '23'])
1480 rb_ret = baytech.reboot(values[nodename], dryrun)
1482 baytech = BayTechRPC16(values, verbose, ['22', '23'])
1483 rb_ret = baytech.reboot(values[nodename], dryrun)
1485 baytech = BayTech(values, verbose, ['22', '23'])
1486 rb_ret = baytech.reboot(values[nodename], dryrun)
1489 elif continue_probe and values['model'].find("ilo") >= 0:
1491 hpilo = HPiLO(values, verbose, ['22'])
1492 rb_ret = hpilo.reboot(0, dryrun)
1494 hpilo = HPiLOHttps(values, verbose, ['443'])
1495 rb_ret = hpilo.reboot(0, dryrun)
1497 hpilo = HPiLOHttps(values, verbose, ['443'])
1498 rb_ret = hpilo.reboot(0, dryrun)
1501 elif continue_probe and values['model'].find("DRAC") >= 0:
1502 # TODO: I don't think DRAC will throw an exception for the
1503 # default method to catch...
1505 drac = DRAC(values, verbose, ['443', '5869'])
1506 rb_ret = drac.reboot(0, dryrun)
1508 drac = DRACDefault(values, verbose, ['22'])
1509 rb_ret = drac.reboot(0, dryrun)
1511 elif continue_probe and values['model'].find("WTI IPS-4") >= 0:
1512 wti = WTIIPS4(values, verbose, ['23'])
1513 rb_ret = wti.reboot(values[nodename], dryrun)
1515 elif continue_probe and values['model'].find("AMT") >= 0:
1516 amt = IntelAMT(values, verbose, ['16992'])
1517 rb_ret = amt.reboot(values[nodename], dryrun)
1519 elif continue_probe and values['model'].find("bbsemaverick") >=0:
1520 print "TRYING BlackBoxPSMaverick"
1521 bbe = BlackBoxPSMaverick(values, verbose, ['80'])
1522 rb_ret = bbe.reboot(values[nodename], dryrun)
1524 elif continue_probe and values['model'].find("ipmi") >=0:
1527 ipmi = IPMI(values, verbose, ['80', '443', '623'])
1528 rb_ret = ipmi.reboot(values[nodename], dryrun)
1530 elif continue_probe and values['model'].find("ePowerSwitch") >=0:
1531 # TODO: allow a different port than http 80.
1532 if values['pcu_id'] in [1089, 1071, 1046, 1035, 1118]:
1533 eps = ePowerSwitchNew(values, verbose, ['80'])
1534 elif values['pcu_id'] in [1003]:
1537 eps = ePowerSwitchOld(values, verbose, ['80'])
1539 eps = ePowerSwitchNew(values, verbose, ['80'])
1541 rb_ret = eps.reboot(values[nodename], dryrun)
1542 elif continue_probe and values['pcu_id'] in [1122]:
1543 custom = CustomPCU(values, verbose, ['80', '443'])
1544 custom.reboot(values[nodename], dryrun)
1546 elif continue_probe:
1547 rb_ret = "Unsupported_PCU"
1549 elif continue_probe == False:
1550 if 'port_status' in values:
1557 except ExceptionPort, err:
1562 #elif continue_probe and values['protocol'] == "racadm" and \
1563 # values['model'] == "RAC":
1564 # rb_ret = racadm_reboot(pcu_name(values),
1565 # values['username'],
1566 # values['password'],
1571 logger.setLevel(logging.DEBUG)
1572 ch = logging.StreamHandler()
1573 ch.setLevel(logging.DEBUG)
1574 formatter = logging.Formatter('LOGGER - %(message)s')
1575 ch.setFormatter(formatter)
1576 logger.addHandler(ch)
1579 if "test" in sys.argv:
1584 for node in sys.argv[1:]:
1585 if node == "test": continue
1587 print "Rebooting %s" % node
1588 if reboot_policy(node, True, dryrun):
1592 except Exception, err:
1593 import traceback; traceback.print_exc()
1596 if __name__ == '__main__':
1597 logger = logging.getLogger("monitor")