summaryrefslogtreecommitdiffstats
path: root/cobbler/utils.py
diff options
context:
space:
mode:
Diffstat (limited to 'cobbler/utils.py')
-rw-r--r--cobbler/utils.py288
1 files changed, 158 insertions, 130 deletions
diff --git a/cobbler/utils.py b/cobbler/utils.py
index 23794a83..18535638 100644
--- a/cobbler/utils.py
+++ b/cobbler/utils.py
@@ -37,6 +37,20 @@ import tempfile
import signal
from cexceptions import *
import codes
+import time
+import netaddr
+import shlex
+
+try:
+ import hashlib as fiver
+ def md5(key):
+ return fiver.md5(key)
+except ImportError:
+ # for Python < 2.5
+ import md5 as fiver
+ def md5(key):
+ return fiver.md5(key)
+
CHEETAH_ERROR_DISCLAIMER="""
# *** ERROR ***
@@ -62,12 +76,12 @@ def _(foo):
MODULE_CACHE = {}
-# import api # factor out
-
_re_kernel = re.compile(r'vmlinuz(.*)')
_re_initrd = re.compile(r'initrd(.*).img')
-def setup_logger(name, log_level=logging.INFO, log_file="/var/log/cobbler/cobbler.log"):
+def setup_logger(name, is_cobblerd=False, log_level=logging.INFO, log_file="/var/log/cobbler/cobbler.log"):
+ if is_cobblerd:
+ log_file = "/var/log/cobbler/cobblerd.log"
logger = logging.getLogger(name)
logger.setLevel(log_level)
try:
@@ -90,18 +104,6 @@ def log_exc(logger):
logger.info("Exception Info:\n%s" % string.join(traceback.format_list(traceback.extract_tb(tb))))
-def print_exc(exc,full=False):
- (t, v, tb) = sys.exc_info()
- try:
- getattr(exc, "from_cobbler")
- print >> sys.stderr, str(exc)[1:-1]
- except:
- print >> sys.stderr, t
- print >> sys.stderr, v
- if full:
- print >> sys.stderr, string.join(traceback.format_list(traceback.extract_tb(tb)))
- return 1
-
def get_exc(exc,full=True):
(t, v, tb) = sys.exc_info()
buf = ""
@@ -133,39 +135,33 @@ def trace_me():
bar = string.join(traceback.format_list(x))
return bar
+def pretty_hex(ip, length=8):
+ """
+ Pads an IP object with leading zeroes so that the result is
+ _length_ hex digits. Also do an upper().
+ """
+ hexval = "%x" % ip.value
+ if len(hexval) < length:
+ hexval = '0' * (length - len(hexval)) + hexval
+ return hexval.upper()
def get_host_ip(ip, shorten=True):
"""
Return the IP encoding needed for the TFTP boot tree.
"""
+ ip = netaddr.IP(ip)
+ cidr = ip.cidr()
- slash = None
- if ip.find("/") != -1:
- # CIDR notation
- (ip, slash) = ip.split("/")
-
- handle = sub_process.Popen("/usr/bin/gethostip %s" % ip, shell=True, stdout=sub_process.PIPE, close_fds=True)
- out = handle.stdout
- results = out.read()
- converted = results.split(" ")[-1][0:8]
-
- if slash is None:
- return converted
+ if len(cidr) == 1: # Just an IP, e.g. a /32
+ return pretty_hex(ip)
else:
- slash = int(slash)
- num = int(converted, 16)
- delta = 32 - slash
- mask = (0xFFFFFFFF << delta)
- num = num & mask
- num = "%0x" % num
- if len(num) != 8:
- num = '0' * (8 - len(num)) + num
- num = num.upper()
- if shorten:
- nibbles = delta / 4
- for x in range(0,nibbles):
- num = num[0:-1]
- return num
+ pretty = pretty_hex(cidr[0])
+ if not shorten or len(cidr) <= 8:
+ # not enough to make the last nibble insignificant
+ return pretty
+ else:
+ cutoff = (32 - cidr.prefixlen) / 4
+ return pretty[0:-cutoff]
def get_config_filename(sys,interface):
"""
@@ -393,28 +389,32 @@ def input_string_or_hash(options,delim=",",allow_multiples=True):
raise CX(_("No idea what to do with list: %s") % options)
elif type(options) == str:
new_dict = {}
- tokens = options.split(delim)
+ tokens = shlex.split(options)
for t in tokens:
- tokens2 = t.split("=")
- if len(tokens2) == 1 and tokens2[0] != '':
+ tokens2 = t.split("=",1)
+ if len(tokens2) == 1:
# this is a singleton option, no value
- tokens2.append(None)
- elif tokens2[0] == '':
- return (False, {})
+ key = tokens2[0]
+ value = None
+ else:
+ key = tokens2[0]
+ value = tokens2[1]
# if we're allowing multiple values for the same key,
# check to see if this token has already been
# inserted into the dictionary of values already
- if tokens2[0] in new_dict.keys() and allow_multiples:
+
+ if key in new_dict.keys() and allow_multiples:
# if so, check to see if there is already a list of values
# otherwise convert the dictionary value to an array, and add
# the new value to the end of the list
- if type(new_dict[tokens2[0]]) == list:
- new_dict[tokens2[0]].append(tokens2[1])
+ if type(new_dict[key]) == list:
+ new_dict[key].append(value)
else:
- new_dict[tokens2[0]] = [new_dict[tokens2[0]], tokens2[1]]
+ new_dict[key] = [new_dict[key], value]
else:
- new_dict[tokens2[0]] = tokens2[1]
+ new_dict[key] = value
+ # make sure we have no empty entries
new_dict.pop('', None)
return (True, new_dict)
elif type(options) == dict:
@@ -457,12 +457,13 @@ def blender(api_handle,remove_hashes, root_obj):
for node in tree:
__consolidate(node,results)
- # add in syslog to results (magic)
- if settings.syslog_port != 0:
- if not results.has_key("kernel_options"):
- results["kernel_options"] = {}
- syslog = "%s:%s" % (results["server"], settings.syslog_port)
- results["kernel_options"]["syslog"] = syslog
+ # hack -- s390 nodes get additional default kernel options
+ arch = results.get("arch","?")
+ if arch.startswith("s390"):
+ keyz = settings.kernel_options_s390x.keys()
+ for k in keyz:
+ if not results.has_key(k):
+ results["kernel_options"][k] = settings.kernel_options_s390x[k]
# determine if we have room to add kssendmac to the kernel options line
kernel_txt = hash_to_string(results["kernel_options"])
@@ -633,7 +634,7 @@ def hash_removals(results,subkey):
return
scan = results[subkey].keys()
for k in scan:
- if k.startswith("!") and k != "!":
+ if str(k).startswith("!") and k != "!":
remove_me = k[1:]
if results[subkey].has_key(remove_me):
del results[subkey][remove_me]
@@ -662,15 +663,33 @@ def hash_to_string(hash):
buffer = buffer + str(key) + "=" + str(value) + " "
return buffer
-def run_triggers(ref,globber,additional=[]):
+def run_triggers(api,ref,globber,additional=[]):
"""
Runs all the trigger scripts in a given directory.
ref can be a cobbler object, if not None, the name will be passed
to the script. If ref is None, the script will be called with
no argumenets. Globber is a wildcard expression indicating which
triggers to run. Example: "/var/lib/cobbler/triggers/blah/*"
+
+ As of Cobbler 1.5.X, this also runs cobbler modules that match the globbing paths.
"""
+ # Python triggers first, before shell
+
+ modules = api.get_modules_in_category(globber)
+ for m in modules:
+ arglist = []
+ if ref:
+ arglist.append(ref.name)
+ for x in additional:
+ arglist.append(x)
+ rc = m.run(api, arglist)
+ if rc != 0:
+ raise CX("cobbler trigger failed: %s" % m.__name__)
+
+ # now do the old shell triggers, which are usually going to be slower, but are easier to write
+ # and support any language
+
triggers = glob.glob(globber)
triggers.sort()
for file in triggers:
@@ -730,7 +749,7 @@ def os_release():
if not os.path.exists("/bin/rpm"):
return ("unknown", 0)
- args = ["/bin/rpm", "-q", "--whatprovides", "redhat-release"]
+ args = ["/bin/rpm", "-q", "--whatprovides", "redhat-release", "--queryformat", "%"+"{name}"+"-%" + "{version}" + "-%" + "{release}"]
cmd = sub_process.Popen(args,shell=False,stdout=sub_process.PIPE,close_fds=True)
data = cmd.communicate()[0]
data = data.rstrip().lower()
@@ -821,7 +840,7 @@ def is_safe_to_hardlink(src,dst,api):
# we're dealing with SELinux and files that are not safe to chcon
return False
-def linkfile(src, dst, symlink_ok=False, api=None):
+def linkfile(src, dst, symlink_ok=False, api=None, verbose=False):
"""
Attempt to create a link dst that points to src. Because file
systems suck we attempt several different methods or bail to
@@ -842,25 +861,30 @@ def linkfile(src, dst, symlink_ok=False, api=None):
if not is_safe_to_hardlink(src,dst,api):
# may have to remove old hardlinks for SELinux reasons
# as previous implementations were not complete
- os.remove(dst)
+ if verbose:
+ print "- removing: %s" % dst
+ os.remove(dst)
else:
- restorecon(dst,api=api)
+ # restorecon(dst,api=api,verbose=verbose)
return True
elif os.path.islink(dst):
# existing path exists and is a symlink, update the symlink
+ if verbose:
+ print "- removing: %s" % dst
os.remove(dst)
if is_safe_to_hardlink(src,dst,api):
# we can try a hardlink if the destination isn't to NFS or Samba
# this will help save space and sync time.
try:
+ if verbose:
+ print "- trying hardlink %s -> %s" % (src,dst)
rc = os.link(src, dst)
- restorecon(dst,api=api)
+ # restorecon(dst,api=api,verbose=verbose)
return rc
except (IOError, OSError):
# hardlink across devices, or link already exists
- # can result in extra call to restorecon but no
- # major harm, we'll just symlink it if we can
+ # we'll just symlink it if we can
# or otherwise copy it
pass
@@ -868,20 +892,24 @@ def linkfile(src, dst, symlink_ok=False, api=None):
# we can symlink anywhere except for /tftpboot because
# that is run chroot, so if we can symlink now, try it.
try:
+ if verbose:
+ print "- trying symlink %s -> %s" % (src,dst)
rc = os.symlink(src, dst)
- restorecon(dst,api=api)
+ # restorecon(dst,api=api,verbose=verbose)
return rc
except (IOError, OSError):
pass
# we couldn't hardlink and we couldn't symlink so we must copy
- return copyfile(src, dst, api=api)
+ return copyfile(src, dst, api=api, verbose=verbose)
-def copyfile(src,dst,api=None):
+def copyfile(src,dst,api=None,verbose=False):
try:
+ if verbose:
+ print "- copying: %s -> %s" % (src,dst)
rc = shutil.copyfile(src,dst)
- restorecon(dst,api)
+ # restorecon(dst,api,verbose=verbose)
return rc
except:
if not os.access(src,os.R_OK):
@@ -982,58 +1010,44 @@ def umount(src):
# 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):
+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:
raise CX(_("Could not find files matching %s") % pattern)
for file in files:
base = os.path.basename(file)
dst1 = os.path.join(dst,os.path.basename(file))
- linkfile(file,dst1,symlink_ok=symlink_ok,api=api)
- restorecon(dst1,api=api)
-
-def restorecon(dest, api):
+ linkfile(file,dst1,symlink_ok=symlink_ok,api=api,verbose=verbose)
+ # restorecon(dst1,api=api,verbose=verbose)
- """
- Wrapper around functions to manage SELinux contexts.
- Use chcon public_content_t where we can to allow
- hardlinking between /var/www and tftpboot but use
- restorecon everywhere else.
- """
-
- if not api.is_selinux_enabled():
- return True
-
- tdest = os.path.realpath(dest)
-
- matched_path = False
- if dest.startswith("/var/www"):
- matched_path = True
- elif dest.find("/tftpboot/"):
- matched_path = True
- remoted = is_remote_file(tdest)
-
-
- if matched_path and not is_remote_file(tdest):
- # ensure the file is flagged as public_content_t
- # because it's something we've likely hardlinked
- # three ways between tftpboot, /var/www and the source
- cmd = ["/usr/bin/chcon","-t","public_content_t", tdest]
- rc = sub_process.call(cmd,shell=False,close_fds=True)
- if rc != 0:
- raise CX("chcon operation failed: %s" % cmd)
-
- if (not matched_path) or (matched_path and remoted):
- # the basic restorecon stuff...
- cmd = [ "/sbin/restorecon",dest ]
- rc = sub_process.call(cmd,shell=False,close_fds=True)
- if rc != 0:
- raise CX("restorecon operation failed: %s" % cmd)
-
- return 0
+#def restorecon(dest, api, verbose=False):
+#
+# """
+# Wrapper around functions to manage SELinux contexts.
+# Use chcon public_content_t where we can to allow
+# hardlinking between /var/www and tftpboot but use
+# restorecon everywhere else.
+# """
+#
+# if not api.is_selinux_enabled():
+# return True
+#
+# tdest = os.path.realpath(dest)
+# # remoted = is_remote_file(tdest)
+#
+# cmd = [ "/sbin/restorecon",dest ]
+# if verbose:
+# print "- %s" % " ".join(cmd)
+# rc = sub_process.call(cmd,shell=False,close_fds=True)
+# if rc != 0:
+# raise CX("restorecon operation failed: %s" % cmd)
+#
+# return 0
-def rmfile(path):
+def rmfile(path,verbose=False):
try:
+ if verbose:
+ print "- removing: %s" % path
os.unlink(path)
return True
except OSError, ioe:
@@ -1042,16 +1056,18 @@ def rmfile(path):
raise CX(_("Error deleting %s") % path)
return True
-def rmtree_contents(path):
+def rmtree_contents(path,verbose=False):
what_to_delete = glob.glob("%s/*" % path)
for x in what_to_delete:
- rmtree(x)
+ rmtree(x,verbose=verbose)
-def rmtree(path):
+def rmtree(path,verbose=False):
try:
if os.path.isfile(path):
- return rmfile(path)
+ return rmfile(path,verbose=verbose)
else:
+ if verbose:
+ print "- removing: %s" % path
return shutil.rmtree(path,ignore_errors=True)
except OSError, ioe:
traceback.print_exc()
@@ -1059,8 +1075,10 @@ def rmtree(path):
raise CX(_("Error deleting %s") % path)
return True
-def mkdir(path,mode=0777):
+def mkdir(path,mode=0777,verbose=False):
try:
+ if verbose:
+ "- mkdir: %s" % path
return os.makedirs(path,mode)
except OSError, oe:
if not oe.errno == 17: # already exists (no constant for 17?)
@@ -1072,16 +1090,24 @@ def set_redhat_management_key(self,key):
self.redhat_management_key = key
return True
-def set_arch(self,arch):
- if arch is None or arch == "":
- arch = "x86"
- if arch in [ "standard", "ia64", "x86", "i386", "ppc", "ppc64", "x86_64", "s390x" ]:
- if arch == "x86" or arch == "standard":
- # be consistent
- arch = "i386"
+def set_redhat_management_server(self,server):
+ self.redhat_management_server = server
+ return True
+
+def set_arch(self,arch,repo=False):
+ if arch is None or arch == "" or arch == "standard" or arch == "x86":
+ arch = "i386"
+
+ if repo:
+ valids = [ "i386", "x86_64", "ia64", "ppc", "ppc64", "s390", "s390x", "noarch", "src" ]
+ else:
+ valids = [ "i386", "x86_64", "ia64", "ppc", "ppc64", "s390", "s390x" ]
+
+ if arch in valids:
self.arch = arch
return True
- raise CX(_("arch choices include: x86, x86_64, ppc, ppc64, s390x and ia64"))
+
+ raise CX("arch choices include: %s" % ", ".join(valids))
def set_os_version(self,os_version):
if os_version == "" or os_version is None:
@@ -1238,8 +1264,8 @@ def set_virt_bridge(self,vbridge):
"""
The default bridge for all virtual interfaces under this profile.
"""
- if vbridge is None:
- vbridge = ""
+ if vbridge is None or vbridge == "":
+ vbridge = self.settings.default_virt_bridge
self.virt_bridge = vbridge
return True
@@ -1304,6 +1330,8 @@ def safe_filter(var):
raise CX("Invalid characters found in input")
def is_selinux_enabled():
+ if not os.path.exists("/usr/sbin/selinuxenabled"):
+ return False
args = "/usr/sbin/selinuxenabled"
selinuxenabled = sub_process.call(args,close_fds=True)
if selinuxenabled == 0:
@@ -1411,7 +1439,7 @@ def popen2(args, **kwargs):
Leftovers from borrowing some bits from Snake, replace this
function with just the subprocess call.
"""
- p = sub_process.Popen(args, stdout=subprocess.PIPE, stdin=subprocess.PIPE, **kwargs)
+ p = sub_process.Popen(args, stdout=sub_process.PIPE, stdin=sub_process.PIPE, **kwargs)
return (p.stdout, p.stdin)
if __name__ == "__main__":