Initial copy of RAPI filebase to the trunk
[ganeti-local] / lib / config.py
index dcd6b6b..f823a00 100644 (file)
@@ -37,12 +37,27 @@ import random
 import re
 
 from ganeti import errors
 import re
 
 from ganeti import errors
+from ganeti import locking
 from ganeti import logger
 from ganeti import utils
 from ganeti import constants
 from ganeti import rpc
 from ganeti import objects
 from ganeti import serializer
 from ganeti import logger
 from ganeti import utils
 from ganeti import constants
 from ganeti import rpc
 from ganeti import objects
 from ganeti import serializer
+from ganeti import ssconf
+
+
+_config_lock = locking.SharedLock()
+
+
+def ValidateConfig():
+  sstore = ssconf.SimpleStore()
+
+  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:
 
 
 class ConfigWriter:
@@ -51,6 +66,7 @@ class ConfigWriter:
   """
   def __init__(self, cfg_file=None, offline=False):
     self.write_count = 0
   """
   def __init__(self, cfg_file=None, offline=False):
     self.write_count = 0
+    self._lock = _config_lock
     self._config_data = None
     self._config_time = None
     self._config_size = None
     self._config_data = None
     self._config_time = None
     self._config_size = None
@@ -75,6 +91,7 @@ class ConfigWriter:
     """
     return os.path.exists(constants.CLUSTER_CONF_FILE)
 
     """
     return os.path.exists(constants.CLUSTER_CONF_FILE)
 
+  @locking.ssynchronized(_config_lock, shared=1)
   def GenerateMAC(self):
     """Generate a MAC for an instance.
 
   def GenerateMAC(self):
     """Generate a MAC for an instance.
 
@@ -82,7 +99,6 @@ class ConfigWriter:
 
     """
     self._OpenConfig()
 
     """
     self._OpenConfig()
-    self._ReleaseLock()
     prefix = self._config_data.cluster.mac_prefix
     all_macs = self._AllMACs()
     retries = 64
     prefix = self._config_data.cluster.mac_prefix
     all_macs = self._AllMACs()
     retries = 64
@@ -98,6 +114,7 @@ class ConfigWriter:
       raise errors.ConfigurationError("Can't generate unique MAC")
     return mac
 
       raise errors.ConfigurationError("Can't generate unique MAC")
     return mac
 
+  @locking.ssynchronized(_config_lock, shared=1)
   def IsMacInUse(self, mac):
     """Predicate: check if the specified MAC is in use in the Ganeti cluster.
 
   def IsMacInUse(self, mac):
     """Predicate: check if the specified MAC is in use in the Ganeti cluster.
 
@@ -106,7 +123,6 @@ class ConfigWriter:
 
     """
     self._OpenConfig()
 
     """
     self._OpenConfig()
-    self._ReleaseLock()
     all_macs = self._AllMACs()
     return mac in all_macs
 
     all_macs = self._AllMACs()
     return mac in all_macs
 
@@ -115,7 +131,6 @@ class ConfigWriter:
 
     """
     self._OpenConfig()
 
     """
     self._OpenConfig()
-    self._ReleaseLock()
     lvnames = set()
     for instance in self._config_data.instances.values():
       node_data = instance.MapLVsByNode()
     lvnames = set()
     for instance in self._config_data.instances.values():
       node_data = instance.MapLVsByNode()
@@ -123,6 +138,7 @@ class ConfigWriter:
         lvnames.update(lv_list)
     return lvnames
 
         lvnames.update(lv_list)
     return lvnames
 
+  @locking.ssynchronized(_config_lock, shared=1)
   def GenerateUniqueID(self, exceptions=None):
     """Generate an unique disk name.
 
   def GenerateUniqueID(self, exceptions=None):
     """Generate an unique disk name.
 
@@ -161,7 +177,6 @@ class ConfigWriter:
 
     """
     self._OpenConfig()
 
     """
     self._OpenConfig()
-    self._ReleaseLock()
 
     result = []
     for instance in self._config_data.instances.values():
 
     result = []
     for instance in self._config_data.instances.values():
@@ -170,11 +185,11 @@ class ConfigWriter:
 
     return result
 
 
     return result
 
