Add case_sensitive keyword to MatchNameComponent
[ganeti-local] / lib / config.py
index 487d480..54f8070 100644 (file)
@@ -175,19 +175,19 @@ class ConfigWriter:
     existing.update(self._AllLVs())
     existing.update(self._config_data.instances.keys())
     existing.update(self._config_data.nodes.keys())
+    existing.update([i.uuid for i in self._AllUUIDObjects() if i.uuid])
     return existing
 
-  @locking.ssynchronized(_config_lock, shared=1)
-  def GenerateUniqueID(self, exceptions=None):
-    """Generate an unique disk name.
+  def _GenerateUniqueID(self, exceptions=None):
+    """Generate an unique UUID.
 
     This checks the current node, instances and disk names for
     duplicates.
 
-    @param exceptions: a list with some other names which should be checked
-        for uniqueness (used for example when you want to get
-        more than one id at one time without adding each one in
-        turn to the config file)
+    @param exceptions: a list with some other names which should be
+        checked for uniqueness (used for example when you want to get
+        more than one id at one time without adding each one in turn
+        to the config file)
 
     @rtype: string
     @return: the unique id
@@ -207,6 +207,22 @@ class ConfigWriter:
     self._temporary_ids.add(unique_id)
     return unique_id
 
+  @locking.ssynchronized(_config_lock, shared=1)
+  def GenerateUniqueID(self, exceptions=None):
+    """Generate an unique ID.
+
+    This is just a wrapper over the unlocked version.
+
+    """
+    return self._GenerateUniqueID(exceptions=exceptions)
+
+  def _CleanupTemporaryIDs(self):
+    """Cleanups the _temporary_ids structure.
+
+    """
+    existing = self._AllIDs(include_temporary=False)
+    self._temporary_ids = self._temporary_ids - existing
+
   def _AllMACs(self):
     """Return all MACs present in the config.
 
@@ -362,7 +378,7 @@ class ConfigWriter:
       result.append("Master node is not a master candidate")
 
     # master candidate checks
-    mc_now, mc_max = self._UnlockedGetMasterCandidateStats()
+    mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats()
     if mc_now < mc_max:
       result.append("Not enough master candidates: actual %d, target %d" %
                     (mc_now, mc_max))
@@ -719,7 +735,10 @@ class ConfigWriter:
     for nic in instance.nics:
       if nic.mac in all_macs:
         raise errors.ConfigurationError("Cannot add instance %s:"
-          " MAC address '%s' already in use." % (instance.name, nic.mac))
+                                        " MAC address '%s' already in use." %
+                                        (instance.name, nic.mac))
+
+    self._EnsureUUID(instance)
 
     instance.serial_no = 1
     instance.ctime = instance.mtime = time.time()
@@ -730,6 +749,18 @@ class ConfigWriter:
       self._temporary_macs.discard(nic.mac)
     self._WriteConfig()
 
+  def _EnsureUUID(self, item):
+    """Ensures a given object has a valid UUID.
+
+    @param item: the instance or node to be checked
+
+    """
+    if not item.uuid:
+      item.uuid = self._GenerateUniqueID()
+    elif item.uuid in self._AllIDs(temporary=True):
+      raise errors.ConfigurationError("Cannot add '%s': UUID already in use" %
+                                      (item.name, item.uuid))
+
   def _SetInstanceStatus(self, instance_name, status):
     """Set the instance's status to a given value.
 
@@ -875,6 +906,8 @@ class ConfigWriter:
     """
     logging.info("Adding node %s to configuration" % node.name)
 
+    self._EnsureUUID(node)
+
     node.serial_no = 1
     node.ctime = node.mtime = time.time()
     self._config_data.nodes[node.name] = node
@@ -982,10 +1015,10 @@ class ConfigWriter:
     @type exceptions: list
     @param exceptions: if passed, list of nodes that should be ignored
     @rtype: tuple
-    @return: tuple of (current, desired and possible)
+    @return: tuple of (current, desired and possible, possible)
 
     """
-    mc_now = mc_max = 0
+    mc_now = mc_should = mc_max = 0
     for node in self._config_data.nodes.values():
       if exceptions and node.name in exceptions:
         continue
@@ -993,8 +1026,8 @@ class ConfigWriter:
         mc_max += 1
       if node.master_candidate:
         mc_now += 1
-    mc_max = min(mc_max, self._config_data.cluster.candidate_pool_size)
-    return (mc_now, mc_max)
+    mc_should = min(mc_max, self._config_data.cluster.candidate_pool_size)
+    return (mc_now, mc_should, mc_max)
 
   @locking.ssynchronized(_config_lock, shared=1)
   def GetMasterCandidateStats(self, exceptions=None):
@@ -1011,14 +1044,16 @@ class ConfigWriter:
     return self._UnlockedGetMasterCandidateStats(exceptions)
 
   @locking.ssynchronized(_config_lock)
-  def MaintainCandidatePool(self):
+  def MaintainCandidatePool(self, exceptions):
     """Try to grow the candidate pool to the desired size.
 
+    @type exceptions: list
+    @param exceptions: if passed, list of nodes that should be ignored
     @rtype: list
     @return: list with the adjusted nodes (L{objects.Node} instances)
 
     """
-    mc_now, mc_max = self._UnlockedGetMasterCandidateStats()
+    mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats(exceptions)
     mod_list = []
     if mc_now < mc_max:
       node_list = self._config_data.nodes.keys()
@@ -1027,7 +1062,8 @@ class ConfigWriter:
         if mc_now >= mc_max:
           break
         node = self._config_data.nodes[name]
-        if node.master_candidate or node.offline or node.drained:
+        if (node.master_candidate or node.offline or node.drained or
+            node.name in exceptions):
           continue
         mod_list.append(node)
         node.master_candidate = True
@@ -1050,6 +1086,14 @@ class ConfigWriter:
     self._config_data.serial_no += 1
     self._config_data.mtime = time.time()
 
+  def _AllUUIDObjects(self):
+    """Returns all objects with uuid attributes.
+
+    """
+    return (self._config_data.instances.values() +
+            self._config_data.nodes.values() +
+            [self._config_data.cluster])
+
   def _OpenConfig(self):
     """Read the config data from disk.
 
@@ -1077,6 +1121,28 @@ class ConfigWriter:
     # ssconf update
     self._last_cluster_serial = -1
 
+    # And finally run our (custom) config upgrade sequence
+    self._UpgradeConfig()
+
+  def _UpgradeConfig(self):
+    """Run upgrade steps that cannot be done purely in the objects.
+
+    This is because some data elements need uniqueness across the
+    whole configuration, etc.
+
+    @warning: this function will call L{_WriteConfig()}, so it needs
+        to either be called with the lock held or from a safe place
+        (the constructor)
+
+    """
+    modified = False
+    for item in self._AllUUIDObjects():
+      if item.uuid is None:
+        item.uuid = self._GenerateUniqueID()
+        modified = True
+    if modified:
+      self._WriteConfig()
+
   def _DistributeConfig(self):
     """Distribute the configuration to the other nodes.
 
@@ -1119,6 +1185,10 @@ class ConfigWriter:
     """Write the configuration data to persistent storage.
 
     """
+    # first, cleanup the _temporary_ids set, if an ID is now in the
+    # other objects it should be discarded to prevent unbounded growth
+    # of that structure
+    self._CleanupTemporaryIDs()
     config_errors = self._UnlockedVerifyConfig()
     if config_errors:
       raise errors.ConfigurationError("Configuration data is not"