Core shared file storage support
authorApollon Oikonomopoulos <apollon@noc.grnet.gr>
Mon, 28 Feb 2011 11:51:03 +0000 (13:51 +0200)
committerIustin Pop <iustin@google.com>
Tue, 1 Mar 2011 17:11:21 +0000 (18:11 +0100)
This patch introduces core file storage support, consisting of the following:

A configure-time switch for enabling/disabling shared file storage
support and controlling the shared file storage location:
--with-shared-file-storage-dir=.  Shared file storage configuration is then
available as _autoconf.ENABLE_SHARED_FILE_STORAGE and
_autoconf.SHARED_FILE_STORAGE_DIR and there is a cluster-wide ssconf
key named "shared_file_storage_dir" for changing the file location.

A new disk template named "sharedfile" (DT_SHARED_FILE), using
ganeti.bdev.FileStorage.

Auxiliary functions in lib/config.py to handle shared file storage.

Signed-off-by: Apollon Oikonomopoulos <apollon@noc.grnet.gr>
[iustin@google.com: small style fixes]
Signed-off-by: Iustin Pop <iustin@google.com>
Reviewed-by: Iustin Pop <iustin@google.com>

13 files changed:
Makefile.am
configure.ac
lib/backend.py
lib/bdev.py
lib/bootstrap.py
lib/cli.py
lib/client/gnt_cluster.py
lib/cmdlib.py
lib/config.py
lib/constants.py
lib/objects.py
lib/opcodes.py
lib/ssconf.py

index 77238f5..81c1dfb 100644 (file)
@@ -712,6 +712,8 @@ lib/_autoconf.py: Makefile vcs-version | lib/.dir
          echo "XEN_INITRD = '$(XEN_INITRD)'"; \
          echo "FILE_STORAGE_DIR = '$(FILE_STORAGE_DIR)'"; \
          echo "ENABLE_FILE_STORAGE = $(ENABLE_FILE_STORAGE)"; \
+         echo "SHARED_FILE_STORAGE_DIR = '$(SHARED_FILE_STORAGE_DIR)'"; \
+         echo "ENABLE_SHARED_FILE_STORAGE = $(ENABLE_SHARED_FILE_STORAGE)"; \
          echo "IALLOCATOR_SEARCH_PATH = [$(IALLOCATOR_SEARCH_PATH)]"; \
          echo "KVM_PATH = '$(KVM_PATH)'"; \
          echo "SOCAT_PATH = '$(SOCAT)'"; \
