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