+ def _UnlockedGetMasterCandidateStats(self, exceptions=None):
+ """Get the number of current and maximum desired and possible candidates.
+
+ @type exceptions: list
+ @param exceptions: if passed, list of nodes that should be ignored
+ @rtype: tuple
+ @return: tuple of (current, desired and possible)
+
+ """
+ mc_now = mc_max = 0
+ for node in self._config_data.nodes.values():
+ if exceptions and node.name in exceptions:
+ continue
+ if not (node.offline or node.drained):
+ 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)
+
+ @locking.ssynchronized(_config_lock, shared=1)
+ def GetMasterCandidateStats(self, exceptions=None):
+ """Get the number of current and maximum possible candidates.
+
+ This is just a wrapper over L{_UnlockedGetMasterCandidateStats}.
+
+ @type exceptions: list
+ @param exceptions: if passed, list of nodes that should be ignored
+ @rtype: tuple
+ @return: tuple of (current, max)
+
+ """
+ return self._UnlockedGetMasterCandidateStats(exceptions)
+
+ @locking.ssynchronized(_config_lock)
+ def MaintainCandidatePool(self):
+ """Try to grow the candidate pool to the desired size.
+
+ @rtype: list
+ @return: list with the adjusted nodes (L{objects.Node} instances)
+
+ """
+ mc_now, mc_max = self._UnlockedGetMasterCandidateStats()
+ mod_list = []
+ if mc_now < mc_max:
+ node_list = self._config_data.nodes.keys()
+ random.shuffle(node_list)
+ for name in node_list:
+ if mc_now >= mc_max:
+ break
+ node = self._config_data.nodes[name]
+ if node.master_candidate or node.offline or node.drained:
+ continue
+ mod_list.append(node)
+ node.master_candidate = True
+ node.serial_no += 1
+ mc_now += 1
+ if mc_now != mc_max:
+ # this should not happen
+ logging.warning("Warning: MaintainCandidatePool didn't manage to"
+ " fill the candidate pool (%d/%d)", mc_now, mc_max)
+ if mod_list:
+ self._config_data.cluster.serial_no += 1
+ self._WriteConfig()
+
+ return mod_list
+