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