From b3d798bd603d4c1fe6bb6740b9f95630ba4ee483 Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 18 Apr 2008 18:25:14 -0400 Subject: Abstract out sync code into it's constituent parts, plus some packaging changes that I left out earlier. --- MANIFEST.in | 1 + cobbler.spec | 1 + cobbler/action_litesync.py | 14 +- cobbler/action_sync.py | 495 ++------------------------------------------- cobbler/dhcpgen.py | 196 ++++++++++++++++++ cobbler/pxegen.py | 322 +++++++++++++++++++++++++++++ cobbler/webui/master.py | 4 +- cobbler/yumgen.py | 108 ++++++++++ 8 files changed, 655 insertions(+), 486 deletions(-) create mode 100644 cobbler/dhcpgen.py create mode 100644 cobbler/pxegen.py create mode 100644 cobbler/yumgen.py diff --git a/MANIFEST.in b/MANIFEST.in index 644c0c2..3216946 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,6 +2,7 @@ include loaders/COPYING_ELILO include loaders/elilo-3.6-ia64.efi include loaders/menu.c32 include config/cobbler.conf +include config/cobbler_svc.conf include config/rsync.exclude include config/cobblerd include config/cobblerd_rotate diff --git a/cobbler.spec b/cobbler.spec index 5ebe403..2d0cc15 100644 --- a/cobbler.spec +++ b/cobbler.spec @@ -134,6 +134,7 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT %{_mandir}/man1/cobbler.1.gz /etc/init.d/cobblerd %config(noreplace) /etc/httpd/conf.d/cobbler.conf +%config(noreplace) /etc/httpd/conf.d/cobbler_svc.conf %dir /var/log/cobbler/syslog %defattr(755,root,root) diff --git a/cobbler/action_litesync.py b/cobbler/action_litesync.py index 89e24b4..c2029d4 100644 --- a/cobbler/action_litesync.py +++ b/cobbler/action_litesync.py @@ -57,7 +57,7 @@ class BootLiteSync: if distro is None: raise CX(_("error in distro lookup: %s") % name) # copy image files to images/$name in webdir & tftpboot: - self.sync.copy_single_distro_files(distro) + self.sync.pxegen.copy_single_distro_files(distro) # cascade sync kids = distro.get_children() for k in kids: @@ -78,7 +78,7 @@ class BootLiteSync: if profile is None: raise CX(_("error in profile lookup")) # rebuild the yum configuration files for any attached repos - self.sync.retemplate_yum_repos(profile,True) + self.sync.yumgen.retemplate_yum_repos(profile,True) # cascade sync kids = profile.get_children() for k in kids: @@ -97,7 +97,7 @@ class BootLiteSync: system = self.systems.find(name=name) if system is None: raise CX(_("error in system lookup for %s") % name) - self.sync.write_all_system_files(system) + self.sync.pxegen.write_all_system_files(system) def add_single_system(self, name): # get the system object: @@ -105,12 +105,12 @@ class BootLiteSync: if system is None: raise CX(_("error in system lookup for %s") % name) # rebuild system_list file in webdir - self.sync.regen_ethers() # /etc/ethers, for dnsmasq & rarpd - self.sync.regen_hosts() # /var/lib/cobbler/cobbler_hosts, pretty much for dnsmasq + self.sync.dhcpgen.regen_ethers() # /etc/ethers, for dnsmasq & rarpd + self.sync.dhcpgen.regen_hosts() # /var/lib/cobbler/cobbler_hosts, pretty much for dnsmasq # write the PXE files for the system - self.sync.write_all_system_files(system) + self.sync.pxegen.write_all_system_files(system) # per system kickstarts - self.sync.retemplate_yum_repos(system,False) + self.sync.yumgen.retemplate_yum_repos(system,False) def remove_single_system(self, name): bootloc = utils.tftpboot_location() diff --git a/cobbler/action_sync.py b/cobbler/action_sync.py index aa53d12..4156ea7 100644 --- a/cobbler/action_sync.py +++ b/cobbler/action_sync.py @@ -27,7 +27,9 @@ import errno import utils from cexceptions import * import templar -import kickgen +import pxegen +import dhcpgen +import yumgen import item_distro import item_profile @@ -57,7 +59,9 @@ class BootSync: self.settings = config.settings() self.repos = config.repos() self.templar = templar.Templar(config) - self.kickgen = kickgen.KickGen(config) + self.pxegen = pxegen.PXEGen(config) + self.dhcpgen = dhcpgen.DHCPGen(config) + self.yumgen = yumgen.YumGen(config) self.bootloc = utils.tftpboot_location() def run(self): @@ -71,195 +75,35 @@ class BootSync: # run pre-triggers... utils.run_triggers(None, "/var/lib/cobbler/triggers/sync/pre/*") - # in case the pre-trigger modified any objects... + # (paranoid) in case the pre-trigger modified any objects... self.api.deserialize() self.distros = self.config.distros() self.profiles = self.config.profiles() self.systems = self.config.systems() self.settings = self.config.settings() self.repos = self.config.repos() + self.pxegen = pxegen.PXEGen(self.config) + self.dhcpgen = dhcpgen.DHCPGen(self.config) + self.yumgen = yumgen.YumGen(self.config) # execute the core of the sync operation self.clean_trees() - self.copy_bootloaders() - self.copy_distros() + self.pxegen.copy_bootloaders() + self.pxegen.copy_distros() for x in self.systems: - self.write_all_system_files(x) - self.retemplate_all_yum_repos() + self.pxegen.write_all_system_files(x) + self.yumgen.retemplate_all_yum_repos() if self.settings.manage_dhcp: # these functions DRT for ISC or dnsmasq - self.write_dhcp_file() - self.regen_ethers() - self.regen_hosts() - self.make_pxe_menu() + self.dhcpgen.write_dhcp_file() + self.dhcpgen.regen_ethers() + self.dhcpgen.regen_hosts() + self.pxegen.make_pxe_menu() # run post-triggers utils.run_triggers(None, "/var/lib/cobbler/triggers/sync/post/*") return True - 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 write_dhcp_file(self): - """ - DHCP files are written when manage_dhcp is set in - /var/lib/cobbler/settings. - """ - - settings_file = self.settings.dhcpd_conf - template_file = "/etc/cobbler/dhcp.template" - mode = self.settings.manage_dhcp_mode.lower() - if mode == "dnsmasq": - settings_file = self.settings.dnsmasq_conf - template_file = "/etc/cobbler/dnsmasq.template" - - try: - f2 = open(template_file,"r") - except: - raise CX(_("error writing template to file: %s") % template_file) - template_data = "" - template_data = f2.read() - f2.close() - - # build each per-system definition - # as configured, this only works for ISC, patches accepted - # from those that care about Itanium. elilo seems to be unmaintained - # so additional maintaince in other areas may be required to keep - # this working. - - elilo = os.path.basename(self.settings.bootloaders["ia64"]) - - system_definitions = {} - counter = 0 - - # we used to just loop through each system, but now we must loop - # through each network interface of each system. - - for system in self.systems: - profile = system.get_conceptual_parent() - distro = profile.get_conceptual_parent() - for (name, interface) in system.interfaces.iteritems(): - - mac = interface["mac_address"] - ip = interface["ip_address"] - host = interface["hostname"] - - if mac is None or mac == "": - # can't write a DHCP entry for this system - continue - - counter = counter + 1 - systxt = "" - - if mode == "isc": - - # the label the entry after the hostname if possible - if host is not None and host != "": - systxt = "\nhost %s {\n" % host - if self.settings.isc_set_host_name: - systxt = systxt + " option host-name = %s;\n" % host - else: - systxt = "\nhost generic%d {\n" % counter - - if distro.arch == "ia64": - # can't use pxelinux.0 anymore - systxt = systxt + " filename \"/%s\";\n" % elilo - systxt = systxt + " hardware ethernet %s;\n" % mac - if ip is not None and ip != "": - systxt = systxt + " fixed-address %s;\n" % ip - systxt = systxt + "}\n" - - else: - # dnsmasq. don't have to write IP and other info here, but we do tag - # each MAC based on the arch of it's distro, if it needs something other - # than pxelinux.0 -- for these arches, and these arches only, a dnsmasq - # reload (full "cobbler sync") would be required after adding the system - # to cobbler, just to tag this relationship. - - if ip is not None and ip != "": - if distro.arch.lower() == "ia64": - systxt = "dhcp-host=net:ia64," + ip + "\n" - # support for other arches needs modifications here - else: - systxt = "" - - dhcp_tag = interface["dhcp_tag"] - if dhcp_tag == "": - dhcp_tag = "default" - - if not system_definitions.has_key(dhcp_tag): - system_definitions[dhcp_tag] = "" - system_definitions[dhcp_tag] = system_definitions[dhcp_tag] + systxt - - # we are now done with the looping through each interface of each system - - metadata = { - "insert_cobbler_system_definitions" : system_definitions.get("default",""), - "date" : time.asctime(time.gmtime()), - "cobbler_server" : self.settings.server, - "next_server" : self.settings.next_server, - "elilo" : elilo - } - - # now add in other DHCP expansions that are not tagged with "default" - for x in system_definitions.keys(): - if x == "default": - continue - metadata["insert_cobbler_system_definitions_%s" % x] = system_definitions[x] - - self.templar.render(template_data, metadata, settings_file, None) - - def regen_ethers(self): - # dnsmasq knows how to read this database of MACs -> IPs, so we'll keep it up to date - # every time we add a system. - # read 'man ethers' for format info - fh = open("/etc/ethers","w+") - for sys in self.systems: - for (name, interface) in sys.interfaces.iteritems(): - mac = interface["mac_address"] - ip = interface["ip_address"] - if mac is None or mac == "": - # can't write this w/o a MAC address - continue - if ip is not None and ip != "": - fh.write(mac.upper() + "\t" + ip + "\n") - fh.close() - - def regen_hosts(self): - # dnsmasq knows how to read this database for host info - # (other things may also make use of this later) - fh = open("/var/lib/cobbler/cobbler_hosts","w+") - for sys in self.systems: - for (name, interface) in sys.interfaces.iteritems(): - mac = interface["mac_address"] - host = interface["hostname"] - ip = interface["ip_address"] - if mac is None or mac == "": - continue - if host is not None and host != "" and ip is not None and ip != "": - fh.write(ip + "\t" + host + "\n") - fh.close() - - - #def templatify(self, data, metadata, outfile): - # for x in metadata.keys(): - # template_data = template_data.replace("$%s" % x, metadata[x]) - def clean_trees(self): """ Delete any previously built pxelinux.cfg tree and virt tree info and then create @@ -288,307 +132,4 @@ class BootSync: utils.rmtree_contents(os.path.join(self.bootloc, "pxelinux.cfg")) utils.rmtree_contents(os.path.join(self.bootloc, "images")) - 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 retemplate_all_yum_repos(self): - for p in self.profiles: - self.retemplate_yum_repos(p,True) - for system in self.systems: - self.retemplate_yum_repos(system,False) - - def retemplate_yum_repos(self,obj,is_profile): - """ - Yum repository management files are in self.settings.webdir/repo_mirror/$name/config.repo - and also potentially in listed in the source_repos structure of the distro object, however - these files have server URLs in them that must be templated out. This function does this. - """ - blended = utils.blender(self.api, False, obj) - - if is_profile: - outseg = "repos_profile" - else: - outseg = "repos_system" - - input_files = [] - - # chance old versions from upgrade do not have a source_repos - # workaround for user bug - if not blended.has_key("source_repos"): - blended["source_repos"] = [] - - # tack on all the install source repos IF there is more than one. - # this is basically to support things like RHEL5 split trees - # if there is only one, then there is no need to do this. - - for r in blended["source_repos"]: - filename = self.settings.webdir + "/" + "/".join(r[0].split("/")[4:]) - input_files.append(filename) - - for repo in blended["repos"]: - input_files.append(os.path.join(self.settings.webdir, "repo_mirror", repo, "config.repo")) - - for infile in input_files: - if infile.find("ks_mirror") == -1: - dispname = infile.split("/")[-2] - else: - dispname = infile.split("/")[-1].replace(".repo","") - confdir = os.path.join(self.settings.webdir, outseg) - outdir = os.path.join(confdir, blended["name"]) - utils.mkdir(outdir) - try: - infile_h = open(infile) - except: - print _("WARNING: cobbler reposync needs to be run on repo (%s), then re-run cobbler sync") % dispname - continue - infile_data = infile_h.read() - infile_h.close() - outfile = os.path.join(outdir, "%s.repo" % (dispname)) - self.templar.render(infile_data, blended, outfile, None) - - - 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 - - - diff --git a/cobbler/dhcpgen.py b/cobbler/dhcpgen.py new file mode 100644 index 0000000..0ec3dda --- /dev/null +++ b/cobbler/dhcpgen.py @@ -0,0 +1,196 @@ +""" +Builds out DHCP info +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 DHCPGen: + """ + Handles conversion of internal state to the tftpboot tree layout + """ + + def __init__(self,config,verbose=False): + """ + Constructor + """ + self.verbose = verbose + 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) + + def write_dhcp_file(self): + """ + DHCP files are written when manage_dhcp is set in + /var/lib/cobbler/settings. + """ + + settings_file = self.settings.dhcpd_conf + template_file = "/etc/cobbler/dhcp.template" + mode = self.settings.manage_dhcp_mode.lower() + if mode == "dnsmasq": + settings_file = self.settings.dnsmasq_conf + template_file = "/etc/cobbler/dnsmasq.template" + + try: + f2 = open(template_file,"r") + except: + raise CX(_("error writing template to file: %s") % template_file) + template_data = "" + template_data = f2.read() + f2.close() + + # build each per-system definition + # as configured, this only works for ISC, patches accepted + # from those that care about Itanium. elilo seems to be unmaintained + # so additional maintaince in other areas may be required to keep + # this working. + + elilo = os.path.basename(self.settings.bootloaders["ia64"]) + + system_definitions = {} + counter = 0 + + # we used to just loop through each system, but now we must loop + # through each network interface of each system. + + for system in self.systems: + profile = system.get_conceptual_parent() + distro = profile.get_conceptual_parent() + for (name, interface) in system.interfaces.iteritems(): + + mac = interface["mac_address"] + ip = interface["ip_address"] + host = interface["hostname"] + + if mac is None or mac == "": + # can't write a DHCP entry for this system + continue + + counter = counter + 1 + systxt = "" + + if mode == "isc": + + # the label the entry after the hostname if possible + if host is not None and host != "": + systxt = "\nhost %s {\n" % host + if self.settings.isc_set_host_name: + systxt = systxt + " option host-name = %s;\n" % host + else: + systxt = "\nhost generic%d {\n" % counter + + if distro.arch == "ia64": + # can't use pxelinux.0 anymore + systxt = systxt + " filename \"/%s\";\n" % elilo + systxt = systxt + " hardware ethernet %s;\n" % mac + if ip is not None and ip != "": + systxt = systxt + " fixed-address %s;\n" % ip + systxt = systxt + "}\n" + + else: + # dnsmasq. don't have to write IP and other info here, but we do tag + # each MAC based on the arch of it's distro, if it needs something other + # than pxelinux.0 -- for these arches, and these arches only, a dnsmasq + # reload (full "cobbler sync") would be required after adding the system + # to cobbler, just to tag this relationship. + + if ip is not None and ip != "": + if distro.arch.lower() == "ia64": + systxt = "dhcp-host=net:ia64," + ip + "\n" + # support for other arches needs modifications here + else: + systxt = "" + + dhcp_tag = interface["dhcp_tag"] + if dhcp_tag == "": + dhcp_tag = "default" + + if not system_definitions.has_key(dhcp_tag): + system_definitions[dhcp_tag] = "" + system_definitions[dhcp_tag] = system_definitions[dhcp_tag] + systxt + + # we are now done with the looping through each interface of each system + + metadata = { + "insert_cobbler_system_definitions" : system_definitions.get("default",""), + "date" : time.asctime(time.gmtime()), + "cobbler_server" : self.settings.server, + "next_server" : self.settings.next_server, + "elilo" : elilo + } + + # now add in other DHCP expansions that are not tagged with "default" + for x in system_definitions.keys(): + if x == "default": + continue + metadata["insert_cobbler_system_definitions_%s" % x] = system_definitions[x] + + self.templar.render(template_data, metadata, settings_file, None) + + def regen_ethers(self): + # dnsmasq knows how to read this database of MACs -> IPs, so we'll keep it up to date + # every time we add a system. + # read 'man ethers' for format info + fh = open("/etc/ethers","w+") + for sys in self.systems: + for (name, interface) in sys.interfaces.iteritems(): + mac = interface["mac_address"] + ip = interface["ip_address"] + if mac is None or mac == "": + # can't write this w/o a MAC address + continue + if ip is not None and ip != "": + fh.write(mac.upper() + "\t" + ip + "\n") + fh.close() + + def regen_hosts(self): + # dnsmasq knows how to read this database for host info + # (other things may also make use of this later) + fh = open("/var/lib/cobbler/cobbler_hosts","w+") + for sys in self.systems: + for (name, interface) in sys.interfaces.iteritems(): + mac = interface["mac_address"] + host = interface["hostname"] + ip = interface["ip_address"] + if mac is None or mac == "": + continue + if host is not None and host != "" and ip is not None and ip != "": + fh.write(ip + "\t" + host + "\n") + fh.close() + + 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 + +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 + + + + diff --git a/cobbler/webui/master.py b/cobbler/webui/master.py index 0eec178..14f06b5 100644 --- a/cobbler/webui/master.py +++ b/cobbler/webui/master.py @@ -33,8 +33,8 @@ VFN=valueForName currentTime=time.time __CHEETAH_version__ = '2.0.1' __CHEETAH_versionTuple__ = (2, 0, 1, 'final', 0) -__CHEETAH_genTime__ = 1208545841.2024181 -__CHEETAH_genTimestamp__ = 'Fri Apr 18 15:10:41 2008' +__CHEETAH_genTime__ = 1208557454.7957201 +__CHEETAH_genTimestamp__ = 'Fri Apr 18 18:24:14 2008' __CHEETAH_src__ = 'webui_templates/master.tmpl' __CHEETAH_srcLastModified__ = 'Fri Feb 15 14:47:43 2008' __CHEETAH_docstring__ = 'Autogenerated by CHEETAH: The Python-Powered Template Engine' diff --git a/cobbler/yumgen.py b/cobbler/yumgen.py new file mode 100644 index 0000000..e39a72e --- /dev/null +++ b/cobbler/yumgen.py @@ -0,0 +1,108 @@ +""" +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 YumGen: + + 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) + + def retemplate_all_yum_repos(self): + for p in self.profiles: + self.retemplate_yum_repos(p,True) + for system in self.systems: + self.retemplate_yum_repos(system,False) + + def retemplate_yum_repos(self,obj,is_profile): + """ + Yum repository management files are in self.settings.webdir/repo_mirror/$name/config.repo + and also potentially in listed in the source_repos structure of the distro object, however + these files have server URLs in them that must be templated out. This function does this. + """ + blended = utils.blender(self.api, False, obj) + + if is_profile: + outseg = "repos_profile" + else: + outseg = "repos_system" + + input_files = [] + + # chance old versions from upgrade do not have a source_repos + # workaround for user bug + if not blended.has_key("source_repos"): + blended["source_repos"] = [] + + # tack on all the install source repos IF there is more than one. + # this is basically to support things like RHEL5 split trees + # if there is only one, then there is no need to do this. + + for r in blended["source_repos"]: + filename = self.settings.webdir + "/" + "/".join(r[0].split("/")[4:]) + input_files.append(filename) + + for repo in blended["repos"]: + input_files.append(os.path.join(self.settings.webdir, "repo_mirror", repo, "config.repo")) + + for infile in input_files: + if infile.find("ks_mirror") == -1: + dispname = infile.split("/")[-2] + else: + dispname = infile.split("/")[-1].replace(".repo","") + confdir = os.path.join(self.settings.webdir, outseg) + outdir = os.path.join(confdir, blended["name"]) + utils.mkdir(outdir) + try: + infile_h = open(infile) + except: + print _("WARNING: cobbler reposync needs to be run on repo (%s), then re-run cobbler sync") % dispname + continue + infile_data = infile_h.read() + infile_h.close() + outfile = os.path.join(outdir, "%s.repo" % (dispname)) + self.templar.render(infile_data, blended, outfile, None) + + -- cgit