X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/26f15862d6dd3ab2ea34de066c8c789a649d70f1..dd7db36058e0e645c634402fe9c26cee69eef753:/lib/utils.py diff --git a/lib/utils.py b/lib/utils.py index b7be906..48388ba 100644 --- a/lib/utils.py +++ b/lib/utils.py @@ -29,7 +29,6 @@ the command line scripts. import sys import os -import sha import time import subprocess import re @@ -47,6 +46,12 @@ import signal from cStringIO import StringIO +try: + from hashlib import sha1 +except ImportError: + import sha + sha1 = sha.new + from ganeti import errors from ganeti import constants @@ -151,11 +156,18 @@ def RunCmd(cmd, env=None, output=None, cwd='/'): if env is not None: cmd_env.update(env) - if output is None: - out, err, status = _RunCmdPipe(cmd, cmd_env, shell, cwd) - else: - status = _RunCmdFile(cmd, cmd_env, shell, output, cwd) - out = err = "" + try: + if output is None: + out, err, status = _RunCmdPipe(cmd, cmd_env, shell, cwd) + else: + status = _RunCmdFile(cmd, cmd_env, shell, output, cwd) + out = err = "" + except OSError, err: + if err.errno == errno.ENOENT: + raise errors.OpExecError("Can't execute '%s': not found (%s)" % + (strcmd, err)) + else: + raise if status >= 0: exitcode = status @@ -330,7 +342,7 @@ def _FingerprintFile(filename): f = open(filename) - fp = sha.sha() + fp = sha1() while True: data = f.read(4096) if not data: @@ -387,6 +399,69 @@ def CheckDict(target, template, logname=None): logging.warning('%s missing keys %s', logname, ', '.join(missing)) +def ForceDictType(target, key_types, allowed_values=None): + """Force the values of a dict to have certain types. + + @type target: dict + @param target: the dict to update + @type key_types: dict + @param key_types: dict mapping target dict keys to types + in constants.ENFORCEABLE_TYPES + @type allowed_values: list + @keyword allowed_values: list of specially allowed values + + """ + if allowed_values is None: + allowed_values = [] + + for key in target: + if key not in key_types: + msg = "Unknown key '%s'" % key + raise errors.TypeEnforcementError(msg) + + if target[key] in allowed_values: + continue + + type = key_types[key] + if type not in constants.ENFORCEABLE_TYPES: + msg = "'%s' has non-enforceable type %s" % (key, type) + raise errors.ProgrammerError(msg) + + if type == constants.VTYPE_STRING: + if not isinstance(target[key], basestring): + if isinstance(target[key], bool) and not target[key]: + target[key] = '' + else: + msg = "'%s' (value %s) is not a valid string" % (key, target[key]) + raise errors.TypeEnforcementError(msg) + elif type == constants.VTYPE_BOOL: + if isinstance(target[key], basestring) and target[key]: + if target[key].lower() == constants.VALUE_FALSE: + target[key] = False + elif target[key].lower() == constants.VALUE_TRUE: + target[key] = True + else: + msg = "'%s' (value %s) is not a valid boolean" % (key, target[key]) + raise errors.TypeEnforcementError(msg) + elif target[key]: + target[key] = True + else: + target[key] = False + elif type == constants.VTYPE_SIZE: + try: + target[key] = ParseUnit(target[key]) + except errors.UnitParseError, err: + msg = "'%s' (value %s) is not a valid size. error: %s" % \ + (key, target[key], err) + raise errors.TypeEnforcementError(msg) + elif type == constants.VTYPE_INT: + try: + target[key] = int(target[key]) + except (ValueError, TypeError): + msg = "'%s' (value %s) is not a valid integer" % (key, target[key]) + raise errors.TypeEnforcementError(msg) + + def IsProcessAlive(pid): """Check if a given pid exists on the system. @@ -416,7 +491,7 @@ def ReadPidFile(pidfile): @type pidfile: string @param pidfile: path to the file containing the pid @rtype: int - @return: The process id, if the file exista and contains a valid PID, + @return: The process id, if the file exists and contains a valid PID, otherwise 0 """ @@ -558,37 +633,6 @@ def BridgeExists(bridge): return os.path.isdir("/sys/class/net/%s/bridge" % bridge) -def CheckBEParams(beparams): - """Checks whether the user-supplied be-params are valid, - and converts them from string format where appropriate. - - @type beparams: dict - @param beparams: new params dict - - """ - if beparams: - for item in beparams: - if item not in constants.BES_PARAMETERS: - raise errors.OpPrereqError("Unknown backend parameter %s" % item) - if item in (constants.BE_MEMORY, constants.BE_VCPUS): - val = beparams[item] - if val != constants.VALUE_DEFAULT: - try: - val = int(val) - except ValueError, err: - raise errors.OpPrereqError("Invalid %s size: %s" % (item, err)) - beparams[item] = val - if item in (constants.BE_AUTO_BALANCE): - val = beparams[item] - if not isinstance(val, bool): - if val == constants.VALUE_TRUE: - beparams[item] = True - elif val == constants.VALUE_FALSE: - beparams[item] = False - else: - raise errors.OpPrereqError("Invalid %s value: %s" % (item, val)) - - def NiceSort(name_list): """Sort a list of strings based on digit and non-digit groupings. @@ -863,6 +907,7 @@ def SetEtcHostsEntry(file_name, ip, hostname, aliases): @param aliases: the list of aliases to add for the hostname """ + # FIXME: use WriteFile + fn rather than duplicating its efforts # Ensure aliases are unique aliases = UniqueSequence([hostname] + aliases)[1:] @@ -885,6 +930,7 @@ def SetEtcHostsEntry(file_name, ip, hostname, aliases): out.flush() os.fsync(out) + os.chmod(tmpname, 0644) os.rename(tmpname, file_name) finally: f.close() @@ -918,6 +964,7 @@ def RemoveEtcHostsEntry(file_name, hostname): @param hostname: the hostname to be removed """ + # FIXME: use WriteFile + fn rather than duplicating its efforts fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name)) try: out = os.fdopen(fd, 'w') @@ -939,6 +986,7 @@ def RemoveEtcHostsEntry(file_name, hostname): out.flush() os.fsync(out) + os.chmod(tmpname, 0644) os.rename(tmpname, file_name) finally: f.close() @@ -1143,7 +1191,25 @@ def GenerateSecret(): @return: a sha1 hexdigest of a block of 64 random bytes """ - return sha.new(os.urandom(64)).hexdigest() + return sha1(os.urandom(64)).hexdigest() + + +def EnsureDirs(dirs): + """Make required directories, if they don't exist. + + @param dirs: list of tuples (dir_name, dir_mode) + @type dirs: list of (string, integer) + + """ + for dir_name, dir_mode in dirs: + try: + os.mkdir(dir_name, dir_mode) + except EnvironmentError, err: + if err.errno != errno.EEXIST: + raise errors.GenericError("Cannot create needed directory" + " '%s': %s" % (dir_name, err)) + if not os.path.isdir(dir_name): + raise errors.GenericError("%s is not a directory" % dir_name) def ReadFile(file_name, size=None): @@ -1179,7 +1245,7 @@ def WriteFile(file_name, fn=None, data=None, mtime/atime of the file. If the function doesn't raise an exception, it has succeeded and the - target file has the new contents. If the file has raised an + target file has the new contents. If the function has raised an exception, an existing target file should be unmodified and the temporary file should be removed. @@ -1188,7 +1254,7 @@ def WriteFile(file_name, fn=None, data=None, @type fn: callable @param fn: content writing function, called with file descriptor as parameter - @type data: sr + @type data: str @param data: contents of the file @type mode: int @param mode: file mode @@ -1211,7 +1277,7 @@ def WriteFile(file_name, fn=None, data=None, @return: None if the 'close' parameter evaluates to True, otherwise the file descriptor - @raise errors.ProgrammerError: if an of the arguments are not valid + @raise errors.ProgrammerError: if any of the arguments are not valid """ if not os.path.isabs(file_name): @@ -1230,6 +1296,7 @@ def WriteFile(file_name, fn=None, data=None, dir_name, base_name = os.path.split(file_name) fd, new_name = tempfile.mkstemp('.new', base_name, dir_name) + do_remove = True # here we need to make sure we remove the temp file, if any error # leaves it in place try: @@ -1250,13 +1317,15 @@ def WriteFile(file_name, fn=None, data=None, os.utime(new_name, (atime, mtime)) if not dry_run: os.rename(new_name, file_name) + do_remove = False finally: if close: os.close(fd) result = None else: result = fd - RemoveFile(new_name) + if do_remove: + RemoveFile(new_name) return result @@ -1410,7 +1479,7 @@ def Daemonize(logfile): @type logfile: str @param logfile: the logfile to which we should redirect stdout/stderr @rtype: int - @returns: the value zero + @return: the value zero """ UMASK = 077 @@ -1716,6 +1785,13 @@ def SetupLogging(logfile, debug=False, stderr_logging=False, program="", # we need to re-raise the exception raise +def IsNormAbsPath(path): + """Check whether a path is absolute and also normalized + + This avoids things like /dir/../../other/path to be valid. + + """ + return os.path.normpath(path) == path and os.path.isabs(path) def TailFile(fname, lines=20): """Return the last lines from a file. @@ -1764,6 +1840,16 @@ def SafeEncode(text): return text +def CommaJoin(names): + """Nicely join a set of identifiers. + + @param names: set, list or tuple + @return: a string with the formatted results + + """ + return ", ".join(["'%s'" % val for val in names]) + + def LockedMethod(fn): """Synchronized object access decorator.