3 # Reboot specified nodes
9 import errno, time, traceback
14 from subprocess import PIPE, Popen
15 from distutils.sysconfig import get_python_lib;
17 import pcucontrol.transports.ssh.pxssh as pxssh
18 import pcucontrol.transports.ssh.pexpect as pexpect
23 # Use our versions of telnetlib and pyssh
24 sys.path.insert(0, os.path.dirname(sys.argv[0]))
25 import pcucontrol.transports.telnetlib as telnetlib
26 sys.path.insert(0, os.path.dirname(sys.argv[0]) + "/pyssh")
27 import pcucontrol.transports.pyssh as pyssh
29 # Event class ID from pcu events
30 #NODE_POWER_CONTROL = 3
33 #MONITOR_USER_ID = 11142
39 class ExceptionNoTransport(Exception): pass
40 class ExceptionNotFound(Exception): pass
41 class ExceptionPassword(Exception): pass
42 class ExceptionTimeout(Exception): pass
43 class ExceptionPrompt(Exception): pass
44 class ExceptionSequence(Exception): pass
45 class ExceptionReset(Exception): pass
46 class ExceptionPort(Exception): pass
47 class ExceptionUsername(Exception): pass
51 # PCU has model, host, preferred-port, user, passwd,
53 # This is an object derived directly form the PLCAPI DB fields
55 def __init__(self, plc_pcu_dict):
56 for field in ['username', 'password', 'site_id',
59 'node_ids', 'ports', ]:
60 if field in plc_pcu_dict:
61 if type(u"") == type(plc_pcu_dict[field]):
62 # NOTE: if is a unicode string, convert it.
63 self.__setattr__(field, str(plc_pcu_dict[field]))
65 self.__setattr__(field, plc_pcu_dict[field])
67 raise Exception("No such field %s in PCU object" % field)
69 # These are the convenience functions build around the PCU object.
71 def __init__(self, plc_pcu_dict):
72 PCU.__init__(self, plc_pcu_dict)
73 self.host = self.pcu_name()
76 if self.hostname is not None and self.hostname is not "":
78 elif self.ip is not None and self.ip is not "":
83 def nodeidToPort(self, node_id):
84 if node_id in self.node_ids:
85 for i in range(0, len(self.node_ids)):
86 if node_id == self.node_ids[i]:
89 raise Exception("No such Node ID: %d" % node_id)
91 def catcherror(self, function, node_port, dryrun):
93 return function(node_port, dryrun)
94 except ExceptionNotFound, err:
95 return "Not found: " + str(err)
96 except ExceptionPassword, err:
97 return "Password exception: " + str(err)
98 except ExceptionTimeout, err:
99 return "Timeout exception: " + str(err)
100 except ExceptionUsername, err:
101 return "No username prompt: " + str(err)
102 except ExceptionSequence, err:
103 return "Sequence error: " + str(err)
104 except ExceptionPrompt, err:
105 return "Prompt exception: " + str(err)
106 except ExceptionNoTransport, err:
107 return "No Transport: " + str(err)
108 except ExceptionPort, err:
109 return "No ports exception: " + str(err)
110 except socket.error, err:
111 return "socket error: timeout: " + str(err)
112 except urllib2.HTTPError, err:
113 return "HTTPError: " + str(err)
114 except urllib2.URLError, err:
115 return "URLError: " + str(err)
116 except EOFError, err:
117 self.transport.close()
119 traceback.print_exc()
120 return "EOF connection reset" + str(err)
122 except Exception, err:
123 #from monitor.common import email_exception
124 #email_exception(self.host)
127 # This class captures the observed pcu records from FindBadPCUs.py
129 def __init__(self, pcu_record_dict):
130 for field in ['port_status',
133 if field in pcu_record_dict:
134 if field == "reboot":
135 self.__setattr__("reboot_str", pcu_record_dict[field])
137 self.__setattr__(field, pcu_record_dict[field])
139 # raise Exception("No such field %s in pcu record dict" % field)
164 def __init__(self, type, verbose):
166 self.verbose = verbose
167 self.transport = None
169 def open(self, host, username=None, password=None, prompt="User Name"):
172 if self.type == self.TELNET:
173 transport = telnetlib.Telnet(host, timeout=self.TELNET_TIMEOUT)
174 transport.set_debuglevel(self.verbose)
175 if username is not None:
176 self.transport = transport
177 self.ifThenSend(prompt, username, ExceptionUsername)
179 elif self.type == self.SSH:
180 if username is not None:
181 transport = pyssh.Ssh(username, host)
182 transport.set_debuglevel(self.verbose)
184 # TODO: have an ssh set_debuglevel() also...
186 raise Exception("Username cannot be None for ssh transport.")
187 elif self.type == self.HTTP:
188 # NOTE: this does not work for all web-based services...
189 self.url = "http://%s:%d/" % (host,80)
190 uri = "%s:%d" % (host,80)
193 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm()
194 authinfo.add_password (None, uri, username, password)
195 authhandler = urllib2.HTTPBasicAuthHandler( authinfo )
197 transport = urllib2.build_opener(authhandler)
199 raise Exception("Unknown transport type: %s" % self.type)
201 self.transport = transport
205 if self.type == self.TELNET:
206 self.transport.close()
207 elif self.type == self.SSH:
208 self.transport.close()
209 elif self.type == self.HTTP:
212 raise Exception("Unknown transport type %s" % self.type)
213 self.transport = None
215 def write(self, msg):
216 return self.send(msg)
219 if self.transport == None:
220 raise ExceptionNoTransport("transport object is type None")
222 return self.transport.write(msg)
224 def sendPassword(self, password, prompt=None):
225 if self.type == self.TELNET:
227 self.ifThenSend("Password", password, ExceptionPassword)
229 self.ifThenSend(prompt, password, ExceptionPassword)
230 elif self.type == self.SSH:
231 self.ifThenSend("password:", password, ExceptionPassword)
232 elif self.type == self.HTTP:
235 raise Exception("Unknown transport type: %s" % self.type)
237 def sendHTTP(self, resource, data):
239 print "POSTing '%s' to %s" % (data,self.url + resource)
242 f = self.transport.open(self.url + resource ,data)
247 except urllib2.URLError,err:
248 print 'Could not open http connection', err
249 return "http transport error"
253 def ifThenSend(self, expected, buffer, ErrorClass=ExceptionPrompt):
255 if self.transport != None:
256 output = self.transport.read_until(expected, self.TELNET_TIMEOUT)
257 if output.find(expected) == -1:
258 #print "OUTPUT: --%s--" % output
259 raise ErrorClass, "'%s' not found: Got: %s" % (expected, output)
261 self.transport.write(buffer + "\r\n")
263 raise ExceptionNoTransport("transport object is type None")
265 def ifElse(self, expected, ErrorClass):
267 self.transport.read_until(expected, self.TELNET_TIMEOUT)
269 raise ErrorClass("Could not find '%s' within timeout" % expected)
271 class PCUControl(PCUModel,PCURecord):
274 There are three cases:
275 1) the pcu_record passed below includes port_status from an
277 2) the external probe failed, and the values are empty
278 3) this call is made independent of port_status.
280 In the first case, the first open port is used.
281 In the third case, the ports are tried in sequence.
283 In this way, the port_status value serves only as an optimization,
284 because closed ports are avoided. The supported_ports value should
285 order ports by their preferred usage.
290 def __init__(self, plc_pcu_record, verbose, ignored=None):
291 PCUModel.__init__(self, plc_pcu_record)
292 PCURecord.__init__(self, plc_pcu_record)
294 def reboot(self, node_port, dryrun):
297 # There are two sources of potential ports. Those that are open and
298 # those that are part of the PCU's supported_ports.
299 # I think we should start with supported_ports and then filter that
302 port_list = self.supported_ports
304 if hasattr(self, 'port_status') and self.port_status:
305 # get out the open ports
306 port_list = filter(lambda x: self.port_status[x] == "open" , self.port_status.keys())
307 port_list = [ int(x) for x in port_list ]
308 # take only the open ports that are supported_ports
309 port_list = filter(lambda x: x in self.supported_ports, port_list)
311 raise ExceptionPort("No Open Port: No transport from open ports")
315 ret = "No implementation for open ports on selected PCU model"
316 for port in port_list:
317 if port not in Transport.porttypemap:
320 type = Transport.porttypemap[port]
321 self.transport = Transport(type, verbose)
323 #print "checking for run_%s" % type
324 if hasattr(self, "run_%s" % type):
325 #print "found run_%s" % type
326 fxn = getattr(self, "run_%s" % type)
327 ret = self.catcherror(fxn, node_port, dryrun)
328 if ret == 0: # NOTE: success!, so stop
335 def run(self, node_port, dryrun):
336 """ This function is to be defined by the specific PCU instance. """
337 raise Exception("This function is not implemented")
340 class BasicPCUControl(PCUModel):
343 This is the basic pcucontrol super class on which the classes in
344 pcucontrol/models/* are based.
346 Each model implements a specific 'run' function that can use any
347 mechanism available to reboot the given PCU using, hostname, password,
348 username, and dryrun.
350 This is best suited for nodes with built-in PCUs, iLO, AMT, DRAC, etc.
354 install_path = get_python_lib(1) + "/pcucontrol/"
356 def __init__(self, plc_pcu_record, ignored=None):
357 PCUModel.__init__(self, plc_pcu_record)
359 def run_expect_script(self, scriptname, **args):
360 #print "Running EXPECT: %s" % scriptname
361 locfg = command.CMD()
369 if 'sequence' in args:
370 seq = " ".join(args['sequence'])
375 cmd_str = get_python_lib(1) + "/pcucontrol/models/exp/"
376 cmd = cmd_str + "%s %s %s '%s' %s %s %s" % (
377 scriptname, host, self.username,
378 self.password, args['dryrun'], args['model'], seq)
380 cmd_out, cmd_err = locfg.run_noexcept("expect " + cmd)
381 return cmd_out.strip() + cmd_err.strip()
383 def reboot(self, node_port, dryrun):
385 looking_for_fxn = 'pcu_test'
387 looking_for_fxn = 'pcu_run'
389 # verify that the function exists.
390 if not hasattr(self, looking_for_fxn):
391 raise Exception("This model (%s) does not implement %s" % (self.model, looking_for_fxn))
393 #print "found function %s in model %s" % (looking_for_fxn, self.model)
394 reboot_fxn = getattr(self, looking_for_fxn)
395 ret = self.catcherror(reboot_fxn, node_port, dryrun)
399 def run(self, node_port, dryrun):
400 """ This function is to be defined by the specific PCU instance. """
401 raise Exception("This function is not implemented")
404 from pcucontrol.util import command
405 from pcucontrol.models import *
408 if pcu['hostname'] is not None and pcu['hostname'] is not "":
409 return pcu['hostname']
410 elif pcu['ip'] is not None and pcu['ip'] is not "":
415 class Unknown(PCUControl):
416 supported_ports = [22,23,80,443,5869,9100,16992]
418 def model_to_object(modelname):
419 if modelname is None:
421 if "AMT" in modelname:
423 elif "BayTech" in modelname:
425 elif "HPiLO" in modelname:
427 elif "IPAL" in modelname:
429 elif "APC" in modelname:
431 elif "DRAC" in modelname:
433 elif "WTI" in modelname:
435 elif "ePowerSwitch" in modelname:
436 return ePowerSwitchNew
437 elif "IPMI" in modelname:
439 elif "BlackBoxPSMaverick" in modelname:
440 return BlackBoxPSMaverick
441 elif "PM211MIP" in modelname:
443 elif "ManualPCU" in modelname:
446 print "UNKNOWN model %s"%modelname
449 def reboot_api(node, pcu, testrun=False):
453 modelname = pcu['model']
455 # get object instance
456 instance = eval('%s(pcu, verbose)' % modelname)
458 i = pcu['node_ids'].index(node['node_id'])
461 rb_ret = instance.reboot(p, testrun)
463 rb_ret = "No modelname in PCU record."
464 # TODO: how to handle the weird, georgetown pcus, the drac faults, and ilo faults
465 except Exception, err:
466 rb_ret = "Exception Model(%s): " % modelname
471 def convert_oldmodelname_to_newmodelname(oldmodelname, pcu_id):
473 update = { 'AP79xx' : 'APCControl13p13',
474 'Masterswitch' : 'APCControl13p13',
475 'DS4-RPC' : 'BayTech',
476 'IP-41x_IP-81x' : 'IPAL',
479 'ePowerSwitch' : 'ePowerSwitchOld',
482 'PM211-MIP' : 'PM211MIP',
483 'AMT2.5' : 'IntelAMT',
484 'AMT3.0' : 'IntelAMT',
485 'WTI_IPS-4' : 'WTIIPS4',
486 'unknown' : 'ManualPCU',
489 'bbsemaverick' : 'BlackBoxPSMaverick',
490 'manualadmin' : 'ManualPCU',
493 if oldmodelname in update:
494 newmodelname = update[oldmodelname]
496 newmodelname = oldmodelname
498 if pcu_id in [1102,1163,1055,1111,1231,1113,1127,1128,1148]:
499 newmodelname = 'APCControl12p3'
500 elif pcu_id in [1110,86]:
501 newmodelname = 'APCControl1p4'
502 elif pcu_id in [1221,1225,1220,1192]:
503 newmodelname = 'APCControl121p3'
504 elif pcu_id in [1173,1240,47,1363,1405,1401,1372,1371]:
505 newmodelname = 'APCControl121p1'
506 elif pcu_id in [1056,1237,1052,1209,1002,1008,1013,1022]:
507 newmodelname = 'BayTechCtrlC'
509 newmodelname = 'BayTechRPC3NC'
510 elif pcu_id in [1057]:
511 newmodelname = 'BayTechCtrlCUnibe'
512 elif pcu_id in [1012]:
513 newmodelname = 'BayTechRPC16'
514 elif pcu_id in [1089, 1071, 1046, 1035, 1118]:
515 newmodelname = 'ePowerSwitchNew'
519 def reboot_test_new(nodename, values, verbose, dryrun):
521 if 'plc_pcu_stats' in values:
522 values.update(values['plc_pcu_stats'])
525 #modelname = convert_oldmodelname_to_newmodelname(values['model'], values['pcu_id'])
526 modelname = values['model']
528 object = eval('%s(values, verbose)' % modelname)
529 rb_ret = object.reboot(values[nodename], dryrun)
532 # TODO: how to handle the weird, georgetown pcus, the drac faults, and ilo faults
533 except ExceptionPort, err:
535 except NameError, err:
540 def reboot_simple(nodeid, values, verbose, dryrun):
543 # Find the index of the given nodeid; use the same index in the ports list
544 port = values['ports'][values['node_ids'].index(nodeid)]
545 object = eval('%s(values, verbose)' % values['model'])
546 rb_ret = object.reboot(port, dryrun)
547 # TODO: how to handle the weird, georgetown pcus, the drac faults, and ilo faults?
548 except ExceptionPort, err:
550 except NameError, err:
556 print "this does not work."
558 if __name__ == '__main__':