+  @locking.ssynchronized(_config_lock, shared=1)
   def VerifyConfig(self):
     """Stub verify function.
     """
     self._OpenConfig()
   def VerifyConfig(self):
     """Stub verify function.
     """
     self._OpenConfig()
-    self._ReleaseLock()
 
     result = []
     seen_macs = []
 
     result = []
     seen_macs = []
@@ -196,7 +211,7 @@ class ConfigWriter:
           seen_macs.append(nic.mac)
     return result
 
           seen_macs.append(nic.mac)
     return result
 
-  def SetDiskID(self, disk, node_name):
+  def _UnlockedSetDiskID(self, disk, node_name):
     """Convert the unique ID to the ID needed on the target nodes.
 
     This is used only for drbd, which needs ip/port configuration.
     """Convert the unique ID to the ID needed on the target nodes.
 
     This is used only for drbd, which needs ip/port configuration.
@@ -205,10 +220,12 @@ class ConfigWriter:
     this helps when the only the top device is passed to the remote
     node.
 
     this helps when the only the top device is passed to the remote
     node.
 
+    This function is for internal use, when the config lock is already held.
+
     """
     if disk.children:
       for child in disk.children:
     """
     if disk.children:
       for child in disk.children:
-        self.SetDiskID(child, node_name)
+        self._UnlockedSetDiskID(child, node_name)
 
     if disk.logical_id is None and disk.physical_id is not None:
       return
 
     if disk.logical_id is None and disk.physical_id is not None:
       return
@@ -217,8 +234,8 @@ class ConfigWriter:
       if node_name not in (pnode, snode):
         raise errors.ConfigurationError("DRBD device not knowing node %s" %
                                         node_name)
       if node_name not in (pnode, snode):
         raise errors.ConfigurationError("DRBD device not knowing node %s" %
                                         node_name)
-      pnode_info = self.GetNodeInfo(pnode)
-      snode_info = self.GetNodeInfo(snode)
+      pnode_info = self._UnlockedGetNodeInfo(pnode)
+      snode_info = self._UnlockedGetNodeInfo(snode)
       if pnode_info is None or snode_info is None:
         raise errors.ConfigurationError("Can't find primary or secondary node"
                                         " for %s" % str(disk))
       if pnode_info is None or snode_info is None:
         raise errors.ConfigurationError("Can't find primary or secondary node"
                                         " for %s" % str(disk))
@@ -232,6 +249,20 @@ class ConfigWriter:
       disk.physical_id = disk.logical_id
     return
 
       disk.physical_id = disk.logical_id
     return
 
+  @locking.ssynchronized(_config_lock)
+  def SetDiskID(self, disk, node_name):
+    """Convert the unique ID to the ID needed on the target nodes.
+
+    This is used only for drbd, which needs ip/port configuration.
+
+    The routine descends down and updates its children also, because
+    this helps when the only the top device is passed to the remote
+    node.
+
+    """
+    return self._UnlockedSetDiskID(disk, node_name)
+
+  @locking.ssynchronized(_config_lock)
   def AddTcpUdpPort(self, port):
     """Adds a new port to the available port pool.
 
   def AddTcpUdpPort(self, port):
     """Adds a new port to the available port pool.
 
@@ -243,14 +274,15 @@ class ConfigWriter:
     self._config_data.cluster.tcpudp_port_pool.add(port)
     self._WriteConfig()
 
     self._config_data.cluster.tcpudp_port_pool.add(port)
     self._WriteConfig()
 
+  @locking.ssynchronized(_config_lock, shared=1)
   def GetPortList(self):
     """Returns a copy of the current port list.
 
     """
     self._OpenConfig()
   def GetPortList(self):
     """Returns a copy of the current port list.
 
     """
     self._OpenConfig()
-    self._ReleaseLock()
     return self._config_data.cluster.tcpudp_port_pool.copy()
 
     return self._config_data.cluster.tcpudp_port_pool.copy()
 
+  @locking.ssynchronized(_config_lock)
   def AllocatePort(self):
     """Allocate a port.
 
   def AllocatePort(self):
     """Allocate a port.
 
@@ -275,6 +307,7 @@ class ConfigWriter:
     self._WriteConfig()
     return port
 
     self._WriteConfig()
     return port
 
