KVM: Abstract runtime file removal in a function
[ganeti-local] / lib / ssconf.py
index b8189d9..cce1141 100644 (file)
@@ -27,6 +27,7 @@ configuration data, which is mostly static and available to all nodes.
 """
 
 import sys
+import re
 
 from ganeti import errors
 from ganeti import constants
@@ -36,6 +37,8 @@ from ganeti import serializer
 
 SSCONF_LOCK_TIMEOUT = 10
 
+RE_VALID_SSCONF_NAME = re.compile(r'^[-_a-z0-9]+$')
+
 
 class SimpleConfigReader(object):
   """Simple class to read configuration file.
@@ -109,38 +112,143 @@ class SimpleConfigWriter(SimpleConfigReader):
                     mode=0600)
 
 
-def _SsconfPath(name):
-  return "%s/ssconf_%s" % (constants.DATA_DIR, name)
-
+class SimpleStore(object):
+  """Interface to static cluster data.
 
-def WriteSsconfFiles(file_name):
-  """Writes legacy ssconf files to be used by external scripts.
+  This is different that the config.ConfigWriter and
+  SimpleConfigReader classes in that it holds data that will always be
+  present, even on nodes which don't have all the cluster data.
 
-  @type file_name: string
-  @param file_name: Path to configuration file
+  Other particularities of the datastore:
+    - keys are restricted to predefined values
 
   """
-  ssconf_lock = utils.FileLock(constants.SSCONF_LOCK_FILE)
+  _SS_FILEPREFIX = "ssconf_"
+  _VALID_KEYS = (
+    constants.SS_CLUSTER_NAME,
+    constants.SS_CLUSTER_TAGS,
+    constants.SS_FILE_STORAGE_DIR,
+    constants.SS_MASTER_CANDIDATES,
+    constants.SS_MASTER_IP,
+    constants.SS_MASTER_NETDEV,
+    constants.SS_MASTER_NODE,
+    constants.SS_NODE_LIST,
+    constants.SS_OFFLINE_NODES,
+    constants.SS_ONLINE_NODES,
+    constants.SS_INSTANCE_LIST,
+    constants.SS_RELEASE_VERSION,
+    )
+  _MAX_SIZE = 131072
+
+  def __init__(self, cfg_location=None):
+    if cfg_location is None:
+      self._cfg_dir = constants.DATA_DIR
+    else:
+      self._cfg_dir = cfg_location
+
+  def KeyToFilename(self, key):
+    """Convert a given key into filename.
 
-  # Read config
-  cfg = SimpleConfigReader(file_name=file_name)
+    """
+    if key not in self._VALID_KEYS:
+      raise errors.ProgrammerError("Invalid key requested from SSConf: '%s'"
+                                   % str(key))
 
-  # Get lock while writing files
-  ssconf_lock.Exclusive(blocking=True, timeout=SSCONF_LOCK_TIMEOUT)
-  try:
-    utils.WriteFile(_SsconfPath("cluster_name"),
-                    data="%s\n" % cfg.GetClusterName())
+    filename = self._cfg_dir + '/' + self._SS_FILEPREFIX + key
+    return filename
+
+  def _ReadFile(self, key):
+    """Generic routine to read keys.
+
+    This will read the file which holds the value requested. Errors
+    will be changed into ConfigurationErrors.
+
+    """
+    filename = self.KeyToFilename(key)
+    try:
+      fh = file(filename, 'r')
+      try:
+        data = fh.read(self._MAX_SIZE)
+        data = data.rstrip('\n')
+      finally:
+        fh.close()
+    except EnvironmentError, err:
+      raise errors.ConfigurationError("Can't read from the ssconf file:"
+                                      " '%s'" % str(err))
+    return data
+
+  def WriteFiles(self, values):
+    """Writes ssconf files used by external scripts.
+
+    @type values: dict
+    @param values: Dictionary of (name, value)
+
+    """
+    ssconf_lock = utils.FileLock(constants.SSCONF_LOCK_FILE)
+
+    # Get lock while writing files
+    ssconf_lock.Exclusive(blocking=True, timeout=SSCONF_LOCK_TIMEOUT)
+    try:
+      for name, value in values.iteritems():
+        if value and not value.endswith("\n"):
+          value += "\n"
+        utils.WriteFile(self.KeyToFilename(name), data=value, mode=0444)
+    finally:
+      ssconf_lock.Unlock()
+
+  def GetFileList(self):
+    """Return the list of all config files.
+
+    This is used for computing node replication data.
+
+    """
+    return [self.KeyToFilename(key) for key in self._VALID_KEYS]
+
+  def GetClusterName(self):
+    """Get the cluster name.
+
+    """
+    return self._ReadFile(constants.SS_CLUSTER_NAME)
+
+  def GetFileStorageDir(self):
+    """Get the file storage dir.
+
+    """
+    return self._ReadFile(constants.SS_FILE_STORAGE_DIR)
+
+  def GetMasterCandidates(self):
+    """Return the list of master candidates.
+
+    """
+    data = self._ReadFile(constants.SS_MASTER_CANDIDATES)
+    nl = data.splitlines(False)
+    return nl
+
+  def GetMasterIP(self):
+    """Get the IP of the master node for this cluster.
+
+    """
+    return self._ReadFile(constants.SS_MASTER_IP)
 
-    utils.WriteFile(_SsconfPath("master_ip"),
-                    data="%s\n" % cfg.GetMasterIP())
+  def GetMasterNetdev(self):
+    """Get the netdev to which we'll add the master ip.
+
+    """
+    return self._ReadFile(constants.SS_MASTER_NETDEV)
+
+  def GetMasterNode(self):
+    """Get the hostname of the master node for this cluster.
 
-    utils.WriteFile(_SsconfPath("master_netdev"),
-                    data="%s\n" % cfg.GetMasterNetdev())
+    """
+    return self._ReadFile(constants.SS_MASTER_NODE)
 
-    utils.WriteFile(_SsconfPath("master_node"),
-                    data="%s\n" % cfg.GetMasterNode())
-  finally:
-    ssconf_lock.Unlock()
+  def GetNodeList(self):
+    """Return the list of cluster nodes.
+
+    """
+    data = self._ReadFile(constants.SS_NODE_LIST)
+    nl = data.splitlines(False)
+    return nl
 
 
 def GetMasterAndMyself(ss=None):
@@ -152,9 +260,14 @@ def GetMasterAndMyself(ss=None):
   The function does not handle any errors, these should be handled in
   the caller (errors.ConfigurationError, errors.ResolverError).
 
+  @param ss: either a sstore.SimpleConfigReader or a
+      sstore.SimpleStore instance
+  @rtype: tuple
+  @return: a tuple (master node name, my own name)
+
   """
   if ss is None:
-    ss = SimpleConfigReader()
+    ss = SimpleStore()
   return ss.GetMasterNode(), utils.HostInfo().name