4 # Copyright (C) 2006, 2007, 2010, 2011 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.
29 # Allow wildcard import in pylint: disable=W0401
41 from ganeti import errors
42 from ganeti import constants
43 from ganeti import compat
44 from ganeti import pathutils
46 from ganeti.utils.algo import *
47 from ganeti.utils.filelock import *
48 from ganeti.utils.hash import *
49 from ganeti.utils.io import *
50 from ganeti.utils.log import *
51 from ganeti.utils.lvm import *
52 from ganeti.utils.mlock import *
53 from ganeti.utils.nodesetup import *
54 from ganeti.utils.process import *
55 from ganeti.utils.retry import *
56 from ganeti.utils.storage import *
57 from ganeti.utils.text import *
58 from ganeti.utils.wrapper import *
59 from ganeti.utils.version import *
60 from ganeti.utils.x509 import *
63 _VALID_SERVICE_NAME_RE = re.compile("^[-_.a-zA-Z0-9]{1,128}$")
65 UUID_RE = re.compile(constants.UUID_REGEX)
68 def ForceDictType(target, key_types, allowed_values=None):
69 """Force the values of a dict to have certain types.
72 @param target: the dict to update
74 @param key_types: dict mapping target dict keys to types
75 in constants.ENFORCEABLE_TYPES
76 @type allowed_values: list
77 @keyword allowed_values: list of specially allowed values
80 if allowed_values is None:
83 if not isinstance(target, dict):
84 msg = "Expected dictionary, got '%s'" % target
85 raise errors.TypeEnforcementError(msg)
88 if key not in key_types:
89 msg = "Unknown parameter '%s'" % key
90 raise errors.TypeEnforcementError(msg)
92 if target[key] in allowed_values:
95 ktype = key_types[key]
96 if ktype not in constants.ENFORCEABLE_TYPES:
97 msg = "'%s' has non-enforceable type %s" % (key, ktype)
98 raise errors.ProgrammerError(msg)
100 if ktype in (constants.VTYPE_STRING, constants.VTYPE_MAYBE_STRING):
101 if target[key] is None and ktype == constants.VTYPE_MAYBE_STRING:
102 msg = ("'None' is not a valid Maybe value for '%s'. "
103 "Use 'VALUE_HS_NOTHING'") % (key, )
105 elif (target[key] == constants.VALUE_HS_NOTHING
106 and ktype == constants.VTYPE_MAYBE_STRING):
108 elif not isinstance(target[key], basestring):
109 if isinstance(target[key], bool) and not target[key]:
112 msg = "'%s' (value %s) is not a valid string" % (key, target[key])
113 raise errors.TypeEnforcementError(msg)
114 elif ktype == constants.VTYPE_BOOL:
115 if isinstance(target[key], basestring) and target[key]:
116 if target[key].lower() == constants.VALUE_FALSE:
118 elif target[key].lower() == constants.VALUE_TRUE:
121 msg = "'%s' (value %s) is not a valid boolean" % (key, target[key])
122 raise errors.TypeEnforcementError(msg)
127 elif ktype == constants.VTYPE_SIZE:
129 target[key] = ParseUnit(target[key])
130 except errors.UnitParseError, err:
131 msg = "'%s' (value %s) is not a valid size. error: %s" % \
132 (key, target[key], err)
133 raise errors.TypeEnforcementError(msg)
134 elif ktype == constants.VTYPE_INT:
136 target[key] = int(target[key])
137 except (ValueError, TypeError):
138 msg = "'%s' (value %s) is not a valid integer" % (key, target[key])
139 raise errors.TypeEnforcementError(msg)
142 def ValidateServiceName(name):
143 """Validate the given service name.
145 @type name: number or string
146 @param name: Service name or port specification
151 except (ValueError, TypeError):
152 # Non-numeric service name
153 valid = _VALID_SERVICE_NAME_RE.match(name)
155 # Numeric port (protocols other than TCP or UDP might need adjustments
157 valid = (numport >= 0 and numport < (1 << 16))
160 raise errors.OpPrereqError("Invalid service name '%s'" % name,
166 def _ComputeMissingKeys(key_path, options, defaults):
167 """Helper functions to compute which keys a invalid.
169 @param key_path: The current key path (if any)
170 @param options: The user provided options
171 @param defaults: The default dictionary
172 @return: A list of invalid keys
175 defaults_keys = frozenset(defaults.keys())
177 for key, value in options.items():
179 new_path = "%s/%s" % (key_path, key)
183 if key not in defaults_keys:
184 invalid.append(new_path)
185 elif isinstance(value, dict):
186 invalid.extend(_ComputeMissingKeys(new_path, value, defaults[key]))
191 def VerifyDictOptions(options, defaults):
192 """Verify a dict has only keys set which also are in the defaults dict.
194 @param options: The user provided options
195 @param defaults: The default dictionary
196 @raise error.OpPrereqError: If one of the keys is not supported
199 invalid = _ComputeMissingKeys("", options, defaults)
202 raise errors.OpPrereqError("Provided option keys not supported: %s" %
203 CommaJoin(invalid), errors.ECODE_INVAL)
206 def ListVolumeGroups():
207 """List volume groups and their size
211 Dictionary with keys volume name and values
212 the size of the volume
215 command = "vgs --noheadings --units m --nosuffix -o name,size"
216 result = RunCmd(command)
221 for line in result.stdout.splitlines():
223 name, size = line.split()
224 size = int(float(size))
225 except (IndexError, ValueError), err:
226 logging.error("Invalid output from vgs (%s): %s", err, line)
234 def BridgeExists(bridge):
235 """Check whether the given bridge exists in the system
238 @param bridge: the bridge name to check
240 @return: True if it does
243 return os.path.isdir("/sys/class/net/%s/bridge" % bridge)
246 def TryConvert(fn, val):
247 """Try to convert a value ignoring errors.
249 This function tries to apply function I{fn} to I{val}. If no
250 C{ValueError} or C{TypeError} exceptions are raised, it will return
251 the result, else it will return the original value. Any other
252 exceptions are propagated to the caller.
255 @param fn: function to apply to the value
256 @param val: the value to be converted
257 @return: The converted value if the conversion was successful,
258 otherwise the original value.
263 except (ValueError, TypeError):
268 def ParseCpuMask(cpu_mask):
269 """Parse a CPU mask definition and return the list of CPU IDs.
271 CPU mask format: comma-separated list of CPU IDs
272 or dash-separated ID ranges
273 Example: "0-2,5" -> "0,1,2,5"
276 @param cpu_mask: CPU mask definition
278 @return: list of CPU IDs
284 for range_def in cpu_mask.split(","):
285 boundaries = range_def.split("-")
286 n_elements = len(boundaries)
288 raise errors.ParseError("Invalid CPU ID range definition"
289 " (only one hyphen allowed): %s" % range_def)
291 lower = int(boundaries[0])
292 except (ValueError, TypeError), err:
293 raise errors.ParseError("Invalid CPU ID value for lower boundary of"
294 " CPU ID range: %s" % str(err))
296 higher = int(boundaries[-1])
297 except (ValueError, TypeError), err:
298 raise errors.ParseError("Invalid CPU ID value for higher boundary of"
299 " CPU ID range: %s" % str(err))
301 raise errors.ParseError("Invalid CPU ID range definition"
302 " (%d > %d): %s" % (lower, higher, range_def))
303 cpu_list.extend(range(lower, higher + 1))
307 def ParseMultiCpuMask(cpu_mask):
308 """Parse a multiple CPU mask definition and return the list of CPU IDs.
310 CPU mask format: colon-separated list of comma-separated list of CPU IDs
311 or dash-separated ID ranges, with optional "all" as CPU value
312 Example: "0-2,5:all:1,5,6:2" -> [ [ 0,1,2,5 ], [ -1 ], [ 1, 5, 6 ], [ 2 ] ]
315 @param cpu_mask: multiple CPU mask definition
316 @rtype: list of lists of int
317 @return: list of lists of CPU IDs
323 for range_def in cpu_mask.split(constants.CPU_PINNING_SEP):
324 if range_def == constants.CPU_PINNING_ALL:
325 cpu_list.append([constants.CPU_PINNING_ALL_VAL, ])
327 # Uniquify and sort the list before adding
328 cpu_list.append(sorted(set(ParseCpuMask(range_def))))
333 def GetHomeDir(user, default=None):
334 """Try to get the homedir of the given user.
336 The user can be passed either as a string (denoting the name) or as
337 an integer (denoting the user id). If the user is not found, the
338 C{default} argument is returned, which defaults to C{None}.
342 if isinstance(user, basestring):
343 result = pwd.getpwnam(user)
344 elif isinstance(user, (int, long)):
345 result = pwd.getpwuid(user)
347 raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
354 def FirstFree(seq, base=0):
355 """Returns the first non-existing integer from seq.
357 The seq argument should be a sorted list of positive integers. The
358 first time the index of an element is smaller than the element
359 value, the index will be returned.
361 The base argument is used to start at a different offset,
362 i.e. C{[3, 4, 6]} with I{offset=3} will return 5.
364 Example: C{[0, 1, 3]} will return I{2}.
367 @param seq: the sequence to be analyzed.
369 @param base: use this value as the base index of the sequence
371 @return: the first non-used index in the sequence
374 for idx, elem in enumerate(seq):
375 assert elem >= base, "Passed element is higher than base offset"
376 if elem > idx + base:
382 def SingleWaitForFdCondition(fdobj, event, timeout):
383 """Waits for a condition to occur on the socket.
385 Immediately returns at the first interruption.
387 @type fdobj: integer or object supporting a fileno() method
388 @param fdobj: entity to wait for events on
390 @param event: ORed condition (see select module)
391 @type timeout: float or None
392 @param timeout: Timeout in seconds
394 @return: None for timeout, otherwise occured conditions
397 check = (event | select.POLLPRI |
398 select.POLLNVAL | select.POLLHUP | select.POLLERR)
400 if timeout is not None:
401 # Poller object expects milliseconds
404 poller = select.poll()
405 poller.register(fdobj, event)
407 # TODO: If the main thread receives a signal and we have no timeout, we
408 # could wait forever. This should check a global "quit" flag or something
410 io_events = poller.poll(timeout)
411 except select.error, err:
412 if err[0] != errno.EINTR:
415 if io_events and io_events[0][1] & check:
416 return io_events[0][1]
421 class FdConditionWaiterHelper(object):
422 """Retry helper for WaitForFdCondition.
424 This class contains the retried and wait functions that make sure
425 WaitForFdCondition can continue waiting until the timeout is actually
430 def __init__(self, timeout):
431 self.timeout = timeout
433 def Poll(self, fdobj, event):
434 result = SingleWaitForFdCondition(fdobj, event, self.timeout)
440 def UpdateTimeout(self, timeout):
441 self.timeout = timeout
444 def WaitForFdCondition(fdobj, event, timeout):
445 """Waits for a condition to occur on the socket.
447 Retries until the timeout is expired, even if interrupted.
449 @type fdobj: integer or object supporting a fileno() method
450 @param fdobj: entity to wait for events on
452 @param event: ORed condition (see select module)
453 @type timeout: float or None
454 @param timeout: Timeout in seconds
456 @return: None for timeout, otherwise occured conditions
459 if timeout is not None:
460 retrywaiter = FdConditionWaiterHelper(timeout)
462 result = Retry(retrywaiter.Poll, RETRY_REMAINING_TIME, timeout,
463 args=(fdobj, event), wait_fn=retrywaiter.UpdateTimeout)
468 while result is None:
469 result = SingleWaitForFdCondition(fdobj, event, timeout)
473 def EnsureDaemon(name):
474 """Check for and start daemon if not alive.
477 result = RunCmd([pathutils.DAEMON_UTIL, "check-and-start", name])
479 logging.error("Can't start daemon '%s', failure %s, output: %s",
480 name, result.fail_reason, result.output)
486 def StopDaemon(name):
490 result = RunCmd([pathutils.DAEMON_UTIL, "stop", name])
492 logging.error("Can't stop daemon '%s', failure %s, output: %s",
493 name, result.fail_reason, result.output)
499 def SplitTime(value):
500 """Splits time as floating point number into a tuple.
502 @param value: Time in seconds
503 @type value: int or float
504 @return: Tuple containing (seconds, microseconds)
507 (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
509 assert 0 <= seconds, \
510 "Seconds must be larger than or equal to 0, but are %s" % seconds
511 assert 0 <= microseconds <= 999999, \
512 "Microseconds must be 0-999999, but are %s" % microseconds
514 return (int(seconds), int(microseconds))
517 def MergeTime(timetuple):
518 """Merges a tuple into time as a floating point number.
520 @param timetuple: Time as tuple, (seconds, microseconds)
521 @type timetuple: tuple
522 @return: Time as a floating point number expressed in seconds
525 (seconds, microseconds) = timetuple
527 assert 0 <= seconds, \
528 "Seconds must be larger than or equal to 0, but are %s" % seconds
529 assert 0 <= microseconds <= 999999, \
530 "Microseconds must be 0-999999, but are %s" % microseconds
532 return float(seconds) + (float(microseconds) * 0.000001)
536 """Return the current timestamp expressed as number of nanoseconds since the
539 @return: nanoseconds since the Unix epoch
542 return int(time.time() * 1000000000)
545 def FindMatch(data, name):
546 """Tries to find an item in a dictionary matching a name.
548 Callers have to ensure the data names aren't contradictory (e.g. a regexp
549 that matches a string). If the name isn't a direct key, all regular
550 expression objects in the dictionary are matched against it.
553 @param data: Dictionary containing data
555 @param name: Name to look for
556 @rtype: tuple; (value in dictionary, matched groups as list)
560 return (data[name], [])
562 for key, value in data.items():
564 if hasattr(key, "match"):
567 return (value, list(m.groups()))
572 def GetMounts(filename=constants.PROC_MOUNTS):
573 """Returns the list of mounted filesystems.
575 This function is Linux-specific.
577 @param filename: path of mounts file (/proc/mounts by default)
578 @rtype: list of tuples
579 @return: list of mount entries (device, mountpoint, fstype, options)
582 # TODO(iustin): investigate non-Linux options (e.g. via mount output)
584 mountlines = ReadFile(filename).splitlines()
585 for line in mountlines:
586 device, mountpoint, fstype, options, _ = line.split(None, 4)
587 data.append((device, mountpoint, fstype, options))
592 def SignalHandled(signums):
593 """Signal Handled decoration.
595 This special decorator installs a signal handler and then calls the target
596 function. The function must accept a 'signal_handlers' keyword argument,
597 which will contain a dict indexed by signal number, with SignalHandler
600 The decorator can be safely stacked with iself, to handle multiple signals
601 with different handlers.
604 @param signums: signals to intercept
608 def sig_function(*args, **kwargs):
609 assert "signal_handlers" not in kwargs or \
610 kwargs["signal_handlers"] is None or \
611 isinstance(kwargs["signal_handlers"], dict), \
612 "Wrong signal_handlers parameter in original function call"
613 if "signal_handlers" in kwargs and kwargs["signal_handlers"] is not None:
614 signal_handlers = kwargs["signal_handlers"]
617 kwargs["signal_handlers"] = signal_handlers
618 sighandler = SignalHandler(signums)
621 signal_handlers[sig] = sighandler
622 return fn(*args, **kwargs)
629 def TimeoutExpired(epoch, timeout, _time_fn=time.time):
630 """Checks whether a timeout has expired.
633 return _time_fn() > (epoch + timeout)
636 class SignalWakeupFd(object):
638 # This is only supported in Python 2.5 and above (some distributions
639 # backported it to Python 2.4)
640 _set_wakeup_fd_fn = signal.set_wakeup_fd
641 except AttributeError:
644 def _SetWakeupFd(self, _): # pylint: disable=R0201
648 def _SetWakeupFd(self, fd):
649 return self._set_wakeup_fd_fn(fd)
652 """Initializes this class.
655 (read_fd, write_fd) = os.pipe()
657 # Once these succeeded, the file descriptors will be closed automatically.
658 # Buffer size 0 is important, otherwise .read() with a specified length
659 # might buffer data and the file descriptors won't be marked readable.
660 self._read_fh = os.fdopen(read_fd, "r", 0)
661 self._write_fh = os.fdopen(write_fd, "w", 0)
663 self._previous = self._SetWakeupFd(self._write_fh.fileno())
666 self.fileno = self._read_fh.fileno
667 self.read = self._read_fh.read
670 """Restores the previous wakeup file descriptor.
673 if hasattr(self, "_previous") and self._previous is not None:
674 self._SetWakeupFd(self._previous)
675 self._previous = None
678 """Notifies the wakeup file descriptor.
681 self._write_fh.write(chr(0))
684 """Called before object deletion.
690 class SignalHandler(object):
691 """Generic signal handler class.
693 It automatically restores the original handler when deconstructed or
694 when L{Reset} is called. You can either pass your own handler
695 function in or query the L{called} attribute to detect whether the
699 @ivar signum: the signals we handle
700 @type called: boolean
701 @ivar called: tracks whether any of the signals have been raised
704 def __init__(self, signum, handler_fn=None, wakeup=None):
705 """Constructs a new SignalHandler instance.
707 @type signum: int or list of ints
708 @param signum: Single signal number or set of signal numbers
709 @type handler_fn: callable
710 @param handler_fn: Signal handling function
713 assert handler_fn is None or callable(handler_fn)
715 self.signum = set(signum)
718 self._handler_fn = handler_fn
719 self._wakeup = wakeup
723 for signum in self.signum:
725 prev_handler = signal.signal(signum, self._HandleSignal)
727 self._previous[signum] = prev_handler
729 # Restore previous handler
730 signal.signal(signum, prev_handler)
735 # Here we have a race condition: a handler may have already been called,
736 # but there's not much we can do about it at this point.
743 """Restore previous handler.
745 This will reset all the signals to their previous handlers.
748 for signum, prev_handler in self._previous.items():
749 signal.signal(signum, prev_handler)
750 # If successful, remove from dict
751 del self._previous[signum]
754 """Unsets the L{called} flag.
756 This function can be used in case a signal may arrive several times.
761 def _HandleSignal(self, signum, frame):
762 """Actual signal handling function.
765 # This is not nice and not absolutely atomic, but it appears to be the only
766 # solution in Python -- there are no atomic types.
770 # Notify whoever is interested in signals
771 self._wakeup.Notify()
774 self._handler_fn(signum, frame)
777 class FieldSet(object):
778 """A simple field set.
780 Among the features are:
781 - checking if a string is among a list of static string or regex objects
782 - checking if a whole list of string matches
783 - returning the matching groups from a regex match
785 Internally, all fields are held as regular expression objects.
788 def __init__(self, *items):
789 self.items = [re.compile("^%s$" % value) for value in items]
791 def Extend(self, other_set):
792 """Extend the field set with the items from another one"""
793 self.items.extend(other_set.items)
795 def Matches(self, field):
796 """Checks if a field matches the current set
799 @param field: the string to match
800 @return: either None or a regular expression match object
803 for m in itertools.ifilter(None, (val.match(field) for val in self.items)):
807 def NonMatching(self, items):
808 """Returns the list of fields not matching the current set
811 @param items: the list of fields to check
813 @return: list of non-matching fields
816 return [val for val in items if not self.Matches(val)]
819 def ValidateDeviceNames(kind, container):
820 """Validate instance device names.
822 Check that a device container contains only unique and valid names.
825 @param kind: One-word item description
826 @type container: list
827 @param container: Container containing the devices
832 for device in container:
833 if isinstance(device, dict):
835 name = device.get(constants.INIC_NAME, None)
837 name = device.get(constants.IDISK_NAME, None)
839 raise errors.OpPrereqError("Invalid container kind '%s'" % kind,
843 # Check that a device name is not the UUID of another device
844 valid.append(device.uuid)
848 except (ValueError, TypeError):
851 raise errors.OpPrereqError("Invalid name '%s'. Purely numeric %s names"
852 " are not allowed" % (name, kind),
855 if name is not None and name.lower() != constants.VALUE_NONE:
857 raise errors.OpPrereqError("%s name '%s' already used" % (kind, name),
858 errors.ECODE_NOTUNIQUE)