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()
362 cmd_str = get_python_lib(1) + "/pcucontrol/models/exp/"
363 cmd = cmd_str + "%s %s %s '%s' %s %s " % (
364 scriptname, self.host, self.username,
365 self.password, args['dryrun'], args['model'])
367 cmd_out, cmd_err = locfg.run_noexcept(cmd)
368 return cmd_out.strip() + cmd_err.strip()
370 def reboot(self, node_port, dryrun):
372 looking_for_fxn = 'pcu_test'
374 looking_for_fxn = 'pcu_run'
376 # verify that the function exists.
377 if not hasattr(self, looking_for_fxn):
378 raise Exception("This model (%s) does not implement %s" % (self.model, looking_for_fxn))
380 print "found function %s in model %s" % (looking_for_fxn, self.model)
381 reboot_fxn = getattr(self, looking_for_fxn)
382 ret = self.catcherror(reboot_fxn, node_port, dryrun)
386 def run(self, node_port, dryrun):
387 """ This function is to be defined by the specific PCU instance. """
388 raise Exception("This function is not implemented")
391 from pcucontrol.util import command
392 from pcucontrol.models import *
395 if pcu['hostname'] is not None and pcu['hostname'] is not "":
396 return pcu['hostname']
397 elif pcu['ip'] is not None and pcu['ip'] is not "":
402 class Unknown(PCUControl):
403 supported_ports = [22,23,80,443,5869,9100,16992]
405 def model_to_object(modelname):
406 if modelname is None:
408 if "AMT" in modelname:
410 elif "BayTech" in modelname:
412 elif "HPiLO" in modelname:
414 elif "IPAL" in modelname:
416 elif "APC" in modelname:
418 elif "DRAC" in modelname:
420 elif "WTI" in modelname:
422 elif "ePowerSwitch" in modelname:
423 return ePowerSwitchNew
424 elif "IPMI" in modelname:
426 elif "BlackBoxPSMaverick" in modelname:
427 return BlackBoxPSMaverick
428 elif "PM211MIP" in modelname:
430 elif "ManualPCU" in modelname:
433 print "UNKNOWN model %s"%modelname
436 def reboot_api(node, pcu, testrun=False):
440 modelname = pcu['model']
442 # get object instance
443 instance = eval('%s(pcu, verbose)' % modelname)
445 i = pcu['node_ids'].index(node['node_id'])
448 rb_ret = instance.reboot(p, testrun)
450 rb_ret = "No modelname in PCU record."
451 # TODO: how to handle the weird, georgetown pcus, the drac faults, and ilo faults
452 except Exception, err:
453 rb_ret = "Exception Model(%s): " % modelname
458 def convert_oldmodelname_to_newmodelname(oldmodelname, pcu_id):
460 update = { 'AP79xx' : 'APCControl13p13',
461 'Masterswitch' : 'APCControl13p13',
462 'DS4-RPC' : 'BayTech',
463 'IP-41x_IP-81x' : 'IPAL',
466 'ePowerSwitch' : 'ePowerSwitchOld',
469 'PM211-MIP' : 'PM211MIP',
470 'AMT2.5' : 'IntelAMT',
471 'AMT3.0' : 'IntelAMT',
472 'WTI_IPS-4' : 'WTIIPS4',
473 'unknown' : 'ManualPCU',
476 'bbsemaverick' : 'BlackBoxPSMaverick',
477 'manualadmin' : 'ManualPCU',
480 if oldmodelname in update:
481 newmodelname = update[oldmodelname]
483 newmodelname = oldmodelname
485 if pcu_id in [1102,1163,1055,1111,1231,1113,1127,1128,1148]:
486 newmodelname = 'APCControl12p3'
487 elif pcu_id in [1110,86]:
488 newmodelname = 'APCControl1p4'
489 elif pcu_id in [1221,1225,1220,1192]:
490 newmodelname = 'APCControl121p3'
491 elif pcu_id in [1173,1240,47,1363,1405,1401,1372,1371]:
492 newmodelname = 'APCControl121p1'
493 elif pcu_id in [1056,1237,1052,1209,1002,1008,1013,1022]:
494 newmodelname = 'BayTechCtrlC'
496 newmodelname = 'BayTechRPC3NC'
497 elif pcu_id in [1057]:
498 newmodelname = 'BayTechCtrlCUnibe'
499 elif pcu_id in [1012]:
500 newmodelname = 'BayTechRPC16'
501 elif pcu_id in [1089, 1071, 1046, 1035, 1118]:
502 newmodelname = 'ePowerSwitchNew'
506 def reboot_test_new(nodename, values, verbose, dryrun):
508 if 'plc_pcu_stats' in values:
509 values.update(values['plc_pcu_stats'])
512 #modelname = convert_oldmodelname_to_newmodelname(values['model'], values['pcu_id'])
513 modelname = values['model']
515 object = eval('%s(values, verbose)' % modelname)
516 rb_ret = object.reboot(values[nodename], dryrun)
519 # TODO: how to handle the weird, georgetown pcus, the drac faults, and ilo faults
520 except ExceptionPort, err:
522 except NameError, err:
528 print "this does not work."
530 if __name__ == '__main__':