+  @locking.ssynchronized(_config_lock, shared=1)
   def GetHostKey(self):
     """Return the rsa hostkey from the config.
 
   def GetHostKey(self):
     """Return the rsa hostkey from the config.
 
@@ -283,9 +316,9 @@ class ConfigWriter:
     Returns: rsa hostkey
     """
     self._OpenConfig()
     Returns: rsa hostkey
     """
     self._OpenConfig()
-    self._ReleaseLock()
     return self._config_data.cluster.rsahostkeypub
 
     return self._config_data.cluster.rsahostkeypub
 
+  @locking.ssynchronized(_config_lock)
   def AddInstance(self, instance):
     """Add an instance to the config.
 
   def AddInstance(self, instance):
     """Add an instance to the config.
 
@@ -323,12 +356,14 @@ class ConfigWriter:
       instance.status = status
       self._WriteConfig()
 
       instance.status = status
       self._WriteConfig()
 
+  @locking.ssynchronized(_config_lock)
   def MarkInstanceUp(self, instance_name):
     """Mark the instance status to up in the config.
 
     """
     self._SetInstanceStatus(instance_name, "up")
 
   def MarkInstanceUp(self, instance_name):
     """Mark the instance status to up in the config.
 
     """
     self._SetInstanceStatus(instance_name, "up")
 
+  @locking.ssynchronized(_config_lock)
   def RemoveInstance(self, instance_name):
     """Remove the instance from the configuration.
 
   def RemoveInstance(self, instance_name):
     """Remove the instance from the configuration.
 
@@ -340,6 +375,7 @@ class ConfigWriter:
     del self._config_data.instances[instance_name]
     self._WriteConfig()
 
     del self._config_data.instances[instance_name]
     self._WriteConfig()
 
+  @locking.ssynchronized(_config_lock)
   def RenameInstance(self, old_name, new_name):
     """Rename an instance.
 
   def RenameInstance(self, old_name, new_name):
     """Rename an instance.
 
@@ -367,12 +403,14 @@ class ConfigWriter:
     self._config_data.instances[inst.name] = inst
     self._WriteConfig()
 
     self._config_data.instances[inst.name] = inst
     self._WriteConfig()
 
+  @locking.ssynchronized(_config_lock)
   def MarkInstanceDown(self, instance_name):
     """Mark the status of an instance to down in the configuration.
 
     """
     self._SetInstanceStatus(instance_name, "down")
 
   def MarkInstanceDown(self, instance_name):
     """Mark the status of an instance to down in the configuration.
 
     """
     self._SetInstanceStatus(instance_name, "down")
 
+  @locking.ssynchronized(_config_lock, shared=1)
   def GetInstanceList(self):
     """Get the list of instances.
 
   def GetInstanceList(self):
     """Get the list of instances.
 
@@ -382,20 +420,20 @@ class ConfigWriter:
 
     """
     self._OpenConfig()
 
     """
     self._OpenConfig()
-    self._ReleaseLock()
 
     return self._config_data.instances.keys()
 
 
     return self._config_data.instances.keys()
 
+  @locking.ssynchronized(_config_lock, shared=1)
   def ExpandInstanceName(self, short_name):
     """Attempt to expand an incomplete instance name.
 
     """
     self._OpenConfig()
   def ExpandInstanceName(self, short_name):
     """Attempt to expand an incomplete instance name.
 
     """
     self._OpenConfig()
-    self._ReleaseLock()
 
     return utils.MatchNameComponent(short_name,
                                     self._config_data.instances.keys())
 
 
     return utils.MatchNameComponent(short_name,
                                     self._config_data.instances.keys())
 
+  @locking.ssynchronized(_config_lock, shared=1)
   def GetInstanceInfo(self, instance_name):
     """Returns informations about an instance.
 
   def GetInstanceInfo(self, instance_name):
     """Returns informations about an instance.
 
@@ -410,13 +448,13 @@ class ConfigWriter:
 
     """
     self._OpenConfig()
 
     """
     self._OpenConfig()
