From: Apollon Oikonomopoulos <[email protected]>

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-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 <[email protected]>
---
 Makefile.am         |    2 +
 configure.ac        |   17 ++++++++++++++++
 lib/backend.py      |    5 +++-
 lib/bdev.py         |    2 +-
 lib/bootstrap.py    |    1 +
 lib/cli.py          |   10 +++++++++
 lib/cmdlib.py       |   52 +++++++++++++++++++++++++++++++++++++++++++-------
 lib/config.py       |    8 +++++++
 lib/constants.py    |   15 ++++++++++---
 lib/objects.py      |    5 ++-
 lib/ssconf.py       |   10 +++++++++
 scripts/gnt-cluster |    1 +
 12 files changed, 112 insertions(+), 16 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index 907a36e..3503320 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -526,6 +526,8 @@ lib/_autoconf.py: Makefile stamp-directories
          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)'"; \
diff --git a/configure.ac b/configure.ac
index a0cecfd..f0e4066 100644
--- a/configure.ac
+++ b/configure.ac
@@ -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],
diff --git a/lib/backend.py b/lib/backend.py
index 2a158d6..0a0d4cc 100644
--- a/lib/backend.py
+++ b/lib/backend.py
@@ -2242,8 +2242,11 @@ def _TransformFileStorageDir(file_storage_dir):
   cfg = _GetConfig()
   file_storage_dir = os.path.normpath(file_storage_dir)
   base_file_storage_dir = cfg.GetFileStorageDir()
+  base_shared_file_storage_dir = cfg.GetSharedFileStorageDir()
   if (os.path.commonprefix([file_storage_dir, base_file_storage_dir]) !=
-      base_file_storage_dir):
+      base_file_storage_dir) and \
+      (os.path.commonprefix([file_storage_dir,base_shared_file_storage_dir]) !=
+      base_shared_file_storage_dir):
     _Fail("File storage directory '%s' is not under base file"
           " storage directory '%s'", file_storage_dir, base_file_storage_dir)
   return file_storage_dir
diff --git a/lib/bdev.py b/lib/bdev.py
index f377f16..3d68aea 100644
--- a/lib/bdev.py
+++ b/lib/bdev.py
@@ -1943,7 +1943,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
 
 
diff --git a/lib/bootstrap.py b/lib/bootstrap.py
index 57b29cc..770568c 100644
--- a/lib/bootstrap.py
+++ b/lib/bootstrap.py
@@ -332,6 +332,7 @@ def InitCluster(cluster_name, mac_prefix,
     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},
diff --git a/lib/cli.py b/lib/cli.py
index ce9f04b..90c64e0 100644
--- a/lib/cli.py
+++ b/lib/cli.py
@@ -70,6 +70,7 @@ __all__ = [
   "FORCE_OPT",
   "FORCE_VARIANT_OPT",
   "GLOBAL_FILEDIR_OPT",
+  "GLOBAL_SHARED_FILEDIR_OPT",
   "HVLIST_OPT",
   "HVOPTS_OPT",
   "HYPERVISOR_OPT",
@@ -863,6 +864,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)
diff --git a/lib/cmdlib.py b/lib/cmdlib.py
index b1889ab..b466a45 100644
--- a/lib/cmdlib.py
+++ b/lib/cmdlib.py
@@ -601,6 +601,17 @@ def _RequireFileStorage():
                                errors.ECODE_INVAL)
 
 
