IAllocator: fix breakage for non-vm_capable nodes
[ganeti-local] / lib / utils.py
index 9535d53..286ef2b 100644 (file)
@@ -100,6 +100,18 @@ _MAC_CHECK = re.compile("^([0-9a-f]{2}:){5}[0-9a-f]{2}$", re.I)
  _TIMEOUT_TERM,
  _TIMEOUT_KILL) = range(3)
 
+#: Shell param checker regexp
+_SHELLPARAM_REGEX = re.compile(r"^[-a-zA-Z0-9._+/:%@]+$")
+
+#: Unit checker regexp
+_PARSEUNIT_REGEX = re.compile(r"^([.\d]+)\s*([a-zA-Z]+)?$")
+
+#: ASN1 time regexp
+_ASN1_TIME_REGEX = re.compile(r"^(\d+)([-+]\d\d)(\d\d)$")
+
+_SORTER_RE = re.compile("^%s(.*)$" % (8 * "(\D+|\d+)?"))
+_SORTER_DIGIT = re.compile("^\d+$")
+
 
 class RunResult(object):
   """Holds the result of running external programs.
@@ -567,21 +579,17 @@ def _RunCmdPipe(cmd, env, via_shell, cwd, interactive, timeout,
 
     while fdmap:
       if poll_timeout:
-        current_timeout = poll_timeout() * 1000
-        if current_timeout < 0:
+        pt = poll_timeout() * 1000
+        if pt < 0:
           if linger_timeout is None:
             logging.warning(msg_timeout)
             if child.poll() is None:
               timeout_action = _TIMEOUT_TERM
               IgnoreProcessNotFound(os.kill, child.pid, signal.SIGTERM)
             linger_timeout = RunningTimeout(_linger_timeout, True).Remaining
-          lt = linger_timeout() * 1000
-          if lt < 0:
+          pt = linger_timeout() * 1000
+          if pt < 0:
             break
-
-          pt = max(0, lt)
-        else:
-          pt = current_timeout
       else:
         pt = None
 
@@ -1274,7 +1282,25 @@ def BridgeExists(bridge):
   return os.path.isdir("/sys/class/net/%s/bridge" % bridge)
 
 
-def NiceSort(name_list):
+def _NiceSortTryInt(val):
+  """Attempts to convert a string to an integer.
+
+  """
+  if val and _SORTER_DIGIT.match(val):
+    return int(val)
+  else:
+    return val
+
+
+def _NiceSortKey(value):
+  """Extract key for sorting.
+
+  """
+  return [_NiceSortTryInt(grp)
+          for grp in _SORTER_RE.match(value).groups()]
+
+
+def NiceSort(values, key=None):
   """Sort a list of strings based on digit and non-digit groupings.
 
   Given a list of names C{['a1', 'a10', 'a11', 'a2']} this function
@@ -1285,30 +1311,21 @@ def NiceSort(name_list):
   or no-digits. Only the first eight such groups are considered, and
   after that we just use what's left of the string.
 
-  @type name_list: list
-  @param name_list: the names to be sorted
+  @type values: list
+  @param values: the names to be sorted
+  @type key: callable or None
+  @param key: function of one argument to extract a comparison key from each
+    list element, must return string
   @rtype: list
   @return: a copy of the name list sorted with our algorithm
 
   """
-  _SORTER_BASE = "(\D+|\d+)"
-  _SORTER_FULL = "^%s%s?%s?%s?%s?%s?%s?%s?.*$" % (_SORTER_BASE, _SORTER_BASE,
-                                                  _SORTER_BASE, _SORTER_BASE,
-                                                  _SORTER_BASE, _SORTER_BASE,
-                                                  _SORTER_BASE, _SORTER_BASE)
-  _SORTER_RE = re.compile(_SORTER_FULL)
-  _SORTER_NODIGIT = re.compile("^\D*$")
-  def _TryInt(val):
-    """Attempts to convert a variable to integer."""
-    if val is None or _SORTER_NODIGIT.match(val):
-      return val
-    rval = int(val)
-    return rval
+  if key is None:
+    keyfunc = _NiceSortKey
+  else:
+    keyfunc = lambda value: _NiceSortKey(key(value))
 
-  to_sort = [([_TryInt(grp) for grp in _SORTER_RE.match(name).groups()], name)
-             for name in name_list]
-  to_sort.sort()
-  return [tup[1] for tup in to_sort]
+  return sorted(values, key=keyfunc)
 
 
 def TryConvert(fn, val):
@@ -1349,7 +1366,7 @@ def IsValidShellParam(word):
   @return: True if the word is 'safe'
 
   """
