From 311f1adfd6750378d80e4c8c7eb2a8a251d4ce7e Mon Sep 17 00:00:00 2001
From: Stephen Soltesz <soltesz@cs.princeton.edu>
Date: Tue, 6 Jan 2009 19:22:26 +0000
Subject: [PATCH] PCU Class objects broken out into individual files.

---
 pcucontrol/models/APCControl.py         | 177 +++++++++++++++++++
 pcucontrol/models/BayTech.py            | 216 ++++++++++++++++++++++++
 pcucontrol/models/BlackBoxPSMaverick.py |  65 +++++++
 pcucontrol/models/CustomPCU.py          |  26 +++
 pcucontrol/models/DRAC.py               | 166 ++++++++++++++++++
 pcucontrol/models/HPiLO.py              |  59 +++++++
 pcucontrol/models/IPAL.py               | 142 ++++++++++++++++
 pcucontrol/models/IntelAMT.py           |  20 +++
 pcucontrol/models/ManualPCU.py          |  13 ++
 pcucontrol/models/OpenIPMI.py           |  25 +++
 pcucontrol/models/PM211MIP.py           |   5 +
 pcucontrol/models/WTIIPS4.py            |  18 ++
 pcucontrol/models/X10Toggle.py          |  49 ++++++
 pcucontrol/models/__init__.py           |  13 ++
 pcucontrol/models/ePowerSwitch.py       | 120 +++++++++++++
 15 files changed, 1114 insertions(+)
 create mode 100644 pcucontrol/models/APCControl.py
 create mode 100644 pcucontrol/models/BayTech.py
 create mode 100644 pcucontrol/models/BlackBoxPSMaverick.py
 create mode 100644 pcucontrol/models/CustomPCU.py
 create mode 100644 pcucontrol/models/DRAC.py
 create mode 100644 pcucontrol/models/HPiLO.py
 create mode 100644 pcucontrol/models/IPAL.py
 create mode 100644 pcucontrol/models/IntelAMT.py
 create mode 100644 pcucontrol/models/ManualPCU.py
 create mode 100644 pcucontrol/models/OpenIPMI.py
 create mode 100644 pcucontrol/models/PM211MIP.py
 create mode 100644 pcucontrol/models/WTIIPS4.py
 create mode 100644 pcucontrol/models/X10Toggle.py
 create mode 100644 pcucontrol/models/__init__.py
 create mode 100644 pcucontrol/models/ePowerSwitch.py

