4 # Copyright (C) 2006, 2007 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.
44 import logging.handlers
52 from cStringIO import StringIO
55 from hashlib import sha1
65 from ganeti import errors
66 from ganeti import constants
70 _re_shell_unquoted = re.compile('^[-.,=:/_+@A-Za-z0-9]+$')
74 #: when set to True, L{RunCmd} is disabled
77 _RANDOM_UUID_FILE = "/proc/sys/kernel/random/uuid"
79 # Structure definition for getsockopt(SOL_SOCKET, SO_PEERCRED, ...):
80 # struct ucred { pid_t pid; uid_t uid; gid_t gid; };
82 # The GNU C Library defines gid_t and uid_t to be "unsigned int" and
85 # IEEE Std 1003.1-2008:
86 # "nlink_t, uid_t, gid_t, and id_t shall be integer types"
87 # "blksize_t, pid_t, and ssize_t shall be signed integer types"
89 _STRUCT_UCRED_SIZE = struct.calcsize(_STRUCT_UCRED)
91 # Flags for mlockall() (from bits/mman.h)
96 class RunResult(object):
97 """Holds the result of running external programs.
100 @ivar exit_code: the exit code of the program, or None (if the program
102 @type signal: int or None
103 @ivar signal: the signal that caused the program to finish, or None
104 (if the program wasn't terminated by a signal)
106 @ivar stdout: the standard output of the program
108 @ivar stderr: the standard error of the program
109 @type failed: boolean
110 @ivar failed: True in case the program was
111 terminated by a signal or exited with a non-zero exit code
112 @ivar fail_reason: a string detailing the termination reason
115 __slots__ = ["exit_code", "signal", "stdout", "stderr",
116 "failed", "fail_reason", "cmd"]
119 def __init__(self, exit_code, signal_, stdout, stderr, cmd):
121 self.exit_code = exit_code
122 self.signal = signal_
125 self.failed = (signal_ is not None or exit_code != 0)
127 if self.signal is not None:
128 self.fail_reason = "terminated by signal %s" % self.signal
129 elif self.exit_code is not None:
130 self.fail_reason = "exited with exit code %s" % self.exit_code
132 self.fail_reason = "unable to determine termination reason"
135 logging.debug("Command '%s' failed (%s); output: %s",
136 self.cmd, self.fail_reason, self.output)
138 def _GetOutput(self):
139 """Returns the combined stdout and stderr for easier usage.
142 return self.stdout + self.stderr
144 output = property(_GetOutput, None, None, "Return full output")
147 def RunCmd(cmd, env=None, output=None, cwd='/', reset_env=False):
148 """Execute a (shell) command.
150 The command should not read from its standard input, as it will be
153 @type cmd: string or list
154 @param cmd: Command to run
156 @param env: Additional environment
158 @param output: if desired, the output of the command can be
159 saved in a file instead of the RunResult instance; this
160 parameter denotes the file name (if not None)
162 @param cwd: if specified, will be used as the working
163 directory for the command; the default will be /
164 @type reset_env: boolean
165 @param reset_env: whether to reset or keep the default os environment
167 @return: RunResult instance
168 @raise errors.ProgrammerError: if we call this when forks are disabled
172 raise errors.ProgrammerError("utils.RunCmd() called with fork() disabled")
174 if isinstance(cmd, list):
175 cmd = [str(val) for val in cmd]
176 strcmd = " ".join(cmd)
181 logging.debug("RunCmd '%s'", strcmd)
184 cmd_env = os.environ.copy()
185 cmd_env["LC_ALL"] = "C"
194 out, err, status = _RunCmdPipe(cmd, cmd_env, shell, cwd)
196 status = _RunCmdFile(cmd, cmd_env, shell, output, cwd)
199 if err.errno == errno.ENOENT:
200 raise errors.OpExecError("Can't execute '%s': not found (%s)" %
212 return RunResult(exitcode, signal_, out, err, strcmd)
215 def _RunCmdPipe(cmd, env, via_shell, cwd):
216 """Run a command and return its output.
218 @type cmd: string or list
219 @param cmd: Command to run
221 @param env: The environment to use
222 @type via_shell: bool
223 @param via_shell: if we should run via the shell
225 @param cwd: the working directory for the program
227 @return: (out, err, status)
230 poller = select.poll()
231 child = subprocess.Popen(cmd, shell=via_shell,
232 stderr=subprocess.PIPE,
233 stdout=subprocess.PIPE,
234 stdin=subprocess.PIPE,
235 close_fds=True, env=env,
239 poller.register(child.stdout, select.POLLIN)
240 poller.register(child.stderr, select.POLLIN)
244 child.stdout.fileno(): (out, child.stdout),
245 child.stderr.fileno(): (err, child.stderr),
248 status = fcntl.fcntl(fd, fcntl.F_GETFL)
249 fcntl.fcntl(fd, fcntl.F_SETFL, status | os.O_NONBLOCK)
253 pollresult = poller.poll()
254 except EnvironmentError, eerr:
255 if eerr.errno == errno.EINTR:
258 except select.error, serr:
259 if serr[0] == errno.EINTR:
263 for fd, event in pollresult:
264 if event & select.POLLIN or event & select.POLLPRI:
265 data = fdmap[fd][1].read()
266 # no data from read signifies EOF (the same as POLLHUP)
268 poller.unregister(fd)
271 fdmap[fd][0].write(data)
272 if (event & select.POLLNVAL or event & select.POLLHUP or
273 event & select.POLLERR):
274 poller.unregister(fd)
280 status = child.wait()
281 return out, err, status
284 def _RunCmdFile(cmd, env, via_shell, output, cwd):
285 """Run a command and save its output to a file.
287 @type cmd: string or list
288 @param cmd: Command to run
290 @param env: The environment to use
291 @type via_shell: bool
292 @param via_shell: if we should run via the shell
294 @param output: the filename in which to save the output
296 @param cwd: the working directory for the program
298 @return: the exit status
301 fh = open(output, "a")
303 child = subprocess.Popen(cmd, shell=via_shell,
304 stderr=subprocess.STDOUT,
306 stdin=subprocess.PIPE,
307 close_fds=True, env=env,
311 status = child.wait()
317 def RunParts(dir_name, env=None, reset_env=False):
318 """Run Scripts or programs in a directory
320 @type dir_name: string
321 @param dir_name: absolute path to a directory
323 @param env: The environment to use
324 @type reset_env: boolean
325 @param reset_env: whether to reset or keep the default os environment
326 @rtype: list of tuples
327 @return: list of (name, (one of RUNDIR_STATUS), RunResult)
333 dir_contents = ListVisibleFiles(dir_name)
335 logging.warning("RunParts: skipping %s (cannot list: %s)", dir_name, err)
338 for relname in sorted(dir_contents):
339 fname = PathJoin(dir_name, relname)
340 if not (os.path.isfile(fname) and os.access(fname, os.X_OK) and
341 constants.EXT_PLUGIN_MASK.match(relname) is not None):
342 rr.append((relname, constants.RUNPARTS_SKIP, None))
345 result = RunCmd([fname], env=env, reset_env=reset_env)
346 except Exception, err: # pylint: disable-msg=W0703
347 rr.append((relname, constants.RUNPARTS_ERR, str(err)))
349 rr.append((relname, constants.RUNPARTS_RUN, result))
354 def GetSocketCredentials(sock):
355 """Returns the credentials of the foreign process connected to a socket.
357 @param sock: Unix socket
358 @rtype: tuple; (number, number, number)
359 @return: The PID, UID and GID of the connected foreign process.
362 peercred = sock.getsockopt(socket.SOL_SOCKET, IN.SO_PEERCRED,
364 return struct.unpack(_STRUCT_UCRED, peercred)
367 def RemoveFile(filename):
368 """Remove a file ignoring some errors.
370 Remove a file, ignoring non-existing ones or directories. Other
374 @param filename: the file to be removed
380 if err.errno not in (errno.ENOENT, errno.EISDIR):
384 def RenameFile(old, new, mkdir=False, mkdir_mode=0750):
388 @param old: Original path
392 @param mkdir: Whether to create target directory if it doesn't exist
393 @type mkdir_mode: int
394 @param mkdir_mode: Mode for newly created directories
398 return os.rename(old, new)
400 # In at least one use case of this function, the job queue, directory
401 # creation is very rare. Checking for the directory before renaming is not
403 if mkdir and err.errno == errno.ENOENT:
404 # Create directory and try again
405 Makedirs(os.path.dirname(new), mode=mkdir_mode)
407 return os.rename(old, new)
412 def Makedirs(path, mode=0750):
413 """Super-mkdir; create a leaf directory and all intermediate ones.
415 This is a wrapper around C{os.makedirs} adding error handling not implemented
420 os.makedirs(path, mode)
422 # Ignore EEXIST. This is only handled in os.makedirs as included in
423 # Python 2.5 and above.
424 if err.errno != errno.EEXIST or not os.path.exists(path):
428 def ResetTempfileModule():
429 """Resets the random name generator of the tempfile module.
431 This function should be called after C{os.fork} in the child process to
432 ensure it creates a newly seeded random generator. Otherwise it would
433 generate the same random parts as the parent process. If several processes
434 race for the creation of a temporary file, this could lead to one not getting
438 # pylint: disable-msg=W0212
439 if hasattr(tempfile, "_once_lock") and hasattr(tempfile, "_name_sequence"):
440 tempfile._once_lock.acquire()
442 # Reset random name generator
443 tempfile._name_sequence = None
445 tempfile._once_lock.release()
447 logging.critical("The tempfile module misses at least one of the"
448 " '_once_lock' and '_name_sequence' attributes")
451 def _FingerprintFile(filename):
452 """Compute the fingerprint of a file.
454 If the file does not exist, a None will be returned
458 @param filename: the filename to checksum
460 @return: the hex digest of the sha checksum of the contents
464 if not (os.path.exists(filename) and os.path.isfile(filename)):
477 return fp.hexdigest()
480 def FingerprintFiles(files):
481 """Compute fingerprints for a list of files.
484 @param files: the list of filename to fingerprint
486 @return: a dictionary filename: fingerprint, holding only
492 for filename in files:
493 cksum = _FingerprintFile(filename)
495 ret[filename] = cksum
500 def ForceDictType(target, key_types, allowed_values=None):
501 """Force the values of a dict to have certain types.
504 @param target: the dict to update
505 @type key_types: dict
506 @param key_types: dict mapping target dict keys to types
507 in constants.ENFORCEABLE_TYPES
508 @type allowed_values: list
509 @keyword allowed_values: list of specially allowed values
512 if allowed_values is None:
515 if not isinstance(target, dict):
516 msg = "Expected dictionary, got '%s'" % target
517 raise errors.TypeEnforcementError(msg)
520 if key not in key_types:
521 msg = "Unknown key '%s'" % key
522 raise errors.TypeEnforcementError(msg)
524 if target[key] in allowed_values:
527 ktype = key_types[key]
528 if ktype not in constants.ENFORCEABLE_TYPES:
529 msg = "'%s' has non-enforceable type %s" % (key, ktype)
530 raise errors.ProgrammerError(msg)
532 if ktype == constants.VTYPE_STRING:
533 if not isinstance(target[key], basestring):
534 if isinstance(target[key], bool) and not target[key]:
537 msg = "'%s' (value %s) is not a valid string" % (key, target[key])
538 raise errors.TypeEnforcementError(msg)
539 elif ktype == constants.VTYPE_BOOL:
540 if isinstance(target[key], basestring) and target[key]:
541 if target[key].lower() == constants.VALUE_FALSE:
543 elif target[key].lower() == constants.VALUE_TRUE:
546 msg = "'%s' (value %s) is not a valid boolean" % (key, target[key])
547 raise errors.TypeEnforcementError(msg)
552 elif ktype == constants.VTYPE_SIZE:
554 target[key] = ParseUnit(target[key])
555 except errors.UnitParseError, err:
556 msg = "'%s' (value %s) is not a valid size. error: %s" % \
557 (key, target[key], err)
558 raise errors.TypeEnforcementError(msg)
559 elif ktype == constants.VTYPE_INT:
561 target[key] = int(target[key])
562 except (ValueError, TypeError):
563 msg = "'%s' (value %s) is not a valid integer" % (key, target[key])
564 raise errors.TypeEnforcementError(msg)
567 def IsProcessAlive(pid):
568 """Check if a given pid exists on the system.
570 @note: zombie status is not handled, so zombie processes
571 will be returned as alive
573 @param pid: the process ID to check
575 @return: True if the process exists
582 except EnvironmentError, err:
583 if err.errno in (errno.ENOENT, errno.ENOTDIR):
585 elif err.errno == errno.EINVAL:
586 raise RetryAgain(err)
589 assert isinstance(pid, int), "pid must be an integer"
593 proc_entry = "/proc/%d/status" % pid
594 # /proc in a multiprocessor environment can have strange behaviors.
595 # Retry the os.stat a few times until we get a good result.
597 return Retry(_TryStat, (0.01, 1.5, 0.1), 0.5, args=[proc_entry])
598 except RetryTimeout, err:
602 def ReadPidFile(pidfile):
603 """Read a pid from a file.
605 @type pidfile: string
606 @param pidfile: path to the file containing the pid
608 @return: The process id, if the file exists and contains a valid PID,
613 raw_data = ReadOneLineFile(pidfile)
614 except EnvironmentError, err:
615 if err.errno != errno.ENOENT:
616 logging.exception("Can't read pid file")
621 except (TypeError, ValueError), err:
622 logging.info("Can't parse pid file contents", exc_info=True)
628 def MatchNameComponent(key, name_list, case_sensitive=True):
629 """Try to match a name against a list.
631 This function will try to match a name like test1 against a list
632 like C{['test1.example.com', 'test2.example.com', ...]}. Against
633 this list, I{'test1'} as well as I{'test1.example'} will match, but
634 not I{'test1.ex'}. A multiple match will be considered as no match
635 at all (e.g. I{'test1'} against C{['test1.example.com',
636 'test1.example.org']}), except when the key fully matches an entry
637 (e.g. I{'test1'} against C{['test1', 'test1.example.com']}).
640 @param key: the name to be searched
641 @type name_list: list
642 @param name_list: the list of strings against which to search the key
643 @type case_sensitive: boolean
644 @param case_sensitive: whether to provide a case-sensitive match
647 @return: None if there is no match I{or} if there are multiple matches,
648 otherwise the element from the list which matches
655 if not case_sensitive:
656 re_flags |= re.IGNORECASE
658 mo = re.compile("^%s(\..*)?$" % re.escape(key), re_flags)
661 for name in name_list:
662 if mo.match(name) is not None:
663 names_filtered.append(name)
664 if not case_sensitive and key == name.upper():
665 string_matches.append(name)
667 if len(string_matches) == 1:
668 return string_matches[0]
669 if len(names_filtered) == 1:
670 return names_filtered[0]
675 """Class implementing resolver and hostname functionality
678 _VALID_NAME_RE = re.compile("^[a-z0-9._-]{1,255}$")
680 def __init__(self, name=None):
681 """Initialize the host name object.
683 If the name argument is not passed, it will use this system's
688 name = self.SysName()
691 self.name, self.aliases, self.ipaddrs = self.LookupHostname(name)
692 self.ip = self.ipaddrs[0]
695 """Returns the hostname without domain.
698 return self.name.split('.')[0]
702 """Return the current system's name.
704 This is simply a wrapper over C{socket.gethostname()}.
707 return socket.gethostname()
710 def LookupHostname(hostname):
714 @param hostname: hostname to look up
717 @return: a tuple (name, aliases, ipaddrs) as returned by
718 C{socket.gethostbyname_ex}
719 @raise errors.ResolverError: in case of errors in resolving
723 result = socket.gethostbyname_ex(hostname)
724 except socket.gaierror, err:
725 # hostname not found in DNS
726 raise errors.ResolverError(hostname, err.args[0], err.args[1])
731 def NormalizeName(cls, hostname):
732 """Validate and normalize the given hostname.
734 @attention: the validation is a bit more relaxed than the standards
735 require; most importantly, we allow underscores in names
736 @raise errors.OpPrereqError: when the name is not valid
739 hostname = hostname.lower()
740 if (not cls._VALID_NAME_RE.match(hostname) or
741 # double-dots, meaning empty label
743 # empty initial label
744 hostname.startswith(".")):
745 raise errors.OpPrereqError("Invalid hostname '%s'" % hostname,
747 if hostname.endswith("."):
748 hostname = hostname.rstrip(".")
752 def GetHostInfo(name=None):
753 """Lookup host name and raise an OpPrereqError for failures"""
756 return HostInfo(name)
757 except errors.ResolverError, err:
758 raise errors.OpPrereqError("The given name (%s) does not resolve: %s" %
759 (err[0], err[2]), errors.ECODE_RESOLVER)
762 def ListVolumeGroups():
763 """List volume groups and their size
767 Dictionary with keys volume name and values
768 the size of the volume
771 command = "vgs --noheadings --units m --nosuffix -o name,size"
772 result = RunCmd(command)
777 for line in result.stdout.splitlines():
779 name, size = line.split()
780 size = int(float(size))
781 except (IndexError, ValueError), err:
782 logging.error("Invalid output from vgs (%s): %s", err, line)
790 def BridgeExists(bridge):
791 """Check whether the given bridge exists in the system
794 @param bridge: the bridge name to check
796 @return: True if it does
799 return os.path.isdir("/sys/class/net/%s/bridge" % bridge)
802 def NiceSort(name_list):
803 """Sort a list of strings based on digit and non-digit groupings.
805 Given a list of names C{['a1', 'a10', 'a11', 'a2']} this function
806 will sort the list in the logical order C{['a1', 'a2', 'a10',
809 The sort algorithm breaks each name in groups of either only-digits
810 or no-digits. Only the first eight such groups are considered, and
811 after that we just use what's left of the string.
813 @type name_list: list
814 @param name_list: the names to be sorted
816 @return: a copy of the name list sorted with our algorithm
819 _SORTER_BASE = "(\D+|\d+)"
820 _SORTER_FULL = "^%s%s?%s?%s?%s?%s?%s?%s?.*$" % (_SORTER_BASE, _SORTER_BASE,
821 _SORTER_BASE, _SORTER_BASE,
822 _SORTER_BASE, _SORTER_BASE,
823 _SORTER_BASE, _SORTER_BASE)
824 _SORTER_RE = re.compile(_SORTER_FULL)
825 _SORTER_NODIGIT = re.compile("^\D*$")
827 """Attempts to convert a variable to integer."""
828 if val is None or _SORTER_NODIGIT.match(val):
833 to_sort = [([_TryInt(grp) for grp in _SORTER_RE.match(name).groups()], name)
834 for name in name_list]
836 return [tup[1] for tup in to_sort]
839 def TryConvert(fn, val):
840 """Try to convert a value ignoring errors.
842 This function tries to apply function I{fn} to I{val}. If no
843 C{ValueError} or C{TypeError} exceptions are raised, it will return
844 the result, else it will return the original value. Any other
845 exceptions are propagated to the caller.
848 @param fn: function to apply to the value
849 @param val: the value to be converted
850 @return: The converted value if the conversion was successful,
851 otherwise the original value.
856 except (ValueError, TypeError):
862 """Verifies the syntax of an IPv4 address.
864 This function checks if the IPv4 address passes is valid or not based
865 on syntax (not IP range, class calculations, etc.).
868 @param ip: the address to be checked
869 @rtype: a regular expression match object
870 @return: a regular expression match object, or None if the
874 unit = "(0|[1-9]\d{0,2})"
875 #TODO: convert and return only boolean
876 return re.match("^%s\.%s\.%s\.%s$" % (unit, unit, unit, unit), ip)
879 def IsValidShellParam(word):
880 """Verifies is the given word is safe from the shell's p.o.v.
882 This means that we can pass this to a command via the shell and be
883 sure that it doesn't alter the command line and is passed as such to
886 Note that we are overly restrictive here, in order to be on the safe
890 @param word: the word to check
892 @return: True if the word is 'safe'
895 return bool(re.match("^[-a-zA-Z0-9._+/:%@]+$", word))
898 def BuildShellCmd(template, *args):
899 """Build a safe shell command line from the given arguments.
901 This function will check all arguments in the args list so that they
902 are valid shell parameters (i.e. they don't contain shell
903 metacharacters). If everything is ok, it will return the result of
907 @param template: the string holding the template for the
910 @return: the expanded command line
914 if not IsValidShellParam(word):
915 raise errors.ProgrammerError("Shell argument '%s' contains"
916 " invalid characters" % word)
917 return template % args
920 def FormatUnit(value, units):
921 """Formats an incoming number of MiB with the appropriate unit.
924 @param value: integer representing the value in MiB (1048576)
926 @param units: the type of formatting we should do:
927 - 'h' for automatic scaling
932 @return: the formatted value (with suffix)
935 if units not in ('m', 'g', 't', 'h'):
936 raise errors.ProgrammerError("Invalid unit specified '%s'" % str(units))
940 if units == 'm' or (units == 'h' and value < 1024):
943 return "%d%s" % (round(value, 0), suffix)
945 elif units == 'g' or (units == 'h' and value < (1024 * 1024)):
948 return "%0.1f%s" % (round(float(value) / 1024, 1), suffix)
953 return "%0.1f%s" % (round(float(value) / 1024 / 1024, 1), suffix)
956 def ParseUnit(input_string):
957 """Tries to extract number and scale from the given string.
959 Input must be in the format C{NUMBER+ [DOT NUMBER+] SPACE*
960 [UNIT]}. If no unit is specified, it defaults to MiB. Return value
961 is always an int in MiB.
964 m = re.match('^([.\d]+)\s*([a-zA-Z]+)?$', str(input_string))
966 raise errors.UnitParseError("Invalid format")
968 value = float(m.groups()[0])
972 lcunit = unit.lower()
976 if lcunit in ('m', 'mb', 'mib'):
977 # Value already in MiB
980 elif lcunit in ('g', 'gb', 'gib'):
983 elif lcunit in ('t', 'tb', 'tib'):
987 raise errors.UnitParseError("Unknown unit: %s" % unit)
989 # Make sure we round up
990 if int(value) < value:
993 # Round up to the next multiple of 4
996 value += 4 - value % 4
1001 def AddAuthorizedKey(file_name, key):
1002 """Adds an SSH public key to an authorized_keys file.
1004 @type file_name: str
1005 @param file_name: path to authorized_keys file
1007 @param key: string containing key
1010 key_fields = key.split()
1012 f = open(file_name, 'a+')
1016 # Ignore whitespace changes
1017 if line.split() == key_fields:
1019 nl = line.endswith('\n')
1023 f.write(key.rstrip('\r\n'))
1030 def RemoveAuthorizedKey(file_name, key):
1031 """Removes an SSH public key from an authorized_keys file.
1033 @type file_name: str
1034 @param file_name: path to authorized_keys file
1036 @param key: string containing key
1039 key_fields = key.split()
1041 fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
1043 out = os.fdopen(fd, 'w')
1045 f = open(file_name, 'r')
1048 # Ignore whitespace changes while comparing lines
1049 if line.split() != key_fields:
1053 os.rename(tmpname, file_name)
1063 def SetEtcHostsEntry(file_name, ip, hostname, aliases):
1064 """Sets the name of an IP address and hostname in /etc/hosts.
1066 @type file_name: str
1067 @param file_name: path to the file to modify (usually C{/etc/hosts})
1069 @param ip: the IP address
1071 @param hostname: the hostname to be added
1073 @param aliases: the list of aliases to add for the hostname
1076 # FIXME: use WriteFile + fn rather than duplicating its efforts
1077 # Ensure aliases are unique
1078 aliases = UniqueSequence([hostname] + aliases)[1:]
1080 fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
1082 out = os.fdopen(fd, 'w')
1084 f = open(file_name, 'r')
1087 fields = line.split()
1088 if fields and not fields[0].startswith('#') and ip == fields[0]:
1092 out.write("%s\t%s" % (ip, hostname))
1094 out.write(" %s" % ' '.join(aliases))
1099 os.chmod(tmpname, 0644)
1100 os.rename(tmpname, file_name)
1110 def AddHostToEtcHosts(hostname):
1111 """Wrapper around SetEtcHostsEntry.
1114 @param hostname: a hostname that will be resolved and added to
1115 L{constants.ETC_HOSTS}
1118 hi = HostInfo(name=hostname)
1119 SetEtcHostsEntry(constants.ETC_HOSTS, hi.ip, hi.name, [hi.ShortName()])
1122 def RemoveEtcHostsEntry(file_name, hostname):
1123 """Removes a hostname from /etc/hosts.
1125 IP addresses without names are removed from the file.
1127 @type file_name: str
1128 @param file_name: path to the file to modify (usually C{/etc/hosts})
1130 @param hostname: the hostname to be removed
1133 # FIXME: use WriteFile + fn rather than duplicating its efforts
1134 fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
1136 out = os.fdopen(fd, 'w')
1138 f = open(file_name, 'r')
1141 fields = line.split()
1142 if len(fields) > 1 and not fields[0].startswith('#'):
1144 if hostname in names:
1145 while hostname in names:
1146 names.remove(hostname)
1148 out.write("%s %s\n" % (fields[0], ' '.join(names)))
1155 os.chmod(tmpname, 0644)
1156 os.rename(tmpname, file_name)
1166 def RemoveHostFromEtcHosts(hostname):
1167 """Wrapper around RemoveEtcHostsEntry.
1170 @param hostname: hostname that will be resolved and its
1171 full and shot name will be removed from
1172 L{constants.ETC_HOSTS}
1175 hi = HostInfo(name=hostname)
1176 RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.name)
1177 RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.ShortName())
1180 def TimestampForFilename():
1181 """Returns the current time formatted for filenames.
1183 The format doesn't contain colons as some shells and applications them as
1187 return time.strftime("%Y-%m-%d_%H_%M_%S")
1190 def CreateBackup(file_name):
1191 """Creates a backup of a file.
1193 @type file_name: str
1194 @param file_name: file to be backed up
1196 @return: the path to the newly created backup
1197 @raise errors.ProgrammerError: for invalid file names
1200 if not os.path.isfile(file_name):
1201 raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
1204 prefix = ("%s.backup-%s." %
1205 (os.path.basename(file_name), TimestampForFilename()))
1206 dir_name = os.path.dirname(file_name)
1208 fsrc = open(file_name, 'rb')
1210 (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
1211 fdst = os.fdopen(fd, 'wb')
1213 logging.debug("Backing up %s at %s", file_name, backup_name)
1214 shutil.copyfileobj(fsrc, fdst)
1223 def ShellQuote(value):
1224 """Quotes shell argument according to POSIX.
1227 @param value: the argument to be quoted
1229 @return: the quoted value
1232 if _re_shell_unquoted.match(value):
1235 return "'%s'" % value.replace("'", "'\\''")
1238 def ShellQuoteArgs(args):
1239 """Quotes a list of shell arguments.
1242 @param args: list of arguments to be quoted
1244 @return: the quoted arguments concatenated with spaces
1247 return ' '.join([ShellQuote(i) for i in args])
1250 def TcpPing(target, port, timeout=10, live_port_needed=False, source=None):
1251 """Simple ping implementation using TCP connect(2).
1253 Check if the given IP is reachable by doing attempting a TCP connect
1257 @param target: the IP or hostname to ping
1259 @param port: the port to connect to
1261 @param timeout: the timeout on the connection attempt
1262 @type live_port_needed: boolean
1263 @param live_port_needed: whether a closed port will cause the
1264 function to return failure, as if there was a timeout
1265 @type source: str or None
1266 @param source: if specified, will cause the connect to be made
1267 from this specific source address; failures to bind other
1268 than C{EADDRNOTAVAIL} will be ignored
1271 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1275 if source is not None:
1277 sock.bind((source, 0))
1278 except socket.error, (errcode, _):
1279 if errcode == errno.EADDRNOTAVAIL:
1282 sock.settimeout(timeout)
1285 sock.connect((target, port))
1288 except socket.timeout:
1290 except socket.error, (errcode, _):
1291 success = (not live_port_needed) and (errcode == errno.ECONNREFUSED)
1296 def OwnIpAddress(address):
1297 """Check if the current host has the the given IP address.
1299 Currently this is done by TCP-pinging the address from the loopback
1302 @type address: string
1303 @param address: the address to check
1305 @return: True if we own the address
1308 return TcpPing(address, constants.DEFAULT_NODED_PORT,
1309 source=constants.LOCALHOST_IP_ADDRESS)
1312 def ListVisibleFiles(path):
1313 """Returns a list of visible files in a directory.
1316 @param path: the directory to enumerate
1318 @return: the list of all files not starting with a dot
1319 @raise ProgrammerError: if L{path} is not an absolue and normalized path
1322 if not IsNormAbsPath(path):
1323 raise errors.ProgrammerError("Path passed to ListVisibleFiles is not"
1324 " absolute/normalized: '%s'" % path)
1325 files = [i for i in os.listdir(path) if not i.startswith(".")]
1330 def GetHomeDir(user, default=None):
1331 """Try to get the homedir of the given user.
1333 The user can be passed either as a string (denoting the name) or as
1334 an integer (denoting the user id). If the user is not found, the
1335 'default' argument is returned, which defaults to None.
1339 if isinstance(user, basestring):
1340 result = pwd.getpwnam(user)
1341 elif isinstance(user, (int, long)):
1342 result = pwd.getpwuid(user)
1344 raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
1348 return result.pw_dir
1352 """Returns a random UUID.
1354 @note: This is a Linux-specific method as it uses the /proc
1359 return ReadFile(_RANDOM_UUID_FILE, size=128).rstrip("\n")
1362 def GenerateSecret(numbytes=20):
1363 """Generates a random secret.
1365 This will generate a pseudo-random secret returning an hex string
1366 (so that it can be used where an ASCII string is needed).
1368 @param numbytes: the number of bytes which will be represented by the returned
1369 string (defaulting to 20, the length of a SHA1 hash)
1371 @return: an hex representation of the pseudo-random sequence
1374 return os.urandom(numbytes).encode('hex')
1377 def EnsureDirs(dirs):
1378 """Make required directories, if they don't exist.
1380 @param dirs: list of tuples (dir_name, dir_mode)
1381 @type dirs: list of (string, integer)
1384 for dir_name, dir_mode in dirs:
1386 os.mkdir(dir_name, dir_mode)
1387 except EnvironmentError, err:
1388 if err.errno != errno.EEXIST:
1389 raise errors.GenericError("Cannot create needed directory"
1390 " '%s': %s" % (dir_name, err))
1391 if not os.path.isdir(dir_name):
1392 raise errors.GenericError("%s is not a directory" % dir_name)
1395 def ReadFile(file_name, size=-1):
1399 @param size: Read at most size bytes (if negative, entire file)
1401 @return: the (possibly partial) content of the file
1404 f = open(file_name, "r")
1411 def WriteFile(file_name, fn=None, data=None,
1412 mode=None, uid=-1, gid=-1,
1413 atime=None, mtime=None, close=True,
1414 dry_run=False, backup=False,
1415 prewrite=None, postwrite=None):
1416 """(Over)write a file atomically.
1418 The file_name and either fn (a function taking one argument, the
1419 file descriptor, and which should write the data to it) or data (the
1420 contents of the file) must be passed. The other arguments are
1421 optional and allow setting the file mode, owner and group, and the
1422 mtime/atime of the file.
1424 If the function doesn't raise an exception, it has succeeded and the
1425 target file has the new contents. If the function has raised an
1426 exception, an existing target file should be unmodified and the
1427 temporary file should be removed.
1429 @type file_name: str
1430 @param file_name: the target filename
1432 @param fn: content writing function, called with
1433 file descriptor as parameter
1435 @param data: contents of the file
1437 @param mode: file mode
1439 @param uid: the owner of the file
1441 @param gid: the group of the file
1443 @param atime: a custom access time to be set on the file
1445 @param mtime: a custom modification time to be set on the file
1446 @type close: boolean
1447 @param close: whether to close file after writing it
1448 @type prewrite: callable
1449 @param prewrite: function to be called before writing content
1450 @type postwrite: callable
1451 @param postwrite: function to be called after writing content
1454 @return: None if the 'close' parameter evaluates to True,
1455 otherwise the file descriptor
1457 @raise errors.ProgrammerError: if any of the arguments are not valid
1460 if not os.path.isabs(file_name):
1461 raise errors.ProgrammerError("Path passed to WriteFile is not"
1462 " absolute: '%s'" % file_name)
1464 if [fn, data].count(None) != 1:
1465 raise errors.ProgrammerError("fn or data required")
1467 if [atime, mtime].count(None) == 1:
1468 raise errors.ProgrammerError("Both atime and mtime must be either"
1471 if backup and not dry_run and os.path.isfile(file_name):
1472 CreateBackup(file_name)
1474 dir_name, base_name = os.path.split(file_name)
1475 fd, new_name = tempfile.mkstemp('.new', base_name, dir_name)
1477 # here we need to make sure we remove the temp file, if any error
1478 # leaves it in place
1480 if uid != -1 or gid != -1:
1481 os.chown(new_name, uid, gid)
1483 os.chmod(new_name, mode)
1484 if callable(prewrite):
1486 if data is not None:
1490 if callable(postwrite):
1493 if atime is not None and mtime is not None:
1494 os.utime(new_name, (atime, mtime))
1496 os.rename(new_name, file_name)
1505 RemoveFile(new_name)
1510 def ReadOneLineFile(file_name, strict=False):
1511 """Return the first non-empty line from a file.
1513 @type strict: boolean
1514 @param strict: if True, abort if the file has more than one
1518 file_lines = ReadFile(file_name).splitlines()
1519 full_lines = filter(bool, file_lines)
1520 if not file_lines or not full_lines:
1521 raise errors.GenericError("No data in one-liner file %s" % file_name)
1522 elif strict and len(full_lines) > 1:
1523 raise errors.GenericError("Too many lines in one-liner file %s" %
1525 return full_lines[0]
1528 def FirstFree(seq, base=0):
1529 """Returns the first non-existing integer from seq.
1531 The seq argument should be a sorted list of positive integers. The
1532 first time the index of an element is smaller than the element
1533 value, the index will be returned.
1535 The base argument is used to start at a different offset,
1536 i.e. C{[3, 4, 6]} with I{offset=3} will return 5.
1538 Example: C{[0, 1, 3]} will return I{2}.
1541 @param seq: the sequence to be analyzed.
1543 @param base: use this value as the base index of the sequence
1545 @return: the first non-used index in the sequence
1548 for idx, elem in enumerate(seq):
1549 assert elem >= base, "Passed element is higher than base offset"
1550 if elem > idx + base:
1556 def SingleWaitForFdCondition(fdobj, event, timeout):
1557 """Waits for a condition to occur on the socket.
1559 Immediately returns at the first interruption.
1561 @type fdobj: integer or object supporting a fileno() method
1562 @param fdobj: entity to wait for events on
1563 @type event: integer
1564 @param event: ORed condition (see select module)
1565 @type timeout: float or None
1566 @param timeout: Timeout in seconds
1568 @return: None for timeout, otherwise occured conditions
1571 check = (event | select.POLLPRI |
1572 select.POLLNVAL | select.POLLHUP | select.POLLERR)
1574 if timeout is not None:
1575 # Poller object expects milliseconds
1578 poller = select.poll()
1579 poller.register(fdobj, event)
1581 # TODO: If the main thread receives a signal and we have no timeout, we
1582 # could wait forever. This should check a global "quit" flag or something
1584 io_events = poller.poll(timeout)
1585 except select.error, err:
1586 if err[0] != errno.EINTR:
1589 if io_events and io_events[0][1] & check:
1590 return io_events[0][1]
1595 class FdConditionWaiterHelper(object):
1596 """Retry helper for WaitForFdCondition.
1598 This class contains the retried and wait functions that make sure
1599 WaitForFdCondition can continue waiting until the timeout is actually
1604 def __init__(self, timeout):
1605 self.timeout = timeout
1607 def Poll(self, fdobj, event):
1608 result = SingleWaitForFdCondition(fdobj, event, self.timeout)
1614 def UpdateTimeout(self, timeout):
1615 self.timeout = timeout
1618 def WaitForFdCondition(fdobj, event, timeout):
1619 """Waits for a condition to occur on the socket.
1621 Retries until the timeout is expired, even if interrupted.
1623 @type fdobj: integer or object supporting a fileno() method
1624 @param fdobj: entity to wait for events on
1625 @type event: integer
1626 @param event: ORed condition (see select module)
1627 @type timeout: float or None
1628 @param timeout: Timeout in seconds
1630 @return: None for timeout, otherwise occured conditions
1633 if timeout is not None:
1634 retrywaiter = FdConditionWaiterHelper(timeout)
1636 result = Retry(retrywaiter.Poll, RETRY_REMAINING_TIME, timeout,
1637 args=(fdobj, event), wait_fn=retrywaiter.UpdateTimeout)
1638 except RetryTimeout:
1642 while result is None:
1643 result = SingleWaitForFdCondition(fdobj, event, timeout)
1647 def UniqueSequence(seq):
1648 """Returns a list with unique elements.
1650 Element order is preserved.
1653 @param seq: the sequence with the source elements
1655 @return: list of unique elements from seq
1659 return [i for i in seq if i not in seen and not seen.add(i)]
1662 def NormalizeAndValidateMac(mac):
1663 """Normalizes and check if a MAC address is valid.
1665 Checks whether the supplied MAC address is formally correct, only
1666 accepts colon separated format. Normalize it to all lower.
1669 @param mac: the MAC to be validated
1671 @return: returns the normalized and validated MAC.
1673 @raise errors.OpPrereqError: If the MAC isn't valid
1676 mac_check = re.compile("^([0-9a-f]{2}(:|$)){6}$", re.I)
1677 if not mac_check.match(mac):
1678 raise errors.OpPrereqError("Invalid MAC address specified: %s" %
1679 mac, errors.ECODE_INVAL)
1684 def TestDelay(duration):
1685 """Sleep for a fixed amount of time.
1687 @type duration: float
1688 @param duration: the sleep duration
1690 @return: False for negative value, True otherwise
1694 return False, "Invalid sleep duration"
1695 time.sleep(duration)
1699 def _CloseFDNoErr(fd, retries=5):
1700 """Close a file descriptor ignoring errors.
1703 @param fd: the file descriptor
1705 @param retries: how many retries to make, in case we get any
1706 other error than EBADF
1711 except OSError, err:
1712 if err.errno != errno.EBADF:
1714 _CloseFDNoErr(fd, retries - 1)
1715 # else either it's closed already or we're out of retries, so we
1716 # ignore this and go on
1719 def CloseFDs(noclose_fds=None):
1720 """Close file descriptors.
1722 This closes all file descriptors above 2 (i.e. except
1725 @type noclose_fds: list or None
1726 @param noclose_fds: if given, it denotes a list of file descriptor
1727 that should not be closed
1730 # Default maximum for the number of available file descriptors.
1731 if 'SC_OPEN_MAX' in os.sysconf_names:
1733 MAXFD = os.sysconf('SC_OPEN_MAX')
1740 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1741 if (maxfd == resource.RLIM_INFINITY):
1744 # Iterate through and close all file descriptors (except the standard ones)
1745 for fd in range(3, maxfd):
1746 if noclose_fds and fd in noclose_fds:
1752 """Lock current process' virtual address space into RAM.
1754 This is equivalent to the C call mlockall(MCL_CURRENT|MCL_FUTURE),
1755 see mlock(2) for more details. This function requires ctypes module.
1759 logging.warning("Cannot set memory lock, ctypes module not found")
1762 libc = ctypes.cdll.LoadLibrary("libc.so.6")
1764 logging.error("Cannot set memory lock, ctypes cannot load libc")
1767 # Some older version of the ctypes module don't have built-in functionality
1768 # to access the errno global variable, where function error codes are stored.
1769 # By declaring this variable as a pointer to an integer we can then access
1770 # its value correctly, should the mlockall call fail, in order to see what
1771 # the actual error code was.
1772 libc.__errno_location.restype = ctypes.POINTER(ctypes.c_int)
1774 if libc.mlockall(_MCL_CURRENT | _MCL_FUTURE):
1775 logging.error("Cannot set memory lock: %s" %
1776 os.strerror(libc.__errno_location().contents.value))
1779 logging.debug("Memory lock set")
1782 def Daemonize(logfile):
1783 """Daemonize the current process.
1785 This detaches the current process from the controlling terminal and
1786 runs it in the background as a daemon.
1789 @param logfile: the logfile to which we should redirect stdout/stderr
1791 @return: the value zero
1794 # pylint: disable-msg=W0212
1795 # yes, we really want os._exit
1801 if (pid == 0): # The first child.
1804 pid = os.fork() # Fork a second child.
1805 if (pid == 0): # The second child.
1809 # exit() or _exit()? See below.
1810 os._exit(0) # Exit parent (the first child) of the second child.
1812 os._exit(0) # Exit parent of the first child.
1816 i = os.open("/dev/null", os.O_RDONLY) # stdin
1817 assert i == 0, "Can't close/reopen stdin"
1818 i = os.open(logfile, os.O_WRONLY|os.O_CREAT|os.O_APPEND, 0600) # stdout
1819 assert i == 1, "Can't close/reopen stdout"
1820 # Duplicate standard output to standard error.
1825 def DaemonPidFileName(name):
1826 """Compute a ganeti pid file absolute path
1829 @param name: the daemon name
1831 @return: the full path to the pidfile corresponding to the given
1835 return PathJoin(constants.RUN_GANETI_DIR, "%s.pid" % name)
1838 def EnsureDaemon(name):
1839 """Check for and start daemon if not alive.
1842 result = RunCmd([constants.DAEMON_UTIL, "check-and-start", name])
1844 logging.error("Can't start daemon '%s', failure %s, output: %s",
1845 name, result.fail_reason, result.output)
1851 def WritePidFile(name):
1852 """Write the current process pidfile.
1854 The file will be written to L{constants.RUN_GANETI_DIR}I{/name.pid}
1857 @param name: the daemon name to use
1858 @raise errors.GenericError: if the pid file already exists and
1859 points to a live process
1863 pidfilename = DaemonPidFileName(name)
1864 if IsProcessAlive(ReadPidFile(pidfilename)):
1865 raise errors.GenericError("%s contains a live process" % pidfilename)
1867 WriteFile(pidfilename, data="%d\n" % pid)
1870 def RemovePidFile(name):
1871 """Remove the current process pidfile.
1873 Any errors are ignored.
1876 @param name: the daemon name used to derive the pidfile name
1879 pidfilename = DaemonPidFileName(name)
1880 # TODO: we could check here that the file contains our pid
1882 RemoveFile(pidfilename)
1883 except: # pylint: disable-msg=W0702
1887 def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
1889 """Kill a process given by its pid.
1892 @param pid: The PID to terminate.
1894 @param signal_: The signal to send, by default SIGTERM
1896 @param timeout: The timeout after which, if the process is still alive,
1897 a SIGKILL will be sent. If not positive, no such checking
1899 @type waitpid: boolean
1900 @param waitpid: If true, we should waitpid on this process after
1901 sending signals, since it's our own child and otherwise it
1902 would remain as zombie
1905 def _helper(pid, signal_, wait):
1906 """Simple helper to encapsulate the kill/waitpid sequence"""
1907 os.kill(pid, signal_)
1910 os.waitpid(pid, os.WNOHANG)
1915 # kill with pid=0 == suicide
1916 raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
1918 if not IsProcessAlive(pid):
1921 _helper(pid, signal_, waitpid)
1926 def _CheckProcess():
1927 if not IsProcessAlive(pid):
1931 (result_pid, _) = os.waitpid(pid, os.WNOHANG)
1941 # Wait up to $timeout seconds
1942 Retry(_CheckProcess, (0.01, 1.5, 0.1), timeout)
1943 except RetryTimeout:
1946 if IsProcessAlive(pid):
1947 # Kill process if it's still alive
1948 _helper(pid, signal.SIGKILL, waitpid)
1951 def FindFile(name, search_path, test=os.path.exists):
1952 """Look for a filesystem object in a given path.
1954 This is an abstract method to search for filesystem object (files,
1955 dirs) under a given search path.
1958 @param name: the name to look for
1959 @type search_path: str
1960 @param search_path: location to start at
1961 @type test: callable
1962 @param test: a function taking one argument that should return True
1963 if the a given object is valid; the default value is
1964 os.path.exists, causing only existing files to be returned
1966 @return: full path to the object if found, None otherwise
1969 # validate the filename mask
1970 if constants.EXT_PLUGIN_MASK.match(name) is None:
1971 logging.critical("Invalid value passed for external script name: '%s'",
1975 for dir_name in search_path:
1976 # FIXME: investigate switch to PathJoin
1977 item_name = os.path.sep.join([dir_name, name])
1978 # check the user test and that we're indeed resolving to the given
1980 if test(item_name) and os.path.basename(item_name) == name:
1985 def CheckVolumeGroupSize(vglist, vgname, minsize):
1986 """Checks if the volume group list is valid.
1988 The function will check if a given volume group is in the list of
1989 volume groups and has a minimum size.
1992 @param vglist: dictionary of volume group names and their size
1994 @param vgname: the volume group we should check
1996 @param minsize: the minimum size we accept
1998 @return: None for success, otherwise the error message
2001 vgsize = vglist.get(vgname, None)
2003 return "volume group '%s' missing" % vgname
2004 elif vgsize < minsize:
2005 return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
2006 (vgname, minsize, vgsize))
2010 def SplitTime(value):
2011 """Splits time as floating point number into a tuple.
2013 @param value: Time in seconds
2014 @type value: int or float
2015 @return: Tuple containing (seconds, microseconds)
2018 (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
2020 assert 0 <= seconds, \
2021 "Seconds must be larger than or equal to 0, but are %s" % seconds
2022 assert 0 <= microseconds <= 999999, \
2023 "Microseconds must be 0-999999, but are %s" % microseconds
2025 return (int(seconds), int(microseconds))
2028 def MergeTime(timetuple):
2029 """Merges a tuple into time as a floating point number.
2031 @param timetuple: Time as tuple, (seconds, microseconds)
2032 @type timetuple: tuple
2033 @return: Time as a floating point number expressed in seconds
2036 (seconds, microseconds) = timetuple
2038 assert 0 <= seconds, \
2039 "Seconds must be larger than or equal to 0, but are %s" % seconds
2040 assert 0 <= microseconds <= 999999, \
2041 "Microseconds must be 0-999999, but are %s" % microseconds
2043 return float(seconds) + (float(microseconds) * 0.000001)
2046 def GetDaemonPort(daemon_name):
2047 """Get the daemon port for this cluster.
2049 Note that this routine does not read a ganeti-specific file, but
2050 instead uses C{socket.getservbyname} to allow pre-customization of
2051 this parameter outside of Ganeti.
2053 @type daemon_name: string
2054 @param daemon_name: daemon name (in constants.DAEMONS_PORTS)
2058 if daemon_name not in constants.DAEMONS_PORTS:
2059 raise errors.ProgrammerError("Unknown daemon: %s" % daemon_name)
2061 (proto, default_port) = constants.DAEMONS_PORTS[daemon_name]
2063 port = socket.getservbyname(daemon_name, proto)
2064 except socket.error:
2070 class LogFileHandler(logging.FileHandler):
2071 """Log handler that doesn't fallback to stderr.
2073 When an error occurs while writing on the logfile, logging.FileHandler tries
2074 to log on stderr. This doesn't work in ganeti since stderr is redirected to
2075 the logfile. This class avoids failures reporting errors to /dev/console.
2078 def __init__(self, filename, mode="a", encoding=None):
2079 """Open the specified file and use it as the stream for logging.
2081 Also open /dev/console to report errors while logging.
2084 logging.FileHandler.__init__(self, filename, mode, encoding)
2085 self.console = open(constants.DEV_CONSOLE, "a")
2087 def handleError(self, record):
2088 """Handle errors which occur during an emit() call.
2090 Try to handle errors with FileHandler method, if it fails write to
2095 logging.Filehandler.handleError(self, record)
2098 self.console.write("Cannot log message:\n%s\n" % self.format(record))
2100 # Log handler tried everything it could, now just give up
2104 def SetupLogging(logfile, debug=0, stderr_logging=False, program="",
2105 multithreaded=False, syslog=constants.SYSLOG_USAGE,
2106 console_logging=False):
2107 """Configures the logging module.
2110 @param logfile: the filename to which we should log
2111 @type debug: integer
2112 @param debug: if greater than zero, enable debug messages, otherwise
2113 only those at C{INFO} and above level
2114 @type stderr_logging: boolean
2115 @param stderr_logging: whether we should also log to the standard error
2117 @param program: the name under which we should log messages
2118 @type multithreaded: boolean
2119 @param multithreaded: if True, will add the thread name to the log file
2120 @type syslog: string
2121 @param syslog: one of 'no', 'yes', 'only':
2122 - if no, syslog is not used
2123 - if yes, syslog is used (in addition to file-logging)
2124 - if only, only syslog is used
2125 @type console_logging: boolean
2126 @param console_logging: if True, will use a FileHandler which falls back to
2127 the system console if logging fails
2128 @raise EnvironmentError: if we can't open the log file and
2129 syslog/stderr logging is disabled
2132 fmt = "%(asctime)s: " + program + " pid=%(process)d"
2133 sft = program + "[%(process)d]:"
2135 fmt += "/%(threadName)s"
2136 sft += " (%(threadName)s)"
2138 fmt += " %(module)s:%(lineno)s"
2139 # no debug info for syslog loggers
2140 fmt += " %(levelname)s %(message)s"
2141 # yes, we do want the textual level, as remote syslog will probably
2142 # lose the error level, and it's easier to grep for it
2143 sft += " %(levelname)s %(message)s"
2144 formatter = logging.Formatter(fmt)
2145 sys_fmt = logging.Formatter(sft)
2147 root_logger = logging.getLogger("")
2148 root_logger.setLevel(logging.NOTSET)
2150 # Remove all previously setup handlers
2151 for handler in root_logger.handlers:
2153 root_logger.removeHandler(handler)
2156 stderr_handler = logging.StreamHandler()
2157 stderr_handler.setFormatter(formatter)
2159 stderr_handler.setLevel(logging.NOTSET)
2161 stderr_handler.setLevel(logging.CRITICAL)
2162 root_logger.addHandler(stderr_handler)
2164 if syslog in (constants.SYSLOG_YES, constants.SYSLOG_ONLY):
2165 facility = logging.handlers.SysLogHandler.LOG_DAEMON
2166 syslog_handler = logging.handlers.SysLogHandler(constants.SYSLOG_SOCKET,
2168 syslog_handler.setFormatter(sys_fmt)
2169 # Never enable debug over syslog
2170 syslog_handler.setLevel(logging.INFO)
2171 root_logger.addHandler(syslog_handler)
2173 if syslog != constants.SYSLOG_ONLY:
2174 # this can fail, if the logging directories are not setup or we have
2175 # a permisssion problem; in this case, it's best to log but ignore
2176 # the error if stderr_logging is True, and if false we re-raise the
2177 # exception since otherwise we could run but without any logs at all
2180 logfile_handler = LogFileHandler(logfile)
2182 logfile_handler = logging.FileHandler(logfile)
2183 logfile_handler.setFormatter(formatter)
2185 logfile_handler.setLevel(logging.DEBUG)
2187 logfile_handler.setLevel(logging.INFO)
2188 root_logger.addHandler(logfile_handler)
2189 except EnvironmentError:
2190 if stderr_logging or syslog == constants.SYSLOG_YES:
2191 logging.exception("Failed to enable logging to file '%s'", logfile)
2193 # we need to re-raise the exception
2197 def IsNormAbsPath(path):
2198 """Check whether a path is absolute and also normalized
2200 This avoids things like /dir/../../other/path to be valid.
2203 return os.path.normpath(path) == path and os.path.isabs(path)
2206 def PathJoin(*args):
2207 """Safe-join a list of path components.
2210 - the first argument must be an absolute path
2211 - no component in the path must have backtracking (e.g. /../),
2212 since we check for normalization at the end
2214 @param args: the path components to be joined
2215 @raise ValueError: for invalid paths
2218 # ensure we're having at least one path passed in
2220 # ensure the first component is an absolute and normalized path name
2222 if not IsNormAbsPath(root):
2223 raise ValueError("Invalid parameter to PathJoin: '%s'" % str(args[0]))
2224 result = os.path.join(*args)
2225 # ensure that the whole path is normalized
2226 if not IsNormAbsPath(result):
2227 raise ValueError("Invalid parameters to PathJoin: '%s'" % str(args))
2228 # check that we're still under the original prefix
2229 prefix = os.path.commonprefix([root, result])
2231 raise ValueError("Error: path joining resulted in different prefix"
2232 " (%s != %s)" % (prefix, root))
2236 def TailFile(fname, lines=20):
2237 """Return the last lines from a file.
2239 @note: this function will only read and parse the last 4KB of
2240 the file; if the lines are very long, it could be that less
2241 than the requested number of lines are returned
2243 @param fname: the file name
2245 @param lines: the (maximum) number of lines to return
2248 fd = open(fname, "r")
2252 pos = max(0, pos-4096)
2254 raw_data = fd.read()
2258 rows = raw_data.splitlines()
2259 return rows[-lines:]
2262 def _ParseAsn1Generalizedtime(value):
2263 """Parses an ASN1 GENERALIZEDTIME timestamp as used by pyOpenSSL.
2266 @param value: ASN1 GENERALIZEDTIME timestamp
2269 m = re.match(r"^(\d+)([-+]\d\d)(\d\d)$", value)
2272 asn1time = m.group(1)
2273 hours = int(m.group(2))
2274 minutes = int(m.group(3))
2275 utcoffset = (60 * hours) + minutes
2277 if not value.endswith("Z"):
2278 raise ValueError("Missing timezone")
2279 asn1time = value[:-1]
2282 parsed = time.strptime(asn1time, "%Y%m%d%H%M%S")
2284 tt = datetime.datetime(*(parsed[:7])) - datetime.timedelta(minutes=utcoffset)
2286 return calendar.timegm(tt.utctimetuple())
2289 def GetX509CertValidity(cert):
2290 """Returns the validity period of the certificate.
2292 @type cert: OpenSSL.crypto.X509
2293 @param cert: X509 certificate object
2296 # The get_notBefore and get_notAfter functions are only supported in
2297 # pyOpenSSL 0.7 and above.
2299 get_notbefore_fn = cert.get_notBefore
2300 except AttributeError:
2303 not_before_asn1 = get_notbefore_fn()
2305 if not_before_asn1 is None:
2308 not_before = _ParseAsn1Generalizedtime(not_before_asn1)
2311 get_notafter_fn = cert.get_notAfter
2312 except AttributeError:
2315 not_after_asn1 = get_notafter_fn()
2317 if not_after_asn1 is None:
2320 not_after = _ParseAsn1Generalizedtime(not_after_asn1)
2322 return (not_before, not_after)
2325 def SafeEncode(text):
2326 """Return a 'safe' version of a source string.
2328 This function mangles the input string and returns a version that
2329 should be safe to display/encode as ASCII. To this end, we first
2330 convert it to ASCII using the 'backslashreplace' encoding which
2331 should get rid of any non-ASCII chars, and then we process it
2332 through a loop copied from the string repr sources in the python; we
2333 don't use string_escape anymore since that escape single quotes and
2334 backslashes too, and that is too much; and that escaping is not
2335 stable, i.e. string_escape(string_escape(x)) != string_escape(x).
2337 @type text: str or unicode
2338 @param text: input data
2340 @return: a safe version of text
2343 if isinstance(text, unicode):
2344 # only if unicode; if str already, we handle it below
2345 text = text.encode('ascii', 'backslashreplace')
2355 elif c < 32 or c >= 127: # non-printable
2356 resu += "\\x%02x" % (c & 0xff)
2362 def UnescapeAndSplit(text, sep=","):
2363 """Split and unescape a string based on a given separator.
2365 This function splits a string based on a separator where the
2366 separator itself can be escape in order to be an element of the
2367 elements. The escaping rules are (assuming coma being the
2369 - a plain , separates the elements
2370 - a sequence \\\\, (double backslash plus comma) is handled as a
2371 backslash plus a separator comma
2372 - a sequence \, (backslash plus comma) is handled as a
2376 @param text: the string to split
2378 @param text: the separator
2380 @return: a list of strings
2383 # we split the list by sep (with no escaping at this stage)
2384 slist = text.split(sep)
2385 # next, we revisit the elements and if any of them ended with an odd
2386 # number of backslashes, then we join it with the next
2390 if e1.endswith("\\"):
2391 num_b = len(e1) - len(e1.rstrip("\\"))
2394 # here the backslashes remain (all), and will be reduced in
2396 rlist.append(e1 + sep + e2)
2399 # finally, replace backslash-something with something
2400 rlist = [re.sub(r"\\(.)", r"\1", v) for v in rlist]
2404 def CommaJoin(names):
2405 """Nicely join a set of identifiers.
2407 @param names: set, list or tuple
2408 @return: a string with the formatted results
2411 return ", ".join([str(val) for val in names])
2414 def BytesToMebibyte(value):
2415 """Converts bytes to mebibytes.
2418 @param value: Value in bytes
2420 @return: Value in mebibytes
2423 return int(round(value / (1024.0 * 1024.0), 0))
2426 def CalculateDirectorySize(path):
2427 """Calculates the size of a directory recursively.
2430 @param path: Path to directory
2432 @return: Size in mebibytes
2437 for (curpath, _, files) in os.walk(path):
2438 for filename in files:
2439 st = os.lstat(PathJoin(curpath, filename))
2442 return BytesToMebibyte(size)
2445 def GetFilesystemStats(path):
2446 """Returns the total and free space on a filesystem.
2449 @param path: Path on filesystem to be examined
2451 @return: tuple of (Total space, Free space) in mebibytes
2454 st = os.statvfs(path)
2456 fsize = BytesToMebibyte(st.f_bavail * st.f_frsize)
2457 tsize = BytesToMebibyte(st.f_blocks * st.f_frsize)
2458 return (tsize, fsize)
2461 def RunInSeparateProcess(fn, *args):
2462 """Runs a function in a separate process.
2464 Note: Only boolean return values are supported.
2467 @param fn: Function to be called
2469 @return: Function's result
2476 # In case the function uses temporary files
2477 ResetTempfileModule()
2480 result = int(bool(fn(*args)))
2481 assert result in (0, 1)
2482 except: # pylint: disable-msg=W0702
2483 logging.exception("Error while calling function in separate process")
2484 # 0 and 1 are reserved for the return value
2487 os._exit(result) # pylint: disable-msg=W0212
2491 # Avoid zombies and check exit code
2492 (_, status) = os.waitpid(pid, 0)
2494 if os.WIFSIGNALED(status):
2496 signum = os.WTERMSIG(status)
2498 exitcode = os.WEXITSTATUS(status)
2501 if not (exitcode in (0, 1) and signum is None):
2502 raise errors.GenericError("Child program failed (code=%s, signal=%s)" %
2505 return bool(exitcode)
2508 def LockedMethod(fn):
2509 """Synchronized object access decorator.
2511 This decorator is intended to protect access to an object using the
2512 object's own lock which is hardcoded to '_lock'.
2515 def _LockDebug(*args, **kwargs):
2517 logging.debug(*args, **kwargs)
2519 def wrapper(self, *args, **kwargs):
2520 # pylint: disable-msg=W0212
2521 assert hasattr(self, '_lock')
2523 _LockDebug("Waiting for %s", lock)
2526 _LockDebug("Acquired %s", lock)
2527 result = fn(self, *args, **kwargs)
2529 _LockDebug("Releasing %s", lock)
2531 _LockDebug("Released %s", lock)
2537 """Locks a file using POSIX locks.
2540 @param fd: the file descriptor we need to lock
2544 fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
2545 except IOError, err:
2546 if err.errno == errno.EAGAIN:
2547 raise errors.LockError("File already locked")
2551 def FormatTime(val):
2552 """Formats a time value.
2554 @type val: float or None
2555 @param val: the timestamp as returned by time.time()
2556 @return: a string value or N/A if we don't have a valid timestamp
2559 if val is None or not isinstance(val, (int, float)):
2561 # these two codes works on Linux, but they are not guaranteed on all
2563 return time.strftime("%F %T", time.localtime(val))
2566 def ReadWatcherPauseFile(filename, now=None, remove_after=3600):
2567 """Reads the watcher pause file.
2569 @type filename: string
2570 @param filename: Path to watcher pause file
2571 @type now: None, float or int
2572 @param now: Current time as Unix timestamp
2573 @type remove_after: int
2574 @param remove_after: Remove watcher pause file after specified amount of
2575 seconds past the pause end time
2582 value = ReadFile(filename)
2583 except IOError, err:
2584 if err.errno != errno.ENOENT:
2588 if value is not None:
2592 logging.warning(("Watcher pause file (%s) contains invalid value,"
2593 " removing it"), filename)
2594 RemoveFile(filename)
2597 if value is not None:
2598 # Remove file if it's outdated
2599 if now > (value + remove_after):
2600 RemoveFile(filename)
2609 class RetryTimeout(Exception):
2610 """Retry loop timed out.
2612 Any arguments which was passed by the retried function to RetryAgain will be
2613 preserved in RetryTimeout, if it is raised. If such argument was an exception
2614 the RaiseInner helper method will reraise it.
2617 def RaiseInner(self):
2618 if self.args and isinstance(self.args[0], Exception):
2621 raise RetryTimeout(*self.args)
2624 class RetryAgain(Exception):
2627 Any arguments passed to RetryAgain will be preserved, if a timeout occurs, as
2628 arguments to RetryTimeout. If an exception is passed, the RaiseInner() method
2629 of the RetryTimeout() method can be used to reraise it.
2634 class _RetryDelayCalculator(object):
2635 """Calculator for increasing delays.
2645 def __init__(self, start, factor, limit):
2646 """Initializes this class.
2649 @param start: Initial delay
2651 @param factor: Factor for delay increase
2652 @type limit: float or None
2653 @param limit: Upper limit for delay or None for no limit
2657 assert factor >= 1.0
2658 assert limit is None or limit >= 0.0
2661 self._factor = factor
2667 """Returns current delay and calculates the next one.
2670 current = self._next
2672 # Update for next run
2673 if self._limit is None or self._next < self._limit:
2674 self._next = min(self._limit, self._next * self._factor)
2679 #: Special delay to specify whole remaining timeout
2680 RETRY_REMAINING_TIME = object()
2683 def Retry(fn, delay, timeout, args=None, wait_fn=time.sleep,
2684 _time_fn=time.time):
2685 """Call a function repeatedly until it succeeds.
2687 The function C{fn} is called repeatedly until it doesn't throw L{RetryAgain}
2688 anymore. Between calls a delay, specified by C{delay}, is inserted. After a
2689 total of C{timeout} seconds, this function throws L{RetryTimeout}.
2691 C{delay} can be one of the following:
2692 - callable returning the delay length as a float
2693 - Tuple of (start, factor, limit)
2694 - L{RETRY_REMAINING_TIME} to sleep until the timeout expires (this is
2695 useful when overriding L{wait_fn} to wait for an external event)
2696 - A static delay as a number (int or float)
2699 @param fn: Function to be called
2700 @param delay: Either a callable (returning the delay), a tuple of (start,
2701 factor, limit) (see L{_RetryDelayCalculator}),
2702 L{RETRY_REMAINING_TIME} or a number (int or float)
2703 @type timeout: float
2704 @param timeout: Total timeout
2705 @type wait_fn: callable
2706 @param wait_fn: Waiting function
2707 @return: Return value of function
2711 assert callable(wait_fn)
2712 assert callable(_time_fn)
2717 end_time = _time_fn() + timeout
2720 # External function to calculate delay
2723 elif isinstance(delay, (tuple, list)):
2724 # Increasing delay with optional upper boundary
2725 (start, factor, limit) = delay
2726 calc_delay = _RetryDelayCalculator(start, factor, limit)
2728 elif delay is RETRY_REMAINING_TIME:
2729 # Always use the remaining time
2734 calc_delay = lambda: delay
2736 assert calc_delay is None or callable(calc_delay)
2741 # pylint: disable-msg=W0142
2743 except RetryAgain, err:
2744 retry_args = err.args
2745 except RetryTimeout:
2746 raise errors.ProgrammerError("Nested retry loop detected that didn't"
2747 " handle RetryTimeout")
2749 remaining_time = end_time - _time_fn()
2751 if remaining_time < 0.0:
2752 # pylint: disable-msg=W0142
2753 raise RetryTimeout(*retry_args)
2755 assert remaining_time >= 0.0
2757 if calc_delay is None:
2758 wait_fn(remaining_time)
2760 current_delay = calc_delay()
2761 if current_delay > 0.0:
2762 wait_fn(current_delay)
2765 class FileLock(object):
2766 """Utility class for file locks.
2769 def __init__(self, fd, filename):
2770 """Constructor for FileLock.
2773 @param fd: File object
2775 @param filename: Path of the file opened at I{fd}
2779 self.filename = filename
2782 def Open(cls, filename):
2783 """Creates and opens a file to be used as a file-based lock.
2785 @type filename: string
2786 @param filename: path to the file to be locked
2789 # Using "os.open" is necessary to allow both opening existing file
2790 # read/write and creating if not existing. Vanilla "open" will truncate an
2791 # existing file -or- allow creating if not existing.
2792 return cls(os.fdopen(os.open(filename, os.O_RDWR | os.O_CREAT), "w+"),
2799 """Close the file and release the lock.
2802 if hasattr(self, "fd") and self.fd:
2806 def _flock(self, flag, blocking, timeout, errmsg):
2807 """Wrapper for fcntl.flock.
2810 @param flag: operation flag
2811 @type blocking: bool
2812 @param blocking: whether the operation should be done in blocking mode.
2813 @type timeout: None or float
2814 @param timeout: for how long the operation should be retried (implies
2816 @type errmsg: string
2817 @param errmsg: error message in case operation fails.
2820 assert self.fd, "Lock was closed"
2821 assert timeout is None or timeout >= 0, \
2822 "If specified, timeout must be positive"
2823 assert not (flag & fcntl.LOCK_NB), "LOCK_NB must not be set"
2825 # When a timeout is used, LOCK_NB must always be set
2826 if not (timeout is None and blocking):
2827 flag |= fcntl.LOCK_NB
2830 self._Lock(self.fd, flag, timeout)
2833 Retry(self._Lock, (0.1, 1.2, 1.0), timeout,
2834 args=(self.fd, flag, timeout))
2835 except RetryTimeout:
2836 raise errors.LockError(errmsg)
2839 def _Lock(fd, flag, timeout):
2841 fcntl.flock(fd, flag)
2842 except IOError, err:
2843 if timeout is not None and err.errno == errno.EAGAIN:
2846 logging.exception("fcntl.flock failed")
2849 def Exclusive(self, blocking=False, timeout=None):
2850 """Locks the file in exclusive mode.
2852 @type blocking: boolean
2853 @param blocking: whether to block and wait until we
2854 can lock the file or return immediately
2855 @type timeout: int or None
2856 @param timeout: if not None, the duration to wait for the lock
2860 self._flock(fcntl.LOCK_EX, blocking, timeout,
2861 "Failed to lock %s in exclusive mode" % self.filename)
2863 def Shared(self, blocking=False, timeout=None):
2864 """Locks the file in shared mode.
2866 @type blocking: boolean
2867 @param blocking: whether to block and wait until we
2868 can lock the file or return immediately
2869 @type timeout: int or None
2870 @param timeout: if not None, the duration to wait for the lock
2874 self._flock(fcntl.LOCK_SH, blocking, timeout,
2875 "Failed to lock %s in shared mode" % self.filename)
2877 def Unlock(self, blocking=True, timeout=None):
2878 """Unlocks the file.
2880 According to C{flock(2)}, unlocking can also be a nonblocking
2883 To make a non-blocking request, include LOCK_NB with any of the above
2886 @type blocking: boolean
2887 @param blocking: whether to block and wait until we
2888 can lock the file or return immediately
2889 @type timeout: int or None
2890 @param timeout: if not None, the duration to wait for the lock
2894 self._flock(fcntl.LOCK_UN, blocking, timeout,
2895 "Failed to unlock %s" % self.filename)
2899 """Splits data chunks into lines separated by newline.
2901 Instances provide a file-like interface.
2904 def __init__(self, line_fn, *args):
2905 """Initializes this class.
2907 @type line_fn: callable
2908 @param line_fn: Function called for each line, first parameter is line
2909 @param args: Extra arguments for L{line_fn}
2912 assert callable(line_fn)
2915 # Python 2.4 doesn't have functools.partial yet
2917 lambda line: line_fn(line, *args) # pylint: disable-msg=W0142
2919 self._line_fn = line_fn
2921 self._lines = collections.deque()
2924 def write(self, data):
2925 parts = (self._buffer + data).split("\n")
2926 self._buffer = parts.pop()
2927 self._lines.extend(parts)
2931 self._line_fn(self._lines.popleft().rstrip("\r\n"))
2936 self._line_fn(self._buffer)
2939 def SignalHandled(signums):
2940 """Signal Handled decoration.
2942 This special decorator installs a signal handler and then calls the target
2943 function. The function must accept a 'signal_handlers' keyword argument,
2944 which will contain a dict indexed by signal number, with SignalHandler
2947 The decorator can be safely stacked with iself, to handle multiple signals
2948 with different handlers.
2951 @param signums: signals to intercept
2955 def sig_function(*args, **kwargs):
2956 assert 'signal_handlers' not in kwargs or \
2957 kwargs['signal_handlers'] is None or \
2958 isinstance(kwargs['signal_handlers'], dict), \
2959 "Wrong signal_handlers parameter in original function call"
2960 if 'signal_handlers' in kwargs and kwargs['signal_handlers'] is not None:
2961 signal_handlers = kwargs['signal_handlers']
2963 signal_handlers = {}
2964 kwargs['signal_handlers'] = signal_handlers
2965 sighandler = SignalHandler(signums)
2968 signal_handlers[sig] = sighandler
2969 return fn(*args, **kwargs)
2976 class SignalHandler(object):
2977 """Generic signal handler class.
2979 It automatically restores the original handler when deconstructed or
2980 when L{Reset} is called. You can either pass your own handler
2981 function in or query the L{called} attribute to detect whether the
2985 @ivar signum: the signals we handle
2986 @type called: boolean
2987 @ivar called: tracks whether any of the signals have been raised
2990 def __init__(self, signum):
2991 """Constructs a new SignalHandler instance.
2993 @type signum: int or list of ints
2994 @param signum: Single signal number or set of signal numbers
2997 self.signum = set(signum)
3002 for signum in self.signum:
3004 prev_handler = signal.signal(signum, self._HandleSignal)
3006 self._previous[signum] = prev_handler
3008 # Restore previous handler
3009 signal.signal(signum, prev_handler)
3012 # Reset all handlers
3014 # Here we have a race condition: a handler may have already been called,
3015 # but there's not much we can do about it at this point.
3022 """Restore previous handler.
3024 This will reset all the signals to their previous handlers.
3027 for signum, prev_handler in self._previous.items():
3028 signal.signal(signum, prev_handler)
3029 # If successful, remove from dict
3030 del self._previous[signum]
3033 """Unsets the L{called} flag.
3035 This function can be used in case a signal may arrive several times.
3040 # we don't care about arguments, but we leave them named for the future
3041 def _HandleSignal(self, signum, frame): # pylint: disable-msg=W0613
3042 """Actual signal handling function.
3045 # This is not nice and not absolutely atomic, but it appears to be the only
3046 # solution in Python -- there are no atomic types.
3050 class FieldSet(object):
3051 """A simple field set.
3053 Among the features are:
3054 - checking if a string is among a list of static string or regex objects
3055 - checking if a whole list of string matches
3056 - returning the matching groups from a regex match
3058 Internally, all fields are held as regular expression objects.
3061 def __init__(self, *items):
3062 self.items = [re.compile("^%s$" % value) for value in items]
3064 def Extend(self, other_set):
3065 """Extend the field set with the items from another one"""
3066 self.items.extend(other_set.items)
3068 def Matches(self, field):
3069 """Checks if a field matches the current set
3072 @param field: the string to match
3073 @return: either None or a regular expression match object
3076 for m in itertools.ifilter(None, (val.match(field) for val in self.items)):
3080 def NonMatching(self, items):
3081 """Returns the list of fields not matching the current set
3084 @param items: the list of fields to check
3086 @return: list of non-matching fields
3089 return [val for val in items if not self.Matches(val)]