cluster-verify checks uniformity of PV sizes
[ganeti-local] / lib / utils / __init__.py
index d808ce4..d3319c1 100644 (file)
@@ -26,52 +26,41 @@ the command line scripts.
 
 """
 
+# Allow wildcard import in pylint: disable=W0401
 
 import os
-import sys
-import time
-import subprocess
 import re
-import socket
-import tempfile
-import shutil
 import errno
 import pwd
+import time
 import itertools
 import select
-import fcntl
-import resource
 import logging
 import signal
-import datetime
-import calendar
-
-from cStringIO import StringIO
 
 from ganeti import errors
 from ganeti import constants
 from ganeti import compat
-
-from ganeti.utils.algo import * # pylint: disable-msg=W0401
-from ganeti.utils.retry import * # pylint: disable-msg=W0401
-from ganeti.utils.text import * # pylint: disable-msg=W0401
-from ganeti.utils.mlock import * # pylint: disable-msg=W0401
-from ganeti.utils.log import * # pylint: disable-msg=W0401
-from ganeti.utils.hash import * # pylint: disable-msg=W0401
-from ganeti.utils.wrapper import * # pylint: disable-msg=W0401
-from ganeti.utils.filelock import * # pylint: disable-msg=W0401
-from ganeti.utils.io import * # pylint: disable-msg=W0401
-from ganeti.utils.x509 import * # pylint: disable-msg=W0401
-from ganeti.utils.nodesetup import * # pylint: disable-msg=W0401
-from ganeti.utils.process import * # pylint: disable-msg=W0401
+from ganeti import pathutils
+
+from ganeti.utils.algo import *
+from ganeti.utils.filelock import *
+from ganeti.utils.hash import *
+from ganeti.utils.io import *
+from ganeti.utils.log import *
+from ganeti.utils.lvm import *
+from ganeti.utils.mlock import *
+from ganeti.utils.nodesetup import *
+from ganeti.utils.process import *
+from ganeti.utils.retry import *
+from ganeti.utils.text import *
+from ganeti.utils.wrapper import *
+from ganeti.utils.x509 import *
 
 
-_RANDOM_UUID_FILE = "/proc/sys/kernel/random/uuid"
-
 _VALID_SERVICE_NAME_RE = re.compile("^[-_.a-zA-Z0-9]{1,128}$")
 
-UUID_RE = re.compile('^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-'
-                     '[a-f0-9]{4}-[a-f0-9]{12}$')
+UUID_RE = re.compile(constants.UUID_REGEX)
 
 
 def ForceDictType(target, key_types, allowed_values=None):
@@ -95,7 +84,7 @@ def ForceDictType(target, key_types, allowed_values=None):
 
   for key in target:
     if key not in key_types:
-      msg = "Unknown key '%s'" % key
+      msg = "Unknown parameter '%s'" % key
       raise errors.TypeEnforcementError(msg)
 
     if target[key] in allowed_values:
@@ -111,7 +100,7 @@ def ForceDictType(target, key_types, allowed_values=None):
         pass
       elif not isinstance(target[key], basestring):
         if isinstance(target[key], bool) and not target[key]:
-          target[key] = ''
+          target[key] = ""
         else:
           msg = "'%s' (value %s) is not a valid string" % (key, target[key])
           raise errors.TypeEnforcementError(msg)
@@ -167,6 +156,46 @@ def ValidateServiceName(name):
   return name
 
 
+def _ComputeMissingKeys(key_path, options, defaults):
+  """Helper functions to compute which keys a invalid.
+
+  @param key_path: The current key path (if any)
+  @param options: The user provided options
+  @param defaults: The default dictionary
+  @return: A list of invalid keys
+
+  """
+  defaults_keys = frozenset(defaults.keys())
+  invalid = []
+  for key, value in options.items():
+    if key_path:
+      new_path = "%s/%s" % (key_path, key)
+    else:
+      new_path = key
+
+    if key not in defaults_keys:
+      invalid.append(new_path)
+    elif isinstance(value, dict):
+      invalid.extend(_ComputeMissingKeys(new_path, value, defaults[key]))
+
+  return invalid
+
+
+def VerifyDictOptions(options, defaults):
+  """Verify a dict has only keys set which also are in the defaults dict.
+
+  @param options: The user provided options
+  @param defaults: The default dictionary
+  @raise error.OpPrereqError: If one of the keys is not supported
+
+  """
+  invalid = _ComputeMissingKeys("", options, defaults)
+
+  if invalid:
+    raise errors.OpPrereqError("Provided option keys not supported: %s" %
+                               CommaJoin(invalid), errors.ECODE_INVAL)
+
+
 def ListVolumeGroups():
   """List volume groups and their size
 
@@ -268,12 +297,38 @@ def ParseCpuMask(cpu_mask):
   return cpu_list
 
 
