Merged ConmuxSSHHost with SSHHost. If the remote machine has a serial
console you can now use SSHHost to hard reset the machine and to log
console output to a file. The class still works file when using a
machine without a serial console, with the caveat that hard reset and
console logging will fail silently.

The change also fixes a couple of issues with the original ConmuxSSHHost
implementation, specifically:
        - it properly terminates all the console logging subprocesses
        - hardreset can wait for the machine to come back up

From: jadmanski@google.com



git-svn-id: http://test.kernel.org/svn/autotest/trunk@811 592f7852-d20e-0410-864c-8624ca9c26a4
diff --git a/server/hosts/__init__.py b/server/hosts/__init__.py
index 41adf94..f849375 100644
--- a/server/hosts/__init__.py
+++ b/server/hosts/__init__.py
@@ -20,7 +20,6 @@
 
 # host implementation classes
 from ssh_host import SSHHost
-from conmux_ssh_host import ConmuxSSHHost
 from guest import Guest
 from kvm_guest import KVMGuest
 
diff --git a/server/hosts/conmux_ssh_host.py b/server/hosts/conmux_ssh_host.py
deleted file mode 100644
index f8bdabd..0000000
--- a/server/hosts/conmux_ssh_host.py
+++ /dev/null
@@ -1,128 +0,0 @@
-#!/usr/bin/python
-#
-# Copyright 2007 Google Inc. Released under the GPL v2
-
-"""
-This module defines the ConmuxSSHHost
-
-Implementation details:
-You should import the "hosts" package instead of importing each type of host.
-
-	ConmuxSSHHost: a remote machine controlled through a serial console
-"""
-
-__author__ = """
-mbligh@google.com (Martin J. Bligh),
-poirier@google.com (Benjamin Poirier),
-stutsman@google.com (Ryan Stutsman)
-"""
-
-import os
-import os.path
-import sys
-import signal
-import subprocess
-
-import utils
-import ssh_host
-import errors
-
-class ConmuxSSHHost(ssh_host.SSHHost):
-	"""
-	This class represents a remote machine controlled through a serial 
-	console on which you can run programs. It is not the machine autoserv 
-	is running on.
-
-	For a machine controlled in this way, it may be possible to support 
-	hard reset, boot strap monitoring or other operations not possible 
-	on a machine controlled through ssh, telnet, ...
-	"""
-
-
-	def __init__(self,
-		     hostname,
-		     logfilename=None,
-		     server=None,
-		     attach=None, *args, **kwargs):
-		super(ConmuxSSHHost, self).__init__(hostname, *args, **kwargs)
-		self.server = server
-		self.attach = attach
-		self.attach = self.__find_console_attach()
-		self.pid = None
-		self.__start_console_log(logfilename)
-
-
-	def hardreset(self):
-		"""
-		Reach out and slap the box in the power switch
-		"""
-		self.__console_run(r"'~$hardreset'")
-
-
-	def __start_console_log(self, logfilename):
-		"""
-		Log the output of the console session to a specified file
-		"""
-		if logfilename == None:
-			return
-		if not self.attach or not os.path.exists(self.attach):
-			return
-		if self.server:
-			to = '%s/%s' % (self.server, self.hostname)
-		else:
-			to = self.hostname
-		cmd = [self.attach, to, 'cat - > %s' % logfilename]
-		self.pid = subprocess.Popen(cmd).pid
-
-	
-	def __find_console_attach(self):
-		if self.attach:
-			return self.attach
-		try:
-			res = utils.run('which conmux-attach')
-			if res.exit_status == 0:
-				return res.stdout.strip()
-		except errors.AutoservRunError, e:
-			pass
-		autoserv_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
-		autotest_conmux = os.path.join(autoserv_dir, '..',
-					       'conmux', 'conmux-attach')
-		autotest_conmux_alt = os.path.join(autoserv_dir,
-						   '..', 'autotest',
-						   'conmux', 'conmux-attach')
-		locations = [autotest_conmux,
-			     autotest_conmux_alt,
-			     '/usr/local/conmux/bin/conmux-attach',
-			     '/usr/bin/conmux-attach']
-		for l in locations:
-			if os.path.exists(l):
-				return l
-
-		print "WARNING: conmux-attach not found on autoserv server"
-		return None
-
-
-	def __console_run(self, cmd):
-		"""
-		Send a command to the conmux session
-		"""
-		if not self.attach or not os.path.exists(self.attach):
-			return
-		if self.server:
-			to = '%s/%s' % (self.server, self.hostname)
-		else:
-			to = self.hostname
-		cmd = '%s %s echo %s' % (self.attach,
-					 to,
-					 cmd)
-		os.system(cmd)
-
-
-	def __del__(self):
-		if self.pid:
-			try:
-				os.kill(self.pid, signal.SIGTERM)
-			except OSError:
-				pass
-		super(ConmuxSSHHost, self).__del__()
-	
diff --git a/server/hosts/guest.py b/server/hosts/guest.py
index f75e90b..0d8270c 100644
--- a/server/hosts/guest.py
+++ b/server/hosts/guest.py
@@ -60,7 +60,7 @@
 		self.controlling_hypervisor.delete_guest(self.hostname)
 
 	
-	def hardreset(self):
+	def hardreset(self, timeout=600, wait=True):
 		"""
 		Perform a "hardreset" of the guest.
 		
@@ -68,4 +68,3 @@
 		even if the guest otherwise innaccessible through ssh.
 		"""
 		return self.controlling_hypervisor.reset_guest(self.hostname)