+def _RequireSharedFileStorage():
+  """Checks that file storage is enabled.
+
+  @raise errors.OpPrereqError: when file storage is disabled
+
+  """
+  if not constants.ENABLE_SHARED_FILE_STORAGE:
+    raise errors.OpPrereqError("Shared file storage disabled at"
+                               " configure time", errors.ECODE_INVAL)
+
+
 def _CheckDiskTemplate(template):
   """Ensure a given disk template is valid.
 
@@ -611,6 +622,8 @@ def _CheckDiskTemplate(template):
     raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
   if template == constants.DT_FILE:
     _RequireFileStorage()
+  elif template == constants.DT_SHARED_FILE:
+    _RequireSharedFileStorage()
 
 
 def _CheckStorageType(storage_type):
@@ -931,7 +944,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 []
 
@@ -3779,6 +3792,7 @@ class LUQueryClusterInfo(NoHooksLU):
       "master_netdev": cluster.master_netdev,
       "volume_group_name": cluster.volume_group_name,
       "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,
@@ -4587,7 +4601,7 @@ class LURenameInstance(LogicalUnit):
     inst = self.instance
     old_name = inst.name
 
-    if inst.disk_template == constants.DT_FILE:
+    if inst.disk_template in (constants.DT_FILE, constants.DT_SHARED_FILE):
       old_file_storage_dir = os.path.dirname(inst.disks[0].logical_id[1])
 
     self.cfg.RenameInstance(inst.name, self.op.new_name)
@@ -4598,7 +4612,7 @@ class LURenameInstance(LogicalUnit):
     # re-read the instance from the configuration after rename
     inst = self.cfg.GetInstanceInfo(self.op.new_name)
 
-    if inst.disk_template == constants.DT_FILE:
+    if inst.disk_template in (constants.DT_FILE, constants.DT_SHARED_FILE):
       new_file_storage_dir = os.path.dirname(inst.disks[0].logical_id[1])
       result = self.rpc.call_file_storage_dir_rename(inst.primary_node,
                                                      old_file_storage_dir,
@@ -5934,6 +5948,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")
+
+    _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
@@ -5971,7 +6000,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)
 
@@ -6051,6 +6080,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:
@@ -6798,7 +6828,7 @@ class LUCreateInstance(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 = ""
@@ -6806,7 +6836,12 @@ class LUCreateInstance(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:
+        GetFileStorageDir = self.cfg.GetSharedFileStorageDir
+      else:
+        GetFileStorageDir = self.cfg.GetFileStorageDir
+
+      file_storage_dir = utils.PathJoin(GetFileStorageDir(),
                                         string_file_storage_dir, instance)
     else:
       file_storage_dir = ""
@@ -8027,7 +8062,7 @@ class LUGrowDisk(LogicalUnit):
 
     self.disk = instance.FindDisk(self.op.disk)
 
-    if instance.disk_template != constants.DT_FILE:
+    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
       _CheckNodesFreeDisk(self, nodenames, self.op.amount)
@@ -8776,7 +8811,8 @@ class LUSetInstanceParams(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:
diff --git a/lib/config.py b/lib/config.py
index aa479c0..15a2acd 100644
--- a/lib/config.py
+++ b/lib/config.py
@@ -800,6 +800,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.
 
@@ -1384,6 +1391,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,
diff --git a/lib/constants.py b/lib/constants.py
index f2e978f..0a92cb5 100644
--- a/lib/constants.py
+++ b/lib/constants.py
@@ -123,7 +123,9 @@ QUEUE_DIR = DATA_DIR + "/queue"
 DAEMON_UTIL = _autoconf.PKGLIBDIR + "/daemon-util"
 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"
@@ -314,15 +316,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])
 
 # logical disk types
 LD_LV = "lvm"
@@ -385,8 +391,8 @@ RIE_CERT_VALIDITY = 24 * 60 * 60
 # Remote import/export connect timeout for socat
 RIE_CONNECT_TIMEOUT = 60
 
-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])
 
@@ -766,6 +772,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"
diff --git a/lib/objects.py b/lib/objects.py
index e640e30..0e1fbfe 100644
--- a/lib/objects.py
+++ b/lib/objects.py
@@ -445,7 +445,7 @@ class Disk(ConfigObject):
     devices needs to (or can) be assembled.
 
     """
-    if self.dev_type in [constants.LD_LV, constants.LD_FILE]:
+    if self.dev_type in [constants.LD_LV, constants.LD_FILE]
       result = [node]
     elif self.dev_type in constants.LDS_DRBD:
       result = [self.logical_id[0], self.logical_id[1]]
@@ -498,7 +498,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:
@@ -857,6 +857,7 @@ class Cluster(TaggableObject):
     "master_netdev",
     "cluster_name",
     "file_storage_dir",
+    "shared_file_storage_dir",
     "enabled_hypervisors",
     "hvparams",
     "os_hvp",
diff --git a/lib/ssconf.py b/lib/ssconf.py
index 035a887..6ea01c9 100644
--- a/lib/ssconf.py
+++ b/lib/ssconf.py
@@ -153,6 +153,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()
 
@@ -270,6 +273,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,
@@ -363,6 +367,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.
 
diff --git a/scripts/gnt-cluster b/scripts/gnt-cluster
index bf160cd..fbe8e75 100755
--- a/scripts/gnt-cluster
+++ b/scripts/gnt-cluster
@@ -284,6 +284,7 @@ def ShowClusterConfig(opts, args):
   ToStdout("  - master netdev: %s", result["master_netdev"])
   ToStdout("  - lvm volume group: %s", result["volume_group_name"])
   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",
-- 
1.7.1

Reply via email to