+def ParseMultiCpuMask(cpu_mask):
+  """Parse a multiple CPU mask definition and return the list of CPU IDs.
+
+  CPU mask format: colon-separated list of comma-separated list of CPU IDs
+  or dash-separated ID ranges, with optional "all" as CPU value
+  Example: "0-2,5:all:1,5,6:2" -> [ [ 0,1,2,5 ], [ -1 ], [ 1, 5, 6 ], [ 2 ] ]
+
+  @type cpu_mask: str
+  @param cpu_mask: multiple CPU mask definition
+  @rtype: list of lists of int
+  @return: list of lists of CPU IDs
+
+  """
+  if not cpu_mask:
+    return []
+  cpu_list = []
+  for range_def in cpu_mask.split(constants.CPU_PINNING_SEP):
+    if range_def == constants.CPU_PINNING_ALL:
+      cpu_list.append([constants.CPU_PINNING_ALL_VAL, ])
+    else:
+      # Uniquify and sort the list before adding
+      cpu_list.append(sorted(set(ParseCpuMask(range_def))))
+
+  return cpu_list
+
+
 def GetHomeDir(user, default=None):
   """Try to get the homedir of the given user.
 
   The user can be passed either as a string (denoting the name) or as
   an integer (denoting the user id). If the user is not found, the
-  'default' argument is returned, which defaults to None.
+  C{default} argument is returned, which defaults to C{None}.
 
   """
   try:
@@ -289,17 +344,6 @@ def GetHomeDir(user, default=None):
   return result.pw_dir
 
 
-def NewUUID():
-  """Returns a random UUID.
-
-  @note: This is a Linux-specific method as it uses the /proc
-      filesystem.
-  @rtype: str
-
-  """
-  return ReadFile(_RANDOM_UUID_FILE, size=128).rstrip("\n")
-
-
 def FirstFree(seq, base=0):
   """Returns the first non-existing integer from seq.
 
@@ -423,7 +467,7 @@ def EnsureDaemon(name):
   """Check for and start daemon if not alive.
 
   """
-  result = RunCmd([constants.DAEMON_UTIL, "check-and-start", name])
+  result = RunCmd([pathutils.DAEMON_UTIL, "check-and-start", name])
   if result.failed:
     logging.error("Can't start daemon '%s', failure %s, output: %s",
                   name, result.fail_reason, result.output)
@@ -436,7 +480,7 @@ def StopDaemon(name):
   """Stop daemon
 
   """
-  result = RunCmd([constants.DAEMON_UTIL, "stop", name])
+  result = RunCmd([pathutils.DAEMON_UTIL, "stop", name])
   if result.failed:
     logging.error("Can't stop daemon '%s', failure %s, output: %s",
                   name, result.fail_reason, result.output)
@@ -445,31 +489,6 @@ def StopDaemon(name):
   return True
 
 
-def CheckVolumeGroupSize(vglist, vgname, minsize):
-  """Checks if the volume group list is valid.
-
-  The function will check if a given volume group is in the list of
-  volume groups and has a minimum size.
-
-  @type vglist: dict
-  @param vglist: dictionary of volume group names and their size
-  @type vgname: str
-  @param vgname: the volume group we should check
-  @type minsize: int
-  @param minsize: the minimum size we accept
-  @rtype: None or str
-  @return: None for success, otherwise the error message
-
-  """
-  vgsize = vglist.get(vgname, None)
-  if vgsize is None:
-    return "volume group '%s' missing" % vgname
-  elif vgsize < minsize:
-    return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
-            (vgname, minsize, vgsize))
-  return None
-
-
 def SplitTime(value):
   """Splits time as floating point number into a tuple.
 
@@ -570,15 +589,15 @@ def SignalHandled(signums):
   """
   def wrap(fn):
     def sig_function(*args, **kwargs):
-      assert 'signal_handlers' not in kwargs or \
-             kwargs['signal_handlers'] is None or \
-             isinstance(kwargs['signal_handlers'], dict), \
+      assert "signal_handlers" not in kwargs or \
+             kwargs["signal_handlers"] is None or \
+             isinstance(kwargs["signal_handlers"], dict), \
              "Wrong signal_handlers parameter in original function call"
-      if 'signal_handlers' in kwargs and kwargs['signal_handlers'] is not None:
-        signal_handlers = kwargs['signal_handlers']
+      if "signal_handlers" in kwargs and kwargs["signal_handlers"] is not None:
+        signal_handlers = kwargs["signal_handlers"]
       else:
         signal_handlers = {}
-        kwargs['signal_handlers'] = signal_handlers
+        kwargs["signal_handlers"] = signal_handlers
       sighandler = SignalHandler(signums)
       try:
         for sig in signums:
@@ -590,6 +609,13 @@ def SignalHandled(signums):
   return wrap
 
 
+def TimeoutExpired(epoch, timeout, _time_fn=time.time):
+  """Checks whether a timeout has expired.
+
+  """
+  return _time_fn() > (epoch + timeout)
+
+
 class SignalWakeupFd(object):
   try:
     # This is only supported in Python 2.5 and above (some distributions
@@ -597,9 +623,11 @@ class SignalWakeupFd(object):
     _set_wakeup_fd_fn = signal.set_wakeup_fd
   except AttributeError:
     # Not supported
-    def _SetWakeupFd(self, _): # pylint: disable-msg=R0201
+
+    def _SetWakeupFd(self, _): # pylint: disable=R0201
       return -1
   else:
+
     def _SetWakeupFd(self, fd):
       return self._set_wakeup_fd_fn(fd)