Fix a couple of epydoc warnings
[ganeti-local] / lib / config.py
index d7a1e9f..58871d5 100644 (file)
@@ -79,6 +79,7 @@ class ConfigWriter:
       self._cfg_file = cfg_file
     self._temporary_ids = set()
     self._temporary_drbds = {}
+    self._temporary_macs = set()
     # Note: in order to prevent errors when resolving our name in
     # _DistributeConfig, we compute it here once and reuse it; it's
     # better to raise an error before starting to modify the config
@@ -110,11 +111,12 @@ class ConfigWriter:
       byte2 = random.randrange(0, 256)
       byte3 = random.randrange(0, 256)
       mac = "%s:%02x:%02x:%02x" % (prefix, byte1, byte2, byte3)
-      if mac not in all_macs:
+      if mac not in all_macs and mac not in self._temporary_macs:
         break
       retries -= 1
     else:
       raise errors.ConfigurationError("Can't generate unique MAC")
+    self._temporary_macs.add(mac)
     return mac
 
   @locking.ssynchronized(_config_lock, shared=1)
@@ -126,7 +128,7 @@ class ConfigWriter:
 
     """
     all_macs = self._AllMACs()
-    return mac in all_macs
+    return mac in all_macs or mac in self._temporary_macs
 
   @locking.ssynchronized(_config_lock, shared=1)
   def GenerateDRBDSecret(self):
@@ -227,6 +229,36 @@ class ConfigWriter:
 
     return result
 
+  def _CheckDiskIDs(self, disk, l_ids, p_ids):
+    """Compute duplicate disk IDs
+
+    @type disk: L{objects.Disk}
+    @param disk: the disk at which to start searching
+    @type l_ids: list
+    @param l_ids: list of current logical ids
+    @type p_ids: list
+    @param p_ids: list of current physical ids
+    @rtype: list
+    @return: a list of error messages
+
+    """
+    result = []
+    if disk.logical_id is not None:
+      if disk.logical_id in l_ids:
+        result.append("duplicate logical id %s" % str(disk.logical_id))
+      else:
+        l_ids.append(disk.logical_id)
+    if disk.physical_id is not None:
+      if disk.physical_id in p_ids:
+        result.append("duplicate physical id %s" % str(disk.physical_id))
+      else:
+        p_ids.append(disk.physical_id)
+
+    if disk.children:
+      for child in disk.children:
+        result.extend(self._CheckDiskIDs(child, l_ids, p_ids))
+    return result
+
   def _UnlockedVerifyConfig(self):
     """Verify function.
 
@@ -239,6 +271,22 @@ class ConfigWriter:
     seen_macs = []
     ports = {}
     data = self._config_data
+    seen_lids = []
+    seen_pids = []
+
+    # global cluster checks
+    if not data.cluster.enabled_hypervisors:
+      result.append("enabled hypervisors list doesn't have any entries")
+    invalid_hvs = set(data.cluster.enabled_hypervisors) - constants.HYPER_TYPES
+    if invalid_hvs:
+      result.append("enabled hypervisors contains invalid entries: %s" %
+                    invalid_hvs)
+
+    if data.cluster.master_node not in data.nodes:
+      result.append("cluster has invalid primary node '%s'" %
+                    data.cluster.master_node)
+
+    # per-instance checks
     for instance_name in data.instances:
       instance = data.instances[instance_name]
       if instance.primary_node not in data.nodes:
@@ -273,6 +321,7 @@ class ConfigWriter:
       for idx, disk in enumerate(instance.disks):
         result.extend(["instance '%s' disk %d error: %s" %
                        (instance.name, idx, msg) for msg in disk.Verify()])
+        result.extend(self._CheckDiskIDs(disk, seen_lids, seen_pids))
 
     # cluster-wide pool of free ports
     for free_port in data.cluster.tcpudp_port_pool:
@@ -304,6 +353,14 @@ class ConfigWriter:
       result.append("Not enough master candidates: actual %d, target %d" %
                     (mc_now, mc_max))
 
+    # node checks
+    for node in data.nodes.values():
+      if [node.master_candidate, node.drained, node.offline].count(True) > 1:
+        result.append("Node %s state is invalid: master_candidate=%s,"
+                      " drain=%s, offline=%s" %
+                      (node.name, node.master_candidate, node.drain,
+                       node.offline))
+
     # drbd minors check
     d_map, duplicates = self._UnlockedComputeDRBDMap()
     for node, minor, instance_a, instance_b in duplicates:
@@ -431,8 +488,8 @@ class ConfigWriter:
     def _AppendUsedPorts(instance_name, disk, used):
       duplicates = []
       if disk.dev_type == constants.LD_DRBD8 and len(disk.logical_id) >= 5:
-        nodeA, nodeB, dummy, minorA, minorB = disk.logical_id[:5]
-        for node, port in ((nodeA, minorA), (nodeB, minorB)):
+        node_a, node_b, _, minor_a, minor_b = disk.logical_id[:5]
+        for node, port in ((node_a, minor_a), (node_b, minor_b)):
           assert node in used, ("Node '%s' of instance '%s' not found"
                                 " in node list" % (node, instance_name))
           if port in used[node]:
