From 0c0439d0a5e6f9b736f4dec60436799257e7dfde Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Tue, 13 Jan 2009 15:18:54 -0500 Subject: Bill Peck's patch adding a new power type. --- cobbler/action_import.py | 135 +++++++++++++++++++++-- cobbler/action_power.py | 1 + cobbler/action_reposync.py | 18 +++ cobbler/action_sync.py | 3 +- cobbler/config.py | 7 ++ cobbler/item_image.py | 21 ++-- cobbler/item_profile.py | 6 + cobbler/item_system.py | 8 +- cobbler/modules/cli_system.py | 2 +- cobbler/pxegen.py | 227 +++++++++++++++++++++++++++++++++++++- cobbler/settings.py | 3 + cobbler/test_basic.py | 11 +- cobbler/utils.py | 44 ++++++++ setup.py | 1 + triggers/restart-services.trigger | 5 + webui_templates/system_edit.tmpl | 2 +- 16 files changed, 464 insertions(+), 30 deletions(-) diff --git a/cobbler/action_import.py b/cobbler/action_import.py index 07fce390..24738cb0 100644 --- a/cobbler/action_import.py +++ b/cobbler/action_import.py @@ -104,7 +104,7 @@ class Importer: if self.kickstart_file and not self.breed: raise CX(_("Kickstart file can only be specified when a specific breed is selected")) - if self.breed and self.breed.lower() not in [ "redhat", "debian", "ubuntu" ]: + if self.breed and self.breed.lower() not in [ "redhat", "debian", "ubuntu", "windows" ]: raise CX(_("Supplied import breed is not supported")) # if --arch is supplied, make sure the user is not importing a path with a different @@ -376,10 +376,10 @@ class Importer: """ This is an os.path.walk routine that looks for potential yum repositories - to be added to the configuration for post-install usage. + to be added to the configuration for post-install usage. """ - matches = {} + matches = {} for x in fnames: if x == "base" or x == "repodata": print "- processing repo at : %s" % dirname @@ -398,7 +398,7 @@ class Importer: # ======================================================================================= - + def process_comps_file(self, comps_path, distro): """ @@ -448,9 +448,9 @@ class Importer: fname = os.path.join(self.settings.webdir, "ks_mirror", "config", "%s-%s.repo" % (distro.name, counter)) repo_url = "http://@@http_server@@/cobbler/ks_mirror/config/%s-%s.repo" % (distro.name, counter) - - repo_url2 = "http://@@http_server@@/cobbler/ks_mirror/%s" % (urlseg) - + + repo_url2 = "http://@@http_server@@/cobbler/ks_mirror/%s" % (urlseg) + distro.source_repos.append([repo_url,repo_url2]) # NOTE: the following file is now a Cheetah template, so it can be remapped @@ -487,29 +487,33 @@ class Importer: except: print _("- error launching createrepo, ignoring...") traceback.print_exc() - + # ======================================================================== def distro_adder(self,foo,dirname,fnames): - + """ This is an os.path.walk routine that finds distributions in the directory to be scanned and then creates them. """ - # FIXME: If there are more than one kernel or initrd image on the same directory, + # FIXME: If there are more than one kernel or initrd image on the same directory, # results are unpredictable initrd = None kernel = None - + + # Windows Variables + startrom = None + setupldr = None + for x in fnames: fullname = os.path.join(dirname,x) if os.path.islink(fullname) and os.path.isdir(fullname): if fullname.startswith(self.path): - # Prevent infinite loop with Sci Linux 5 + # Prevent infinite loop with Sci Linux 5 print "- warning: avoiding symlink loop" continue print "- following symlink: %s" % fullname @@ -519,6 +523,10 @@ class Importer: initrd = os.path.join(dirname,x) if ( x.startswith("vmlinuz") or x.startswith("kernel.img") ) and x.find("initrd") == -1: kernel = os.path.join(dirname,x) + if x.lower().startswith("startrom.n1_"): + startrom = os.path.join(dirname,x) + if x.lower().startswith("setupldr.ex_"): + setupldr = os.path.join(dirname,x) if initrd is not None and kernel is not None and dirname.find("isolinux") == -1: adtl = self.add_entry(dirname,kernel,initrd) if adtl != None: @@ -526,6 +534,10 @@ class Importer: # Not resetting these values causes problems importing debian media because there are remaining items in fnames initrd = None kernel = None + elif startrom is not None and setupldr is not None: + self.add_win_entry(dirname,startrom,setupldr) + startrom = None + setupldr = None # ======================================================================== @@ -649,6 +661,86 @@ class Importer: # ======================================================================== + def add_win_entry(self, dirname, startrom, setupldr): + + proposed_name = self.get_proposed_name(dirname) + proposed_arch = self.get_proposed_arch(dirname) + if self.arch and proposed_arch and self.arch != proposed_arch: + raise CX(_("Arch from pathname (%s) does not match with supplied one %s")%(proposed_arch,self.arch)) + + importer = import_factory(dirname,self.path) + if self.breed and self.breed != importer.breed: + raise CX( _("Requested breed (%s); breed found is %s") % ( self.breed , breed ) ) + + #archs = importer.learn_arch_from_tree() + #if self.arch and self.arch not in archs: + # raise CX(_("Given arch (%s) not found on imported tree %s")%(self.arch,importer.get_pkgdir())) + #if proposed_arch: + # if proposed_arch not in archs: + # print _("Warning: arch from pathname (%s) not found on imported tree %s") % (proposed_arch,importer.get_pkgdir()) + # return + # + # archs = [ proposed_arch ] + + archs = [ proposed_arch ] + + if len(archs)>1: + print _("- Warning : Multiple archs found : %s") % (archs) + + distros_added = [] + + for pxe_arch in archs: + + name = proposed_name + "-" + pxe_arch + existing_distro = self.distros.find(name=name) + + if existing_distro is not None: + print _("- warning: skipping import, as distro name already exists: %s") % name + continue + + else: + print _("- creating new distro: %s") % name + distro = self.config.new_distro() + + distro.set_name(name) + distro.set_kernel(startrom) + distro.set_initrd(setupldr) + distro.set_arch(pxe_arch) + distro.set_breed(importer.breed) + distro.source_repos = [] + + self.distros.add(distro,save=True) + distros_added.append(distro) + + existing_profile = self.profiles.find(name=name) + + # see if the profile name is already used, if so, skip it and + # do not modify the existing profile + + if existing_profile is None: + print _("- creating new profile: %s") % name + profile = self.config.new_profile() + else: + print _("- skipping existing profile, name already exists: %s") % name + continue + + # save our minimal profile which just points to the distribution and a good + # default answer file + + profile.set_name(name) + profile.set_distro(name) + if self.kickstart_file: + profile.set_kickstart(self.kickstart_file) + + # save our new profile to the collection + + self.profiles.add(profile,save=True) + + self.api.serialize() + return distros_added + + # ======================================================================== + def get_proposed_name(self,dirname): """ @@ -737,6 +829,7 @@ def guess_breed(kerneldir,path): [ 'Fedora' , "redhat" ], [ 'Server' , "redhat" ], [ 'Client' , "redhat" ], + [ 'setup.exe' , "windows" ], ] guess = None @@ -789,6 +882,8 @@ def import_factory(kerneldir,path): return DebianImporter(rootdir) elif breed == "ubuntu": return UbuntuImporter(rootdir) + elif breed == "windows": + return WindowsImporter(rootdir) elif breed: raise CX(_("Unknown breed %s")%breed) else: @@ -1160,3 +1255,19 @@ class UbuntuImporter ( DebianImporter ) : return os_version , "/var/lib/cobbler/kickstarts/sample.seed" + +class WindowsImporter( BaseImporter ): + def __init__(self,(rootdir,pkgdir)): + self.breed = "windows" + self.rootdir = rootdir + self.pkgdir = "i386" + + def set_variance(self, flavor, major, minor, arch): + # TODO: figure out how to determine if this media is SP1/SP2/etc. + # Assuming no service pack for now (SP zero?) + + if flavor == "XP": + return "SP0", "/var/lib/cobbler/kickstarts/winxp-default.sif" + + def match_kernelarch_file(self, filename): + return True diff --git a/cobbler/action_power.py b/cobbler/action_power.py index 9db4535c..8f92cc58 100644 --- a/cobbler/action_power.py +++ b/cobbler/action_power.py @@ -137,6 +137,7 @@ class PowerTool: "lpar" : os.path.join(powerdir,"power_lpar.template"), "bladecenter": os.path.join(powerdir,"power_bladecenter.template"), "virsh" : os.path.join(powerdir,"power_virsh.template"), + "integrity" : os.path.join(powerdir,"power_integrity.template"), } result = map.get(self.system.power_type, "") diff --git a/cobbler/action_reposync.py b/cobbler/action_reposync.py index 4bb484c5..14ef1112 100644 --- a/cobbler/action_reposync.py +++ b/cobbler/action_reposync.py @@ -345,6 +345,24 @@ class RepoSync: if rc !=0: raise CX(_("cobbler reposync failed")) + repodata_path = os.path.join(dest_path, "repodata") + + if not os.path.exists("/usr/bin/wget"): + raise CX(_("no /usr/bin/wget found, please install wget")) + + cmd2 = "/usr/bin/wget -q %s/repodata/comps.xml -O /dev/null" % (repo_mirror) + rc = sub_process.call(cmd2, shell=True, close_fds=True) + if rc == 0: + if not os.path.isdir(repodata_path): + os.makedirs(repodata_path) + + cmd2 = "/usr/bin/wget -q %s/repodata/comps.xml -O %s/comps.xml" % (repo_mirror, repodata_path) + print _("- %s") % cmd2 + + rc = sub_process.call(cmd2, shell=True, close_fds=True) + if rc !=0: + raise CX(_("wget failed")) + # now run createrepo to rebuild the index if repo.mirror_locally: diff --git a/cobbler/action_sync.py b/cobbler/action_sync.py index 4b334044..1ba91656 100644 --- a/cobbler/action_sync.py +++ b/cobbler/action_sync.py @@ -116,7 +116,7 @@ class BootSync: if self.verbose: print "- copying images" self.pxegen.copy_images() - + self.pxegen.generate_windows_files() for x in self.systems: if self.verbose: print "- copying files for system: %s" % x.name @@ -136,6 +136,7 @@ class BootSync: if self.verbose: print "- generating PXE menu structure" self.pxegen.make_pxe_menu() + self.pxegen.write_tftpd_rules(True) # run post-triggers if self.verbose: diff --git a/cobbler/config.py b/cobbler/config.py index ec43f4a9..78a4d7ca 100644 --- a/cobbler/config.py +++ b/cobbler/config.py @@ -24,6 +24,7 @@ import os import weakref import time import random +import string import binascii import item_distro as distro @@ -87,6 +88,12 @@ class Config: data = "%s%s" % (time.time(), random.uniform(1,9999999)) return binascii.b2a_base64(data).replace("=","").strip() + def generate_random_id(self,length=8): + """ + Return a random string using ASCII 0..9 and A..z + """ + return string.join(random.Random().sample(string.letters+string.digits, length),'') + def __cmp(self,a,b): return cmp(a.name,b.name) diff --git a/cobbler/item_image.py b/cobbler/item_image.py index b11dc9c0..5a4123b2 100644 --- a/cobbler/item_image.py +++ b/cobbler/item_image.py @@ -119,26 +119,33 @@ class Image(item.Item): * hostname:/path/to/the/filename.ext * /path/to/the/filename.ext """ - print "STARTING WITH FILENAME: %s" % filename uri = "" - auth = hostname = path = "" + scheme = auth = hostname = path = "" # we'll discard the protocol if it's supplied, for legacy support if filename.find("://") != -1: - ignored, uri = filename.split("://") + scheme, uri = filename.split("://") filename = uri else: uri = filename if filename.find("@") != -1: auth, filename = filename.split("@") + # extract the hostname + # 1. if we have a colon, then everything before it is a hostname + # 2. if we don't have a colon, then check if we had a scheme; if + # we did, then grab all before the first forward slash as the + # hostname; otherwise, we've got a bad file if filename.find(":") != -1: hostname, filename = filename.split(":") elif filename[0] != '/': - index = filename.find("/") - hostname = filename[:index] - filename = filename[index:] + if len(scheme) > 0: + index = filename.find("/") + hostname = filename[:index] + filename = filename[index:] + else: + raise CX(_("invalid file: %s" % filename)) # raise an exception if we don't have a valid path - if filename[0] != '/': + if len(filename) > 0 and filename[0] != '/': raise CX(_("file contains an invalid path: %s" % filename)) if filename.find("/") != -1: path, filename = filename.rsplit("/", 1) diff --git a/cobbler/item_profile.py b/cobbler/item_profile.py index cdc8fce2..d30eb52a 100644 --- a/cobbler/item_profile.py +++ b/cobbler/item_profile.py @@ -44,6 +44,7 @@ class Profile(item.Item): """ self.name = None self.uid = "" + self.random_id = "" self.owners = self.settings.default_ownership self.distro = (None, '<>')[is_subobject] self.enable_menu = (self.settings.enable_menu, '<>')[is_subobject] @@ -132,6 +133,10 @@ class Profile(item.Item): if self.uid == '': self.uid = self.config.generate_uid() + self.random_id = self.load_item(seed_data,'random_id','') + if self.random_id == '' or len(self.random_id) != 4: + self.random_id = self.config.generate_random_id(4) + return self def set_parent(self,parent_name): @@ -300,6 +305,7 @@ class Profile(item.Item): 'mtime' : self.mtime, 'name_servers' : self.name_servers, 'uid' : self.uid, + 'random_id' : self.random_id, 'redhat_management_key' : self.redhat_management_key } diff --git a/cobbler/item_system.py b/cobbler/item_system.py index a4cbd2a5..4939fa02 100644 --- a/cobbler/item_system.py +++ b/cobbler/item_system.py @@ -65,6 +65,7 @@ class System(item.Item): self.ctime = 0 self.mtime = 0 self.uid = "" + self.random_id = "" self.power_type = self.settings.power_management_default_type self.power_address = "" self.power_user = "" @@ -197,6 +198,10 @@ interface. self.uid = self.load_item(seed_data,'uid','') if self.uid == '': self.uid = self.config.generate_uid() + + self.random_id = self.load_item(seed_data,'random_id','') + if self.random_id == '' or len(self.random_id) != 4: + self.random_id = self.config.generate_random_id(4) # power management integration features @@ -592,7 +597,7 @@ interface. if power_type is None: power_type = "" power_type = power_type.lower() - valid = "bullpap wti apc_snmp ether-wake ipmilan drac ipmitool ilo rsai lpar bladecenter virsh none" + valid = "bullpap wti apc_snmp ether-wake ipmilan drac ipmitool ilo rsai lpar bladecenter virsh integrity none" choices = valid.split(" ") choices.sort() if power_type not in choices: @@ -632,6 +637,7 @@ interface. return { 'name' : self.name, 'uid' : self.uid, + 'random_id' : self.random_id, 'kernel_options' : self.kernel_options, 'kernel_options_post' : self.kernel_options_post, 'depth' : self.depth, diff --git a/cobbler/modules/cli_system.py b/cobbler/modules/cli_system.py index 4805f9c9..0c6499d7 100644 --- a/cobbler/modules/cli_system.py +++ b/cobbler/modules/cli_system.py @@ -101,7 +101,7 @@ class SystemFunction(commands.CobblerFunction): if not self.matches_args(args,["dumpvars","remove","report","getks","list"]): p.add_option("--power-pass", dest="power_pass", help="password for power management interface") if not self.matches_args(args,["dumpvars","poweron","poweroff","reboot","remove","report","getks","list"]): - p.add_option("--power-type", dest="power_type", help="one of: none, apc_snmp, bullpap, drac, ether-wake, ilo, ipmilan, ipmitool, wti, lpar, bladecenter, virsh") + p.add_option("--power-type", dest="power_type", help="one of: none, apc_snmp, bullpap, drac, ether-wake, ilo, ipmilan, ipmitool, wti, lpar, bladecenter, virsh, integrity") if not self.matches_args(args,["dumpvars","remove","report","getks","list"]): p.add_option("--power-user", dest="power_user", help="username for power management interface, if required") diff --git a/cobbler/pxegen.py b/cobbler/pxegen.py index a55f7c6a..7d256d95 100644 --- a/cobbler/pxegen.py +++ b/cobbler/pxegen.py @@ -29,6 +29,8 @@ import sys import glob import traceback import errno +import sub_process +import string import utils from cexceptions import * @@ -109,6 +111,9 @@ class PXEGen: """ errors = list() for d in self.distros: + # we don't copy the files for windows distros + if d.breed == "windows": + continue try: if self.verbose: print "- copying files for distro: %s" % d.name @@ -174,6 +179,149 @@ class PXEGen: utils.linkfile(filename, newfile, api=self.api, verbose=self.verbose) return True + def generate_windows_files(self): + utils.mkdir("/tftpboot/profiles") + for p in self.profiles: + distro = p.get_conceptual_parent() + if distro and distro.breed != "windows": + continue + else: + self.generate_windows_profile_pxe(p) + utils.mkdir("/tftpboot/systems") + for s in self.systems: + profile = s.get_conceptual_parent() + if profile: + distro = profile.get_conceptual_parent() + if distro and distro.breed != "windows": + continue + else: + self.generate_windows_system_pxe(s) + + def generate_windows_profile_pxe(self, profile): + distro = profile.get_conceptual_parent() + + dest_dir = os.path.join("/tftpboot/profiles", profile.name) + utils.mkdir(dest_dir) + + utils.cabextract(distro.kernel, dest_dir, self.api) + + src_file = os.path.join(dest_dir, "startrom.n12") + dest_file = os.path.join(dest_dir, "winpxe.0") + cmd = [ "/bin/sed", "-i", "-e", "s/ntldr/L%s/gi" % profile.random_id, src_file ] + sub_process.call(cmd, shell=False, close_fds=False) + os.rename(src_file, dest_file) + + utils.cabextract(distro.initrd, dest_dir, self.api) + + src_file = os.path.join(dest_dir, "setupldr.exe") + dest_file = os.path.join(dest_dir, "NTLDR") + cmd = [ "/bin/sed", "-i", "-e", "s/winnt\\.sif/w%s.sif/gi" % profile.random_id, src_file ] + sub_process.call(cmd, shell=False, close_fds=False) + cmd = [ "/bin/sed", "-i", "-e", "s/ntdetect\\.com/ntd_%s.com/gi" % profile.random_id, src_file ] + sub_process.call(cmd, shell=False, close_fds=False) + os.rename(src_file, dest_file) + + src_dir = os.path.dirname(distro.kernel) + src_file = os.path.join(src_dir, "ntdetect.com") + file_name = os.path.join(dest_dir, "ntdetect.com") + utils.copyfile(src_file, file_name, self.api) + + template = profile.kickstart + fd = open(template, "r") + template_data = fd.read() + fd.close() + + blended = utils.blender(self.api, False, profile) + blended['next_server'] = self.settings.next_server + + ksmeta = blended.get("ks_meta",{}) + del blended["ks_meta"] + blended.update(ksmeta) # make available at top level + + # this is a workaround for a dumb bug in cheetah... + # a backslash before a variable is always treated as + # escaping the $, so things are not rendered correctly. + # The only option is to use another variable for + # backslashes when leading another variable. i.e.: + # \\$variable + blended['bsp'] = '\\' + + data = self.templar.render(template_data, blended, None) + + # write .sif to /tftpboot/profiles/$name/winnt.sif + file_name = os.path.join(dest_dir, "winnt.sif") + fd = open(file_name, "w") + fd.write(data) + fd.close() + + return True + + def generate_windows_system_pxe(self, system): + profile = system.get_conceptual_parent() + if not profile: + return False + + distro = profile.get_conceptual_parent() + + dest_dir = os.path.join("/tftpboot/systems", system.name) + utils.mkdir(dest_dir) + + utils.cabextract(distro.kernel, dest_dir, self.api) + + src_file = os.path.join(dest_dir, "startrom.n12") + dest_file = os.path.join(dest_dir, "winpxe.0") + cmd = [ "/bin/sed", "-i", "-e", "s/ntldr/L%s/gi" % system.random_id, src_file ] + sub_process.call(cmd, shell=False, close_fds=False) + os.rename(src_file, dest_file) + + utils.cabextract(distro.initrd, dest_dir, self.api) + + src_file = os.path.join(dest_dir, "setupldr.exe") + dest_file = os.path.join(dest_dir, "NTLDR") + cmd = [ "/bin/sed", "-i", "-e", "s/winnt\\.sif/w%s.sif/gi" % system.random_id, src_file ] + sub_process.call(cmd, shell=False, close_fds=False) + cmd = [ "/bin/sed", "-i", "-e", "s/ntdetect\\.com/ntd_%s.com/gi" % system.random_id, src_file ] + sub_process.call(cmd, shell=False, close_fds=False) + os.rename(src_file, dest_file) + + src_dir = os.path.dirname(distro.kernel) + src_file = os.path.join(src_dir, "ntdetect.com") + file_name = os.path.join(dest_dir, "ntdetect.com") + utils.copyfile(src_file, file_name, self.api) + + template = system.kickstart + if template == "<>": + template = profile.kickstart + + fd = open(template, "r") + template_data = fd.read() + fd.close() + + blended = utils.blender(self.api, False, system) + blended['next_server'] = self.settings.next_server + + ksmeta = blended.get("ks_meta",{}) + del blended["ks_meta"] + blended.update(ksmeta) # make available at top level + + # this is a workaround for a dumb bug in cheetah... + # a backslash before a variable is always treated as + # escaping the $, so things are not rendered correctly. + # The only option is to use another variable for + # backslashes when leading another variable. i.e.: + # \\$variable + blended['bsp'] = '\\' + + data = self.templar.render(template_data, blended, None) + + # write .sif to /tftpboot/systems/$name/winnt.sif + file_name = os.path.join(dest_dir, "winnt.sif") + fd = open(file_name, "w") + fd.write(data) + fd.close() + + return True + def write_all_system_files(self,system): profile = system.get_conceptual_parent() @@ -308,7 +456,7 @@ class PXEGen: distro = profile.get_conceptual_parent() # xen distros can be ruled out as they won't boot if distro.name.find("-xen") != -1: - # can't PXE Xen + # can't PXE Xen continue contents = self.write_pxe_file(None,None,profile,distro,distro.arch,include_header=False) if contents is not None: @@ -400,8 +548,14 @@ class PXEGen: if image is None: # profile or system+profile based, not image based, or system+image based - 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)) + if distro is not None and distro.breed == "windows": + if system: + kernel_path = "systems/%s/winpxe.0" % system.name + elif profile: + kernel_path = "profiles/%s/winpxe.0" % profile.name + else: + 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 if system: @@ -423,7 +577,11 @@ class PXEGen: # choose a template if system: if system.netboot_enabled: - template = os.path.join(self.settings.pxe_template_dir,"pxesystem.template") + if distro is not None and distro.breed == "windows": + template = os.path.join(self.settings.pxe_template_dir,"pxesystem_win.template") + else: + template = os.path.join(self.settings.pxe_template_dir,"pxesystem.template") + if arch == "s390x": template = os.path.join(self.settings.pxe_template_dir,"pxesystem_s390x.template") elif arch == "ia64": @@ -455,7 +613,10 @@ class PXEGen: else: template = os.path.join(self.settings.pxe_template_dir,"pxelocal.template") else: - template = os.path.join(self.settings.pxe_template_dir,"pxeprofile.template") + if distro is not None and distro.breed == "windows": + template = os.path.join(self.settings.pxe_template_dir,"pxeprofile_win.template") + else: + template = os.path.join(self.settings.pxe_template_dir,"pxeprofile.template") # now build the kernel command line @@ -508,6 +669,9 @@ class PXEGen: metadata["menu_label"] = "MENU LABEL %s" % image.name metadata["profile_name"] = image.name + if system: + metadata["system_name"] = system.name + if kernel_path is not None: metadata["kernel_path"] = kernel_path if initrd_path is not None: @@ -626,4 +790,57 @@ class PXEGen: return results + def write_tftpd_rules(self,write_file=False): + buffer = "" + template = "/etc/cobbler/tftpd-rules.template" + rules_dst = self.settings.tftpd_rules + ldr_rule_line = "re\t^L$random_id\t\t%s/$name/NTLDR\n" + sif_rule_line = "re\t/.*w$random_id\\.sif\t%s/$name/winnt.sif\n" + ntd_rule_line = "re\t/.*ntd_$random_id\.com\t%s/$name/ntdetect.com\n" + + fd = open(template, "r") + buffer = fd.read() + fd.close() + + buffer += "\n" + + for profile in self.profiles: + try: + effective_distro = profile.get_conceptual_parent() + if effective_distro.breed != "windows": + continue + except: + print _(" warning: could not determine distro for profile %s") % profile.name + continue + try: + blended = utils.blender(self.api, False, profile) + buffer += self.templar.render(ldr_rule_line % "profiles", blended, None) + buffer += self.templar.render(sif_rule_line % "profiles", blended, None) + buffer += self.templar.render(ntd_rule_line % "profiles", blended, None) + except: + print _(" warning: could not generate tftpd rules for profile %s") % profile.name + + for system in self.systems: + try: + effective_distro = system.get_conceptual_parent().get_conceptual_parent() + if effective_distro.breed != "windows": + continue + except: + print _(" warning: could not determine distro for system %s") % system.name + continue + try: + blended = utils.blender(self.api, False, system) + buffer += self.templar.render(ldr_rule_line % "systems", blended, None) + buffer += self.templar.render(sif_rule_line % "systems", blended, None) + buffer += self.templar.render(ntd_rule_line % "systems", blended, None) + except: + print _(" warning: could not generate tftpd rules for system %s") % system.name + + if write_file: + fd = open(rules_dst, "w") + fd.write(buffer) + fd.close() + + return buffer + diff --git a/cobbler/settings.py b/cobbler/settings.py index ec0e254d..54b5b0b9 100644 --- a/cobbler/settings.py +++ b/cobbler/settings.py @@ -70,6 +70,7 @@ DEFAULTS = { }, "manage_dhcp" : 0, "manage_dns" : 0, + "manage_xinetd" : 0, "manage_forward_zones" : [], "manage_reverse_zones" : [], "mgmt_classes" : [], @@ -89,12 +90,14 @@ DEFAULTS = { "register_new_installs" : 0, "restart_dns" : 1, "restart_dhcp" : 1, + "restart_xinetd" : 1, "run_install_triggers" : 1, "server" : "127.0.0.1", "snippetsdir" : "/var/lib/cobbler/snippets", "syslog_port" : 25150, "tftpd_bin" : "/usr/sbin/in.tftpd", "tftpd_conf" : "/etc/xinetd.d/tftp", + "tftpd_rules" : "/etc/tftpd.rules", "vsftpd_bin" : "/usr/sbin/vsftpd", "webdir" : "/var/www/cobbler", "xmlrpc_port" : 25151, diff --git a/cobbler/test_basic.py b/cobbler/test_basic.py index 7f6a062b..27ecabe0 100644 --- a/cobbler/test_basic.py +++ b/cobbler/test_basic.py @@ -873,8 +873,15 @@ class TestImage(BootTest): # ensure that only valid names are accepted and invalid ones are rejected image = self.api.new_image() self.assertTrue(image.set_file("nfs://hostname/path/to/filename.iso")) - self.assertTrue(image.set_file("/mcpierce@hostname:/path/to/filename.iso")) - self.assertTrue(image.set_file("path/to/filename.iso")) + self.assertTrue(image.set_file("nfs://mcpierce@hostname:/path/to/filename.iso")) + self.assertTrue(image.set_file("nfs://hostname:/path/to/filename.iso")) + self.assertTrue(image.set_file("nfs://hostname/filename.iso")) + self.assertTrue(image.set_file("hostname:/path/to/the/filename.iso")) + self.failUnlessRaises(CX, image.set_file, "hostname:filename.iso") + self.failUnlessRaises(CX, image.set_file, "path/to/filename.iso") + self.failUnlessRaises(CX, image.set_file, "hostname:") + # port is not allowed + self.failUnlessRaises(CX, image.set_file, "nfs://hostname:1234/path/to/the/filename.iso") #class TestCLIBasic(BootTest): # diff --git a/cobbler/utils.py b/cobbler/utils.py index 8603d0eb..94543d9c 100644 --- a/cobbler/utils.py +++ b/cobbler/utils.py @@ -898,6 +898,50 @@ def copyfile(src,dst,api=None,verbose=False): # traceback.print_exc() # raise CX(_("Error copying %(src)s to %(dst)s") % { "src" : src, "dst" : dst}) +def cabextract(src,dst,api=None): + """ + Extract a cab file, used for importing off of Windows based cds + """ + try: + if not os.path.isdir(dst): + raise CX(_("Error in cabextract: the destination (%s) must be a directory") % dst) + cmd = [ "/usr/bin/cabextract", "-d", dst, src ] + rc = sub_process.call(cmd, shell=False, close_fds=True) + return rc + except: + if not os.access(src,os.R_OK): + raise CX(_("Cannot read: %s") % src) + if not os.path.samefile(src,dst): + # accomodate for the possibility that we already copied + # the file as a symlink/hardlink + raise + # traceback.print_exc() + # raise CX(_("Error copying %(src)s to %(dst)s") % { "src" : src, "dst" : dst}) + +def bindmount(src,dst): + """ + Use mount --bind as an alternative to linking. This is required + for things in the tftp root since in.tftpd will not follow symlinks + and you cannot hard link directories (or across partitions). + """ + try: + if not os.path.isdir(src): + raise CX(_("Error in bindmount: the source (%s) must be a directory") % src) + if not os.path.isdir(dst): + raise CX(_("Error in bindmount: the destination (%s) must be a directory") % dst) + cmd = [ "/bin/mount", "--bind", dst, src ] + rc = sub_process.call(cmd, shell=False, close_fds=True) + return rc + except: + if not os.access(src,os.R_OK): + raise CX(_("Cannot read: %s") % src) + if not os.path.samefile(src,dst): + # accomodate for the possibility that we already copied + # the file as a symlink/hardlink + raise + # traceback.print_exc() + # raise CX(_("Error bind-mounting %(src)s to %(dst)s") % { "src" : src, "dst" : dst}) + def copyfile_pattern(pattern,dst,require_match=True,symlink_ok=False,api=None, verbose=False): files = glob.glob(pattern) if require_match and not len(files) > 0: diff --git a/setup.py b/setup.py index c2a6987b..eb5818b2 100644 --- a/setup.py +++ b/setup.py @@ -229,6 +229,7 @@ if __name__ == "__main__": # templates for power management (powerpath, ['templates/power_apc_snmp.template']), + (powerpath, ['templates/power_integrity.template']), (powerpath, ['templates/power_ipmilan.template']), (powerpath, ['templates/power_bullpap.template']), (powerpath, ['templates/power_ipmitool.template']), diff --git a/triggers/restart-services.trigger b/triggers/restart-services.trigger index 82dc0427..553aec01 100644 --- a/triggers/restart-services.trigger +++ b/triggers/restart-services.trigger @@ -8,8 +8,10 @@ bootapi = capi.BootAPI() settings = bootapi.settings() manage_dhcp = str(settings.manage_dhcp).lower() manage_dns = str(settings.manage_dns).lower() +manage_xinetd = str(settings.manage_xinetd).lower() restart_dhcp = str(settings.restart_dhcp).lower() restart_dns = str(settings.restart_dns).lower() +restart_xinetd = str(settings.restart_xinetd).lower() omapi_enabled = settings.omapi_enabled omapi_port = settings.omapi_port @@ -43,5 +45,8 @@ if manage_dns != "0" and restart_dns != "0": elif bootapi.dns.what() == "dnsmasq" and not has_restarted_dnsmasq: rc = os.ssytem("/sbin/service dnsmasq restart") +if manage_xinetd != "0" and restart_xinetd != "0": + rc = os.system("/sbin/service xinetd restart") + sys.exit(rc) diff --git a/webui_templates/system_edit.tmpl b/webui_templates/system_edit.tmpl index 37d0bf29..15c3fc6b 100644 --- a/webui_templates/system_edit.tmpl +++ b/webui_templates/system_edit.tmpl @@ -684,7 +684,7 @@ redhatmanagementkey"