-    self._ReleaseLock()
 
     if instance_name not in self._config_data.instances:
       return None
 
     return self._config_data.instances[instance_name]
 
 
     if instance_name not in self._config_data.instances:
       return None
 
     return self._config_data.instances[instance_name]
 
+  @locking.ssynchronized(_config_lock)
   def AddNode(self, node):
     """Add a node to the configuration.
 
   def AddNode(self, node):
     """Add a node to the configuration.
 
@@ -428,6 +466,7 @@ class ConfigWriter:
     self._config_data.nodes[node.name] = node
     self._WriteConfig()
 
     self._config_data.nodes[node.name] = node
     self._WriteConfig()
 
+  @locking.ssynchronized(_config_lock)
   def RemoveNode(self, node_name):
     """Remove a node from the configuration.
 
   def RemoveNode(self, node_name):
     """Remove a node from the configuration.
 
@@ -439,45 +478,67 @@ class ConfigWriter:
     del self._config_data.nodes[node_name]
     self._WriteConfig()
 
     del self._config_data.nodes[node_name]
     self._WriteConfig()
 
+  @locking.ssynchronized(_config_lock, shared=1)
   def ExpandNodeName(self, short_name):
     """Attempt to expand an incomplete instance name.
 
     """
     self._OpenConfig()
   def ExpandNodeName(self, short_name):
     """Attempt to expand an incomplete instance name.
 
     """
     self._OpenConfig()
-    self._ReleaseLock()
 
     return utils.MatchNameComponent(short_name,
                                     self._config_data.nodes.keys())
 
 
     return utils.MatchNameComponent(short_name,
                                     self._config_data.nodes.keys())
 
-  def GetNodeInfo(self, node_name):
+  def _UnlockedGetNodeInfo(self, node_name):
     """Get the configuration of a node, as stored in the config.
 
     """Get the configuration of a node, as stored in the config.
 
+    This function is for internal use, when the config lock is already held.
+
     Args: node: nodename (tuple) of the node
 
     Returns: the node object
 
     """
     self._OpenConfig()
     Args: node: nodename (tuple) of the node
 
     Returns: the node object
 
     """
     self._OpenConfig()
-    self._ReleaseLock()
 
     if node_name not in self._config_data.nodes:
       return None
 
     return self._config_data.nodes[node_name]
 
 
     if node_name not in self._config_data.nodes:
       return None
 
     return self._config_data.nodes[node_name]
 
-  def GetNodeList(self):
+
+  @locking.ssynchronized(_config_lock, shared=1)
+  def GetNodeInfo(self, node_name):
+    """Get the configuration of a node, as stored in the config.
+
+    Args: node: nodename (tuple) of the node
+
+    Returns: the node object
+
+    """
+    return self._UnlockedGetNodeInfo(node_name)
+
+  def _UnlockedGetNodeList(self):
     """Return the list of nodes which are in the configuration.
 
     """Return the list of nodes which are in the configuration.
 
+    This function is for internal use, when the config lock is already held.
+
     """
     self._OpenConfig()
     """
     self._OpenConfig()
-    self._ReleaseLock()
     return self._config_data.nodes.keys()
 
     return self._config_data.nodes.keys()
 
+
+  @locking.ssynchronized(_config_lock, shared=1)
+  def GetNodeList(self):
+    """Return the list of nodes which are in the configuration.
+
+    """
+    return self._UnlockedGetNodeList()
+
+  @locking.ssynchronized(_config_lock, shared=1)
   def DumpConfig(self):
     """Return the entire configuration of the cluster.
     """
     self._OpenConfig()
   def DumpConfig(self):
     """Return the entire configuration of the cluster.
     """
     self._OpenConfig()
-    self._ReleaseLock()
     return self._config_data
 
   def _BumpSerialNo(self):
     return self._config_data
 
   def _BumpSerialNo(self):
@@ -505,6 +566,10 @@ class ConfigWriter:
         self._config_inode == st.st_ino):
       # data is current, so skip loading of config file
       return
         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:
     f = open(self._cfg_file, 'r')
     try:
       try:
