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
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.
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))
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()
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.
"""
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
@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
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):
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()
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
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.
# 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.
"""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"