diff --git a/pcucontrol/models/APCControl.py b/pcucontrol/models/APCControl.py
new file mode 100644
index 0000000..62f5f6f
--- /dev/null
+++ b/pcucontrol/models/APCControl.py
@@ -0,0 +1,177 @@
+from pcucontrol.reboot import *
+
+class APCControl(PCUControl):
+	supported_ports = [22,23,80,443]
+	reboot_sequence = []
+
+	def run(self, node_port, dryrun):
+		print "RUNNING!!!!!!!!!!!!"
+		if self.type == Transport.HTTPS or self.type == Transport.HTTP:
+			print "APC via http...."
+			return self.run_http_or_https(node_port, dryrun)
+		else:
+			print "APC via telnet/ssh...."
+			return self.run_telnet_or_ssh(node_port, dryrun)
+	
+	def run_ssh(self, node_port, dryrun):
+		return self.run_telnet_or_ssh(node_port, dryrun)
+	def run_telnet(self, node_port, dryrun):
+		return self.run_telnet_or_ssh(node_port, dryrun)
+
+	def run_telnet_or_ssh(self, node_port, dryrun):
+		self.transport.open(self.host, self.username)
+		self.transport.sendPassword(self.password)
+
+		first = True
+		for val in self.reboot_sequence:
+			if first:
+				self.transport.ifThenSend("\r\n> ", val, ExceptionPassword)
+				first = False
+			else:
+				self.transport.ifThenSend("\r\n> ", val)
+
+		if not dryrun:
+			self.transport.ifThenSend("Enter 'YES' to continue or <ENTER> to cancel", 
+							"YES\r\n",
+							ExceptionSequence)
+		else:
+			self.transport.ifThenSend("Enter 'YES' to continue or <ENTER> to cancel", 
+							"", ExceptionSequence)
+		self.transport.ifThenSend("Press <ENTER> to continue...", "", ExceptionSequence)
+
+		self.transport.close()
+		return 0
+
+	def run_http(self, node_port, dryrun):
+		return self.run_http_or_https(node_port, dryrun)
+	def run_https(self, node_port, dryrun):
+		return self.run_http_or_https(node_port, dryrun)
+
+	def run_http_or_https(self, node_port, dryrun):
+		if not dryrun:
+			# send reboot signal.
+			# TODO: send a ManualPCU() reboot request for this PCU.
+			# NOTE: this model defies automation because, the port numbering
+			# 	and the form numbers are not consistent across models.  There is
+			# 	not direct mapping from port# to form#.
+			return "Manual Reboot Required"
+
+		else:
+			# TODO: also send message for https, since that doesn't work this way...
+			if self.type == Transport.HTTPS:
+				cmd = self.get_https_cmd()
+			elif self.type == Transport.HTTP:
+				cmd = self.get_http_cmd()
+			else:
+				raise ExceptionNoTransport("Unsupported transport for http command")
+
+		cmd = cmd % ( self.username, self.password, self.host)
+		print "CMD: %s" % cmd
+
+		p = os.popen(cmd)
+		result = p.read()
+		if len(result.split('\n')) > 2:
+			self.logout()
+			return 0
+		else:
+			# NOTE: an error has occurred, so no need to log out.
+			print "RESULT: ", result
+			return result
+
+	def get_https_cmd(self):
+		version = self.get_version()
+		print "VERSION: %s" % version
+		if "AP96" in version:
+			cmd = "curl -s --insecure --user '%s:%s' https://%s/outlets.htm " + \
+				  " | grep -E '^[^<]+' " + \
+				  " | grep -v 'Protected Object' "
+		else:
+			# NOTE: no other case known right now...
+			cmd = "curl -s --insecure --user '%s:%s' https://%s/outlets.htm " + \
+				  " | grep -E '^[^<]+' " + \
+				  " | grep -v 'Protected Object' "
+			
+		return cmd
+	
+	def get_http_cmd(self):
+		version = self.get_version()
+		print "VERSION: %s" % version
+		if "AP7900" in version:
+			cmd = "curl -s --anyauth --user '%s:%s' http://%s/rPDUout.htm | grep -E '^[^<]+'" 
+		elif "AP7920" in version:
+			cmd = "curl -s --anyauth --user '%s:%s' http://%s/ms3out.htm | grep -E '^[^<]+' " 
+		else:
+			# default case...
+			print "USING DEFAULT"
+			cmd = "curl -s --anyauth --user '%s:%s' http://%s/ms3out.htm | grep -E '^[^<]+' " 
+			
+		return cmd
+
+	def get_version(self):
+		# NOTE: this command returns and formats all data.
+		#cmd = """curl -s --anyauth --user '%s:%s' http://%s/about.htm """ +
+		#      """ | sed -e "s/<[^>]*>//g" -e "s/&nbsp;//g" -e "/^$/d" """ +
+		#	  """ | awk '{line=$0 ; if ( ! /:/ && length(pline) > 0 ) \
+		#	  		     { print pline, line } else { pline=line} }' """ + 
+		#	  """ | grep Model """
+
+		# NOTE: we may need to return software version, no model version to
+		# 		know which file to request on the server.
+
+		if self.type == Transport.HTTP:
+			cmd = """curl -s --anyauth --user '%s:%s' http://%s/about.htm """ + \
+				  """ | sed -e "s/<[^>]*>//g" -e "s/&nbsp;//g" -e "/^$/d" """ + \
+				  """ | grep -E "AP[[:digit:]]+" """
+				  #""" | grep -E "v[[:digit:]].*" """
+		elif self.type == Transport.HTTPS:
+			cmd = """curl -s --insecure --user '%s:%s' https://%s/about.htm """ + \
+				  """ | sed -e "s/<[^>]*>//g" -e "s/&nbsp;//g" -e "/^$/d" """ + \
+				  """ | grep -E "AP[[:digit:]]+" """
+				  #""" | grep -E "v[[:digit:]].*" """
+		else:
+			raise ExceptionNoTransport("Unsupported transport to get version")
+
+		cmd = cmd % ( self.username, self.password, self.host)
+		p = os.popen(cmd)
+		result = p.read()
+		return result.strip()
+
+	def logout(self):
+		# NOTE: log out again, to allow other uses to access the machine.
+		if self.type == Transport.HTTP:
+			cmd = """curl -s --anyauth --user '%s:%s' http://%s/logout.htm """ + \
+				  """ | grep -E '^[^<]+' """
+		elif self.type == Transport.HTTPS:
+			cmd = """curl -s --insecure --user '%s:%s' http://%s/logout.htm """ + \
+				  """ | grep -E '^[^<]+' """
+		else:
+			raise ExceptionNoTransport("Unsupported transport to logout")
+
+		cmd = cmd % ( self.username, self.password, self.host)
+		p = os.popen(cmd)
+		print p.read()
+
+class APCControl12p3(APCControl):
+	def run_telnet_or_ssh(self, node_port, dryrun):
+		self.reboot_sequence = ["1", "2", str(node_port), "3"]
+		return super(APCControl12p3, self).run_telnet_or_ssh(node_port, dryrun)
+
+class APCControl1p4(APCControl):
+	def run_telnet_or_ssh(self, node_port, dryrun):
+		self.reboot_sequence = ["1", str(node_port), "4"]
+		return super(APCControl1p4, self).run_telnet_or_ssh(node_port, dryrun)
+
+class APCControl121p3(APCControl):
+	def run_telnet_or_ssh(self, node_port, dryrun):
+		self.reboot_sequence = ["1", "2", "1", str(node_port), "3"]
+		return super(APCControl121p3, self).run_telnet_or_ssh(node_port, dryrun)
+
+class APCControl121p1(APCControl):
+	def run_telnet_or_ssh(self, node_port, dryrun):
+		self.reboot_sequence = ["1", "2", "1", str(node_port), "1", "3"]
+		return super(APCControl121p1, self).run_telnet_or_ssh(node_port, dryrun)
+
+class APCControl13p13(APCControl):
+	def run_telnet_or_ssh(self, node_port, dryrun):
+		self.reboot_sequence = ["1", "3", str(node_port), "1", "3"]
+		return super(APCControl13p13, self).run_telnet_or_ssh(node_port, dryrun)
diff --git a/pcucontrol/models/BayTech.py b/pcucontrol/models/BayTech.py
new file mode 100644
index 0000000..83de3a5
--- /dev/null
+++ b/pcucontrol/models/BayTech.py
@@ -0,0 +1,216 @@
+from pcucontrol.reboot import *
+
+class BayTechRPC3NC(PCUControl):
+	def run_telnet(self, node_port, dryrun):
+		return self.run_ssh(node_port, dryrun)
+
+	def run_ssh(self, node_port, dryrun):
+		self.transport.open(self.host, self.username, None, "Enter user name:")
+		self.transport.sendPassword(self.password, "Enter Password:")
+
+		#self.transport.ifThenSend("RPC-16>", "Status")
+		self.transport.ifThenSend("RPC3-NC>", "Reboot %d" % node_port)
+
+		# Reboot Outlet  N	  (Y/N)?
+		if dryrun:
+			self.transport.ifThenSend("(Y/N)?", "N")
+		else:
+			self.transport.ifThenSend("(Y/N)?", "Y")
+		self.transport.ifThenSend("RPC3-NC>", "")
+
+		self.transport.close()
+		return 0
+
+class BayTechRPC16(PCUControl):
+	def run_telnet(self, node_port, dryrun):
+		return self.run_ssh(node_port, dryrun)
+	def run_ssh(self, node_port, dryrun):
+		self.transport.open(self.host, self.username, None, "Enter user name:")
+		self.transport.sendPassword(self.password, "Enter Password:")
+
+		#self.transport.ifThenSend("RPC-16>", "Status")
+
+		self.transport.ifThenSend("RPC-16>", "Reboot %d" % node_port)
+
+		# Reboot Outlet  N	  (Y/N)?
+		if dryrun:
+			self.transport.ifThenSend("(Y/N)?", "N")
+		else:
+			self.transport.ifThenSend("(Y/N)?", "Y")
+		self.transport.ifThenSend("RPC-16>", "")
+
+		self.transport.close()
+		return 0
+
+class BayTechCtrlCUnibe(PCUControl):
+	"""
+		For some reason, these units let you log in fine, but they hang
+		indefinitely, unless you send a Ctrl-C after the password.  No idea
+		why.
+	"""
+	def run_ssh(self, node_port, dryrun):
+		print "BayTechCtrlC %s" % self.host
+
+		ssh_options="-o StrictHostKeyChecking=no -o PasswordAuthentication=yes -o PubkeyAuthentication=no"
+		s = pxssh.pxssh()
+		if not s.login(self.host, self.username, self.password, ssh_options):
+			raise ExceptionPassword("Invalid Password")
+		# Otherwise, the login succeeded.
+
+		# Send a ctrl-c to the remote process.
+		print "sending ctrl-c"
+		s.send(chr(3))
+
+		# Control Outlets  (5 ,1).........5
+		try:
+			#index = s.expect("Enter Request")
+			index = s.expect(["Enter Request :"])
+
+			if index == 0:
+				print "3"
+				s.send("3\r\n")
+				index = s.expect(["DS-RPC>", "Enter user name:"])
+				if index == 1:
+					s.send(self.username + "\r\n")
+					index = s.expect(["DS-RPC>"])
+
+				if index == 0:
+					print "Reboot %d" % node_port
+					time.sleep(5)
+					s.send("Reboot %d\r\n" % node_port)
+
+					time.sleep(5)
+					index = s.expect(["\(Y/N\)\?", "Port in use", "DS-RPC>"])
+					if index == 0:
+						if dryrun:
+							print "sending N"
+							s.send("N\r\n")
+						else:
+							print "sending Y"
+							s.send("Y\r\n")
+					elif index == 1:
+						raise ExceptionPrompt("PCU Reported 'Port in use.'")
+					elif index == 2:
+						raise ExceptionSequence("Issued command 'Reboot' failed.")
+
+				time.sleep(5)
+				index = s.expect(["DS-RPC>"])
+				#print "got prompt back"
+
+			s.close()
+
+		except pexpect.EOF:
+			raise ExceptionPrompt("EOF before expected Prompt")
+		except pexpect.TIMEOUT:
+			raise ExceptionPrompt("Timeout before expected Prompt")
+
+		return 0
+
+class BayTechCtrlC(PCUControl):
+	"""
+		For some reason, these units let you log in fine, but they hang
+		indefinitely, unless you send a Ctrl-C after the password.  No idea
+		why.
+	"""
+	def run_ssh(self, node_port, dryrun):
+		print "BayTechCtrlC %s" % self.host
+
+		ssh_options="-o StrictHostKeyChecking=no -o PasswordAuthentication=yes -o PubkeyAuthentication=no"
+		s = pxssh.pxssh()
+		if not s.login(self.host, self.username, self.password, ssh_options):
+			raise ExceptionPassword("Invalid Password")
+		# Otherwise, the login succeeded.
+
+		# Send a ctrl-c to the remote process.
+		print "SENDING ctrl-c"
+		s.send(chr(3))
+
+		# Control Outlets  (5 ,1).........5
+		try:
+			print "EXPECTING: ", "Enter Request :"
+			index = s.expect(["Enter Request :"])
+
+			if index == 0:
+				print "SENDING: 5"
+				s.send("5\r\n")
+				print "EXPECTING: ", "DS-RPC>"
+				index = s.expect(["DS-RPC>", "Enter user name:", "Port in use."])
+				if index == 1:
+					print "sending username"
+					s.send(self.username + "\r\n")
+					index = s.expect(["DS-RPC>"])
+				elif index == 2:
+					raise ExceptionPrompt("PCU Reported 'Port in use.'")
+
+				if index == 0:
+					print "SENDING: Reboot %d" % node_port
+					s.send("Reboot %d\r\n" % node_port)
+
+					print "SLEEPING: 5"
+					time.sleep(5)
+					print "EXPECTING: ", "Y/N?"
+					index = s.expect(["\(Y/N\)\?", "Port in use", "DS-RPC>"])
+					if index == 0:
+						if dryrun:
+							print "sending N"
+							s.send("N\r\n")
+						else:
+							print "SENDING: Y"
+							s.send("Y\r\n")
+					elif index == 1:
+						raise ExceptionPrompt("PCU Reported 'Port in use.'")
+					elif index == 2:
+						raise ExceptionSequence("Issued command 'Reboot' failed.")
+
+				# NOTE: for some reason, the script times out with the
+				# following line.  In manual tests, it works correctly, but
+				# with automated tests, evidently it fails.
+				print "SLEEPING: 5"
+				time.sleep(5)
+				#print "TOTAL--", s.allstr, "--EOT"
+				index = s.expect(["DS-RPC>"])
+				print "got prompt back"
+
+			s.close()
+
+		except pexpect.EOF:
+			raise ExceptionPrompt("EOF before 'Enter Request' Prompt")
+		except pexpect.TIMEOUT:
+			raise ExceptionPrompt("Timeout before Prompt")
+
+		return 0
+
+class BayTech(PCUControl):
+	supported_ports = [22,23]
+
+	def run_telnet(self, node_port, dryrun):
+		return self.run_ssh(node_port, dryrun)
+
+	def run_ssh(self, node_port, dryrun):
+		self.transport.open(self.host, self.username)
+		self.transport.sendPassword(self.password)
+
+		# Control Outlets  (5 ,1).........5
+		self.transport.ifThenSend("Enter Request :", "5")
+
+		# Reboot N
+		try:
+			self.transport.ifThenSend("DS-RPC>", "Reboot %d" % node_port, ExceptionNotFound)
+		except ExceptionNotFound, msg:
+			# one machine is configured to ask for a username,
+			# even after login...
+			print "msg: %s" % msg
+			self.transport.write(self.username + "\r\n")
+			time.sleep(5)
+			self.transport.ifThenSend("DS-RPC>", "Reboot %d" % node_port)
+
+		# Reboot Outlet  N	  (Y/N)?
+		if dryrun:
+			self.transport.ifThenSend("(Y/N)?", "N")
+		else:
+			self.transport.ifThenSend("(Y/N)?", "Y")
+		time.sleep(5)
+		self.transport.ifThenSend("DS-RPC>", "")
+
+		self.transport.close()
+		return 0
diff --git a/pcucontrol/models/BlackBoxPSMaverick.py b/pcucontrol/models/BlackBoxPSMaverick.py
new file mode 100644
index 0000000..ee414dc
--- /dev/null
+++ b/pcucontrol/models/BlackBoxPSMaverick.py
@@ -0,0 +1,65 @@
+from pcucontrol.reboot import *
+
+### rebooting european BlackBox PSE boxes
+# Thierry Parmentelat - May 11 2005
+# tested on 4-ports models known as PSE505-FR
+# uses http to POST a data 'P<port>=r'
+# relies on basic authentication within http1.0
+# first curl-based script was
+# curl --http1.0 --basic --user <username>:<password> --data P<port>=r \
+#	http://<hostname>:<http_port>/cmd.html && echo OK
+
+# log in:
+
+## BB PSMaverick
+class BlackBoxPSMaverick(PCUControl):
+	supported_ports = [80]
+
+	def run_http(self, node_port, dryrun):
+		if not dryrun:
+			# send reboot signal.
+			cmd = "curl -s --data 'P%s=r' --anyauth --user '%s:%s' http://%s/config/home_f.html" % ( node_port, self.username, self.password, self.host)
+		else:
+			# else, just try to log in
+			cmd = "curl -s --anyauth --user '%s:%s' http://%s/config/home_f.html" % ( self.username, self.password, self.host)
+
+		p = os.popen(cmd)
+		result = p.read()
+		print "RESULT: ", result
+
+		if len(result.split()) > 3:
+			return 0
+		else:
+			return result
+
+def bbpse_reboot (pcu_ip,username,password,port_in_pcu,http_port, dryrun):
+
+	global verbose
+
+	url = "http://%s:%d/cmd.html" % (pcu_ip,http_port)
+	data= "P%d=r" % port_in_pcu
+	if verbose:
+		logger.debug("POSTing '%s' on %s" % (data,url))
+
+	authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm()
+	uri = "%s:%d" % (pcu_ip,http_port)
+	authinfo.add_password (None, uri, username, password)
+	authhandler = urllib2.HTTPBasicAuthHandler( authinfo )
+
+	opener = urllib2.build_opener(authhandler)
+	urllib2.install_opener(opener)
+
+	if (dryrun):
+		return 0
+
+	try:
+		f = urllib2.urlopen(url,data)
+
+		r= f.read()
+		if verbose:
+			logger.debug(r)
+		return 0
+
+	except urllib2.URLError,err:
+		logger.info('Could not open http connection', err)
+		return "bbpse error"
diff --git a/pcucontrol/models/CustomPCU.py b/pcucontrol/models/CustomPCU.py
new file mode 100644
index 0000000..60de348
--- /dev/null
+++ b/pcucontrol/models/CustomPCU.py
@@ -0,0 +1,26 @@
+# Each method follows the following format:
+#    CustomPCU_loginbase()
+# 
+# This provides a simple means of lookup given the custom type.
+#    The only problem might come up if a single site has multiple custom PCUs.
+#    That would be pretty wierd though...
+
+from pcucontrol.reboot import *
+
+class CustomPCU_uniklu(PCUControl):
+	def run_http(self, node_port, dryrun):
+		url = "https://www-itec.uni-klu.ac.at/plab-pcu/index.php" 
+
+		if not dryrun:
+			# Turn host off, then on
+			formstr = "plab%s=off" % node_port
+			os.system("curl --user %s:%s --form '%s' --insecure %s" % (self.username, self.password, formstr, url))
+			time.sleep(5)
+			formstr = "plab%s=on" % node_port
+			os.system("curl --user %s:%s --form '%s' --insecure %s" % (self.username, self.password, formstr, url))
+		else:
+			os.system("curl --user %s:%s --insecure %s" % (self.username, self.password, url))
+
+
+		
+
diff --git a/pcucontrol/models/DRAC.py b/pcucontrol/models/DRAC.py
new file mode 100644
index 0000000..a2731cd
--- /dev/null
+++ b/pcucontrol/models/DRAC.py
@@ -0,0 +1,166 @@
+from pcucontrol.reboot import *
+
+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()
+		if not s.login(self.host, self.username, self.password, ssh_options,
+						original_prompts="Dell", login_timeout=Transport.TELNET_TIMEOUT):
+			raise ExceptionPassword("Invalid Password")
+
+		print "logging in..."
+		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
+			if dryrun:
+				if index == 0:
+					s.send("racadm getsysinfo")
+				elif index == 1:
+					s.send("getsysinfo")
+			else:
+				if index == 0:
+					s.send("racadm serveraction powercycle")
+				elif index == 1:
+					s.send("serveraction powercycle")
+				
+			s.send("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
+
+class DRACDefault(PCUControl):
+	supported_ports = [22,443,5869]
+	def run_ssh(self, node_port, dryrun):
+		self.transport.open(self.host, self.username)
+		self.transport.sendPassword(self.password)
+
+		print "logging in..."
+		self.transport.write("\r\n")
+		# Testing Reboot ?
+		if dryrun:
+			self.transport.ifThenSend("[%s]#" % self.username, "getsysinfo")
+		else:
+			# Reset this machine
+			self.transport.ifThenSend("[%s]#" % self.username, "serveraction powercycle")
+
+		self.transport.ifThenSend("[%s]#" % self.username, "exit")
+
+		self.transport.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
+			p.tochild.write("%s\n" % username)
+			p.tochild.write("%s\n" % password)
+			p.tochild.close()
+			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 -i serveraction powercycle" % ip],
+				username, password)
+		else:
+			output = runcmd(cmd, ["-r %s -i getsysinfo" % ip],
+				username, password)
+
+		print "RUNCMD: %s" % output
+		if verbose:
+			logger.debug(output)
+		return 0
+
+	except Exception, err:
+		logger.debug("runcmd raised exception %s" % err)
+		if verbose:
+			logger.debug(err)
+		return err
diff --git a/pcucontrol/models/HPiLO.py b/pcucontrol/models/HPiLO.py
new file mode 100644
index 0000000..25d4331
--- /dev/null
+++ b/pcucontrol/models/HPiLO.py
@@ -0,0 +1,59 @@
+from pcucontrol.reboot import *
+
+class HPiLO(PCUControl):
+	supported_ports = [22,443]
+	def run(self, node_port, dryrun):
+		if self.type == Transport.SSH:
+			return self.run_ssh(node_port, dryrun)
+		elif self.type == Transport.HTTP or self.type == Transport.HTTPS:
+			return self.run_https(node_port, dryrun)
+		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("</system1>hpiLO->", "POWER")
+		else:
+			# Reset this machine
+			self.transport.ifThenSend("</system1>hpiLO->", "reset")
+
+		self.transport.ifThenSend("</system1>hpiLO->", "exit")
+
+		self.transport.close()
+		return 0
+		
+	def run_https(self, node_port, dryrun):
+
+		locfg = command.CMD()
+
+		cmd_str = config.MONITOR_SCRIPT_ROOT + "/pcucontrol/models/hpilo/"
+		
+		cmd = cmd_str + "locfg.pl -s %s -f %s -u %s -p '%s' | grep 'MESSAGE' | grep -v 'No error'" % (
+					self.host, cmd_str+"iloxml/Get_Network.xml", 
+					self.username, self.password)
+		sout, serr = locfg.run_noexcept(cmd)
+
+		if sout.strip() != "" or serr.strip() != "":
+			print "sout: %s" % sout.strip()
+			return sout.strip() + serr.strip()
+
+		if not dryrun:
+			locfg = command.CMD()
+			cmd = cmd_str + "locfg.pl -s %s -f %s -u %s -p '%s' | grep 'MESSAGE' | grep -v 'No error'" % (
+						self.host, cmd_str+"iloxml/Reset_Server.xml", 
+						self.username, self.password)
+			sout, serr = locfg.run_noexcept(cmd)
+
+			if sout.strip() != "":
+				print "sout: %s" % sout.strip()
+				#return sout.strip()
+
+		return 0
diff --git a/pcucontrol/models/IPAL.py b/pcucontrol/models/IPAL.py
new file mode 100644
index 0000000..75668db
--- /dev/null
+++ b/pcucontrol/models/IPAL.py
@@ -0,0 +1,142 @@
+from pcucontrol.reboot import *
+
+class IPAL(PCUControl):
+	""" 
+		This now uses a proprietary format for communicating with the PCU.  I
+		prefer it to Telnet, and Web access, since it's much lighter weight
+		and, more importantly, IT WORKS!! HHAHHHAHAHAHAHAHA!
+	"""
+	supported_ports = [9100,23,80]
+
+	def format_msg(self, data, cmd):
+		esc = chr(int('1b',16))
+		return "%c%s%c%s%c" % (esc, self.password, esc, data, cmd) # esc, 'q', chr(4))
+	
+	def recv_noblock(self, s, count):
+		import errno
+
+		try:
+			# TODO: make sleep backoff, before stopping.
+			time.sleep(4)
+			ret = s.recv(count, socket.MSG_DONTWAIT)
+		except socket.error, e:
+			if e[0] == errno.EAGAIN:
+				raise Exception(e[1])
+			else:
+				# TODO: not other exceptions.
+				raise Exception(e)
+		return ret
+
+	#def run(self, node_port, dryrun):
+	#	if self.type == Transport.IPAL:
+	#		ret = self.run_ipal(node_port, dryrun)
+	#		if ret != 0:
+	#			ret2 = self.run_telnet(node_port, dryrun)
+	#			if ret2 != 0:
+	#				return ret
+	#			return ret2
+	#		return ret
+	#	elif self.type == Transport.TELNET:
+	#		return self.run_telnet(node_port, dryrun)
+	#	else:
+	#		raise ExceptionNoTransport("Unimplemented Transport for IPAL")
+	
+	def run_telnet(self, node_port, dryrun):
+		# TELNET version of protocol...
+		self.transport.open(self.host)
+		## XXX Some iPals require you to hit Enter a few times first
+		self.transport.ifThenSend("Password >", "\r\n\r\n", ExceptionNotFound)
+		# Login
+		self.transport.ifThenSend("Password >", self.password, ExceptionPassword)
+		self.transport.write("\r\n\r\n")
+		if not dryrun: # P# - Pulse relay
+			print "node_port %s" % node_port
+			self.transport.ifThenSend("Enter >", 
+							"P7", # % node_port, 
+							ExceptionNotFound)
+			print "send newlines"
+			self.transport.write("\r\n\r\n")
+			print "after new lines"
+		# Get the next prompt
+		print "wait for enter"
+		self.transport.ifElse("Enter >", ExceptionTimeout)
+		print "closing "
+		self.transport.close()
+		return 0
+
+	def run_ipal(self, node_port, dryrun):
+		import errno
+
+		power_on = False
+
+		print "open socket"
+		s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+		try:
+			print "connect"
+			s.connect((self.host, 9100))
+		except socket.error, e:
+			s.close()
+			if e[0] == errno.ECONNREFUSED:
+				# cannot connect to remote host
+				raise Exception(e[1])
+			else:
+				# TODO: what other conditions are there?
+				raise Exception(e)
+				
+		# get current status
+		print "Checking status"
+		s.send(self.format_msg("", 'O'))
+		ret = self.recv_noblock(s, 8)
+		print "Current status is '%s'" % ret
+
+		if ret == '':
+			raise Exception("Status returned 'another session already open' %s : %s" % (node_port, ret))
+				
+		if node_port < len(ret):
+			status = ret[node_port]
+			if status == '1':
+				# up
+				power_on = True
+			elif status == '0':
+				# down
+				power_on = False
+			else:
+				raise Exception("Unknown status for PCU socket %s : %s" % (node_port, ret))
+		else:
+			raise Exception("Mismatch between configured port and PCU status: %s %s" % (node_port, ret))
+			
+
+		if not dryrun:
+			if power_on:
+				print "Pulsing %s" % node_port
+				s.send(self.format_msg("%s" % node_port, 'P'))
+			else:
+				# NOTE: turn power on ; do not pulse the port.
+				print "Power was off, so turning on ..."
+				s.send(self.format_msg("%s" % node_port, 'E'))
+				#s.send(self.format_msg("%s" % node_port, 'P'))
+
+			print "Receiving response."
+			ret = self.recv_noblock(s, 8)
+			print "Current status is '%s'" % ret
+
+			if node_port < len(ret):
+				status = ret[node_port]
+				if status == '1':
+					# up
+					power_on = True
+				elif status == '0':
+					# down
+					power_on = False
+				else:
+					raise Exception("Unknown status for PCU socket %s : %s" % (node_port, ret))
+			else:
+				raise Exception("Mismatch between configured port and PCU status: %s %s" % (node_port, ret))
+
+			if power_on:
+				return 0
+			else:
+				return "Failed Power On"
+
+		s.close()
+		return 0
diff --git a/pcucontrol/models/IntelAMT.py b/pcucontrol/models/IntelAMT.py
new file mode 100644
index 0000000..af201ba
--- /dev/null
+++ b/pcucontrol/models/IntelAMT.py
@@ -0,0 +1,20 @@
+from pcucontrol.reboot import *
+
+class IntelAMT(PCUControl):
+	supported_ports = [16992]
+
+	def run_amt(self, node_port, dryrun):
+
+		cmd = command.CMD()
+		# TODO: need to make this path universal; not relative to pwd.
+		cmd_str = config.MONITOR_SCRIPT_ROOT + "/pcucontrol/models/intelamt/remoteControl"
+
+		if dryrun:
+			# NOTE: -p checks the power state of the host.
+			# TODO: parse the output to find out if it's ok or not.
+			cmd_str += " -p http://%s:16992/RemoteControlService  -user admin -pass '%s' " % (self.host, self.password )
+		else:
+			cmd_str += " -A http://%s:16992/RemoteControlService -user admin -pass '%s' " % (self.host, self.password )
+			
+		print cmd_str
+		return cmd.system(cmd_str, Transport.TELNET_TIMEOUT)
diff --git a/pcucontrol/models/ManualPCU.py b/pcucontrol/models/ManualPCU.py
new file mode 100644
index 0000000..3a21cd9
--- /dev/null
+++ b/pcucontrol/models/ManualPCU.py
@@ -0,0 +1,13 @@
+from pcucontrol.reboot import *
+
+class ManualPCU(PCUControl):
+	supported_ports = [22,23,80,443]
+
+	def run_http(self, node_port, dryrun):
+		if not dryrun:
+			# TODO: send email message to monitor admin requesting manual
+			# intervention.  This should always be an option for ridiculous,
+			# custom jobs.
+			pass
+		return 0
+
diff --git a/pcucontrol/models/OpenIPMI.py b/pcucontrol/models/OpenIPMI.py
new file mode 100644
index 0000000..f52ea39
--- /dev/null
+++ b/pcucontrol/models/OpenIPMI.py
@@ -0,0 +1,25 @@
+
+from pcucontrol.reboot import *
+
+class OpenIPMI(PCUControl):
+
+	supported_ports = [80,443,623]
+
+	# TODO: get exit codes to determine success or failure...
+	def run_https(self, node_port, dryrun):
+
+		if not dryrun:
+			cmd = "ipmitool -I lanplus -H %s -U %s -P '%s' power cycle  "
+			(i,p) = os.popen4(cmd % ( self.host, self.username, self.password) )
+			result = p.read()
+			print "RESULT: ", result
+		else:
+			cmd = "ipmitool -I lanplus -H %s -U %s -P '%s' user list  "
+			(i,p) = os.popen4(cmd % ( self.host, self.username, self.password) )
+			result = p.read()
+			print "RESULT: ", result
+
+		if "Error" in result:
+			return result
+		else:
+			return 0
diff --git a/pcucontrol/models/PM211MIP.py b/pcucontrol/models/PM211MIP.py
new file mode 100644
index 0000000..007b307
--- /dev/null
+++ b/pcucontrol/models/PM211MIP.py
@@ -0,0 +1,5 @@
+from pcucontrol.reboot import *
+from ManualPCU import *
+
+class PM211MIP(ManualPCU):
+	supported_ports = [80,443]
diff --git a/pcucontrol/models/WTIIPS4.py b/pcucontrol/models/WTIIPS4.py
new file mode 100644
index 0000000..aebde31
--- /dev/null
+++ b/pcucontrol/models/WTIIPS4.py
@@ -0,0 +1,18 @@
+from pcucontrol.reboot import *
+
+class WTIIPS4(PCUControl):
+	supported_ports = [23]
+	def run_telnet(self, node_port, dryrun):
+		self.transport.open(self.host)
+		self.transport.sendPassword(self.password, "Enter Password:")
+
+		self.transport.ifThenSend("IPS> ", "/Boot %s" % node_port)
+		if not dryrun:
+			self.transport.ifThenSend("Sure? (Y/N): ", "N")
+		else:
+			self.transport.ifThenSend("Sure? (Y/N): ", "Y")
+
+		self.transport.ifThenSend("IPS> ", "")
+
+		self.transport.close()
+		return 0
diff --git a/pcucontrol/models/X10Toggle.py b/pcucontrol/models/X10Toggle.py
new file mode 100644
index 0000000..20d7cef
--- /dev/null
+++ b/pcucontrol/models/X10Toggle.py
@@ -0,0 +1,49 @@
+
+from pcucontrol.reboot import *
+### rebooting x10toggle based systems addressed by port
+# Marc E. Fiuczynski - May 31 2005
+# tested on 4-ports models known as PSE505-FR
+# uses ssh and password to login to an account
+# that will cause the system to be powercycled.
+
+TELNET_TIMEOUT = 120
+def telnet_answer(telnet, expected, buffer):
+	global verbose
+
+	output = telnet.read_until(expected, TELNET_TIMEOUT)
+	#if verbose:
+	#	logger.debug(output)
+	if output.find(expected) == -1:
+		raise ExceptionNotFound, "'%s' not found" % expected
+	else:
+		telnet.write(buffer + "\r\n")
+
+def x10toggle_reboot(ip, username, password, port, dryrun):
+	global verbose
+
+	ssh = None
+	try:
+		ssh = pyssh.Ssh(username, ip)
+		ssh.open()
+
+		# Login
+		telnet_answer(ssh, "password:", password)
+
+		if not dryrun:
+			# Reboot
+			telnet_answer(ssh, "x10toggle>", "A%d" % port)
+
+		# Close
+		output = ssh.close()
+		if verbose:
+			logger.debug(output)
+		return 0
+
+	except Exception, err:
+		if verbose:
+			logger.debug(err)
+		if ssh:
+			output = ssh.close()
+			if verbose:
+				logger.debug(output)
+		return errno.ETIMEDOUT
diff --git a/pcucontrol/models/__init__.py b/pcucontrol/models/__init__.py
new file mode 100644
index 0000000..c9b2bb1
--- /dev/null
+++ b/pcucontrol/models/__init__.py
@@ -0,0 +1,13 @@
+from OpenIPMI import *
+from IPAL import *
+from APCControl import *
+from IntelAMT import *
+from DRAC import *
+from HPiLO import *
+from BayTech import *
+from WTIIPS4 import *
+from ePowerSwitch import *
+from BlackBoxPSMaverick import *
+from CustomPCU import *
+from ManualPCU import *
+from PM211MIP import *
diff --git a/pcucontrol/models/ePowerSwitch.py b/pcucontrol/models/ePowerSwitch.py
new file mode 100644
index 0000000..7650689
--- /dev/null
+++ b/pcucontrol/models/ePowerSwitch.py
@@ -0,0 +1,120 @@
+from pcucontrol.reboot import *
+
+class ePowerSwitchNew(PCUControl):
+	# NOTE:
+	# 		The old code used Python's HTTPPasswordMgrWithDefaultRealm()
+	#		For some reason this both doesn't work and in some cases, actually
+	#		hangs the PCU.  Definitely not what we want.
+	#		
+	# 		The code below is much simpler.  Just letting things fail first,
+	# 		and then, trying again with authentication string in the header.
+	#		
+	def run_http(self, node_port, dryrun):
+		self.transport = None
+		self.url = "http://%s:%d/" % (self.host,80)
+		uri = "%s:%d" % (self.host,80)
+
+		req = urllib2.Request(self.url)
+		try:
+			handle = urllib2.urlopen(req)
+		except IOError, e:
+			# NOTE: this is expected to fail initially
+			pass
+		else:
+			print self.url
+			print "-----------"
+			print handle.read()
+			print "-----------"
+			return "ERROR: not protected by HTTP authentication"
+
+		if not hasattr(e, 'code') or e.code != 401:
+			return "ERROR: failed for: %s" % str(e)
+
+		base64data = base64.encodestring("%s:%s" % (self.username, self.password))[:-1]
+		# NOTE: assuming basic realm authentication.
+		authheader = "Basic %s" % base64data
+		req.add_header("Authorization", authheader)
+
+		try:
+			f = urllib2.urlopen(req)
+		except IOError, e:
+			# failing here means the User/passwd is wrong (hopefully)
+			raise ExceptionPassword("Incorrect username/password")
+
+		# NOTE: after verifying that the user/password is correct, 
+		# 		actually reboot the given node.
+		if not dryrun:
+			try:
+				data = urllib.urlencode({'P%d' % node_port : "r"})
+				req = urllib2.Request(self.url + "cmd.html")
+				req.add_header("Authorization", authheader)
+				# add data to handler,
+				f = urllib2.urlopen(req, data)
+				if self.verbose: print f.read()
+			except:
+				import traceback; traceback.print_exc()
+
+				# fetch url one more time on cmd.html, econtrol.html or whatever.
+				# pass
+		else:
+			if self.verbose: print f.read()
+
+		return 0
+
+class ePowerSwitchOld(PCUControl):
+	def run_http(self, node_port, dryrun):
+		self.url = "http://%s:%d/" % (self.host,80)
+		uri = "%s:%d" % (self.host,80)
+
+		# create authinfo
+		authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm()
+		authinfo.add_password (None, uri, self.username, self.password)
+		authhandler = urllib2.HTTPBasicAuthHandler( authinfo )
+
+		# NOTE: it doesn't seem to matter whether this authinfo is here or not.
+		transport = urllib2.build_opener(authinfo)
+		f = transport.open(self.url)
+		if self.verbose: print f.read()
+
+		if not dryrun:
+			transport = urllib2.build_opener(authhandler)
+			f = transport.open(self.url + "cmd.html", "P%d=r" % node_port)
+			if self.verbose: print f.read()
+
+		self.transport.close()
+		return 0
+
+class ePowerSwitchOld(PCUControl):
+	supported_ports = [80]
+	def run_http(self, node_port, dryrun):
+		self.url = "http://%s:%d/" % (self.host,80)
+		uri = "%s:%d" % (self.host,80)
+
+		# TODO: I'm still not sure what the deal is here.
+		# 		two independent calls appear to need to be made before the
+		# 		reboot will succeed.  It doesn't seem to be possible to do
+		# 		this with a single call.  I have no idea why.
+
+		# create authinfo
+		authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm()
+		authinfo.add_password (None, uri, self.username, self.password)
+		authhandler = urllib2.HTTPBasicAuthHandler( authinfo )
+
+		# NOTE: it doesn't seem to matter whether this authinfo is here or not.
+		transport = urllib2.build_opener()
+		f = transport.open(self.url + "elogin.html", "pwd=%s" % self.password)
+		if self.verbose: print f.read()
+
+		if not dryrun:
+			transport = urllib2.build_opener(authhandler)
+			f = transport.open(self.url + "econtrol.html", "P%d=r" % node_port)
+			if self.verbose: print f.read()
+
+		#	data= "P%d=r" % node_port
+		#self.open(self.host, self.username, self.password)
+		#self.sendHTTP("elogin.html", "pwd=%s" % self.password)
+		#self.sendHTTP("econtrol.html", data)
+		#self.sendHTTP("cmd.html", data)
+
+		#self.close()
+		return 0
-- 
2.47.0