index de6642b..e08864e 100644 (file)
@@ -115,6 +115,23 @@ AC_ARG_WITH([file-storage-dir],
 AC_SUBST(FILE_STORAGE_DIR, $file_storage_dir)
 AC_SUBST(ENABLE_FILE_STORAGE, $enable_file_storage)
 
+# --with-shared-file-storage-dir=...
+AC_ARG_WITH([shared-file-storage-dir],
+  [AS_HELP_STRING([--with-shared-file-storage-dir=PATH],
+    [directory to store files for shared file-based backend]
+    [ (default is /srv/ganeti/shared-file-storage)]
+  )],
+  [[shared_file_storage_dir="$withval";
+    if test "$withval" != no; then
+      enable_shared_file_storage=True
+    else
+      enable_shared_file_storage=False
+    fi
+  ]],
+  [[shared_file_storage_dir="/srv/ganeti/shared-file-storage"; enable_shared_file_storage="True"]])
+AC_SUBST(SHARED_FILE_STORAGE_DIR, $shared_file_storage_dir)
+AC_SUBST(ENABLE_SHARED_FILE_STORAGE, $enable_shared_file_storage)
+
 # --with-kvm-path=...
 AC_ARG_WITH([kvm-path],
   [AS_HELP_STRING([--with-kvm-path=PATH],
index 3415bad..391bb7a 100644 (file)
@@ -2404,15 +2404,15 @@ def BlockdevRename(devlist):
     _Fail("; ".join(msgs))
 
 
-def _TransformFileStorageDir(file_storage_dir):
+def _TransformFileStorageDir(fs_dir):
   """Checks whether given file_storage_dir is valid.
 
-  Checks wheter the given file_storage_dir is within the cluster-wide
-  default file_storage_dir stored in SimpleStore. Only paths under that
-  directory are allowed.
+  Checks wheter the given fs_dir is within the cluster-wide default
+  file_storage_dir or the shared_file_storage_dir, which are stored in
+  SimpleStore. Only paths under those directories are allowed.
 
-  @type file_storage_dir: str
-  @param file_storage_dir: the path to check
+  @type fs_dir: str
+  @param fs_dir: the path to check
 
   @return: the normalized path if valid, None otherwise
 
@@ -2420,13 +2420,15 @@ def _TransformFileStorageDir(file_storage_dir):
   if not constants.ENABLE_FILE_STORAGE:
     _Fail("File storage disabled at configure time")
   cfg = _GetConfig()
-  file_storage_dir = os.path.normpath(file_storage_dir)
-  base_file_storage_dir = cfg.GetFileStorageDir()
-  if (os.path.commonprefix([file_storage_dir, base_file_storage_dir]) !=
-      base_file_storage_dir):
+  fs_dir = os.path.normpath(fs_dir)
+  base_fstore = cfg.GetFileStorageDir()
+  base_shared = cfg.GetSharedFileStorageDir()
+  if ((os.path.commonprefix([fs_dir, base_fstore]) != base_fstore) and
+      (os.path.commonprefix([fs_dir, base_shared]) != base_shared)):
     _Fail("File storage directory '%s' is not under base file"
-          " storage directory '%s'", file_storage_dir, base_file_storage_dir)
-  return file_storage_dir
+          " storage directory '%s' or shared storage directory '%s'",
+          fs_dir, base_fstore, base_shared)
+  return fs_dir
 
 
 def CreateFileStorageDir(file_storage_dir):
index 7904eea..69797bc 100644 (file)
@@ -1,7 +1,7 @@
 #
 #
 
-# Copyright (C) 2006, 2007, 2010 Google Inc.
+# Copyright (C) 2006, 2007, 2010, 2011 Google Inc.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -2074,7 +2074,7 @@ DEV_MAP = {
   constants.LD_DRBD8: DRBD8,
   }
 
-if constants.ENABLE_FILE_STORAGE:
+if constants.ENABLE_FILE_STORAGE or constants.ENABLE_SHARED_FILE_STORAGE:
   DEV_MAP[constants.LD_FILE] = FileStorage
 
 
index af5b028..fd7ff83 100644 (file)
@@ -410,6 +410,7 @@ def InitCluster(cluster_name, mac_prefix, # pylint: disable-msg=R0913
     master_netdev=master_netdev,
     cluster_name=clustername.name,
     file_storage_dir=file_storage_dir,
+    shared_file_storage_dir=shared_file_storage_dir,
     enabled_hypervisors=enabled_hypervisors,
     beparams={constants.PP_DEFAULT: beparams},
     nicparams={constants.PP_DEFAULT: nicparams},
index ca3a7b6..57ff3eb 100644 (file)
@@ -79,6 +79,7 @@ __all__ = [
   "FORCE_VARIANT_OPT",
   "GLOBAL_FILEDIR_OPT",
   "HID_OS_OPT",
+  "GLOBAL_SHARED_FILEDIR_OPT",
   "HVLIST_OPT",
   "HVOPTS_OPT",
   "HYPERVISOR_OPT",
@@ -972,6 +973,15 @@ GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
                                 metavar="DIR",
                                 default=constants.DEFAULT_FILE_STORAGE_DIR)
 
+GLOBAL_SHARED_FILEDIR_OPT = cli_option("--shared-file-storage-dir",
+                            dest="shared_file_storage_dir",
+                            help="Specify the default directory (cluster-"
+                            "wide) for storing the shared file-based"
+                            " disks [%s]" %
+                            constants.DEFAULT_SHARED_FILE_STORAGE_DIR,
+                            metavar="SHAREDDIR",
+                            default=constants.DEFAULT_SHARED_FILE_STORAGE_DIR)
+
 NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
                                    help="Don't modify /etc/hosts",
                                    action="store_false", default=True)
index 10b2a1b..981696f 100644 (file)
@@ -352,6 +352,8 @@ def ShowClusterConfig(opts, args):
   ToStdout("  - lvm reserved volumes: %s", reserved_lvs)
   ToStdout("  - drbd usermode helper: %s", result["drbd_usermode_helper"])
   ToStdout("  - file storage path: %s", result["file_storage_dir"])
+  ToStdout("  - shared file storage path: %s",
+           result["shared_file_storage_dir"])
   ToStdout("  - maintenance of node health: %s",
            result["maintain_node_health"])
   ToStdout("  - uid pool: %s",
index ef9f1c9..2b3f2e0 100644 (file)
@@ -1036,7 +1036,7 @@ def _GetStorageTypeArgs(cfg, storage_type):
   # Special case for file storage
   if storage_type == constants.ST_FILE:
     # storage.FileStorage wants a list of storage directories
-    return [[cfg.GetFileStorageDir()]]
+    return [[cfg.GetFileStorageDir(), cfg.GetSharedFileStorageDir()]]
 
   return []
 
@@ -4696,6 +4696,7 @@ class LUClusterQuery(NoHooksLU):
       "volume_group_name": cluster.volume_group_name,
       "drbd_usermode_helper": cluster.drbd_usermode_helper,
       "file_storage_dir": cluster.file_storage_dir,
+      "shared_file_storage_dir": cluster.shared_file_storage_dir,
       "maintain_node_health": cluster.maintain_node_health,
       "ctime": cluster.ctime,
       "mtime": cluster.mtime,
@@ -5542,7 +5543,7 @@ class LUInstanceRename(LogicalUnit):
     old_name = inst.name
 
     rename_file_storage = False
-    if (inst.disk_template == constants.DT_FILE and
+    if (inst.disk_template in (constants.DT_FILE, constants.DT_SHARED_FILE) and
         self.op.new_name != inst.name):
       old_file_storage_dir = os.path.dirname(inst.disks[0].logical_id[1])
       rename_file_storage = True
@@ -6635,6 +6636,21 @@ def _GenerateDiskTemplate(lu, template_name,
                                                          disk_index)),
                               mode=disk["mode"])
       disks.append(disk_dev)
+  elif template_name == constants.DT_SHARED_FILE:
+    if len(secondary_nodes) != 0:
+      raise errors.ProgrammerError("Wrong template configuration")
+
+    opcodes.RequireSharedFileStorage()
+
+    for idx, disk in enumerate(disk_info):
+      disk_index = idx + base_index
+      disk_dev = objects.Disk(dev_type=constants.LD_FILE, size=disk["size"],
+                              iv_name="disk/%d" % disk_index,
+                              logical_id=(file_driver,
+                                          "%s/disk%d" % (file_storage_dir,
+                                                         disk_index)),
+                              mode=disk["mode"])
+      disks.append(disk_dev)
   else:
     raise errors.ProgrammerError("Invalid disk template '%s'" % template_name)
   return disks
@@ -6744,7 +6760,7 @@ def _CreateDisks(lu, instance, to_skip=None, target_node=None):
     pnode = target_node
     all_nodes = [pnode]
 
-  if instance.disk_template == constants.DT_FILE:
+  if instance.disk_template in (constants.DT_FILE, constants.DT_SHARED_FILE):
     file_storage_dir = os.path.dirname(instance.disks[0].logical_id[1])
     result = lu.rpc.call_file_storage_dir_create(pnode, file_storage_dir)
 
@@ -6834,6 +6850,7 @@ def _ComputeDiskSizePerVG(disk_template, disks):
     # 128 MB are added for drbd metadata for each disk
     constants.DT_DRBD8: _compute(disks, 128),
     constants.DT_FILE: {},
+    constants.DT_SHARED_FILE: {},
   }
 
   if disk_template not in req_size_dict:
@@ -6854,6 +6871,7 @@ def _ComputeDiskSize(disk_template, disks):
     # 128 MB are added for drbd metadata for each disk
     constants.DT_DRBD8: sum(d["size"] + 128 for d in disks),
     constants.DT_FILE: None,
+    constants.DT_SHARED_FILE: 0,
   }
 
   if disk_template not in req_size_dict:
@@ -7657,7 +7675,7 @@ class LUInstanceCreate(LogicalUnit):
     else:
       network_port = None
 
-    if constants.ENABLE_FILE_STORAGE:
+    if constants.ENABLE_FILE_STORAGE or constants.ENABLE_SHARED_FILE_STORAGE:
       # this is needed because os.path.join does not accept None arguments
       if self.op.file_storage_dir is None:
         string_file_storage_dir = ""
@@ -7665,7 +7683,12 @@ class LUInstanceCreate(LogicalUnit):
         string_file_storage_dir = self.op.file_storage_dir
 
       # build the full file storage dir path
-      file_storage_dir = utils.PathJoin(self.cfg.GetFileStorageDir(),
+      if self.op.disk_template == constants.DT_SHARED_FILE:
+        get_fsd_fn = self.cfg.GetSharedFileStorageDir
+      else:
+        get_fsd_fn = self.cfg.GetFileStorageDir
+
+      file_storage_dir = utils.PathJoin(get_fsd_fn(),
                                         string_file_storage_dir, instance)
     else:
       file_storage_dir = ""
@@ -8811,9 +8834,10 @@ class LUInstanceGrowDisk(LogicalUnit):
 
     self.disk = instance.FindDisk(self.op.disk)
 
-    if instance.disk_template != constants.DT_FILE:
-      # TODO: check the free disk space for file, when that feature
-      # will be supported
+    if instance.disk_template not in (constants.DT_FILE,
+                                      constants.DT_SHARED_FILE):
+      # TODO: check the free disk space for file, when that feature will be
+      # supported
       _CheckNodesFreeDiskPerVG(self, nodenames,
                                self.disk.ComputeGrowth(self.op.amount))
 
@@ -9554,7 +9578,8 @@ class LUInstanceSetParams(LogicalUnit):
         result.append(("disk/%d" % device_idx, "remove"))
       elif disk_op == constants.DDM_ADD:
         # add a new disk
-        if instance.disk_template == constants.DT_FILE:
+        if instance.disk_template in (constants.DT_FILE,
+                                        constants.DT_SHARED_FILE):
           file_driver, file_path = instance.disks[0].logical_id
           file_path = os.path.dirname(file_path)
         else:
index 86b98fc..39fca63 100644 (file)
@@ -878,6 +878,13 @@ class ConfigWriter:
     return self._config_data.cluster.file_storage_dir
 
   @locking.ssynchronized(_config_lock, shared=1)
+  def GetSharedFileStorageDir(self):
+    """Get the shared file storage dir for this cluster.
+
+    """
+    return self._config_data.cluster.shared_file_storage_dir
+
+  @locking.ssynchronized(_config_lock, shared=1)
   def GetHypervisorType(self):
     """Get the hypervisor type for this cluster.
 
@@ -1738,6 +1745,7 @@ class ConfigWriter:
       constants.SS_CLUSTER_NAME: cluster.cluster_name,
       constants.SS_CLUSTER_TAGS: cluster_tags,
       constants.SS_FILE_STORAGE_DIR: cluster.file_storage_dir,
+      constants.SS_SHARED_FILE_STORAGE_DIR: cluster.shared_file_storage_dir,
       constants.SS_MASTER_CANDIDATES: mc_data,
       constants.SS_MASTER_CANDIDATES_IPS: mc_ips_data,
       constants.SS_MASTER_IP: cluster.master_ip,
index 63c4880..4c55f3a 100644 (file)
@@ -144,7 +144,9 @@ SETUP_SSH = _autoconf.TOOLSDIR + "/setup-ssh"
 KVM_IFUP = _autoconf.PKGLIBDIR + "/kvm-ifup"
 ETC_HOSTS = "/etc/hosts"
 DEFAULT_FILE_STORAGE_DIR = _autoconf.FILE_STORAGE_DIR
+DEFAULT_SHARED_FILE_STORAGE_DIR = _autoconf.SHARED_FILE_STORAGE_DIR
 ENABLE_FILE_STORAGE = _autoconf.ENABLE_FILE_STORAGE
+ENABLE_SHARED_FILE_STORAGE = _autoconf.ENABLE_SHARED_FILE_STORAGE
 SYSCONFDIR = _autoconf.SYSCONFDIR
 TOOLSDIR = _autoconf.TOOLSDIR
 CONF_DIR = SYSCONFDIR + "/ganeti"
@@ -360,15 +362,19 @@ DT_DISKLESS = "diskless"
 DT_PLAIN = "plain"
 DT_DRBD8 = "drbd"
 DT_FILE = "file"
+DT_SHARED_FILE = "sharedfile"
 
 # the set of network-mirrored disk templates
 DTS_NET_MIRROR = frozenset([DT_DRBD8])
 
+# the set of externally mirrored disk templates
+DTS_EXT_MIRROR = frozenset([DT_SHARED_FILE])
+
 # the set of non-lvm-based disk templates
-DTS_NOT_LVM = frozenset([DT_DISKLESS, DT_FILE])
+DTS_NOT_LVM = frozenset([DT_DISKLESS, DT_FILE, DT_SHARED_FILE])
 
 # the set of disk templates which can be grown
-DTS_GROWABLE = frozenset([DT_PLAIN, DT_DRBD8, DT_FILE])
+DTS_GROWABLE = frozenset([DT_PLAIN, DT_DRBD8, DT_FILE, DT_SHARED_FILE])
 
 # the set of disk templates that allow adoption
 DTS_MAY_ADOPT = frozenset([DT_PLAIN])
@@ -449,8 +455,8 @@ RIE_CONNECT_RETRIES = 10
 #: Give child process up to 5 seconds to exit after sending a signal
 CHILD_LINGER_TIMEOUT = 5.0
 
-DISK_TEMPLATES = frozenset([DT_DISKLESS, DT_PLAIN,
-                            DT_DRBD8, DT_FILE])
+DISK_TEMPLATES = frozenset([DT_DISKLESS, DT_PLAIN, DT_DRBD8,
+                            DT_FILE, DT_SHARED_FILE])
 
 FILE_DRIVER = frozenset([FD_LOOP, FD_BLKTAP])
 
@@ -1077,6 +1083,7 @@ MAX_DISKS = 16
 SS_CLUSTER_NAME = "cluster_name"
 SS_CLUSTER_TAGS = "cluster_tags"
 SS_FILE_STORAGE_DIR = "file_storage_dir"
+SS_SHARED_FILE_STORAGE_DIR = "shared_file_storage_dir"
 SS_MASTER_CANDIDATES = "master_candidates"
 SS_MASTER_CANDIDATES_IPS = "master_candidates_ips"
 SS_MASTER_IP = "master_ip"
index cc7f25c..50dfe4d 100644 (file)
@@ -558,7 +558,7 @@ class Disk(ConfigObject):
     actual algorithms from bdev.
 
     """
-    if self.dev_type == constants.LD_LV or self.dev_type == constants.LD_FILE:
+    if self.dev_type in (constants.LD_LV, constants.LD_FILE):
       self.size += amount
     elif self.dev_type == constants.LD_DRBD8:
       if self.children:
@@ -1066,6 +1066,7 @@ class Cluster(TaggableObject):
     "master_netdev",
     "cluster_name",
     "file_storage_dir",
+    "shared_file_storage_dir",
     "enabled_hypervisors",
     "hvparams",
     "os_hvp",
index 5b16e18..a0530c0 100644 (file)
@@ -162,6 +162,20 @@ def RequireFileStorage():
                                errors.ECODE_INVAL)
 
 
+def RequireSharedFileStorage():
+  """Checks that shared file storage is enabled.
+
+  While it doesn't really fit into this module, L{utils} was deemed too large
+  of a dependency to be imported for just one or two functions.
+
+  @raise errors.OpPrereqError: when shared file storage is disabled
+
+  """
+  if not constants.ENABLE_SHARED_FILE_STORAGE:
+    raise errors.OpPrereqError("Shared file storage disabled at"
+                               " configure time", errors.ECODE_INVAL)
+
+
 @ht.WithDesc("CheckFileStorage")
 def _CheckFileStorage(value):
   """Ensures file storage is enabled if used.
@@ -169,6 +183,8 @@ def _CheckFileStorage(value):
   """
   if value == constants.DT_FILE:
     RequireFileStorage()
+  elif value == constants.DT_SHARED_FILE:
+    RequireSharedFileStorage()
   return True
 
 
index 2eccc59..5498ba3 100644 (file)
@@ -155,6 +155,9 @@ class SimpleConfigReader(object):
   def GetFileStorageDir(self):
     return self._config_data["cluster"]["file_storage_dir"]
 
+  def GetSharedFileStorageDir(self):
+    return self._config_data["cluster"]["shared_file_storage_dir"]
+
   def GetNodeList(self):
     return self._config_data["nodes"].keys()
 
@@ -272,6 +275,7 @@ class SimpleStore(object):
     constants.SS_CLUSTER_NAME,
     constants.SS_CLUSTER_TAGS,
     constants.SS_FILE_STORAGE_DIR,
+    constants.SS_SHARED_FILE_STORAGE_DIR,
     constants.SS_MASTER_CANDIDATES,
     constants.SS_MASTER_CANDIDATES_IPS,
     constants.SS_MASTER_IP,
@@ -369,6 +373,12 @@ class SimpleStore(object):
     """
     return self._ReadFile(constants.SS_FILE_STORAGE_DIR)
 
+  def GetSharedFileStorageDir(self):
+    """Get the shared file storage dir.
+
+    """
+    return self._ReadFile(constants.SS_SHARED_FILE_STORAGE_DIR)
+
   def GetMasterCandidates(self):
     """Return the list of master candidates.