-  return bool(re.match("^[-a-zA-Z0-9._+/:%@]+$", word))
+  return bool(_SHELLPARAM_REGEX.match(word))
 
 
 def BuildShellCmd(template, *args):
@@ -1418,7 +1435,7 @@ def ParseUnit(input_string):
   is always an int in MiB.
 
   """
-  m = re.match('^([.\d]+)\s*([a-zA-Z]+)?$', str(input_string))
+  m = _PARSEUNIT_REGEX.match(str(input_string))
   if not m:
     raise errors.UnitParseError("Invalid format")
 
@@ -1663,8 +1680,8 @@ def RemoveHostFromEtcHosts(hostname):
 def TimestampForFilename():
   """Returns the current time formatted for filenames.
 
-  The format doesn't contain colons as some shells and applications them as
-  separators.
+  The format doesn't contain colons as some shells and applications treat them
+  as separators. Uses the local timezone.
 
   """
   return time.strftime("%Y-%m-%d_%H_%M_%S")
@@ -2191,6 +2208,29 @@ def UniqueSequence(seq):
   return [i for i in seq if i not in seen and not seen.add(i)]
 
 
+def FindDuplicates(seq):
+  """Identifies duplicates in a list.
+
+  Does not preserve element order.
+
+  @type seq: sequence
+  @param seq: Sequence with source elements
+  @rtype: list
+  @return: List of duplicate elements from seq
+
+  """
+  dup = set()
+  seen = set()
+
+  for item in seq:
+    if item in seen:
+      dup.add(item)
+    else:
+      seen.add(item)
+
+  return list(dup)
+
+
 def NormalizeAndValidateMac(mac):
   """Normalizes and check if a MAC address is valid.
 
@@ -2795,21 +2835,15 @@ def TailFile(fname, lines=20):
   return rows[-lines:]
 
 
-def FormatTimestampWithTZ(secs):
-  """Formats a Unix timestamp with the local timezone.
-
-  """
-  return time.strftime("%F %T %Z", time.gmtime(secs))
-
-
 def _ParseAsn1Generalizedtime(value):
   """Parses an ASN1 GENERALIZEDTIME timestamp as used by pyOpenSSL.
 
   @type value: string
   @param value: ASN1 GENERALIZEDTIME timestamp
+  @return: Seconds since the Epoch (1970-01-01 00:00:00 UTC)
 
   """
-  m = re.match(r"^(\d+)([-+]\d\d)(\d\d)$", value)
+  m = _ASN1_TIME_REGEX.match(value)
   if m:
     # We have an offset
     asn1time = m.group(1)
@@ -2888,19 +2922,18 @@ def _VerifyCertificateInner(expired, not_before, not_after, now,
 
     if not_before is not None and not_after is not None:
       msg += (" (valid from %s to %s)" %
-              (FormatTimestampWithTZ(not_before),
-               FormatTimestampWithTZ(not_after)))
+              (FormatTime(not_before), FormatTime(not_after)))
     elif not_before is not None:
-      msg += " (valid from %s)" % FormatTimestampWithTZ(not_before)
+      msg += " (valid from %s)" % FormatTime(not_before)
     elif not_after is not None:
-      msg += " (valid until %s)" % FormatTimestampWithTZ(not_after)
+      msg += " (valid until %s)" % FormatTime(not_after)
 
     return (CERT_ERROR, msg)
 
   elif not_before is not None and not_before > now:
     return (CERT_WARNING,
             "Certificate not yet valid (valid from %s)" %
-            FormatTimestampWithTZ(not_before))
+            FormatTime(not_before))
 
   elif not_after is not None:
     remaining_days = int((not_after - now) / (24 * 3600))
@@ -3326,7 +3359,8 @@ def FormatTime(val):
   """Formats a time value.
 
   @type val: float or None
-  @param val: the timestamp as returned by time.time()
+  @param val: Timestamp as returned by time.time() (seconds since Epoch,
+    1970-01-01 00:00:00 UTC)
   @return: a string value or N/A if we don't have a valid timestamp
 
   """