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 small utilities
45 from cStringIO import StringIO
47 from ganeti import errors
48 from ganeti import constants
52 _re_shell_unquoted = re.compile('^[-.,=:/_+@A-Za-z0-9]+$')
59 class RunResult(object):
60 """Simple class for holding the result of running external programs.
63 exit_code: the exit code of the program, or None (if the program
65 signal: numeric signal that caused the program to finish, or None
66 (if the program wasn't terminated by a signal)
67 stdout: the standard output of the program
68 stderr: the standard error of the program
69 failed: a Boolean value which is True in case the program was
70 terminated by a signal or exited with a non-zero exit code
71 fail_reason: a string detailing the termination reason
74 __slots__ = ["exit_code", "signal", "stdout", "stderr",
75 "failed", "fail_reason", "cmd"]
78 def __init__(self, exit_code, signal_, stdout, stderr, cmd):
80 self.exit_code = exit_code
84 self.failed = (signal_ is not None or exit_code != 0)
86 if self.signal is not None:
87 self.fail_reason = "terminated by signal %s" % self.signal
88 elif self.exit_code is not None:
89 self.fail_reason = "exited with exit code %s" % self.exit_code
91 self.fail_reason = "unable to determine termination reason"
94 logging.debug("Command '%s' failed (%s); output: %s",
95 self.cmd, self.fail_reason, self.output)
98 """Returns the combined stdout and stderr for easier usage.
101 return self.stdout + self.stderr
103 output = property(_GetOutput, None, None, "Return full output")
106 def RunCmd(cmd, env=None):
107 """Execute a (shell) command.
109 The command should not read from its standard input, as it will be
112 @param cmd: Command to run
113 @type cmd: string or list
114 @param env: Additional environment
116 @return: `RunResult` instance
121 raise errors.ProgrammerError("utils.RunCmd() called with fork() disabled")
123 if isinstance(cmd, list):
124 cmd = [str(val) for val in cmd]
125 strcmd = " ".join(cmd)
130 logging.debug("RunCmd '%s'", strcmd)
132 cmd_env = os.environ.copy()
133 cmd_env["LC_ALL"] = "C"
137 poller = select.poll()
138 child = subprocess.Popen(cmd, shell=shell,
139 stderr=subprocess.PIPE,
140 stdout=subprocess.PIPE,
141 stdin=subprocess.PIPE,
142 close_fds=True, env=cmd_env)
145 poller.register(child.stdout, select.POLLIN)
146 poller.register(child.stderr, select.POLLIN)
150 child.stdout.fileno(): (out, child.stdout),
151 child.stderr.fileno(): (err, child.stderr),
154 status = fcntl.fcntl(fd, fcntl.F_GETFL)
155 fcntl.fcntl(fd, fcntl.F_SETFL, status | os.O_NONBLOCK)
158 for fd, event in poller.poll():
159 if event & select.POLLIN or event & select.POLLPRI:
160 data = fdmap[fd][1].read()
161 # no data from read signifies EOF (the same as POLLHUP)
163 poller.unregister(fd)
166 fdmap[fd][0].write(data)
167 if (event & select.POLLNVAL or event & select.POLLHUP or
168 event & select.POLLERR):
169 poller.unregister(fd)
175 status = child.wait()
183 return RunResult(exitcode, signal_, out, err, strcmd)
186 def RemoveFile(filename):
187 """Remove a file ignoring some errors.
189 Remove a file, ignoring non-existing ones or directories. Other
196 if err.errno not in (errno.ENOENT, errno.EISDIR):
200 def _FingerprintFile(filename):
201 """Compute the fingerprint of a file.
203 If the file does not exist, a None will be returned
207 filename - Filename (str)
210 if not (os.path.exists(filename) and os.path.isfile(filename)):
223 return fp.hexdigest()
226 def FingerprintFiles(files):
227 """Compute fingerprints for a list of files.
230 files - array of filenames. ( [str, ...] )
233 dictionary of filename: fingerprint for the files that exist
238 for filename in files:
239 cksum = _FingerprintFile(filename)
241 ret[filename] = cksum
246 def CheckDict(target, template, logname=None):
247 """Ensure a dictionary has a required set of keys.
249 For the given dictionaries `target` and `template`, ensure target
250 has all the keys from template. Missing keys are added with values
254 target - the dictionary to check
255 template - template dictionary
256 logname - a caller-chosen string to identify the debug log
257 entry; if None, no logging will be done
267 target[k] = template[k]
269 if missing and logname:
270 logging.warning('%s missing keys %s', logname, ', '.join(missing))
273 def IsProcessAlive(pid):
274 """Check if a given pid exists on the system.
276 Returns: true or false, depending on if the pid exists or not
278 Remarks: zombie processes treated as not alive, and giving a pid <=
279 0 makes the function to return False.
286 f = open("/proc/%d/status" % pid)
288 if err.errno in (errno.ENOENT, errno.ENOTDIR):
295 state = data[1].split()
296 if len(state) > 1 and state[1] == "Z":
304 def ReadPidFile(pidfile):
305 """Read the pid from a file.
307 @param pidfile: Path to a file containing the pid to be checked
308 @type pidfile: string (filename)
309 @return: The process id, if the file exista and contains a valid PID,
315 pf = open(pidfile, 'r')
316 except EnvironmentError, err:
317 if err.errno != errno.ENOENT:
318 logging.exception("Can't read pid file?!")
323 except ValueError, err:
324 logging.info("Can't parse pid file contents", exc_info=True)
330 def MatchNameComponent(key, name_list):
331 """Try to match a name against a list.
333 This function will try to match a name like test1 against a list
334 like ['test1.example.com', 'test2.example.com', ...]. Against this
335 list, 'test1' as well as 'test1.example' will match, but not
336 'test1.ex'. A multiple match will be considered as no match at all
337 (e.g. 'test1' against ['test1.example.com', 'test1.example.org']).
340 key: the name to be searched
341 name_list: the list of strings against which to search the key
344 None if there is no match *or* if there are multiple matches
345 otherwise the element from the list which matches
348 mo = re.compile("^%s(\..*)?$" % re.escape(key))
349 names_filtered = [name for name in name_list if mo.match(name) is not None]
350 if len(names_filtered) != 1:
352 return names_filtered[0]
356 """Class implementing resolver and hostname functionality
359 def __init__(self, name=None):
360 """Initialize the host name object.
362 If the name argument is not passed, it will use this system's
367 name = self.SysName()
370 self.name, self.aliases, self.ipaddrs = self.LookupHostname(name)
371 self.ip = self.ipaddrs[0]
374 """Returns the hostname without domain.
377 return self.name.split('.')[0]
381 """Return the current system's name.
383 This is simply a wrapper over socket.gethostname()
386 return socket.gethostname()
389 def LookupHostname(hostname):
393 hostname: hostname to look up
396 a tuple (name, aliases, ipaddrs) as returned by socket.gethostbyname_ex
397 in case of errors in resolving, we raise a ResolverError
401 result = socket.gethostbyname_ex(hostname)
402 except socket.gaierror, err:
403 # hostname not found in DNS
404 raise errors.ResolverError(hostname, err.args[0], err.args[1])
409 def ListVolumeGroups():
410 """List volume groups and their size
413 Dictionary with keys volume name and values the size of the volume
416 command = "vgs --noheadings --units m --nosuffix -o name,size"
417 result = RunCmd(command)
422 for line in result.stdout.splitlines():
424 name, size = line.split()
425 size = int(float(size))
426 except (IndexError, ValueError), err:
427 logging.error("Invalid output from vgs (%s): %s", err, line)
435 def BridgeExists(bridge):
436 """Check whether the given bridge exists in the system
439 True if it does, false otherwise.
442 return os.path.isdir("/sys/class/net/%s/bridge" % bridge)
445 def NiceSort(name_list):
446 """Sort a list of strings based on digit and non-digit groupings.
448 Given a list of names ['a1', 'a10', 'a11', 'a2'] this function will
449 sort the list in the logical order ['a1', 'a2', 'a10', 'a11'].
451 The sort algorithm breaks each name in groups of either only-digits
452 or no-digits. Only the first eight such groups are considered, and
453 after that we just use what's left of the string.
456 - a copy of the list sorted according to our algorithm
459 _SORTER_BASE = "(\D+|\d+)"
460 _SORTER_FULL = "^%s%s?%s?%s?%s?%s?%s?%s?.*$" % (_SORTER_BASE, _SORTER_BASE,
461 _SORTER_BASE, _SORTER_BASE,
462 _SORTER_BASE, _SORTER_BASE,
463 _SORTER_BASE, _SORTER_BASE)
464 _SORTER_RE = re.compile(_SORTER_FULL)
465 _SORTER_NODIGIT = re.compile("^\D*$")
467 """Attempts to convert a variable to integer."""
468 if val is None or _SORTER_NODIGIT.match(val):
473 to_sort = [([_TryInt(grp) for grp in _SORTER_RE.match(name).groups()], name)
474 for name in name_list]
476 return [tup[1] for tup in to_sort]
479 def TryConvert(fn, val):
480 """Try to convert a value ignoring errors.
482 This function tries to apply function `fn` to `val`. If no
483 ValueError or TypeError exceptions are raised, it will return the
484 result, else it will return the original value. Any other exceptions
485 are propagated to the caller.
490 except (ValueError, TypeError), err:
496 """Verifies the syntax of an IP address.
498 This function checks if the ip address passes is valid or not based
499 on syntax (not ip range, class calculations or anything).
502 unit = "(0|[1-9]\d{0,2})"
503 return re.match("^%s\.%s\.%s\.%s$" % (unit, unit, unit, unit), ip)
506 def IsValidShellParam(word):
507 """Verifies is the given word is safe from the shell's p.o.v.
509 This means that we can pass this to a command via the shell and be
510 sure that it doesn't alter the command line and is passed as such to
513 Note that we are overly restrictive here, in order to be on the safe
517 return bool(re.match("^[-a-zA-Z0-9._+/:%@]+$", word))
520 def BuildShellCmd(template, *args):
521 """Build a safe shell command line from the given arguments.
523 This function will check all arguments in the args list so that they
524 are valid shell parameters (i.e. they don't contain shell
525 metacharaters). If everything is ok, it will return the result of
530 if not IsValidShellParam(word):
531 raise errors.ProgrammerError("Shell argument '%s' contains"
532 " invalid characters" % word)
533 return template % args
536 def FormatUnit(value):
537 """Formats an incoming number of MiB with the appropriate unit.
539 Value needs to be passed as a numeric type. Return value is always a string.
543 return "%dM" % round(value, 0)
545 elif value < (1024 * 1024):
546 return "%0.1fG" % round(float(value) / 1024, 1)
549 return "%0.1fT" % round(float(value) / 1024 / 1024, 1)
552 def ParseUnit(input_string):
553 """Tries to extract number and scale from the given string.
555 Input must be in the format NUMBER+ [DOT NUMBER+] SPACE* [UNIT]. If no unit
556 is specified, it defaults to MiB. Return value is always an int in MiB.
559 m = re.match('^([.\d]+)\s*([a-zA-Z]+)?$', input_string)
561 raise errors.UnitParseError("Invalid format")
563 value = float(m.groups()[0])
567 lcunit = unit.lower()
571 if lcunit in ('m', 'mb', 'mib'):
572 # Value already in MiB
575 elif lcunit in ('g', 'gb', 'gib'):
578 elif lcunit in ('t', 'tb', 'tib'):
582 raise errors.UnitParseError("Unknown unit: %s" % unit)
584 # Make sure we round up
585 if int(value) < value:
588 # Round up to the next multiple of 4
591 value += 4 - value % 4
596 def AddAuthorizedKey(file_name, key):
597 """Adds an SSH public key to an authorized_keys file.
600 file_name: Path to authorized_keys file
601 key: String containing key
603 key_fields = key.split()
605 f = open(file_name, 'a+')
609 # Ignore whitespace changes
610 if line.split() == key_fields:
612 nl = line.endswith('\n')
616 f.write(key.rstrip('\r\n'))
623 def RemoveAuthorizedKey(file_name, key):
624 """Removes an SSH public key from an authorized_keys file.
627 file_name: Path to authorized_keys file
628 key: String containing key
630 key_fields = key.split()
632 fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
634 out = os.fdopen(fd, 'w')
636 f = open(file_name, 'r')
639 # Ignore whitespace changes while comparing lines
640 if line.split() != key_fields:
644 os.rename(tmpname, file_name)
654 def SetEtcHostsEntry(file_name, ip, hostname, aliases):
655 """Sets the name of an IP address and hostname in /etc/hosts.
658 # Ensure aliases are unique
659 aliases = UniqueSequence([hostname] + aliases)[1:]
661 fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
663 out = os.fdopen(fd, 'w')
665 f = open(file_name, 'r')
669 fields = line.split()
670 if fields and not fields[0].startswith('#') and ip == fields[0]:
674 out.write("%s\t%s" % (ip, hostname))
676 out.write(" %s" % ' '.join(aliases))
681 os.rename(tmpname, file_name)
691 def AddHostToEtcHosts(hostname):
692 """Wrapper around SetEtcHostsEntry.
695 hi = HostInfo(name=hostname)
696 SetEtcHostsEntry(constants.ETC_HOSTS, hi.ip, hi.name, [hi.ShortName()])
699 def RemoveEtcHostsEntry(file_name, hostname):
700 """Removes a hostname from /etc/hosts.
702 IP addresses without names are removed from the file.
704 fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
706 out = os.fdopen(fd, 'w')
708 f = open(file_name, 'r')
711 fields = line.split()
712 if len(fields) > 1 and not fields[0].startswith('#'):
714 if hostname in names:
715 while hostname in names:
716 names.remove(hostname)
718 out.write("%s %s\n" % (fields[0], ' '.join(names)))
725 os.rename(tmpname, file_name)
735 def RemoveHostFromEtcHosts(hostname):
736 """Wrapper around RemoveEtcHostsEntry.
739 hi = HostInfo(name=hostname)
740 RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.name)
741 RemoveEtcHostsEntry(constants.ETC_HOSTS, hi.ShortName())
744 def CreateBackup(file_name):
745 """Creates a backup of a file.
747 Returns: the path to the newly created backup file.
750 if not os.path.isfile(file_name):
751 raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
754 prefix = '%s.backup-%d.' % (os.path.basename(file_name), int(time.time()))
755 dir_name = os.path.dirname(file_name)
757 fsrc = open(file_name, 'rb')
759 (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
760 fdst = os.fdopen(fd, 'wb')
762 shutil.copyfileobj(fsrc, fdst)
771 def ShellQuote(value):
772 """Quotes shell argument according to POSIX.
775 if _re_shell_unquoted.match(value):
778 return "'%s'" % value.replace("'", "'\\''")
781 def ShellQuoteArgs(args):
782 """Quotes all given shell arguments and concatenates using spaces.
785 return ' '.join([ShellQuote(i) for i in args])
788 def TcpPing(target, port, timeout=10, live_port_needed=False, source=None):
789 """Simple ping implementation using TCP connect(2).
791 Try to do a TCP connect(2) from an optional source IP to the
792 specified target IP and the specified target port. If the optional
793 parameter live_port_needed is set to true, requires the remote end
794 to accept the connection. The timeout is specified in seconds and
795 defaults to 10 seconds. If the source optional argument is not
796 passed, the source address selection is left to the kernel,
797 otherwise we try to connect using the passed address (failures to
798 bind other than EADDRNOTAVAIL will be ignored).
801 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
805 if source is not None:
807 sock.bind((source, 0))
808 except socket.error, (errcode, errstring):
809 if errcode == errno.EADDRNOTAVAIL:
812 sock.settimeout(timeout)
815 sock.connect((target, port))
818 except socket.timeout:
820 except socket.error, (errcode, errstring):
821 success = (not live_port_needed) and (errcode == errno.ECONNREFUSED)
826 def OwnIpAddress(address):
827 """Check if the current host has the the given IP address.
829 Currently this is done by tcp-pinging the address from the loopback
832 @type address: string
833 @param address: the addres to check
837 return TcpPing(address, constants.DEFAULT_NODED_PORT,
838 source=constants.LOCALHOST_IP_ADDRESS)
841 def ListVisibleFiles(path):
842 """Returns a list of all visible files in a directory.
845 files = [i for i in os.listdir(path) if not i.startswith(".")]
850 def GetHomeDir(user, default=None):
851 """Try to get the homedir of the given user.
853 The user can be passed either as a string (denoting the name) or as
854 an integer (denoting the user id). If the user is not found, the
855 'default' argument is returned, which defaults to None.
859 if isinstance(user, basestring):
860 result = pwd.getpwnam(user)
861 elif isinstance(user, (int, long)):
862 result = pwd.getpwuid(user)
864 raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
872 """Returns a random UUID.
875 f = open("/proc/sys/kernel/random/uuid", "r")
877 return f.read(128).rstrip("\n")
882 def GenerateSecret():
883 """Generates a random secret.
885 This will generate a pseudo-random secret, and return its sha digest
886 (so that it can be used where an ASCII string is needed).
889 return sha.new(os.urandom(64)).hexdigest()
892 def ReadFile(file_name, size=None):
895 @type size: None or int
896 @param size: Read at most size bytes
899 f = open(file_name, "r")
909 def WriteFile(file_name, fn=None, data=None,
910 mode=None, uid=-1, gid=-1,
911 atime=None, mtime=None, close=True,
912 dry_run=False, backup=False,
913 prewrite=None, postwrite=None):
914 """(Over)write a file atomically.
916 The file_name and either fn (a function taking one argument, the
917 file descriptor, and which should write the data to it) or data (the
918 contents of the file) must be passed. The other arguments are
919 optional and allow setting the file mode, owner and group, and the
920 mtime/atime of the file.
922 If the function doesn't raise an exception, it has succeeded and the
923 target file has the new contents. If the file has raised an
924 exception, an existing target file should be unmodified and the
925 temporary file should be removed.
928 file_name: New filename
929 fn: Content writing function, called with file descriptor as parameter
930 data: Content as string
935 mtime: Modification time
936 close: Whether to close file after writing it
937 prewrite: Function object called before writing content
938 postwrite: Function object called after writing content
941 None if "close" parameter evaluates to True, otherwise file descriptor.
944 if not os.path.isabs(file_name):
945 raise errors.ProgrammerError("Path passed to WriteFile is not"
946 " absolute: '%s'" % file_name)
948 if [fn, data].count(None) != 1:
949 raise errors.ProgrammerError("fn or data required")
951 if [atime, mtime].count(None) == 1:
952 raise errors.ProgrammerError("Both atime and mtime must be either"
955 if backup and not dry_run and os.path.isfile(file_name):
956 CreateBackup(file_name)
958 dir_name, base_name = os.path.split(file_name)
959 fd, new_name = tempfile.mkstemp('.new', base_name, dir_name)
960 # here we need to make sure we remove the temp file, if any error
963 if uid != -1 or gid != -1:
964 os.chown(new_name, uid, gid)
966 os.chmod(new_name, mode)
967 if callable(prewrite):
973 if callable(postwrite):
976 if atime is not None and mtime is not None:
977 os.utime(new_name, (atime, mtime))
979 os.rename(new_name, file_name)
991 def FirstFree(seq, base=0):
992 """Returns the first non-existing integer from seq.
994 The seq argument should be a sorted list of positive integers. The
995 first time the index of an element is smaller than the element
996 value, the index will be returned.
998 The base argument is used to start at a different offset,
999 i.e. [3, 4, 6] with offset=3 will return 5.
1001 Example: [0, 1, 3] will return 2.
1004 for idx, elem in enumerate(seq):
1005 assert elem >= base, "Passed element is higher than base offset"
1006 if elem > idx + base:
1012 def all(seq, pred=bool):
1013 "Returns True if pred(x) is True for every element in the iterable"
1014 for elem in itertools.ifilterfalse(pred, seq):
1019 def any(seq, pred=bool):
1020 "Returns True if pred(x) is True for at least one element in the iterable"
1021 for elem in itertools.ifilter(pred, seq):
1026 def UniqueSequence(seq):
1027 """Returns a list with unique elements.
1029 Element order is preserved.
1032 return [i for i in seq if i not in seen and not seen.add(i)]
1035 def IsValidMac(mac):
1036 """Predicate to check if a MAC address is valid.
1038 Checks wether the supplied MAC address is formally correct, only
1039 accepts colon separated format.
1041 mac_check = re.compile("^([0-9a-f]{2}(:|$)){6}$")
1042 return mac_check.match(mac) is not None
1045 def TestDelay(duration):
1046 """Sleep for a fixed amount of time.
1051 time.sleep(duration)
1055 def Daemonize(logfile, noclose_fds=None):
1056 """Daemonize the current process.
1058 This detaches the current process from the controlling terminal and
1059 runs it in the background as a daemon.
1064 # Default maximum for the number of available file descriptors.
1065 if 'SC_OPEN_MAX' in os.sysconf_names:
1067 MAXFD = os.sysconf('SC_OPEN_MAX')
1077 if (pid == 0): # The first child.
1080 pid = os.fork() # Fork a second child.
1081 if (pid == 0): # The second child.
1085 # exit() or _exit()? See below.
1086 os._exit(0) # Exit parent (the first child) of the second child.
1088 os._exit(0) # Exit parent of the first child.
1089 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1090 if (maxfd == resource.RLIM_INFINITY):
1093 # Iterate through and close all file descriptors.
1094 for fd in range(0, maxfd):
1095 if noclose_fds and fd in noclose_fds:
1099 except OSError: # ERROR, fd wasn't open to begin with (ignored)
1101 os.open(logfile, os.O_RDWR|os.O_CREAT|os.O_APPEND, 0600)
1102 # Duplicate standard input to standard output and standard error.
1103 os.dup2(0, 1) # standard output (1)
1104 os.dup2(0, 2) # standard error (2)
1108 def DaemonPidFileName(name):
1109 """Compute a ganeti pid file absolute path, given the daemon name.
1112 return os.path.join(constants.RUN_GANETI_DIR, "%s.pid" % name)
1115 def WritePidFile(name):
1116 """Write the current process pidfile.
1118 The file will be written to constants.RUN_GANETI_DIR/name.pid
1122 pidfilename = DaemonPidFileName(name)
1123 if IsProcessAlive(ReadPidFile(pidfilename)):
1124 raise errors.GenericError("%s contains a live process" % pidfilename)
1126 WriteFile(pidfilename, data="%d\n" % pid)
1129 def RemovePidFile(name):
1130 """Remove the current process pidfile.
1132 Any errors are ignored.
1136 pidfilename = DaemonPidFileName(name)
1137 # TODO: we could check here that the file contains our pid
1139 RemoveFile(pidfilename)
1144 def KillProcess(pid, signal_=signal.SIGTERM, timeout=30):
1145 """Kill a process given by its pid.
1148 @param pid: The PID to terminate.
1150 @param signal_: The signal to send, by default SIGTERM
1152 @param timeout: The timeout after which, if the process is still alive,
1153 a SIGKILL will be sent. If not positive, no such checking
1158 # kill with pid=0 == suicide
1159 raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
1161 if not IsProcessAlive(pid):
1163 os.kill(pid, signal_)
1166 end = time.time() + timeout
1167 while time.time() < end and IsProcessAlive(pid):
1169 if IsProcessAlive(pid):
1170 os.kill(pid, signal.SIGKILL)
1173 def FindFile(name, search_path, test=os.path.exists):
1174 """Look for a filesystem object in a given path.
1176 This is an abstract method to search for filesystem object (files,
1177 dirs) under a given search path.
1180 - name: the name to look for
1181 - search_path: list of directory names
1182 - test: the test which the full path must satisfy
1183 (defaults to os.path.exists)
1186 - full path to the item if found
1190 for dir_name in search_path:
1191 item_name = os.path.sep.join([dir_name, name])
1197 def CheckVolumeGroupSize(vglist, vgname, minsize):
1198 """Checks if the volume group list is valid.
1200 A non-None return value means there's an error, and the return value
1201 is the error message.
1204 vgsize = vglist.get(vgname, None)
1206 return "volume group '%s' missing" % vgname
1207 elif vgsize < minsize:
1208 return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
1209 (vgname, minsize, vgsize))
1213 def SplitTime(value):
1214 """Splits time as floating point number into a tuple.
1216 @param value: Time in seconds
1217 @type value: int or float
1218 @return: Tuple containing (seconds, microseconds)
1221 (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
1223 assert 0 <= seconds, \
1224 "Seconds must be larger than or equal to 0, but are %s" % seconds
1225 assert 0 <= microseconds <= 999999, \
1226 "Microseconds must be 0-999999, but are %s" % microseconds
1228 return (int(seconds), int(microseconds))
1231 def MergeTime(timetuple):
1232 """Merges a tuple into time as a floating point number.
1234 @param timetuple: Time as tuple, (seconds, microseconds)
1235 @type timetuple: tuple
1236 @return: Time as a floating point number expressed in seconds
1239 (seconds, microseconds) = timetuple
1241 assert 0 <= seconds, \
1242 "Seconds must be larger than or equal to 0, but are %s" % seconds
1243 assert 0 <= microseconds <= 999999, \
1244 "Microseconds must be 0-999999, but are %s" % microseconds
1246 return float(seconds) + (float(microseconds) * 0.000001)
1249 def GetNodeDaemonPort():
1250 """Get the node daemon port for this cluster.
1252 Note that this routine does not read a ganeti-specific file, but
1253 instead uses socket.getservbyname to allow pre-customization of
1254 this parameter outside of Ganeti.
1258 port = socket.getservbyname("ganeti-noded", "tcp")
1259 except socket.error:
1260 port = constants.DEFAULT_NODED_PORT
1265 def GetNodeDaemonPassword():
1266 """Get the node password for the cluster.
1269 return ReadFile(constants.CLUSTER_PASSWORD_FILE)
1272 def SetupLogging(logfile, debug=False, stderr_logging=False, program=""):
1273 """Configures the logging module.
1276 fmt = "%(asctime)s: " + program + " "
1278 fmt += ("pid=%(process)d/%(threadName)s %(levelname)s"
1279 " %(module)s:%(lineno)s %(message)s")
1281 fmt += "pid=%(process)d %(levelname)s %(message)s"
1282 formatter = logging.Formatter(fmt)
1284 root_logger = logging.getLogger("")
1285 root_logger.setLevel(logging.NOTSET)
1288 stderr_handler = logging.StreamHandler()
1289 stderr_handler.setFormatter(formatter)
1291 stderr_handler.setLevel(logging.NOTSET)
1293 stderr_handler.setLevel(logging.CRITICAL)
1294 root_logger.addHandler(stderr_handler)
1296 # this can fail, if the logging directories are not setup or we have
1297 # a permisssion problem; in this case, it's best to log but ignore
1298 # the error if stderr_logging is True, and if false we re-raise the
1299 # exception since otherwise we could run but without any logs at all
1301 logfile_handler = logging.FileHandler(logfile)
1302 logfile_handler.setFormatter(formatter)
1304 logfile_handler.setLevel(logging.DEBUG)
1306 logfile_handler.setLevel(logging.INFO)
1307 root_logger.addHandler(logfile_handler)
1308 except EnvironmentError, err:
1310 logging.exception("Failed to enable logging to file '%s'", logfile)
1312 # we need to re-raise the exception
1316 def LockedMethod(fn):
1317 """Synchronized object access decorator.
1319 This decorator is intended to protect access to an object using the
1320 object's own lock which is hardcoded to '_lock'.
1323 def _LockDebug(*args, **kwargs):
1325 logging.debug(*args, **kwargs)
1327 def wrapper(self, *args, **kwargs):
1328 assert hasattr(self, '_lock')
1330 _LockDebug("Waiting for %s", lock)
1333 _LockDebug("Acquired %s", lock)
1334 result = fn(self, *args, **kwargs)
1336 _LockDebug("Releasing %s", lock)
1338 _LockDebug("Released %s", lock)
1344 """Locks a file using POSIX locks.
1348 fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
1349 except IOError, err:
1350 if err.errno == errno.EAGAIN:
1351 raise errors.LockError("File already locked")
1355 class FileLock(object):
1356 """Utility class for file locks.
1359 def __init__(self, filename):
1360 self.filename = filename
1361 self.fd = open(self.filename, "w")
1371 def _flock(self, flag, blocking, timeout, errmsg):
1372 """Wrapper for fcntl.flock.
1375 @param flag: Operation flag
1376 @type blocking: bool
1377 @param blocking: Whether the operation should be done in blocking mode.
1378 @type timeout: None or float
1379 @param timeout: For how long the operation should be retried (implies
1381 @type errmsg: string
1382 @param errmsg: Error message in case operation fails.
1385 assert self.fd, "Lock was closed"
1386 assert timeout is None or timeout >= 0, \
1387 "If specified, timeout must be positive"
1389 if timeout is not None:
1390 flag |= fcntl.LOCK_NB
1391 timeout_end = time.time() + timeout
1393 # Blocking doesn't have effect with timeout
1395 flag |= fcntl.LOCK_NB
1401 fcntl.flock(self.fd, flag)
1403 except IOError, err:
1404 if err.errno in (errno.EAGAIN, ):
1405 if timeout_end is not None and time.time() < timeout_end:
1406 # Wait before trying again
1407 time.sleep(max(0.1, min(1.0, timeout)))
1409 raise errors.LockError(errmsg)
1411 logging.exception("fcntl.flock failed")
1414 def Exclusive(self, blocking=False, timeout=None):
1415 """Locks the file in exclusive mode.
1418 self._flock(fcntl.LOCK_EX, blocking, timeout,
1419 "Failed to lock %s in exclusive mode" % self.filename)
1421 def Shared(self, blocking=False, timeout=None):
1422 """Locks the file in shared mode.
1425 self._flock(fcntl.LOCK_SH, blocking, timeout,
1426 "Failed to lock %s in shared mode" % self.filename)
1428 def Unlock(self, blocking=True, timeout=None):
1429 """Unlocks the file.
1431 According to "man flock", unlocking can also be a nonblocking operation:
1432 "To make a non-blocking request, include LOCK_NB with any of the above
1436 self._flock(fcntl.LOCK_UN, blocking, timeout,
1437 "Failed to unlock %s" % self.filename)
1440 class SignalHandler(object):
1441 """Generic signal handler class.
1443 It automatically restores the original handler when deconstructed or when
1444 Reset() is called. You can either pass your own handler function in or query
1445 the "called" attribute to detect whether the signal was sent.
1448 def __init__(self, signum):
1449 """Constructs a new SignalHandler instance.
1451 @param signum: Single signal number or set of signal numbers
1454 if isinstance(signum, (int, long)):
1455 self.signum = set([signum])
1457 self.signum = set(signum)
1463 for signum in self.signum:
1465 prev_handler = signal.signal(signum, self._HandleSignal)
1467 self._previous[signum] = prev_handler
1469 # Restore previous handler
1470 signal.signal(signum, prev_handler)
1473 # Reset all handlers
1475 # Here we have a race condition: a handler may have already been called,
1476 # but there's not much we can do about it at this point.
1483 """Restore previous handler.
1486 for signum, prev_handler in self._previous.items():
1487 signal.signal(signum, prev_handler)
1488 # If successful, remove from dict
1489 del self._previous[signum]
1492 """Unsets "called" flag.
1494 This function can be used in case a signal may arrive several times.
1499 def _HandleSignal(self, signum, frame):
1500 """Actual signal handling function.
1503 # This is not nice and not absolutely atomic, but it appears to be the only
1504 # solution in Python -- there are no atomic types.