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
64 from ganeti import netutils
68 _re_shell_unquoted = re.compile('^[-.,=:/_+@A-Za-z0-9]+$')
72 #: when set to True, L{RunCmd} is disabled
75 _RANDOM_UUID_FILE = "/proc/sys/kernel/random/uuid"
77 HEX_CHAR_RE = r"[a-zA-Z0-9]"
78 VALID_X509_SIGNATURE_SALT = re.compile("^%s+$" % HEX_CHAR_RE, re.S)
79 X509_SIGNATURE = re.compile(r"^%s:\s*(?P<salt>%s+)/(?P<sign>%s+)$" %
80 (re.escape(constants.X509_CERT_SIGNATURE_HEADER),
81 HEX_CHAR_RE, HEX_CHAR_RE),
84 _VALID_SERVICE_NAME_RE = re.compile("^[-_.a-zA-Z0-9]{1,128}$")
86 # Certificate verification results
88 CERT_ERROR) = range(1, 3)
90 # Flags for mlockall() (from bits/mman.h)
95 class RunResult(object):
96 """Holds the result of running external programs.
99 @ivar exit_code: the exit code of the program, or None (if the program
101 @type signal: int or None
102 @ivar signal: the signal that caused the program to finish, or None
103 (if the program wasn't terminated by a signal)
105 @ivar stdout: the standard output of the program
107 @ivar stderr: the standard error of the program
108 @type failed: boolean
109 @ivar failed: True in case the program was
110 terminated by a signal or exited with a non-zero exit code
111 @ivar fail_reason: a string detailing the termination reason
114 __slots__ = ["exit_code", "signal", "stdout", "stderr",
115 "failed", "fail_reason", "cmd"]
118 def __init__(self, exit_code, signal_, stdout, stderr, cmd):
120 self.exit_code = exit_code
121 self.signal = signal_
124 self.failed = (signal_ is not None or exit_code != 0)
126 if self.signal is not None:
127 self.fail_reason = "terminated by signal %s" % self.signal
128 elif self.exit_code is not None:
129 self.fail_reason = "exited with exit code %s" % self.exit_code
131 self.fail_reason = "unable to determine termination reason"
134 logging.debug("Command '%s' failed (%s); output: %s",
135 self.cmd, self.fail_reason, self.output)
137 def _GetOutput(self):
138 """Returns the combined stdout and stderr for easier usage.
141 return self.stdout + self.stderr
143 output = property(_GetOutput, None, None, "Return full output")
146 def _BuildCmdEnvironment(env, reset):
147 """Builds the environment for an external program.
153 cmd_env = os.environ.copy()
154 cmd_env["LC_ALL"] = "C"
162 def RunCmd(cmd, env=None, output=None, cwd="/", reset_env=False):
163 """Execute a (shell) command.
165 The command should not read from its standard input, as it will be
168 @type cmd: string or list
169 @param cmd: Command to run
171 @param env: Additional environment variables
173 @param output: if desired, the output of the command can be
174 saved in a file instead of the RunResult instance; this
175 parameter denotes the file name (if not None)
177 @param cwd: if specified, will be used as the working
178 directory for the command; the default will be /
179 @type reset_env: boolean
180 @param reset_env: whether to reset or keep the default os environment
182 @return: RunResult instance
183 @raise errors.ProgrammerError: if we call this when forks are disabled
187 raise errors.ProgrammerError("utils.RunCmd() called with fork() disabled")
189 if isinstance(cmd, basestring):
193 cmd = [str(val) for val in cmd]
194 strcmd = ShellQuoteArgs(cmd)
198 logging.debug("RunCmd %s, output file '%s'", strcmd, output)
200 logging.debug("RunCmd %s", strcmd)
202 cmd_env = _BuildCmdEnvironment(env, reset_env)
206 out, err, status = _RunCmdPipe(cmd, cmd_env, shell, cwd)
208 status = _RunCmdFile(cmd, cmd_env, shell, output, cwd)
211 if err.errno == errno.ENOENT:
212 raise errors.OpExecError("Can't execute '%s': not found (%s)" %
224 return RunResult(exitcode, signal_, out, err, strcmd)
227 def StartDaemon(cmd, env=None, cwd="/", output=None, output_fd=None,
229 """Start a daemon process after forking twice.
231 @type cmd: string or list
232 @param cmd: Command to run
234 @param env: Additional environment variables
236 @param cwd: Working directory for the program
238 @param output: Path to file in which to save the output
240 @param output_fd: File descriptor for output
241 @type pidfile: string
242 @param pidfile: Process ID file
244 @return: Daemon process ID
245 @raise errors.ProgrammerError: if we call this when forks are disabled
249 raise errors.ProgrammerError("utils.StartDaemon() called with fork()"
252 if output and not (bool(output) ^ (output_fd is not None)):
253 raise errors.ProgrammerError("Only one of 'output' and 'output_fd' can be"
256 if isinstance(cmd, basestring):
257 cmd = ["/bin/sh", "-c", cmd]
259 strcmd = ShellQuoteArgs(cmd)
262 logging.debug("StartDaemon %s, output file '%s'", strcmd, output)
264 logging.debug("StartDaemon %s", strcmd)
266 cmd_env = _BuildCmdEnvironment(env, False)
268 # Create pipe for sending PID back
269 (pidpipe_read, pidpipe_write) = os.pipe()
272 # Create pipe for sending error messages
273 (errpipe_read, errpipe_write) = os.pipe()
280 # Child process, won't return
281 _StartDaemonChild(errpipe_read, errpipe_write,
282 pidpipe_read, pidpipe_write,
284 output, output_fd, pidfile)
286 # Well, maybe child process failed
287 os._exit(1) # pylint: disable-msg=W0212
289 _CloseFDNoErr(errpipe_write)
291 # Wait for daemon to be started (or an error message to arrive) and read
292 # up to 100 KB as an error message
293 errormsg = RetryOnSignal(os.read, errpipe_read, 100 * 1024)
295 _CloseFDNoErr(errpipe_read)
297 _CloseFDNoErr(pidpipe_write)
299 # Read up to 128 bytes for PID
300 pidtext = RetryOnSignal(os.read, pidpipe_read, 128)
302 _CloseFDNoErr(pidpipe_read)
304 # Try to avoid zombies by waiting for child process
311 raise errors.OpExecError("Error when starting daemon process: %r" %
316 except (ValueError, TypeError), err:
317 raise errors.OpExecError("Error while trying to parse PID %r: %s" %
321 def _StartDaemonChild(errpipe_read, errpipe_write,
322 pidpipe_read, pidpipe_write,
324 output, fd_output, pidfile):
325 """Child process for starting daemon.
329 # Close parent's side
330 _CloseFDNoErr(errpipe_read)
331 _CloseFDNoErr(pidpipe_read)
333 # First child process
338 # And fork for the second time
341 # Exit first child process
342 os._exit(0) # pylint: disable-msg=W0212
344 # Make sure pipe is closed on execv* (and thereby notifies original process)
345 SetCloseOnExecFlag(errpipe_write, True)
347 # List of file descriptors to be left open
348 noclose_fds = [errpipe_write]
353 # TODO: Atomic replace with another locked file instead of writing into
355 fd_pidfile = os.open(pidfile, os.O_WRONLY | os.O_CREAT, 0600)
357 # Lock the PID file (and fail if not possible to do so). Any code
358 # wanting to send a signal to the daemon should try to lock the PID
359 # file before reading it. If acquiring the lock succeeds, the daemon is
360 # no longer running and the signal should not be sent.
363 os.write(fd_pidfile, "%d\n" % os.getpid())
364 except Exception, err:
365 raise Exception("Creating and locking PID file failed: %s" % err)
367 # Keeping the file open to hold the lock
368 noclose_fds.append(fd_pidfile)
370 SetCloseOnExecFlag(fd_pidfile, False)
375 fd_devnull = os.open(os.devnull, os.O_RDWR)
377 assert not output or (bool(output) ^ (fd_output is not None))
379 if fd_output is not None:
384 # TODO: Implement flag to set append=yes/no
385 fd_output = os.open(output, os.O_WRONLY | os.O_CREAT, 0600)
386 except EnvironmentError, err:
387 raise Exception("Opening output file failed: %s" % err)
389 fd_output = fd_devnull
391 # Redirect standard I/O
392 os.dup2(fd_devnull, 0)
393 os.dup2(fd_output, 1)
394 os.dup2(fd_output, 2)
396 # Send daemon PID to parent
397 RetryOnSignal(os.write, pidpipe_write, str(os.getpid()))
399 # Close all file descriptors except stdio and error message pipe
400 CloseFDs(noclose_fds=noclose_fds)
402 # Change working directory
406 os.execvp(args[0], args)
408 os.execvpe(args[0], args, env)
409 except: # pylint: disable-msg=W0702
411 # Report errors to original process
412 buf = str(sys.exc_info()[1])
414 RetryOnSignal(os.write, errpipe_write, buf)
415 except: # pylint: disable-msg=W0702
416 # Ignore errors in error handling
419 os._exit(1) # pylint: disable-msg=W0212
422 def _RunCmdPipe(cmd, env, via_shell, cwd):
423 """Run a command and return its output.
425 @type cmd: string or list
426 @param cmd: Command to run
428 @param env: The environment to use
429 @type via_shell: bool
430 @param via_shell: if we should run via the shell
432 @param cwd: the working directory for the program
434 @return: (out, err, status)
437 poller = select.poll()
438 child = subprocess.Popen(cmd, shell=via_shell,
439 stderr=subprocess.PIPE,
440 stdout=subprocess.PIPE,
441 stdin=subprocess.PIPE,
442 close_fds=True, env=env,
446 poller.register(child.stdout, select.POLLIN)
447 poller.register(child.stderr, select.POLLIN)
451 child.stdout.fileno(): (out, child.stdout),
452 child.stderr.fileno(): (err, child.stderr),
455 SetNonblockFlag(fd, True)
458 pollresult = RetryOnSignal(poller.poll)
460 for fd, event in pollresult:
461 if event & select.POLLIN or event & select.POLLPRI:
462 data = fdmap[fd][1].read()
463 # no data from read signifies EOF (the same as POLLHUP)
465 poller.unregister(fd)
468 fdmap[fd][0].write(data)
469 if (event & select.POLLNVAL or event & select.POLLHUP or
470 event & select.POLLERR):
471 poller.unregister(fd)
477 status = child.wait()
478 return out, err, status
481 def _RunCmdFile(cmd, env, via_shell, output, cwd):
482 """Run a command and save its output to a file.
484 @type cmd: string or list
485 @param cmd: Command to run
487 @param env: The environment to use
488 @type via_shell: bool
489 @param via_shell: if we should run via the shell
491 @param output: the filename in which to save the output
493 @param cwd: the working directory for the program
495 @return: the exit status
498 fh = open(output, "a")
500 child = subprocess.Popen(cmd, shell=via_shell,
501 stderr=subprocess.STDOUT,
503 stdin=subprocess.PIPE,
504 close_fds=True, env=env,
508 status = child.wait()
514 def SetCloseOnExecFlag(fd, enable):
515 """Sets or unsets the close-on-exec flag on a file descriptor.
518 @param fd: File descriptor
520 @param enable: Whether to set or unset it.
523 flags = fcntl.fcntl(fd, fcntl.F_GETFD)
526 flags |= fcntl.FD_CLOEXEC
528 flags &= ~fcntl.FD_CLOEXEC
530 fcntl.fcntl(fd, fcntl.F_SETFD, flags)
533 def SetNonblockFlag(fd, enable):
534 """Sets or unsets the O_NONBLOCK flag on on a file descriptor.
537 @param fd: File descriptor
539 @param enable: Whether to set or unset it
542 flags = fcntl.fcntl(fd, fcntl.F_GETFL)
545 flags |= os.O_NONBLOCK
547 flags &= ~os.O_NONBLOCK
549 fcntl.fcntl(fd, fcntl.F_SETFL, flags)
552 def RetryOnSignal(fn, *args, **kwargs):
553 """Calls a function again if it failed due to EINTR.
558 return fn(*args, **kwargs)
559 except EnvironmentError, err:
560 if err.errno != errno.EINTR:
562 except (socket.error, select.error), err:
563 # In python 2.6 and above select.error is an IOError, so it's handled
564 # above, in 2.5 and below it's not, and it's handled here.
565 if not (err.args and err.args[0] == errno.EINTR):
569 def RunParts(dir_name, env=None, reset_env=False):
570 """Run Scripts or programs in a directory
572 @type dir_name: string
573 @param dir_name: absolute path to a directory
575 @param env: The environment to use
576 @type reset_env: boolean
577 @param reset_env: whether to reset or keep the default os environment
578 @rtype: list of tuples
579 @return: list of (name, (one of RUNDIR_STATUS), RunResult)
585 dir_contents = ListVisibleFiles(dir_name)
587 logging.warning("RunParts: skipping %s (cannot list: %s)", dir_name, err)
590 for relname in sorted(dir_contents):
591 fname = PathJoin(dir_name, relname)
592 if not (os.path.isfile(fname) and os.access(fname, os.X_OK) and
593 constants.EXT_PLUGIN_MASK.match(relname) is not None):
594 rr.append((relname, constants.RUNPARTS_SKIP, None))
597 result = RunCmd([fname], env=env, reset_env=reset_env)
598 except Exception, err: # pylint: disable-msg=W0703
599 rr.append((relname, constants.RUNPARTS_ERR, str(err)))
601 rr.append((relname, constants.RUNPARTS_RUN, result))
606 def RemoveFile(filename):
607 """Remove a file ignoring some errors.
609 Remove a file, ignoring non-existing ones or directories. Other
613 @param filename: the file to be removed
619 if err.errno not in (errno.ENOENT, errno.EISDIR):
623 def RemoveDir(dirname):
624 """Remove an empty directory.
626 Remove a directory, ignoring non-existing ones.
627 Other errors are passed. This includes the case,
628 where the directory is not empty, so it can't be removed.
631 @param dirname: the empty directory to be removed
637 if err.errno != errno.ENOENT:
641 def RenameFile(old, new, mkdir=False, mkdir_mode=0750):
645 @param old: Original path
649 @param mkdir: Whether to create target directory if it doesn't exist
650 @type mkdir_mode: int
651 @param mkdir_mode: Mode for newly created directories
655 return os.rename(old, new)
657 # In at least one use case of this function, the job queue, directory
658 # creation is very rare. Checking for the directory before renaming is not
660 if mkdir and err.errno == errno.ENOENT:
661 # Create directory and try again
662 Makedirs(os.path.dirname(new), mode=mkdir_mode)
664 return os.rename(old, new)
669 def Makedirs(path, mode=0750):
670 """Super-mkdir; create a leaf directory and all intermediate ones.
672 This is a wrapper around C{os.makedirs} adding error handling not implemented
677 os.makedirs(path, mode)
679 # Ignore EEXIST. This is only handled in os.makedirs as included in
680 # Python 2.5 and above.
681 if err.errno != errno.EEXIST or not os.path.exists(path):
685 def ResetTempfileModule():
686 """Resets the random name generator of the tempfile module.
688 This function should be called after C{os.fork} in the child process to
689 ensure it creates a newly seeded random generator. Otherwise it would
690 generate the same random parts as the parent process. If several processes
691 race for the creation of a temporary file, this could lead to one not getting
695 # pylint: disable-msg=W0212
696 if hasattr(tempfile, "_once_lock") and hasattr(tempfile, "_name_sequence"):
697 tempfile._once_lock.acquire()
699 # Reset random name generator
700 tempfile._name_sequence = None
702 tempfile._once_lock.release()
704 logging.critical("The tempfile module misses at least one of the"
705 " '_once_lock' and '_name_sequence' attributes")
708 def _FingerprintFile(filename):
709 """Compute the fingerprint of a file.
711 If the file does not exist, a None will be returned
715 @param filename: the filename to checksum
717 @return: the hex digest of the sha checksum of the contents
721 if not (os.path.exists(filename) and os.path.isfile(filename)):
726 fp = compat.sha1_hash()
734 return fp.hexdigest()
737 def FingerprintFiles(files):
738 """Compute fingerprints for a list of files.
741 @param files: the list of filename to fingerprint
743 @return: a dictionary filename: fingerprint, holding only
749 for filename in files:
750 cksum = _FingerprintFile(filename)
752 ret[filename] = cksum
757 def ForceDictType(target, key_types, allowed_values=None):
758 """Force the values of a dict to have certain types.
761 @param target: the dict to update
762 @type key_types: dict
763 @param key_types: dict mapping target dict keys to types
764 in constants.ENFORCEABLE_TYPES
765 @type allowed_values: list
766 @keyword allowed_values: list of specially allowed values
769 if allowed_values is None:
772 if not isinstance(target, dict):
773 msg = "Expected dictionary, got '%s'" % target
774 raise errors.TypeEnforcementError(msg)
777 if key not in key_types:
778 msg = "Unknown key '%s'" % key
779 raise errors.TypeEnforcementError(msg)
781 if target[key] in allowed_values:
784 ktype = key_types[key]
785 if ktype not in constants.ENFORCEABLE_TYPES:
786 msg = "'%s' has non-enforceable type %s" % (key, ktype)
787 raise errors.ProgrammerError(msg)
789 if ktype in (constants.VTYPE_STRING, constants.VTYPE_MAYBE_STRING):
790 if target[key] is None and ktype == constants.VTYPE_MAYBE_STRING:
792 elif not isinstance(target[key], basestring):
793 if isinstance(target[key], bool) and not target[key]:
796 msg = "'%s' (value %s) is not a valid string" % (key, target[key])
797 raise errors.TypeEnforcementError(msg)
798 elif ktype == constants.VTYPE_BOOL:
799 if isinstance(target[key], basestring) and target[key]:
800 if target[key].lower() == constants.VALUE_FALSE:
802 elif target[key].lower() == constants.VALUE_TRUE:
805 msg = "'%s' (value %s) is not a valid boolean" % (key, target[key])
806 raise errors.TypeEnforcementError(msg)
811 elif ktype == constants.VTYPE_SIZE:
813 target[key] = ParseUnit(target[key])
814 except errors.UnitParseError, err:
815 msg = "'%s' (value %s) is not a valid size. error: %s" % \
816 (key, target[key], err)
817 raise errors.TypeEnforcementError(msg)
818 elif ktype == constants.VTYPE_INT:
820 target[key] = int(target[key])
821 except (ValueError, TypeError):
822 msg = "'%s' (value %s) is not a valid integer" % (key, target[key])
823 raise errors.TypeEnforcementError(msg)
826 def _GetProcStatusPath(pid):
827 """Returns the path for a PID's proc status file.
830 @param pid: Process ID
834 return "/proc/%d/status" % pid
837 def IsProcessAlive(pid):
838 """Check if a given pid exists on the system.
840 @note: zombie status is not handled, so zombie processes
841 will be returned as alive
843 @param pid: the process ID to check
845 @return: True if the process exists
852 except EnvironmentError, err:
853 if err.errno in (errno.ENOENT, errno.ENOTDIR):
855 elif err.errno == errno.EINVAL:
856 raise RetryAgain(err)
859 assert isinstance(pid, int), "pid must be an integer"
863 # /proc in a multiprocessor environment can have strange behaviors.
864 # Retry the os.stat a few times until we get a good result.
866 return Retry(_TryStat, (0.01, 1.5, 0.1), 0.5,
867 args=[_GetProcStatusPath(pid)])
868 except RetryTimeout, err:
872 def _ParseSigsetT(sigset):
873 """Parse a rendered sigset_t value.
875 This is the opposite of the Linux kernel's fs/proc/array.c:render_sigset_t
879 @param sigset: Rendered signal set from /proc/$pid/status
881 @return: Set of all enabled signal numbers
887 for ch in reversed(sigset):
890 # The following could be done in a loop, but it's easier to read and
891 # understand in the unrolled form
893 result.add(signum + 1)
895 result.add(signum + 2)
897 result.add(signum + 3)
899 result.add(signum + 4)
906 def _GetProcStatusField(pstatus, field):
907 """Retrieves a field from the contents of a proc status file.
909 @type pstatus: string
910 @param pstatus: Contents of /proc/$pid/status
912 @param field: Name of field whose value should be returned
916 for line in pstatus.splitlines():
917 parts = line.split(":", 1)
919 if len(parts) < 2 or parts[0] != field:
922 return parts[1].strip()
927 def IsProcessHandlingSignal(pid, signum, status_path=None):
928 """Checks whether a process is handling a signal.
931 @param pid: Process ID
933 @param signum: Signal number
937 if status_path is None:
938 status_path = _GetProcStatusPath(pid)
941 proc_status = ReadFile(status_path)
942 except EnvironmentError, err:
943 # In at least one case, reading /proc/$pid/status failed with ESRCH.
944 if err.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL, errno.ESRCH):
948 sigcgt = _GetProcStatusField(proc_status, "SigCgt")
950 raise RuntimeError("%s is missing 'SigCgt' field" % status_path)
952 # Now check whether signal is handled
953 return signum in _ParseSigsetT(sigcgt)
956 def ReadPidFile(pidfile):
957 """Read a pid from a file.
959 @type pidfile: string
960 @param pidfile: path to the file containing the pid
962 @return: The process id, if the file exists and contains a valid PID,
967 raw_data = ReadOneLineFile(pidfile)
968 except EnvironmentError, err:
969 if err.errno != errno.ENOENT:
970 logging.exception("Can't read pid file")
975 except (TypeError, ValueError), err:
976 logging.info("Can't parse pid file contents", exc_info=True)
982 def ReadLockedPidFile(path):
983 """Reads a locked PID file.
985 This can be used together with L{StartDaemon}.
988 @param path: Path to PID file
989 @return: PID as integer or, if file was unlocked or couldn't be opened, None
993 fd = os.open(path, os.O_RDONLY)
994 except EnvironmentError, err:
995 if err.errno == errno.ENOENT:
996 # PID file doesn't exist
1002 # Try to acquire lock
1004 except errors.LockError:
1005 # Couldn't lock, daemon is running
1006 return int(os.read(fd, 100))
1013 def MatchNameComponent(key, name_list, case_sensitive=True):
1014 """Try to match a name against a list.
1016 This function will try to match a name like test1 against a list
1017 like C{['test1.example.com', 'test2.example.com', ...]}. Against
1018 this list, I{'test1'} as well as I{'test1.example'} will match, but
1019 not I{'test1.ex'}. A multiple match will be considered as no match
1020 at all (e.g. I{'test1'} against C{['test1.example.com',
1021 'test1.example.org']}), except when the key fully matches an entry
1022 (e.g. I{'test1'} against C{['test1', 'test1.example.com']}).
1025 @param key: the name to be searched
1026 @type name_list: list
1027 @param name_list: the list of strings against which to search the key
1028 @type case_sensitive: boolean
1029 @param case_sensitive: whether to provide a case-sensitive match
1032 @return: None if there is no match I{or} if there are multiple matches,
1033 otherwise the element from the list which matches
1036 if key in name_list:
1040 if not case_sensitive:
1041 re_flags |= re.IGNORECASE
1043 mo = re.compile("^%s(\..*)?$" % re.escape(key), re_flags)
1046 for name in name_list:
1047 if mo.match(name) is not None:
1048 names_filtered.append(name)
1049 if not case_sensitive and key == name.upper():
1050 string_matches.append(name)
1052 if len(string_matches) == 1:
1053 return string_matches[0]
1054 if len(names_filtered) == 1:
1055 return names_filtered[0]
1059 def ValidateServiceName(name):
1060 """Validate the given service name.
1062 @type name: number or string
1063 @param name: Service name or port specification
1068 except (ValueError, TypeError):
1069 # Non-numeric service name
1070 valid = _VALID_SERVICE_NAME_RE.match(name)
1072 # Numeric port (protocols other than TCP or UDP might need adjustments
1074 valid = (numport >= 0 and numport < (1 << 16))
1077 raise errors.OpPrereqError("Invalid service name '%s'" % name,
1083 def ListVolumeGroups():
1084 """List volume groups and their size
1088 Dictionary with keys volume name and values
1089 the size of the volume
1092 command = "vgs --noheadings --units m --nosuffix -o name,size"
1093 result = RunCmd(command)
1098 for line in result.stdout.splitlines():
1100 name, size = line.split()
1101 size = int(float(size))
1102 except (IndexError, ValueError), err:
1103 logging.error("Invalid output from vgs (%s): %s", err, line)
1111 def BridgeExists(bridge):
1112 """Check whether the given bridge exists in the system
1115 @param bridge: the bridge name to check
1117 @return: True if it does
1120 return os.path.isdir("/sys/class/net/%s/bridge" % bridge)
1123 def NiceSort(name_list):
1124 """Sort a list of strings based on digit and non-digit groupings.
1126 Given a list of names C{['a1', 'a10', 'a11', 'a2']} this function
1127 will sort the list in the logical order C{['a1', 'a2', 'a10',
1130 The sort algorithm breaks each name in groups of either only-digits
1131 or no-digits. Only the first eight such groups are considered, and
1132 after that we just use what's left of the string.
1134 @type name_list: list
1135 @param name_list: the names to be sorted
1137 @return: a copy of the name list sorted with our algorithm
1140 _SORTER_BASE = "(\D+|\d+)"
1141 _SORTER_FULL = "^%s%s?%s?%s?%s?%s?%s?%s?.*$" % (_SORTER_BASE, _SORTER_BASE,
1142 _SORTER_BASE, _SORTER_BASE,
1143 _SORTER_BASE, _SORTER_BASE,
1144 _SORTER_BASE, _SORTER_BASE)
1145 _SORTER_RE = re.compile(_SORTER_FULL)
1146 _SORTER_NODIGIT = re.compile("^\D*$")
1148 """Attempts to convert a variable to integer."""
1149 if val is None or _SORTER_NODIGIT.match(val):
1154 to_sort = [([_TryInt(grp) for grp in _SORTER_RE.match(name).groups()], name)
1155 for name in name_list]
1157 return [tup[1] for tup in to_sort]
1160 def TryConvert(fn, val):
1161 """Try to convert a value ignoring errors.
1163 This function tries to apply function I{fn} to I{val}. If no
1164 C{ValueError} or C{TypeError} exceptions are raised, it will return
1165 the result, else it will return the original value. Any other
1166 exceptions are propagated to the caller.
1169 @param fn: function to apply to the value
1170 @param val: the value to be converted
1171 @return: The converted value if the conversion was successful,
1172 otherwise the original value.
1177 except (ValueError, TypeError):
1182 def IsValidShellParam(word):
1183 """Verifies is the given word is safe from the shell's p.o.v.
1185 This means that we can pass this to a command via the shell and be
1186 sure that it doesn't alter the command line and is passed as such to
1189 Note that we are overly restrictive here, in order to be on the safe
1193 @param word: the word to check
1195 @return: True if the word is 'safe'
1198 return bool(re.match("^[-a-zA-Z0-9._+/:%@]+$", word))
1201 def BuildShellCmd(template, *args):
1202 """Build a safe shell command line from the given arguments.
1204 This function will check all arguments in the args list so that they
1205 are valid shell parameters (i.e. they don't contain shell
1206 metacharacters). If everything is ok, it will return the result of
1210 @param template: the string holding the template for the
1213 @return: the expanded command line
1217 if not IsValidShellParam(word):
1218 raise errors.ProgrammerError("Shell argument '%s' contains"
1219 " invalid characters" % word)
1220 return template % args
1223 def FormatUnit(value, units):
1224 """Formats an incoming number of MiB with the appropriate unit.
1227 @param value: integer representing the value in MiB (1048576)
1229 @param units: the type of formatting we should do:
1230 - 'h' for automatic scaling
1235 @return: the formatted value (with suffix)
1238 if units not in ('m', 'g', 't', 'h'):
1239 raise errors.ProgrammerError("Invalid unit specified '%s'" % str(units))
1243 if units == 'm' or (units == 'h' and value < 1024):
1246 return "%d%s" % (round(value, 0), suffix)
1248 elif units == 'g' or (units == 'h' and value < (1024 * 1024)):
1251 return "%0.1f%s" % (round(float(value) / 1024, 1), suffix)
1256 return "%0.1f%s" % (round(float(value) / 1024 / 1024, 1), suffix)
1259 def ParseUnit(input_string):
1260 """Tries to extract number and scale from the given string.
1262 Input must be in the format C{NUMBER+ [DOT NUMBER+] SPACE*
1263 [UNIT]}. If no unit is specified, it defaults to MiB. Return value
1264 is always an int in MiB.
1267 m = re.match('^([.\d]+)\s*([a-zA-Z]+)?$', str(input_string))
1269 raise errors.UnitParseError("Invalid format")
1271 value = float(m.groups()[0])
1273 unit = m.groups()[1]
1275 lcunit = unit.lower()
1279 if lcunit in ('m', 'mb', 'mib'):
1280 # Value already in MiB
1283 elif lcunit in ('g', 'gb', 'gib'):
1286 elif lcunit in ('t', 'tb', 'tib'):
1287 value *= 1024 * 1024
1290 raise errors.UnitParseError("Unknown unit: %s" % unit)
1292 # Make sure we round up
1293 if int(value) < value:
1296 # Round up to the next multiple of 4
1299 value += 4 - value % 4
1304 def ParseCpuMask(cpu_mask):
1305 """Parse a CPU mask definition and return the list of CPU IDs.
1307 CPU mask format: comma-separated list of CPU IDs
1308 or dash-separated ID ranges
1309 Example: "0-2,5" -> "0,1,2,5"
1312 @param cpu_mask: CPU mask definition
1314 @return: list of CPU IDs
1320 for range_def in cpu_mask.split(","):
1321 boundaries = range_def.split("-")
1322 n_elements = len(boundaries)
1324 raise errors.ParseError("Invalid CPU ID range definition"
1325 " (only one hyphen allowed): %s" % range_def)
1327 lower = int(boundaries[0])
1328 except (ValueError, TypeError), err:
1329 raise errors.ParseError("Invalid CPU ID value for lower boundary of"
1330 " CPU ID range: %s" % str(err))
1332 higher = int(boundaries[-1])
1333 except (ValueError, TypeError), err:
1334 raise errors.ParseError("Invalid CPU ID value for higher boundary of"
1335 " CPU ID range: %s" % str(err))
1337 raise errors.ParseError("Invalid CPU ID range definition"
1338 " (%d > %d): %s" % (lower, higher, range_def))
1339 cpu_list.extend(range(lower, higher + 1))
1343 def AddAuthorizedKey(file_obj, key):
1344 """Adds an SSH public key to an authorized_keys file.
1346 @type file_obj: str or file handle
1347 @param file_obj: path to authorized_keys file
1349 @param key: string containing key
1352 key_fields = key.split()
1354 if isinstance(file_obj, basestring):
1355 f = open(file_obj, 'a+')
1362 # Ignore whitespace changes
1363 if line.split() == key_fields:
1365 nl = line.endswith('\n')
1369 f.write(key.rstrip('\r\n'))
1376 def RemoveAuthorizedKey(file_name, key):
1377 """Removes an SSH public key from an authorized_keys file.
1379 @type file_name: str
1380 @param file_name: path to authorized_keys file
1382 @param key: string containing key
1385 key_fields = key.split()
1387 fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
1389 out = os.fdopen(fd, 'w')
1391 f = open(file_name, 'r')
1394 # Ignore whitespace changes while comparing lines
1395 if line.split() != key_fields:
1399 os.rename(tmpname, file_name)
1409 def SetEtcHostsEntry(file_name, ip, hostname, aliases):
1410 """Sets the name of an IP address and hostname in /etc/hosts.
1412 @type file_name: str
1413 @param file_name: path to the file to modify (usually C{/etc/hosts})
1415 @param ip: the IP address
1417 @param hostname: the hostname to be added
1419 @param aliases: the list of aliases to add for the hostname
1422 # FIXME: use WriteFile + fn rather than duplicating its efforts
1423 # Ensure aliases are unique
1424 aliases = UniqueSequence([hostname] + aliases)[1:]
1426 fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
1428 out = os.fdopen(fd, 'w')
1430 f = open(file_name, 'r')
1433 fields = line.split()
1434 if fields and not fields[0].startswith('#') and ip == fields[0]:
1438 out.write("%s\t%s" % (ip, hostname))
1440 out.write(" %s" % ' '.join(aliases))
1445 os.chmod(tmpname, 0644)
1446 os.rename(tmpname, file_name)
1456 def AddHostToEtcHosts(hostname):
1457 """Wrapper around SetEtcHostsEntry.
1460 @param hostname: a hostname that will be resolved and added to
1461 L{constants.ETC_HOSTS}
1464 hi = netutils.HostInfo(name=hostname)
1465 SetEtcHostsEntry(constants.ETC_HOSTS, hi.ip, hi.name, [hi.ShortName()])
1468 def RemoveEtcHostsEntry(file_name, hostname):
1469 """Removes a hostname from /etc/hosts.
1471 IP addresses without names are removed from the file.
1473 @type file_name: str
1474 @param file_name: path to the file to modify (usually C{/etc/hosts})
1476 @param hostname: the hostname to be removed
1479 # FIXME: use WriteFile + fn rather than duplicating its efforts
1480 fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
1482 out = os.fdopen(fd, 'w')
1484 f = open(file_name, 'r')
1487 fields = line.split()
1488 if len(fields) > 1 and not fields[0].startswith('#'):
1490 if hostname in names:
1491 while hostname in names:
1492 names.remove(hostname)
1494 out.write("%s %s\n" % (fields[0], ' '.join(names)))
1501 os.chmod(tmpname, 0644)
1502 os.rename(tmpname, file_name)
1512 def RemoveHostFromEtcHosts(hostname):
1513 """Wrapper around RemoveEtcHostsEntry.
1516 @param hostname: hostname that will be resolved and its
1517 full and shot name will be removed from
1518 L{constants.ETC_HOSTS}
1521 hi = netutils.HostInfo(name=hostname)
1522 RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.name)
1523 RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.ShortName())
1526 def TimestampForFilename():
1527 """Returns the current time formatted for filenames.
1529 The format doesn't contain colons as some shells and applications them as
1533 return time.strftime("%Y-%m-%d_%H_%M_%S")
1536 def CreateBackup(file_name):
1537 """Creates a backup of a file.
1539 @type file_name: str
1540 @param file_name: file to be backed up
1542 @return: the path to the newly created backup
1543 @raise errors.ProgrammerError: for invalid file names
1546 if not os.path.isfile(file_name):
1547 raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
1550 prefix = ("%s.backup-%s." %
1551 (os.path.basename(file_name), TimestampForFilename()))
1552 dir_name = os.path.dirname(file_name)
1554 fsrc = open(file_name, 'rb')
1556 (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
1557 fdst = os.fdopen(fd, 'wb')
1559 logging.debug("Backing up %s at %s", file_name, backup_name)
1560 shutil.copyfileobj(fsrc, fdst)
1569 def ShellQuote(value):
1570 """Quotes shell argument according to POSIX.
1573 @param value: the argument to be quoted
1575 @return: the quoted value
1578 if _re_shell_unquoted.match(value):
1581 return "'%s'" % value.replace("'", "'\\''")
1584 def ShellQuoteArgs(args):
1585 """Quotes a list of shell arguments.
1588 @param args: list of arguments to be quoted
1590 @return: the quoted arguments concatenated with spaces
1593 return ' '.join([ShellQuote(i) for i in args])
1597 """Helper class to write scripts with indentation.
1602 def __init__(self, fh):
1603 """Initializes this class.
1609 def IncIndent(self):
1610 """Increase indentation level by 1.
1615 def DecIndent(self):
1616 """Decrease indentation level by 1.
1619 assert self._indent > 0
1622 def Write(self, txt, *args):
1623 """Write line to output file.
1626 assert self._indent >= 0
1628 self._fh.write(self._indent * self.INDENT_STR)
1631 self._fh.write(txt % args)
1635 self._fh.write("\n")
1638 def ListVisibleFiles(path):
1639 """Returns a list of visible files in a directory.
1642 @param path: the directory to enumerate
1644 @return: the list of all files not starting with a dot
1645 @raise ProgrammerError: if L{path} is not an absolue and normalized path
1648 if not IsNormAbsPath(path):
1649 raise errors.ProgrammerError("Path passed to ListVisibleFiles is not"
1650 " absolute/normalized: '%s'" % path)
1651 files = [i for i in os.listdir(path) if not i.startswith(".")]
1655 def GetHomeDir(user, default=None):
1656 """Try to get the homedir of the given user.
1658 The user can be passed either as a string (denoting the name) or as
1659 an integer (denoting the user id). If the user is not found, the
1660 'default' argument is returned, which defaults to None.
1664 if isinstance(user, basestring):
1665 result = pwd.getpwnam(user)
1666 elif isinstance(user, (int, long)):
1667 result = pwd.getpwuid(user)
1669 raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
1673 return result.pw_dir
1677 """Returns a random UUID.
1679 @note: This is a Linux-specific method as it uses the /proc
1684 return ReadFile(_RANDOM_UUID_FILE, size=128).rstrip("\n")
1687 def GenerateSecret(numbytes=20):
1688 """Generates a random secret.
1690 This will generate a pseudo-random secret returning an hex string
1691 (so that it can be used where an ASCII string is needed).
1693 @param numbytes: the number of bytes which will be represented by the returned
1694 string (defaulting to 20, the length of a SHA1 hash)
1696 @return: an hex representation of the pseudo-random sequence
1699 return os.urandom(numbytes).encode('hex')
1702 def EnsureDirs(dirs):
1703 """Make required directories, if they don't exist.
1705 @param dirs: list of tuples (dir_name, dir_mode)
1706 @type dirs: list of (string, integer)
1709 for dir_name, dir_mode in dirs:
1711 os.mkdir(dir_name, dir_mode)
1712 except EnvironmentError, err:
1713 if err.errno != errno.EEXIST:
1714 raise errors.GenericError("Cannot create needed directory"
1715 " '%s': %s" % (dir_name, err))
1717 os.chmod(dir_name, dir_mode)
1718 except EnvironmentError, err:
1719 raise errors.GenericError("Cannot change directory permissions on"
1720 " '%s': %s" % (dir_name, err))
1721 if not os.path.isdir(dir_name):
1722 raise errors.GenericError("%s is not a directory" % dir_name)
1725 def ReadFile(file_name, size=-1):
1729 @param size: Read at most size bytes (if negative, entire file)
1731 @return: the (possibly partial) content of the file
1734 f = open(file_name, "r")
1741 def WriteFile(file_name, fn=None, data=None,
1742 mode=None, uid=-1, gid=-1,
1743 atime=None, mtime=None, close=True,
1744 dry_run=False, backup=False,
1745 prewrite=None, postwrite=None):
1746 """(Over)write a file atomically.
1748 The file_name and either fn (a function taking one argument, the
1749 file descriptor, and which should write the data to it) or data (the
1750 contents of the file) must be passed. The other arguments are
1751 optional and allow setting the file mode, owner and group, and the
1752 mtime/atime of the file.
1754 If the function doesn't raise an exception, it has succeeded and the
1755 target file has the new contents. If the function has raised an
1756 exception, an existing target file should be unmodified and the
1757 temporary file should be removed.
1759 @type file_name: str
1760 @param file_name: the target filename
1762 @param fn: content writing function, called with
1763 file descriptor as parameter
1765 @param data: contents of the file
1767 @param mode: file mode
1769 @param uid: the owner of the file
1771 @param gid: the group of the file
1773 @param atime: a custom access time to be set on the file
1775 @param mtime: a custom modification time to be set on the file
1776 @type close: boolean
1777 @param close: whether to close file after writing it
1778 @type prewrite: callable
1779 @param prewrite: function to be called before writing content
1780 @type postwrite: callable
1781 @param postwrite: function to be called after writing content
1784 @return: None if the 'close' parameter evaluates to True,
1785 otherwise the file descriptor
1787 @raise errors.ProgrammerError: if any of the arguments are not valid
1790 if not os.path.isabs(file_name):
1791 raise errors.ProgrammerError("Path passed to WriteFile is not"
1792 " absolute: '%s'" % file_name)
1794 if [fn, data].count(None) != 1:
1795 raise errors.ProgrammerError("fn or data required")
1797 if [atime, mtime].count(None) == 1:
1798 raise errors.ProgrammerError("Both atime and mtime must be either"
1801 if backup and not dry_run and os.path.isfile(file_name):
1802 CreateBackup(file_name)
1804 dir_name, base_name = os.path.split(file_name)
1805 fd, new_name = tempfile.mkstemp('.new', base_name, dir_name)
1807 # here we need to make sure we remove the temp file, if any error
1808 # leaves it in place
1810 if uid != -1 or gid != -1:
1811 os.chown(new_name, uid, gid)
1813 os.chmod(new_name, mode)
1814 if callable(prewrite):
1816 if data is not None:
1820 if callable(postwrite):
1823 if atime is not None and mtime is not None:
1824 os.utime(new_name, (atime, mtime))
1826 os.rename(new_name, file_name)
1835 RemoveFile(new_name)
1840 def ReadOneLineFile(file_name, strict=False):
1841 """Return the first non-empty line from a file.
1843 @type strict: boolean
1844 @param strict: if True, abort if the file has more than one
1848 file_lines = ReadFile(file_name).splitlines()
1849 full_lines = filter(bool, file_lines)
1850 if not file_lines or not full_lines:
1851 raise errors.GenericError("No data in one-liner file %s" % file_name)
1852 elif strict and len(full_lines) > 1:
1853 raise errors.GenericError("Too many lines in one-liner file %s" %
1855 return full_lines[0]
1858 def FirstFree(seq, base=0):
1859 """Returns the first non-existing integer from seq.
1861 The seq argument should be a sorted list of positive integers. The
1862 first time the index of an element is smaller than the element
1863 value, the index will be returned.
1865 The base argument is used to start at a different offset,
1866 i.e. C{[3, 4, 6]} with I{offset=3} will return 5.
1868 Example: C{[0, 1, 3]} will return I{2}.
1871 @param seq: the sequence to be analyzed.
1873 @param base: use this value as the base index of the sequence
1875 @return: the first non-used index in the sequence
1878 for idx, elem in enumerate(seq):
1879 assert elem >= base, "Passed element is higher than base offset"
1880 if elem > idx + base:
1886 def SingleWaitForFdCondition(fdobj, event, timeout):
1887 """Waits for a condition to occur on the socket.
1889 Immediately returns at the first interruption.
1891 @type fdobj: integer or object supporting a fileno() method
1892 @param fdobj: entity to wait for events on
1893 @type event: integer
1894 @param event: ORed condition (see select module)
1895 @type timeout: float or None
1896 @param timeout: Timeout in seconds
1898 @return: None for timeout, otherwise occured conditions
1901 check = (event | select.POLLPRI |
1902 select.POLLNVAL | select.POLLHUP | select.POLLERR)
1904 if timeout is not None:
1905 # Poller object expects milliseconds
1908 poller = select.poll()
1909 poller.register(fdobj, event)
1911 # TODO: If the main thread receives a signal and we have no timeout, we
1912 # could wait forever. This should check a global "quit" flag or something
1914 io_events = poller.poll(timeout)
1915 except select.error, err:
1916 if err[0] != errno.EINTR:
1919 if io_events and io_events[0][1] & check:
1920 return io_events[0][1]
1925 class FdConditionWaiterHelper(object):
1926 """Retry helper for WaitForFdCondition.
1928 This class contains the retried and wait functions that make sure
1929 WaitForFdCondition can continue waiting until the timeout is actually
1934 def __init__(self, timeout):
1935 self.timeout = timeout
1937 def Poll(self, fdobj, event):
1938 result = SingleWaitForFdCondition(fdobj, event, self.timeout)
1944 def UpdateTimeout(self, timeout):
1945 self.timeout = timeout
1948 def WaitForFdCondition(fdobj, event, timeout):
1949 """Waits for a condition to occur on the socket.
1951 Retries until the timeout is expired, even if interrupted.
1953 @type fdobj: integer or object supporting a fileno() method
1954 @param fdobj: entity to wait for events on
1955 @type event: integer
1956 @param event: ORed condition (see select module)
1957 @type timeout: float or None
1958 @param timeout: Timeout in seconds
1960 @return: None for timeout, otherwise occured conditions
1963 if timeout is not None:
1964 retrywaiter = FdConditionWaiterHelper(timeout)
1966 result = Retry(retrywaiter.Poll, RETRY_REMAINING_TIME, timeout,
1967 args=(fdobj, event), wait_fn=retrywaiter.UpdateTimeout)
1968 except RetryTimeout:
1972 while result is None:
1973 result = SingleWaitForFdCondition(fdobj, event, timeout)
1977 def UniqueSequence(seq):
1978 """Returns a list with unique elements.
1980 Element order is preserved.
1983 @param seq: the sequence with the source elements
1985 @return: list of unique elements from seq
1989 return [i for i in seq if i not in seen and not seen.add(i)]
1992 def NormalizeAndValidateMac(mac):
1993 """Normalizes and check if a MAC address is valid.
1995 Checks whether the supplied MAC address is formally correct, only
1996 accepts colon separated format. Normalize it to all lower.
1999 @param mac: the MAC to be validated
2001 @return: returns the normalized and validated MAC.
2003 @raise errors.OpPrereqError: If the MAC isn't valid
2006 mac_check = re.compile("^([0-9a-f]{2}(:|$)){6}$", re.I)
2007 if not mac_check.match(mac):
2008 raise errors.OpPrereqError("Invalid MAC address specified: %s" %
2009 mac, errors.ECODE_INVAL)
2014 def TestDelay(duration):
2015 """Sleep for a fixed amount of time.
2017 @type duration: float
2018 @param duration: the sleep duration
2020 @return: False for negative value, True otherwise
2024 return False, "Invalid sleep duration"
2025 time.sleep(duration)
2029 def _CloseFDNoErr(fd, retries=5):
2030 """Close a file descriptor ignoring errors.
2033 @param fd: the file descriptor
2035 @param retries: how many retries to make, in case we get any
2036 other error than EBADF
2041 except OSError, err:
2042 if err.errno != errno.EBADF:
2044 _CloseFDNoErr(fd, retries - 1)
2045 # else either it's closed already or we're out of retries, so we
2046 # ignore this and go on
2049 def CloseFDs(noclose_fds=None):
2050 """Close file descriptors.
2052 This closes all file descriptors above 2 (i.e. except
2055 @type noclose_fds: list or None
2056 @param noclose_fds: if given, it denotes a list of file descriptor
2057 that should not be closed
2060 # Default maximum for the number of available file descriptors.
2061 if 'SC_OPEN_MAX' in os.sysconf_names:
2063 MAXFD = os.sysconf('SC_OPEN_MAX')
2070 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
2071 if (maxfd == resource.RLIM_INFINITY):
2074 # Iterate through and close all file descriptors (except the standard ones)
2075 for fd in range(3, maxfd):
2076 if noclose_fds and fd in noclose_fds:
2081 def Mlockall(_ctypes=ctypes):
2082 """Lock current process' virtual address space into RAM.
2084 This is equivalent to the C call mlockall(MCL_CURRENT|MCL_FUTURE),
2085 see mlock(2) for more details. This function requires ctypes module.
2087 @raises errors.NoCtypesError: if ctypes module is not found
2091 raise errors.NoCtypesError()
2093 libc = _ctypes.cdll.LoadLibrary("libc.so.6")
2095 logging.error("Cannot set memory lock, ctypes cannot load libc")
2098 # Some older version of the ctypes module don't have built-in functionality
2099 # to access the errno global variable, where function error codes are stored.
2100 # By declaring this variable as a pointer to an integer we can then access
2101 # its value correctly, should the mlockall call fail, in order to see what
2102 # the actual error code was.
2103 # pylint: disable-msg=W0212
2104 libc.__errno_location.restype = _ctypes.POINTER(_ctypes.c_int)
2106 if libc.mlockall(_MCL_CURRENT | _MCL_FUTURE):
2107 # pylint: disable-msg=W0212
2108 logging.error("Cannot set memory lock: %s",
2109 os.strerror(libc.__errno_location().contents.value))
2112 logging.debug("Memory lock set")
2115 def Daemonize(logfile, run_uid, run_gid):
2116 """Daemonize the current process.
2118 This detaches the current process from the controlling terminal and
2119 runs it in the background as a daemon.
2122 @param logfile: the logfile to which we should redirect stdout/stderr
2124 @param run_uid: Run the child under this uid
2126 @param run_gid: Run the child under this gid
2128 @return: the value zero
2131 # pylint: disable-msg=W0212
2132 # yes, we really want os._exit
2138 if (pid == 0): # The first child.
2140 # FIXME: When removing again and moving to start-stop-daemon privilege drop
2141 # make sure to check for config permission and bail out when invoked
2146 pid = os.fork() # Fork a second child.
2147 if (pid == 0): # The second child.
2151 # exit() or _exit()? See below.
2152 os._exit(0) # Exit parent (the first child) of the second child.
2154 os._exit(0) # Exit parent of the first child.
2158 i = os.open("/dev/null", os.O_RDONLY) # stdin
2159 assert i == 0, "Can't close/reopen stdin"
2160 i = os.open(logfile, os.O_WRONLY|os.O_CREAT|os.O_APPEND, 0600) # stdout
2161 assert i == 1, "Can't close/reopen stdout"
2162 # Duplicate standard output to standard error.
2167 def DaemonPidFileName(name):
2168 """Compute a ganeti pid file absolute path
2171 @param name: the daemon name
2173 @return: the full path to the pidfile corresponding to the given
2177 return PathJoin(constants.RUN_GANETI_DIR, "%s.pid" % name)
2180 def EnsureDaemon(name):
2181 """Check for and start daemon if not alive.
2184 result = RunCmd([constants.DAEMON_UTIL, "check-and-start", name])
2186 logging.error("Can't start daemon '%s', failure %s, output: %s",
2187 name, result.fail_reason, result.output)
2193 def StopDaemon(name):
2197 result = RunCmd([constants.DAEMON_UTIL, "stop", name])
2199 logging.error("Can't stop daemon '%s', failure %s, output: %s",
2200 name, result.fail_reason, result.output)
2206 def WritePidFile(name):
2207 """Write the current process pidfile.
2209 The file will be written to L{constants.RUN_GANETI_DIR}I{/name.pid}
2212 @param name: the daemon name to use
2213 @raise errors.GenericError: if the pid file already exists and
2214 points to a live process
2218 pidfilename = DaemonPidFileName(name)
2219 if IsProcessAlive(ReadPidFile(pidfilename)):
2220 raise errors.GenericError("%s contains a live process" % pidfilename)
2222 WriteFile(pidfilename, data="%d\n" % pid)
2225 def RemovePidFile(name):
2226 """Remove the current process pidfile.
2228 Any errors are ignored.
2231 @param name: the daemon name used to derive the pidfile name
2234 pidfilename = DaemonPidFileName(name)
2235 # TODO: we could check here that the file contains our pid
2237 RemoveFile(pidfilename)
2238 except: # pylint: disable-msg=W0702
2242 def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
2244 """Kill a process given by its pid.
2247 @param pid: The PID to terminate.
2249 @param signal_: The signal to send, by default SIGTERM
2251 @param timeout: The timeout after which, if the process is still alive,
2252 a SIGKILL will be sent. If not positive, no such checking
2254 @type waitpid: boolean
2255 @param waitpid: If true, we should waitpid on this process after
2256 sending signals, since it's our own child and otherwise it
2257 would remain as zombie
2260 def _helper(pid, signal_, wait):
2261 """Simple helper to encapsulate the kill/waitpid sequence"""
2262 if IgnoreProcessNotFound(os.kill, pid, signal_) and wait:
2264 os.waitpid(pid, os.WNOHANG)
2269 # kill with pid=0 == suicide
2270 raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
2272 if not IsProcessAlive(pid):
2275 _helper(pid, signal_, waitpid)
2280 def _CheckProcess():
2281 if not IsProcessAlive(pid):
2285 (result_pid, _) = os.waitpid(pid, os.WNOHANG)
2295 # Wait up to $timeout seconds
2296 Retry(_CheckProcess, (0.01, 1.5, 0.1), timeout)
2297 except RetryTimeout:
2300 if IsProcessAlive(pid):
2301 # Kill process if it's still alive
2302 _helper(pid, signal.SIGKILL, waitpid)
2305 def FindFile(name, search_path, test=os.path.exists):
2306 """Look for a filesystem object in a given path.
2308 This is an abstract method to search for filesystem object (files,
2309 dirs) under a given search path.
2312 @param name: the name to look for
2313 @type search_path: str
2314 @param search_path: location to start at
2315 @type test: callable
2316 @param test: a function taking one argument that should return True
2317 if the a given object is valid; the default value is
2318 os.path.exists, causing only existing files to be returned
2320 @return: full path to the object if found, None otherwise
2323 # validate the filename mask
2324 if constants.EXT_PLUGIN_MASK.match(name) is None:
2325 logging.critical("Invalid value passed for external script name: '%s'",
2329 for dir_name in search_path:
2330 # FIXME: investigate switch to PathJoin
2331 item_name = os.path.sep.join([dir_name, name])
2332 # check the user test and that we're indeed resolving to the given
2334 if test(item_name) and os.path.basename(item_name) == name:
2339 def CheckVolumeGroupSize(vglist, vgname, minsize):
2340 """Checks if the volume group list is valid.
2342 The function will check if a given volume group is in the list of
2343 volume groups and has a minimum size.
2346 @param vglist: dictionary of volume group names and their size
2348 @param vgname: the volume group we should check
2350 @param minsize: the minimum size we accept
2352 @return: None for success, otherwise the error message
2355 vgsize = vglist.get(vgname, None)
2357 return "volume group '%s' missing" % vgname
2358 elif vgsize < minsize:
2359 return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
2360 (vgname, minsize, vgsize))
2364 def SplitTime(value):
2365 """Splits time as floating point number into a tuple.
2367 @param value: Time in seconds
2368 @type value: int or float
2369 @return: Tuple containing (seconds, microseconds)
2372 (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
2374 assert 0 <= seconds, \
2375 "Seconds must be larger than or equal to 0, but are %s" % seconds
2376 assert 0 <= microseconds <= 999999, \
2377 "Microseconds must be 0-999999, but are %s" % microseconds
2379 return (int(seconds), int(microseconds))
2382 def MergeTime(timetuple):
2383 """Merges a tuple into time as a floating point number.
2385 @param timetuple: Time as tuple, (seconds, microseconds)
2386 @type timetuple: tuple
2387 @return: Time as a floating point number expressed in seconds
2390 (seconds, microseconds) = timetuple
2392 assert 0 <= seconds, \
2393 "Seconds must be larger than or equal to 0, but are %s" % seconds
2394 assert 0 <= microseconds <= 999999, \
2395 "Microseconds must be 0-999999, but are %s" % microseconds
2397 return float(seconds) + (float(microseconds) * 0.000001)
2400 class LogFileHandler(logging.FileHandler):
2401 """Log handler that doesn't fallback to stderr.
2403 When an error occurs while writing on the logfile, logging.FileHandler tries
2404 to log on stderr. This doesn't work in ganeti since stderr is redirected to
2405 the logfile. This class avoids failures reporting errors to /dev/console.
2408 def __init__(self, filename, mode="a", encoding=None):
2409 """Open the specified file and use it as the stream for logging.
2411 Also open /dev/console to report errors while logging.
2414 logging.FileHandler.__init__(self, filename, mode, encoding)
2415 self.console = open(constants.DEV_CONSOLE, "a")
2417 def handleError(self, record): # pylint: disable-msg=C0103
2418 """Handle errors which occur during an emit() call.
2420 Try to handle errors with FileHandler method, if it fails write to
2425 logging.FileHandler.handleError(self, record)
2426 except Exception: # pylint: disable-msg=W0703
2428 self.console.write("Cannot log message:\n%s\n" % self.format(record))
2429 except Exception: # pylint: disable-msg=W0703
2430 # Log handler tried everything it could, now just give up
2434 def SetupLogging(logfile, debug=0, stderr_logging=False, program="",
2435 multithreaded=False, syslog=constants.SYSLOG_USAGE,
2436 console_logging=False):
2437 """Configures the logging module.
2440 @param logfile: the filename to which we should log
2441 @type debug: integer
2442 @param debug: if greater than zero, enable debug messages, otherwise
2443 only those at C{INFO} and above level
2444 @type stderr_logging: boolean
2445 @param stderr_logging: whether we should also log to the standard error
2447 @param program: the name under which we should log messages
2448 @type multithreaded: boolean
2449 @param multithreaded: if True, will add the thread name to the log file
2450 @type syslog: string
2451 @param syslog: one of 'no', 'yes', 'only':
2452 - if no, syslog is not used
2453 - if yes, syslog is used (in addition to file-logging)
2454 - if only, only syslog is used
2455 @type console_logging: boolean
2456 @param console_logging: if True, will use a FileHandler which falls back to
2457 the system console if logging fails
2458 @raise EnvironmentError: if we can't open the log file and
2459 syslog/stderr logging is disabled
2462 fmt = "%(asctime)s: " + program + " pid=%(process)d"
2463 sft = program + "[%(process)d]:"
2465 fmt += "/%(threadName)s"
2466 sft += " (%(threadName)s)"
2468 fmt += " %(module)s:%(lineno)s"
2469 # no debug info for syslog loggers
2470 fmt += " %(levelname)s %(message)s"
2471 # yes, we do want the textual level, as remote syslog will probably
2472 # lose the error level, and it's easier to grep for it
2473 sft += " %(levelname)s %(message)s"
2474 formatter = logging.Formatter(fmt)
2475 sys_fmt = logging.Formatter(sft)
2477 root_logger = logging.getLogger("")
2478 root_logger.setLevel(logging.NOTSET)
2480 # Remove all previously setup handlers
2481 for handler in root_logger.handlers:
2483 root_logger.removeHandler(handler)
2486 stderr_handler = logging.StreamHandler()
2487 stderr_handler.setFormatter(formatter)
2489 stderr_handler.setLevel(logging.NOTSET)
2491 stderr_handler.setLevel(logging.CRITICAL)
2492 root_logger.addHandler(stderr_handler)
2494 if syslog in (constants.SYSLOG_YES, constants.SYSLOG_ONLY):
2495 facility = logging.handlers.SysLogHandler.LOG_DAEMON
2496 syslog_handler = logging.handlers.SysLogHandler(constants.SYSLOG_SOCKET,
2498 syslog_handler.setFormatter(sys_fmt)
2499 # Never enable debug over syslog
2500 syslog_handler.setLevel(logging.INFO)
2501 root_logger.addHandler(syslog_handler)
2503 if syslog != constants.SYSLOG_ONLY:
2504 # this can fail, if the logging directories are not setup or we have
2505 # a permisssion problem; in this case, it's best to log but ignore
2506 # the error if stderr_logging is True, and if false we re-raise the
2507 # exception since otherwise we could run but without any logs at all
2510 logfile_handler = LogFileHandler(logfile)
2512 logfile_handler = logging.FileHandler(logfile)
2513 logfile_handler.setFormatter(formatter)
2515 logfile_handler.setLevel(logging.DEBUG)
2517 logfile_handler.setLevel(logging.INFO)
2518 root_logger.addHandler(logfile_handler)
2519 except EnvironmentError:
2520 if stderr_logging or syslog == constants.SYSLOG_YES:
2521 logging.exception("Failed to enable logging to file '%s'", logfile)
2523 # we need to re-raise the exception
2527 def IsNormAbsPath(path):
2528 """Check whether a path is absolute and also normalized
2530 This avoids things like /dir/../../other/path to be valid.
2533 return os.path.normpath(path) == path and os.path.isabs(path)
2536 def PathJoin(*args):
2537 """Safe-join a list of path components.
2540 - the first argument must be an absolute path
2541 - no component in the path must have backtracking (e.g. /../),
2542 since we check for normalization at the end
2544 @param args: the path components to be joined
2545 @raise ValueError: for invalid paths
2548 # ensure we're having at least one path passed in
2550 # ensure the first component is an absolute and normalized path name
2552 if not IsNormAbsPath(root):
2553 raise ValueError("Invalid parameter to PathJoin: '%s'" % str(args[0]))
2554 result = os.path.join(*args)
2555 # ensure that the whole path is normalized
2556 if not IsNormAbsPath(result):
2557 raise ValueError("Invalid parameters to PathJoin: '%s'" % str(args))
2558 # check that we're still under the original prefix
2559 prefix = os.path.commonprefix([root, result])
2561 raise ValueError("Error: path joining resulted in different prefix"
2562 " (%s != %s)" % (prefix, root))
2566 def TailFile(fname, lines=20):
2567 """Return the last lines from a file.
2569 @note: this function will only read and parse the last 4KB of
2570 the file; if the lines are very long, it could be that less
2571 than the requested number of lines are returned
2573 @param fname: the file name
2575 @param lines: the (maximum) number of lines to return
2578 fd = open(fname, "r")
2582 pos = max(0, pos-4096)
2584 raw_data = fd.read()
2588 rows = raw_data.splitlines()
2589 return rows[-lines:]
2592 def FormatTimestampWithTZ(secs):
2593 """Formats a Unix timestamp with the local timezone.
2596 return time.strftime("%F %T %Z", time.gmtime(secs))
2599 def _ParseAsn1Generalizedtime(value):
2600 """Parses an ASN1 GENERALIZEDTIME timestamp as used by pyOpenSSL.
2603 @param value: ASN1 GENERALIZEDTIME timestamp
2606 m = re.match(r"^(\d+)([-+]\d\d)(\d\d)$", value)
2609 asn1time = m.group(1)
2610 hours = int(m.group(2))
2611 minutes = int(m.group(3))
2612 utcoffset = (60 * hours) + minutes
2614 if not value.endswith("Z"):
2615 raise ValueError("Missing timezone")
2616 asn1time = value[:-1]
2619 parsed = time.strptime(asn1time, "%Y%m%d%H%M%S")
2621 tt = datetime.datetime(*(parsed[:7])) - datetime.timedelta(minutes=utcoffset)
2623 return calendar.timegm(tt.utctimetuple())
2626 def GetX509CertValidity(cert):
2627 """Returns the validity period of the certificate.
2629 @type cert: OpenSSL.crypto.X509
2630 @param cert: X509 certificate object
2633 # The get_notBefore and get_notAfter functions are only supported in
2634 # pyOpenSSL 0.7 and above.
2636 get_notbefore_fn = cert.get_notBefore
2637 except AttributeError:
2640 not_before_asn1 = get_notbefore_fn()
2642 if not_before_asn1 is None:
2645 not_before = _ParseAsn1Generalizedtime(not_before_asn1)
2648 get_notafter_fn = cert.get_notAfter
2649 except AttributeError:
2652 not_after_asn1 = get_notafter_fn()
2654 if not_after_asn1 is None:
2657 not_after = _ParseAsn1Generalizedtime(not_after_asn1)
2659 return (not_before, not_after)
2662 def _VerifyCertificateInner(expired, not_before, not_after, now,
2663 warn_days, error_days):
2664 """Verifies certificate validity.
2667 @param expired: Whether pyOpenSSL considers the certificate as expired
2668 @type not_before: number or None
2669 @param not_before: Unix timestamp before which certificate is not valid
2670 @type not_after: number or None
2671 @param not_after: Unix timestamp after which certificate is invalid
2673 @param now: Current time as Unix timestamp
2674 @type warn_days: number or None
2675 @param warn_days: How many days before expiration a warning should be reported
2676 @type error_days: number or None
2677 @param error_days: How many days before expiration an error should be reported
2681 msg = "Certificate is expired"
2683 if not_before is not None and not_after is not None:
2684 msg += (" (valid from %s to %s)" %
2685 (FormatTimestampWithTZ(not_before),
2686 FormatTimestampWithTZ(not_after)))
2687 elif not_before is not None:
2688 msg += " (valid from %s)" % FormatTimestampWithTZ(not_before)
2689 elif not_after is not None:
2690 msg += " (valid until %s)" % FormatTimestampWithTZ(not_after)
2692 return (CERT_ERROR, msg)
2694 elif not_before is not None and not_before > now:
2695 return (CERT_WARNING,
2696 "Certificate not yet valid (valid from %s)" %
2697 FormatTimestampWithTZ(not_before))
2699 elif not_after is not None:
2700 remaining_days = int((not_after - now) / (24 * 3600))
2702 msg = "Certificate expires in about %d days" % remaining_days
2704 if error_days is not None and remaining_days <= error_days:
2705 return (CERT_ERROR, msg)
2707 if warn_days is not None and remaining_days <= warn_days:
2708 return (CERT_WARNING, msg)
2713 def VerifyX509Certificate(cert, warn_days, error_days):
2714 """Verifies a certificate for LUVerifyCluster.
2716 @type cert: OpenSSL.crypto.X509
2717 @param cert: X509 certificate object
2718 @type warn_days: number or None
2719 @param warn_days: How many days before expiration a warning should be reported
2720 @type error_days: number or None
2721 @param error_days: How many days before expiration an error should be reported
2724 # Depending on the pyOpenSSL version, this can just return (None, None)
2725 (not_before, not_after) = GetX509CertValidity(cert)
2727 return _VerifyCertificateInner(cert.has_expired(), not_before, not_after,
2728 time.time(), warn_days, error_days)
2731 def SignX509Certificate(cert, key, salt):
2732 """Sign a X509 certificate.
2734 An RFC822-like signature header is added in front of the certificate.
2736 @type cert: OpenSSL.crypto.X509
2737 @param cert: X509 certificate object
2739 @param key: Key for HMAC
2741 @param salt: Salt for HMAC
2743 @return: Serialized and signed certificate in PEM format
2746 if not VALID_X509_SIGNATURE_SALT.match(salt):
2747 raise errors.GenericError("Invalid salt: %r" % salt)
2749 # Dumping as PEM here ensures the certificate is in a sane format
2750 cert_pem = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)
2752 return ("%s: %s/%s\n\n%s" %
2753 (constants.X509_CERT_SIGNATURE_HEADER, salt,
2754 Sha1Hmac(key, cert_pem, salt=salt),
2758 def _ExtractX509CertificateSignature(cert_pem):
2759 """Helper function to extract signature from X509 certificate.
2762 # Extract signature from original PEM data
2763 for line in cert_pem.splitlines():
2764 if line.startswith("---"):
2767 m = X509_SIGNATURE.match(line.strip())
2769 return (m.group("salt"), m.group("sign"))
2771 raise errors.GenericError("X509 certificate signature is missing")
2774 def LoadSignedX509Certificate(cert_pem, key):
2775 """Verifies a signed X509 certificate.
2777 @type cert_pem: string
2778 @param cert_pem: Certificate in PEM format and with signature header
2780 @param key: Key for HMAC
2781 @rtype: tuple; (OpenSSL.crypto.X509, string)
2782 @return: X509 certificate object and salt
2785 (salt, signature) = _ExtractX509CertificateSignature(cert_pem)
2788 cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert_pem)
2790 # Dump again to ensure it's in a sane format
2791 sane_pem = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)
2793 if not VerifySha1Hmac(key, sane_pem, signature, salt=salt):
2794 raise errors.GenericError("X509 certificate signature is invalid")
2799 def Sha1Hmac(key, text, salt=None):
2800 """Calculates the HMAC-SHA1 digest of a text.
2802 HMAC is defined in RFC2104.
2805 @param key: Secret key
2810 salted_text = salt + text
2814 return hmac.new(key, salted_text, compat.sha1).hexdigest()
2817 def VerifySha1Hmac(key, text, digest, salt=None):
2818 """Verifies the HMAC-SHA1 digest of a text.
2820 HMAC is defined in RFC2104.
2823 @param key: Secret key
2825 @type digest: string
2826 @param digest: Expected digest
2828 @return: Whether HMAC-SHA1 digest matches
2831 return digest.lower() == Sha1Hmac(key, text, salt=salt).lower()
2834 def SafeEncode(text):
2835 """Return a 'safe' version of a source string.
2837 This function mangles the input string and returns a version that
2838 should be safe to display/encode as ASCII. To this end, we first
2839 convert it to ASCII using the 'backslashreplace' encoding which
2840 should get rid of any non-ASCII chars, and then we process it
2841 through a loop copied from the string repr sources in the python; we
2842 don't use string_escape anymore since that escape single quotes and
2843 backslashes too, and that is too much; and that escaping is not
2844 stable, i.e. string_escape(string_escape(x)) != string_escape(x).
2846 @type text: str or unicode
2847 @param text: input data
2849 @return: a safe version of text
2852 if isinstance(text, unicode):
2853 # only if unicode; if str already, we handle it below
2854 text = text.encode('ascii', 'backslashreplace')
2864 elif c < 32 or c >= 127: # non-printable
2865 resu += "\\x%02x" % (c & 0xff)
2871 def UnescapeAndSplit(text, sep=","):
2872 """Split and unescape a string based on a given separator.
2874 This function splits a string based on a separator where the
2875 separator itself can be escape in order to be an element of the
2876 elements. The escaping rules are (assuming coma being the
2878 - a plain , separates the elements
2879 - a sequence \\\\, (double backslash plus comma) is handled as a
2880 backslash plus a separator comma
2881 - a sequence \, (backslash plus comma) is handled as a
2885 @param text: the string to split
2887 @param text: the separator
2889 @return: a list of strings
2892 # we split the list by sep (with no escaping at this stage)
2893 slist = text.split(sep)
2894 # next, we revisit the elements and if any of them ended with an odd
2895 # number of backslashes, then we join it with the next
2899 if e1.endswith("\\"):
2900 num_b = len(e1) - len(e1.rstrip("\\"))
2903 # here the backslashes remain (all), and will be reduced in
2905 rlist.append(e1 + sep + e2)
2908 # finally, replace backslash-something with something
2909 rlist = [re.sub(r"\\(.)", r"\1", v) for v in rlist]
2913 def CommaJoin(names):
2914 """Nicely join a set of identifiers.
2916 @param names: set, list or tuple
2917 @return: a string with the formatted results
2920 return ", ".join([str(val) for val in names])
2923 def BytesToMebibyte(value):
2924 """Converts bytes to mebibytes.
2927 @param value: Value in bytes
2929 @return: Value in mebibytes
2932 return int(round(value / (1024.0 * 1024.0), 0))
2935 def CalculateDirectorySize(path):
2936 """Calculates the size of a directory recursively.
2939 @param path: Path to directory
2941 @return: Size in mebibytes
2946 for (curpath, _, files) in os.walk(path):
2947 for filename in files:
2948 st = os.lstat(PathJoin(curpath, filename))
2951 return BytesToMebibyte(size)
2954 def GetMounts(filename=constants.PROC_MOUNTS):
2955 """Returns the list of mounted filesystems.
2957 This function is Linux-specific.
2959 @param filename: path of mounts file (/proc/mounts by default)
2960 @rtype: list of tuples
2961 @return: list of mount entries (device, mountpoint, fstype, options)
2964 # TODO(iustin): investigate non-Linux options (e.g. via mount output)
2966 mountlines = ReadFile(filename).splitlines()
2967 for line in mountlines:
2968 device, mountpoint, fstype, options, _ = line.split(None, 4)
2969 data.append((device, mountpoint, fstype, options))
2974 def GetFilesystemStats(path):
2975 """Returns the total and free space on a filesystem.
2978 @param path: Path on filesystem to be examined
2980 @return: tuple of (Total space, Free space) in mebibytes
2983 st = os.statvfs(path)
2985 fsize = BytesToMebibyte(st.f_bavail * st.f_frsize)
2986 tsize = BytesToMebibyte(st.f_blocks * st.f_frsize)
2987 return (tsize, fsize)
2990 def RunInSeparateProcess(fn, *args):
2991 """Runs a function in a separate process.
2993 Note: Only boolean return values are supported.
2996 @param fn: Function to be called
2998 @return: Function's result
3005 # In case the function uses temporary files
3006 ResetTempfileModule()
3009 result = int(bool(fn(*args)))
3010 assert result in (0, 1)
3011 except: # pylint: disable-msg=W0702
3012 logging.exception("Error while calling function in separate process")
3013 # 0 and 1 are reserved for the return value
3016 os._exit(result) # pylint: disable-msg=W0212
3020 # Avoid zombies and check exit code
3021 (_, status) = os.waitpid(pid, 0)
3023 if os.WIFSIGNALED(status):
3025 signum = os.WTERMSIG(status)
3027 exitcode = os.WEXITSTATUS(status)
3030 if not (exitcode in (0, 1) and signum is None):
3031 raise errors.GenericError("Child program failed (code=%s, signal=%s)" %
3034 return bool(exitcode)
3037 def IgnoreProcessNotFound(fn, *args, **kwargs):
3038 """Ignores ESRCH when calling a process-related function.
3040 ESRCH is raised when a process is not found.
3043 @return: Whether process was found
3048 except EnvironmentError, err:
3050 if err.errno == errno.ESRCH:
3057 def IgnoreSignals(fn, *args, **kwargs):
3058 """Tries to call a function ignoring failures due to EINTR.
3062 return fn(*args, **kwargs)
3063 except EnvironmentError, err:
3064 if err.errno == errno.EINTR:
3068 except (select.error, socket.error), err:
3069 # In python 2.6 and above select.error is an IOError, so it's handled
3070 # above, in 2.5 and below it's not, and it's handled here.
3071 if err.args and err.args[0] == errno.EINTR:
3078 """Locks a file using POSIX locks.
3081 @param fd: the file descriptor we need to lock
3085 fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
3086 except IOError, err:
3087 if err.errno == errno.EAGAIN:
3088 raise errors.LockError("File already locked")
3092 def FormatTime(val):
3093 """Formats a time value.
3095 @type val: float or None
3096 @param val: the timestamp as returned by time.time()
3097 @return: a string value or N/A if we don't have a valid timestamp
3100 if val is None or not isinstance(val, (int, float)):
3102 # these two codes works on Linux, but they are not guaranteed on all
3104 return time.strftime("%F %T", time.localtime(val))
3107 def FormatSeconds(secs):
3108 """Formats seconds for easier reading.
3111 @param secs: Number of seconds
3113 @return: Formatted seconds (e.g. "2d 9h 19m 49s")
3118 secs = round(secs, 0)
3121 # Negative values would be a bit tricky
3122 for unit, one in [("d", 24 * 60 * 60), ("h", 60 * 60), ("m", 60)]:
3123 (complete, secs) = divmod(secs, one)
3124 if complete or parts:
3125 parts.append("%d%s" % (complete, unit))
3127 parts.append("%ds" % secs)
3129 return " ".join(parts)
3132 def ReadWatcherPauseFile(filename, now=None, remove_after=3600):
3133 """Reads the watcher pause file.
3135 @type filename: string
3136 @param filename: Path to watcher pause file
3137 @type now: None, float or int
3138 @param now: Current time as Unix timestamp
3139 @type remove_after: int
3140 @param remove_after: Remove watcher pause file after specified amount of
3141 seconds past the pause end time
3148 value = ReadFile(filename)
3149 except IOError, err:
3150 if err.errno != errno.ENOENT:
3154 if value is not None:
3158 logging.warning(("Watcher pause file (%s) contains invalid value,"
3159 " removing it"), filename)
3160 RemoveFile(filename)
3163 if value is not None:
3164 # Remove file if it's outdated
3165 if now > (value + remove_after):
3166 RemoveFile(filename)
3175 class RetryTimeout(Exception):
3176 """Retry loop timed out.
3178 Any arguments which was passed by the retried function to RetryAgain will be
3179 preserved in RetryTimeout, if it is raised. If such argument was an exception
3180 the RaiseInner helper method will reraise it.
3183 def RaiseInner(self):
3184 if self.args and isinstance(self.args[0], Exception):
3187 raise RetryTimeout(*self.args)
3190 class RetryAgain(Exception):
3193 Any arguments passed to RetryAgain will be preserved, if a timeout occurs, as
3194 arguments to RetryTimeout. If an exception is passed, the RaiseInner() method
3195 of the RetryTimeout() method can be used to reraise it.
3200 class _RetryDelayCalculator(object):
3201 """Calculator for increasing delays.
3211 def __init__(self, start, factor, limit):
3212 """Initializes this class.
3215 @param start: Initial delay
3217 @param factor: Factor for delay increase
3218 @type limit: float or None
3219 @param limit: Upper limit for delay or None for no limit
3223 assert factor >= 1.0
3224 assert limit is None or limit >= 0.0
3227 self._factor = factor
3233 """Returns current delay and calculates the next one.
3236 current = self._next
3238 # Update for next run
3239 if self._limit is None or self._next < self._limit:
3240 self._next = min(self._limit, self._next * self._factor)
3245 #: Special delay to specify whole remaining timeout
3246 RETRY_REMAINING_TIME = object()
3249 def Retry(fn, delay, timeout, args=None, wait_fn=time.sleep,
3250 _time_fn=time.time):
3251 """Call a function repeatedly until it succeeds.
3253 The function C{fn} is called repeatedly until it doesn't throw L{RetryAgain}
3254 anymore. Between calls a delay, specified by C{delay}, is inserted. After a
3255 total of C{timeout} seconds, this function throws L{RetryTimeout}.
3257 C{delay} can be one of the following:
3258 - callable returning the delay length as a float
3259 - Tuple of (start, factor, limit)
3260 - L{RETRY_REMAINING_TIME} to sleep until the timeout expires (this is
3261 useful when overriding L{wait_fn} to wait for an external event)
3262 - A static delay as a number (int or float)
3265 @param fn: Function to be called
3266 @param delay: Either a callable (returning the delay), a tuple of (start,
3267 factor, limit) (see L{_RetryDelayCalculator}),
3268 L{RETRY_REMAINING_TIME} or a number (int or float)
3269 @type timeout: float
3270 @param timeout: Total timeout
3271 @type wait_fn: callable
3272 @param wait_fn: Waiting function
3273 @return: Return value of function
3277 assert callable(wait_fn)
3278 assert callable(_time_fn)
3283 end_time = _time_fn() + timeout
3286 # External function to calculate delay
3289 elif isinstance(delay, (tuple, list)):
3290 # Increasing delay with optional upper boundary
3291 (start, factor, limit) = delay
3292 calc_delay = _RetryDelayCalculator(start, factor, limit)
3294 elif delay is RETRY_REMAINING_TIME:
3295 # Always use the remaining time
3300 calc_delay = lambda: delay
3302 assert calc_delay is None or callable(calc_delay)
3307 # pylint: disable-msg=W0142
3309 except RetryAgain, err:
3310 retry_args = err.args
3311 except RetryTimeout:
3312 raise errors.ProgrammerError("Nested retry loop detected that didn't"
3313 " handle RetryTimeout")
3315 remaining_time = end_time - _time_fn()
3317 if remaining_time < 0.0:
3318 # pylint: disable-msg=W0142
3319 raise RetryTimeout(*retry_args)
3321 assert remaining_time >= 0.0
3323 if calc_delay is None:
3324 wait_fn(remaining_time)
3326 current_delay = calc_delay()
3327 if current_delay > 0.0:
3328 wait_fn(current_delay)
3331 def GetClosedTempfile(*args, **kwargs):
3332 """Creates a temporary file and returns its path.
3335 (fd, path) = tempfile.mkstemp(*args, **kwargs)
3340 def GenerateSelfSignedX509Cert(common_name, validity):
3341 """Generates a self-signed X509 certificate.
3343 @type common_name: string
3344 @param common_name: commonName value
3346 @param validity: Validity for certificate in seconds
3349 # Create private and public key
3350 key = OpenSSL.crypto.PKey()
3351 key.generate_key(OpenSSL.crypto.TYPE_RSA, constants.RSA_KEY_BITS)
3353 # Create self-signed certificate
3354 cert = OpenSSL.crypto.X509()
3356 cert.get_subject().CN = common_name
3357 cert.set_serial_number(1)
3358 cert.gmtime_adj_notBefore(0)
3359 cert.gmtime_adj_notAfter(validity)
3360 cert.set_issuer(cert.get_subject())
3361 cert.set_pubkey(key)
3362 cert.sign(key, constants.X509_CERT_SIGN_DIGEST)
3364 key_pem = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key)
3365 cert_pem = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)
3367 return (key_pem, cert_pem)
3370 def GenerateSelfSignedSslCert(filename, common_name=constants.X509_CERT_CN,
3371 validity=constants.X509_CERT_DEFAULT_VALIDITY):
3372 """Legacy function to generate self-signed X509 certificate.
3375 @param filename: path to write certificate to
3376 @type common_name: string
3377 @param common_name: commonName value
3379 @param validity: validity of certificate in number of days
3382 # TODO: Investigate using the cluster name instead of X505_CERT_CN for
3383 # common_name, as cluster-renames are very seldom, and it'd be nice if RAPI
3384 # and node daemon certificates have the proper Subject/Issuer.
3385 (key_pem, cert_pem) = GenerateSelfSignedX509Cert(common_name,
3386 validity * 24 * 60 * 60)
3388 WriteFile(filename, mode=0400, data=key_pem + cert_pem)
3391 class FileLock(object):
3392 """Utility class for file locks.
3395 def __init__(self, fd, filename):
3396 """Constructor for FileLock.
3399 @param fd: File object
3401 @param filename: Path of the file opened at I{fd}
3405 self.filename = filename
3408 def Open(cls, filename):
3409 """Creates and opens a file to be used as a file-based lock.
3411 @type filename: string
3412 @param filename: path to the file to be locked
3415 # Using "os.open" is necessary to allow both opening existing file
3416 # read/write and creating if not existing. Vanilla "open" will truncate an
3417 # existing file -or- allow creating if not existing.
3418 return cls(os.fdopen(os.open(filename, os.O_RDWR | os.O_CREAT), "w+"),
3425 """Close the file and release the lock.
3428 if hasattr(self, "fd") and self.fd:
3432 def _flock(self, flag, blocking, timeout, errmsg):
3433 """Wrapper for fcntl.flock.
3436 @param flag: operation flag
3437 @type blocking: bool
3438 @param blocking: whether the operation should be done in blocking mode.
3439 @type timeout: None or float
3440 @param timeout: for how long the operation should be retried (implies
3442 @type errmsg: string
3443 @param errmsg: error message in case operation fails.
3446 assert self.fd, "Lock was closed"
3447 assert timeout is None or timeout >= 0, \
3448 "If specified, timeout must be positive"
3449 assert not (flag & fcntl.LOCK_NB), "LOCK_NB must not be set"
3451 # When a timeout is used, LOCK_NB must always be set
3452 if not (timeout is None and blocking):
3453 flag |= fcntl.LOCK_NB
3456 self._Lock(self.fd, flag, timeout)
3459 Retry(self._Lock, (0.1, 1.2, 1.0), timeout,
3460 args=(self.fd, flag, timeout))
3461 except RetryTimeout:
3462 raise errors.LockError(errmsg)
3465 def _Lock(fd, flag, timeout):
3467 fcntl.flock(fd, flag)
3468 except IOError, err:
3469 if timeout is not None and err.errno == errno.EAGAIN:
3472 logging.exception("fcntl.flock failed")
3475 def Exclusive(self, blocking=False, timeout=None):
3476 """Locks the file in exclusive mode.
3478 @type blocking: boolean
3479 @param blocking: whether to block and wait until we
3480 can lock the file or return immediately
3481 @type timeout: int or None
3482 @param timeout: if not None, the duration to wait for the lock
3486 self._flock(fcntl.LOCK_EX, blocking, timeout,
3487 "Failed to lock %s in exclusive mode" % self.filename)
3489 def Shared(self, blocking=False, timeout=None):
3490 """Locks the file in shared mode.
3492 @type blocking: boolean
3493 @param blocking: whether to block and wait until we
3494 can lock the file or return immediately
3495 @type timeout: int or None
3496 @param timeout: if not None, the duration to wait for the lock
3500 self._flock(fcntl.LOCK_SH, blocking, timeout,
3501 "Failed to lock %s in shared mode" % self.filename)
3503 def Unlock(self, blocking=True, timeout=None):
3504 """Unlocks the file.
3506 According to C{flock(2)}, unlocking can also be a nonblocking
3509 To make a non-blocking request, include LOCK_NB with any of the above
3512 @type blocking: boolean
3513 @param blocking: whether to block and wait until we
3514 can lock the file or return immediately
3515 @type timeout: int or None
3516 @param timeout: if not None, the duration to wait for the lock
3520 self._flock(fcntl.LOCK_UN, blocking, timeout,
3521 "Failed to unlock %s" % self.filename)
3525 """Splits data chunks into lines separated by newline.
3527 Instances provide a file-like interface.
3530 def __init__(self, line_fn, *args):
3531 """Initializes this class.
3533 @type line_fn: callable
3534 @param line_fn: Function called for each line, first parameter is line
3535 @param args: Extra arguments for L{line_fn}
3538 assert callable(line_fn)
3541 # Python 2.4 doesn't have functools.partial yet
3543 lambda line: line_fn(line, *args) # pylint: disable-msg=W0142
3545 self._line_fn = line_fn
3547 self._lines = collections.deque()
3550 def write(self, data):
3551 parts = (self._buffer + data).split("\n")
3552 self._buffer = parts.pop()
3553 self._lines.extend(parts)
3557 self._line_fn(self._lines.popleft().rstrip("\r\n"))
3562 self._line_fn(self._buffer)
3565 def SignalHandled(signums):
3566 """Signal Handled decoration.
3568 This special decorator installs a signal handler and then calls the target
3569 function. The function must accept a 'signal_handlers' keyword argument,
3570 which will contain a dict indexed by signal number, with SignalHandler
3573 The decorator can be safely stacked with iself, to handle multiple signals
3574 with different handlers.
3577 @param signums: signals to intercept
3581 def sig_function(*args, **kwargs):
3582 assert 'signal_handlers' not in kwargs or \
3583 kwargs['signal_handlers'] is None or \
3584 isinstance(kwargs['signal_handlers'], dict), \
3585 "Wrong signal_handlers parameter in original function call"
3586 if 'signal_handlers' in kwargs and kwargs['signal_handlers'] is not None:
3587 signal_handlers = kwargs['signal_handlers']
3589 signal_handlers = {}
3590 kwargs['signal_handlers'] = signal_handlers
3591 sighandler = SignalHandler(signums)
3594 signal_handlers[sig] = sighandler
3595 return fn(*args, **kwargs)
3602 class SignalWakeupFd(object):
3604 # This is only supported in Python 2.5 and above (some distributions
3605 # backported it to Python 2.4)
3606 _set_wakeup_fd_fn = signal.set_wakeup_fd
3607 except AttributeError:
3609 def _SetWakeupFd(self, _): # pylint: disable-msg=R0201
3612 def _SetWakeupFd(self, fd):
3613 return self._set_wakeup_fd_fn(fd)
3616 """Initializes this class.
3619 (read_fd, write_fd) = os.pipe()
3621 # Once these succeeded, the file descriptors will be closed automatically.
3622 # Buffer size 0 is important, otherwise .read() with a specified length
3623 # might buffer data and the file descriptors won't be marked readable.
3624 self._read_fh = os.fdopen(read_fd, "r", 0)
3625 self._write_fh = os.fdopen(write_fd, "w", 0)
3627 self._previous = self._SetWakeupFd(self._write_fh.fileno())
3630 self.fileno = self._read_fh.fileno
3631 self.read = self._read_fh.read
3634 """Restores the previous wakeup file descriptor.
3637 if hasattr(self, "_previous") and self._previous is not None:
3638 self._SetWakeupFd(self._previous)
3639 self._previous = None
3642 """Notifies the wakeup file descriptor.
3645 self._write_fh.write("\0")
3648 """Called before object deletion.
3654 class SignalHandler(object):
3655 """Generic signal handler class.
3657 It automatically restores the original handler when deconstructed or
3658 when L{Reset} is called. You can either pass your own handler
3659 function in or query the L{called} attribute to detect whether the
3663 @ivar signum: the signals we handle
3664 @type called: boolean
3665 @ivar called: tracks whether any of the signals have been raised
3668 def __init__(self, signum, handler_fn=None, wakeup=None):
3669 """Constructs a new SignalHandler instance.
3671 @type signum: int or list of ints
3672 @param signum: Single signal number or set of signal numbers
3673 @type handler_fn: callable
3674 @param handler_fn: Signal handling function
3677 assert handler_fn is None or callable(handler_fn)
3679 self.signum = set(signum)
3682 self._handler_fn = handler_fn
3683 self._wakeup = wakeup
3687 for signum in self.signum:
3689 prev_handler = signal.signal(signum, self._HandleSignal)
3691 self._previous[signum] = prev_handler
3693 # Restore previous handler
3694 signal.signal(signum, prev_handler)
3697 # Reset all handlers
3699 # Here we have a race condition: a handler may have already been called,
3700 # but there's not much we can do about it at this point.
3707 """Restore previous handler.
3709 This will reset all the signals to their previous handlers.
3712 for signum, prev_handler in self._previous.items():
3713 signal.signal(signum, prev_handler)
3714 # If successful, remove from dict
3715 del self._previous[signum]
3718 """Unsets the L{called} flag.
3720 This function can be used in case a signal may arrive several times.
3725 def _HandleSignal(self, signum, frame):
3726 """Actual signal handling function.
3729 # This is not nice and not absolutely atomic, but it appears to be the only
3730 # solution in Python -- there are no atomic types.
3734 # Notify whoever is interested in signals
3735 self._wakeup.Notify()
3737 if self._handler_fn:
3738 self._handler_fn(signum, frame)
3741 class FieldSet(object):
3742 """A simple field set.
3744 Among the features are:
3745 - checking if a string is among a list of static string or regex objects
3746 - checking if a whole list of string matches
3747 - returning the matching groups from a regex match
3749 Internally, all fields are held as regular expression objects.
3752 def __init__(self, *items):
3753 self.items = [re.compile("^%s$" % value) for value in items]
3755 def Extend(self, other_set):
3756 """Extend the field set with the items from another one"""
3757 self.items.extend(other_set.items)
3759 def Matches(self, field):
3760 """Checks if a field matches the current set
3763 @param field: the string to match
3764 @return: either None or a regular expression match object
3767 for m in itertools.ifilter(None, (val.match(field) for val in self.items)):
3771 def NonMatching(self, items):
3772 """Returns the list of fields not matching the current set
3775 @param items: the list of fields to check
3777 @return: list of non-matching fields
3780 return [val for val in items if not self.Matches(val)]