Merge branch 'next' into branch-2.1
[ganeti-local] / lib / config.py
index a6f3d69..acdca2e 100644 (file)
@@ -79,6 +79,7 @@ class ConfigWriter:
       self._cfg_file = cfg_file
     self._temporary_ids = set()
     self._temporary_drbds = {}
       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
     # 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)
       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")
         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)
     return mac
 
   @locking.ssynchronized(_config_lock, shared=1)
@@ -126,7 +128,7 @@ class ConfigWriter:
 
     """
     all_macs = self._AllMACs()
 
     """
     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):
 
   @locking.ssynchronized(_config_lock, shared=1)
   def GenerateDRBDSecret(self):
@@ -227,6 +229,36 @@ class ConfigWriter:
 
     return result
 
 
     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.
 
   def _UnlockedVerifyConfig(self):
     """Verify function.
 
@@ -239,6 +271,8 @@ class ConfigWriter:
     seen_macs = []
     ports = {}
     data = self._config_data
     seen_macs = []
     ports = {}
     data = self._config_data
+    seen_lids = []
+    seen_pids = []
     for instance_name in data.instances:
       instance = data.instances[instance_name]
       if instance.primary_node not in data.nodes:
     for instance_name in data.instances:
       instance = data.instances[instance_name]
       if instance.primary_node not in data.nodes:
@@ -273,6 +307,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()])
       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:
 
     # cluster-wide pool of free ports
     for free_port in data.cluster.tcpudp_port_pool:
@@ -439,8 +474,8 @@ class ConfigWriter:
     def _AppendUsedPorts(instance_name, disk, used):
       duplicates = []
       if disk.dev_type == constants.LD_DRBD8 and len(disk.logical_id) >= 5:
     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]:
           assert node in used, ("Node '%s' of instance '%s' not found"
                                 " in node list" % (node, instance_name))
           if port in used[node]:
@@ -652,10 +687,18 @@ class ConfigWriter:
       all_lvs = instance.MapLVsByNode()
       logging.info("Instance '%s' DISK_LAYOUT: %s", instance.name, all_lvs)
 
       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)
     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):
     self._WriteConfig()
 
   def _SetInstanceStatus(self, instance_name, status):
@@ -753,7 +796,7 @@ class ConfigWriter:
                                     self._config_data.instances.keys())
 
   def _UnlockedGetInstanceInfo(self, instance_name):
                                     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.
 
 
     This function is for internal use, when the config lock is already held.
 
@@ -765,9 +808,9 @@ class ConfigWriter:
 
   @locking.ssynchronized(_config_lock, shared=1)
   def GetInstanceInfo(self, instance_name):
 
   @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.
     an instance are taken from the live systems.
 
     @param instance_name: name of the instance, e.g.
@@ -784,7 +827,7 @@ class ConfigWriter:
     """Get the configuration of all instances.
 
     @rtype: dict
     """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
 
     """
               would GetInstanceInfo return for the node
 
     """
@@ -902,15 +945,19 @@ class ConfigWriter:
                     for node in self._UnlockedGetNodeList()])
     return my_dict
 
                     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.
 
     """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
     @rtype: tuple
     @return: tuple of (current, desired and possible)
 
     """
     mc_now = mc_max = 0
-    for node in self._config_data.nodes.itervalues():
+    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:
       if not (node.offline or node.drained):
         mc_max += 1
       if node.master_candidate:
@@ -919,16 +966,18 @@ class ConfigWriter:
     return (mc_now, mc_max)
 
   @locking.ssynchronized(_config_lock, shared=1)
     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}.
 
     """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)
 
     """
     @rtype: tuple
     @return: tuple of (current, max)
 
     """
-    return self._UnlockedGetMasterCandidateStats()
+    return self._UnlockedGetMasterCandidateStats(exceptions)
 
   @locking.ssynchronized(_config_lock)
   def MaintainCandidatePool(self):
 
   @locking.ssynchronized(_config_lock)
   def MaintainCandidatePool(self):
