-Some code cleaning to remove old ipal implementation.
[monitor.git] / reboot.py
1 #!/usr/bin/python
2 #
3 # Reboot specified nodes
4 #
5
6 import getpass, getopt
7 import os, sys
8 import xml, xmlrpclib
9 import errno, time, traceback
10 import urllib2
11 import threading, popen2
12 import array, struct
13 #from socket import *
14 import socket
15 import plc
16 import base64
17 from subprocess import PIPE, Popen
18 import ssh.pxssh as pxssh
19 import ssh.pexpect as pexpect
20 import socket
21
22 # Use our versions of telnetlib and pyssh
23 sys.path.insert(0, os.path.dirname(sys.argv[0]))
24 import telnetlib
25 sys.path.insert(0, os.path.dirname(sys.argv[0]) + "/pyssh")    
26 import pyssh
27
28 # Timeouts in seconds
29 TELNET_TIMEOUT = 45
30
31 # Event class ID from pcu events
32 #NODE_POWER_CONTROL = 3
33
34 # Monitor user ID
35 #MONITOR_USER_ID = 11142
36
37 import logging
38 logger = logging.getLogger("monitor")
39 verbose = 1
40 #dryrun = 0;
41
42 class ExceptionNoTransport(Exception): pass
43 class ExceptionNotFound(Exception): pass
44 class ExceptionPassword(Exception): pass
45 class ExceptionTimeout(Exception): pass
46 class ExceptionPrompt(Exception): pass
47 class ExceptionSequence(Exception): pass
48 class ExceptionReset(Exception): pass
49 class ExceptionPort(Exception): pass
50 class ExceptionUsername(Exception): pass
51
52 def telnet_answer(telnet, expected, buffer):
53         global verbose
54
55         output = telnet.read_until(expected, TELNET_TIMEOUT)
56         #if verbose:
57         #       logger.debug(output)
58         if output.find(expected) == -1:
59                 raise ExceptionNotFound, "'%s' not found" % expected
60         else:
61                 telnet.write(buffer + "\r\n")
62
63
64 # PCU has model, host, preferred-port, user, passwd, 
65
66 # This is an object derived directly form the PLCAPI DB fields
67 class PCU(object):
68         def __init__(self, plc_pcu_dict):
69                 for field in ['username', 'password', 'site_id', 
70                                                 'hostname', 'ip', 
71                                                 'pcu_id', 'model', 
72                                                 'node_ids', 'ports', ]:
73                         if field in plc_pcu_dict:
74                                 self.__setattr__(field, plc_pcu_dict[field])
75                         else:
76                                 raise Exception("No such field %s in PCU object" % field)
77
78 # These are the convenience functions build around the PCU object.
79 class PCUModel(PCU):
80         def __init__(self, plc_pcu_dict):
81                 PCU.__init__(self, plc_pcu_dict)
82                 self.host = self.pcu_name()
83
84         def pcu_name(self):
85                 if self.hostname is not None and self.hostname is not "":
86                         return self.hostname
87                 elif self.ip is not None and self.ip is not "":
88                         return self.ip
89                 else:
90                         return None
91
92         def nodeidToPort(self, node_id):
93                 if node_id in self.node_ids:
94                         for i in range(0, len(self.node_ids)):
95                                 if node_id == self.node_ids[i]:
96                                         return self.ports[i]
97
98                 raise Exception("No such Node ID: %d" % node_id)
99
100 # This class captures the observed pcu records from FindBadPCUs.py
101 class PCURecord:
102         def __init__(self, pcu_record_dict):
103                 for field in ['nodenames', 'portstatus', 
104                                                 'dnsmatch', 
105                                                 'complete_entry', ]:
106                         if field in pcu_record_dict:
107                                 if field == "reboot":
108                                         self.__setattr__("reboot_str", pcu_record_dict[field])
109                                 else:
110                                         self.__setattr__(field, pcu_record_dict[field])
111                         else:
112                                 raise Exception("No such field %s in pcu record dict" % field)
113
114 class Transport:
115         TELNET = 1
116         SSH    = 2
117         HTTP   = 3
118
119         TELNET_TIMEOUT = 60
120
121         def __init__(self, type, verbose):
122                 self.type = type
123                 self.verbose = verbose
124                 self.transport = None
125
126 #       def __del__(self):
127 #               if self.transport:
128 #                       self.close()
129
130         def open(self, host, username=None, password=None, prompt="User Name"):
131                 transport = None
132
133                 if self.type == self.TELNET:
134                         transport = telnetlib.Telnet(host, timeout=self.TELNET_TIMEOUT)
135                         transport.set_debuglevel(self.verbose)
136                         if username is not None:
137                                 self.transport = transport
138                                 self.ifThenSend(prompt, username, ExceptionUsername)
139
140                 elif self.type == self.SSH:
141                         if username is not None:
142                                 transport = pyssh.Ssh(username, host)
143                                 transport.set_debuglevel(self.verbose)
144                                 transport.open()
145                                 # TODO: have an ssh set_debuglevel() also...
146                         else:
147                                 raise Exception("Username cannot be None for ssh transport.")
148                 elif self.type == self.HTTP:
149                         self.url = "http://%s:%d/" % (host,80)
150                         uri = "%s:%d" % (host,80)
151
152                         # create authinfo
153                         authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm()
154                         authinfo.add_password (None, uri, username, password)
155                         authhandler = urllib2.HTTPBasicAuthHandler( authinfo )
156
157                         transport = urllib2.build_opener(authhandler)
158
159                 else:
160                         raise Exception("Unknown transport type: %s" % self.type)
161
162                 self.transport = transport
163                 return True
164
165         def close(self):
166                 if self.type == self.TELNET:
167                         self.transport.close() 
168                 elif self.type == self.SSH:
169                         self.transport.close() 
170                 elif self.type == self.HTTP:
171                         pass
172                 else:
173                         raise Exception("Unknown transport type %s" % self.type)
174                 self.transport = None
175
176         def sendHTTP(self, resource, data):
177                 if self.verbose:
178                         print "POSTing '%s' to %s" % (data,self.url + resource)
179
180                 try:
181                         f = self.transport.open(self.url + resource ,data)
182                         r = f.read()
183                         if self.verbose:
184                                 print r
185
186                 except urllib2.URLError,err:
187                         logger.info('Could not open http connection', err)
188                         return "http transport error"
189
190                 return 0
191
192         def sendPassword(self, password, prompt=None):
193                 if self.type == self.TELNET:
194                         if prompt == None:
195                                 self.ifThenSend("Password", password, ExceptionPassword)
196                         else:
197                                 self.ifThenSend(prompt, password, ExceptionPassword)
198                 elif self.type == self.SSH:
199                         self.ifThenSend("password:", password, ExceptionPassword)
200                 elif self.type == self.HTTP:
201                         pass
202                 else:
203                         raise Exception("Unknown transport type: %s" % self.type)
204
205         def ifThenSend(self, expected, buffer, ErrorClass=ExceptionPrompt):
206
207                 if self.transport != None:
208                         output = self.transport.read_until(expected, self.TELNET_TIMEOUT)
209                         if output.find(expected) == -1:
210                                 raise ErrorClass, "'%s' not found" % expected
211                         else:
212                                 self.transport.write(buffer + "\r\n")
213                 else:
214                         raise ExceptionNoTransport("transport object is type None")
215
216         def ifElse(self, expected, ErrorClass):
217                 try:
218                         self.transport.read_until(expected, self.TELNET_TIMEOUT)
219                 except:
220                         raise ErrorClass("Could not find '%s' within timeout" % expected)
221                         
222
223 class PCUControl(Transport,PCUModel,PCURecord):
224         def __init__(self, plc_pcu_record, verbose, supported_ports=[]):
225                 PCUModel.__init__(self, plc_pcu_record)
226                 PCURecord.__init__(self, plc_pcu_record)
227                 type = None
228                 if self.portstatus:
229                         if '22' in supported_ports and self.portstatus['22'] == "open":
230                                 type = Transport.SSH
231                         elif '23' in supported_ports and self.portstatus['23'] == "open":
232                                 type = Transport.TELNET
233                         elif '80' in supported_ports and self.portstatus['80'] == "open":
234                                 type = Transport.HTTP
235                         elif '443' in supported_ports and self.portstatus['443'] == "open":
236                                 type = Transport.HTTP
237                         elif '5869' in supported_ports and self.portstatus['5869'] == "open":
238                                 # For DRAC cards.  not sure how much it's used in the
239                                 # protocol.. but racadm opens this port.
240                                 type = Transport.HTTP
241                         else:
242                                 raise ExceptionPort("Unsupported Port: No transport from open ports")
243                 Transport.__init__(self, type, verbose)
244
245         def run(self, node_port, dryrun):
246                 """ This function is to be defined by the specific PCU instance.  """
247                 pass
248                 
249         def reboot(self, node_port, dryrun):
250                 try:
251                         return self.run(node_port, dryrun)
252                 except ExceptionNotFound, err:
253                         return "error: " + str(err)
254                 except ExceptionPassword, err:
255                         return "password exception: " + str(err)
256                 except ExceptionTimeout, err:
257                         return "timeout exception: " + str(err)
258                 except ExceptionUsername, err:
259                         return "exception: no username prompt: " + str(err)
260                 except ExceptionSequence, err:
261                         return "sequence error: " + str(err)
262                 except ExceptionPrompt, err:
263                         return "prompt exception: " + str(err)
264                 except ExceptionPort, err:
265                         return "no ports exception: " + str(err)
266                 except socket.error, err:
267                         return "socket error: timeout: " + str(err)
268                 except EOFError, err:
269                         if self.verbose:
270                                 logger.debug("reboot: EOF")
271                                 logger.debug(err)
272                         self.transport.close()
273                         import traceback
274                         traceback.print_exc()
275                         return "EOF connection reset" + str(err)
276                 #except Exception, err:
277                 #       if self.verbose:
278                 #               logger.debug("reboot: Exception")
279                 #               logger.debug(err)
280                 #       if self.transport:
281                 #               self.transport.close()
282                 #       import traceback
283                 #       traceback.print_exc()
284                 #       return  "generic exception; unknown problem."
285
286                 
287 class IPAL(PCUControl):
288         def run(self, node_port, dryrun):
289                 self.open(self.host)
290
291                 # XXX Some iPals require you to hit Enter a few times first
292                 self.ifThenSend("Password >", "\r\n\r\n", ExceptionNotFound)
293
294                 # Login
295                 self.ifThenSend("Password >", self.password, ExceptionPassword)
296                 self.transport.write("\r\n\r\n")
297
298                 if not dryrun: # P# - Pulse relay
299                         self.ifThenSend("Enter >", 
300                                                         "P%d" % node_port, 
301                                                         ExceptionNotFound)
302                 # Get the next prompt
303                 self.ifElse("Enter >", ExceptionTimeout)
304
305                 self.close()
306                 return 0
307
308 class APCEurope(PCUControl):
309         def run(self, node_port, dryrun):
310                 self.open(self.host, self.username)
311                 self.sendPassword(self.password)
312
313                 self.ifThenSend("\r\n> ", "1", ExceptionPassword)
314                 self.ifThenSend("\r\n> ", "2")
315                 self.ifThenSend("\r\n> ", str(node_port))
316                 # 3- Immediate Reboot             
317                 self.ifThenSend("\r\n> ", "3")
318
319                 if not dryrun:
320                         self.ifThenSend("Enter 'YES' to continue or <ENTER> to cancel", 
321                                                         "YES\r\n",
322                                                         ExceptionSequence)
323                 else:
324                         self.ifThenSend("Enter 'YES' to continue or <ENTER> to cancel", 
325                                                         "", ExceptionSequence)
326                 self.ifThenSend("Press <ENTER> to continue...", "", ExceptionSequence)
327
328                 self.close()
329                 return 0
330
331 class APCBrazil(PCUControl):
332         def run(self, node_port, dryrun):
333                 self.open(self.host, self.username)
334                 self.sendPassword(self.password)
335
336                 self.ifThenSend("\r\n> ", "1", ExceptionPassword)
337                 self.ifThenSend("\r\n> ", str(node_port))
338                 # 4- Immediate Reboot             
339                 self.ifThenSend("\r\n> ", "4")
340
341                 if not dryrun:
342                         self.ifThenSend("Enter 'YES' to continue or <ENTER> to cancel", 
343                                                         "YES\r\n",
344                                                         ExceptionSequence)
345                 else:
346                         self.ifThenSend("Enter 'YES' to continue or <ENTER> to cancel", 
347                                                         "", ExceptionSequence)
348                 self.ifThenSend("Press <ENTER> to continue...", "", ExceptionSequence)
349
350                 self.close()
351                 return 0
352
353 class APCBerlin(PCUControl):
354         def run(self, node_port, dryrun):
355                 self.open(self.host, self.username)
356                 self.sendPassword(self.password)
357
358                 self.ifThenSend("\r\n> ", "1", ExceptionPassword)
359                 self.ifThenSend("\r\n> ", "2")
360                 self.ifThenSend("\r\n> ", "1")
361                 self.ifThenSend("\r\n> ", str(node_port))
362                 # 3- Immediate Reboot             
363                 self.ifThenSend("\r\n> ", "3")
364
365                 if not dryrun:
366                         self.ifThenSend("Enter 'YES' to continue or <ENTER> to cancel", 
367                                                         "YES\r\n",
368                                                         ExceptionSequence)
369                 else:
370                         self.ifThenSend("Enter 'YES' to continue or <ENTER> to cancel", 
371                                                         "", ExceptionSequence)
372                 self.ifThenSend("Press <ENTER> to continue...", "", ExceptionSequence)
373
374                 self.close()
375                 return 0
376
377 class APCFolsom(PCUControl):
378         def run(self, node_port, dryrun):
379                 self.open(self.host, self.username)
380                 self.sendPassword(self.password)
381
382                 self.ifThenSend("\r\n> ", "1", ExceptionPassword)
383                 self.ifThenSend("\r\n> ", "2")
384                 self.ifThenSend("\r\n> ", "1")
385                 self.ifThenSend("\r\n> ", str(node_port))
386                 self.ifThenSend("\r\n> ", "1")
387
388                 # 3- Immediate Reboot             
389                 self.ifThenSend("\r\n> ", "3")
390
391                 if not dryrun:
392                         self.ifThenSend("Enter 'YES' to continue or <ENTER> to cancel", 
393                                                         "YES\r\n",
394                                                         ExceptionSequence)
395                 else:
396                         self.ifThenSend("Enter 'YES' to continue or <ENTER> to cancel", 
397                                                         "", ExceptionSequence)
398                 self.ifThenSend("Press <ENTER> to continue...", "", ExceptionSequence)
399
400                 self.close()
401                 return 0
402
403 class APCMaster(PCUControl):
404         def run(self, node_port, dryrun):
405                 self.open(self.host, self.username)
406                 self.sendPassword(self.password)
407
408                 # 1- Device Manager
409                 self.ifThenSend("\r\n> ", "1", ExceptionPassword)
410                 # 3- Outlet Control/Config
411                 self.ifThenSend("\r\n> ", "3")
412                 # n- Outlet n
413                 self.ifThenSend("\r\n> ", str(node_port))
414                 # 1- Control Outlet
415                 self.ifThenSend("\r\n> ", "1")
416                 # 3- Immediate Reboot             
417                 self.ifThenSend("\r\n> ", "3")
418
419                 if not dryrun:
420                         self.ifThenSend("Enter 'YES' to continue or <ENTER> to cancel", 
421                                                         "YES\r\n",
422                                                         ExceptionSequence)
423                 else:
424                         self.ifThenSend("Enter 'YES' to continue or <ENTER> to cancel", 
425                                                         "", ExceptionSequence)
426                 self.ifThenSend("Press <ENTER> to continue...", "", ExceptionSequence)
427
428                 self.close()
429                 return 0
430
431 class APC(PCUControl):
432         def __init__(self, plc_pcu_record, verbose):
433                 PCUControl.__init__(self, plc_pcu_record, verbose)
434
435                 self.master = APCMaster(plc_pcu_record, verbose)
436                 self.folsom = APCFolsom(plc_pcu_record, verbose)
437                 self.europe = APCEurope(plc_pcu_record, verbose)
438
439         def run(self, node_port, dryrun):
440                 try_again = True
441                 sleep_time = 1
442
443                 for pcu in [self.master, self.europe, self.folsom]:
444                         if try_again:
445                                 try:
446                                         print "-*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*_*"
447                                         try_again = False
448                                         print "sleeping 5"
449                                         time.sleep(sleep_time)
450                                         ret = pcu.reboot(node_port, dryrun)
451                                 except ExceptionSequence, err:
452                                         del pcu
453                                         sleep_time = 130
454                                         try_again = True
455
456                 if try_again:
457                         return "Unknown reboot sequence for APC PCU"
458                 else:
459                         return ret
460
461 class DRACRacAdm(PCUControl):
462         def run(self, node_port, dryrun):
463
464                 print "trying racadm_reboot..."
465                 racadm_reboot(self.host, self.username, self.password, node_port, dryrun)
466
467                 return 0
468
469 class DRAC(PCUControl):
470         def run(self, node_port, dryrun):
471                 self.open(self.host, self.username)
472                 self.sendPassword(self.password)
473
474                 print "logging in..."
475                 self.transport.write("\r\n")
476                 # Testing Reboot ?
477                 if dryrun:
478                         self.ifThenSend("[%s]#" % self.username, "getsysinfo")
479                 else:
480                         # Reset this machine
481                         self.ifThenSend("[%s]#" % self.username, "serveraction powercycle")
482
483                 self.ifThenSend("[%s]#" % self.username, "exit")
484
485                 self.close()
486                 return 0
487
488 class HPiLO(PCUControl):
489         def run(self, node_port, dryrun):
490                 self.open(self.host, self.username)
491                 self.sendPassword(self.password)
492
493                 # </>hpiLO-> 
494                 self.ifThenSend("</>hpiLO->", "cd system1")
495
496                 # Reboot Outlet  N        (Y/N)?
497                 if dryrun:
498                         self.ifThenSend("</system1>hpiLO->", "POWER")
499                 else:
500                         # Reset this machine
501                         self.ifThenSend("</system1>hpiLO->", "reset")
502
503                 self.ifThenSend("</system1>hpiLO->", "exit")
504
505                 self.close()
506                 return 0
507
508                 
509 class HPiLOHttps(PCUControl):
510         def run(self, node_port, dryrun):
511
512                 cmd = "cmdhttps/locfg.pl -s %s -f %s -u %s -p %s" % (
513                                         self.host, "iloxml/Get_Network.xml", 
514                                         self.username, self.password)
515                 p_ilo  = Popen(cmd, stdout=PIPE, shell=True)
516                 cmd2 = "grep 'MESSAGE' | grep -v 'No error'"
517                 p_grep = Popen(cmd2, stdin=p_ilo.stdout, stdout=PIPE, stderr=PIPE, shell=True)
518                 sout, serr = p_grep.communicate()
519
520                 p_ilo.wait()
521                 p_grep.wait()
522                 if sout.strip() != "":
523                         print "sout: %s" % sout.strip()
524                         return sout.strip()
525
526                 if not dryrun:
527                         cmd = "cmdhttps/locfg.pl -s %s -f %s -u %s -p %s" % (
528                                         self.host, "iloxml/Reset_Server.xml", 
529                                         self.username, self.password)
530                         print cmd
531                         p_ilo = Popen(cmd, stdin=PIPE, stdout=PIPE, shell=True)
532                         cmd2 = "grep 'MESSAGE' | grep -v 'No error'"
533                         p_grep = Popen(cmd2, stdin=p_ilo.stdout, stdout=PIPE, stderr=PIPE)
534                         sout, serr = p_grep.communicate()
535                         try: p_ilo.wait()
536                         except: 
537                                 print "p_ilo wait failed."
538                                 pass
539                         try: p_grep.wait()
540                         except: 
541                                 print "p_grep wait failed."
542                                 pass
543
544                         if sout.strip() != "":
545                                 print "sout: %s" % sout.strip()
546                                 return sout.strip()
547
548                 return 0
549
550 class BayTechAU(PCUControl):
551         def run(self, node_port, dryrun):
552                 self.open(self.host, self.username, None, "Enter user name:")
553                 self.sendPassword(self.password, "Enter Password:")
554
555                 #self.ifThenSend("RPC-16>", "Status")
556                 self.ifThenSend("RPC3-NC>", "Reboot %d" % node_port)
557
558                 # Reboot Outlet  N        (Y/N)?
559                 if dryrun:
560                         self.ifThenSend("(Y/N)?", "N")
561                 else:
562                         self.ifThenSend("(Y/N)?", "Y")
563                 self.ifThenSend("RPC3-NC>", "")
564
565                 self.close()
566                 return 0
567
568 class BayTechGeorgeTown(PCUControl):
569         def run(self, node_port, dryrun):
570                 self.open(self.host, self.username, None, "Enter user name:")
571                 self.sendPassword(self.password, "Enter Password:")
572
573                 #self.ifThenSend("RPC-16>", "Status")
574
575                 self.ifThenSend("RPC-16>", "Reboot %d" % node_port)
576
577                 # Reboot Outlet  N        (Y/N)?
578                 if dryrun:
579                         self.ifThenSend("(Y/N)?", "N")
580                 else:
581                         self.ifThenSend("(Y/N)?", "Y")
582                 self.ifThenSend("RPC-16>", "")
583
584                 self.close()
585                 return 0
586
587 class BayTechCtrlCUnibe(PCUControl):
588         """
589                 For some reason, these units let you log in fine, but they hang
590                 indefinitely, unless you send a Ctrl-C after the password.  No idea
591                 why.
592         """
593         def run(self, node_port, dryrun):
594                 print "BayTechCtrlC %s" % self.host
595
596                 ssh_options="-o StrictHostKeyChecking=no -o PasswordAuthentication=yes -o PubkeyAuthentication=no"
597                 s = pxssh.pxssh()
598                 if not s.login(self.host, self.username, self.password, ssh_options):
599                         raise ExceptionPassword("Invalid Password")
600                 # Otherwise, the login succeeded.
601
602                 # Send a ctrl-c to the remote process.
603                 print "sending ctrl-c"
604                 s.send(chr(3))
605
606                 # Control Outlets  (5 ,1).........5
607                 try:
608                         index = s.expect(["Enter Request :"])
609
610                         if index == 0:
611                                 print "3"
612                                 s.send("3\r\n")
613                                 index = s.expect(["DS-RPC>", "Enter user name:"])
614                                 if index == 1:
615                                         s.send(self.username + "\r\n")
616                                         index = s.expect(["DS-RPC>"])
617
618                                 if index == 0:
619                                         print "Reboot %d" % node_port
620                                         s.send("Reboot %d\r\n" % node_port)
621
622                                         index = s.expect(["(Y/N)?"])
623                                         if index == 0:
624                                                 if dryrun:
625                                                         print "sending N"
626                                                         s.send("N\r\n")
627                                                 else:
628                                                         print "sending Y"
629                                                         s.send("Y\r\n")
630
631                                 #index = s.expect(["DS-RPC>"])
632                                 #print "got prompt back"
633
634                         s.close()
635
636                 except pexpect.EOF:
637                         raise ExceptionPrompt("EOF before 'Enter Request' Prompt")
638                 except pexpect.TIMEOUT:
639                         raise ExceptionPrompt("Timeout before 'Enter Request' Prompt")
640
641                 return 0
642
643 class BayTechCtrlC(PCUControl):
644         """
645                 For some reason, these units let you log in fine, but they hang
646                 indefinitely, unless you send a Ctrl-C after the password.  No idea
647                 why.
648         """
649         def run(self, node_port, dryrun):
650                 print "BayTechCtrlC %s" % self.host
651
652                 ssh_options="-o StrictHostKeyChecking=no -o PasswordAuthentication=yes -o PubkeyAuthentication=no"
653                 s = pxssh.pxssh()
654                 if not s.login(self.host, self.username, self.password, ssh_options):
655                         raise ExceptionPassword("Invalid Password")
656                 # Otherwise, the login succeeded.
657
658                 # Send a ctrl-c to the remote process.
659                 print "sending ctrl-c"
660                 s.send(chr(3))
661
662                 # Control Outlets  (5 ,1).........5
663                 try:
664                         index = s.expect(["Enter Request :"])
665
666                         if index == 0:
667                                 print "5"
668                                 s.send("5\r\n")
669                                 index = s.expect(["DS-RPC>", "Enter user name:"])
670                                 if index == 1:
671                                         print "sending username"
672                                         s.send(self.username + "\r\n")
673                                         index = s.expect(["DS-RPC>"])
674
675                                 if index == 0:
676                                         print "Reboot %d" % node_port
677                                         s.send("Reboot %d\r\n" % node_port)
678
679                                         index = s.expect(["(Y/N)?"])
680                                         if index == 0:
681                                                 if dryrun:
682                                                         print "sending N"
683                                                         s.send("N\r\n")
684                                                 else:
685                                                         print "sending Y"
686                                                         s.send("Y\r\n")
687
688                                 #index = s.expect(["DS-RPC>"])
689                                 #print "got prompt back"
690
691                         s.close()
692
693                 except pexpect.EOF:
694                         raise ExceptionPrompt("EOF before 'Enter Request' Prompt")
695                 except pexpect.TIMEOUT:
696                         raise ExceptionPrompt("Timeout before 'Enter Request' Prompt")
697
698                 return 0
699
700 class BayTech(PCUControl):
701         def run(self, node_port, dryrun):
702                 self.open(self.host, self.username)
703                 self.sendPassword(self.password)
704
705                 # Control Outlets  (5 ,1).........5
706                 self.ifThenSend("Enter Request :", "5")
707
708                 # Reboot N
709                 try:
710                         self.ifThenSend("DS-RPC>", "Reboot %d" % node_port, ExceptionNotFound)
711                 except ExceptionNotFound, msg:
712                         # one machine is configured to ask for a username,
713                         # even after login...
714                         print "msg: %s" % msg
715                         self.transport.write(self.username + "\r\n")
716                         self.ifThenSend("DS-RPC>", "Reboot %d" % node_port)
717
718                 # Reboot Outlet  N        (Y/N)?
719                 if dryrun:
720                         self.ifThenSend("(Y/N)?", "N")
721                 else:
722                         self.ifThenSend("(Y/N)?", "Y")
723                 self.ifThenSend("DS-RPC>", "")
724
725                 self.close()
726                 return 0
727
728 class WTIIPS4(PCUControl):
729         def run(self, node_port, dryrun):
730                 self.open(self.host)
731                 self.sendPassword(self.password, "Enter Password:")
732
733                 self.ifThenSend("IPS> ", "/Boot %s" % node_port)
734                 if not dryrun:
735                         self.ifThenSend("Sure? (Y/N): ", "N")
736                 else:
737                         self.ifThenSend("Sure? (Y/N): ", "Y")
738
739                 self.ifThenSend("IPS> ", "")
740
741                 self.close()
742                 return 0
743
744 class ePowerSwitchGood(PCUControl):
745         # NOTE:
746         #               The old code used Python's HTTPPasswordMgrWithDefaultRealm()
747         #               For some reason this both doesn't work and in some cases, actually
748         #               hangs the PCU.  Definitely not what we want.
749         #               
750         #               The code below is much simpler.  Just letting things fail first,
751         #               and then, trying again with authentication string in the header.
752         #               
753         def run(self, node_port, dryrun):
754                 self.transport = None
755                 self.url = "http://%s:%d/" % (self.host,80)
756                 uri = "%s:%d" % (self.host,80)
757
758                 req = urllib2.Request(self.url)
759                 try:
760                         handle = urllib2.urlopen(req)
761                 except IOError, e:
762                         # NOTE: this is expected to fail initially
763                         pass
764                 else:
765                         print self.url
766                         print "-----------"
767                         print handle.read()
768                         print "-----------"
769                         return "ERROR: not protected by HTTP authentication"
770
771                 if not hasattr(e, 'code') or e.code != 401:
772                         return "ERROR: failed for: %s" % str(e)
773
774                 base64data = base64.encodestring("%s:%s" % (self.username, self.password))[:-1]
775                 # NOTE: assuming basic realm authentication.
776                 authheader = "Basic %s" % base64data
777                 req.add_header("Authorization", authheader)
778
779                 try:
780                         f = urllib2.urlopen(req)
781                 except IOError, e:
782                         # failing here means the User/passwd is wrong (hopefully)
783                         raise ExceptionPassword("Incorrect username/password")
784
785                 # TODO: after verifying that the user/password is correct, we should
786                 # actually reboot the given node.
787
788                 if not dryrun:
789                         # add data to handler,
790                         # fetch url one more time on cmd.html, econtrol.html or whatever.
791                         pass
792
793                 if self.verbose: print f.read()
794
795                 self.close()
796                 return 0
797
798
799 class ePowerSwitchOld(PCUControl):
800         def run(self, node_port, dryrun):
801                 self.url = "http://%s:%d/" % (self.host,80)
802                 uri = "%s:%d" % (self.host,80)
803
804                 # create authinfo
805                 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm()
806                 authinfo.add_password (None, uri, self.username, self.password)
807                 authhandler = urllib2.HTTPBasicAuthHandler( authinfo )
808
809                 # NOTE: it doesn't seem to matter whether this authinfo is here or not.
810                 transport = urllib2.build_opener(authinfo)
811                 f = transport.open(self.url)
812                 if self.verbose: print f.read()
813
814                 if not dryrun:
815                         transport = urllib2.build_opener(authhandler)
816                         f = transport.open(self.url + "cmd.html", "P%d=r" % node_port)
817                         if self.verbose: print f.read()
818
819                 self.close()
820                 return 0
821
822 class ePowerSwitch(PCUControl):
823         def run(self, node_port, dryrun):
824                 self.url = "http://%s:%d/" % (self.host,80)
825                 uri = "%s:%d" % (self.host,80)
826
827                 # TODO: I'm still not sure what the deal is here.
828                 #               two independent calls appear to need to be made before the
829                 #               reboot will succeed.  It doesn't seem to be possible to do
830                 #               this with a single call.  I have no idea why.
831
832                 # create authinfo
833                 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm()
834                 authinfo.add_password (None, uri, self.username, self.password)
835                 authhandler = urllib2.HTTPBasicAuthHandler( authinfo )
836
837                 # NOTE: it doesn't seem to matter whether this authinfo is here or not.
838                 transport = urllib2.build_opener()
839                 f = transport.open(self.url + "elogin.html", "pwd=%s" % self.password)
840                 if self.verbose: print f.read()
841
842                 if not dryrun:
843                         transport = urllib2.build_opener(authhandler)
844                         f = transport.open(self.url + "econtrol.html", "P%d=r" % node_port)
845                         if self.verbose: print f.read()
846
847                 #       data= "P%d=r" % node_port
848                 #self.open(self.host, self.username, self.password)
849                 #self.sendHTTP("elogin.html", "pwd=%s" % self.password)
850                 #self.sendHTTP("econtrol.html", data)
851                 #self.sendHTTP("cmd.html", data)
852
853                 self.close()
854                 return 0
855                 
856
857 ### rebooting european BlackBox PSE boxes
858 # Thierry Parmentelat - May 11 2005
859 # tested on 4-ports models known as PSE505-FR
860 # uses http to POST a data 'P<port>=r'
861 # relies on basic authentication within http1.0
862 # first curl-based script was
863 # curl --http1.0 --basic --user <username>:<password> --data P<port>=r \
864 #       http://<hostname>:<http_port>/cmd.html && echo OK
865
866 def bbpse_reboot (pcu_ip,username,password,port_in_pcu,http_port, dryrun):
867
868         global verbose
869
870         url = "http://%s:%d/cmd.html" % (pcu_ip,http_port)
871         data= "P%d=r" % port_in_pcu
872         if verbose:
873                 logger.debug("POSTing '%s' on %s" % (data,url))
874
875         authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm()
876         uri = "%s:%d" % (pcu_ip,http_port)
877         authinfo.add_password (None, uri, username, password)
878         authhandler = urllib2.HTTPBasicAuthHandler( authinfo )
879
880         opener = urllib2.build_opener(authhandler)
881         urllib2.install_opener(opener)
882
883         if (dryrun):
884                 return 0
885
886         try:
887                 f = urllib2.urlopen(url,data)
888
889                 r= f.read()
890                 if verbose:
891                         logger.debug(r)
892                 return 0
893
894         except urllib2.URLError,err:
895                 logger.info('Could not open http connection', err)
896                 return "bbpse error"
897
898 ### rebooting x10toggle based systems addressed by port
899 # Marc E. Fiuczynski - May 31 2005
900 # tested on 4-ports models known as PSE505-FR
901 # uses ssh and password to login to an account
902 # that will cause the system to be powercycled.
903
904 def x10toggle_reboot(ip, username, password, port, dryrun):
905         global verbose
906
907         ssh = None
908         try:
909                 ssh = pyssh.Ssh(username, ip)
910                 ssh.open()
911
912                 # Login
913                 telnet_answer(ssh, "password:", password)
914
915                 if not dryrun:
916                         # Reboot
917                         telnet_answer(ssh, "x10toggle>", "A%d" % port)
918
919                 # Close
920                 output = ssh.close()
921                 if verbose:
922                         logger.debug(output)
923                 return 0
924
925         except Exception, err:
926                 if verbose:
927                         logger.debug(err)
928                 if ssh:
929                         output = ssh.close()
930                         if verbose:
931                                 logger.debug(output)
932                 return errno.ETIMEDOUT
933
934 ### rebooting Dell systems via RAC card
935 # Marc E. Fiuczynski - June 01 2005
936 # tested with David Lowenthal's itchy/scratchy nodes at UGA
937 #
938
939 def runcmd(command, args, username, password, timeout = None):
940
941         result = [None]
942         result_ready = threading.Condition()
943
944         def set_result(x):
945
946                 result_ready.acquire()
947                 try:
948                         result[0] = x
949                 finally:
950                         result_ready.notify()
951                         result_ready.release()
952
953         def do_command(command, username, password):
954
955                 try:
956                         # Popen4 is a popen-type class that combines stdout and stderr
957                         p = popen2.Popen4(command)
958
959                         # read all output data
960                         p.tochild.write("%s\n" % username)
961                         p.tochild.write("%s\n" % password)
962                         p.tochild.close()
963                         data = p.fromchild.read()
964
965                         while True:
966                                 # might get interrupted by a signal in poll() or waitpid()
967                                 try:
968                                         retval = p.wait()
969                                         set_result((retval, data))
970                                         break
971                                 except OSError, ex:
972                                         if ex.errno == errno.EINTR:
973                                                 continue
974                                         raise ex
975                 except Exception, ex:
976                         set_result(ex)
977
978         if args:
979                 command = " ".join([command] + args)
980
981         worker = threading.Thread(target = do_command, args = (command, username, password, ))
982         worker.setDaemon(True)
983         result_ready.acquire()
984         worker.start()
985         result_ready.wait(timeout)
986         try:
987                 if result == [None]:
988                         raise Exception, "command timed-out: '%s'" % command
989         finally:
990                 result_ready.release()
991         result = result[0]
992
993         if isinstance(result, Exception):
994                 raise result
995         else:
996                 (retval, data) = result
997                 if os.WIFEXITED(retval) and os.WEXITSTATUS(retval) == 0:
998                         return data
999                 else:
1000                         out = "system command ('%s') " % command
1001                         if os.WIFEXITED(retval):
1002                                 out += "failed, rc = %d" % os.WEXITSTATUS(retval)
1003                         else:
1004                                 out += "killed by signal %d" % os.WTERMSIG(retval)
1005                         if data:
1006                                 out += "; output follows:\n" + data
1007                         raise Exception, out
1008
1009 def racadm_reboot(host, username, password, port, dryrun):
1010         global verbose
1011
1012         ip = socket.gethostbyname(host)
1013         try:
1014                 cmd = "/usr/sbin/racadm"
1015                 os.stat(cmd)
1016                 if not dryrun:
1017                         output = runcmd(cmd, ["-r %s -i serveraction powercycle" % ip],
1018                                 username, password)
1019                 else:
1020                         output = runcmd(cmd, ["-r %s -i getsysinfo" % ip],
1021                                 username, password)
1022
1023                 print "RUNCMD: %s" % output
1024                 if verbose:
1025                         logger.debug(output)
1026                 return 0
1027
1028         except Exception, err:
1029                 logger.debug("runcmd raised exception %s" % err)
1030                 if verbose:
1031                         logger.debug(err)
1032                 return -1
1033
1034 def pcu_name(pcu):
1035         if pcu['hostname'] is not None and pcu['hostname'] is not "":
1036                 return pcu['hostname']
1037         elif pcu['ip'] is not None and pcu['ip'] is not "":
1038                 return pcu['ip']
1039         else:
1040                 return None
1041
1042 def get_pcu_values(pcu_id):
1043         # TODO: obviously, this shouldn't be loaded each time...
1044         import soltesz
1045         fb =soltesz.dbLoad("findbadpcus")
1046
1047         try:
1048                 values = fb['nodes']["id_%s" % pcu_id]['values']
1049         except:
1050                 values = None
1051
1052         return values
1053
1054 def check_open_port(values, port_list):
1055         ret = False
1056
1057         if 'portstatus' in values:
1058                 for port in port_list:
1059                         if      port in values['portstatus'] and \
1060                                 values['portstatus'][port] == "open":
1061
1062                                 ret = True
1063         
1064         return ret
1065         
1066 def reboot_policy(nodename, continue_probe, dryrun):
1067         global verbose
1068
1069         pcu = plc.getpcu(nodename)
1070         if not pcu:
1071                 return False # "%s has no pcu" % nodename
1072
1073         values = get_pcu_values(pcu['pcu_id'])
1074         if values == None:
1075                 return False #"no info for pcu_id %s" % pcu['pcu_id']
1076         
1077         # Try the PCU first
1078         logger.debug("Trying PCU %s %s" % (pcu['hostname'], pcu['model']))
1079
1080         ret = reboot_test(nodename, values, continue_probe, verbose, dryrun)
1081
1082         if ret != 0:
1083                 print ret
1084                 return False
1085         else:
1086                 return True
1087
1088 def reboot_test(nodename, values, continue_probe, verbose, dryrun):
1089         rb_ret = ""
1090
1091         try:
1092                 # DataProbe iPal (many sites)
1093                 if  continue_probe and values['model'].find("Dataprobe IP-41x/IP-81x") >= 0:
1094                         ipal = IPAL(values, verbose, ['23'])
1095                         rb_ret = ipal.reboot(values[nodename], dryrun)
1096                                 
1097                 # APC Masterswitch (Berkeley)
1098                 elif continue_probe and values['model'].find("APC AP79xx/Masterswitch") >= 0:
1099
1100                         # TODO: make a more robust version of APC
1101                         if values['pcu_id'] in [1163,1055,1111,1231,1113,1127,1128,1148]:
1102                                 apc = APCEurope(values, verbose, ['22', '23'])
1103                                 rb_ret = apc.reboot(values[nodename], dryrun)
1104
1105                         elif values['pcu_id'] in [1110,86]:
1106                                 apc = APCBrazil(values, verbose, ['22', '23'])
1107                                 rb_ret = apc.reboot(values[nodename], dryrun)
1108
1109                         elif values['pcu_id'] in [1221,1225]:
1110                                 apc = APCBerlin(values, verbose, ['22', '23'])
1111                                 rb_ret = apc.reboot(values[nodename], dryrun)
1112
1113                         elif values['pcu_id'] in [1173,1221,1220]:
1114                                 apc = APCFolsom(values, verbose, ['22', '23'])
1115                                 rb_ret = apc.reboot(values[nodename], dryrun)
1116
1117                         else:
1118                                 apc = APCMaster(values, verbose, ['22', '23'])
1119                                 rb_ret = apc.reboot(values[nodename], dryrun)
1120
1121                 # BayTech DS4-RPC
1122                 elif continue_probe and values['model'].find("Baytech DS4-RPC") >= 0:
1123                         if values['pcu_id'] in [1237,1052,1209,1002,1008,1041,1013,1022]:
1124                                 # These  require a 'ctrl-c' to be sent... 
1125                                 baytech = BayTechCtrlC(values, verbose, ['22', '23'])
1126                                 rb_ret = baytech.reboot(values[nodename], dryrun)
1127
1128                         elif values['pcu_id'] in [93]:
1129                                 baytech = BayTechAU(values, verbose, ['22', '23'])
1130                                 rb_ret = baytech.reboot(values[nodename], dryrun)
1131
1132                         elif values['pcu_id'] in [1057]:
1133                                 # These  require a 'ctrl-c' to be sent... 
1134                                 baytech = BayTechCtrlCUnibe(values, verbose, ['22', '23'])
1135                                 rb_ret = baytech.reboot(values[nodename], dryrun)
1136
1137                         elif values['pcu_id'] in [1012]:
1138                                 # This pcu sometimes doesn't present the 'Username' prompt,
1139                                 # unless you immediately try again...
1140                                 try:
1141                                         baytech = BayTechGeorgeTown(values, verbose, ['22', '23'])
1142                                         rb_ret = baytech.reboot(values[nodename], dryrun)
1143                                 except:
1144                                         baytech = BayTechGeorgeTown(values, verbose, ['22', '23'])
1145                                         rb_ret = baytech.reboot(values[nodename], dryrun)
1146                         else:
1147                                 baytech = BayTech(values, verbose, ['22', '23'])
1148                                 rb_ret = baytech.reboot(values[nodename], dryrun)
1149
1150                 # iLO
1151                 elif continue_probe and values['model'].find("HP iLO") >= 0:
1152                         try:
1153                                 hpilo = HPiLO(values, verbose, ['22'])
1154                                 rb_ret = hpilo.reboot(0, dryrun)
1155                                 if rb_ret != 0:
1156                                         hpilo = HPiLOHttps(values, verbose, ['443'])
1157                                         rb_ret = hpilo.reboot(0, dryrun)
1158                         except:
1159                                 hpilo = HPiLOHttps(values, verbose, ['443'])
1160                                 rb_ret = hpilo.reboot(0, dryrun)
1161
1162                 # DRAC ssh
1163                 elif continue_probe and values['model'].find("Dell RAC") >= 0:
1164                         # TODO: I don't think DRACRacAdm will throw an exception for the
1165                         # default method to catch...
1166                         try:
1167                                 drac = DRACRacAdm(values, verbose, ['443', '5869'])
1168                                 rb_ret = drac.reboot(0, dryrun)
1169                         except:
1170                                 drac = DRAC(values, verbose, ['22'])
1171                                 rb_ret = drac.reboot(0, dryrun)
1172
1173                 elif continue_probe and values['model'].find("WTI IPS-4") >= 0:
1174                                 wti = WTIIPS4(values, verbose, ['23'])
1175                                 rb_ret = wti.reboot(values[nodename], dryrun)
1176
1177                 # BlackBox PSExxx-xx (e.g. PSE505-FR)
1178                 elif continue_probe and \
1179                         (values['model'].find("BlackBox PS5xx") >= 0 or
1180                          values['model'].find("ePowerSwitch 1/4/8x") >=0 ):
1181
1182                         # TODO: allow a different port than http 80.
1183                         if values['pcu_id'] in [1089, 1071, 1046, 1035, 1118]:
1184                                 eps = ePowerSwitchGood(values, verbose, ['80'])
1185                         elif values['pcu_id'] in [1003]:
1186                                 eps = ePowerSwitch(values, verbose, ['80'])
1187                         else:
1188                                 eps = ePowerSwitchGood(values, verbose, ['80'])
1189
1190                         rb_ret = eps.reboot(values[nodename], dryrun)
1191
1192                 elif continue_probe:
1193                         rb_ret = "Unsupported_PCU"
1194
1195                 elif continue_probe == False:
1196                         if 'portstatus' in values:
1197                                 rb_ret = "NetDown"
1198                         else:
1199                                 rb_ret = "Not_Run"
1200                 else:
1201                         rb_ret = -1
1202
1203         except ExceptionPort, err:
1204                 rb_ret = str(err)
1205
1206         return rb_ret
1207         # ????
1208         #elif continue_probe and values['protocol'] == "racadm" and \
1209         #               values['model'] == "RAC":
1210         #       rb_ret = racadm_reboot(pcu_name(values),
1211         #                                                                 values['username'],
1212         #                                                                 values['password'],
1213         #                                                                 pcu[nodename],
1214         #                                                                 dryrun)
1215
1216 def main():
1217         logger.setLevel(logging.DEBUG)
1218         ch = logging.StreamHandler()
1219         ch.setLevel(logging.DEBUG)
1220         formatter = logging.Formatter('LOGGER - %(message)s')
1221         ch.setFormatter(formatter)
1222         logger.addHandler(ch)
1223
1224         try:
1225                 for node in sys.argv[1:]:
1226                         print "Rebooting %s" % node
1227                         if reboot_policy(node, True, False):
1228                                 print "success"
1229                         else:
1230                                 print "failed"
1231         except Exception, err:
1232                 print err
1233
1234 if __name__ == '__main__':
1235         import plc
1236         logger = logging.getLogger("monitor")
1237         main()