""" 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 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() # xen distros can be ruled out as they won't boot if distro.name.find("-xen") != -1: continue 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 + '/images/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