diff options
Diffstat (limited to 'cobbler/pxegen.py')
-rw-r--r-- | cobbler/pxegen.py | 322 |
1 files changed, 322 insertions, 0 deletions
diff --git a/cobbler/pxegen.py b/cobbler/pxegen.py new file mode 100644 index 0000000..527b6a2 --- /dev/null +++ b/cobbler/pxegen.py @@ -0,0 +1,322 @@ +""" +Builds out filesystem trees/data based on the object tree. +This is the code behind 'cobbler sync'. + +Copyright 2006-2008, Red Hat, Inc +Michael DeHaan <mdehaan@redhat.com> + +This software may be freely redistributed under the terms of the GNU +general public license. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +""" + +import os +import os.path +import shutil +import time +import sub_process +import sys +import glob +import traceback +import errno + +import utils +from cexceptions import * +import templar + +import item_distro +import item_profile +import item_repo +import item_system + +from utils import _ + + +class PXEGen: + """ + Handles building out PXE stuff + """ + + def __init__(self,config): + """ + Constructor + """ + self.config = config + self.api = config.api + self.distros = config.distros() + self.profiles = config.profiles() + self.systems = config.systems() + self.settings = config.settings() + self.repos = config.repos() + self.templar = templar.Templar(config) + self.bootloc = utils.tftpboot_location() + + def copy_bootloaders(self): + """ + Copy bootloaders to the configured tftpboot directory + NOTE: we support different arch's if defined in + /var/lib/cobbler/settings. + """ + for loader in self.settings.bootloaders.keys(): + path = self.settings.bootloaders[loader] + newname = os.path.basename(path) + destpath = os.path.join(self.bootloc, newname) + utils.copyfile(path, destpath) + utils.copyfile("/var/lib/cobbler/menu.c32", os.path.join(self.bootloc, "menu.c32")) + + # Copy memtest to tftpboot if package is installed on system + for memtest in glob.glob('/boot/memtest*'): + base = os.path.basename(memtest) + utils.copyfile(memtest,os.path.join(self.bootloc,"images",base)) + + def copy_distros(self): + """ + A distro is a kernel and an initrd. Copy all of them and error + out if any files are missing. The conf file was correct if built + via the CLI or API, though it's possible files have been moved + since or perhaps they reference NFS directories that are no longer + mounted. + + NOTE: this has to be done for both tftp and http methods + """ + # copy is a 4-letter word but tftpboot runs chroot, thus it's required. + for d in self.distros: + print _("sync distro: %s") % d.name + self.copy_single_distro_files(d) + + def copy_single_distro_files(self, d): + for dirtree in [self.bootloc, self.settings.webdir]: + distros = os.path.join(dirtree, "images") + distro_dir = os.path.join(distros,d.name) + utils.mkdir(distro_dir) + kernel = utils.find_kernel(d.kernel) # full path + initrd = utils.find_initrd(d.initrd) # full path + if kernel is None or not os.path.isfile(kernel): + raise CX(_("kernel not found: %(file)s, distro: %(distro)s") % { "file" : d.kernel, "distro" : d.name }) + if initrd is None or not os.path.isfile(initrd): + raise CX(_("initrd not found: %(file)s, distro: %(distro)s") % { "file" : d.initrd, "distro" : d.name }) + b_kernel = os.path.basename(kernel) + b_initrd = os.path.basename(initrd) + if kernel.startswith(dirtree): + utils.linkfile(kernel, os.path.join(distro_dir, b_kernel)) + else: + utils.copyfile(kernel, os.path.join(distro_dir, b_kernel)) + if initrd.startswith(dirtree): + utils.linkfile(initrd, os.path.join(distro_dir, b_initrd)) + else: + utils.copyfile(initrd, os.path.join(distro_dir, b_initrd)) + + def write_all_system_files(self,system): + + profile = system.get_conceptual_parent() + if profile is None: + raise CX(_("system %(system)s references a missing profile %(profile)s") % { "system" : system.name, "profile" : system.profile}) + distro = profile.get_conceptual_parent() + if distro is None: + raise CX(_("profile %(profile)s references a missing distro %(distro)s") % { "profile" : system.profile, "distro" : profile.distro}) + + # this used to just generate a single PXE config file, but now must + # generate one record for each described NIC ... + + counter = 0 + for (name,interface) in system.interfaces.iteritems(): + + ip = interface["ip_address"] + + f1 = utils.get_config_filename(system,interface=name) + + # for tftp only ... + if distro.arch in [ "x86", "x86_64", "standard"]: + # pxelinux wants a file named $name under pxelinux.cfg + f2 = os.path.join(self.bootloc, "pxelinux.cfg", f1) + if distro.arch == "ia64": + # elilo expects files to be named "$name.conf" in the root + # and can not do files based on the MAC address + if ip is not None and ip != "": + print _("Warning: Itanium system object (%s) needs an IP address to PXE") % system.name + + filename = "%s.conf" % utils.get_config_filename(system,interface=name) + f2 = os.path.join(self.bootloc, filename) + + f3 = os.path.join(self.settings.webdir, "systems", f1) + + if system.netboot_enabled and system.is_pxe_supported(): + if distro.arch in [ "x86", "x86_64", "standard"]: + self.write_pxe_file(f2,system,profile,distro,False) + if distro.arch == "ia64": + self.write_pxe_file(f2,system,profile,distro,True) + else: + # ensure the file doesn't exist + utils.rmfile(f2) + + counter = counter + 1 + + + def make_pxe_menu(self): + # only do this if there is NOT a system named default. + default = self.systems.find(name="default") + if default is not None: + return + + fname = os.path.join(self.bootloc, "pxelinux.cfg", "default") + + # read the default template file + template_src = open("/etc/cobbler/pxedefault.template") + template_data = template_src.read() + + # sort the profiles + profile_list = [profile for profile in self.profiles] + def sort_name(a,b): + return cmp(a.name,b.name) + profile_list.sort(sort_name) + + # build out the menu entries + pxe_menu_items = "" + for profile in profile_list: + distro = profile.get_conceptual_parent() + contents = self.write_pxe_file(None,None,profile,distro,False,include_header=False) + if contents is not None: + pxe_menu_items = pxe_menu_items + contents + "\n" + + # if we have any memtest files in images, make entries for them + # after we list the profiles + memtests = glob.glob(self.bootloc + "/images/memtest*") + if len(memtests) > 0: + pxe_menu_items = pxe_menu_items + "\n\n" + for memtest in glob.glob(self.bootloc + '/memtest*'): + base = os.path.basename(memtest) + contents = self.write_memtest_pxe("/images/%s" % base) + pxe_menu_items = pxe_menu_items + contents + "\n" + + # save the template. + metadata = { "pxe_menu_items" : pxe_menu_items } + outfile = os.path.join(self.bootloc, "pxelinux.cfg", "default") + self.templar.render(template_data, metadata, outfile, None) + template_src.close() + + def write_memtest_pxe(self,filename): + """ + Write a configuration file for memtest + """ + + # just some random variables + template = None + metadata = {} + buffer = "" + + template = "/etc/cobbler/pxeprofile.template" + + # store variables for templating + metadata["menu_label"] = "MENU LABEL %s" % os.path.basename(filename) + metadata["profile_name"] = os.path.basename(filename) + metadata["kernel_path"] = "/images/%s" % os.path.basename(filename) + metadata["initrd_path"] = "" + metadata["append_line"] = "" + + # get the template + template_fh = open(template) + template_data = template_fh.read() + template_fh.close() + + # return results + buffer = self.templar.render(template_data, metadata, None) + return buffer + + + + def write_pxe_file(self,filename,system,profile,distro,is_ia64, include_header=True): + """ + Write a configuration file for the boot loader(s). + More system-specific configuration may come in later, if so + that would appear inside the system object in api.py + + NOTE: relevant to tftp only + """ + + # --- + # system might have netboot_enabled set to False (see item_system.py), if so, + # don't do anything else and flag the error condition. + if system is not None and not system.netboot_enabled: + return None + + # --- + # just some random variables + template = None + metadata = {} + buffer = "" + + # --- + # find kernel and initrd + kernel_path = os.path.join("/images",distro.name,os.path.basename(distro.kernel)) + initrd_path = os.path.join("/images",distro.name,os.path.basename(distro.initrd)) + + # Find the kickstart if we inherit from another profile + kickstart_path = utils.blender(self.api, True, profile)["kickstart"] + + # --- + # choose a template + if system is None: + template = "/etc/cobbler/pxeprofile.template" + elif not is_ia64: + template = "/etc/cobbler/pxesystem.template" + else: + template = "/etc/cobbler/pxesystem_ia64.template" + + # now build the kernel command line + if system is not None: + blended = utils.blender(self.api, True, system) + else: + blended = utils.blender(self.api, True,profile) + kopts = blended["kernel_options"] + + # generate the append line + append_line = "append %s" % utils.hash_to_string(kopts) + if not is_ia64: + append_line = "%s initrd=%s" % (append_line, initrd_path) + if len(append_line) >= 255 + len("append "): + print _("warning: kernel option length exceeds 255") + + # kickstart path rewriting (get URLs for local files) + if kickstart_path is not None and kickstart_path != "": + + if system is not None and kickstart_path.startswith("/"): + kickstart_path = "http://%s/cblr/svc/?op=ks&system=%s" % (blended["http_server"], system.name) + elif kickstart_path.startswith("/") or kickstart_path.find("/cobbler/kickstarts/") != -1: + kickstart_path = "http://%s/cblr/svc/?op=ks&profile=%s" % (blended["http_server"], profile.name) + + if distro.breed is None or distro.breed == "redhat": + append_line = "%s ks=%s" % (append_line, kickstart_path) + elif distro.breed == "suse": + append_line = "%s autoyast=%s" % (append_line, kickstart_path) + elif distro.breed == "debian": + append_line = "%s auto=true url=%s" % (append_line, kickstart_path) + append_line = append_line.replace("ksdevice","interface") + + # store variables for templating + metadata["menu_label"] = "" + if not is_ia64 and system is None: + metadata["menu_label"] = "MENU LABEL %s" % profile.name + metadata["profile_name"] = profile.name + metadata["kernel_path"] = kernel_path + metadata["initrd_path"] = initrd_path + metadata["append_line"] = append_line + + # get the template + template_fh = open(template) + template_data = template_fh.read() + template_fh.close() + + # save file and/or return results, depending on how called. + buffer = self.templar.render(template_data, metadata, None) + if filename is not None: + fd = open(filename, "w") + fd.write(buffer) + fd.close() + return buffer + + + + |