4 # Copyright (C) 2006, 2007, 2010 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 """Ganeti utility module.
24 This module holds functions that can be used in both daemons (all) and
25 the command line scripts.
45 import logging.handlers
53 from cStringIO import StringIO
56 # pylint: disable-msg=F0401
61 from ganeti import errors
62 from ganeti import constants
63 from ganeti import compat
67 _re_shell_unquoted = re.compile('^[-.,=:/_+@A-Za-z0-9]+$')
71 #: when set to True, L{RunCmd} is disabled
74 _RANDOM_UUID_FILE = "/proc/sys/kernel/random/uuid"
76 HEX_CHAR_RE = r"[a-zA-Z0-9]"
77 VALID_X509_SIGNATURE_SALT = re.compile("^%s+$" % HEX_CHAR_RE, re.S)
78 X509_SIGNATURE = re.compile(r"^%s:\s*(?P<salt>%s+)/(?P<sign>%s+)$" %
79 (re.escape(constants.X509_CERT_SIGNATURE_HEADER),
80 HEX_CHAR_RE, HEX_CHAR_RE),
83 _VALID_SERVICE_NAME_RE = re.compile("^[-_.a-zA-Z0-9]{1,128}$")
85 # Certificate verification results
87 CERT_ERROR) = range(1, 3)
89 # Flags for mlockall() (from bits/mman.h)
94 class RunResult(object):
95 """Holds the result of running external programs.
98 @ivar exit_code: the exit code of the program, or None (if the program
100 @type signal: int or None
101 @ivar signal: the signal that caused the program to finish, or None
102 (if the program wasn't terminated by a signal)
104 @ivar stdout: the standard output of the program
106 @ivar stderr: the standard error of the program
107 @type failed: boolean
108 @ivar failed: True in case the program was
109 terminated by a signal or exited with a non-zero exit code
110 @ivar fail_reason: a string detailing the termination reason
113 __slots__ = ["exit_code", "signal", "stdout", "stderr",
114 "failed", "fail_reason", "cmd"]
117 def __init__(self, exit_code, signal_, stdout, stderr, cmd):
119 self.exit_code = exit_code
120 self.signal = signal_
123 self.failed = (signal_ is not None or exit_code != 0)
125 if self.signal is not None:
126 self.fail_reason = "terminated by signal %s" % self.signal
127 elif self.exit_code is not None:
128 self.fail_reason = "exited with exit code %s" % self.exit_code
130 self.fail_reason = "unable to determine termination reason"
133 logging.debug("Command '%s' failed (%s); output: %s",
134 self.cmd, self.fail_reason, self.output)
136 def _GetOutput(self):
137 """Returns the combined stdout and stderr for easier usage.
140 return self.stdout + self.stderr
142 output = property(_GetOutput, None, None, "Return full output")
145 def _BuildCmdEnvironment(env, reset):
146 """Builds the environment for an external program.
152 cmd_env = os.environ.copy()
153 cmd_env["LC_ALL"] = "C"
161 def RunCmd(cmd, env=None, output=None, cwd="/", reset_env=False):
162 """Execute a (shell) command.
164 The command should not read from its standard input, as it will be
167 @type cmd: string or list
168 @param cmd: Command to run
170 @param env: Additional environment variables
172 @param output: if desired, the output of the command can be
173 saved in a file instead of the RunResult instance; this
174 parameter denotes the file name (if not None)
176 @param cwd: if specified, will be used as the working
177 directory for the command; the default will be /
178 @type reset_env: boolean
179 @param reset_env: whether to reset or keep the default os environment
181 @return: RunResult instance
182 @raise errors.ProgrammerError: if we call this when forks are disabled
186 raise errors.ProgrammerError("utils.RunCmd() called with fork() disabled")
188 if isinstance(cmd, basestring):
192 cmd = [str(val) for val in cmd]
193 strcmd = ShellQuoteArgs(cmd)
197 logging.debug("RunCmd %s, output file '%s'", strcmd, output)
199 logging.debug("RunCmd %s", strcmd)
201 cmd_env = _BuildCmdEnvironment(env, reset_env)
205 out, err, status = _RunCmdPipe(cmd, cmd_env, shell, cwd)
207 status = _RunCmdFile(cmd, cmd_env, shell, output, cwd)
210 if err.errno == errno.ENOENT:
211 raise errors.OpExecError("Can't execute '%s': not found (%s)" %
223 return RunResult(exitcode, signal_, out, err, strcmd)
226 def StartDaemon(cmd, env=None, cwd="/", output=None, output_fd=None,
228 """Start a daemon process after forking twice.
230 @type cmd: string or list
231 @param cmd: Command to run
233 @param env: Additional environment variables
235 @param cwd: Working directory for the program
237 @param output: Path to file in which to save the output
239 @param output_fd: File descriptor for output
240 @type pidfile: string
241 @param pidfile: Process ID file
243 @return: Daemon process ID
244 @raise errors.ProgrammerError: if we call this when forks are disabled
248 raise errors.ProgrammerError("utils.StartDaemon() called with fork()"
251 if output and not (bool(output) ^ (output_fd is not None)):
252 raise errors.ProgrammerError("Only one of 'output' and 'output_fd' can be"
255 if isinstance(cmd, basestring):
256 cmd = ["/bin/sh", "-c", cmd]
258 strcmd = ShellQuoteArgs(cmd)
261 logging.debug("StartDaemon %s, output file '%s'", strcmd, output)
263 logging.debug("StartDaemon %s", strcmd)
265 cmd_env = _BuildCmdEnvironment(env, False)
267 # Create pipe for sending PID back
268 (pidpipe_read, pidpipe_write) = os.pipe()
271 # Create pipe for sending error messages
272 (errpipe_read, errpipe_write) = os.pipe()
279 # Child process, won't return
280 _StartDaemonChild(errpipe_read, errpipe_write,
281 pidpipe_read, pidpipe_write,
283 output, output_fd, pidfile)
285 # Well, maybe child process failed
286 os._exit(1) # pylint: disable-msg=W0212
288 _CloseFDNoErr(errpipe_write)
290 # Wait for daemon to be started (or an error message to arrive) and read
291 # up to 100 KB as an error message
292 errormsg = RetryOnSignal(os.read, errpipe_read, 100 * 1024)
294 _CloseFDNoErr(errpipe_read)
296 _CloseFDNoErr(pidpipe_write)
298 # Read up to 128 bytes for PID
299 pidtext = RetryOnSignal(os.read, pidpipe_read, 128)
301 _CloseFDNoErr(pidpipe_read)
303 # Try to avoid zombies by waiting for child process
310 raise errors.OpExecError("Error when starting daemon process: %r" %
315 except (ValueError, TypeError), err:
316 raise errors.OpExecError("Error while trying to parse PID %r: %s" %
320 def _StartDaemonChild(errpipe_read, errpipe_write,
321 pidpipe_read, pidpipe_write,
323 output, fd_output, pidfile):
324 """Child process for starting daemon.
328 # Close parent's side
329 _CloseFDNoErr(errpipe_read)
330 _CloseFDNoErr(pidpipe_read)
332 # First child process
337 # And fork for the second time
340 # Exit first child process
341 os._exit(0) # pylint: disable-msg=W0212
343 # Make sure pipe is closed on execv* (and thereby notifies original process)
344 SetCloseOnExecFlag(errpipe_write, True)
346 # List of file descriptors to be left open
347 noclose_fds = [errpipe_write]
352 # TODO: Atomic replace with another locked file instead of writing into
354 fd_pidfile = os.open(pidfile, os.O_WRONLY | os.O_CREAT, 0600)
356 # Lock the PID file (and fail if not possible to do so). Any code
357 # wanting to send a signal to the daemon should try to lock the PID
358 # file before reading it. If acquiring the lock succeeds, the daemon is
359 # no longer running and the signal should not be sent.
362 os.write(fd_pidfile, "%d\n" % os.getpid())
363 except Exception, err:
364 raise Exception("Creating and locking PID file failed: %s" % err)
366 # Keeping the file open to hold the lock
367 noclose_fds.append(fd_pidfile)
369 SetCloseOnExecFlag(fd_pidfile, False)
374 fd_devnull = os.open(os.devnull, os.O_RDWR)
376 assert not output or (bool(output) ^ (fd_output is not None))
378 if fd_output is not None:
383 # TODO: Implement flag to set append=yes/no
384 fd_output = os.open(output, os.O_WRONLY | os.O_CREAT, 0600)
385 except EnvironmentError, err:
386 raise Exception("Opening output file failed: %s" % err)
388 fd_output = fd_devnull
390 # Redirect standard I/O
391 os.dup2(fd_devnull, 0)
392 os.dup2(fd_output, 1)
393 os.dup2(fd_output, 2)
395 # Send daemon PID to parent
396 RetryOnSignal(os.write, pidpipe_write, str(os.getpid()))
398 # Close all file descriptors except stdio and error message pipe
399 CloseFDs(noclose_fds=noclose_fds)
401 # Change working directory
405 os.execvp(args[0], args)
407 os.execvpe(args[0], args, env)
408 except: # pylint: disable-msg=W0702
410 # Report errors to original process
411 buf = str(sys.exc_info()[1])
413 RetryOnSignal(os.write, errpipe_write, buf)
414 except: # pylint: disable-msg=W0702
415 # Ignore errors in error handling
418 os._exit(1) # pylint: disable-msg=W0212
421 def _RunCmdPipe(cmd, env, via_shell, cwd):
422 """Run a command and return its output.
424 @type cmd: string or list
425 @param cmd: Command to run
427 @param env: The environment to use
428 @type via_shell: bool
429 @param via_shell: if we should run via the shell
431 @param cwd: the working directory for the program
433 @return: (out, err, status)
436 poller = select.poll()
437 child = subprocess.Popen(cmd, shell=via_shell,
438 stderr=subprocess.PIPE,
439 stdout=subprocess.PIPE,
440 stdin=subprocess.PIPE,
441 close_fds=True, env=env,
445 poller.register(child.stdout, select.POLLIN)
446 poller.register(child.stderr, select.POLLIN)
450 child.stdout.fileno(): (out, child.stdout),
451 child.stderr.fileno(): (err, child.stderr),
454 SetNonblockFlag(fd, True)
457 pollresult = RetryOnSignal(poller.poll)
459 for fd, event in pollresult:
460 if event & select.POLLIN or event & select.POLLPRI:
461 data = fdmap[fd][1].read()
462 # no data from read signifies EOF (the same as POLLHUP)
464 poller.unregister(fd)
467 fdmap[fd][0].write(data)
468 if (event & select.POLLNVAL or event & select.POLLHUP or
469 event & select.POLLERR):
470 poller.unregister(fd)
476 status = child.wait()
477 return out, err, status
480 def _RunCmdFile(cmd, env, via_shell, output, cwd):
481 """Run a command and save its output to a file.
483 @type cmd: string or list
484 @param cmd: Command to run
486 @param env: The environment to use
487 @type via_shell: bool
488 @param via_shell: if we should run via the shell
490 @param output: the filename in which to save the output
492 @param cwd: the working directory for the program
494 @return: the exit status
497 fh = open(output, "a")
499 child = subprocess.Popen(cmd, shell=via_shell,
500 stderr=subprocess.STDOUT,
502 stdin=subprocess.PIPE,
503 close_fds=True, env=env,
507 status = child.wait()
513 def SetCloseOnExecFlag(fd, enable):
514 """Sets or unsets the close-on-exec flag on a file descriptor.
517 @param fd: File descriptor
519 @param enable: Whether to set or unset it.
522 flags = fcntl.fcntl(fd, fcntl.F_GETFD)
525 flags |= fcntl.FD_CLOEXEC
527 flags &= ~fcntl.FD_CLOEXEC
529 fcntl.fcntl(fd, fcntl.F_SETFD, flags)
532 def SetNonblockFlag(fd, enable):
533 """Sets or unsets the O_NONBLOCK flag on on a file descriptor.
536 @param fd: File descriptor
538 @param enable: Whether to set or unset it
541 flags = fcntl.fcntl(fd, fcntl.F_GETFL)
544 flags |= os.O_NONBLOCK
546 flags &= ~os.O_NONBLOCK
548 fcntl.fcntl(fd, fcntl.F_SETFL, flags)
551 def RetryOnSignal(fn, *args, **kwargs):
552 """Calls a function again if it failed due to EINTR.
557 return fn(*args, **kwargs)
558 except EnvironmentError, err:
559 if err.errno != errno.EINTR:
561 except (socket.error, select.error), err:
562 # In python 2.6 and above select.error is an IOError, so it's handled
563 # above, in 2.5 and below it's not, and it's handled here.
564 if not (err.args and err.args[0] == errno.EINTR):
568 def RunParts(dir_name, env=None, reset_env=False):
569 """Run Scripts or programs in a directory
571 @type dir_name: string
572 @param dir_name: absolute path to a directory
574 @param env: The environment to use
575 @type reset_env: boolean
576 @param reset_env: whether to reset or keep the default os environment
577 @rtype: list of tuples
578 @return: list of (name, (one of RUNDIR_STATUS), RunResult)
584 dir_contents = ListVisibleFiles(dir_name)
586 logging.warning("RunParts: skipping %s (cannot list: %s)", dir_name, err)
589 for relname in sorted(dir_contents):
590 fname = PathJoin(dir_name, relname)
591 if not (os.path.isfile(fname) and os.access(fname, os.X_OK) and
592 constants.EXT_PLUGIN_MASK.match(relname) is not None):
593 rr.append((relname, constants.RUNPARTS_SKIP, None))
596 result = RunCmd([fname], env=env, reset_env=reset_env)
597 except Exception, err: # pylint: disable-msg=W0703
598 rr.append((relname, constants.RUNPARTS_ERR, str(err)))
600 rr.append((relname, constants.RUNPARTS_RUN, result))
605 def RemoveFile(filename):
606 """Remove a file ignoring some errors.
608 Remove a file, ignoring non-existing ones or directories. Other
612 @param filename: the file to be removed
618 if err.errno not in (errno.ENOENT, errno.EISDIR):
622 def RemoveDir(dirname):
623 """Remove an empty directory.
625 Remove a directory, ignoring non-existing ones.
626 Other errors are passed. This includes the case,
627 where the directory is not empty, so it can't be removed.
630 @param dirname: the empty directory to be removed
636 if err.errno != errno.ENOENT:
640 def RenameFile(old, new, mkdir=False, mkdir_mode=0750):
644 @param old: Original path
648 @param mkdir: Whether to create target directory if it doesn't exist
649 @type mkdir_mode: int
650 @param mkdir_mode: Mode for newly created directories
654 return os.rename(old, new)
656 # In at least one use case of this function, the job queue, directory
657 # creation is very rare. Checking for the directory before renaming is not
659 if mkdir and err.errno == errno.ENOENT:
660 # Create directory and try again
661 Makedirs(os.path.dirname(new), mode=mkdir_mode)
663 return os.rename(old, new)
668 def Makedirs(path, mode=0750):
669 """Super-mkdir; create a leaf directory and all intermediate ones.
671 This is a wrapper around C{os.makedirs} adding error handling not implemented
676 os.makedirs(path, mode)
678 # Ignore EEXIST. This is only handled in os.makedirs as included in
679 # Python 2.5 and above.
680 if err.errno != errno.EEXIST or not os.path.exists(path):
684 def ResetTempfileModule():
685 """Resets the random name generator of the tempfile module.
687 This function should be called after C{os.fork} in the child process to
688 ensure it creates a newly seeded random generator. Otherwise it would
689 generate the same random parts as the parent process. If several processes
690 race for the creation of a temporary file, this could lead to one not getting
694 # pylint: disable-msg=W0212
695 if hasattr(tempfile, "_once_lock") and hasattr(tempfile, "_name_sequence"):
696 tempfile._once_lock.acquire()
698 # Reset random name generator
699 tempfile._name_sequence = None
701 tempfile._once_lock.release()
703 logging.critical("The tempfile module misses at least one of the"
704 " '_once_lock' and '_name_sequence' attributes")
707 def _FingerprintFile(filename):
708 """Compute the fingerprint of a file.
710 If the file does not exist, a None will be returned
714 @param filename: the filename to checksum
716 @return: the hex digest of the sha checksum of the contents
720 if not (os.path.exists(filename) and os.path.isfile(filename)):
725 fp = compat.sha1_hash()
733 return fp.hexdigest()
736 def FingerprintFiles(files):
737 """Compute fingerprints for a list of files.
740 @param files: the list of filename to fingerprint
742 @return: a dictionary filename: fingerprint, holding only
748 for filename in files:
749 cksum = _FingerprintFile(filename)
751 ret[filename] = cksum
756 def ForceDictType(target, key_types, allowed_values=None):
757 """Force the values of a dict to have certain types.
760 @param target: the dict to update
761 @type key_types: dict
762 @param key_types: dict mapping target dict keys to types
763 in constants.ENFORCEABLE_TYPES
764 @type allowed_values: list
765 @keyword allowed_values: list of specially allowed values
768 if allowed_values is None:
771 if not isinstance(target, dict):
772 msg = "Expected dictionary, got '%s'" % target
773 raise errors.TypeEnforcementError(msg)
776 if key not in key_types:
777 msg = "Unknown key '%s'" % key
778 raise errors.TypeEnforcementError(msg)
780 if target[key] in allowed_values:
783 ktype = key_types[key]
784 if ktype not in constants.ENFORCEABLE_TYPES:
785 msg = "'%s' has non-enforceable type %s" % (key, ktype)
786 raise errors.ProgrammerError(msg)
788 if ktype in (constants.VTYPE_STRING, constants.VTYPE_MAYBE_STRING):
789 if target[key] is None and ktype == constants.VTYPE_MAYBE_STRING:
791 elif not isinstance(target[key], basestring):
792 if isinstance(target[key], bool) and not target[key]:
795 msg = "'%s' (value %s) is not a valid string" % (key, target[key])
796 raise errors.TypeEnforcementError(msg)
797 elif ktype == constants.VTYPE_BOOL:
798 if isinstance(target[key], basestring) and target[key]:
799 if target[key].lower() == constants.VALUE_FALSE:
801 elif target[key].lower() == constants.VALUE_TRUE:
804 msg = "'%s' (value %s) is not a valid boolean" % (key, target[key])
805 raise errors.TypeEnforcementError(msg)
810 elif ktype == constants.VTYPE_SIZE:
812 target[key] = ParseUnit(target[key])
813 except errors.UnitParseError, err:
814 msg = "'%s' (value %s) is not a valid size. error: %s" % \
815 (key, target[key], err)
816 raise errors.TypeEnforcementError(msg)
817 elif ktype == constants.VTYPE_INT:
819 target[key] = int(target[key])
820 except (ValueError, TypeError):
821 msg = "'%s' (value %s) is not a valid integer" % (key, target[key])
822 raise errors.TypeEnforcementError(msg)
825 def _GetProcStatusPath(pid):
826 """Returns the path for a PID's proc status file.
829 @param pid: Process ID
833 return "/proc/%d/status" % pid
836 def IsProcessAlive(pid):
837 """Check if a given pid exists on the system.
839 @note: zombie status is not handled, so zombie processes
840 will be returned as alive
842 @param pid: the process ID to check
844 @return: True if the process exists
851 except EnvironmentError, err:
852 if err.errno in (errno.ENOENT, errno.ENOTDIR):
854 elif err.errno == errno.EINVAL:
855 raise RetryAgain(err)
858 assert isinstance(pid, int), "pid must be an integer"
862 # /proc in a multiprocessor environment can have strange behaviors.
863 # Retry the os.stat a few times until we get a good result.
865 return Retry(_TryStat, (0.01, 1.5, 0.1), 0.5,
866 args=[_GetProcStatusPath(pid)])
867 except RetryTimeout, err:
871 def _ParseSigsetT(sigset):
872 """Parse a rendered sigset_t value.
874 This is the opposite of the Linux kernel's fs/proc/array.c:render_sigset_t
878 @param sigset: Rendered signal set from /proc/$pid/status
880 @return: Set of all enabled signal numbers
886 for ch in reversed(sigset):
889 # The following could be done in a loop, but it's easier to read and
890 # understand in the unrolled form
892 result.add(signum + 1)
894 result.add(signum + 2)
896 result.add(signum + 3)
898 result.add(signum + 4)
905 def _GetProcStatusField(pstatus, field):
906 """Retrieves a field from the contents of a proc status file.
908 @type pstatus: string
909 @param pstatus: Contents of /proc/$pid/status
911 @param field: Name of field whose value should be returned
915 for line in pstatus.splitlines():
916 parts = line.split(":", 1)
918 if len(parts) < 2 or parts[0] != field:
921 return parts[1].strip()
926 def IsProcessHandlingSignal(pid, signum, status_path=None):
927 """Checks whether a process is handling a signal.
930 @param pid: Process ID
932 @param signum: Signal number
936 if status_path is None:
937 status_path = _GetProcStatusPath(pid)
940 proc_status = ReadFile(status_path)
941 except EnvironmentError, err:
942 # In at least one case, reading /proc/$pid/status failed with ESRCH.
943 if err.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL, errno.ESRCH):
947 sigcgt = _GetProcStatusField(proc_status, "SigCgt")
949 raise RuntimeError("%s is missing 'SigCgt' field" % status_path)
951 # Now check whether signal is handled
952 return signum in _ParseSigsetT(sigcgt)
955 def ReadPidFile(pidfile):
956 """Read a pid from a file.
958 @type pidfile: string
959 @param pidfile: path to the file containing the pid
961 @return: The process id, if the file exists and contains a valid PID,
966 raw_data = ReadOneLineFile(pidfile)
967 except EnvironmentError, err:
968 if err.errno != errno.ENOENT:
969 logging.exception("Can't read pid file")
974 except (TypeError, ValueError), err:
975 logging.info("Can't parse pid file contents", exc_info=True)
981 def ReadLockedPidFile(path):
982 """Reads a locked PID file.
984 This can be used together with L{StartDaemon}.
987 @param path: Path to PID file
988 @return: PID as integer or, if file was unlocked or couldn't be opened, None
992 fd = os.open(path, os.O_RDONLY)
993 except EnvironmentError, err:
994 if err.errno == errno.ENOENT:
995 # PID file doesn't exist
1001 # Try to acquire lock
1003 except errors.LockError:
1004 # Couldn't lock, daemon is running
1005 return int(os.read(fd, 100))
1012 def MatchNameComponent(key, name_list, case_sensitive=True):
1013 """Try to match a name against a list.
1015 This function will try to match a name like test1 against a list
1016 like C{['test1.example.com', 'test2.example.com', ...]}. Against
1017 this list, I{'test1'} as well as I{'test1.example'} will match, but
1018 not I{'test1.ex'}. A multiple match will be considered as no match
1019 at all (e.g. I{'test1'} against C{['test1.example.com',
1020 'test1.example.org']}), except when the key fully matches an entry
1021 (e.g. I{'test1'} against C{['test1', 'test1.example.com']}).
1024 @param key: the name to be searched
1025 @type name_list: list
1026 @param name_list: the list of strings against which to search the key
1027 @type case_sensitive: boolean
1028 @param case_sensitive: whether to provide a case-sensitive match
1031 @return: None if there is no match I{or} if there are multiple matches,
1032 otherwise the element from the list which matches
1035 if key in name_list:
1039 if not case_sensitive:
1040 re_flags |= re.IGNORECASE
1042 mo = re.compile("^%s(\..*)?$" % re.escape(key), re_flags)
1045 for name in name_list:
1046 if mo.match(name) is not None:
1047 names_filtered.append(name)
1048 if not case_sensitive and key == name.upper():
1049 string_matches.append(name)
1051 if len(string_matches) == 1:
1052 return string_matches[0]
1053 if len(names_filtered) == 1:
1054 return names_filtered[0]
1058 def ValidateServiceName(name):
1059 """Validate the given service name.
1061 @type name: number or string
1062 @param name: Service name or port specification
1067 except (ValueError, TypeError):
1068 # Non-numeric service name
1069 valid = _VALID_SERVICE_NAME_RE.match(name)
1071 # Numeric port (protocols other than TCP or UDP might need adjustments
1073 valid = (numport >= 0 and numport < (1 << 16))
1076 raise errors.OpPrereqError("Invalid service name '%s'" % name,
1082 def ListVolumeGroups():
1083 """List volume groups and their size
1087 Dictionary with keys volume name and values
1088 the size of the volume
1091 command = "vgs --noheadings --units m --nosuffix -o name,size"
1092 result = RunCmd(command)
1097 for line in result.stdout.splitlines():
1099 name, size = line.split()
1100 size = int(float(size))
1101 except (IndexError, ValueError), err:
1102 logging.error("Invalid output from vgs (%s): %s", err, line)
1110 def BridgeExists(bridge):
1111 """Check whether the given bridge exists in the system
1114 @param bridge: the bridge name to check
1116 @return: True if it does
1119 return os.path.isdir("/sys/class/net/%s/bridge" % bridge)
1122 def NiceSort(name_list):
1123 """Sort a list of strings based on digit and non-digit groupings.
1125 Given a list of names C{['a1', 'a10', 'a11', 'a2']} this function
1126 will sort the list in the logical order C{['a1', 'a2', 'a10',
1129 The sort algorithm breaks each name in groups of either only-digits
1130 or no-digits. Only the first eight such groups are considered, and
1131 after that we just use what's left of the string.
1133 @type name_list: list
1134 @param name_list: the names to be sorted
1136 @return: a copy of the name list sorted with our algorithm
1139 _SORTER_BASE = "(\D+|\d+)"
1140 _SORTER_FULL = "^%s%s?%s?%s?%s?%s?%s?%s?.*$" % (_SORTER_BASE, _SORTER_BASE,
1141 _SORTER_BASE, _SORTER_BASE,
1142 _SORTER_BASE, _SORTER_BASE,
1143 _SORTER_BASE, _SORTER_BASE)
1144 _SORTER_RE = re.compile(_SORTER_FULL)
1145 _SORTER_NODIGIT = re.compile("^\D*$")
1147 """Attempts to convert a variable to integer."""
1148 if val is None or _SORTER_NODIGIT.match(val):
1153 to_sort = [([_TryInt(grp) for grp in _SORTER_RE.match(name).groups()], name)
1154 for name in name_list]
1156 return [tup[1] for tup in to_sort]
1159 def TryConvert(fn, val):
1160 """Try to convert a value ignoring errors.
1162 This function tries to apply function I{fn} to I{val}. If no
1163 C{ValueError} or C{TypeError} exceptions are raised, it will return
1164 the result, else it will return the original value. Any other
1165 exceptions are propagated to the caller.
1168 @param fn: function to apply to the value
1169 @param val: the value to be converted
1170 @return: The converted value if the conversion was successful,
1171 otherwise the original value.
1176 except (ValueError, TypeError):
1181 def IsValidShellParam(word):
1182 """Verifies is the given word is safe from the shell's p.o.v.
1184 This means that we can pass this to a command via the shell and be
1185 sure that it doesn't alter the command line and is passed as such to
1188 Note that we are overly restrictive here, in order to be on the safe
1192 @param word: the word to check
1194 @return: True if the word is 'safe'
1197 return bool(re.match("^[-a-zA-Z0-9._+/:%@]+$", word))
1200 def BuildShellCmd(template, *args):
1201 """Build a safe shell command line from the given arguments.
1203 This function will check all arguments in the args list so that they
1204 are valid shell parameters (i.e. they don't contain shell
1205 metacharacters). If everything is ok, it will return the result of
1209 @param template: the string holding the template for the
1212 @return: the expanded command line
1216 if not IsValidShellParam(word):
1217 raise errors.ProgrammerError("Shell argument '%s' contains"
1218 " invalid characters" % word)
1219 return template % args
1222 def FormatUnit(value, units):
1223 """Formats an incoming number of MiB with the appropriate unit.
1226 @param value: integer representing the value in MiB (1048576)
1228 @param units: the type of formatting we should do:
1229 - 'h' for automatic scaling
1234 @return: the formatted value (with suffix)
1237 if units not in ('m', 'g', 't', 'h'):
1238 raise errors.ProgrammerError("Invalid unit specified '%s'" % str(units))
1242 if units == 'm' or (units == 'h' and value < 1024):
1245 return "%d%s" % (round(value, 0), suffix)
1247 elif units == 'g' or (units == 'h' and value < (1024 * 1024)):
1250 return "%0.1f%s" % (round(float(value) / 1024, 1), suffix)
1255 return "%0.1f%s" % (round(float(value) / 1024 / 1024, 1), suffix)
1258 def ParseUnit(input_string):
1259 """Tries to extract number and scale from the given string.
1261 Input must be in the format C{NUMBER+ [DOT NUMBER+] SPACE*
1262 [UNIT]}. If no unit is specified, it defaults to MiB. Return value
1263 is always an int in MiB.
1266 m = re.match('^([.\d]+)\s*([a-zA-Z]+)?$', str(input_string))
1268 raise errors.UnitParseError("Invalid format")
1270 value = float(m.groups()[0])
1272 unit = m.groups()[1]
1274 lcunit = unit.lower()
1278 if lcunit in ('m', 'mb', 'mib'):
1279 # Value already in MiB
1282 elif lcunit in ('g', 'gb', 'gib'):
1285 elif lcunit in ('t', 'tb', 'tib'):
1286 value *= 1024 * 1024
1289 raise errors.UnitParseError("Unknown unit: %s" % unit)
1291 # Make sure we round up
1292 if int(value) < value:
1295 # Round up to the next multiple of 4
1298 value += 4 - value % 4
1303 def ParseCpuMask(cpu_mask):
1304 """Parse a CPU mask definition and return the list of CPU IDs.
1306 CPU mask format: comma-separated list of CPU IDs
1307 or dash-separated ID ranges
1308 Example: "0-2,5" -> "0,1,2,5"
1311 @param cpu_mask: CPU mask definition
1313 @return: list of CPU IDs
1319 for range_def in cpu_mask.split(","):
1320 boundaries = range_def.split("-")
1321 n_elements = len(boundaries)
1323 raise errors.ParseError("Invalid CPU ID range definition"
1324 " (only one hyphen allowed): %s" % range_def)
1326 lower = int(boundaries[0])
1327 except (ValueError, TypeError), err:
1328 raise errors.ParseError("Invalid CPU ID value for lower boundary of"
1329 " CPU ID range: %s" % str(err))
1331 higher = int(boundaries[-1])
1332 except (ValueError, TypeError), err:
1333 raise errors.ParseError("Invalid CPU ID value for higher boundary of"
1334 " CPU ID range: %s" % str(err))
1336 raise errors.ParseError("Invalid CPU ID range definition"
1337 " (%d > %d): %s" % (lower, higher, range_def))
1338 cpu_list.extend(range(lower, higher + 1))
1342 def AddAuthorizedKey(file_obj, key):
1343 """Adds an SSH public key to an authorized_keys file.
1345 @type file_obj: str or file handle
1346 @param file_obj: path to authorized_keys file
1348 @param key: string containing key
1351 key_fields = key.split()
1353 if isinstance(file_obj, basestring):
1354 f = open(file_obj, 'a+')
1361 # Ignore whitespace changes
1362 if line.split() == key_fields:
1364 nl = line.endswith('\n')
1368 f.write(key.rstrip('\r\n'))
1375 def RemoveAuthorizedKey(file_name, key):
1376 """Removes an SSH public key from an authorized_keys file.
1378 @type file_name: str
1379 @param file_name: path to authorized_keys file
1381 @param key: string containing key
1384 key_fields = key.split()
1386 fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
1388 out = os.fdopen(fd, 'w')
1390 f = open(file_name, 'r')
1393 # Ignore whitespace changes while comparing lines
1394 if line.split() != key_fields:
1398 os.rename(tmpname, file_name)
1408 def SetEtcHostsEntry(file_name, ip, hostname, aliases):
1409 """Sets the name of an IP address and hostname in /etc/hosts.
1411 @type file_name: str
1412 @param file_name: path to the file to modify (usually C{/etc/hosts})
1414 @param ip: the IP address
1416 @param hostname: the hostname to be added
1418 @param aliases: the list of aliases to add for the hostname
1421 # FIXME: use WriteFile + fn rather than duplicating its efforts
1422 # Ensure aliases are unique
1423 aliases = UniqueSequence([hostname] + aliases)[1:]
1425 fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
1427 out = os.fdopen(fd, 'w')
1429 f = open(file_name, 'r')
1432 fields = line.split()
1433 if fields and not fields[0].startswith('#') and ip == fields[0]:
1437 out.write("%s\t%s" % (ip, hostname))
1439 out.write(" %s" % ' '.join(aliases))
1444 os.chmod(tmpname, 0644)
1445 os.rename(tmpname, file_name)
1455 def AddHostToEtcHosts(hostname):
1456 """Wrapper around SetEtcHostsEntry.
1459 @param hostname: a hostname that will be resolved and added to
1460 L{constants.ETC_HOSTS}
1463 SetEtcHostsEntry(constants.ETC_HOSTS, hostname.ip, hostname.name,
1464 [hostname.name.split(".")[0]])
1467 def RemoveEtcHostsEntry(file_name, hostname):
1468 """Removes a hostname from /etc/hosts.
1470 IP addresses without names are removed from the file.
1472 @type file_name: str
1473 @param file_name: path to the file to modify (usually C{/etc/hosts})
1475 @param hostname: the hostname to be removed
1478 # FIXME: use WriteFile + fn rather than duplicating its efforts
1479 fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
1481 out = os.fdopen(fd, 'w')
1483 f = open(file_name, 'r')
1486 fields = line.split()
1487 if len(fields) > 1 and not fields[0].startswith('#'):
1489 if hostname in names:
1490 while hostname in names:
1491 names.remove(hostname)
1493 out.write("%s %s\n" % (fields[0], ' '.join(names)))
1500 os.chmod(tmpname, 0644)
1501 os.rename(tmpname, file_name)
1511 def RemoveHostFromEtcHosts(hostname):
1512 """Wrapper around RemoveEtcHostsEntry.
1515 @param hostname: hostname that will be resolved and its
1516 full and shot name will be removed from
1517 L{constants.ETC_HOSTS}
1520 RemoveEtcHostsEntry(constants.ETC_HOSTS, hostname)
1521 RemoveEtcHostsEntry(constants.ETC_HOSTS, hostname.split(".")[0])
1524 def TimestampForFilename():
1525 """Returns the current time formatted for filenames.
1527 The format doesn't contain colons as some shells and applications them as
1531 return time.strftime("%Y-%m-%d_%H_%M_%S")
1534 def CreateBackup(file_name):
1535 """Creates a backup of a file.
1537 @type file_name: str
1538 @param file_name: file to be backed up
1540 @return: the path to the newly created backup
1541 @raise errors.ProgrammerError: for invalid file names
1544 if not os.path.isfile(file_name):
1545 raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
1548 prefix = ("%s.backup-%s." %
1549 (os.path.basename(file_name), TimestampForFilename()))
1550 dir_name = os.path.dirname(file_name)
1552 fsrc = open(file_name, 'rb')
1554 (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
1555 fdst = os.fdopen(fd, 'wb')
1557 logging.debug("Backing up %s at %s", file_name, backup_name)
1558 shutil.copyfileobj(fsrc, fdst)
1567 def ShellQuote(value):
1568 """Quotes shell argument according to POSIX.
1571 @param value: the argument to be quoted
1573 @return: the quoted value
1576 if _re_shell_unquoted.match(value):
1579 return "'%s'" % value.replace("'", "'\\''")
1582 def ShellQuoteArgs(args):
1583 """Quotes a list of shell arguments.
1586 @param args: list of arguments to be quoted
1588 @return: the quoted arguments concatenated with spaces
1591 return ' '.join([ShellQuote(i) for i in args])
1595 """Helper class to write scripts with indentation.
1600 def __init__(self, fh):
1601 """Initializes this class.
1607 def IncIndent(self):
1608 """Increase indentation level by 1.
1613 def DecIndent(self):
1614 """Decrease indentation level by 1.
1617 assert self._indent > 0
1620 def Write(self, txt, *args):
1621 """Write line to output file.
1624 assert self._indent >= 0
1626 self._fh.write(self._indent * self.INDENT_STR)
1629 self._fh.write(txt % args)
1633 self._fh.write("\n")
1636 def ListVisibleFiles(path):
1637 """Returns a list of visible files in a directory.
1640 @param path: the directory to enumerate
1642 @return: the list of all files not starting with a dot
1643 @raise ProgrammerError: if L{path} is not an absolue and normalized path
1646 if not IsNormAbsPath(path):
1647 raise errors.ProgrammerError("Path passed to ListVisibleFiles is not"
1648 " absolute/normalized: '%s'" % path)
1649 files = [i for i in os.listdir(path) if not i.startswith(".")]
1653 def GetHomeDir(user, default=None):
1654 """Try to get the homedir of the given user.
1656 The user can be passed either as a string (denoting the name) or as
1657 an integer (denoting the user id). If the user is not found, the
1658 'default' argument is returned, which defaults to None.
1662 if isinstance(user, basestring):
1663 result = pwd.getpwnam(user)
1664 elif isinstance(user, (int, long)):
1665 result = pwd.getpwuid(user)
1667 raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
1671 return result.pw_dir
1675 """Returns a random UUID.
1677 @note: This is a Linux-specific method as it uses the /proc
1682 return ReadFile(_RANDOM_UUID_FILE, size=128).rstrip("\n")
1685 def GenerateSecret(numbytes=20):
1686 """Generates a random secret.
1688 This will generate a pseudo-random secret returning an hex string
1689 (so that it can be used where an ASCII string is needed).
1691 @param numbytes: the number of bytes which will be represented by the returned
1692 string (defaulting to 20, the length of a SHA1 hash)
1694 @return: an hex representation of the pseudo-random sequence
1697 return os.urandom(numbytes).encode('hex')
1700 def EnsureDirs(dirs):
1701 """Make required directories, if they don't exist.
1703 @param dirs: list of tuples (dir_name, dir_mode)
1704 @type dirs: list of (string, integer)
1707 for dir_name, dir_mode in dirs:
1709 os.mkdir(dir_name, dir_mode)
1710 except EnvironmentError, err:
1711 if err.errno != errno.EEXIST:
1712 raise errors.GenericError("Cannot create needed directory"
1713 " '%s': %s" % (dir_name, err))
1715 os.chmod(dir_name, dir_mode)
1716 except EnvironmentError, err:
1717 raise errors.GenericError("Cannot change directory permissions on"
1718 " '%s': %s" % (dir_name, err))
1719 if not os.path.isdir(dir_name):
1720 raise errors.GenericError("%s is not a directory" % dir_name)
1723 def ReadFile(file_name, size=-1):
1727 @param size: Read at most size bytes (if negative, entire file)
1729 @return: the (possibly partial) content of the file
1732 f = open(file_name, "r")
1739 def WriteFile(file_name, fn=None, data=None,
1740 mode=None, uid=-1, gid=-1,
1741 atime=None, mtime=None, close=True,
1742 dry_run=False, backup=False,
1743 prewrite=None, postwrite=None):
1744 """(Over)write a file atomically.
1746 The file_name and either fn (a function taking one argument, the
1747 file descriptor, and which should write the data to it) or data (the
1748 contents of the file) must be passed. The other arguments are
1749 optional and allow setting the file mode, owner and group, and the
1750 mtime/atime of the file.
1752 If the function doesn't raise an exception, it has succeeded and the
1753 target file has the new contents. If the function has raised an
1754 exception, an existing target file should be unmodified and the
1755 temporary file should be removed.
1757 @type file_name: str
1758 @param file_name: the target filename
1760 @param fn: content writing function, called with
1761 file descriptor as parameter
1763 @param data: contents of the file
1765 @param mode: file mode
1767 @param uid: the owner of the file
1769 @param gid: the group of the file
1771 @param atime: a custom access time to be set on the file
1773 @param mtime: a custom modification time to be set on the file
1774 @type close: boolean
1775 @param close: whether to close file after writing it
1776 @type prewrite: callable
1777 @param prewrite: function to be called before writing content
1778 @type postwrite: callable
1779 @param postwrite: function to be called after writing content
1782 @return: None if the 'close' parameter evaluates to True,
1783 otherwise the file descriptor
1785 @raise errors.ProgrammerError: if any of the arguments are not valid
1788 if not os.path.isabs(file_name):
1789 raise errors.ProgrammerError("Path passed to WriteFile is not"
1790 " absolute: '%s'" % file_name)
1792 if [fn, data].count(None) != 1:
1793 raise errors.ProgrammerError("fn or data required")
1795 if [atime, mtime].count(None) == 1:
1796 raise errors.ProgrammerError("Both atime and mtime must be either"
1799 if backup and not dry_run and os.path.isfile(file_name):
1800 CreateBackup(file_name)
1802 dir_name, base_name = os.path.split(file_name)
1803 fd, new_name = tempfile.mkstemp('.new', base_name, dir_name)
1805 # here we need to make sure we remove the temp file, if any error
1806 # leaves it in place
1808 if uid != -1 or gid != -1:
1809 os.chown(new_name, uid, gid)
1811 os.chmod(new_name, mode)
1812 if callable(prewrite):
1814 if data is not None:
1818 if callable(postwrite):
1821 if atime is not None and mtime is not None:
1822 os.utime(new_name, (atime, mtime))
1824 os.rename(new_name, file_name)
1833 RemoveFile(new_name)
1838 def ReadOneLineFile(file_name, strict=False):
1839 """Return the first non-empty line from a file.
1841 @type strict: boolean
1842 @param strict: if True, abort if the file has more than one
1846 file_lines = ReadFile(file_name).splitlines()
1847 full_lines = filter(bool, file_lines)
1848 if not file_lines or not full_lines:
1849 raise errors.GenericError("No data in one-liner file %s" % file_name)
1850 elif strict and len(full_lines) > 1:
1851 raise errors.GenericError("Too many lines in one-liner file %s" %
1853 return full_lines[0]
1856 def FirstFree(seq, base=0):
1857 """Returns the first non-existing integer from seq.
1859 The seq argument should be a sorted list of positive integers. The
1860 first time the index of an element is smaller than the element
1861 value, the index will be returned.
1863 The base argument is used to start at a different offset,
1864 i.e. C{[3, 4, 6]} with I{offset=3} will return 5.
1866 Example: C{[0, 1, 3]} will return I{2}.
1869 @param seq: the sequence to be analyzed.
1871 @param base: use this value as the base index of the sequence
1873 @return: the first non-used index in the sequence
1876 for idx, elem in enumerate(seq):
1877 assert elem >= base, "Passed element is higher than base offset"
1878 if elem > idx + base:
1884 def SingleWaitForFdCondition(fdobj, event, timeout):
1885 """Waits for a condition to occur on the socket.
1887 Immediately returns at the first interruption.
1889 @type fdobj: integer or object supporting a fileno() method
1890 @param fdobj: entity to wait for events on
1891 @type event: integer
1892 @param event: ORed condition (see select module)
1893 @type timeout: float or None
1894 @param timeout: Timeout in seconds
1896 @return: None for timeout, otherwise occured conditions
1899 check = (event | select.POLLPRI |
1900 select.POLLNVAL | select.POLLHUP | select.POLLERR)
1902 if timeout is not None:
1903 # Poller object expects milliseconds
1906 poller = select.poll()
1907 poller.register(fdobj, event)
1909 # TODO: If the main thread receives a signal and we have no timeout, we
1910 # could wait forever. This should check a global "quit" flag or something
1912 io_events = poller.poll(timeout)
1913 except select.error, err:
1914 if err[0] != errno.EINTR:
1917 if io_events and io_events[0][1] & check:
1918 return io_events[0][1]
1923 class FdConditionWaiterHelper(object):
1924 """Retry helper for WaitForFdCondition.
1926 This class contains the retried and wait functions that make sure
1927 WaitForFdCondition can continue waiting until the timeout is actually
1932 def __init__(self, timeout):
1933 self.timeout = timeout
1935 def Poll(self, fdobj, event):
1936 result = SingleWaitForFdCondition(fdobj, event, self.timeout)
1942 def UpdateTimeout(self, timeout):
1943 self.timeout = timeout
1946 def WaitForFdCondition(fdobj, event, timeout):
1947 """Waits for a condition to occur on the socket.
1949 Retries until the timeout is expired, even if interrupted.
1951 @type fdobj: integer or object supporting a fileno() method
1952 @param fdobj: entity to wait for events on
1953 @type event: integer
1954 @param event: ORed condition (see select module)
1955 @type timeout: float or None
1956 @param timeout: Timeout in seconds
1958 @return: None for timeout, otherwise occured conditions
1961 if timeout is not None:
1962 retrywaiter = FdConditionWaiterHelper(timeout)
1964 result = Retry(retrywaiter.Poll, RETRY_REMAINING_TIME, timeout,
1965 args=(fdobj, event), wait_fn=retrywaiter.UpdateTimeout)
1966 except RetryTimeout:
1970 while result is None:
1971 result = SingleWaitForFdCondition(fdobj, event, timeout)
1975 def UniqueSequence(seq):
1976 """Returns a list with unique elements.
1978 Element order is preserved.
1981 @param seq: the sequence with the source elements
1983 @return: list of unique elements from seq
1987 return [i for i in seq if i not in seen and not seen.add(i)]
1990 def NormalizeAndValidateMac(mac):
1991 """Normalizes and check if a MAC address is valid.
1993 Checks whether the supplied MAC address is formally correct, only
1994 accepts colon separated format. Normalize it to all lower.
1997 @param mac: the MAC to be validated
1999 @return: returns the normalized and validated MAC.
2001 @raise errors.OpPrereqError: If the MAC isn't valid
2004 mac_check = re.compile("^([0-9a-f]{2}(:|$)){6}$", re.I)
2005 if not mac_check.match(mac):
2006 raise errors.OpPrereqError("Invalid MAC address specified: %s" %
2007 mac, errors.ECODE_INVAL)
2012 def TestDelay(duration):
2013 """Sleep for a fixed amount of time.
2015 @type duration: float
2016 @param duration: the sleep duration
2018 @return: False for negative value, True otherwise
2022 return False, "Invalid sleep duration"
2023 time.sleep(duration)
2027 def _CloseFDNoErr(fd, retries=5):
2028 """Close a file descriptor ignoring errors.
2031 @param fd: the file descriptor
2033 @param retries: how many retries to make, in case we get any
2034 other error than EBADF
2039 except OSError, err:
2040 if err.errno != errno.EBADF:
2042 _CloseFDNoErr(fd, retries - 1)
2043 # else either it's closed already or we're out of retries, so we
2044 # ignore this and go on
2047 def CloseFDs(noclose_fds=None):
2048 """Close file descriptors.
2050 This closes all file descriptors above 2 (i.e. except
2053 @type noclose_fds: list or None
2054 @param noclose_fds: if given, it denotes a list of file descriptor
2055 that should not be closed
2058 # Default maximum for the number of available file descriptors.
2059 if 'SC_OPEN_MAX' in os.sysconf_names:
2061 MAXFD = os.sysconf('SC_OPEN_MAX')
2068 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
2069 if (maxfd == resource.RLIM_INFINITY):
2072 # Iterate through and close all file descriptors (except the standard ones)
2073 for fd in range(3, maxfd):
2074 if noclose_fds and fd in noclose_fds:
2079 def Mlockall(_ctypes=ctypes):
2080 """Lock current process' virtual address space into RAM.
2082 This is equivalent to the C call mlockall(MCL_CURRENT|MCL_FUTURE),
2083 see mlock(2) for more details. This function requires ctypes module.
2085 @raises errors.NoCtypesError: if ctypes module is not found
2089 raise errors.NoCtypesError()
2091 libc = _ctypes.cdll.LoadLibrary("libc.so.6")
2093 logging.error("Cannot set memory lock, ctypes cannot load libc")
2096 # Some older version of the ctypes module don't have built-in functionality
2097 # to access the errno global variable, where function error codes are stored.
2098 # By declaring this variable as a pointer to an integer we can then access
2099 # its value correctly, should the mlockall call fail, in order to see what
2100 # the actual error code was.
2101 # pylint: disable-msg=W0212
2102 libc.__errno_location.restype = _ctypes.POINTER(_ctypes.c_int)
2104 if libc.mlockall(_MCL_CURRENT | _MCL_FUTURE):
2105 # pylint: disable-msg=W0212
2106 logging.error("Cannot set memory lock: %s",
2107 os.strerror(libc.__errno_location().contents.value))
2110 logging.debug("Memory lock set")
2113 def Daemonize(logfile, run_uid, run_gid):
2114 """Daemonize the current process.
2116 This detaches the current process from the controlling terminal and
2117 runs it in the background as a daemon.
2120 @param logfile: the logfile to which we should redirect stdout/stderr
2122 @param run_uid: Run the child under this uid
2124 @param run_gid: Run the child under this gid
2126 @return: the value zero
2129 # pylint: disable-msg=W0212
2130 # yes, we really want os._exit
2136 if (pid == 0): # The first child.
2138 # FIXME: When removing again and moving to start-stop-daemon privilege drop
2139 # make sure to check for config permission and bail out when invoked
2144 pid = os.fork() # Fork a second child.
2145 if (pid == 0): # The second child.
2149 # exit() or _exit()? See below.
2150 os._exit(0) # Exit parent (the first child) of the second child.
2152 os._exit(0) # Exit parent of the first child.
2156 i = os.open("/dev/null", os.O_RDONLY) # stdin
2157 assert i == 0, "Can't close/reopen stdin"
2158 i = os.open(logfile, os.O_WRONLY|os.O_CREAT|os.O_APPEND, 0600) # stdout
2159 assert i == 1, "Can't close/reopen stdout"
2160 # Duplicate standard output to standard error.
2165 def DaemonPidFileName(name):
2166 """Compute a ganeti pid file absolute path
2169 @param name: the daemon name
2171 @return: the full path to the pidfile corresponding to the given
2175 return PathJoin(constants.RUN_GANETI_DIR, "%s.pid" % name)
2178 def EnsureDaemon(name):
2179 """Check for and start daemon if not alive.
2182 result = RunCmd([constants.DAEMON_UTIL, "check-and-start", name])
2184 logging.error("Can't start daemon '%s', failure %s, output: %s",
2185 name, result.fail_reason, result.output)
2191 def StopDaemon(name):
2195 result = RunCmd([constants.DAEMON_UTIL, "stop", name])
2197 logging.error("Can't stop daemon '%s', failure %s, output: %s",
2198 name, result.fail_reason, result.output)
2204 def WritePidFile(name):
2205 """Write the current process pidfile.
2207 The file will be written to L{constants.RUN_GANETI_DIR}I{/name.pid}
2210 @param name: the daemon name to use
2211 @raise errors.GenericError: if the pid file already exists and
2212 points to a live process
2216 pidfilename = DaemonPidFileName(name)
2217 if IsProcessAlive(ReadPidFile(pidfilename)):
2218 raise errors.GenericError("%s contains a live process" % pidfilename)
2220 WriteFile(pidfilename, data="%d\n" % pid)
2223 def RemovePidFile(name):
2224 """Remove the current process pidfile.
2226 Any errors are ignored.
2229 @param name: the daemon name used to derive the pidfile name
2232 pidfilename = DaemonPidFileName(name)
2233 # TODO: we could check here that the file contains our pid
2235 RemoveFile(pidfilename)
2236 except: # pylint: disable-msg=W0702
2240 def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
2242 """Kill a process given by its pid.
2245 @param pid: The PID to terminate.
2247 @param signal_: The signal to send, by default SIGTERM
2249 @param timeout: The timeout after which, if the process is still alive,
2250 a SIGKILL will be sent. If not positive, no such checking
2252 @type waitpid: boolean
2253 @param waitpid: If true, we should waitpid on this process after
2254 sending signals, since it's our own child and otherwise it
2255 would remain as zombie
2258 def _helper(pid, signal_, wait):
2259 """Simple helper to encapsulate the kill/waitpid sequence"""
2260 if IgnoreProcessNotFound(os.kill, pid, signal_) and wait:
2262 os.waitpid(pid, os.WNOHANG)
2267 # kill with pid=0 == suicide
2268 raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
2270 if not IsProcessAlive(pid):
2273 _helper(pid, signal_, waitpid)
2278 def _CheckProcess():
2279 if not IsProcessAlive(pid):
2283 (result_pid, _) = os.waitpid(pid, os.WNOHANG)
2293 # Wait up to $timeout seconds
2294 Retry(_CheckProcess, (0.01, 1.5, 0.1), timeout)
2295 except RetryTimeout:
2298 if IsProcessAlive(pid):
2299 # Kill process if it's still alive
2300 _helper(pid, signal.SIGKILL, waitpid)
2303 def FindFile(name, search_path, test=os.path.exists):
2304 """Look for a filesystem object in a given path.
2306 This is an abstract method to search for filesystem object (files,
2307 dirs) under a given search path.
2310 @param name: the name to look for
2311 @type search_path: str
2312 @param search_path: location to start at
2313 @type test: callable
2314 @param test: a function taking one argument that should return True
2315 if the a given object is valid; the default value is
2316 os.path.exists, causing only existing files to be returned
2318 @return: full path to the object if found, None otherwise
2321 # validate the filename mask
2322 if constants.EXT_PLUGIN_MASK.match(name) is None:
2323 logging.critical("Invalid value passed for external script name: '%s'",
2327 for dir_name in search_path:
2328 # FIXME: investigate switch to PathJoin
2329 item_name = os.path.sep.join([dir_name, name])
2330 # check the user test and that we're indeed resolving to the given
2332 if test(item_name) and os.path.basename(item_name) == name:
2337 def CheckVolumeGroupSize(vglist, vgname, minsize):
2338 """Checks if the volume group list is valid.
2340 The function will check if a given volume group is in the list of
2341 volume groups and has a minimum size.
2344 @param vglist: dictionary of volume group names and their size
2346 @param vgname: the volume group we should check
2348 @param minsize: the minimum size we accept
2350 @return: None for success, otherwise the error message
2353 vgsize = vglist.get(vgname, None)
2355 return "volume group '%s' missing" % vgname
2356 elif vgsize < minsize:
2357 return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
2358 (vgname, minsize, vgsize))
2362 def SplitTime(value):
2363 """Splits time as floating point number into a tuple.
2365 @param value: Time in seconds
2366 @type value: int or float
2367 @return: Tuple containing (seconds, microseconds)
2370 (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
2372 assert 0 <= seconds, \
2373 "Seconds must be larger than or equal to 0, but are %s" % seconds
2374 assert 0 <= microseconds <= 999999, \
2375 "Microseconds must be 0-999999, but are %s" % microseconds
2377 return (int(seconds), int(microseconds))
2380 def MergeTime(timetuple):
2381 """Merges a tuple into time as a floating point number.
2383 @param timetuple: Time as tuple, (seconds, microseconds)
2384 @type timetuple: tuple
2385 @return: Time as a floating point number expressed in seconds
2388 (seconds, microseconds) = timetuple
2390 assert 0 <= seconds, \
2391 "Seconds must be larger than or equal to 0, but are %s" % seconds
2392 assert 0 <= microseconds <= 999999, \
2393 "Microseconds must be 0-999999, but are %s" % microseconds
2395 return float(seconds) + (float(microseconds) * 0.000001)
2398 class LogFileHandler(logging.FileHandler):
2399 """Log handler that doesn't fallback to stderr.
2401 When an error occurs while writing on the logfile, logging.FileHandler tries
2402 to log on stderr. This doesn't work in ganeti since stderr is redirected to
2403 the logfile. This class avoids failures reporting errors to /dev/console.
2406 def __init__(self, filename, mode="a", encoding=None):
2407 """Open the specified file and use it as the stream for logging.
2409 Also open /dev/console to report errors while logging.
2412 logging.FileHandler.__init__(self, filename, mode, encoding)
2413 self.console = open(constants.DEV_CONSOLE, "a")
2415 def handleError(self, record): # pylint: disable-msg=C0103
2416 """Handle errors which occur during an emit() call.
2418 Try to handle errors with FileHandler method, if it fails write to
2423 logging.FileHandler.handleError(self, record)
2424 except Exception: # pylint: disable-msg=W0703
2426 self.console.write("Cannot log message:\n%s\n" % self.format(record))
2427 except Exception: # pylint: disable-msg=W0703
2428 # Log handler tried everything it could, now just give up
2432 def SetupLogging(logfile, debug=0, stderr_logging=False, program="",
2433 multithreaded=False, syslog=constants.SYSLOG_USAGE,
2434 console_logging=False):
2435 """Configures the logging module.
2438 @param logfile: the filename to which we should log
2439 @type debug: integer
2440 @param debug: if greater than zero, enable debug messages, otherwise
2441 only those at C{INFO} and above level
2442 @type stderr_logging: boolean
2443 @param stderr_logging: whether we should also log to the standard error
2445 @param program: the name under which we should log messages
2446 @type multithreaded: boolean
2447 @param multithreaded: if True, will add the thread name to the log file
2448 @type syslog: string
2449 @param syslog: one of 'no', 'yes', 'only':
2450 - if no, syslog is not used
2451 - if yes, syslog is used (in addition to file-logging)
2452 - if only, only syslog is used
2453 @type console_logging: boolean
2454 @param console_logging: if True, will use a FileHandler which falls back to
2455 the system console if logging fails
2456 @raise EnvironmentError: if we can't open the log file and
2457 syslog/stderr logging is disabled
2460 fmt = "%(asctime)s: " + program + " pid=%(process)d"
2461 sft = program + "[%(process)d]:"
2463 fmt += "/%(threadName)s"
2464 sft += " (%(threadName)s)"
2466 fmt += " %(module)s:%(lineno)s"
2467 # no debug info for syslog loggers
2468 fmt += " %(levelname)s %(message)s"
2469 # yes, we do want the textual level, as remote syslog will probably
2470 # lose the error level, and it's easier to grep for it
2471 sft += " %(levelname)s %(message)s"
2472 formatter = logging.Formatter(fmt)
2473 sys_fmt = logging.Formatter(sft)
2475 root_logger = logging.getLogger("")
2476 root_logger.setLevel(logging.NOTSET)
2478 # Remove all previously setup handlers
2479 for handler in root_logger.handlers:
2481 root_logger.removeHandler(handler)
2484 stderr_handler = logging.StreamHandler()
2485 stderr_handler.setFormatter(formatter)
2487 stderr_handler.setLevel(logging.NOTSET)
2489 stderr_handler.setLevel(logging.CRITICAL)
2490 root_logger.addHandler(stderr_handler)
2492 if syslog in (constants.SYSLOG_YES, constants.SYSLOG_ONLY):
2493 facility = logging.handlers.SysLogHandler.LOG_DAEMON
2494 syslog_handler = logging.handlers.SysLogHandler(constants.SYSLOG_SOCKET,
2496 syslog_handler.setFormatter(sys_fmt)
2497 # Never enable debug over syslog
2498 syslog_handler.setLevel(logging.INFO)
2499 root_logger.addHandler(syslog_handler)
2501 if syslog != constants.SYSLOG_ONLY:
2502 # this can fail, if the logging directories are not setup or we have
2503 # a permisssion problem; in this case, it's best to log but ignore
2504 # the error if stderr_logging is True, and if false we re-raise the
2505 # exception since otherwise we could run but without any logs at all
2508 logfile_handler = LogFileHandler(logfile)
2510 logfile_handler = logging.FileHandler(logfile)
2511 logfile_handler.setFormatter(formatter)
2513 logfile_handler.setLevel(logging.DEBUG)
2515 logfile_handler.setLevel(logging.INFO)
2516 root_logger.addHandler(logfile_handler)
2517 except EnvironmentError:
2518 if stderr_logging or syslog == constants.SYSLOG_YES:
2519 logging.exception("Failed to enable logging to file '%s'", logfile)
2521 # we need to re-raise the exception
2525 def IsNormAbsPath(path):
2526 """Check whether a path is absolute and also normalized
2528 This avoids things like /dir/../../other/path to be valid.
2531 return os.path.normpath(path) == path and os.path.isabs(path)
2534 def PathJoin(*args):
2535 """Safe-join a list of path components.
2538 - the first argument must be an absolute path
2539 - no component in the path must have backtracking (e.g. /../),
2540 since we check for normalization at the end
2542 @param args: the path components to be joined
2543 @raise ValueError: for invalid paths
2546 # ensure we're having at least one path passed in
2548 # ensure the first component is an absolute and normalized path name
2550 if not IsNormAbsPath(root):
2551 raise ValueError("Invalid parameter to PathJoin: '%s'" % str(args[0]))
2552 result = os.path.join(*args)
2553 # ensure that the whole path is normalized
2554 if not IsNormAbsPath(result):
2555 raise ValueError("Invalid parameters to PathJoin: '%s'" % str(args))
2556 # check that we're still under the original prefix
2557 prefix = os.path.commonprefix([root, result])
2559 raise ValueError("Error: path joining resulted in different prefix"
2560 " (%s != %s)" % (prefix, root))
2564 def TailFile(fname, lines=20):
2565 """Return the last lines from a file.
2567 @note: this function will only read and parse the last 4KB of
2568 the file; if the lines are very long, it could be that less
2569 than the requested number of lines are returned
2571 @param fname: the file name
2573 @param lines: the (maximum) number of lines to return
2576 fd = open(fname, "r")
2580 pos = max(0, pos-4096)
2582 raw_data = fd.read()
2586 rows = raw_data.splitlines()
2587 return rows[-lines:]
2590 def FormatTimestampWithTZ(secs):
2591 """Formats a Unix timestamp with the local timezone.
2594 return time.strftime("%F %T %Z", time.gmtime(secs))
2597 def _ParseAsn1Generalizedtime(value):
2598 """Parses an ASN1 GENERALIZEDTIME timestamp as used by pyOpenSSL.
2601 @param value: ASN1 GENERALIZEDTIME timestamp
2604 m = re.match(r"^(\d+)([-+]\d\d)(\d\d)$", value)
2607 asn1time = m.group(1)
2608 hours = int(m.group(2))
2609 minutes = int(m.group(3))
2610 utcoffset = (60 * hours) + minutes
2612 if not value.endswith("Z"):
2613 raise ValueError("Missing timezone")
2614 asn1time = value[:-1]
2617 parsed = time.strptime(asn1time, "%Y%m%d%H%M%S")
2619 tt = datetime.datetime(*(parsed[:7])) - datetime.timedelta(minutes=utcoffset)
2621 return calendar.timegm(tt.utctimetuple())
2624 def GetX509CertValidity(cert):
2625 """Returns the validity period of the certificate.
2627 @type cert: OpenSSL.crypto.X509
2628 @param cert: X509 certificate object
2631 # The get_notBefore and get_notAfter functions are only supported in
2632 # pyOpenSSL 0.7 and above.
2634 get_notbefore_fn = cert.get_notBefore
2635 except AttributeError:
2638 not_before_asn1 = get_notbefore_fn()
2640 if not_before_asn1 is None:
2643 not_before = _ParseAsn1Generalizedtime(not_before_asn1)
2646 get_notafter_fn = cert.get_notAfter
2647 except AttributeError:
2650 not_after_asn1 = get_notafter_fn()
2652 if not_after_asn1 is None:
2655 not_after = _ParseAsn1Generalizedtime(not_after_asn1)
2657 return (not_before, not_after)
2660 def _VerifyCertificateInner(expired, not_before, not_after, now,
2661 warn_days, error_days):
2662 """Verifies certificate validity.
2665 @param expired: Whether pyOpenSSL considers the certificate as expired
2666 @type not_before: number or None
2667 @param not_before: Unix timestamp before which certificate is not valid
2668 @type not_after: number or None
2669 @param not_after: Unix timestamp after which certificate is invalid
2671 @param now: Current time as Unix timestamp
2672 @type warn_days: number or None
2673 @param warn_days: How many days before expiration a warning should be reported
2674 @type error_days: number or None
2675 @param error_days: How many days before expiration an error should be reported
2679 msg = "Certificate is expired"
2681 if not_before is not None and not_after is not None:
2682 msg += (" (valid from %s to %s)" %
2683 (FormatTimestampWithTZ(not_before),
2684 FormatTimestampWithTZ(not_after)))
2685 elif not_before is not None:
2686 msg += " (valid from %s)" % FormatTimestampWithTZ(not_before)
2687 elif not_after is not None:
2688 msg += " (valid until %s)" % FormatTimestampWithTZ(not_after)
2690 return (CERT_ERROR, msg)
2692 elif not_before is not None and not_before > now:
2693 return (CERT_WARNING,
2694 "Certificate not yet valid (valid from %s)" %
2695 FormatTimestampWithTZ(not_before))
2697 elif not_after is not None:
2698 remaining_days = int((not_after - now) / (24 * 3600))
2700 msg = "Certificate expires in about %d days" % remaining_days
2702 if error_days is not None and remaining_days <= error_days:
2703 return (CERT_ERROR, msg)
2705 if warn_days is not None and remaining_days <= warn_days:
2706 return (CERT_WARNING, msg)
2711 def VerifyX509Certificate(cert, warn_days, error_days):
2712 """Verifies a certificate for LUVerifyCluster.
2714 @type cert: OpenSSL.crypto.X509
2715 @param cert: X509 certificate object
2716 @type warn_days: number or None
2717 @param warn_days: How many days before expiration a warning should be reported
2718 @type error_days: number or None
2719 @param error_days: How many days before expiration an error should be reported
2722 # Depending on the pyOpenSSL version, this can just return (None, None)
2723 (not_before, not_after) = GetX509CertValidity(cert)
2725 return _VerifyCertificateInner(cert.has_expired(), not_before, not_after,
2726 time.time(), warn_days, error_days)
2729 def SignX509Certificate(cert, key, salt):
2730 """Sign a X509 certificate.
2732 An RFC822-like signature header is added in front of the certificate.
2734 @type cert: OpenSSL.crypto.X509
2735 @param cert: X509 certificate object
2737 @param key: Key for HMAC
2739 @param salt: Salt for HMAC
2741 @return: Serialized and signed certificate in PEM format
2744 if not VALID_X509_SIGNATURE_SALT.match(salt):
2745 raise errors.GenericError("Invalid salt: %r" % salt)
2747 # Dumping as PEM here ensures the certificate is in a sane format
2748 cert_pem = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)
2750 return ("%s: %s/%s\n\n%s" %
2751 (constants.X509_CERT_SIGNATURE_HEADER, salt,
2752 Sha1Hmac(key, cert_pem, salt=salt),
2756 def _ExtractX509CertificateSignature(cert_pem):
2757 """Helper function to extract signature from X509 certificate.
2760 # Extract signature from original PEM data
2761 for line in cert_pem.splitlines():
2762 if line.startswith("---"):
2765 m = X509_SIGNATURE.match(line.strip())
2767 return (m.group("salt"), m.group("sign"))
2769 raise errors.GenericError("X509 certificate signature is missing")
2772 def LoadSignedX509Certificate(cert_pem, key):
2773 """Verifies a signed X509 certificate.
2775 @type cert_pem: string
2776 @param cert_pem: Certificate in PEM format and with signature header
2778 @param key: Key for HMAC
2779 @rtype: tuple; (OpenSSL.crypto.X509, string)
2780 @return: X509 certificate object and salt
2783 (salt, signature) = _ExtractX509CertificateSignature(cert_pem)
2786 cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert_pem)
2788 # Dump again to ensure it's in a sane format
2789 sane_pem = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)
2791 if not VerifySha1Hmac(key, sane_pem, signature, salt=salt):
2792 raise errors.GenericError("X509 certificate signature is invalid")
2797 def Sha1Hmac(key, text, salt=None):
2798 """Calculates the HMAC-SHA1 digest of a text.
2800 HMAC is defined in RFC2104.
2803 @param key: Secret key
2808 salted_text = salt + text
2812 return hmac.new(key, salted_text, compat.sha1).hexdigest()
2815 def VerifySha1Hmac(key, text, digest, salt=None):
2816 """Verifies the HMAC-SHA1 digest of a text.
2818 HMAC is defined in RFC2104.
2821 @param key: Secret key
2823 @type digest: string
2824 @param digest: Expected digest
2826 @return: Whether HMAC-SHA1 digest matches
2829 return digest.lower() == Sha1Hmac(key, text, salt=salt).lower()
2832 def SafeEncode(text):
2833 """Return a 'safe' version of a source string.
2835 This function mangles the input string and returns a version that
2836 should be safe to display/encode as ASCII. To this end, we first
2837 convert it to ASCII using the 'backslashreplace' encoding which
2838 should get rid of any non-ASCII chars, and then we process it
2839 through a loop copied from the string repr sources in the python; we
2840 don't use string_escape anymore since that escape single quotes and
2841 backslashes too, and that is too much; and that escaping is not
2842 stable, i.e. string_escape(string_escape(x)) != string_escape(x).
2844 @type text: str or unicode
2845 @param text: input data
2847 @return: a safe version of text
2850 if isinstance(text, unicode):
2851 # only if unicode; if str already, we handle it below
2852 text = text.encode('ascii', 'backslashreplace')
2862 elif c < 32 or c >= 127: # non-printable
2863 resu += "\\x%02x" % (c & 0xff)
2869 def UnescapeAndSplit(text, sep=","):
2870 """Split and unescape a string based on a given separator.
2872 This function splits a string based on a separator where the
2873 separator itself can be escape in order to be an element of the
2874 elements. The escaping rules are (assuming coma being the
2876 - a plain , separates the elements
2877 - a sequence \\\\, (double backslash plus comma) is handled as a
2878 backslash plus a separator comma
2879 - a sequence \, (backslash plus comma) is handled as a
2883 @param text: the string to split
2885 @param text: the separator
2887 @return: a list of strings
2890 # we split the list by sep (with no escaping at this stage)
2891 slist = text.split(sep)
2892 # next, we revisit the elements and if any of them ended with an odd
2893 # number of backslashes, then we join it with the next
2897 if e1.endswith("\\"):
2898 num_b = len(e1) - len(e1.rstrip("\\"))
2901 # here the backslashes remain (all), and will be reduced in
2903 rlist.append(e1 + sep + e2)
2906 # finally, replace backslash-something with something
2907 rlist = [re.sub(r"\\(.)", r"\1", v) for v in rlist]
2911 def CommaJoin(names):
2912 """Nicely join a set of identifiers.
2914 @param names: set, list or tuple
2915 @return: a string with the formatted results
2918 return ", ".join([str(val) for val in names])
2921 def BytesToMebibyte(value):
2922 """Converts bytes to mebibytes.
2925 @param value: Value in bytes
2927 @return: Value in mebibytes
2930 return int(round(value / (1024.0 * 1024.0), 0))
2933 def CalculateDirectorySize(path):
2934 """Calculates the size of a directory recursively.
2937 @param path: Path to directory
2939 @return: Size in mebibytes
2944 for (curpath, _, files) in os.walk(path):
2945 for filename in files:
2946 st = os.lstat(PathJoin(curpath, filename))
2949 return BytesToMebibyte(size)
2952 def GetMounts(filename=constants.PROC_MOUNTS):
2953 """Returns the list of mounted filesystems.
2955 This function is Linux-specific.
2957 @param filename: path of mounts file (/proc/mounts by default)
2958 @rtype: list of tuples
2959 @return: list of mount entries (device, mountpoint, fstype, options)
2962 # TODO(iustin): investigate non-Linux options (e.g. via mount output)
2964 mountlines = ReadFile(filename).splitlines()
2965 for line in mountlines:
2966 device, mountpoint, fstype, options, _ = line.split(None, 4)
2967 data.append((device, mountpoint, fstype, options))
2972 def GetFilesystemStats(path):
2973 """Returns the total and free space on a filesystem.
2976 @param path: Path on filesystem to be examined
2978 @return: tuple of (Total space, Free space) in mebibytes
2981 st = os.statvfs(path)
2983 fsize = BytesToMebibyte(st.f_bavail * st.f_frsize)
2984 tsize = BytesToMebibyte(st.f_blocks * st.f_frsize)
2985 return (tsize, fsize)
2988 def RunInSeparateProcess(fn, *args):
2989 """Runs a function in a separate process.
2991 Note: Only boolean return values are supported.
2994 @param fn: Function to be called
2996 @return: Function's result
3003 # In case the function uses temporary files
3004 ResetTempfileModule()
3007 result = int(bool(fn(*args)))
3008 assert result in (0, 1)
3009 except: # pylint: disable-msg=W0702
3010 logging.exception("Error while calling function in separate process")
3011 # 0 and 1 are reserved for the return value
3014 os._exit(result) # pylint: disable-msg=W0212
3018 # Avoid zombies and check exit code
3019 (_, status) = os.waitpid(pid, 0)
3021 if os.WIFSIGNALED(status):
3023 signum = os.WTERMSIG(status)
3025 exitcode = os.WEXITSTATUS(status)
3028 if not (exitcode in (0, 1) and signum is None):
3029 raise errors.GenericError("Child program failed (code=%s, signal=%s)" %
3032 return bool(exitcode)
3035 def IgnoreProcessNotFound(fn, *args, **kwargs):
3036 """Ignores ESRCH when calling a process-related function.
3038 ESRCH is raised when a process is not found.
3041 @return: Whether process was found
3046 except EnvironmentError, err:
3048 if err.errno == errno.ESRCH:
3055 def IgnoreSignals(fn, *args, **kwargs):
3056 """Tries to call a function ignoring failures due to EINTR.
3060 return fn(*args, **kwargs)
3061 except EnvironmentError, err:
3062 if err.errno == errno.EINTR:
3066 except (select.error, socket.error), err:
3067 # In python 2.6 and above select.error is an IOError, so it's handled
3068 # above, in 2.5 and below it's not, and it's handled here.
3069 if err.args and err.args[0] == errno.EINTR:
3076 """Locks a file using POSIX locks.
3079 @param fd: the file descriptor we need to lock
3083 fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
3084 except IOError, err:
3085 if err.errno == errno.EAGAIN:
3086 raise errors.LockError("File already locked")
3090 def FormatTime(val):
3091 """Formats a time value.
3093 @type val: float or None
3094 @param val: the timestamp as returned by time.time()
3095 @return: a string value or N/A if we don't have a valid timestamp
3098 if val is None or not isinstance(val, (int, float)):
3100 # these two codes works on Linux, but they are not guaranteed on all
3102 return time.strftime("%F %T", time.localtime(val))
3105 def FormatSeconds(secs):
3106 """Formats seconds for easier reading.
3109 @param secs: Number of seconds
3111 @return: Formatted seconds (e.g. "2d 9h 19m 49s")
3116 secs = round(secs, 0)
3119 # Negative values would be a bit tricky
3120 for unit, one in [("d", 24 * 60 * 60), ("h", 60 * 60), ("m", 60)]:
3121 (complete, secs) = divmod(secs, one)
3122 if complete or parts:
3123 parts.append("%d%s" % (complete, unit))
3125 parts.append("%ds" % secs)
3127 return " ".join(parts)
3130 def ReadWatcherPauseFile(filename, now=None, remove_after=3600):
3131 """Reads the watcher pause file.
3133 @type filename: string
3134 @param filename: Path to watcher pause file
3135 @type now: None, float or int
3136 @param now: Current time as Unix timestamp
3137 @type remove_after: int
3138 @param remove_after: Remove watcher pause file after specified amount of
3139 seconds past the pause end time
3146 value = ReadFile(filename)
3147 except IOError, err:
3148 if err.errno != errno.ENOENT:
3152 if value is not None:
3156 logging.warning(("Watcher pause file (%s) contains invalid value,"
3157 " removing it"), filename)
3158 RemoveFile(filename)
3161 if value is not None:
3162 # Remove file if it's outdated
3163 if now > (value + remove_after):
3164 RemoveFile(filename)
3173 class RetryTimeout(Exception):
3174 """Retry loop timed out.
3176 Any arguments which was passed by the retried function to RetryAgain will be
3177 preserved in RetryTimeout, if it is raised. If such argument was an exception
3178 the RaiseInner helper method will reraise it.
3181 def RaiseInner(self):
3182 if self.args and isinstance(self.args[0], Exception):
3185 raise RetryTimeout(*self.args)
3188 class RetryAgain(Exception):
3191 Any arguments passed to RetryAgain will be preserved, if a timeout occurs, as
3192 arguments to RetryTimeout. If an exception is passed, the RaiseInner() method
3193 of the RetryTimeout() method can be used to reraise it.
3198 class _RetryDelayCalculator(object):
3199 """Calculator for increasing delays.
3209 def __init__(self, start, factor, limit):
3210 """Initializes this class.
3213 @param start: Initial delay
3215 @param factor: Factor for delay increase
3216 @type limit: float or None
3217 @param limit: Upper limit for delay or None for no limit
3221 assert factor >= 1.0
3222 assert limit is None or limit >= 0.0
3225 self._factor = factor
3231 """Returns current delay and calculates the next one.
3234 current = self._next
3236 # Update for next run
3237 if self._limit is None or self._next < self._limit:
3238 self._next = min(self._limit, self._next * self._factor)
3243 #: Special delay to specify whole remaining timeout
3244 RETRY_REMAINING_TIME = object()
3247 def Retry(fn, delay, timeout, args=None, wait_fn=time.sleep,
3248 _time_fn=time.time):
3249 """Call a function repeatedly until it succeeds.
3251 The function C{fn} is called repeatedly until it doesn't throw L{RetryAgain}
3252 anymore. Between calls a delay, specified by C{delay}, is inserted. After a
3253 total of C{timeout} seconds, this function throws L{RetryTimeout}.
3255 C{delay} can be one of the following:
3256 - callable returning the delay length as a float
3257 - Tuple of (start, factor, limit)
3258 - L{RETRY_REMAINING_TIME} to sleep until the timeout expires (this is
3259 useful when overriding L{wait_fn} to wait for an external event)
3260 - A static delay as a number (int or float)
3263 @param fn: Function to be called
3264 @param delay: Either a callable (returning the delay), a tuple of (start,
3265 factor, limit) (see L{_RetryDelayCalculator}),
3266 L{RETRY_REMAINING_TIME} or a number (int or float)
3267 @type timeout: float
3268 @param timeout: Total timeout
3269 @type wait_fn: callable
3270 @param wait_fn: Waiting function
3271 @return: Return value of function
3275 assert callable(wait_fn)
3276 assert callable(_time_fn)
3281 end_time = _time_fn() + timeout
3284 # External function to calculate delay
3287 elif isinstance(delay, (tuple, list)):
3288 # Increasing delay with optional upper boundary
3289 (start, factor, limit) = delay
3290 calc_delay = _RetryDelayCalculator(start, factor, limit)
3292 elif delay is RETRY_REMAINING_TIME:
3293 # Always use the remaining time
3298 calc_delay = lambda: delay
3300 assert calc_delay is None or callable(calc_delay)
3305 # pylint: disable-msg=W0142
3307 except RetryAgain, err:
3308 retry_args = err.args
3309 except RetryTimeout:
3310 raise errors.ProgrammerError("Nested retry loop detected that didn't"
3311 " handle RetryTimeout")
3313 remaining_time = end_time - _time_fn()
3315 if remaining_time < 0.0:
3316 # pylint: disable-msg=W0142
3317 raise RetryTimeout(*retry_args)
3319 assert remaining_time >= 0.0
3321 if calc_delay is None:
3322 wait_fn(remaining_time)
3324 current_delay = calc_delay()
3325 if current_delay > 0.0:
3326 wait_fn(current_delay)
3329 def GetClosedTempfile(*args, **kwargs):
3330 """Creates a temporary file and returns its path.
3333 (fd, path) = tempfile.mkstemp(*args, **kwargs)
3338 def GenerateSelfSignedX509Cert(common_name, validity):
3339 """Generates a self-signed X509 certificate.
3341 @type common_name: string
3342 @param common_name: commonName value
3344 @param validity: Validity for certificate in seconds
3347 # Create private and public key
3348 key = OpenSSL.crypto.PKey()
3349 key.generate_key(OpenSSL.crypto.TYPE_RSA, constants.RSA_KEY_BITS)
3351 # Create self-signed certificate
3352 cert = OpenSSL.crypto.X509()
3354 cert.get_subject().CN = common_name
3355 cert.set_serial_number(1)
3356 cert.gmtime_adj_notBefore(0)
3357 cert.gmtime_adj_notAfter(validity)
3358 cert.set_issuer(cert.get_subject())
3359 cert.set_pubkey(key)
3360 cert.sign(key, constants.X509_CERT_SIGN_DIGEST)
3362 key_pem = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key)
3363 cert_pem = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)
3365 return (key_pem, cert_pem)
3368 def GenerateSelfSignedSslCert(filename, common_name=constants.X509_CERT_CN,
3369 validity=constants.X509_CERT_DEFAULT_VALIDITY):
3370 """Legacy function to generate self-signed X509 certificate.
3373 @param filename: path to write certificate to
3374 @type common_name: string
3375 @param common_name: commonName value
3377 @param validity: validity of certificate in number of days
3380 # TODO: Investigate using the cluster name instead of X505_CERT_CN for
3381 # common_name, as cluster-renames are very seldom, and it'd be nice if RAPI
3382 # and node daemon certificates have the proper Subject/Issuer.
3383 (key_pem, cert_pem) = GenerateSelfSignedX509Cert(common_name,
3384 validity * 24 * 60 * 60)
3386 WriteFile(filename, mode=0400, data=key_pem + cert_pem)
3389 class FileLock(object):
3390 """Utility class for file locks.
3393 def __init__(self, fd, filename):
3394 """Constructor for FileLock.
3397 @param fd: File object
3399 @param filename: Path of the file opened at I{fd}
3403 self.filename = filename
3406 def Open(cls, filename):
3407 """Creates and opens a file to be used as a file-based lock.
3409 @type filename: string
3410 @param filename: path to the file to be locked
3413 # Using "os.open" is necessary to allow both opening existing file
3414 # read/write and creating if not existing. Vanilla "open" will truncate an
3415 # existing file -or- allow creating if not existing.
3416 return cls(os.fdopen(os.open(filename, os.O_RDWR | os.O_CREAT), "w+"),
3423 """Close the file and release the lock.
3426 if hasattr(self, "fd") and self.fd:
3430 def _flock(self, flag, blocking, timeout, errmsg):
3431 """Wrapper for fcntl.flock.
3434 @param flag: operation flag
3435 @type blocking: bool
3436 @param blocking: whether the operation should be done in blocking mode.
3437 @type timeout: None or float
3438 @param timeout: for how long the operation should be retried (implies
3440 @type errmsg: string
3441 @param errmsg: error message in case operation fails.
3444 assert self.fd, "Lock was closed"
3445 assert timeout is None or timeout >= 0, \
3446 "If specified, timeout must be positive"
3447 assert not (flag & fcntl.LOCK_NB), "LOCK_NB must not be set"
3449 # When a timeout is used, LOCK_NB must always be set
3450 if not (timeout is None and blocking):
3451 flag |= fcntl.LOCK_NB
3454 self._Lock(self.fd, flag, timeout)
3457 Retry(self._Lock, (0.1, 1.2, 1.0), timeout,
3458 args=(self.fd, flag, timeout))
3459 except RetryTimeout:
3460 raise errors.LockError(errmsg)
3463 def _Lock(fd, flag, timeout):
3465 fcntl.flock(fd, flag)
3466 except IOError, err:
3467 if timeout is not None and err.errno == errno.EAGAIN:
3470 logging.exception("fcntl.flock failed")
3473 def Exclusive(self, blocking=False, timeout=None):
3474 """Locks the file in exclusive mode.
3476 @type blocking: boolean
3477 @param blocking: whether to block and wait until we
3478 can lock the file or return immediately
3479 @type timeout: int or None
3480 @param timeout: if not None, the duration to wait for the lock
3484 self._flock(fcntl.LOCK_EX, blocking, timeout,
3485 "Failed to lock %s in exclusive mode" % self.filename)
3487 def Shared(self, blocking=False, timeout=None):
3488 """Locks the file in shared mode.
3490 @type blocking: boolean
3491 @param blocking: whether to block and wait until we
3492 can lock the file or return immediately
3493 @type timeout: int or None
3494 @param timeout: if not None, the duration to wait for the lock
3498 self._flock(fcntl.LOCK_SH, blocking, timeout,
3499 "Failed to lock %s in shared mode" % self.filename)
3501 def Unlock(self, blocking=True, timeout=None):
3502 """Unlocks the file.
3504 According to C{flock(2)}, unlocking can also be a nonblocking
3507 To make a non-blocking request, include LOCK_NB with any of the above
3510 @type blocking: boolean
3511 @param blocking: whether to block and wait until we
3512 can lock the file or return immediately
3513 @type timeout: int or None
3514 @param timeout: if not None, the duration to wait for the lock
3518 self._flock(fcntl.LOCK_UN, blocking, timeout,
3519 "Failed to unlock %s" % self.filename)
3523 """Splits data chunks into lines separated by newline.
3525 Instances provide a file-like interface.
3528 def __init__(self, line_fn, *args):
3529 """Initializes this class.
3531 @type line_fn: callable
3532 @param line_fn: Function called for each line, first parameter is line
3533 @param args: Extra arguments for L{line_fn}
3536 assert callable(line_fn)
3539 # Python 2.4 doesn't have functools.partial yet
3541 lambda line: line_fn(line, *args) # pylint: disable-msg=W0142
3543 self._line_fn = line_fn
3545 self._lines = collections.deque()
3548 def write(self, data):
3549 parts = (self._buffer + data).split("\n")
3550 self._buffer = parts.pop()
3551 self._lines.extend(parts)
3555 self._line_fn(self._lines.popleft().rstrip("\r\n"))
3560 self._line_fn(self._buffer)
3563 def SignalHandled(signums):
3564 """Signal Handled decoration.
3566 This special decorator installs a signal handler and then calls the target
3567 function. The function must accept a 'signal_handlers' keyword argument,
3568 which will contain a dict indexed by signal number, with SignalHandler
3571 The decorator can be safely stacked with iself, to handle multiple signals
3572 with different handlers.
3575 @param signums: signals to intercept
3579 def sig_function(*args, **kwargs):
3580 assert 'signal_handlers' not in kwargs or \
3581 kwargs['signal_handlers'] is None or \
3582 isinstance(kwargs['signal_handlers'], dict), \
3583 "Wrong signal_handlers parameter in original function call"
3584 if 'signal_handlers' in kwargs and kwargs['signal_handlers'] is not None:
3585 signal_handlers = kwargs['signal_handlers']
3587 signal_handlers = {}
3588 kwargs['signal_handlers'] = signal_handlers
3589 sighandler = SignalHandler(signums)
3592 signal_handlers[sig] = sighandler
3593 return fn(*args, **kwargs)
3600 class SignalWakeupFd(object):
3602 # This is only supported in Python 2.5 and above (some distributions
3603 # backported it to Python 2.4)
3604 _set_wakeup_fd_fn = signal.set_wakeup_fd
3605 except AttributeError:
3607 def _SetWakeupFd(self, _): # pylint: disable-msg=R0201
3610 def _SetWakeupFd(self, fd):
3611 return self._set_wakeup_fd_fn(fd)
3614 """Initializes this class.
3617 (read_fd, write_fd) = os.pipe()
3619 # Once these succeeded, the file descriptors will be closed automatically.
3620 # Buffer size 0 is important, otherwise .read() with a specified length
3621 # might buffer data and the file descriptors won't be marked readable.
3622 self._read_fh = os.fdopen(read_fd, "r", 0)
3623 self._write_fh = os.fdopen(write_fd, "w", 0)
3625 self._previous = self._SetWakeupFd(self._write_fh.fileno())
3628 self.fileno = self._read_fh.fileno
3629 self.read = self._read_fh.read
3632 """Restores the previous wakeup file descriptor.
3635 if hasattr(self, "_previous") and self._previous is not None:
3636 self._SetWakeupFd(self._previous)
3637 self._previous = None
3640 """Notifies the wakeup file descriptor.
3643 self._write_fh.write("\0")
3646 """Called before object deletion.
3652 class SignalHandler(object):
3653 """Generic signal handler class.
3655 It automatically restores the original handler when deconstructed or
3656 when L{Reset} is called. You can either pass your own handler
3657 function in or query the L{called} attribute to detect whether the
3661 @ivar signum: the signals we handle
3662 @type called: boolean
3663 @ivar called: tracks whether any of the signals have been raised
3666 def __init__(self, signum, handler_fn=None, wakeup=None):
3667 """Constructs a new SignalHandler instance.
3669 @type signum: int or list of ints
3670 @param signum: Single signal number or set of signal numbers
3671 @type handler_fn: callable
3672 @param handler_fn: Signal handling function
3675 assert handler_fn is None or callable(handler_fn)
3677 self.signum = set(signum)
3680 self._handler_fn = handler_fn
3681 self._wakeup = wakeup
3685 for signum in self.signum:
3687 prev_handler = signal.signal(signum, self._HandleSignal)
3689 self._previous[signum] = prev_handler
3691 # Restore previous handler
3692 signal.signal(signum, prev_handler)
3695 # Reset all handlers
3697 # Here we have a race condition: a handler may have already been called,
3698 # but there's not much we can do about it at this point.
3705 """Restore previous handler.
3707 This will reset all the signals to their previous handlers.
3710 for signum, prev_handler in self._previous.items():
3711 signal.signal(signum, prev_handler)
3712 # If successful, remove from dict
3713 del self._previous[signum]
3716 """Unsets the L{called} flag.
3718 This function can be used in case a signal may arrive several times.
3723 def _HandleSignal(self, signum, frame):
3724 """Actual signal handling function.
3727 # This is not nice and not absolutely atomic, but it appears to be the only
3728 # solution in Python -- there are no atomic types.
3732 # Notify whoever is interested in signals
3733 self._wakeup.Notify()
3735 if self._handler_fn:
3736 self._handler_fn(signum, frame)
3739 class FieldSet(object):
3740 """A simple field set.
3742 Among the features are:
3743 - checking if a string is among a list of static string or regex objects
3744 - checking if a whole list of string matches
3745 - returning the matching groups from a regex match
3747 Internally, all fields are held as regular expression objects.
3750 def __init__(self, *items):
3751 self.items = [re.compile("^%s$" % value) for value in items]
3753 def Extend(self, other_set):
3754 """Extend the field set with the items from another one"""
3755 self.items.extend(other_set.items)
3757 def Matches(self, field):
3758 """Checks if a field matches the current set
3761 @param field: the string to match
3762 @return: either None or a regular expression match object
3765 for m in itertools.ifilter(None, (val.match(field) for val in self.items)):
3769 def NonMatching(self, items):
3770 """Returns the list of fields not matching the current set
3773 @param items: the list of fields to check
3775 @return: list of non-matching fields
3778 return [val for val in items if not self.Matches(val)]