From 06c94424b5e2f86d4764f79773c30c227200a64a Mon Sep 17 00:00:00 2001
From: Devan Goodwin <dgoodwin@rm-rf.ca>
Date: Wed, 13 Oct 2010 10:57:20 -0300
Subject: [PATCH] Add preliminary support for EFI grub booting.

Sync will now generate an efidefault grub menu for your tftp server.
This menu will contain options to boot all cobbler profiles at this
point. No per-system menus are generated as it appears EFI systems look
for the images relative to the tftp root, which is a problem for our
sync cleanup routines which wipe out directories we assume are under
cobbler's control.

Patch switches location for download of loaders from mdehaan's
fedorapeople page to my own. Here I have added two new grub images to be
used for EFI booting, assuming you configure your dhcp server to use
them.

Includes a little dead code / style cleanup in pxegen.py.
---
 cobbler/action_check.py            |   29 +++---
 cobbler/action_dlcontent.py        |    4 +-
 cobbler/pxegen.py                  |  193 +++++++++++++++++++++++-------------
 templates/pxe/efidefault.template  |    5 +
 templates/pxe/grubprofile.template |    4 +
 5 files changed, 150 insertions(+), 85 deletions(-)
 create mode 100644 templates/pxe/efidefault.template
 create mode 100644 templates/pxe/grubprofile.template

diff --git a/cobbler/action_check.py b/cobbler/action_check.py
index 388c936..cc07af5 100644
--- a/cobbler/action_check.py
+++ b/cobbler/action_check.py
@@ -305,17 +305,23 @@ class BootCheck:
        # FIXME: move zpxe.rexx to loaders
 
        bootloaders = {
-          "elilo"      : [ "ia64",       [ "/var/lib/cobbler/loaders/elilo*.efi" ]],
-          "menu.c32"   : [ "x86/x86_64", [ "/usr/share/syslinux/menu.c32", "/usr/lib/syslinux/menu.c32", "/var/lib/cobbler/loaders/menu.c32" ]],
-          "yaboot"     : [ "ppc/ppc64",  [ "/var/lib/cobbler/loaders/yaboot*" ]],
-          "pxelinux.0" : [ "x86_64",     [ "/usr/share/syslinux/pxelinux.0", "/usr/lib/syslinux/pxelinux.0", "/var/lib/cobbler/loaders/pxelinux.0" ]]
+          "elilo"      : [ "/var/lib/cobbler/loaders/elilo*.efi" ],
+          "menu.c32"   : [ "/usr/share/syslinux/menu.c32", 
+              "/usr/lib/syslinux/menu.c32", 
+              "/var/lib/cobbler/loaders/menu.c32" ],
+          "yaboot"     : [ "/var/lib/cobbler/loaders/yaboot*" ],
+          "pxelinux.0" : [ "/usr/share/syslinux/pxelinux.0", 
+              "/usr/lib/syslinux/pxelinux.0", 
+              "/var/lib/cobbler/loaders/pxelinux.0" ],
+          "efi"        : [ "/var/lib/cobbler/loaders/grub-x86.efi", 
+              "/var/lib/cobbler/loaders/grub-x86_64.efi" ],
        }
 
        # look for bootloaders at the glob locations above
        found_bootloaders = []
        items = bootloaders.keys()
        for loader_name in items:
-          arch, patterns = bootloaders[loader_name]
+          patterns = bootloaders[loader_name]
           for pattern in patterns:
              matches = glob.glob(pattern)
              if len(matches) > 0:
@@ -327,18 +333,7 @@ class BootCheck:
           if loader_name not in found_bootloaders:
              not_found.append(loader_name)
 
-       missing = False
-       # generate warnings about what we haven't found.
-       for loader_name in not_found:
-          arch = bootloaders[loader_name][0]
-          patterns = bootloaders[loader_name][1]
-          if len(patterns) == 0:
-             pattern_str = patterns[0]
-          else:
-             pattern_str = " or ".join(patterns)
-          missing = True
-
-       if missing:
+       if len(not_found) > 0:
           status.append("some network boot-loaders are missing from /var/lib/cobbler/loaders, you may run 'cobbler get-loaders' to download them, or, if you only want to handle x86/x86_64 netbooting, you may ensure that you have installed a *recent* version of the syslinux package installed and can ignore this message entirely.  Files in this directory, should you want to support all architectures, should include pxelinux.0, menu.c32, elilo.efi, and yaboot. The 'cobbler get-loaders' command is the easiest way to resolve these requirements.")
  
    def check_tftpd_bin(self,status):