@@ -644,10 +701,18 @@ class ConfigWriter:
       all_lvs = instance.MapLVsByNode()
       logging.info("Instance '%s' DISK_LAYOUT: %s", instance.name, all_lvs)
 
+    all_macs = self._AllMACs()
+    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))
+
     instance.serial_no = 1
     self._config_data.instances[instance.name] = instance
     self._config_data.cluster.serial_no += 1
     self._UnlockedReleaseDRBDMinors(instance.name)
+    for nic in instance.nics:
+      self._temporary_macs.discard(nic.mac)
     self._WriteConfig()
 
   def _SetInstanceStatus(self, instance_name, status):
@@ -745,7 +810,7 @@ class ConfigWriter:
                                     self._config_data.instances.keys())
 
   def _UnlockedGetInstanceInfo(self, instance_name):
-    """Returns informations about an instance.
+    """Returns information about an instance.
 
     This function is for internal use, when the config lock is already held.
 
@@ -757,9 +822,9 @@ class ConfigWriter:
 
   @locking.ssynchronized(_config_lock, shared=1)
   def GetInstanceInfo(self, instance_name):
-    """Returns informations about an instance.
+    """Returns information about an instance.
 
-    It takes the information from the configuration file. Other informations of
+    It takes the information from the configuration file. Other information of
     an instance are taken from the live systems.
 
     @param instance_name: name of the instance, e.g.
@@ -776,7 +841,7 @@ class ConfigWriter:
     """Get the configuration of all instances.
 
     @rtype: dict
-    @returns: dict of (instance, instance_info), where instance_info is what
+    @return: dict of (instance, instance_info), where instance_info is what
               would GetInstanceInfo return for the node
 
     """
@@ -894,16 +959,20 @@ class ConfigWriter:
                     for node in self._UnlockedGetNodeList()])
     return my_dict
 
-  def _UnlockedGetMasterCandidateStats(self):
+  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.itervalues():
-      if not node.offline:
+    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
@@ -911,16 +980,18 @@ class ConfigWriter:
     return (mc_now, mc_max)
 
   @locking.ssynchronized(_config_lock, shared=1)
-  def GetMasterCandidateStats(self):
+  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()
+    return self._UnlockedGetMasterCandidateStats(exceptions)
 
   @locking.ssynchronized(_config_lock)
   def MaintainCandidatePool(self):
@@ -939,7 +1010,7 @@ class ConfigWriter:
         if mc_now >= mc_max:
           break
         node = self._config_data.nodes[name]
-        if node.master_candidate or node.offline:
+        if node.master_candidate or node.offline or node.drained:
           continue
         mod_list.append(node)
         node.master_candidate = True
@@ -1077,8 +1148,10 @@ class ConfigWriter:
     node_data = fn(node_names)
 
     cluster = self._config_data.cluster
+    cluster_tags = fn(cluster.GetTags())
     return {
       constants.SS_CLUSTER_NAME: cluster.cluster_name,
+      constants.SS_CLUSTER_TAGS: cluster_tags,
       constants.SS_FILE_STORAGE_DIR: cluster.file_storage_dir,
       constants.SS_MASTER_CANDIDATES: mc_data,
       constants.SS_MASTER_IP: cluster.master_ip,
@@ -1091,32 +1164,6 @@ class ConfigWriter:
       constants.SS_RELEASE_VERSION: constants.RELEASE_VERSION,
       }
 
-  @locking.ssynchronized(_config_lock)
-  def InitConfig(self, version, cluster_config, master_node_config):
-    """Create the initial cluster configuration.
-
-    It will contain the current node, which will also be the master
-    node, and no instances.
-
-    @type version: int
-    @param version: Configuration version
-    @type cluster_config: objects.Cluster
-    @param cluster_config: Cluster configuration
-    @type master_node_config: objects.Node
-    @param master_node_config: Master node configuration
-
-    """
-    nodes = {
-      master_node_config.name: master_node_config,
-      }
-
-    self._config_data = objects.ConfigData(version=version,
-                                           cluster=cluster_config,
-                                           nodes=nodes,
-                                           instances={},
-                                           serial_no=1)
-    self._WriteConfig()
-
   @locking.ssynchronized(_config_lock, shared=1)
   def GetVGName(self):
     """Return the volume group name.
@@ -1149,7 +1196,7 @@ class ConfigWriter:
 
   @locking.ssynchronized(_config_lock, shared=1)
   def GetClusterInfo(self):
-    """Returns informations about the cluster
+    """Returns information about the cluster
 
     @rtype: L{objects.Cluster}
     @return: the cluster object
@@ -1197,5 +1244,7 @@ class ConfigWriter:
 
     if isinstance(target, objects.Instance):
       self._UnlockedReleaseDRBDMinors(target.name)
+      for nic in target.nics:
+        self._temporary_macs.discard(nic.mac)
 
     self._WriteConfig()