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 locfg = command.CMD()
367 cmd_str = get_python_lib(1) + "/pcucontrol/models/exp/"
368 cmd = cmd_str + "%s %s %s '%s' %s %s " % (
369 scriptname, host, self.username,
370 self.password, args['dryrun'], args['model'])
372 cmd_out, cmd_err = locfg.run_noexcept(cmd)
373 return cmd_out.strip() + cmd_err.strip()
375 def reboot(self, node_port, dryrun):
377 looking_for_fxn = 'pcu_test'
379 looking_for_fxn = 'pcu_run'
381 # verify that the function exists.
382 if not hasattr(self, looking_for_fxn):
383 raise Exception("This model (%s) does not implement %s" % (self.model, looking_for_fxn))
385 print "found function %s in model %s" % (looking_for_fxn, self.model)
386 reboot_fxn = getattr(self, looking_for_fxn)
387 ret = self.catcherror(reboot_fxn, node_port, dryrun)
391 def run(self, node_port, dryrun):
392 """ This function is to be defined by the specific PCU instance. """
393 raise Exception("This function is not implemented")
396 from pcucontrol.util import command
397 from pcucontrol.models import *
400 if pcu['hostname'] is not None and pcu['hostname'] is not "":
401 return pcu['hostname']
402 elif pcu['ip'] is not None and pcu['ip'] is not "":
407 class Unknown(PCUControl):
408 supported_ports = [22,23,80,443,5869,9100,16992]
410 def model_to_object(modelname):
411 if modelname is None:
413 if "AMT" in modelname:
415 elif "BayTech" in modelname:
417 elif "HPiLO" in modelname:
419 elif "IPAL" in modelname:
421 elif "APC" in modelname:
423 elif "DRAC" in modelname:
425 elif "WTI" in modelname:
427 elif "ePowerSwitch" in modelname:
428 return ePowerSwitchNew
429 elif "IPMI" in modelname:
431 elif "BlackBoxPSMaverick" in modelname:
432 return BlackBoxPSMaverick
433 elif "PM211MIP" in modelname:
435 elif "ManualPCU" in modelname:
438 print "UNKNOWN model %s"%modelname
441 def reboot_api(node, pcu, testrun=False):
445 modelname = pcu['model']
447 # get object instance
448 instance = eval('%s(pcu, verbose)' % modelname)
450 i = pcu['node_ids'].index(node['node_id'])
453 rb_ret = instance.reboot(p, testrun)
455 rb_ret = "No modelname in PCU record."
456 # TODO: how to handle the weird, georgetown pcus, the drac faults, and ilo faults
457 except Exception, err:
458 rb_ret = "Exception Model(%s): " % modelname
463 def convert_oldmodelname_to_newmodelname(oldmodelname, pcu_id):
465 update = { 'AP79xx' : 'APCControl13p13',
466 'Masterswitch' : 'APCControl13p13',
467 'DS4-RPC' : 'BayTech',
468 'IP-41x_IP-81x' : 'IPAL',
471 'ePowerSwitch' : 'ePowerSwitchOld',
474 'PM211-MIP' : 'PM211MIP',
475 'AMT2.5' : 'IntelAMT',
476 'AMT3.0' : 'IntelAMT',
477 'WTI_IPS-4' : 'WTIIPS4',
478 'unknown' : 'ManualPCU',
481 'bbsemaverick' : 'BlackBoxPSMaverick',
482 'manualadmin' : 'ManualPCU',
485 if oldmodelname in update:
486 newmodelname = update[oldmodelname]
488 newmodelname = oldmodelname
490 if pcu_id in [1102,1163,1055,1111,1231,1113,1127,1128,1148]:
491 newmodelname = 'APCControl12p3'
492 elif pcu_id in [1110,86]:
493 newmodelname = 'APCControl1p4'
494 elif pcu_id in [1221,1225,1220,1192]:
495 newmodelname = 'APCControl121p3'
496 elif pcu_id in [1173,1240,47,1363,1405,1401,1372,1371]:
497 newmodelname = 'APCControl121p1'
498 elif pcu_id in [1056,1237,1052,1209,1002,1008,1013,1022]:
499 newmodelname = 'BayTechCtrlC'
501 newmodelname = 'BayTechRPC3NC'
502 elif pcu_id in [1057]:
503 newmodelname = 'BayTechCtrlCUnibe'
504 elif pcu_id in [1012]:
505 newmodelname = 'BayTechRPC16'
506 elif pcu_id in [1089, 1071, 1046, 1035, 1118]:
507 newmodelname = 'ePowerSwitchNew'
511 def reboot_test_new(nodename, values, verbose, dryrun):
513 if 'plc_pcu_stats' in values:
514 values.update(values['plc_pcu_stats'])
517 #modelname = convert_oldmodelname_to_newmodelname(values['model'], values['pcu_id'])
518 modelname = values['model']
520 object = eval('%s(values, verbose)' % modelname)
521 rb_ret = object.reboot(values[nodename], dryrun)
524 # TODO: how to handle the weird, georgetown pcus, the drac faults, and ilo faults
525 except ExceptionPort, err:
527 except NameError, err:
533 print "this does not work."
535 if __name__ == '__main__':