diff --git a/cobbler/action_dlcontent.py b/cobbler/action_dlcontent.py
index 0aafcd1..b3adae0 100644
--- a/cobbler/action_dlcontent.py
+++ b/cobbler/action_dlcontent.py
@@ -46,7 +46,7 @@ class ContentDownloader:
        they can still source their cross-arch bootloader content manually.
        """
 
-       content_server = "http://mdehaan.fedorapeople.org/loaders"
+       content_server = "http://dgoodwin.fedorapeople.org/loaders"
        dest = "/var/lib/cobbler/loaders"
 
        files = (
@@ -58,6 +58,8 @@ class ContentDownloader:
           ( "%s/yaboot-1.3.14-12" % content_server, "%s/yaboot" % dest),
           ( "%s/pxelinux.0-3.61" % content_server, "%s/pxelinux.0" % dest),
           ( "%s/menu.c32-3.61" % content_server, "%s/menu.c32" % dest),
+          ( "%s/grub-0.97-x86.efi" % content_server, "%s/grub-x86.efi" % dest),
+          ( "%s/grub-0.97-x86_64.efi" % content_server, "%s/grub-x86_64.efi" % dest),
        )
 
        self.logger.info("downloading content required to netboot all arches")
diff --git a/cobbler/pxegen.py b/cobbler/pxegen.py
index f2c67e6..77f4da5 100644
--- a/cobbler/pxegen.py
+++ b/cobbler/pxegen.py
@@ -55,7 +55,7 @@ class PXEGen:
     Handles building out PXE stuff
     """
 
-    def __init__(self,config,logger):
+    def __init__(self, config, logger):
         """
         Constructor
         """
@@ -84,29 +84,45 @@ class PXEGen:
         # copy syslinux from one of two locations
         try:
             try:
-                utils.copyfile_pattern('/var/lib/cobbler/loaders/pxelinux.0', dst, api=self.api, logger=self.logger)
-                utils.copyfile_pattern('/var/lib/cobbler/loaders/menu.c32', dst, api=self.api, logger=self.logger)
+                utils.copyfile_pattern('/var/lib/cobbler/loaders/pxelinux.0', 
+                        dst, api=self.api, logger=self.logger)
+                utils.copyfile_pattern('/var/lib/cobbler/loaders/menu.c32', 
+                        dst, api=self.api, logger=self.logger)
             except:
-                utils.copyfile_pattern('/usr/share/syslinux/pxelinux.0', dst, api=self.api, logger=self.logger)
-                utils.copyfile_pattern('/usr/share/syslinux/menu.c32', dst, api=self.api, logger=self.logger)
+                utils.copyfile_pattern('/usr/share/syslinux/pxelinux.0', 
+                        dst, api=self.api, logger=self.logger)
+                utils.copyfile_pattern('/usr/share/syslinux/menu.c32', 
+                        dst, api=self.api, logger=self.logger)
 
         except:
-            utils.copyfile_pattern('/usr/lib/syslinux/pxelinux.0',   dst, api=self.api, logger=self.logger)
-            utils.copyfile_pattern('/usr/lib/syslinux/menu.c32',   dst, api=self.api, logger=self.logger)
+            utils.copyfile_pattern('/usr/lib/syslinux/pxelinux.0',   
+                    dst, api=self.api, logger=self.logger)
+            utils.copyfile_pattern('/usr/lib/syslinux/menu.c32',   
+                    dst, api=self.api, logger=self.logger)
  
         # copy memtest only if we find it
-        utils.copyfile_pattern('/boot/memtest*', dst, require_match=False, api=self.api, logger=self.logger)
+        utils.copyfile_pattern('/boot/memtest*', 
+                dst, require_match=False, api=self.api, logger=self.logger)
   
         # copy elilo which we include for IA64 targets
