Implement 'out' direction on allocator tests
[ganeti-local] / lib / config.py
index c5fa6bc..d8c2605 100644 (file)
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#
 #
 
 # Copyright (C) 2006, 2007 Google Inc.
 
 """Configuration management for Ganeti
 
-This module provides the interface to the ganeti cluster configuration.
-
+This module provides the interface to the Ganeti cluster configuration.
 
-The configuration data is stored on every node but is updated on the
-master only. After each update, the master distributes the data to the
-other nodes.
+The configuration data is stored on every node but is updated on the master
+only. After each update, the master distributes the data to the other nodes.
 
-Currently the data storage format is pickle as yaml was initially not
-available, then we used it but it was a memory-eating slow beast, so
-we reverted to pickle using custom Unpicklers.
+Currently, the data storage format is JSON. YAML was slow and consuming too
+much memory.
 
 """
 
 import os
-import socket
 import tempfile
 import random
 
@@ -46,28 +42,13 @@ from ganeti import constants
 from ganeti import rpc
 from ganeti import objects
 
-def _my_uuidgen():
-  """Poor-man's uuidgen using the uuidgen binary.
-
-  """
-  result = utils.RunCmd(["uuidgen", "-r"])
-  if result.failed:
-    return None
-  return result.stdout.rstrip('\n')
-
-
-try:
-  import uuid
-  _uuidgen = uuid.uuid4
-except ImportError:
-  _uuidgen = _my_uuidgen
-
 
 class ConfigWriter:
   """The interface to the cluster configuration.
 
   """
   def __init__(self, cfg_file=None, offline=False):
+    self.write_count = 0
     self._config_data = None
     self._config_time = None
     self._config_size = None
@@ -78,6 +59,11 @@ class ConfigWriter:
     else:
       self._cfg_file = cfg_file
     self._temporary_ids = 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
+    # file than after it was modified
+    self._my_hostname = utils.HostInfo().name
 
   # this method needs to be static, so that we can call it on the class
   @staticmethod
@@ -107,9 +93,21 @@ class ConfigWriter:
         break
       retries -= 1
     else:
-      raise errors.ConfigurationError, ("Can't generate unique MAC")
+      raise errors.ConfigurationError("Can't generate unique MAC")
     return mac
 
+  def IsMacInUse(self, mac):
+    """Predicate: check if the specified MAC is in use in the Ganeti cluster.
+
+    This only checks instances managed by this cluster, it does not
+    check for potential collisions elsewhere.
+
+    """
+    self._OpenConfig()
+    self._ReleaseLock()
+    all_macs = self._AllMACs()
+    return mac in all_macs
+
   def _ComputeAllLVs(self):
     """Compute the list of all LVs.
 
@@ -147,12 +145,12 @@ class ConfigWriter:
       existing.update(exceptions)
     retries = 64
     while retries > 0:
-      unique_id = _uuidgen()
+      unique_id = utils.NewUUID()
       if unique_id not in existing and unique_id is not None:
         break
     else:
-      raise errors.ConfigurationError, ("Not able generate an unique ID"
-                                        " (last tried ID: %s" % unique_id)
+      raise errors.ConfigurationError("Not able generate an unique ID"
+                                      " (last tried ID: %s" % unique_id)
     self._temporary_ids.add(unique_id)
     return unique_id
 
@@ -182,15 +180,15 @@ class ConfigWriter:
     for instance_name in data.instances:
       instance = data.instances[instance_name]
       if instance.primary_node not in data.nodes:
-        result.append("Instance '%s' has invalid primary node '%s'" %
+        result.append("instance '%s' has invalid primary node '%s'" %
                       (instance_name, instance.primary_node))
       for snode in instance.secondary_nodes:
         if snode not in data.nodes:
-          result.append("Instance '%s' has invalid secondary node '%s'" %
+          result.append("instance '%s' has invalid secondary node '%s'" %
                         (instance_name, snode))
       for idx, nic in enumerate(instance.nics):
         if nic.mac in seen_macs:
-          result.append("Instance '%s' has NIC %d mac %s duplicate" %
+          result.append("instance '%s' has NIC %d mac %s duplicate" %
                         (instance_name, idx, nic.mac))
         else:
           seen_macs.append(nic.mac)
@@ -212,11 +210,11 @@ class ConfigWriter:
 
     if disk.logical_id is None and disk.physical_id is not None:
       return
-    if disk.dev_type == "drbd":
+    if disk.dev_type in constants.LDS_DRBD:
       pnode, snode, port = disk.logical_id
       if node_name not in (pnode, snode):
-        raise errors.ConfigurationError, ("DRBD device not knowing node %s" %
-                                          node_name)
+        raise errors.ConfigurationError("DRBD device not knowing node %s" %
+                                        node_name)
       pnode_info = self.GetNodeInfo(pnode)
       snode_info = self.GetNodeInfo(snode)
       if pnode_info is None or snode_info is None:
@@ -237,7 +235,7 @@ class ConfigWriter:
 
     """
     if not isinstance(port, int):
