This patch adds bootloader functionality to autoserv:

From: Benjamin Poirier <poirier@google.com>
Signed-off-by: Martin J. Bligh <mbligh@google.com>

* removed Lilo and Grub classes
* Bootloader uses boottool, boottool support brought in from autotest, get_title(), get_info() improved, add_kernel() has 'default' parameter to add an entry and make it the default one in one simple step
* Kernel has new methods to ease installation, get_version(), get_initrd_name() and get_image_name()
* DEBKernel changed to implement those method and call the host's bootloader during install()

Installing and booting .deb kernel now works.
Example usage:

rh= hosts.SSHHost("192.168.0.1")

print rh.run("uname -a").stdout

kernel= deb_kernel.DEBKernel()
kernel.get("/home/poirier/linux-2.6.22_2.6.22_amd64.deb")

kernel.install(rh)

rh.reboot()
rh.wait_up()

print rh.run("uname -a").stdout

Credit goes to Ryan for bringing in boottool support.



git-svn-id: http://test.kernel.org/svn/autotest/trunk@574 592f7852-d20e-0410-864c-8624ca9c26a4
diff --git a/server/deb_kernel.py b/server/deb_kernel.py
index a8c1471..3560068 100644
--- a/server/deb_kernel.py
+++ b/server/deb_kernel.py
@@ -35,19 +35,83 @@
 		super(DEBKernel, self).__init__()
 
 
-	def install(self, host):
-		# this directory will get cleaned up for us automatically
+	def install(self, host, **kwargs):
+		"""Install a kernel on the remote host.
+		
+		This will also invoke the guest's bootloader to set this
+		kernel as the default kernel.
+		
+		Args:
+			host: the host on which to install the kernel
+			[kwargs]: remaining keyword arguments will be passed 
+				to Bootloader.add_kernel()
+		
+		Raises:
+			AutoservError: no package has yet been obtained. Call
+				DEBKernel.get() with a .deb package.
+		"""
+		if self.source_material is None:
+			raise AutoservError("A kernel must first be \
+			specified via get()")
+		
 		remote_tmpdir = host.get_tmp_dir()
 		basename = os.path.basename(self.source_material)
 		remote_filename = os.path.join(remote_tmpdir, basename)
 		host.send_file(self.source_material, remote_filename)
-		try:
-			result = host.run('dpkg -i %s'
-					  % remote_filename)
-			if result.exit_status:
-				raise AutoservError('dpkg failed \
-				installing %s:\n\n%s'% (remote_filename,
-							result.stderr))
-		except NameError, e:
-			raise AutoservError('A kernel must first be \
-			specified via get_from_file or get_from_url')
+		host.run('dpkg -i "%s"' % (utils.sh_escape(remote_filename),))
+		host.run('mkinitramfs -o "%s" "%s"' % (
+			utils.sh_escape(self.get_initrd_name()), 
+			utils.sh_escape(self.get_version()),))
+		
+		host.bootloader.add_kernel(self.get_image_name(), 
+			initrd=self.get_initrd_name(), **kwargs)
+	
+	def get_version(self):
+		"""Get the version of the kernel to be installed.
+		
+		Returns:
+			The version string, as would be returned 
+			by 'make kernelrelease'.
+		
+		Raises:
+			AutoservError: no package has yet been obtained. Call
+				DEBKernel.get() with a .deb package.
+		"""
+		if self.source_material is None:
+			raise AutoservError("A kernel must first be \
+			specified via get()")
+		
+		retval= utils.run('dpkg-deb -f "%s" version' % 
+			utils.sh_escape(self.source_material),)
+		return retval.stdout.strip()
+	
+	def get_image_name(self):
+		"""Get the name of the kernel image to be installed.
+		
+		Returns:
+			The full path to the kernel image file as it will be 
+			installed on the host.
+		
+		Raises:
+			AutoservError: no package has yet been obtained. Call
+				DEBKernel.get() with a .deb package.
+		"""
+		return "/boot/vmlinuz-%s" % (self.get_version(),)
+	
+	def get_initrd_name(self):
+		"""Get the name of the initrd file to be installed.
+		
+		Returns:
+			The full path to the initrd file as it will be 
+			installed on the host. If the package includes no 
+			initrd file, None is returned
+		
+		Raises:
+			AutoservError: no package has yet been obtained. Call
+				DEBKernel.get() with a .deb package.
+		"""
+		if self.source_material is None:
+			raise AutoservError("A kernel must first be \
+			specified via get()")
+		
+		return "/boot/initrd.img-%s" % (self.get_version(),)
diff --git a/server/hosts/__init__.py b/server/hosts/__init__.py
index 1b40526..41adf94 100644
--- a/server/hosts/__init__.py
+++ b/server/hosts/__init__.py
@@ -26,8 +26,6 @@
 
 # bootloader classes
 from bootloader import Bootloader
-from lilo import Lilo
-from grub import Grub
 
 # command result class
 from base_classes import CmdResult
diff --git a/server/hosts/base_classes.py b/server/hosts/base_classes.py
index dac153a..4564fc1 100644
--- a/server/hosts/base_classes.py
+++ b/server/hosts/base_classes.py
@@ -19,7 +19,7 @@
 
 import time
 import textwrap
-
+import bootloader
 
 class Host(object):
 	"""This class represents a machine on which you can run programs.
@@ -36,6 +36,7 @@
 	
 	def __init__(self):
 		super(Host, self).__init__()
+		self.bootloader= bootloader.Bootloader(self)
 	
 	def run(self, command):
 		pass
diff --git a/server/hosts/bootloader.py b/server/hosts/bootloader.py
index eeaa5fb..a7f717e 100644
--- a/server/hosts/bootloader.py
+++ b/server/hosts/bootloader.py
@@ -11,23 +11,144 @@
 poirier@google.com (Benjamin Poirier),
 stutsman@google.com (Ryan Stutsman)"""
 
