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