-        utils.copyfile_pattern('/var/lib/cobbler/loaders/elilo.efi', dst, require_match=False, api=self.api, logger=self.logger)
+        utils.copyfile_pattern('/var/lib/cobbler/loaders/elilo.efi', dst, 
+                require_match=False, api=self.api, logger=self.logger)
 
         # copy yaboot which we include for PowerPC targets
-        utils.copyfile_pattern('/var/lib/cobbler/loaders/yaboot', dst, require_match=False, api=self.api, logger=self.logger)
+        utils.copyfile_pattern('/var/lib/cobbler/loaders/yaboot', dst, 
+                require_match=False, api=self.api, logger=self.logger)
 
         try:
-            utils.copyfile_pattern('/usr/lib/syslinux/memdisk',   dst, api=self.api, logger=self.logger)
+            utils.copyfile_pattern('/usr/lib/syslinux/memdisk',   
+                    dst, api=self.api, logger=self.logger)
         except:
-            utils.copyfile_pattern('/usr/share/syslinux/memdisk', dst, require_match=False, api=self.api, logger=self.logger)
+            utils.copyfile_pattern('/usr/share/syslinux/memdisk', dst, 
+                    require_match=False, api=self.api, logger=self.logger)
+
+        # Copy grub EFI bootloaders if possible:
+        utils.copyfile_pattern('/var/lib/cobbler/loaders/grub*.efi', dst, 
+                require_match=False, api=self.api, logger=self.logger)
+            
 
     def copy_images(self):
         """
@@ -231,6 +247,7 @@ class PXEGen:
             if working_arch in [ "i386", "x86", "x86_64", "standard"]:
                 # pxelinux wants a file named $name under pxelinux.cfg
                 f2 = os.path.join(self.bootloc, "pxelinux.cfg", f1)
+
             elif working_arch == "ia64":
                 # elilo expects files to be named "$name.conf" in the root
                 # and can not do files based on the MAC address
@@ -239,6 +256,7 @@ class PXEGen:
 
                 filename = "%s.conf" % utils.get_config_filename(system,interface=name)
                 f2 = os.path.join(self.bootloc, filename)
+
             elif working_arch.startswith("ppc"):
                 # Determine filename for system-specific yaboot.conf
                 filename = "%s" % utils.get_config_filename(system, interface=name).lower()
@@ -304,6 +322,9 @@ class PXEGen:
         listfile.close()
 
     def make_actual_pxe_menu(self):
+        """
+        Generates both pxe and grub boot menus.
+        """
         # only do this if there is NOT a system named default.
         default = self.systems.find(name="default")
 
@@ -322,8 +343,12 @@ class PXEGen:
         image_list = [image for image in self.images]
         image_list.sort(sort_name)
 
-        # build out the menu entries
+        # Build out menu items and append each to this master list, used for 
+        # the default menus:
         pxe_menu_items = ""
+        grub_menu_items = ""
+
+        # For now, profiles are the only items we want grub EFI boot menu entries for:
         for profile in profile_list:
             if not profile.enable_menu:
                # This profile has been excluded from the menu
@@ -333,14 +358,24 @@ class PXEGen:
             if distro.name.find("-xen") != -1 or distro.arch not in ["i386", "x86_64"]:
                 # can't PXE Xen
                 continue
-            contents = self.write_pxe_file(None,None,profile,distro,distro.arch,include_header=False)
+            contents = self.write_pxe_file(filename=None, system=None, 
+                    profile=profile, distro=distro, 
+                    arch=distro.arch, include_header=False)
             if contents is not None:
                 pxe_menu_items = pxe_menu_items + contents + "\n"
 
+            grub_contents = self.write_pxe_file(filename=None, system=None, 
+                    profile=profile, distro=distro, 
+                    arch=distro.arch, include_header=False, format="grub")
+            if grub_contents is not None:
+                grub_menu_items = grub_menu_items + grub_contents + "\n"
+
+
         # image names towards the bottom
         for image in image_list:
             if os.path.exists(image.file):
-                contents = self.write_pxe_file(None,None,None,None,image.arch,image=image)
+                contents = self.write_pxe_file(filename=None, system=None, 
+                        profile=None, distro=None, arch=image.arch, image=image)
                 if contents is not None:
                     pxe_menu_items = pxe_menu_items + contents + "\n"
 
@@ -354,15 +389,19 @@ class PXEGen:
                 contents = self.write_memtest_pxe("/%s" % base)
                 pxe_menu_items = pxe_menu_items + contents + "\n"
               
-        # save the template.
+        # Write the PXE menu:
         metadata = { "pxe_menu_items" : pxe_menu_items, "pxe_timeout_profile" : timeout_action}
         outfile = os.path.join(self.bootloc, "pxelinux.cfg", "default")
-
-        # read the default template file
         template_src = open(os.path.join(self.settings.pxe_template_dir,"pxedefault.template"))
         template_data = template_src.read()
+        self.templar.render(template_data, metadata, outfile, None)
+        template_src.close()
 
-        # write the template
+        # Write the grub menu:
+        metadata = { "grub_menu_items" : grub_menu_items }
+        outfile = os.path.join(self.bootloc, "efidefault")
+        template_src = open(os.path.join(self.settings.pxe_template_dir, "efidefault.template"))
+        template_data = template_src.read()
         self.templar.render(template_data, metadata, outfile, None)
         template_src.close()
 
@@ -398,7 +437,8 @@ class PXEGen:
         return buffer
 
 
-    def write_pxe_file(self,filename,system,profile,distro,arch,image=None,include_header=True,metadata=None):
+    def write_pxe_file(self, filename, system, profile, distro, arch, 
+            image=None, include_header=True, metadata=None, format="pxe"):
         """
         Write a configuration file for the boot loader(s).
         More system-specific configuration may come in later, if so