@@ -514,23 +579,14 @@ class ConfigWriter:
     finally:
       f.close()
     if (not hasattr(data, 'cluster') or
     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"
       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
     self._config_inode = st.st_ino
 
     self._config_data = data
     self._config_time = st.st_mtime
     self._config_size = st.st_size
     self._config_inode = st.st_ino
 
-  def _ReleaseLock(self):
-    """xxxx
-    """
-
   def _DistributeConfig(self):
     """Distribute the configuration to the other nodes.
 
   def _DistributeConfig(self):
     """Distribute the configuration to the other nodes.
 
@@ -541,18 +597,16 @@ class ConfigWriter:
     if self._offline:
       return True
     bad = False
     if self._offline:
       return True
     bad = False
-    nodelist = self.GetNodeList()
+    nodelist = self._UnlockedGetNodeList()
     myhostname = self._my_hostname
 
     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))
       if not result[node]:
         logger.Error("copy of file %s to node %s failed" %
                      (self._cfg_file, node))
@@ -589,6 +643,7 @@ class ConfigWriter:
     # and redistribute the config file
     self._DistributeConfig()
 
     # and redistribute the config file
     self._DistributeConfig()
 
+  @locking.ssynchronized(_config_lock)
   def InitConfig(self, node, primary_ip, secondary_ip,
                  hostkeypub, mac_prefix, vg_name, def_bridge):
     """Create the initial cluster configuration.
   def InitConfig(self, node, primary_ip, secondary_ip,
                  hostkeypub, mac_prefix, vg_name, def_bridge):
     """Create the initial cluster configuration.
@@ -604,8 +659,7 @@ class ConfigWriter:
 
     """
     hu_port = constants.FIRST_DRBD_PORT - 1
 
     """
     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,
                                    rsahostkeypub=hostkeypub,
                                    highest_used_port=hu_port,
                                    mac_prefix=mac_prefix,
@@ -622,14 +676,15 @@ class ConfigWriter:
                                            cluster=globalconfig)
     self._WriteConfig()
 
                                            cluster=globalconfig)
     self._WriteConfig()
 
+  @locking.ssynchronized(_config_lock, shared=1)
   def GetVGName(self):
     """Return the volume group name.
 
     """
     self._OpenConfig()
   def GetVGName(self):
     """Return the volume group name.
 
     """
     self._OpenConfig()
-    self._ReleaseLock()
     return self._config_data.cluster.volume_group_name
 
     return self._config_data.cluster.volume_group_name
 
+  @locking.ssynchronized(_config_lock)
   def SetVGName(self, vg_name):
     """Set the volume group name.
 
   def SetVGName(self, vg_name):
     """Set the volume group name.
 
@@ -638,22 +693,23 @@ class ConfigWriter:
     self._config_data.cluster.volume_group_name = vg_name
     self._WriteConfig()
 
     self._config_data.cluster.volume_group_name = vg_name
     self._WriteConfig()
 
+  @locking.ssynchronized(_config_lock, shared=1)
   def GetDefBridge(self):
     """Return the default bridge.
 
     """
     self._OpenConfig()
   def GetDefBridge(self):
     """Return the default bridge.
 
     """
     self._OpenConfig()
-    self._ReleaseLock()
     return self._config_data.cluster.default_bridge
 
     return self._config_data.cluster.default_bridge
 
+  @locking.ssynchronized(_config_lock, shared=1)
   def GetMACPrefix(self):
     """Return the mac prefix.
 
     """
     self._OpenConfig()
   def GetMACPrefix(self):
     """Return the mac prefix.
 
     """
     self._OpenConfig()
-    self._ReleaseLock()
     return self._config_data.cluster.mac_prefix
 
     return self._config_data.cluster.mac_prefix
 
+  @locking.ssynchronized(_config_lock, shared=1)
   def GetClusterInfo(self):
     """Returns informations about the cluster
 
   def GetClusterInfo(self):
     """Returns informations about the cluster
 
@@ -662,10 +718,10 @@ class ConfigWriter:
 
     """
     self._OpenConfig()
 
     """
     self._OpenConfig()
-    self._ReleaseLock()
 
     return self._config_data.cluster
 
 
     return self._config_data.cluster
 
+  @locking.ssynchronized(_config_lock)
   def Update(self, target):
     """Notify function to be called after updates.
 
   def Update(self, target):
     """Notify function to be called after updates.