fixed what looked like a typo in pculist.kid, restores 'view all'
[monitor.git] / pcucontrol / 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 urllib
12 import threading, popen2
13 import array, struct
14 import base64
15 from subprocess import PIPE, Popen
16 import pcucontrol.transports.ssh.pxssh as pxssh
17 import pcucontrol.transports.ssh.pexpect as pexpect
18 import socket
19
20
21
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
28 # Event class ID from pcu events
29 #NODE_POWER_CONTROL = 3
30
31 # Monitor user ID
32 #MONITOR_USER_ID = 11142
33
34 import logging
35 verbose = 1
36 #dryrun = 0;
37
38 class ExceptionNoTransport(Exception): pass
39 class ExceptionNotFound(Exception): pass
40 class ExceptionPassword(Exception): pass
41 class ExceptionTimeout(Exception): pass
42 class ExceptionPrompt(Exception): pass
43 class ExceptionSequence(Exception): pass
44 class ExceptionReset(Exception): pass
45 class ExceptionPort(Exception): pass
46 class ExceptionUsername(Exception): pass
47
48
49
50 # PCU has model, host, preferred-port, user, passwd, 
51
52 # This is an object derived directly form the PLCAPI DB fields
53 class PCU(object):
54         def __init__(self, plc_pcu_dict):
55                 for field in ['username', 'password', 'site_id', 
56                                                 'hostname', 'ip', 
57                                                 'pcu_id', 'model', 
58                                                 'node_ids', 'ports', ]:
59                         if field in plc_pcu_dict:
60                                 self.__setattr__(field, plc_pcu_dict[field])
61                         else:
62                                 raise Exception("No such field %s in PCU object" % field)
63
64 # These are the convenience functions build around the PCU object.
65 class PCUModel(PCU):
66         def __init__(self, plc_pcu_dict):
67                 PCU.__init__(self, plc_pcu_dict)
68                 self.host = self.pcu_name()
69
70         def pcu_name(self):
71                 if self.hostname is not None and self.hostname is not "":
72                         return self.hostname
73                 elif self.ip is not None and self.ip is not "":
74                         return self.ip
75                 else:
76                         return None
77
78         def nodeidToPort(self, node_id):
79                 if node_id in self.node_ids:
80                         for i in range(0, len(self.node_ids)):
81                                 if node_id == self.node_ids[i]:
82                                         return self.ports[i]
83
84                 raise Exception("No such Node ID: %d" % node_id)
85
86 # This class captures the observed pcu records from FindBadPCUs.py
87 class PCURecord:
88         def __init__(self, pcu_record_dict):
89                 for field in ['port_status', 
90                                                 'dns_status', 
91                                                 'entry_complete', ]:
92                         if field in pcu_record_dict:
93                                 if field == "reboot":
94                                         self.__setattr__("reboot_str", pcu_record_dict[field])
95                                 else:
96                                         self.__setattr__(field, pcu_record_dict[field])
97                         #else:
98                         #       raise Exception("No such field %s in pcu record dict" % field)
99
100 class Transport:
101         TELNET = "telnet"
102         SSH    = "ssh"
103         HTTP   = "http"
104         HTTPS  = "https"
105         IPAL   = "ipal"
106         DRAC   = "drac"
107         AMT    = "amt"
108
109         TELNET_TIMEOUT = 120
110
111         porttypemap = {
112                         5869 : DRAC,
113                         22 : SSH,
114                         23 : TELNET,
115                         443 : HTTPS,
116                         80 :  HTTP,
117                         9100 : IPAL,
118                         16992 : AMT,
119                 }
120
121         def __init__(self, type, verbose):
122                 self.type = type
123                 self.verbose = verbose
124                 self.transport = None
125
126         def open(self, host, username=None, password=None, prompt="User Name"):
127                 transport = None
128
129                 if self.type == self.TELNET:
130                         transport = telnetlib.Telnet(host, timeout=self.TELNET_TIMEOUT)
131                         transport.set_debuglevel(self.verbose)
132                         if username is not None:
133                                 self.transport = transport
134                                 self.ifThenSend(prompt, username, ExceptionUsername)
135
136                 elif self.type == self.SSH:
137                         if username is not None:
138                                 transport = pyssh.Ssh(username, host)
139                                 transport.set_debuglevel(self.verbose)
140                                 transport.open()
141                                 # TODO: have an ssh set_debuglevel() also...
142                         else:
143                                 raise Exception("Username cannot be None for ssh transport.")
144                 elif self.type == self.HTTP:
145                         # NOTE: this does not work for all web-based services...
146                         self.url = "http://%s:%d/" % (host,80)
147                         uri = "%s:%d" % (host,80)
148
149                         # create authinfo
150                         authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm()
151                         authinfo.add_password (None, uri, username, password)
152                         authhandler = urllib2.HTTPBasicAuthHandler( authinfo )
153
154                         transport = urllib2.build_opener(authhandler)
155                 else:
156                         raise Exception("Unknown transport type: %s" % self.type)
157
158                 self.transport = transport
159                 return True
160
161         def close(self):
162                 if self.type == self.TELNET:
163                         self.transport.close() 
164                 elif self.type == self.SSH:
165                         self.transport.close() 
166                 elif self.type == self.HTTP:
167                         pass
168                 else:
169                         raise Exception("Unknown transport type %s" % self.type)
170                 self.transport = None
171
172         def write(self, msg):
173                 return self.send(msg)
174
175         def send(self, msg):
176                 if self.transport == None:
177                         raise ExceptionNoTransport("transport object is type None")
178                         
179                 return self.transport.write(msg)
180
181         def sendPassword(self, password, prompt=None):
182                 if self.type == self.TELNET:
183                         if prompt == None:
184                                 self.ifThenSend("Password", password, ExceptionPassword)
185                         else:
186                                 self.ifThenSend(prompt, password, ExceptionPassword)
187                 elif self.type == self.SSH:
188                         self.ifThenSend("password:", password, ExceptionPassword)
189                 elif self.type == self.HTTP:
190                         pass
191                 else:
192                         raise Exception("Unknown transport type: %s" % self.type)
193
194         def sendHTTP(self, resource, data):
195                 if self.verbose:
196                         print "POSTing '%s' to %s" % (data,self.url + resource)
197
198                 try:
199                         f = self.transport.open(self.url + resource ,data)
200                         r = f.read()
201                         if self.verbose:
202                                 print r
203
204                 except urllib2.URLError,err:
205                         print 'Could not open http connection', err
206                         return "http transport error"
207
208                 return 0
209
210         def ifThenSend(self, expected, buffer, ErrorClass=ExceptionPrompt):
211
212                 if self.transport != None:
213                         output = self.transport.read_until(expected, self.TELNET_TIMEOUT)
214                         if output.find(expected) == -1:
215                                 print "OUTPUT: --%s--" % output
216                                 raise ErrorClass, "'%s' not found" % expected
217                         else:
218                                 self.transport.write(buffer + "\r\n")
219                 else:
220                         raise ExceptionNoTransport("transport object is type None")
221
222         def ifElse(self, expected, ErrorClass):
223                 try:
224                         self.transport.read_until(expected, self.TELNET_TIMEOUT)
225                 except:
226                         raise ErrorClass("Could not find '%s' within timeout" % expected)
227
228 class PCUControl(PCUModel,PCURecord):
229
230         """ 
231                 There are three cases:
232                         1) the pcu_record passed below includes port_status from an
233                                 external probe.
234                         2) the external probe failed, and the values are empty
235                         3) this call is made independent of port_status.
236
237                 In the first case, the first open port is used.
238                 In the third case, the ports are tried in sequence.
239
240                 In this way, the port_status value serves only as an optimization,
241                 because closed ports are avoided.  The supported_ports value should
242                 order ports by their preferred usage.
243         """
244
245         supported_ports = []
246
247         def __init__(self, plc_pcu_record, verbose, ignored=None):
248                 PCUModel.__init__(self, plc_pcu_record)
249                 PCURecord.__init__(self, plc_pcu_record)
250
251         def reboot(self, node_port, dryrun):
252
253                 port_list = []
254                 # There are two sources of potential ports.  Those that are open and
255                 # those that are part of the PCU's supported_ports.  
256                 #  I think we should start with supported_ports and then filter that
257                 #  by the open ports.
258
259                 port_list = self.supported_ports
260
261                 if hasattr(self, 'port_status') and self.port_status:
262                         # get out the open ports
263                         port_list = filter(lambda x: self.port_status[x] == "open" , self.port_status.keys())
264                         port_list = [ int(x) for x in port_list ]
265                         # take only the open ports that are supported_ports
266                         port_list = filter(lambda x: x in self.supported_ports, port_list)
267                         if port_list == []:
268                                 raise ExceptionPort("No Open Port: No transport from open ports")
269
270                 print port_list
271
272                 ret = "No implementation for open ports on selected PCU model"
273                 for port in port_list:
274                         if port not in Transport.porttypemap:
275                                 continue
276
277                         type = Transport.porttypemap[port]
278                         self.transport = Transport(type, verbose)
279
280                         print "checking for run_%s" % type
281                         if hasattr(self, "run_%s" % type):
282                                 print "found run_%s" % type
283                                 fxn = getattr(self, "run_%s" % type)
284                                 ret = self.catcherror(fxn, node_port, dryrun)
285                                 if ret == 0: # NOTE: success!, so stop
286                                         break
287                         else:
288                                 continue
289
290                 return ret
291
292         def run(self, node_port, dryrun):
293                 """ This function is to be defined by the specific PCU instance.  """
294                 raise Exception("This function is not implemented")
295                 pass
296
297         #def reboot(self, node_port, dryrun):
298
299         def catcherror(self, function, node_port, dryrun):
300                 try:
301                         return function(node_port, dryrun)
302                 except ExceptionNotFound, err:
303                         return "error: " + str(err)
304                 except ExceptionPassword, err:
305                         return "Password exception: " + str(err)
306                 except ExceptionTimeout, err:
307                         return "Timeout exception: " + str(err)
308                 except ExceptionUsername, err:
309                         return "No username prompt: " + str(err)
310                 except ExceptionSequence, err:
311                         return "Sequence error: " + str(err)
312                 except ExceptionPrompt, err:
313                         return "Prompt exception: " + str(err)
314                 except ExceptionNoTransport, err:
315                         return "No Transport: " + str(err)
316                 except ExceptionPort, err:
317                         return "No ports exception: " + str(err)
318                 except socket.error, err:
319                         return "socket error: timeout: " + str(err)
320                 except urllib2.HTTPError, err:
321                         return "HTTPError: " + str(err)
322                 except urllib2.URLError, err:
323                         return "URLError: " + str(err)
324                 except EOFError, err:
325                         self.transport.close()
326                         import traceback
327                         traceback.print_exc()
328                         return "EOF connection reset" + str(err)
329                 except Exception, err:
330                         from monitor.common import email_exception
331                         email_exception(self.host)
332                         raise Exception(err)
333
334 from pcucontrol.util import command
335 from pcucontrol.models import *
336
337 def pcu_name(pcu):
338         if pcu['hostname'] is not None and pcu['hostname'] is not "":
339                 return pcu['hostname']
340         elif pcu['ip'] is not None and pcu['ip'] is not "":
341                 return pcu['ip']
342         else:
343                 return None
344
345 class Unknown(PCUControl):
346         supported_ports = [22,23,80,443,5869,9100,16992]
347
348 def model_to_object(modelname):
349         if modelname is None:
350                 return ManualPCU 
351         if "AMT" in modelname:
352                 return IntelAMT
353         elif "BayTech" in modelname:
354                 return BayTech
355         elif "HPiLO" in modelname:
356                 return HPiLO
357         elif "IPAL" in modelname:
358                 return IPAL
359         elif "APC" in modelname:
360                 return APCControl
361         elif "DRAC" in modelname:
362                 return DRAC
363         elif "WTI" in modelname:
364                 return WTIIPS4
365         elif "ePowerSwitch" in modelname:
366                 return ePowerSwitchNew
367         elif "IPMI" in modelname:
368                 return OpenIPMI
369         elif "BlackBoxPSMaverick" in modelname:
370                 return BlackBoxPSMaverick
371         elif "PM211MIP" in modelname:
372                 return PM211MIP
373         elif "ManualPCU" in modelname:
374                 return ManualPCU 
375         else:
376                 print "UNKNOWN model %s"%modelname
377                 return Unknown
378
379 def reboot_api(node, pcu):
380         rb_ret = ""
381
382         try:
383                 modelname = pcu['model']
384                 if modelname:
385                         # get object instance 
386                         instance = eval('%s(pcu, verbose)' % modelname)
387                         # get pcu port 
388                         i = pcu['node_ids'].index(node['node_id'])
389                         p = pcu['ports'][i]
390                         # reboot
391                         rb_ret = instance.reboot(p, False)
392                 else:
393                         rb_ret =  "No modelname in PCU record."
394                 # TODO: how to handle the weird, georgetown pcus, the drac faults, and ilo faults
395         except Exception, err:
396                 rb_ret = "Exception Model(%s): " % modelname 
397                 rb_ret += str(err)
398
399         return rb_ret
400
401 def convert_oldmodelname_to_newmodelname(oldmodelname, pcu_id):
402         newmodelname = None
403         update = {      'AP79xx' : 'APCControl13p13',
404                                 'Masterswitch' : 'APCControl13p13',
405                                 'DS4-RPC' : 'BayTech',
406                                 'IP-41x_IP-81x' : 'IPAL',
407                                 'DRAC3' : 'DRAC',
408                                 'DRAC4' : 'DRAC',
409                                 'ePowerSwitch' : 'ePowerSwitchOld',
410                                 'ilo2' : 'HPiLO',
411                                 'ilo1' : 'HPiLO',
412                                 'PM211-MIP' : 'PM211MIP',
413                                 'AMT2.5' : 'IntelAMT',
414                                 'AMT3.0' : 'IntelAMT',
415                                 'WTI_IPS-4' : 'WTIIPS4',
416                                 'unknown'  : 'ManualPCU',
417                                 'DRAC5' : 'DRAC',
418                                 'ipmi'  : 'OpenIPMI',
419                                 'bbsemaverick' : 'BlackBoxPSMaverick',
420                                 'manualadmin'  : 'ManualPCU',
421         }
422
423         if oldmodelname in update:
424                 newmodelname = update[oldmodelname]
425         else:
426                 newmodelname = oldmodelname
427
428         if pcu_id in [1102,1163,1055,1111,1231,1113,1127,1128,1148]:
429                 newmodelname = 'APCControl12p3'
430         elif pcu_id in [1110,86]:
431                 newmodelname = 'APCControl1p4'
432         elif pcu_id in [1221,1225,1220,1192]:
433                 newmodelname = 'APCControl121p3'
434         elif pcu_id in [1173,1240,47,1363,1405,1401,1372,1371]:
435                 newmodelname = 'APCControl121p1'
436         elif pcu_id in [1056,1237,1052,1209,1002,1008,1013,1022]:
437                 newmodelname = 'BayTechCtrlC'
438         elif pcu_id in [93]:
439                 newmodelname = 'BayTechRPC3NC'
440         elif pcu_id in [1057]:
441                 newmodelname = 'BayTechCtrlCUnibe'
442         elif pcu_id in [1012]:
443                 newmodelname = 'BayTechRPC16'
444         elif pcu_id in [1089, 1071, 1046, 1035, 1118]:
445                 newmodelname = 'ePowerSwitchNew'
446
447         return newmodelname
448
449 def reboot_test_new(nodename, values, verbose, dryrun):
450         rb_ret = ""
451         if 'plc_pcu_stats' in values:
452                 values.update(values['plc_pcu_stats'])
453
454         try:
455                 #modelname = convert_oldmodelname_to_newmodelname(values['model'], values['pcu_id'])
456                 modelname = values['model']
457                 if modelname:
458                         object = eval('%s(values, verbose)' % modelname)
459                         rb_ret = object.reboot(values[nodename], dryrun)
460                 else:
461                         rb_ret =  "Not_Run"
462                 # TODO: how to handle the weird, georgetown pcus, the drac faults, and ilo faults
463         except ExceptionPort, err:
464                 rb_ret = str(err)
465         except NameError, err:
466                 rb_ret = str(err)
467
468         return rb_ret
469
470 def main():
471         print "this does not work."
472
473 if __name__ == '__main__':
474         main()