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.
46 from cStringIO import StringIO
49 from hashlib import sha1
54 from ganeti import errors
55 from ganeti import constants
59 _re_shell_unquoted = re.compile('^[-.,=:/_+@A-Za-z0-9]+$')
63 #: when set to True, L{RunCmd} is disabled
66 _RANDOM_UUID_FILE = "/proc/sys/kernel/random/uuid"
69 class RunResult(object):
70 """Holds the result of running external programs.
73 @ivar exit_code: the exit code of the program, or None (if the program
75 @type signal: int or None
76 @ivar signal: the signal that caused the program to finish, or None
77 (if the program wasn't terminated by a signal)
79 @ivar stdout: the standard output of the program
81 @ivar stderr: the standard error of the program
83 @ivar failed: True in case the program was
84 terminated by a signal or exited with a non-zero exit code
85 @ivar fail_reason: a string detailing the termination reason
88 __slots__ = ["exit_code", "signal", "stdout", "stderr",
89 "failed", "fail_reason", "cmd"]
92 def __init__(self, exit_code, signal_, stdout, stderr, cmd):
94 self.exit_code = exit_code
98 self.failed = (signal_ is not None or exit_code != 0)
100 if self.signal is not None:
101 self.fail_reason = "terminated by signal %s" % self.signal
102 elif self.exit_code is not None:
103 self.fail_reason = "exited with exit code %s" % self.exit_code
105 self.fail_reason = "unable to determine termination reason"
108 logging.debug("Command '%s' failed (%s); output: %s",
109 self.cmd, self.fail_reason, self.output)
111 def _GetOutput(self):
112 """Returns the combined stdout and stderr for easier usage.
115 return self.stdout + self.stderr
117 output = property(_GetOutput, None, None, "Return full output")
120 def RunCmd(cmd, env=None, output=None, cwd='/'):
121 """Execute a (shell) command.
123 The command should not read from its standard input, as it will be
126 @type cmd: string or list
127 @param cmd: Command to run
129 @param env: Additional environment
131 @param output: if desired, the output of the command can be
132 saved in a file instead of the RunResult instance; this
133 parameter denotes the file name (if not None)
135 @param cwd: if specified, will be used as the working
136 directory for the command; the default will be /
138 @return: RunResult instance
139 @raise errors.ProgrammerError: if we call this when forks are disabled
143 raise errors.ProgrammerError("utils.RunCmd() called with fork() disabled")
145 if isinstance(cmd, list):
146 cmd = [str(val) for val in cmd]
147 strcmd = " ".join(cmd)
152 logging.debug("RunCmd '%s'", strcmd)
154 cmd_env = os.environ.copy()
155 cmd_env["LC_ALL"] = "C"
161 out, err, status = _RunCmdPipe(cmd, cmd_env, shell, cwd)
163 status = _RunCmdFile(cmd, cmd_env, shell, output, cwd)
166 if err.errno == errno.ENOENT:
167 raise errors.OpExecError("Can't execute '%s': not found (%s)" %
179 return RunResult(exitcode, signal_, out, err, strcmd)
182 def _RunCmdPipe(cmd, env, via_shell, cwd):
183 """Run a command and return its output.
185 @type cmd: string or list
186 @param cmd: Command to run
188 @param env: The environment to use
189 @type via_shell: bool
190 @param via_shell: if we should run via the shell
192 @param cwd: the working directory for the program
194 @return: (out, err, status)
197 poller = select.poll()
198 child = subprocess.Popen(cmd, shell=via_shell,
199 stderr=subprocess.PIPE,
200 stdout=subprocess.PIPE,
201 stdin=subprocess.PIPE,
202 close_fds=True, env=env,
206 poller.register(child.stdout, select.POLLIN)
207 poller.register(child.stderr, select.POLLIN)
211 child.stdout.fileno(): (out, child.stdout),
212 child.stderr.fileno(): (err, child.stderr),
215 status = fcntl.fcntl(fd, fcntl.F_GETFL)
216 fcntl.fcntl(fd, fcntl.F_SETFL, status | os.O_NONBLOCK)
220 pollresult = poller.poll()
221 except EnvironmentError, eerr:
222 if eerr.errno == errno.EINTR:
225 except select.error, serr:
226 if serr[0] == errno.EINTR:
230 for fd, event in pollresult:
231 if event & select.POLLIN or event & select.POLLPRI:
232 data = fdmap[fd][1].read()
233 # no data from read signifies EOF (the same as POLLHUP)
235 poller.unregister(fd)
238 fdmap[fd][0].write(data)
239 if (event & select.POLLNVAL or event & select.POLLHUP or
240 event & select.POLLERR):
241 poller.unregister(fd)
247 status = child.wait()
248 return out, err, status
251 def _RunCmdFile(cmd, env, via_shell, output, cwd):
252 """Run a command and save its output to a file.
254 @type cmd: string or list
255 @param cmd: Command to run
257 @param env: The environment to use
258 @type via_shell: bool
259 @param via_shell: if we should run via the shell
261 @param output: the filename in which to save the output
263 @param cwd: the working directory for the program
265 @return: the exit status
268 fh = open(output, "a")
270 child = subprocess.Popen(cmd, shell=via_shell,
271 stderr=subprocess.STDOUT,
273 stdin=subprocess.PIPE,
274 close_fds=True, env=env,
278 status = child.wait()
284 def RemoveFile(filename):
285 """Remove a file ignoring some errors.
287 Remove a file, ignoring non-existing ones or directories. Other
291 @param filename: the file to be removed
297 if err.errno not in (errno.ENOENT, errno.EISDIR):
301 def RenameFile(old, new, mkdir=False, mkdir_mode=0750):
305 @param old: Original path
309 @param mkdir: Whether to create target directory if it doesn't exist
310 @type mkdir_mode: int
311 @param mkdir_mode: Mode for newly created directories
315 return os.rename(old, new)
317 # In at least one use case of this function, the job queue, directory
318 # creation is very rare. Checking for the directory before renaming is not
320 if mkdir and err.errno == errno.ENOENT:
321 # Create directory and try again
322 os.makedirs(os.path.dirname(new), mkdir_mode)
323 return os.rename(old, new)
327 def _FingerprintFile(filename):
328 """Compute the fingerprint of a file.
330 If the file does not exist, a None will be returned
334 @param filename: the filename to checksum
336 @return: the hex digest of the sha checksum of the contents
340 if not (os.path.exists(filename) and os.path.isfile(filename)):
353 return fp.hexdigest()
356 def FingerprintFiles(files):
357 """Compute fingerprints for a list of files.
360 @param files: the list of filename to fingerprint
362 @return: a dictionary filename: fingerprint, holding only
368 for filename in files:
369 cksum = _FingerprintFile(filename)
371 ret[filename] = cksum
376 def ForceDictType(target, key_types, allowed_values=None):
377 """Force the values of a dict to have certain types.
380 @param target: the dict to update
381 @type key_types: dict
382 @param key_types: dict mapping target dict keys to types
383 in constants.ENFORCEABLE_TYPES
384 @type allowed_values: list
385 @keyword allowed_values: list of specially allowed values
388 if allowed_values is None:
391 if not isinstance(target, dict):
392 msg = "Expected dictionary, got '%s'" % target
393 raise errors.TypeEnforcementError(msg)
396 if key not in key_types:
397 msg = "Unknown key '%s'" % key
398 raise errors.TypeEnforcementError(msg)
400 if target[key] in allowed_values:
403 ktype = key_types[key]
404 if ktype not in constants.ENFORCEABLE_TYPES:
405 msg = "'%s' has non-enforceable type %s" % (key, ktype)
406 raise errors.ProgrammerError(msg)
408 if ktype == constants.VTYPE_STRING:
409 if not isinstance(target[key], basestring):
410 if isinstance(target[key], bool) and not target[key]:
413 msg = "'%s' (value %s) is not a valid string" % (key, target[key])
414 raise errors.TypeEnforcementError(msg)
415 elif ktype == constants.VTYPE_BOOL:
416 if isinstance(target[key], basestring) and target[key]:
417 if target[key].lower() == constants.VALUE_FALSE:
419 elif target[key].lower() == constants.VALUE_TRUE:
422 msg = "'%s' (value %s) is not a valid boolean" % (key, target[key])
423 raise errors.TypeEnforcementError(msg)
428 elif ktype == constants.VTYPE_SIZE:
430 target[key] = ParseUnit(target[key])
431 except errors.UnitParseError, err:
432 msg = "'%s' (value %s) is not a valid size. error: %s" % \
433 (key, target[key], err)
434 raise errors.TypeEnforcementError(msg)
435 elif ktype == constants.VTYPE_INT:
437 target[key] = int(target[key])
438 except (ValueError, TypeError):
439 msg = "'%s' (value %s) is not a valid integer" % (key, target[key])
440 raise errors.TypeEnforcementError(msg)
443 def IsProcessAlive(pid):
444 """Check if a given pid exists on the system.
446 @note: zombie status is not handled, so zombie processes
447 will be returned as alive
449 @param pid: the process ID to check
451 @return: True if the process exists
458 os.stat("/proc/%d/status" % pid)
460 except EnvironmentError, err:
461 if err.errno in (errno.ENOENT, errno.ENOTDIR):
466 def ReadPidFile(pidfile):
467 """Read a pid from a file.
469 @type pidfile: string
470 @param pidfile: path to the file containing the pid
472 @return: The process id, if the file exists and contains a valid PID,
477 raw_data = ReadFile(pidfile)
478 except EnvironmentError, err:
479 if err.errno != errno.ENOENT:
480 logging.exception("Can't read pid file")
485 except ValueError, err:
486 logging.info("Can't parse pid file contents", exc_info=True)
492 def MatchNameComponent(key, name_list):
493 """Try to match a name against a list.
495 This function will try to match a name like test1 against a list
496 like C{['test1.example.com', 'test2.example.com', ...]}. Against
497 this list, I{'test1'} as well as I{'test1.example'} will match, but
498 not I{'test1.ex'}. A multiple match will be considered as no match
499 at all (e.g. I{'test1'} against C{['test1.example.com',
500 'test1.example.org']}).
503 @param key: the name to be searched
504 @type name_list: list
505 @param name_list: the list of strings against which to search the key
508 @return: None if there is no match I{or} if there are multiple matches,
509 otherwise the element from the list which matches
512 mo = re.compile("^%s(\..*)?$" % re.escape(key))
513 names_filtered = [name for name in name_list if mo.match(name) is not None]
514 if len(names_filtered) != 1:
516 return names_filtered[0]
520 """Class implementing resolver and hostname functionality
523 def __init__(self, name=None):
524 """Initialize the host name object.
526 If the name argument is not passed, it will use this system's
531 name = self.SysName()
534 self.name, self.aliases, self.ipaddrs = self.LookupHostname(name)
535 self.ip = self.ipaddrs[0]
538 """Returns the hostname without domain.
541 return self.name.split('.')[0]
545 """Return the current system's name.
547 This is simply a wrapper over C{socket.gethostname()}.
550 return socket.gethostname()
553 def LookupHostname(hostname):
557 @param hostname: hostname to look up
560 @return: a tuple (name, aliases, ipaddrs) as returned by
561 C{socket.gethostbyname_ex}
562 @raise errors.ResolverError: in case of errors in resolving
566 result = socket.gethostbyname_ex(hostname)
567 except socket.gaierror, err:
568 # hostname not found in DNS
569 raise errors.ResolverError(hostname, err.args[0], err.args[1])
574 def ListVolumeGroups():
575 """List volume groups and their size
579 Dictionary with keys volume name and values
580 the size of the volume
583 command = "vgs --noheadings --units m --nosuffix -o name,size"
584 result = RunCmd(command)
589 for line in result.stdout.splitlines():
591 name, size = line.split()
592 size = int(float(size))
593 except (IndexError, ValueError), err:
594 logging.error("Invalid output from vgs (%s): %s", err, line)
602 def BridgeExists(bridge):
603 """Check whether the given bridge exists in the system
606 @param bridge: the bridge name to check
608 @return: True if it does
611 return os.path.isdir("/sys/class/net/%s/bridge" % bridge)
614 def NiceSort(name_list):
615 """Sort a list of strings based on digit and non-digit groupings.
617 Given a list of names C{['a1', 'a10', 'a11', 'a2']} this function
618 will sort the list in the logical order C{['a1', 'a2', 'a10',
621 The sort algorithm breaks each name in groups of either only-digits
622 or no-digits. Only the first eight such groups are considered, and
623 after that we just use what's left of the string.
625 @type name_list: list
626 @param name_list: the names to be sorted
628 @return: a copy of the name list sorted with our algorithm
631 _SORTER_BASE = "(\D+|\d+)"
632 _SORTER_FULL = "^%s%s?%s?%s?%s?%s?%s?%s?.*$" % (_SORTER_BASE, _SORTER_BASE,
633 _SORTER_BASE, _SORTER_BASE,
634 _SORTER_BASE, _SORTER_BASE,
635 _SORTER_BASE, _SORTER_BASE)
636 _SORTER_RE = re.compile(_SORTER_FULL)
637 _SORTER_NODIGIT = re.compile("^\D*$")
639 """Attempts to convert a variable to integer."""
640 if val is None or _SORTER_NODIGIT.match(val):
645 to_sort = [([_TryInt(grp) for grp in _SORTER_RE.match(name).groups()], name)
646 for name in name_list]
648 return [tup[1] for tup in to_sort]
651 def TryConvert(fn, val):
652 """Try to convert a value ignoring errors.
654 This function tries to apply function I{fn} to I{val}. If no
655 C{ValueError} or C{TypeError} exceptions are raised, it will return
656 the result, else it will return the original value. Any other
657 exceptions are propagated to the caller.
660 @param fn: function to apply to the value
661 @param val: the value to be converted
662 @return: The converted value if the conversion was successful,
663 otherwise the original value.
668 except (ValueError, TypeError):
674 """Verifies the syntax of an IPv4 address.
676 This function checks if the IPv4 address passes is valid or not based
677 on syntax (not IP range, class calculations, etc.).
680 @param ip: the address to be checked
681 @rtype: a regular expression match object
682 @return: a regular expression match object, or None if the
686 unit = "(0|[1-9]\d{0,2})"
687 #TODO: convert and return only boolean
688 return re.match("^%s\.%s\.%s\.%s$" % (unit, unit, unit, unit), ip)
691 def IsValidShellParam(word):
692 """Verifies is the given word is safe from the shell's p.o.v.
694 This means that we can pass this to a command via the shell and be
695 sure that it doesn't alter the command line and is passed as such to
698 Note that we are overly restrictive here, in order to be on the safe
702 @param word: the word to check
704 @return: True if the word is 'safe'
707 return bool(re.match("^[-a-zA-Z0-9._+/:%@]+$", word))
710 def BuildShellCmd(template, *args):
711 """Build a safe shell command line from the given arguments.
713 This function will check all arguments in the args list so that they
714 are valid shell parameters (i.e. they don't contain shell
715 metacharacters). If everything is ok, it will return the result of
719 @param template: the string holding the template for the
722 @return: the expanded command line
726 if not IsValidShellParam(word):
727 raise errors.ProgrammerError("Shell argument '%s' contains"
728 " invalid characters" % word)
729 return template % args
732 def FormatUnit(value, units):
733 """Formats an incoming number of MiB with the appropriate unit.
736 @param value: integer representing the value in MiB (1048576)
738 @param units: the type of formatting we should do:
739 - 'h' for automatic scaling
744 @return: the formatted value (with suffix)
747 if units not in ('m', 'g', 't', 'h'):
748 raise errors.ProgrammerError("Invalid unit specified '%s'" % str(units))
752 if units == 'm' or (units == 'h' and value < 1024):
755 return "%d%s" % (round(value, 0), suffix)
757 elif units == 'g' or (units == 'h' and value < (1024 * 1024)):
760 return "%0.1f%s" % (round(float(value) / 1024, 1), suffix)
765 return "%0.1f%s" % (round(float(value) / 1024 / 1024, 1), suffix)
768 def ParseUnit(input_string):
769 """Tries to extract number and scale from the given string.
771 Input must be in the format C{NUMBER+ [DOT NUMBER+] SPACE*
772 [UNIT]}. If no unit is specified, it defaults to MiB. Return value
773 is always an int in MiB.
776 m = re.match('^([.\d]+)\s*([a-zA-Z]+)?$', str(input_string))
778 raise errors.UnitParseError("Invalid format")
780 value = float(m.groups()[0])
784 lcunit = unit.lower()
788 if lcunit in ('m', 'mb', 'mib'):
789 # Value already in MiB
792 elif lcunit in ('g', 'gb', 'gib'):
795 elif lcunit in ('t', 'tb', 'tib'):
799 raise errors.UnitParseError("Unknown unit: %s" % unit)
801 # Make sure we round up
802 if int(value) < value:
805 # Round up to the next multiple of 4
808 value += 4 - value % 4
813 def AddAuthorizedKey(file_name, key):
814 """Adds an SSH public key to an authorized_keys file.
817 @param file_name: path to authorized_keys file
819 @param key: string containing key
822 key_fields = key.split()
824 f = open(file_name, 'a+')
828 # Ignore whitespace changes
829 if line.split() == key_fields:
831 nl = line.endswith('\n')
835 f.write(key.rstrip('\r\n'))
842 def RemoveAuthorizedKey(file_name, key):
843 """Removes an SSH public key from an authorized_keys file.
846 @param file_name: path to authorized_keys file
848 @param key: string containing key
851 key_fields = key.split()
853 fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
855 out = os.fdopen(fd, 'w')
857 f = open(file_name, 'r')
860 # Ignore whitespace changes while comparing lines
861 if line.split() != key_fields:
865 os.rename(tmpname, file_name)
875 def SetEtcHostsEntry(file_name, ip, hostname, aliases):
876 """Sets the name of an IP address and hostname in /etc/hosts.
879 @param file_name: path to the file to modify (usually C{/etc/hosts})
881 @param ip: the IP address
883 @param hostname: the hostname to be added
885 @param aliases: the list of aliases to add for the hostname
888 # FIXME: use WriteFile + fn rather than duplicating its efforts
889 # Ensure aliases are unique
890 aliases = UniqueSequence([hostname] + aliases)[1:]
892 fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
894 out = os.fdopen(fd, 'w')
896 f = open(file_name, 'r')
899 fields = line.split()
900 if fields and not fields[0].startswith('#') and ip == fields[0]:
904 out.write("%s\t%s" % (ip, hostname))
906 out.write(" %s" % ' '.join(aliases))
911 os.chmod(tmpname, 0644)
912 os.rename(tmpname, file_name)
922 def AddHostToEtcHosts(hostname):
923 """Wrapper around SetEtcHostsEntry.
926 @param hostname: a hostname that will be resolved and added to
927 L{constants.ETC_HOSTS}
930 hi = HostInfo(name=hostname)
931 SetEtcHostsEntry(constants.ETC_HOSTS, hi.ip, hi.name, [hi.ShortName()])
934 def RemoveEtcHostsEntry(file_name, hostname):
935 """Removes a hostname from /etc/hosts.
937 IP addresses without names are removed from the file.
940 @param file_name: path to the file to modify (usually C{/etc/hosts})
942 @param hostname: the hostname to be removed
945 # FIXME: use WriteFile + fn rather than duplicating its efforts
946 fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
948 out = os.fdopen(fd, 'w')
950 f = open(file_name, 'r')
953 fields = line.split()
954 if len(fields) > 1 and not fields[0].startswith('#'):
956 if hostname in names:
957 while hostname in names:
958 names.remove(hostname)
960 out.write("%s %s\n" % (fields[0], ' '.join(names)))
967 os.chmod(tmpname, 0644)
968 os.rename(tmpname, file_name)
978 def RemoveHostFromEtcHosts(hostname):
979 """Wrapper around RemoveEtcHostsEntry.
982 @param hostname: hostname that will be resolved and its
983 full and shot name will be removed from
984 L{constants.ETC_HOSTS}
987 hi = HostInfo(name=hostname)
988 RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.name)
989 RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.ShortName())
992 def CreateBackup(file_name):
993 """Creates a backup of a file.
996 @param file_name: file to be backed up
998 @return: the path to the newly created backup
999 @raise errors.ProgrammerError: for invalid file names
1002 if not os.path.isfile(file_name):
1003 raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
1006 prefix = '%s.backup-%d.' % (os.path.basename(file_name), int(time.time()))
1007 dir_name = os.path.dirname(file_name)
1009 fsrc = open(file_name, 'rb')
1011 (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
1012 fdst = os.fdopen(fd, 'wb')
1014 shutil.copyfileobj(fsrc, fdst)
1023 def ShellQuote(value):
1024 """Quotes shell argument according to POSIX.
1027 @param value: the argument to be quoted
1029 @return: the quoted value
1032 if _re_shell_unquoted.match(value):
1035 return "'%s'" % value.replace("'", "'\\''")
1038 def ShellQuoteArgs(args):
1039 """Quotes a list of shell arguments.
1042 @param args: list of arguments to be quoted
1044 @return: the quoted arguments concatenated with spaces
1047 return ' '.join([ShellQuote(i) for i in args])
1050 def TcpPing(target, port, timeout=10, live_port_needed=False, source=None):
1051 """Simple ping implementation using TCP connect(2).
1053 Check if the given IP is reachable by doing attempting a TCP connect
1057 @param target: the IP or hostname to ping
1059 @param port: the port to connect to
1061 @param timeout: the timeout on the connection attempt
1062 @type live_port_needed: boolean
1063 @param live_port_needed: whether a closed port will cause the
1064 function to return failure, as if there was a timeout
1065 @type source: str or None
1066 @param source: if specified, will cause the connect to be made
1067 from this specific source address; failures to bind other
1068 than C{EADDRNOTAVAIL} will be ignored
1071 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1075 if source is not None:
1077 sock.bind((source, 0))
1078 except socket.error, (errcode, _):
1079 if errcode == errno.EADDRNOTAVAIL:
1082 sock.settimeout(timeout)
1085 sock.connect((target, port))
1088 except socket.timeout:
1090 except socket.error, (errcode, errstring):
1091 success = (not live_port_needed) and (errcode == errno.ECONNREFUSED)
1096 def OwnIpAddress(address):
1097 """Check if the current host has the the given IP address.
1099 Currently this is done by TCP-pinging the address from the loopback
1102 @type address: string
1103 @param address: the address to check
1105 @return: True if we own the address
1108 return TcpPing(address, constants.DEFAULT_NODED_PORT,
1109 source=constants.LOCALHOST_IP_ADDRESS)
1112 def ListVisibleFiles(path):
1113 """Returns a list of visible files in a directory.
1116 @param path: the directory to enumerate
1118 @return: the list of all files not starting with a dot
1121 files = [i for i in os.listdir(path) if not i.startswith(".")]
1126 def GetHomeDir(user, default=None):
1127 """Try to get the homedir of the given user.
1129 The user can be passed either as a string (denoting the name) or as
1130 an integer (denoting the user id). If the user is not found, the
1131 'default' argument is returned, which defaults to None.
1135 if isinstance(user, basestring):
1136 result = pwd.getpwnam(user)
1137 elif isinstance(user, (int, long)):
1138 result = pwd.getpwuid(user)
1140 raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
1144 return result.pw_dir
1148 """Returns a random UUID.
1150 @note: This is a Linux-specific method as it uses the /proc
1155 return ReadFile(_RANDOM_UUID_FILE, size=128).rstrip("\n")
1158 def GenerateSecret(numbytes=20):
1159 """Generates a random secret.
1161 This will generate a pseudo-random secret returning an hex string
1162 (so that it can be used where an ASCII string is needed).
1164 @param numbytes: the number of bytes which will be represented by the returned
1165 string (defaulting to 20, the length of a SHA1 hash)
1167 @return: an hex representation of the pseudo-random sequence
1170 return os.urandom(numbytes).encode('hex')
1173 def EnsureDirs(dirs):
1174 """Make required directories, if they don't exist.
1176 @param dirs: list of tuples (dir_name, dir_mode)
1177 @type dirs: list of (string, integer)
1180 for dir_name, dir_mode in dirs:
1182 os.mkdir(dir_name, dir_mode)
1183 except EnvironmentError, err:
1184 if err.errno != errno.EEXIST:
1185 raise errors.GenericError("Cannot create needed directory"
1186 " '%s': %s" % (dir_name, err))
1187 if not os.path.isdir(dir_name):
1188 raise errors.GenericError("%s is not a directory" % dir_name)
1191 def ReadFile(file_name, size=None):
1194 @type size: None or int
1195 @param size: Read at most size bytes
1197 @return: the (possibly partial) content of the file
1200 f = open(file_name, "r")
1210 def WriteFile(file_name, fn=None, data=None,
1211 mode=None, uid=-1, gid=-1,
1212 atime=None, mtime=None, close=True,
1213 dry_run=False, backup=False,
1214 prewrite=None, postwrite=None):
1215 """(Over)write a file atomically.
1217 The file_name and either fn (a function taking one argument, the
1218 file descriptor, and which should write the data to it) or data (the
1219 contents of the file) must be passed. The other arguments are
1220 optional and allow setting the file mode, owner and group, and the
1221 mtime/atime of the file.
1223 If the function doesn't raise an exception, it has succeeded and the
1224 target file has the new contents. If the function has raised an
1225 exception, an existing target file should be unmodified and the
1226 temporary file should be removed.
1228 @type file_name: str
1229 @param file_name: the target filename
1231 @param fn: content writing function, called with
1232 file descriptor as parameter
1234 @param data: contents of the file
1236 @param mode: file mode
1238 @param uid: the owner of the file
1240 @param gid: the group of the file
1242 @param atime: a custom access time to be set on the file
1244 @param mtime: a custom modification time to be set on the file
1245 @type close: boolean
1246 @param close: whether to close file after writing it
1247 @type prewrite: callable
1248 @param prewrite: function to be called before writing content
1249 @type postwrite: callable
1250 @param postwrite: function to be called after writing content
1253 @return: None if the 'close' parameter evaluates to True,
1254 otherwise the file descriptor
1256 @raise errors.ProgrammerError: if any of the arguments are not valid
1259 if not os.path.isabs(file_name):
1260 raise errors.ProgrammerError("Path passed to WriteFile is not"
1261 " absolute: '%s'" % file_name)
1263 if [fn, data].count(None) != 1:
1264 raise errors.ProgrammerError("fn or data required")
1266 if [atime, mtime].count(None) == 1:
1267 raise errors.ProgrammerError("Both atime and mtime must be either"
1270 if backup and not dry_run and os.path.isfile(file_name):
1271 CreateBackup(file_name)
1273 dir_name, base_name = os.path.split(file_name)
1274 fd, new_name = tempfile.mkstemp('.new', base_name, dir_name)
1276 # here we need to make sure we remove the temp file, if any error
1277 # leaves it in place
1279 if uid != -1 or gid != -1:
1280 os.chown(new_name, uid, gid)
1282 os.chmod(new_name, mode)
1283 if callable(prewrite):
1285 if data is not None:
1289 if callable(postwrite):
1292 if atime is not None and mtime is not None:
1293 os.utime(new_name, (atime, mtime))
1295 os.rename(new_name, file_name)
1304 RemoveFile(new_name)
1309 def FirstFree(seq, base=0):
1310 """Returns the first non-existing integer from seq.
1312 The seq argument should be a sorted list of positive integers. The
1313 first time the index of an element is smaller than the element
1314 value, the index will be returned.
1316 The base argument is used to start at a different offset,
1317 i.e. C{[3, 4, 6]} with I{offset=3} will return 5.
1319 Example: C{[0, 1, 3]} will return I{2}.
1322 @param seq: the sequence to be analyzed.
1324 @param base: use this value as the base index of the sequence
1326 @return: the first non-used index in the sequence
1329 for idx, elem in enumerate(seq):
1330 assert elem >= base, "Passed element is higher than base offset"
1331 if elem > idx + base:
1337 def all(seq, pred=bool):
1338 "Returns True if pred(x) is True for every element in the iterable"
1339 for _ in itertools.ifilterfalse(pred, seq):
1344 def any(seq, pred=bool):
1345 "Returns True if pred(x) is True for at least one element in the iterable"
1346 for _ in itertools.ifilter(pred, seq):
1351 def UniqueSequence(seq):
1352 """Returns a list with unique elements.
1354 Element order is preserved.
1357 @param seq: the sequence with the source elements
1359 @return: list of unique elements from seq
1363 return [i for i in seq if i not in seen and not seen.add(i)]
1366 def IsValidMac(mac):
1367 """Predicate to check if a MAC address is valid.
1369 Checks whether the supplied MAC address is formally correct, only
1370 accepts colon separated format.
1373 @param mac: the MAC to be validated
1375 @return: True is the MAC seems valid
1378 mac_check = re.compile("^([0-9a-f]{2}(:|$)){6}$")
1379 return mac_check.match(mac) is not None
1382 def TestDelay(duration):
1383 """Sleep for a fixed amount of time.
1385 @type duration: float
1386 @param duration: the sleep duration
1388 @return: False for negative value, True otherwise
1392 return False, "Invalid sleep duration"
1393 time.sleep(duration)
1397 def _CloseFDNoErr(fd, retries=5):
1398 """Close a file descriptor ignoring errors.
1401 @param fd: the file descriptor
1403 @param retries: how many retries to make, in case we get any
1404 other error than EBADF
1409 except OSError, err:
1410 if err.errno != errno.EBADF:
1412 _CloseFDNoErr(fd, retries - 1)
1413 # else either it's closed already or we're out of retries, so we
1414 # ignore this and go on
1417 def CloseFDs(noclose_fds=None):
1418 """Close file descriptors.
1420 This closes all file descriptors above 2 (i.e. except
1423 @type noclose_fds: list or None
1424 @param noclose_fds: if given, it denotes a list of file descriptor
1425 that should not be closed
1428 # Default maximum for the number of available file descriptors.
1429 if 'SC_OPEN_MAX' in os.sysconf_names:
1431 MAXFD = os.sysconf('SC_OPEN_MAX')
1438 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1439 if (maxfd == resource.RLIM_INFINITY):
1442 # Iterate through and close all file descriptors (except the standard ones)
1443 for fd in range(3, maxfd):
1444 if noclose_fds and fd in noclose_fds:
1449 def Daemonize(logfile):
1450 """Daemonize the current process.
1452 This detaches the current process from the controlling terminal and
1453 runs it in the background as a daemon.
1456 @param logfile: the logfile to which we should redirect stdout/stderr
1458 @return: the value zero
1466 if (pid == 0): # The first child.
1469 pid = os.fork() # Fork a second child.
1470 if (pid == 0): # The second child.
1474 # exit() or _exit()? See below.
1475 os._exit(0) # Exit parent (the first child) of the second child.
1477 os._exit(0) # Exit parent of the first child.
1481 i = os.open("/dev/null", os.O_RDONLY) # stdin
1482 assert i == 0, "Can't close/reopen stdin"
1483 i = os.open(logfile, os.O_WRONLY|os.O_CREAT|os.O_APPEND, 0600) # stdout
1484 assert i == 1, "Can't close/reopen stdout"
1485 # Duplicate standard output to standard error.
1490 def DaemonPidFileName(name):
1491 """Compute a ganeti pid file absolute path
1494 @param name: the daemon name
1496 @return: the full path to the pidfile corresponding to the given
1500 return os.path.join(constants.RUN_GANETI_DIR, "%s.pid" % name)
1503 def WritePidFile(name):
1504 """Write the current process pidfile.
1506 The file will be written to L{constants.RUN_GANETI_DIR}I{/name.pid}
1509 @param name: the daemon name to use
1510 @raise errors.GenericError: if the pid file already exists and
1511 points to a live process
1515 pidfilename = DaemonPidFileName(name)
1516 if IsProcessAlive(ReadPidFile(pidfilename)):
1517 raise errors.GenericError("%s contains a live process" % pidfilename)
1519 WriteFile(pidfilename, data="%d\n" % pid)
1522 def RemovePidFile(name):
1523 """Remove the current process pidfile.
1525 Any errors are ignored.
1528 @param name: the daemon name used to derive the pidfile name
1531 pidfilename = DaemonPidFileName(name)
1532 # TODO: we could check here that the file contains our pid
1534 RemoveFile(pidfilename)
1539 def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
1541 """Kill a process given by its pid.
1544 @param pid: The PID to terminate.
1546 @param signal_: The signal to send, by default SIGTERM
1548 @param timeout: The timeout after which, if the process is still alive,
1549 a SIGKILL will be sent. If not positive, no such checking
1551 @type waitpid: boolean
1552 @param waitpid: If true, we should waitpid on this process after
1553 sending signals, since it's our own child and otherwise it
1554 would remain as zombie
1557 def _helper(pid, signal_, wait):
1558 """Simple helper to encapsulate the kill/waitpid sequence"""
1559 os.kill(pid, signal_)
1562 os.waitpid(pid, os.WNOHANG)
1567 # kill with pid=0 == suicide
1568 raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
1570 if not IsProcessAlive(pid):
1572 _helper(pid, signal_, waitpid)
1576 # Wait up to $timeout seconds
1577 end = time.time() + timeout
1579 while time.time() < end and IsProcessAlive(pid):
1581 (result_pid, _) = os.waitpid(pid, os.WNOHANG)
1587 # Make wait time longer for next try
1591 if IsProcessAlive(pid):
1592 # Kill process if it's still alive
1593 _helper(pid, signal.SIGKILL, waitpid)
1596 def FindFile(name, search_path, test=os.path.exists):
1597 """Look for a filesystem object in a given path.
1599 This is an abstract method to search for filesystem object (files,
1600 dirs) under a given search path.
1603 @param name: the name to look for
1604 @type search_path: str
1605 @param search_path: location to start at
1606 @type test: callable
1607 @param test: a function taking one argument that should return True
1608 if the a given object is valid; the default value is
1609 os.path.exists, causing only existing files to be returned
1611 @return: full path to the object if found, None otherwise
1614 for dir_name in search_path:
1615 item_name = os.path.sep.join([dir_name, name])
1621 def CheckVolumeGroupSize(vglist, vgname, minsize):
1622 """Checks if the volume group list is valid.
1624 The function will check if a given volume group is in the list of
1625 volume groups and has a minimum size.
1628 @param vglist: dictionary of volume group names and their size
1630 @param vgname: the volume group we should check
1632 @param minsize: the minimum size we accept
1634 @return: None for success, otherwise the error message
1637 vgsize = vglist.get(vgname, None)
1639 return "volume group '%s' missing" % vgname
1640 elif vgsize < minsize:
1641 return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
1642 (vgname, minsize, vgsize))
1646 def SplitTime(value):
1647 """Splits time as floating point number into a tuple.
1649 @param value: Time in seconds
1650 @type value: int or float
1651 @return: Tuple containing (seconds, microseconds)
1654 (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
1656 assert 0 <= seconds, \
1657 "Seconds must be larger than or equal to 0, but are %s" % seconds
1658 assert 0 <= microseconds <= 999999, \
1659 "Microseconds must be 0-999999, but are %s" % microseconds
1661 return (int(seconds), int(microseconds))
1664 def MergeTime(timetuple):
1665 """Merges a tuple into time as a floating point number.
1667 @param timetuple: Time as tuple, (seconds, microseconds)
1668 @type timetuple: tuple
1669 @return: Time as a floating point number expressed in seconds
1672 (seconds, microseconds) = timetuple
1674 assert 0 <= seconds, \
1675 "Seconds must be larger than or equal to 0, but are %s" % seconds
1676 assert 0 <= microseconds <= 999999, \
1677 "Microseconds must be 0-999999, but are %s" % microseconds
1679 return float(seconds) + (float(microseconds) * 0.000001)
1682 def GetDaemonPort(daemon_name):
1683 """Get the daemon port for this cluster.
1685 Note that this routine does not read a ganeti-specific file, but
1686 instead uses C{socket.getservbyname} to allow pre-customization of
1687 this parameter outside of Ganeti.
1689 @type daemon_name: string
1690 @param daemon_name: daemon name (in constants.DAEMONS_PORTS)
1694 if daemon_name not in constants.DAEMONS_PORTS:
1695 raise errors.ProgrammerError("Unknown daemon: %s" % daemon_name)
1697 (proto, default_port) = constants.DAEMONS_PORTS[daemon_name]
1699 port = socket.getservbyname(daemon_name, proto)
1700 except socket.error:
1706 def SetupLogging(logfile, debug=False, stderr_logging=False, program="",
1707 multithreaded=False):
1708 """Configures the logging module.
1711 @param logfile: the filename to which we should log
1712 @type debug: boolean
1713 @param debug: whether to enable debug messages too or
1714 only those at C{INFO} and above level
1715 @type stderr_logging: boolean
1716 @param stderr_logging: whether we should also log to the standard error
1718 @param program: the name under which we should log messages
1719 @type multithreaded: boolean
1720 @param multithreaded: if True, will add the thread name to the log file
1721 @raise EnvironmentError: if we can't open the log file and
1722 stderr logging is disabled
1725 fmt = "%(asctime)s: " + program + " pid=%(process)d"
1727 fmt += "/%(threadName)s"
1729 fmt += " %(module)s:%(lineno)s"
1730 fmt += " %(levelname)s %(message)s"
1731 formatter = logging.Formatter(fmt)
1733 root_logger = logging.getLogger("")
1734 root_logger.setLevel(logging.NOTSET)
1736 # Remove all previously setup handlers
1737 for handler in root_logger.handlers:
1739 root_logger.removeHandler(handler)
1742 stderr_handler = logging.StreamHandler()
1743 stderr_handler.setFormatter(formatter)
1745 stderr_handler.setLevel(logging.NOTSET)
1747 stderr_handler.setLevel(logging.CRITICAL)
1748 root_logger.addHandler(stderr_handler)
1750 # this can fail, if the logging directories are not setup or we have
1751 # a permisssion problem; in this case, it's best to log but ignore
1752 # the error if stderr_logging is True, and if false we re-raise the
1753 # exception since otherwise we could run but without any logs at all
1755 logfile_handler = logging.FileHandler(logfile)
1756 logfile_handler.setFormatter(formatter)
1758 logfile_handler.setLevel(logging.DEBUG)
1760 logfile_handler.setLevel(logging.INFO)
1761 root_logger.addHandler(logfile_handler)
1762 except EnvironmentError:
1764 logging.exception("Failed to enable logging to file '%s'", logfile)
1766 # we need to re-raise the exception
1770 def IsNormAbsPath(path):
1771 """Check whether a path is absolute and also normalized
1773 This avoids things like /dir/../../other/path to be valid.
1776 return os.path.normpath(path) == path and os.path.isabs(path)
1779 def TailFile(fname, lines=20):
1780 """Return the last lines from a file.
1782 @note: this function will only read and parse the last 4KB of
1783 the file; if the lines are very long, it could be that less
1784 than the requested number of lines are returned
1786 @param fname: the file name
1788 @param lines: the (maximum) number of lines to return
1791 fd = open(fname, "r")
1795 pos = max(0, pos-4096)
1797 raw_data = fd.read()
1801 rows = raw_data.splitlines()
1802 return rows[-lines:]
1805 def SafeEncode(text):
1806 """Return a 'safe' version of a source string.
1808 This function mangles the input string and returns a version that
1809 should be safe to display/encode as ASCII. To this end, we first
1810 convert it to ASCII using the 'backslashreplace' encoding which
1811 should get rid of any non-ASCII chars, and then we process it
1812 through a loop copied from the string repr sources in the python; we
1813 don't use string_escape anymore since that escape single quotes and
1814 backslashes too, and that is too much; and that escaping is not
1815 stable, i.e. string_escape(string_escape(x)) != string_escape(x).
1817 @type text: str or unicode
1818 @param text: input data
1820 @return: a safe version of text
1823 if isinstance(text, unicode):
1824 # only if unicode; if str already, we handle it below
1825 text = text.encode('ascii', 'backslashreplace')
1835 elif c < 32 or c >= 127: # non-printable
1836 resu += "\\x%02x" % (c & 0xff)
1842 def CommaJoin(names):
1843 """Nicely join a set of identifiers.
1845 @param names: set, list or tuple
1846 @return: a string with the formatted results
1849 return ", ".join(["'%s'" % val for val in names])
1852 def BytesToMebibyte(value):
1853 """Converts bytes to mebibytes.
1856 @param value: Value in bytes
1858 @return: Value in mebibytes
1861 return int(round(value / (1024.0 * 1024.0), 0))
1864 def CalculateDirectorySize(path):
1865 """Calculates the size of a directory recursively.
1868 @param path: Path to directory
1870 @return: Size in mebibytes
1875 for (curpath, _, files) in os.walk(path):
1876 for filename in files:
1877 st = os.lstat(os.path.join(curpath, filename))
1880 return BytesToMebibyte(size)
1883 def GetFreeFilesystemSpace(path):
1884 """Returns the free space on a filesystem.
1887 @param path: Path on filesystem to be examined
1889 @return: Free space in mebibytes
1892 st = os.statvfs(path)
1894 return BytesToMebibyte(st.f_bavail * st.f_frsize)
1897 def LockedMethod(fn):
1898 """Synchronized object access decorator.
1900 This decorator is intended to protect access to an object using the
1901 object's own lock which is hardcoded to '_lock'.
1904 def _LockDebug(*args, **kwargs):
1906 logging.debug(*args, **kwargs)
1908 def wrapper(self, *args, **kwargs):
1909 assert hasattr(self, '_lock')
1911 _LockDebug("Waiting for %s", lock)
1914 _LockDebug("Acquired %s", lock)
1915 result = fn(self, *args, **kwargs)
1917 _LockDebug("Releasing %s", lock)
1919 _LockDebug("Released %s", lock)
1925 """Locks a file using POSIX locks.
1928 @param fd: the file descriptor we need to lock
1932 fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
1933 except IOError, err:
1934 if err.errno == errno.EAGAIN:
1935 raise errors.LockError("File already locked")
1939 def FormatTime(val):
1940 """Formats a time value.
1942 @type val: float or None
1943 @param val: the timestamp as returned by time.time()
1944 @return: a string value or N/A if we don't have a valid timestamp
1947 if val is None or not isinstance(val, (int, float)):
1949 # these two codes works on Linux, but they are not guaranteed on all
1951 return time.strftime("%F %T", time.localtime(val))
1954 def ReadWatcherPauseFile(filename, now=None, remove_after=3600):
1955 """Reads the watcher pause file.
1957 @type filename: string
1958 @param filename: Path to watcher pause file
1959 @type now: None, float or int
1960 @param now: Current time as Unix timestamp
1961 @type remove_after: int
1962 @param remove_after: Remove watcher pause file after specified amount of
1963 seconds past the pause end time
1970 value = ReadFile(filename)
1971 except IOError, err:
1972 if err.errno != errno.ENOENT:
1976 if value is not None:
1980 logging.warning(("Watcher pause file (%s) contains invalid value,"
1981 " removing it"), filename)
1982 RemoveFile(filename)
1985 if value is not None:
1986 # Remove file if it's outdated
1987 if now > (value + remove_after):
1988 RemoveFile(filename)
1997 class FileLock(object):
1998 """Utility class for file locks.
2001 def __init__(self, filename):
2002 """Constructor for FileLock.
2004 This will open the file denoted by the I{filename} argument.
2007 @param filename: path to the file to be locked
2010 self.filename = filename
2011 self.fd = open(self.filename, "w")
2017 """Close the file and release the lock.
2024 def _flock(self, flag, blocking, timeout, errmsg):
2025 """Wrapper for fcntl.flock.
2028 @param flag: operation flag
2029 @type blocking: bool
2030 @param blocking: whether the operation should be done in blocking mode.
2031 @type timeout: None or float
2032 @param timeout: for how long the operation should be retried (implies
2034 @type errmsg: string
2035 @param errmsg: error message in case operation fails.
2038 assert self.fd, "Lock was closed"
2039 assert timeout is None or timeout >= 0, \
2040 "If specified, timeout must be positive"
2042 if timeout is not None:
2043 flag |= fcntl.LOCK_NB
2044 timeout_end = time.time() + timeout
2046 # Blocking doesn't have effect with timeout
2048 flag |= fcntl.LOCK_NB
2054 fcntl.flock(self.fd, flag)
2056 except IOError, err:
2057 if err.errno in (errno.EAGAIN, ):
2058 if timeout_end is not None and time.time() < timeout_end:
2059 # Wait before trying again
2060 time.sleep(max(0.1, min(1.0, timeout)))
2062 raise errors.LockError(errmsg)
2064 logging.exception("fcntl.flock failed")
2067 def Exclusive(self, blocking=False, timeout=None):
2068 """Locks the file in exclusive mode.
2070 @type blocking: boolean
2071 @param blocking: whether to block and wait until we
2072 can lock the file or return immediately
2073 @type timeout: int or None
2074 @param timeout: if not None, the duration to wait for the lock
2078 self._flock(fcntl.LOCK_EX, blocking, timeout,
2079 "Failed to lock %s in exclusive mode" % self.filename)
2081 def Shared(self, blocking=False, timeout=None):
2082 """Locks the file in shared mode.
2084 @type blocking: boolean
2085 @param blocking: whether to block and wait until we
2086 can lock the file or return immediately
2087 @type timeout: int or None
2088 @param timeout: if not None, the duration to wait for the lock
2092 self._flock(fcntl.LOCK_SH, blocking, timeout,
2093 "Failed to lock %s in shared mode" % self.filename)
2095 def Unlock(self, blocking=True, timeout=None):
2096 """Unlocks the file.
2098 According to C{flock(2)}, unlocking can also be a nonblocking
2101 To make a non-blocking request, include LOCK_NB with any of the above
2104 @type blocking: boolean
2105 @param blocking: whether to block and wait until we
2106 can lock the file or return immediately
2107 @type timeout: int or None
2108 @param timeout: if not None, the duration to wait for the lock
2112 self._flock(fcntl.LOCK_UN, blocking, timeout,
2113 "Failed to unlock %s" % self.filename)
2116 def SignalHandled(signums):
2117 """Signal Handled decoration.
2119 This special decorator installs a signal handler and then calls the target
2120 function. The function must accept a 'signal_handlers' keyword argument,
2121 which will contain a dict indexed by signal number, with SignalHandler
2124 The decorator can be safely stacked with iself, to handle multiple signals
2125 with different handlers.
2128 @param signums: signals to intercept
2132 def sig_function(*args, **kwargs):
2133 assert 'signal_handlers' not in kwargs or \
2134 kwargs['signal_handlers'] is None or \
2135 isinstance(kwargs['signal_handlers'], dict), \
2136 "Wrong signal_handlers parameter in original function call"
2137 if 'signal_handlers' in kwargs and kwargs['signal_handlers'] is not None:
2138 signal_handlers = kwargs['signal_handlers']
2140 signal_handlers = {}
2141 kwargs['signal_handlers'] = signal_handlers
2142 sighandler = SignalHandler(signums)
2145 signal_handlers[sig] = sighandler
2146 return fn(*args, **kwargs)
2153 class SignalHandler(object):
2154 """Generic signal handler class.
2156 It automatically restores the original handler when deconstructed or
2157 when L{Reset} is called. You can either pass your own handler
2158 function in or query the L{called} attribute to detect whether the
2162 @ivar signum: the signals we handle
2163 @type called: boolean
2164 @ivar called: tracks whether any of the signals have been raised
2167 def __init__(self, signum):
2168 """Constructs a new SignalHandler instance.
2170 @type signum: int or list of ints
2171 @param signum: Single signal number or set of signal numbers
2174 self.signum = set(signum)
2179 for signum in self.signum:
2181 prev_handler = signal.signal(signum, self._HandleSignal)
2183 self._previous[signum] = prev_handler
2185 # Restore previous handler
2186 signal.signal(signum, prev_handler)
2189 # Reset all handlers
2191 # Here we have a race condition: a handler may have already been called,
2192 # but there's not much we can do about it at this point.
2199 """Restore previous handler.
2201 This will reset all the signals to their previous handlers.
2204 for signum, prev_handler in self._previous.items():
2205 signal.signal(signum, prev_handler)
2206 # If successful, remove from dict
2207 del self._previous[signum]
2210 """Unsets the L{called} flag.
2212 This function can be used in case a signal may arrive several times.
2217 def _HandleSignal(self, signum, frame):
2218 """Actual signal handling function.
2221 # This is not nice and not absolutely atomic, but it appears to be the only
2222 # solution in Python -- there are no atomic types.
2226 class FieldSet(object):
2227 """A simple field set.
2229 Among the features are:
2230 - checking if a string is among a list of static string or regex objects
2231 - checking if a whole list of string matches
2232 - returning the matching groups from a regex match
2234 Internally, all fields are held as regular expression objects.
2237 def __init__(self, *items):
2238 self.items = [re.compile("^%s$" % value) for value in items]
2240 def Extend(self, other_set):
2241 """Extend the field set with the items from another one"""
2242 self.items.extend(other_set.items)
2244 def Matches(self, field):
2245 """Checks if a field matches the current set
2248 @param field: the string to match
2249 @return: either False or a regular expression match object
2252 for m in itertools.ifilter(None, (val.match(field) for val in self.items)):
2256 def NonMatching(self, items):
2257 """Returns the list of fields not matching the current set
2260 @param items: the list of fields to check
2262 @return: list of non-matching fields
2265 return [val for val in items if not self.Matches(val)]