+import os.path
+import sys
+import weakref
+
+import errors
+import utils
+
+
+BOOTTOOL_SRC = '../client/tools/boottool'  # Get it from autotest client
+
 
 class Bootloader(object):
 	"""This class represents a bootloader.
 	
 	It can be used to add a kernel to the list of kernels that can be 
 	booted by a bootloader. It can also make sure that this kernel will 
-	be the one chosen at next reboot.
+	be the one chosen at next reboot."""
 	
-	Implementation details:
-	This is an abstract class, leaf subclasses must implement the methods
-	listed here. You must not instantiate this class but should 
-	instantiate one of those leaf subclasses."""
+	def __init__(self, host, xen_mode=False):
+		super(Bootloader, self).__init__()
+		self.__host = weakref.ref(host)
+		self.__boottool_path = None
+		self.xen_mode = xen_mode
 	
-	host = None
+	def get_type(self):
+		return self.__run_boottool('--bootloader-probe').stdout.strip()
 	
-	def add_entry(self, name, image, initrd, root, options, default=True):
-		pass
+	def get_architecture(self):
+		return self.__run_boottool('--arch-probe').stdout.strip()
 	
-	def remove_entry(self, name):
-		pass
+	def get_titles(self):
+		return self.__run_boottool('--info all | grep title | '
+			'cut -d " " -f2-').stdout.strip().split('\n')
+	
+	def get_default(self):
+		return self.__run_boottool('--default').stdout.strip()
+	
+	def get_info(self, index):
+		retval= self.__run_boottool(
+			'--info=%s' % index).stdout.strip().split("\n")
+		
+		result= {}
+		for line in retval:
+			(key, val,)= line.split(":")
+			result[key.strip()]= val.strip()
+		
+		return result
+	
+	def set_default(self, index):
+		self.__run_boottool('--set-default=%s' % index)
+	
+	# 'kernel' can be a position number or a title
+	def add_args(self, kernel, args):
+		parameters = '--update-kernel=%s --args="%s"' % (kernel, args)
+		
+		#add parameter if this is a Xen entry
+		if self.xen_mode:
+			parameters += ' --xen'
+		
+		self.__run_boottool(parameters)
+	
+	def add_xen_hypervisor_args(self, kernel, args):
+		self.__run_boottool('--xen --update-xenhyper=%s --xha="%s"' \
+				    % (kernel, args))
+	
+	def remove_args(self, kernel, args):
+		params = '--update-kernel=%s --remove-args=%s' % (kernel, args)
+		
+		#add parameter if this is a Xen entry
+		if self.xen_mode:
+			params += ' --xen'
+		
+		self.__run_boottool(params)
+	
+	def remove_xen_hypervisor_args(self, kernel, args):
+		self.__run_boottool('--xen --update-xenhyper=%s '
+			'--remove-args="%s"') % (kernel, args)
+	
+	def add_kernel(self, path, title='autoserv', root=None, args=None, 
+		initrd=None, xen_hypervisor=None, default=True):
+		"""
+		If an entry with the same title is already present, it will be 
+		replaced.
+		"""
+		if title in self.get_titles():
+			self.__run_boottool('--remove-kernel "%s"' % (
+				utils.sh_escape(title),))
+		
+		parameters = '--add-kernel "%s" --title "%s"' % (
+			utils.sh_escape(path), utils.sh_escape(title),)
+		
+		if root:
+			parameters += ' --root "%s"' % (utils.sh_escape(root),)
+		
+		if args:
+			parameters += ' --args "%s"' % (utils.sh_escape(args),)
+		
+		# add an initrd now or forever hold your peace
+		if initrd:
+			parameters += ' --initrd "%s"' % (
+				utils.sh_escape(initrd),)
+		
+		if default:
+			parameters += ' --make-default'
+		
+		# add parameter if this is a Xen entry
+		if self.xen_mode:
+			parameters += ' --xen'
+			if xen_hypervisor:
+				parameters += ' --xenhyper "%s"' % (
+					utils.sh_escape(xen_hypervisor),)
+		
+		self.__run_boottool(parameters)
+	
+	def remove_kernel(self, kernel):
+		self.__run_boottool('--remove-kernel=%s' % kernel)
+	
+	def boot_once(self, title):
+		self.__run_boottool('--boot-once --title=%s' % title)
+	
+	def __install_boottool(self):
+		if self.__host() is None:
+			raise errors.AutoservError("Host does not exist anymore")
+		tmpdir = self.__host().get_tmp_dir()
+		self.__host().send_file(os.path.abspath(os.path.join(
+			os.path.dirname(sys.argv[0]), BOOTTOOL_SRC)), tmpdir)
+		self.__boottool_path= os.path.join(tmpdir, 
+			os.path.basename(BOOTTOOL_SRC))
+	
+	def __get_boottool_path(self):
+		if not self.__boottool_path:
+			self.__install_boottool()
+		return self.__boottool_path
+	
+	def __set_boottool_path(self, path):
+		self.__boottool_path = path
+	
+	boottool_path = property(__get_boottool_path, __set_boottool_path)
+	
+	def __run_boottool(self, cmd):
+		return self.__host().run(self.boottool_path + ' ' + cmd)
diff --git a/server/kernel.py b/server/kernel.py
index db6762a..998eae9 100644
--- a/server/kernel.py
+++ b/server/kernel.py
@@ -28,4 +28,11 @@
 	must not instantiate this class but should instantiate one of those 
 	leaf subclasses."""
 	
-	pass
+	def get_version():
+		pass
+	
+	def get_image_name():
+		pass
+	
+	def get_initrd_name():
+		pass