-
diff --git a/server/hosts/ssh_host.py b/server/hosts/ssh_host.py
index 463546f..9aff6c6 100644
--- a/server/hosts/ssh_host.py
+++ b/server/hosts/ssh_host.py
@@ -18,7 +18,7 @@
 """
 
 
-import types, os, time, re
+import types, os, sys, signal, subprocess, time, re
 import base_classes, utils, errors, bootloader
 
 
@@ -31,12 +31,20 @@
 	configured for password-less login, for example through public key 
 	authentication.
 
+	It includes support for controlling the machine through a serial
+	console on which you can run programs. If such a serial console is
+	set up on the machine then capabilities such as hard reset and
+	boot strap monitoring are available. If the machine does not have a
+	serial console available then ordinary SSH-based commands will
+	still be available, but attempts to use extensions such as
+	console logging or hard reset will fail silently.
+
 	Implementation details:
 	This is a leaf class in an abstract class hierarchy, it must 
 	implement the unimplemented methods in parent classes.
 	"""
 
-	def __init__(self, hostname, user="root", port=22, initialize=True):
+	def __init__(self, hostname, user="root", port=22, initialize=True, logfilename=None, conmux_server=None, conmux_attach=None):
 		"""
 		Construct a SSHHost object
 		
@@ -52,6 +60,11 @@
 		self.tmp_dirs= []
 		self.initialize = initialize
 
+		self.conmux_server = conmux_server
+		self.conmux_attach = self.__find_console_attach(conmux_attach)
+		self.logger_pid = None
+		self.__start_console_log(logfilename)
+
 		super(SSHHost, self).__init__()
 		self.bootloader = bootloader.Bootloader(self)
 
@@ -65,6 +78,93 @@
 				self.run('rm -rf "%s"' % (utils.sh_escape(dir)))
 			except errors.AutoservRunError:
 				pass
+		if self.logger_pid:
+			try:
+				pgid = os.getpgid(self.logger_pid)
+				os.killpg(pgid, signal.SIGTERM)
+			except OSError:
+				pass
+
+
+	def __wait_for_restart(self, timeout):
+		self.wait_down(60)	# Make sure he's dead, Jim
+		self.wait_up(timeout)
+		time.sleep(2) # this is needed for complete reliability
+		self.wait_up(timeout)
+		print "Reboot complete"
+
+
+	def hardreset(self, timeout=600, wait=True):
+		"""
+		Reach out and slap the box in the power switch
+		"""
+		result = self.__console_run(r"'~$hardreset'")
+		if wait:
+			self.__wait_for_restart(timeout)
+		return result
+
+
+	def __start_console_log(self, logfilename):
+		"""
+		Log the output of the console session to a specified file
+		"""
+		if logfilename == None:
+			return
+		if not self.conmux_attach or not os.path.exists(self.conmux_attach):
+			return
+		if self.conmux_server:
+			to = '%s/%s' % (self.conmux_server, self.hostname)
+		else:
+			to = self.hostname
+		cmd = [self.conmux_attach, to, 'cat - > %s' % logfilename]
+		logger = subprocess.Popen(cmd,
+					  stderr=open('/dev/null', 'w'),
+					  preexec_fn=lambda: os.setpgid(0, 0))
+		self.logger_pid = logger.pid
+
+
+	def __find_console_attach(self, conmux_attach):
+		if conmux_attach:
+			return conmux_attach
+		try:
+			res = utils.run('which conmux-attach')
+			if res.exit_status == 0:
+				return res.stdout.strip()
+		except errors.AutoservRunError, e:
+			pass
+		autoserv_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
+		autotest_conmux = os.path.join(autoserv_dir, '..',
+					       'conmux', 'conmux-attach')
+		autotest_conmux_alt = os.path.join(autoserv_dir,
+						   '..', 'autotest',
+						   'conmux', 'conmux-attach')
+		locations = [autotest_conmux,
+			     autotest_conmux_alt,
+			     '/usr/local/conmux/bin/conmux-attach',
+			     '/usr/bin/conmux-attach']
+		for l in locations:
+			if os.path.exists(l):
+				return l
+
+		print "WARNING: conmux-attach not found on autoserv server"
+		return None
+
+
+	def __console_run(self, cmd):
+		"""
+		Send a command to the conmux session
+		"""
+		if not self.conmux_attach or not os.path.exists(self.conmux_attach):
+			return False
+		if self.conmux_server:
+			to = '%s/%s' % (self.conmux_server, self.hostname)
+		else:
+			to = self.hostname
+		cmd = '%s %s echo %s 2> /dev/null' % (self.conmux_attach,
+						      to,
+						      cmd)
+		result = os.system(cmd)
+		return result == 0
 
 
 	def run(self, command, timeout=None, ignore_status=False):
@@ -113,12 +213,7 @@
 		print "Reboot: initiating reboot"
 		self.run('reboot')
 		if wait:
-			self.wait_down(60)	# Make sure he's dead, Jim
-			print "Reboot: machine has gone down"
-			self.wait_up(timeout)
-			time.sleep(2) # this is needed for complete reliability
-			self.wait_up(timeout)
-			print "Reboot complete"
+			self.__wait_for_restart(timeout)
 
 
 	def get_file(self, source, dest):