Unify the “--backend-parameters” option
[ganeti-local] / lib / config.py
index 4f7efac..91621b9 100644 (file)
@@ -35,6 +35,7 @@ import os
 import tempfile
 import random
 import logging
+import time
 
 from ganeti import errors
 from ganeti import locking
@@ -273,6 +274,20 @@ class ConfigWriter:
     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:
@@ -658,7 +673,7 @@ class ConfigWriter:
     """Get the hypervisor type for this cluster.
 
     """
-    return self._config_data.cluster.default_hypervisor
+    return self._config_data.cluster.enabled_hypervisors[0]
 
   @locking.ssynchronized(_config_lock, shared=1)
   def GetHostKey(self):
@@ -694,6 +709,7 @@ class ConfigWriter:
           " MAC address '%s' already in use." % (instance.name, nic.mac))
 
     instance.serial_no = 1
+    instance.ctime = instance.mtime = time.time()
     self._config_data.instances[instance.name] = instance
     self._config_data.cluster.serial_no += 1
     self._UnlockedReleaseDRBDMinors(instance.name)
@@ -715,6 +731,7 @@ class ConfigWriter:
     if instance.admin_up != status:
       instance.admin_up = status
       instance.serial_no += 1
+      instance.mtime = time.time()
       self._WriteConfig()
 
   @locking.ssynchronized(_config_lock)
@@ -846,6 +863,7 @@ class ConfigWriter:
     logging.info("Adding node %s to configuration" % node.name)
 
     node.serial_no = 1
+    node.ctime = node.mtime = time.time()
     self._config_data.nodes[node.name] = node
     self._config_data.cluster.serial_no += 1
     self._WriteConfig()
@@ -1017,19 +1035,18 @@ class ConfigWriter:
 
     """
     self._config_data.serial_no += 1
+    self._config_data.mtime = time.time()
 
   def _OpenConfig(self):
     """Read the config data from disk.
 
     """
-    f = open(self._cfg_file, 'r')
+    raw_data = utils.ReadFile(self._cfg_file)
+
     try:
-      try:
-        data = objects.ConfigData.FromDict(serializer.Load(f.read()))
-      except Exception, err:
-        raise errors.ConfigurationError(err)
-    finally:
-      f.close()
+      data = objects.ConfigData.FromDict(serializer.Load(raw_data))
+    except Exception, err:
+      raise errors.ConfigurationError(err)
 
     # Make sure the configuration has the right version
     _ValidateConfig(data)
@@ -1072,10 +1089,12 @@ class ConfigWriter:
 
     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.fail_msg
+      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
 
@@ -1092,16 +1111,9 @@ class ConfigWriter:
       destination = self._cfg_file
     self._BumpSerialNo()
     txt = serializer.Dump(self._config_data.ToDict())
-    dir_name, file_name = os.path.split(destination)
-    fd, name = tempfile.mkstemp('.newconfig', file_name, dir_name)
-    f = os.fdopen(fd, 'w')
-    try:
-      f.write(txt)
-      os.fsync(f.fileno())
-    finally:
-      f.close()
-    # we don't need to do os.close(fd) as f.close() did it
-    os.rename(name, destination)
+
+    utils.WriteFile(destination, data=txt)
+
     self.write_count += 1
 
     # and redistribute the config file to master candidates
@@ -1110,8 +1122,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:
-        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.fail_msg
+          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):
@@ -1126,12 +1144,20 @@ class ConfigWriter:
     instance_names = utils.NiceSort(self._UnlockedGetInstanceList())
     node_names = utils.NiceSort(self._UnlockedGetNodeList())
     node_info = [self._UnlockedGetNodeInfo(name) for name in node_names]
+    node_pri_ips = ["%s %s" % (ninfo.name, ninfo.primary_ip)
+                    for ninfo in node_info]
+    node_snd_ips = ["%s %s" % (ninfo.name, ninfo.secondary_ip)
+                    for ninfo in node_info]
 
     instance_data = fn(instance_names)
     off_data = fn(node.name for node in node_info if node.offline)
     on_data = fn(node.name for node in node_info if not node.offline)
     mc_data = fn(node.name for node in node_info if node.master_candidate)
+    mc_ips_data = fn(node.primary_ip for node in node_info
+                     if node.master_candidate)
     node_data = fn(node_names)
+    node_pri_ips_data = fn(node_pri_ips)
+    node_snd_ips_data = fn(node_snd_ips)
 
     cluster = self._config_data.cluster
     cluster_tags = fn(cluster.GetTags())
@@ -1140,10 +1166,13 @@ class ConfigWriter:
       constants.SS_CLUSTER_TAGS: cluster_tags,
       constants.SS_FILE_STORAGE_DIR: cluster.file_storage_dir,
       constants.SS_MASTER_CANDIDATES: mc_data,
+      constants.SS_MASTER_CANDIDATES_IPS: mc_ips_data,
       constants.SS_MASTER_IP: cluster.master_ip,
       constants.SS_MASTER_NETDEV: cluster.master_netdev,
       constants.SS_MASTER_NODE: cluster.master_node,
       constants.SS_NODE_LIST: node_data,
+      constants.SS_NODE_PRIMARY_IPS: node_pri_ips_data,
+      constants.SS_NODE_SECONDARY_IPS: node_snd_ips_data,
       constants.SS_OFFLINE_NODES: off_data,
       constants.SS_ONLINE_NODES: on_data,
       constants.SS_INSTANCE_LIST: instance_data,
@@ -1167,13 +1196,6 @@ class ConfigWriter:
     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.
 
@@ -1223,10 +1245,12 @@ class ConfigWriter:
       raise errors.ConfigurationError("Configuration updated since object"
                                       " has been read or unknown object")
     target.serial_no += 1
+    target.mtime = now = time.time()
 
     if update_serial:
       # for node updates, we need to increase the cluster serial too
       self._config_data.cluster.serial_no += 1
+      self._config_data.cluster.mtime = now
 
     if isinstance(target, objects.Instance):
       self._UnlockedReleaseDRBDMinors(target.name)