@@ -410,6 +450,8 @@ class PXEGen:
         short-circuited and simpler.  All of it goes through the
         templating engine, see the templates in /etc/cobbler for
         more details
+
+        Can be used for different formats, "pxe" (default) and "grub".
         """
 
         if arch is None:
@@ -504,27 +546,79 @@ class PXEGen:
 
             if arch.startswith("s390"):
                 template = os.path.join(self.settings.pxe_template_dir,"pxeprofile_s390x.template")
+            elif format == "grub":
+                template = os.path.join(self.settings.pxe_template_dir,"grubprofile.template")
             else:
                 template = os.path.join(self.settings.pxe_template_dir,"pxeprofile.template")
 
-        # now build the kernel command line
+        # generate the kernel options and append line:
+        kernel_options = self.build_kernel_options(system, profile, distro, 
+                image, arch, kickstart_path)
+        metadata["kernel_options"] = kernel_options
+
+        if metadata.has_key("initrd_path") and (not arch or arch not in ["ia64", "ppc", "ppc64"]):
+            append_line = "append initrd=%s" % (metadata["initrd_path"])
+        else:
+            append_line = "append "
+        append_line = "%s %s" % (append_line, kernel_options)
+        if arch.startswith("ppc") or arch.startswith("s390"):
+            # remove the prefix "append"
+            # TODO: this looks like it's removing more than append, really 
+            # not sure what's up here...
+            append_line = append_line[7:]
+        metadata["append_line"] = append_line
+
+        # store variables for templating
+        metadata["menu_label"] = ""
+        if profile:
+            if not arch in [ "ia64", "ppc", "ppc64", "s390", "s390x" ]:
+                metadata["menu_label"] = "MENU LABEL %s" % profile.name
+                metadata["profile_name"] = profile.name
+        elif image:
+            metadata["menu_label"] = "MENU LABEL %s" % image.name
+            metadata["profile_name"] = image.name
+
+        if system:
+            metadata["system_name"] = system.name
+
+
+        # get the template
+        if kernel_path is not None:
+            template_fh = open(template)
+            template_data = template_fh.read()
+            template_fh.close()
+        else:
+            # this is something we can't PXE boot
+            template_data = "\n"        
+
+        # save file and/or return results, depending on how called.
+        buffer = self.templar.render(template_data, metadata, None)
+        if filename is not None:
+            self.logger.info("generating: %s" % filename)
+            fd = open(filename, "w")
+            fd.write(buffer)
+            fd.close()
+        return buffer
+
+    def build_kernel_options(self, system, profile, distro, image, arch, 
+            kickstart_path):
+        """
+        Builds the full kernel options line.
+        """
+
         if system is not None:
             blended = utils.blender(self.api, True, system)
         elif profile is not None:
             blended = utils.blender(self.api, True, profile)
         else:
             blended = utils.blender(self.api, True, image)
-        kopts = blended.get("kernel_options","")
 
-        # generate the append line
+        kopts = blended.get("kernel_options", "")
         hkopts = utils.hash_to_string(kopts)
-        if metadata.has_key("initrd_path") and (not arch or arch not in ["ia64", "ppc", "ppc64"]):
-            append_line = "append initrd=%s %s" % (metadata["initrd_path"], hkopts)
-        else:
-            append_line = "append %s" % hkopts
-
+        append_line = "%s" % hkopts
         # FIXME - the append_line length limit is architecture specific
-        if len(append_line) >= 255 + len("append "):
+        # TODO: why is this checked here, before we finish adding everything?
+        if len(append_line) >= 255:
             self.logger.warning("warning: kernel option length exceeds 255")
 
         # kickstart path rewriting (get URLs for local files)
@@ -553,11 +647,11 @@ class PXEGen:
                 # interface=bootif causes a failure
                 append_line = append_line.replace("interface=bootif","")
             elif distro.breed == "vmware":
-                append_line = "%s vmkopts=debugLogToSerial:1 mem=512M ks=%s" % (append_line, kickstart_path)
+                append_line = "%s vmkopts=debugLogToSerial:1 mem=512M ks=%s" % \
+                    (append_line, kickstart_path)
                 # interface=bootif causes a failure
                 append_line = append_line.replace("ksdevice=bootif","")
 
-
         if distro is not None and (distro.breed in [ "debian", "ubuntu" ]):
             # Hostname is required as a parameter, the one in the preseed is
             # not respected, so calculate if we have one here.
@@ -587,42 +681,7 @@ class PXEGen:
             # the existence of "stable" in the dists directory
             append_line = "%s suite=%s" % (append_line, distro.os_version)
 
-        if arch.startswith("ppc") or arch.startswith("s390"):
-            # remove the prefix "append"
-            append_line = append_line[7:]
-
-        # store variables for templating
-        metadata["menu_label"] = ""
-        if profile:
-            if not arch in [ "ia64", "ppc", "ppc64", "s390", "s390x" ]:
-                metadata["menu_label"] = "MENU LABEL %s" % profile.name
-                metadata["profile_name"] = profile.name
-        elif image:
-            metadata["menu_label"] = "MENU LABEL %s" % image.name
-            metadata["profile_name"] = image.name
-
-        if system:
-            metadata["system_name"] = system.name
-
-        metadata["append_line"] = append_line
-
-        # get the template
-        if kernel_path is not None:
-            template_fh = open(template)
-            template_data = template_fh.read()
-            template_fh.close()
-        else:
-            # this is something we can't PXE boot
-            template_data = "\n"        
-
-        # save file and/or return results, depending on how called.
-        buffer = self.templar.render(template_data, metadata, None)
-        if filename is not None:
-            self.logger.info("generating: %s" % filename)
-            fd = open(filename, "w")
-            fd.write(buffer)
-            fd.close()
-        return buffer
+        return append_line
 
     def write_templates(self,obj,write_file=False,path=None):
         """
diff --git a/templates/pxe/efidefault.template b/templates/pxe/efidefault.template
new file mode 100644
index 0000000..0b1165e
--- /dev/null
+++ b/templates/pxe/efidefault.template
@@ -0,0 +1,5 @@
+default=0
+timeout=0
+
+$grub_menu_items
+
diff --git a/templates/pxe/grubprofile.template b/templates/pxe/grubprofile.template
new file mode 100644
index 0000000..3ef6735
--- /dev/null
+++ b/templates/pxe/grubprofile.template
@@ -0,0 +1,4 @@
+title $profile_name
+    root (nd)
+    kernel $kernel_path $kernel_options 
+    initrd $initrd_path
-- 
1.7.2.3

