From 4c1af5526615cf1958b801176c802283d7d4d308 Mon Sep 17 00:00:00 2001 From: Stephen Soltesz Date: Sat, 16 Apr 2011 20:26:16 -0400 Subject: [PATCH] Enhance, simplify and beautify DRAC & HPiLO support with expect scripts Added pcuProxy&HPiLoProxy support for when pcu is behind a firewall. Added BasicPCUControl definition to simplify implementation. --- pcucontrol/models/DRAC.py | 183 +------ pcucontrol/models/HPiLO.py | 85 +--- pcucontrol/models/HPiLOProxy.py | 18 + pcucontrol/models/__init__.py | 1 + pcucontrol/models/exp/DRAC.exp | 104 ++++ pcucontrol/models/exp/HPiLO.exp | 62 +++ pcucontrol/reboot.py | 844 +++++++++++++++++--------------- pcucontrol/support/pcuProxy.php | 62 +++ pcucontrol/util/command.py | 6 +- 9 files changed, 746 insertions(+), 619 deletions(-) create mode 100644 pcucontrol/models/HPiLOProxy.py create mode 100755 pcucontrol/models/exp/DRAC.exp create mode 100755 pcucontrol/models/exp/HPiLO.exp create mode 100644 pcucontrol/support/pcuProxy.php diff --git a/pcucontrol/models/DRAC.py b/pcucontrol/models/DRAC.py index 11d23f6..672d92f 100644 --- a/pcucontrol/models/DRAC.py +++ b/pcucontrol/models/DRAC.py @@ -1,161 +1,28 @@ from pcucontrol.reboot import * import time -class DRAC(PCUControl): - supported_ports = [22,443,5869] - def run_drac(self, node_port, dryrun): - print "trying racadm_reboot..." - return racadm_reboot(self.host, self.username, self.password, node_port, dryrun) - - def run_ssh(self, node_port, dryrun): - ssh_options="-o StrictHostKeyChecking=no "+\ - "-o PasswordAuthentication=yes "+\ - "-o PubkeyAuthentication=no" - s = pxssh.pxssh() - try: - if not s.login(self.host, self.username, self.password, ssh_options, - original_prompts="Dell", login_timeout=Transport.TELNET_TIMEOUT): - raise ExceptionPassword("Invalid Password") - except pexpect.EOF: - raise ExceptionPrompt("Disconnect before login prompt") - - print "logging in... %s" % self.host - s.send("\r\n\r\n") - try: - # Testing Reboot ? - #index = s.expect(["DRAC 5", "[%s]#" % self.username ]) - # NOTE: be careful to escape any characters used by 're.compile' - index = s.expect(["\$", "\[%s\]#" % self.username, "/.*>" ]) - print "INDEX:", index - print s - if dryrun: - if index == 0: - s.sendline("racadm getsysinfo") - elif index == 1: - s.sendline("getsysinfo") - elif index == 2: - s.sendline("racadm getsysinfo") - else: - print "serveraction powercycle" - if index == 0: - s.sendline("racadm serveraction powercycle") - elif index == 1: - s.sendline("serveraction powercycle") - elif index == 2: - s.sendline("racadm serveraction powercycle") - - # TODO: this is really lousy. Without the sleep, the sendlines - # don't completely get through. Even the added, expect line - # returns right away without waiting for the commands above to - # complete... Therefore, this delay is guaranteed to fail in some - # other context... - s.send("\r\n\r\n") - time.sleep(20) - index = s.expect(["\$", "\[%s\]#" % self.username, "/.*>" ]) - print s - print "INDEX 2:", index - s.sendline("exit") - - except pexpect.EOF: - raise ExceptionPrompt("EOF before expected Prompt") - except pexpect.TIMEOUT: - print s - raise ExceptionPrompt("Timeout before expected Prompt") - - s.close() - - return 0 - -### rebooting Dell systems via RAC card -# Marc E. Fiuczynski - June 01 2005 -# tested with David Lowenthal's itchy/scratchy nodes at UGA -# -def runcmd(command, args, username, password, timeout = None): - - result = [None] - result_ready = threading.Condition() - - def set_result(x): - - result_ready.acquire() - try: - result[0] = x - finally: - result_ready.notify() - result_ready.release() - - def do_command(command, username, password): - - try: - # Popen4 is a popen-type class that combines stdout and stderr - p = popen2.Popen4(command) - - # read all output data - data = p.fromchild.read() - - while True: - # might get interrupted by a signal in poll() or waitpid() - try: - retval = p.wait() - set_result((retval, data)) - break - except OSError, ex: - if ex.errno == errno.EINTR: - continue - raise ex - except Exception, ex: - set_result(ex) - - if args: - command = " ".join([command] + args) - - worker = threading.Thread(target = do_command, args = (command, username, password, )) - worker.setDaemon(True) - result_ready.acquire() - worker.start() - result_ready.wait(timeout) - try: - if result == [None]: - raise Exception, "command timed-out: '%s'" % command - finally: - result_ready.release() - result = result[0] - - if isinstance(result, Exception): - raise result - else: - (retval, data) = result - if os.WIFEXITED(retval) and os.WEXITSTATUS(retval) == 0: - return data - else: - out = "system command ('%s') " % command - if os.WIFEXITED(retval): - out += "failed, rc = %d" % os.WEXITSTATUS(retval) - else: - out += "killed by signal %d" % os.WTERMSIG(retval) - if data: - out += "; output follows:\n" + data - raise Exception, out - -def racadm_reboot(host, username, password, port, dryrun): - global verbose - - ip = socket.gethostbyname(host) - try: - cmd = "/usr/sbin/racadm" - os.stat(cmd) - if not dryrun: - output = runcmd(cmd, ["-r %s -u %s -p '%s' serveraction powercycle" % (ip, username, password)], - username, password) - else: - output = runcmd(cmd, ["-r %s -u %s -p '%s' getsysinfo" % (ip, username, password)], - username, password) - - print "RUNCMD: %s" % output - if verbose: - print output - return 0 - - except Exception, err: - print "runcmd raised exception %s" % err - return str(err) +class DRAC(BasicPCUControl): + supported_ports = [22,443,5869] + def run_drac(self, node_port, dryrun): + return self.run_expect_script("DRAC.exp racadm5", dryrun=dryrun, model="None") + + def run_ssh(self, node_port, dryrun): + return self.run_expect_script("DRAC.exp ssh", dryrun=dryrun, model="None") + + def pcu_run(self, node_port): + r = self.run_ssh(node_port, dryrun=False) + if "No error" in r: + return r + r2 = self.run_drac(node_port, dryrun=False) + if "No error" in r2: + return r2 + return r + " :: " +r2 + + def pcu_test(self, node_port): + r = self.run_ssh(node_port, dryrun=True) + if "No error" in r: + return r + r2 = self.run_drac(node_port, dryrun=True) + if "No error" in r2: + return r2 + return r + " :: " +r2 diff --git a/pcucontrol/models/HPiLO.py b/pcucontrol/models/HPiLO.py index 2037ab7..e0595be 100644 --- a/pcucontrol/models/HPiLO.py +++ b/pcucontrol/models/HPiLO.py @@ -2,8 +2,8 @@ from pcucontrol.reboot import * from distutils.sysconfig import get_python_lib; import subprocess -class HPiLO(PCUControl): - supported_ports = [22,443] +class HPiLO(BasicPCUControl): + supported_ports = [443,22] def run(self, node_port, dryrun): if self.type == Transport.SSH: return self.run_ssh(node_port, dryrun) @@ -12,67 +12,26 @@ class HPiLO(PCUControl): else: raise ExceptionNoTransport("Unimplemented Transport for HPiLO %s" % self.type) - def run_ssh(self, node_port, dryrun): - - self.transport.open(self.host, self.username) - self.transport.sendPassword(self.password) - - # hpiLO-> - self.transport.ifThenSend("hpiLO->", "cd system1") - - # Reboot Outlet N (Y/N)? - if dryrun: - self.transport.ifThenSend("hpiLO->", "POWER") - else: - # Reset this machine - self.transport.ifThenSend("hpiLO->", "reset") + def pcu_run(self, node_port): + r = self.run_https(node_port, dryrun=False) + if "No error" in r: + return r + r2 = self.run_ssh(node_port, dryrun=False) + if "No error" in r2: + return r2 + return r + " :: " +r2 + + def pcu_test(self, node_port): + r = self.run_https(node_port, dryrun=True) + if "No error" in r: + return r + r2 = self.run_ssh(node_port, dryrun=True) + if "No error" in r2: + return r2 + return r + " :: " +r2 - self.transport.ifThenSend("hpiLO->", "exit") - - self.transport.close() - - # NOTE: if an error occurs earlier, an exception should be thrown - return 0 + def run_ssh(self, node_port, dryrun): + return self.run_expect_script("HPiLO.exp ssh", dryrun=dryrun, model="None") def run_https(self, node_port, dryrun): - - locfg = command.CMD() - - cmd_str = get_python_lib(1) + "/pcucontrol/models/hpilo/" - - cmd = cmd_str + "locfg.pl -s %s -f %s -u %s -p '%s' " % ( - self.host, cmd_str+"iloxml/Get_Network.xml", - self.username, self.password) - cmd_out, cmd_err = locfg.run_noexcept(cmd) - - if locfg.s.returncode != 0: - return cmd_out.strip() + cmd_err.strip() - - if dryrun: - return 0 - - locfg = command.CMD() - cmd = cmd_str + "locfg.pl -s %s -f %s -u %s -p '%s'" % ( - self.host, cmd_str+"iloxml/PowerOn_Server.xml", - self.username, self.password) - cmd_out, cmd_err = locfg.run_noexcept(cmd) - - locfg = command.CMD() - cmd = cmd_str + "locfg.pl -s %s -f %s -u %s -p '%s'" % ( - self.host, cmd_str+"iloxml/Reset_Server.xml", - self.username, self.password) - cmd_out, cmd_err = locfg.run_noexcept(cmd) - - cmd = "grep 'MESSAGE' | grep -v 'No error' | grep -v 'Server being reset.'" - p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, close_fds=True) - (grep_in, grep_out ) = (p.stdin, p.stdout) - grep_in.write(cmd_out) - grep_in.close() # close so read does not block - output = grep_out.read() - if output.strip() != "": - print "grep_out: %s" % output.strip() - return output.strip() - - # NOTE: if an error occurs earlier, an exception should be thrown - return 0 + return self.run_expect_script("HPiLO.exp https", dryrun=dryrun, model="None") diff --git a/pcucontrol/models/HPiLOProxy.py b/pcucontrol/models/HPiLOProxy.py new file mode 100644 index 0000000..1acfb02 --- /dev/null +++ b/pcucontrol/models/HPiLOProxy.py @@ -0,0 +1,18 @@ +from pcucontrol.reboot import * +import subprocess +import urllib + +class HPiLOProxy(BasicPCUControl): + supported_ports = [80] + + def pcu_run(self, node_port): + return self.proxy(node_port, False) + + def pcu_test(self, node_port): + return self.proxy(node_port, True) + + def proxy(self, node_port, dryrun): + return self.run_expect_script("HPiLO.exp proxy", + dryrun=dryrun, + model=self.__class__.__name__) + diff --git a/pcucontrol/models/__init__.py b/pcucontrol/models/__init__.py index 01d8f4b..5158cff 100644 --- a/pcucontrol/models/__init__.py +++ b/pcucontrol/models/__init__.py @@ -5,6 +5,7 @@ from APCControl import * from IntelAMT import * from DRAC import * from HPiLO import * +from HPiLOProxy import * from BayTech import * from WTIIPS4 import * from ePowerSwitch import * diff --git a/pcucontrol/models/exp/DRAC.exp b/pcucontrol/models/exp/DRAC.exp new file mode 100755 index 0000000..efa4562 --- /dev/null +++ b/pcucontrol/models/exp/DRAC.exp @@ -0,0 +1,104 @@ +#!/usr/bin/expect + +set timeout 20 +set method [lindex $argv 0] +set host [lindex $argv 1] +set user [lindex $argv 2] +set password [lindex $argv 3] +set dryrun [lindex $argv 4] +set model [lindex $argv 5] + +log_user 0 +if { "$dryrun" == "True" } { + set reset_msg "Test: No error" +} else { + set reset_msg "Reset: No error" +} + + +if { "$method" == "ssh" } { + send_user "spawn ssh -o StrictHostKeyChecking=no -o PasswordAuthentication=yes -o PubkeyAuthentication=no $user@$host\n" + set cont 0 + for { set retry 0 } { $retry < 2 } { incr retry 1 } { + + # NOTE: For unknown reason, some DRAC modules work only with + # stderr redirected to stdout, others only work without it. + if { $retry == 0 } { + spawn ssh -o StrictHostKeyChecking=no -o PasswordAuthentication=yes -o PubkeyAuthentication=no $user@$host 2>&1 + } else { + spawn ssh -o StrictHostKeyChecking=no -o PasswordAuthentication=yes -o PubkeyAuthentication=no $user@$host + } + + expect { + "Are you sure you want to continue connecting (yes/no)? " { send "yes\n" ; exp_continue } + "password: " { send "$password\n" ; exp_continue } + "Permission denied" { send_user "DRAC ssh: username/password: $expect_out(0,string)"; exit } + "Could not resolve hostname" { send_user "DRAC ssh: $expect_out(0,string)"; exit } + -re "Received disconnect.*" { send_user "DRAC ssh: $expect_out(0,string)"; exit} + -re "\\$|/.*>|\[$user\]#" { send "\r"; } + eof { set cont 1 } + } + if { $cont == 0 } { break; } + + } + + if { "$dryrun" == "True" } { + expect { + -re "\\$|/.*>" { send "racadm getsysinfo\r"; } + -re "\[$user\]#" { send "getsysinfo\r" } + } + } else { + expect { + -re "\\$|/.*>" { send "racadm serveraction powercycle\r"; } + -re "\[$user\]#" { send "serveraction powercycle\r" } + } + } + expect -re "\\$|/.*>|\[$user\]#" { send "exit\r"; } + + expect eof { send_user "$reset_msg\n" } + +} else { + if { [string match "*racadm*" $method] } { + if { "$dryrun" == "True" } { + set rac_cmd "getsysinfo" + } else { + set rac_cmd "serveraction powercycle" + } + send_user "/opt/dell/srvadmin/bin/$method -r $host -u $user -p '$password' $rac_cmd\n"; + set cont 0 + for { set retry 0 } { $retry < 2 } { incr retry 1 } { + + # NOTE: For unknown reason, placing the pasword + # on the cmdline causes racadm to fail. + # NOTE: For unknown reason, some DRAC modules work only with + # stderr redirected to stdout, others only work without it. + if { $retry == 0 } { + set x [ spawn /opt/dell/srvadmin/bin/$method -i -r $host $rac_cmd 2>&1 ] + } else { + set x [ spawn /opt/dell/srvadmin/bin/$method -i -r $host $rac_cmd ] + } + + expect { + -re "ERROR:.*" { send_user "DRAC: $expect_out(0,string)"; exit } + -re "UserName:|username:" { send "$user\r" ; exp_continue }; + -re "Password:|password:" { send "$password\r" }; + } + + expect { + -re "Authentication failed.*" { send_user "DRAC: $expect_out(0,string)"; exit } + -re "This.*not support remote RACADM" { send_user "DRAC: $expect_out(0,string)" ; exit } + -re "ERROR: The syntax of the command specified is not correct." { set cont 1 } + -re "RAC Information:" { sleep .1; } + #-re "ERROR:.*" { send_user "DRAC: $expect_out(0,string)"; exit } + } + if { $cont == 0 } { break; } + } + # If one of these are present, then the status information has been returned. + # otherwise an expect error will be thrown, signaling the caller. + expect { + -re "Power Status.*" { sleep .1; } + -re "RAC Firmware.*" { sleep .1; } + } + expect eof { send_user "$reset_msg\n" } + } +} diff --git a/pcucontrol/models/exp/HPiLO.exp b/pcucontrol/models/exp/HPiLO.exp new file mode 100755 index 0000000..ec064a5 --- /dev/null +++ b/pcucontrol/models/exp/HPiLO.exp @@ -0,0 +1,62 @@ +#!/usr/bin/expect + +set timeout 60 +set method [lindex $argv 0] +set host [lindex $argv 1] +set user [lindex $argv 2] +set password [lindex $argv 3] +set dryrun [lindex $argv 4] +set model [lindex $argv 5] + +log_user 0 + +if { "$method" == "https" } { + if { "$dryrun" == "True" } { + set file $path/iloxml/Get_Network.xml + } else { + #set file $path/iloxml/Reset_Server.xml + set file $path/iloxml/Get_Network.xml + } + spawn "$path/locfg.pl" -s $host -u $user -p '$password' -f $file +} elseif { "$method" == "ssh" } { + if { "$dryrun" == "True" } { + set reset_cmd "POWER\r" + set reset_msg "Test: No error" + } else { + #set reset_cmd "reset\r" + set reset_cmd "POWER\r" + set reset_msg "Reset: No error" + } + spawn ssh $user@$host +} elseif { "$method" == "proxy" } { + set url "http://www.cs.princeton.edu/~soltesz/pcuProxy.php" + set query "username=$user&password=$password&hostname=$host&model=$model&dryrun=$dryrun" + spawn curl -s "$url?$query" +} + +if { "$method" == "proxy" || "$method" == "https" } { + # Check for any one of the following patterns, then print it and stop. + expect { + "MESSAGE='Server being reset.'" { send_user "Reset: No error" } + "MESSAGE='Host power is already ON.'" { send_user "$expect_out(0,string)"} + "MESSAGE='Host power is already OFF.'" { send_user "$expect_out(0,string)"} + "MESSAGE='Login credentials rejected.'" { send_user "$expect_out(0,string)"} + "" { send_user "Test: No error"} + -re "ERROR:.*" { send_user "$expect_out(0,string)" } + eof { send_user "Reached EOF without status report: $expect_out(buffer)" } + } +} elseif { "$method" == "ssh" } { + + expect { + "Are you sure you want to continue connecting (yes/no)? " { send "yes\n" ; exp_continue } + "password: " { send "$password\n" ; exp_continue } + "Permission denied" { send_user "HPiLO username/password: $expect_out(0,string)"; exit } + "Could not resolve hostname" { send_user "HPiLO hostname: $expect_out(0,string)"; exit } + "hpiLO-> " { send "cd system1\r" } + } + + expect "hpiLO-> " { send $reset_cmd } + expect "hpiLO-> " { send "exit\r" } + expect eof { send_user $reset_msg } + +} diff --git a/pcucontrol/reboot.py b/pcucontrol/reboot.py index 122edbb..a6e1934 100755 --- a/pcucontrol/reboot.py +++ b/pcucontrol/reboot.py @@ -12,6 +12,7 @@ import urllib import array, struct import base64 from subprocess import PIPE, Popen +from distutils.sysconfig import get_python_lib; import subprocess import pcucontrol.transports.ssh.pxssh as pxssh import pcucontrol.transports.ssh.pexpect as pexpect @@ -51,430 +52,479 @@ class ExceptionUsername(Exception): pass # This is an object derived directly form the PLCAPI DB fields class PCU(object): - def __init__(self, plc_pcu_dict): - for field in ['username', 'password', 'site_id', - 'hostname', 'ip', - 'pcu_id', 'model', - 'node_ids', 'ports', ]: - if field in plc_pcu_dict: - if type(u"") == type(plc_pcu_dict[field]): - # NOTE: if is a unicode string, convert it. - self.__setattr__(field, str(plc_pcu_dict[field])) - else: - self.__setattr__(field, plc_pcu_dict[field]) - else: - raise Exception("No such field %s in PCU object" % field) + def __init__(self, plc_pcu_dict): + for field in ['username', 'password', 'site_id', + 'hostname', 'ip', + 'pcu_id', 'model', + 'node_ids', 'ports', ]: + if field in plc_pcu_dict: + if type(u"") == type(plc_pcu_dict[field]): + # NOTE: if is a unicode string, convert it. + self.__setattr__(field, str(plc_pcu_dict[field])) + else: + self.__setattr__(field, plc_pcu_dict[field]) + else: + raise Exception("No such field %s in PCU object" % field) # These are the convenience functions build around the PCU object. class PCUModel(PCU): - def __init__(self, plc_pcu_dict): - PCU.__init__(self, plc_pcu_dict) - self.host = self.pcu_name() - - def pcu_name(self): - if self.hostname is not None and self.hostname is not "": - return self.hostname - elif self.ip is not None and self.ip is not "": - return self.ip - else: - return None - - def nodeidToPort(self, node_id): - if node_id in self.node_ids: - for i in range(0, len(self.node_ids)): - if node_id == self.node_ids[i]: - return self.ports[i] - - raise Exception("No such Node ID: %d" % node_id) + def __init__(self, plc_pcu_dict): + PCU.__init__(self, plc_pcu_dict) + self.host = self.pcu_name() + + def pcu_name(self): + if self.hostname is not None and self.hostname is not "": + return self.hostname + elif self.ip is not None and self.ip is not "": + return self.ip + else: + return None + + def nodeidToPort(self, node_id): + if node_id in self.node_ids: + for i in range(0, len(self.node_ids)): + if node_id == self.node_ids[i]: + return self.ports[i] + + raise Exception("No such Node ID: %d" % node_id) + + def catcherror(self, function, node_port): + try: + return function(node_port) + except ExceptionNotFound, err: + return "Not found: " + str(err) + except ExceptionPassword, err: + return "Password exception: " + str(err) + except ExceptionTimeout, err: + return "Timeout exception: " + str(err) + except ExceptionUsername, err: + return "No username prompt: " + str(err) + except ExceptionSequence, err: + return "Sequence error: " + str(err) + except ExceptionPrompt, err: + return "Prompt exception: " + str(err) + except ExceptionNoTransport, err: + return "No Transport: " + str(err) + except ExceptionPort, err: + return "No ports exception: " + str(err) + except socket.error, err: + return "socket error: timeout: " + str(err) + except urllib2.HTTPError, err: + return "HTTPError: " + str(err) + except urllib2.URLError, err: + return "URLError: " + str(err) + except EOFError, err: + self.transport.close() + import traceback + traceback.print_exc() + return "EOF connection reset" + str(err) + + except Exception, err: + #from monitor.common import email_exception + #email_exception(self.host) + raise Exception(err) # This class captures the observed pcu records from FindBadPCUs.py class PCURecord: - def __init__(self, pcu_record_dict): - for field in ['port_status', - 'dns_status', - 'entry_complete', ]: - if field in pcu_record_dict: - if field == "reboot": - self.__setattr__("reboot_str", pcu_record_dict[field]) - else: - self.__setattr__(field, pcu_record_dict[field]) - #else: - # raise Exception("No such field %s in pcu record dict" % field) + def __init__(self, pcu_record_dict): + for field in ['port_status', + 'dns_status', + 'entry_complete', ]: + if field in pcu_record_dict: + if field == "reboot": + self.__setattr__("reboot_str", pcu_record_dict[field]) + else: + self.__setattr__(field, pcu_record_dict[field]) + #else: + # raise Exception("No such field %s in pcu record dict" % field) class Transport: - TELNET = "telnet" - SSH = "ssh" - HTTP = "http" - HTTPS = "https" - IPAL = "ipal" - IPMI = "ipmi" - DRAC = "drac" - AMT = "amt" - - TELNET_TIMEOUT = 120 - - porttypemap = { - 5869 : DRAC, - 22 : SSH, - 23 : TELNET, - 443 : HTTPS, - 80 : HTTP, - 9100 : IPAL, - 623 : IPMI, - 16992 : AMT, - } - - def __init__(self, type, verbose): - self.type = type - self.verbose = verbose - self.transport = None - - def open(self, host, username=None, password=None, prompt="User Name"): - transport = None - - if self.type == self.TELNET: - transport = telnetlib.Telnet(host, timeout=self.TELNET_TIMEOUT) - transport.set_debuglevel(self.verbose) - if username is not None: - self.transport = transport - self.ifThenSend(prompt, username, ExceptionUsername) - - elif self.type == self.SSH: - if username is not None: - transport = pyssh.Ssh(username, host) - transport.set_debuglevel(self.verbose) - transport.open() - # TODO: have an ssh set_debuglevel() also... - else: - raise Exception("Username cannot be None for ssh transport.") - elif self.type == self.HTTP: - # NOTE: this does not work for all web-based services... - self.url = "http://%s:%d/" % (host,80) - uri = "%s:%d" % (host,80) - - # create authinfo - authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm() - authinfo.add_password (None, uri, username, password) - authhandler = urllib2.HTTPBasicAuthHandler( authinfo ) - - transport = urllib2.build_opener(authhandler) - else: - raise Exception("Unknown transport type: %s" % self.type) - - self.transport = transport - return True - - def close(self): - if self.type == self.TELNET: - self.transport.close() - elif self.type == self.SSH: - self.transport.close() - elif self.type == self.HTTP: - pass - else: - raise Exception("Unknown transport type %s" % self.type) - self.transport = None - - def write(self, msg): - return self.send(msg) - - def send(self, msg): - if self.transport == None: - raise ExceptionNoTransport("transport object is type None") - - return self.transport.write(msg) - - def sendPassword(self, password, prompt=None): - if self.type == self.TELNET: - if prompt == None: - self.ifThenSend("Password", password, ExceptionPassword) - else: - self.ifThenSend(prompt, password, ExceptionPassword) - elif self.type == self.SSH: - self.ifThenSend("password:", password, ExceptionPassword) - elif self.type == self.HTTP: - pass - else: - raise Exception("Unknown transport type: %s" % self.type) - - def sendHTTP(self, resource, data): - if self.verbose: - print "POSTing '%s' to %s" % (data,self.url + resource) - - try: - f = self.transport.open(self.url + resource ,data) - r = f.read() - if self.verbose: - print r - - except urllib2.URLError,err: - print 'Could not open http connection', err - return "http transport error" - - return 0 - - def ifThenSend(self, expected, buffer, ErrorClass=ExceptionPrompt): - - if self.transport != None: - output = self.transport.read_until(expected, self.TELNET_TIMEOUT) - if output.find(expected) == -1: - print "OUTPUT: --%s--" % output - raise ErrorClass, "'%s' not found: Got: %s" % (expected, output) - else: - self.transport.write(buffer + "\r\n") - else: - raise ExceptionNoTransport("transport object is type None") - - def ifElse(self, expected, ErrorClass): - try: - self.transport.read_until(expected, self.TELNET_TIMEOUT) - except: - raise ErrorClass("Could not find '%s' within timeout" % expected) + TELNET = "telnet" + SSH = "ssh" + HTTP = "http" + HTTPS = "https" + IPAL = "ipal" + IPMI = "ipmi" + DRAC = "drac" + AMT = "amt" + + TELNET_TIMEOUT = 120 + + porttypemap = { + 5869 : DRAC, + 22 : SSH, + 23 : TELNET, + 443 : HTTPS, + 80 : HTTP, + 9100 : IPAL, + 623 : IPMI, + 16992 : AMT, + } + + def __init__(self, type, verbose): + self.type = type + self.verbose = verbose + self.transport = None + + def open(self, host, username=None, password=None, prompt="User Name"): + transport = None + + if self.type == self.TELNET: + transport = telnetlib.Telnet(host, timeout=self.TELNET_TIMEOUT) + transport.set_debuglevel(self.verbose) + if username is not None: + self.transport = transport + self.ifThenSend(prompt, username, ExceptionUsername) + + elif self.type == self.SSH: + if username is not None: + transport = pyssh.Ssh(username, host) + transport.set_debuglevel(self.verbose) + transport.open() + # TODO: have an ssh set_debuglevel() also... + else: + raise Exception("Username cannot be None for ssh transport.") + elif self.type == self.HTTP: + # NOTE: this does not work for all web-based services... + self.url = "http://%s:%d/" % (host,80) + uri = "%s:%d" % (host,80) + + # create authinfo + authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm() + authinfo.add_password (None, uri, username, password) + authhandler = urllib2.HTTPBasicAuthHandler( authinfo ) + + transport = urllib2.build_opener(authhandler) + else: + raise Exception("Unknown transport type: %s" % self.type) + + self.transport = transport + return True + + def close(self): + if self.type == self.TELNET: + self.transport.close() + elif self.type == self.SSH: + self.transport.close() + elif self.type == self.HTTP: + pass + else: + raise Exception("Unknown transport type %s" % self.type) + self.transport = None + + def write(self, msg): + return self.send(msg) + + def send(self, msg): + if self.transport == None: + raise ExceptionNoTransport("transport object is type None") + + return self.transport.write(msg) + + def sendPassword(self, password, prompt=None): + if self.type == self.TELNET: + if prompt == None: + self.ifThenSend("Password", password, ExceptionPassword) + else: + self.ifThenSend(prompt, password, ExceptionPassword) + elif self.type == self.SSH: + self.ifThenSend("password:", password, ExceptionPassword) + elif self.type == self.HTTP: + pass + else: + raise Exception("Unknown transport type: %s" % self.type) + + def sendHTTP(self, resource, data): + if self.verbose: + print "POSTing '%s' to %s" % (data,self.url + resource) + + try: + f = self.transport.open(self.url + resource ,data) + r = f.read() + if self.verbose: + print r + + except urllib2.URLError,err: + print 'Could not open http connection', err + return "http transport error" + + return 0 + + def ifThenSend(self, expected, buffer, ErrorClass=ExceptionPrompt): + + if self.transport != None: + output = self.transport.read_until(expected, self.TELNET_TIMEOUT) + if output.find(expected) == -1: + print "OUTPUT: --%s--" % output + raise ErrorClass, "'%s' not found: Got: %s" % (expected, output) + else: + self.transport.write(buffer + "\r\n") + else: + raise ExceptionNoTransport("transport object is type None") + + def ifElse(self, expected, ErrorClass): + try: + self.transport.read_until(expected, self.TELNET_TIMEOUT) + except: + raise ErrorClass("Could not find '%s' within timeout" % expected) class PCUControl(PCUModel,PCURecord): - """ - There are three cases: - 1) the pcu_record passed below includes port_status from an - external probe. - 2) the external probe failed, and the values are empty - 3) this call is made independent of port_status. - - In the first case, the first open port is used. - In the third case, the ports are tried in sequence. - - In this way, the port_status value serves only as an optimization, - because closed ports are avoided. The supported_ports value should - order ports by their preferred usage. - """ - - supported_ports = [] - - def __init__(self, plc_pcu_record, verbose, ignored=None): - PCUModel.__init__(self, plc_pcu_record) - PCURecord.__init__(self, plc_pcu_record) - - def reboot(self, node_port, dryrun): - - port_list = [] - # There are two sources of potential ports. Those that are open and - # those that are part of the PCU's supported_ports. - # I think we should start with supported_ports and then filter that - # by the open ports. - - port_list = self.supported_ports - - if hasattr(self, 'port_status') and self.port_status: - # get out the open ports - port_list = filter(lambda x: self.port_status[x] == "open" , self.port_status.keys()) - port_list = [ int(x) for x in port_list ] - # take only the open ports that are supported_ports - port_list = filter(lambda x: x in self.supported_ports, port_list) - if port_list == []: - raise ExceptionPort("No Open Port: No transport from open ports") - - print port_list - - ret = "No implementation for open ports on selected PCU model" - for port in port_list: - if port not in Transport.porttypemap: - continue - - type = Transport.porttypemap[port] - self.transport = Transport(type, verbose) - - print "checking for run_%s" % type - if hasattr(self, "run_%s" % type): - print "found run_%s" % type - fxn = getattr(self, "run_%s" % type) - ret = self.catcherror(fxn, node_port, dryrun) - if ret == 0: # NOTE: success!, so stop - break - else: - continue - - return ret - - def run(self, node_port, dryrun): - """ This function is to be defined by the specific PCU instance. """ - raise Exception("This function is not implemented") - pass - - #def reboot(self, node_port, dryrun): - - def catcherror(self, function, node_port, dryrun): - try: - return function(node_port, dryrun) - except ExceptionNotFound, err: - return "error: " + str(err) - except ExceptionPassword, err: - return "Password exception: " + str(err) - except ExceptionTimeout, err: - return "Timeout exception: " + str(err) - except ExceptionUsername, err: - return "No username prompt: " + str(err) - except ExceptionSequence, err: - return "Sequence error: " + str(err) - except ExceptionPrompt, err: - return "Prompt exception: " + str(err) - except ExceptionNoTransport, err: - return "No Transport: " + str(err) - except ExceptionPort, err: - return "No ports exception: " + str(err) - except socket.error, err: - return "socket error: timeout: " + str(err) - except urllib2.HTTPError, err: - return "HTTPError: " + str(err) - except urllib2.URLError, err: - return "URLError: " + str(err) - except EOFError, err: - self.transport.close() - import traceback - traceback.print_exc() - return "EOF connection reset" + str(err) - except Exception, err: - #from monitor.common import email_exception - #email_exception(self.host) - raise Exception(err) + """ + There are three cases: + 1) the pcu_record passed below includes port_status from an + external probe. + 2) the external probe failed, and the values are empty + 3) this call is made independent of port_status. + + In the first case, the first open port is used. + In the third case, the ports are tried in sequence. + + In this way, the port_status value serves only as an optimization, + because closed ports are avoided. The supported_ports value should + order ports by their preferred usage. + """ + + supported_ports = [] + + def __init__(self, plc_pcu_record, verbose, ignored=None): + PCUModel.__init__(self, plc_pcu_record) + PCURecord.__init__(self, plc_pcu_record) + + def reboot(self, node_port, dryrun): + + port_list = [] + # There are two sources of potential ports. Those that are open and + # those that are part of the PCU's supported_ports. + # I think we should start with supported_ports and then filter that + # by the open ports. + + port_list = self.supported_ports + + if hasattr(self, 'port_status') and self.port_status: + # get out the open ports + port_list = filter(lambda x: self.port_status[x] == "open" , self.port_status.keys()) + port_list = [ int(x) for x in port_list ] + # take only the open ports that are supported_ports + port_list = filter(lambda x: x in self.supported_ports, port_list) + if port_list == []: + raise ExceptionPort("No Open Port: No transport from open ports") + + print port_list + + ret = "No implementation for open ports on selected PCU model" + for port in port_list: + if port not in Transport.porttypemap: + continue + + type = Transport.porttypemap[port] + self.transport = Transport(type, verbose) + + print "checking for run_%s" % type + if hasattr(self, "run_%s" % type): + print "found run_%s" % type + fxn = getattr(self, "run_%s" % type) + ret = self.catcherror(fxn, node_port, dryrun) + if ret == 0: # NOTE: success!, so stop + break + else: + continue + + return ret + + def run(self, node_port, dryrun): + """ This function is to be defined by the specific PCU instance. """ + raise Exception("This function is not implemented") + + +class BasicPCUControl(PCUModel): + + """ + This is the basic pcucontrol super class on which the classes in + pcucontrol/models/* are based. + + Each model implements a specific 'run' function that can use any + mechanism available to reboot the given PCU using, hostname, password, + username, and dryrun. + + This is best suited for nodes with built-in PCUs, iLO, AMT, DRAC, etc. + """ + + supported_ports = [] + install_path = get_python_lib(1) + "/pcucontrol/" + + def __init__(self, plc_pcu_record, ignored=None): + PCUModel.__init__(self, plc_pcu_record) + + def run_expect_script(self, scriptname, **args): + locfg = command.CMD() + + cmd_str = get_python_lib(1) + "/pcucontrol/models/exp/" + cmd = cmd_str + "%s %s %s '%s' %s %s " % ( + scriptname, self.host, self.username, + self.password, args['dryrun'], args['model']) + cmd_out, cmd_err = locfg.run_noexcept(cmd) + return cmd_out.strip() + cmd_err.strip() + + def reboot(self, node_port, dryrun): + if dryrun: + looking_for_fxn = 'pcu_test' + else: + looking_for_fxn = 'pcu_run' + + # verify that the function exists. + if not hasattr(self, looking_for_fxn): + raise Exception("This model (%s) does not implement %s" % (self.model, looking_for_fxn)) + + print "found function %s in model %s" % (looking_for_fxn, self.model) + reboot_fxn = getattr(self, looking_for_fxn) + ret = self.catcherror(reboot_fxn, node_port) + + return ret + + def run(self, node_port, dryrun): + """ This function is to be defined by the specific PCU instance. """ + raise Exception("This function is not implemented") + from pcucontrol.util import command from pcucontrol.models import * def pcu_name(pcu): - if pcu['hostname'] is not None and pcu['hostname'] is not "": - return pcu['hostname'] - elif pcu['ip'] is not None and pcu['ip'] is not "": - return pcu['ip'] - else: - return None + if pcu['hostname'] is not None and pcu['hostname'] is not "": + return pcu['hostname'] + elif pcu['ip'] is not None and pcu['ip'] is not "": + return pcu['ip'] + else: + return None class Unknown(PCUControl): - supported_ports = [22,23,80,443,5869,9100,16992] + supported_ports = [22,23,80,443,5869,9100,16992] def model_to_object(modelname): - if modelname is None: - return ManualPCU - if "AMT" in modelname: - return IntelAMT - elif "BayTech" in modelname: - return BayTech - elif "HPiLO" in modelname: - return HPiLO - elif "IPAL" in modelname: - return IPAL - elif "APC" in modelname: - return APCControl - elif "DRAC" in modelname: - return DRAC - elif "WTI" in modelname: - return WTIIPS4 - elif "ePowerSwitch" in modelname: - return ePowerSwitchNew - elif "IPMI" in modelname: - return OpenIPMI - elif "BlackBoxPSMaverick" in modelname: - return BlackBoxPSMaverick - elif "PM211MIP" in modelname: - return PM211MIP - elif "ManualPCU" in modelname: - return ManualPCU - else: - print "UNKNOWN model %s"%modelname - return Unknown + if modelname is None: + return ManualPCU + if "AMT" in modelname: + return IntelAMT + elif "BayTech" in modelname: + return BayTech + elif "HPiLO" in modelname: + return HPiLO + elif "IPAL" in modelname: + return IPAL + elif "APC" in modelname: + return APCControl + elif "DRAC" in modelname: + return DRAC + elif "WTI" in modelname: + return WTIIPS4 + elif "ePowerSwitch" in modelname: + return ePowerSwitchNew + elif "IPMI" in modelname: + return OpenIPMI + elif "BlackBoxPSMaverick" in modelname: + return BlackBoxPSMaverick + elif "PM211MIP" in modelname: + return PM211MIP + elif "ManualPCU" in modelname: + return ManualPCU + else: + print "UNKNOWN model %s"%modelname + return Unknown def reboot_api(node, pcu, testrun=False): - rb_ret = "" - - try: - modelname = pcu['model'] - if modelname: - # get object instance - instance = eval('%s(pcu, verbose)' % modelname) - # get pcu port - i = pcu['node_ids'].index(node['node_id']) - p = pcu['ports'][i] - # reboot - rb_ret = instance.reboot(p, testrun) - else: - rb_ret = "No modelname in PCU record." - # TODO: how to handle the weird, georgetown pcus, the drac faults, and ilo faults - except Exception, err: - rb_ret = "Exception Model(%s): " % modelname - rb_ret += str(err) - - return rb_ret + rb_ret = "" + + try: + modelname = pcu['model'] + if modelname: + # get object instance + instance = eval('%s(pcu, verbose)' % modelname) + # get pcu port + i = pcu['node_ids'].index(node['node_id']) + p = pcu['ports'][i] + # reboot + rb_ret = instance.reboot(p, testrun) + else: + rb_ret = "No modelname in PCU record." + # TODO: how to handle the weird, georgetown pcus, the drac faults, and ilo faults + except Exception, err: + rb_ret = "Exception Model(%s): " % modelname + rb_ret += str(err) + + return rb_ret def convert_oldmodelname_to_newmodelname(oldmodelname, pcu_id): - newmodelname = None - update = { 'AP79xx' : 'APCControl13p13', - 'Masterswitch' : 'APCControl13p13', - 'DS4-RPC' : 'BayTech', - 'IP-41x_IP-81x' : 'IPAL', - 'DRAC3' : 'DRAC', - 'DRAC4' : 'DRAC', - 'ePowerSwitch' : 'ePowerSwitchOld', - 'ilo2' : 'HPiLO', - 'ilo1' : 'HPiLO', - 'PM211-MIP' : 'PM211MIP', - 'AMT2.5' : 'IntelAMT', - 'AMT3.0' : 'IntelAMT', - 'WTI_IPS-4' : 'WTIIPS4', - 'unknown' : 'ManualPCU', - 'DRAC5' : 'DRAC', - 'ipmi' : 'OpenIPMI', - 'bbsemaverick' : 'BlackBoxPSMaverick', - 'manualadmin' : 'ManualPCU', - } - - if oldmodelname in update: - newmodelname = update[oldmodelname] - else: - newmodelname = oldmodelname - - if pcu_id in [1102,1163,1055,1111,1231,1113,1127,1128,1148]: - newmodelname = 'APCControl12p3' - elif pcu_id in [1110,86]: - newmodelname = 'APCControl1p4' - elif pcu_id in [1221,1225,1220,1192]: - newmodelname = 'APCControl121p3' - elif pcu_id in [1173,1240,47,1363,1405,1401,1372,1371]: - newmodelname = 'APCControl121p1' - elif pcu_id in [1056,1237,1052,1209,1002,1008,1013,1022]: - newmodelname = 'BayTechCtrlC' - elif pcu_id in [93]: - newmodelname = 'BayTechRPC3NC' - elif pcu_id in [1057]: - newmodelname = 'BayTechCtrlCUnibe' - elif pcu_id in [1012]: - newmodelname = 'BayTechRPC16' - elif pcu_id in [1089, 1071, 1046, 1035, 1118]: - newmodelname = 'ePowerSwitchNew' - - return newmodelname + newmodelname = None + update = { 'AP79xx' : 'APCControl13p13', + 'Masterswitch' : 'APCControl13p13', + 'DS4-RPC' : 'BayTech', + 'IP-41x_IP-81x' : 'IPAL', + 'DRAC3' : 'DRAC', + 'DRAC4' : 'DRAC', + 'ePowerSwitch' : 'ePowerSwitchOld', + 'ilo2' : 'HPiLO', + 'ilo1' : 'HPiLO', + 'PM211-MIP' : 'PM211MIP', + 'AMT2.5' : 'IntelAMT', + 'AMT3.0' : 'IntelAMT', + 'WTI_IPS-4' : 'WTIIPS4', + 'unknown' : 'ManualPCU', + 'DRAC5' : 'DRAC', + 'ipmi' : 'OpenIPMI', + 'bbsemaverick' : 'BlackBoxPSMaverick', + 'manualadmin' : 'ManualPCU', + } + + if oldmodelname in update: + newmodelname = update[oldmodelname] + else: + newmodelname = oldmodelname + + if pcu_id in [1102,1163,1055,1111,1231,1113,1127,1128,1148]: + newmodelname = 'APCControl12p3' + elif pcu_id in [1110,86]: + newmodelname = 'APCControl1p4' + elif pcu_id in [1221,1225,1220,1192]: + newmodelname = 'APCControl121p3' + elif pcu_id in [1173,1240,47,1363,1405,1401,1372,1371]: + newmodelname = 'APCControl121p1' + elif pcu_id in [1056,1237,1052,1209,1002,1008,1013,1022]: + newmodelname = 'BayTechCtrlC' + elif pcu_id in [93]: + newmodelname = 'BayTechRPC3NC' + elif pcu_id in [1057]: + newmodelname = 'BayTechCtrlCUnibe' + elif pcu_id in [1012]: + newmodelname = 'BayTechRPC16' + elif pcu_id in [1089, 1071, 1046, 1035, 1118]: + newmodelname = 'ePowerSwitchNew' + + return newmodelname def reboot_test_new(nodename, values, verbose, dryrun): - rb_ret = "" - if 'plc_pcu_stats' in values: - values.update(values['plc_pcu_stats']) - - try: - #modelname = convert_oldmodelname_to_newmodelname(values['model'], values['pcu_id']) - modelname = values['model'] - if modelname: - object = eval('%s(values, verbose)' % modelname) - rb_ret = object.reboot(values[nodename], dryrun) - else: - rb_ret = "Not_Run" - # TODO: how to handle the weird, georgetown pcus, the drac faults, and ilo faults - except ExceptionPort, err: - rb_ret = str(err) - except NameError, err: - rb_ret = str(err) - - return rb_ret + rb_ret = "" + if 'plc_pcu_stats' in values: + values.update(values['plc_pcu_stats']) + + try: + #modelname = convert_oldmodelname_to_newmodelname(values['model'], values['pcu_id']) + modelname = values['model'] + if modelname: + object = eval('%s(values, verbose)' % modelname) + rb_ret = object.reboot(values[nodename], dryrun) + else: + rb_ret = "Not_Run" + # TODO: how to handle the weird, georgetown pcus, the drac faults, and ilo faults + except ExceptionPort, err: + rb_ret = str(err) + except NameError, err: + rb_ret = str(err) + + return rb_ret def main(): - print "this does not work." + print "this does not work." if __name__ == '__main__': - main() + main() diff --git a/pcucontrol/support/pcuProxy.php b/pcucontrol/support/pcuProxy.php new file mode 100644 index 0000000..3cfcc47 --- /dev/null +++ b/pcucontrol/support/pcuProxy.php @@ -0,0 +1,62 @@ +&1"; + logit("cmd: " . $cmd); + return system($cmd); + + $f = fopen("output.log", 'a'); + fwrite($f, print_r($_REQUEST, TRUE)); + fclose($f); + +} + + if ( isset($_REQUEST['hostname']) && + isset($_REQUEST['username']) && + isset($_REQUEST['password']) && + isset($_REQUEST['model']) ) + { + $host=$_REQUEST['hostname']; + $username=$_REQUEST['username']; + $password=$_REQUEST['password']; + $model=$_REQUEST['model']; + if ( isset($_REQUEST['dryrun']) ) + { + $dryrun = $_REQUEST['dryrun']; + settype($dryrun, "boolean"); + } else { + $dryrun = TRUE; + } + + logit(print_r($_REQUEST, TRUE)); + + if (strcmp($model,"HPiLOProxy") == 0) { + if ( $dryrun ) + { + run_hp_script("hpilo/iloxml/Get_Network.xml", $host, $username, $password); + } else { + run_hp_script("hpilo/iloxml/PowerOn_Server.xml", $host, $username, $password); + echo run_hp_script("hpilo/iloxml/Reset_Server.xml", $host, $username, $password); + } + } elseif ( strcmp($model,"OpenIPMIProxy") == 0 ) + { + # cmd = "ipmitool -I lanplus -H $host -U $username -P '$password' user list" + # cmd = "ipmitool -I lanplus -H $host -U $username -P '$password' power cycle" + echo system("which ipmitool 2>&1"); + } else + { + echo "what do you want me to do with this?"; + } + + } else { + echo "Please provide hostname, username, and password"; + } + +?> diff --git a/pcucontrol/util/command.py b/pcucontrol/util/command.py index 42e00a9..74b5fae 100644 --- a/pcucontrol/util/command.py +++ b/pcucontrol/util/command.py @@ -91,7 +91,11 @@ class CMD: def run(self, cmd, timeout=COMMAND_TIMEOUT*2): - s = Sopen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) + s = Sopen(cmd, shell=True, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True) self.s = s (f_in, f_out, f_err) = (s.stdin, s.stdout, s.stderr) lout, lin, lerr = select([f_out], [], [f_err], timeout) -- 2.43.0