Add a FirstFree function to utils.py
[ganeti-local] / lib / config.py
index 5cd8c58..bf06d48 100644 (file)
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#
 #
 
 # Copyright (C) 2006, 2007 Google Inc.
@@ -34,6 +34,7 @@ much memory.
 import os
 import tempfile
 import random
+import re
 
 from ganeti import errors
 from ganeti import logger
@@ -41,23 +42,18 @@ from ganeti import utils
 from ganeti import constants
 from ganeti import rpc
 from ganeti import objects
+from ganeti import serializer
+from ganeti import ssconf
 
 
-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')
+def ValidateConfig():
+  sstore = ssconf.SimpleStore()
 
-
-try:
-  import uuid
-  _uuidgen = uuid.uuid4
-except ImportError:
-  _uuidgen = _my_uuidgen
+  if sstore.GetConfigVersion() != constants.CONFIG_VERSION:
+    raise errors.ConfigurationError("Cluster configuration version"
+                                    " mismatch, got %s instead of %s" %
+                                    (sstore.GetConfigVersion(),
+                                     constants.CONFIG_VERSION))
 
 
 class ConfigWriter:
@@ -65,6 +61,7 @@ class ConfigWriter:
 
   """
   def __init__(self, cfg_file=None, offline=False):
+    self.write_count = 0
     self._config_data = None
     self._config_time = None
     self._config_size = None
@@ -112,6 +109,18 @@ class ConfigWriter:
       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.
 
@@ -149,7 +158,7 @@ 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:
@@ -184,15 +193,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)
@@ -214,7 +223,7 @@ 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" %
@@ -307,18 +316,29 @@ class ConfigWriter:
     self._config_data.instances[instance.name] = instance
     self._WriteConfig()
 
-  def MarkInstanceUp(self, instance_name):
-    """Mark the instance status to up in the config.
+  def _SetInstanceStatus(self, instance_name, status):
+    """Set the instance's status to a given value.
 
     """
+    if status not in ("up", "down"):
+      raise errors.ProgrammerError("Invalid status '%s' passed to"
+                                   " ConfigWriter._SetInstanceStatus()" %
+                                   status)
     self._OpenConfig()
 
     if instance_name not in self._config_data.instances:
       raise errors.ConfigurationError("Unknown instance '%s'" %
                                       instance_name)
     instance = self._config_data.instances[instance_name]
-    instance.status = "up"
-    self._WriteConfig()
+    if instance.status != status:
+      instance.status = status
+      self._WriteConfig()
+
+  def MarkInstanceUp(self, instance_name):
+    """Mark the instance status to up in the config.
+
+    """
+    self._SetInstanceStatus(instance_name, "up")
 
   def RemoveInstance(self, instance_name):
     """Remove the instance from the configuration.
@@ -345,6 +365,16 @@ class ConfigWriter:
     inst = self._config_data.instances[old_name]
     del self._config_data.instances[old_name]
     inst.name = new_name
+
+    for disk in inst.disks:
+      if disk.dev_type == constants.LD_FILE:
+        # rename the file paths in logical and physical id
+        file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1]))
+        disk.physical_id = disk.logical_id = (disk.logical_id[0],
+                                              os.path.join(file_storage_dir,
+                                                           inst.name,
+                                                           disk.iv_name))
+
     self._config_data.instances[inst.name] = inst
     self._WriteConfig()
 
@@ -352,13 +382,7 @@ class ConfigWriter:
     """Mark the status of an instance to down in the configuration.
 
     """
-    self._OpenConfig()
-
-    if instance_name not in self._config_data.instances:
-      raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
-    instance = self._config_data.instances[instance_name]
-    instance.status = "down"
-    self._WriteConfig()
+    self._SetInstanceStatus(instance_name, "down")
 
   def GetInstanceList(self):
     """Get the list of instances.
@@ -492,24 +516,22 @@ class ConfigWriter:
         self._config_inode == st.st_ino):
       # data is current, so skip loading of config file
       return
+
+    # Make sure the configuration has the right version
+    ValidateConfig()
+
     f = open(self._cfg_file, 'r')
     try:
       try:
-        data = objects.ConfigData.Load(f)
+        data = objects.ConfigData.FromDict(serializer.Load(f.read()))
       except Exception, err:
-        raise
         raise errors.ConfigurationError(err)
     finally:
       f.close()
     if (not hasattr(data, 'cluster') or
-        not hasattr(data.cluster, 'config_version')):
+        not hasattr(data.cluster, 'rsahostkeypub')):
       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))
+                                      " (missing cluster.rsahostkeypub)")
     self._config_data = data
     self._config_time = st.st_mtime
     self._config_size = st.st_size
@@ -532,15 +554,13 @@ class ConfigWriter:
     nodelist = self.GetNodeList()
     myhostname = self._my_hostname
 
-    tgt_list = []
-    for node in nodelist:
-      nodeinfo = self.GetNodeInfo(node)
-      if nodeinfo.name == myhostname:
-        continue
-      tgt_list.append(node)
+    try:
+      nodelist.remove(myhostname)
+    except ValueError:
+      pass
 
-    result = rpc.call_upload_file(tgt_list, self._cfg_file)
-    for node in tgt_list:
+    result = rpc.call_upload_file(nodelist, self._cfg_file)
+    for node in nodelist:
       if not result[node]:
         logger.Error("copy of file %s to node %s failed" %
                      (self._cfg_file, node))
@@ -554,16 +574,18 @@ class ConfigWriter:
     if destination is None:
       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:
-      self._config_data.Dump(f)
+      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)
+    self.write_count += 1
     # re-set our cache as not to re-read the config file
     try:
       st = os.stat(destination)
@@ -590,8 +612,7 @@ class ConfigWriter:
 
     """
     hu_port = constants.FIRST_DRBD_PORT - 1
-    globalconfig = objects.Cluster(config_version=constants.CONFIG_VERSION,
-                                   serial_no=1,
+    globalconfig = objects.Cluster(serial_no=1,
                                    rsahostkeypub=hostkeypub,
                                    highest_used_port=hu_port,
                                    mac_prefix=mac_prefix,
@@ -616,6 +637,14 @@ class ConfigWriter:
     self._ReleaseLock()
     return self._config_data.cluster.volume_group_name
 
+  def SetVGName(self, vg_name):
+    """Set the volume group name.
+
+    """
+    self._OpenConfig()
+    self._config_data.cluster.volume_group_name = vg_name
+    self._WriteConfig()
+
   def GetDefBridge(self):
     """Return the default bridge.