-      raise errors.ProgrammerError, ("Invalid type passed for port")
+      raise errors.ProgrammerError("Invalid type passed for port")
 
     self._OpenConfig()
     self._config_data.cluster.tcpudp_port_pool.add(port)
@@ -267,9 +265,9 @@ class ConfigWriter:
     else:
       port = self._config_data.cluster.highest_used_port + 1
       if port >= constants.LAST_DRBD_PORT:
-        raise errors.ConfigurationError, ("The highest used port is greater"
-                                          " than %s. Aborting." %
-                                          constants.LAST_DRBD_PORT)
+        raise errors.ConfigurationError("The highest used port is greater"
+                                        " than %s. Aborting." %
+                                        constants.LAST_DRBD_PORT)
       self._config_data.cluster.highest_used_port = port
 
     self._WriteConfig()
@@ -312,8 +310,8 @@ class ConfigWriter:
     self._OpenConfig()
 
     if instance_name not in self._config_data.instances:
-      raise errors.ConfigurationError, ("Unknown instance '%s'" %
-                                        instance_name)
+      raise errors.ConfigurationError("Unknown instance '%s'" %
+                                      instance_name)
     instance = self._config_data.instances[instance_name]
     instance.status = "up"
     self._WriteConfig()
@@ -325,11 +323,27 @@ class ConfigWriter:
     self._OpenConfig()
 
     if instance_name not in self._config_data.instances:
-      raise errors.ConfigurationError, ("Unknown instance '%s'" %
-                                        instance_name)
+      raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
     del self._config_data.instances[instance_name]
     self._WriteConfig()
 
+  def RenameInstance(self, old_name, new_name):
+    """Rename an instance.
+
+    This needs to be done in ConfigWriter and not by RemoveInstance
+    combined with AddInstance as only we can guarantee an atomic
+    rename.
+
+    """
+    self._OpenConfig()
+    if old_name not in self._config_data.instances:
+      raise errors.ConfigurationError("Unknown instance '%s'" % old_name)
+    inst = self._config_data.instances[old_name]
+    del self._config_data.instances[old_name]
+    inst.name = new_name
+    self._config_data.instances[inst.name] = inst
+    self._WriteConfig()
+
   def MarkInstanceDown(self, instance_name):
     """Mark the status of an instance to down in the configuration.
 
@@ -337,8 +351,7 @@ class ConfigWriter:
     self._OpenConfig()
 
     if instance_name not in self._config_data.instances:
-      raise errors.ConfigurationError, ("Unknown instance '%s'" %
-                                        instance_name)
+      raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
     instance = self._config_data.instances[instance_name]
     instance.status = "down"
     self._WriteConfig()
@@ -404,7 +417,7 @@ class ConfigWriter:
     """
     self._OpenConfig()
     if node_name not in self._config_data.nodes:
-      raise errors.ConfigurationError, ("Unknown node '%s'" % node_name)
+      raise errors.ConfigurationError("Unknown node '%s'" % node_name)
 
     del self._config_data.nodes[node_name]
     self._WriteConfig()
@@ -467,7 +480,7 @@ class ConfigWriter:
     try:
       st = os.stat(self._cfg_file)
     except OSError, err:
-      raise errors.ConfigurationError, "Can't stat config file: %s" % err
+      raise errors.ConfigurationError("Can't stat config file: %s" % err)
     if (self._config_data is not None and
         self._config_time is not None and
         self._config_time == st.st_mtime and
@@ -478,20 +491,20 @@ class ConfigWriter:
     f = open(self._cfg_file, 'r')
     try:
       try:
-        data = objects.ConfigObject.Load(f)
+        data = objects.ConfigData.Load(f)
       except Exception, err:
-        raise errors.ConfigurationError, err
+        raise errors.ConfigurationError(err)
     finally:
       f.close()
     if (not hasattr(data, 'cluster') or
         not hasattr(data.cluster, 'config_version')):
-      raise errors.ConfigurationError, ("Incomplete configuration"
-                                        " (missing cluster.config_version)")
+      raise errors.ConfigurationError("Incomplete configuration"
+                                      " (missing cluster.config_version)")
     if data.cluster.config_version != constants.CONFIG_VERSION:
-      raise errors.ConfigurationError, ("Cluster configuration version"
-                                        " mismatch, got %s instead of %s" %
-                                        (data.cluster.config_version,
-                                         constants.CONFIG_VERSION))
+      raise errors.ConfigurationError("Cluster configuration version"
+                                      " mismatch, got %s instead of %s" %
+                                      (data.cluster.config_version,
+                                       constants.CONFIG_VERSION))
     self._config_data = data
     self._config_time = st.st_mtime
     self._config_size = st.st_size
@@ -512,7 +525,7 @@ class ConfigWriter:
       return True
     bad = False
     nodelist = self.GetNodeList()
-    myhostname = socket.gethostname()
+    myhostname = self._my_hostname
 
     tgt_list = []
     for node in nodelist:
@@ -546,11 +559,12 @@ class ConfigWriter:
       f.close()
     # we don't need to do os.close(fd) as f.close() did it
     os.rename(name, destination)
+    self.write_count += 1
     # re-set our cache as not to re-read the config file
     try:
       st = os.stat(destination)
     except OSError, err:
-      raise errors.ConfigurationError, "Can't stat config file: %s" % err
+      raise errors.ConfigurationError("Can't stat config file: %s" % err)
     self._config_time = st.st_mtime
     self._config_size = st.st_size
     self._config_inode = st.st_ino
@@ -637,8 +651,8 @@ class ConfigWriter:
 
     """
     if self._config_data is None:
-      raise errors.ProgrammerError, ("Configuration file not read,"
-                                     " cannot save.")
+      raise errors.ProgrammerError("Configuration file not read,"
+                                   " cannot save.")
     if isinstance(target, objects.Cluster):
       test = target == self._config_data.cluster
     elif isinstance(target, objects.Node):
@@ -646,9 +660,9 @@ class ConfigWriter:
     elif isinstance(target, objects.Instance):
       test = target in self._config_data.instances.values()
     else:
-      raise errors.ProgrammerError, ("Invalid object type (%s) passed to"
-                                     " ConfigWriter.Update" % type(target))
+      raise errors.ProgrammerError("Invalid object type (%s) passed to"
+                                   " ConfigWriter.Update" % type(target))
     if not test:
-      raise errors.ConfigurationError, ("Configuration updated since object"
-                                        " has been read or unknown object")
+      raise errors.ConfigurationError("Configuration updated since object"
+                                      " has been read or unknown object")
     self._WriteConfig()