@@ -1023,10 +1072,12 @@ class ConfigWriter:
 
     result = rpc.RpcRunner.call_upload_file(node_list, self._cfg_file,
                                             address_list=addr_list)
 
     result = rpc.RpcRunner.call_upload_file(node_list, self._cfg_file,
                                             address_list=addr_list)
-    for node in node_list:
-      if not result[node]:
-        logging.error("copy of file %s to node %s failed",
-                      self._cfg_file, node)
+    for to_node, to_result in result.items():
+      msg = to_result.RemoteFailMsg()
+      if msg:
+        msg = ("Copy of file %s to node %s failed: %s" %
+               (self._cfg_file, to_node, msg))
+        logging.error(msg)
         bad = True
     return not bad
 
         bad = True
     return not bad
 
@@ -1061,8 +1112,14 @@ class ConfigWriter:
     # Write ssconf files on all nodes (including locally)
     if self._last_cluster_serial < self._config_data.cluster.serial_no:
       if not self._offline:
     # Write ssconf files on all nodes (including locally)
     if self._last_cluster_serial < self._config_data.cluster.serial_no:
       if not self._offline:
-        rpc.RpcRunner.call_write_ssconf_files(self._UnlockedGetNodeList(),
-                                              self._UnlockedGetSsconfValues())
+        result = rpc.RpcRunner.call_write_ssconf_files(\
+          self._UnlockedGetNodeList(),
+          self._UnlockedGetSsconfValues())
+        for nname, nresu in result.items():
+          msg = nresu.RemoteFailMsg()
+          if msg:
+            logging.warning("Error while uploading ssconf files to"
+                            " node %s: %s", nname, msg)
       self._last_cluster_serial = self._config_data.cluster.serial_no
 
   def _UnlockedGetSsconfValues(self):
       self._last_cluster_serial = self._config_data.cluster.serial_no
 
   def _UnlockedGetSsconfValues(self):
@@ -1085,8 +1142,10 @@ class ConfigWriter:
     node_data = fn(node_names)
 
     cluster = self._config_data.cluster
     node_data = fn(node_names)
 
     cluster = self._config_data.cluster
+    cluster_tags = fn(cluster.GetTags())
     return {
       constants.SS_CLUSTER_NAME: cluster.cluster_name,
     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,
       constants.SS_FILE_STORAGE_DIR: cluster.file_storage_dir,
       constants.SS_MASTER_CANDIDATES: mc_data,
       constants.SS_MASTER_IP: cluster.master_ip,
@@ -1142,13 +1201,6 @@ class ConfigWriter:
     self._WriteConfig()
 
   @locking.ssynchronized(_config_lock, shared=1)
     self._WriteConfig()
 
   @locking.ssynchronized(_config_lock, shared=1)
-  def GetDefBridge(self):
-    """Return the default bridge.
-
-    """
-    return self._config_data.cluster.default_bridge
-
-  @locking.ssynchronized(_config_lock, shared=1)
   def GetMACPrefix(self):
     """Return the mac prefix.
 
   def GetMACPrefix(self):
     """Return the mac prefix.
 
@@ -1157,7 +1209,7 @@ class ConfigWriter:
 
   @locking.ssynchronized(_config_lock, shared=1)
   def GetClusterInfo(self):
 
   @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
 
     @rtype: L{objects.Cluster}
     @return: the cluster object
@@ -1205,5 +1257,7 @@ class ConfigWriter:
 
     if isinstance(target, objects.Instance):
       self._UnlockedReleaseDRBDMinors(target.name)
 
     if isinstance(target, objects.Instance):
       self._UnlockedReleaseDRBDMinors(target.name)
+      for nic in target.nics:
+        self._temporary_macs.discard(nic.mac)
 
     self._WriteConfig()
 
     self._WriteConfig()