diff --git a/Makefile.am b/Makefile.am
index 77238f5ae6bb365a42f28ac1950711b501218283..81c1dfba56dbc328260d737bdd64f67917c8e9e1 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -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)'"; \
diff --git a/configure.ac b/configure.ac
index de6642b8d66bd137ec417c2a6551091c7098d245..e08864efc3742a759c3968b10e0ae9dede3ea771 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 3415badbb99198bbd4c9e7c92d2a16bd493d6432..391bb7a0a9da1c3a23d04779eda85edf15c96559 100644
--- a/lib/backend.py
+++ b/lib/backend.py
@@ -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):
diff --git a/lib/bdev.py b/lib/bdev.py
index 7904eeab6c24cb42540f58bae99a7c94f86919df..69797bc9986bf5eb4600cfe9fe4fd493401465f6 100644
--- a/lib/bdev.py
+++ b/lib/bdev.py
@@ -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
diff --git a/lib/bootstrap.py b/lib/bootstrap.py
index af5b02886c692b964a2ada789ba5ae03111622f1..fd7ff835a7cea0531491cb0329889f756fc1e59b 100644
--- a/lib/bootstrap.py
+++ b/lib/bootstrap.py
@@ -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},
diff --git a/lib/cli.py b/lib/cli.py
index ca3a7b6e2b34a82149849ede633c2ec4bf7c0267..57ff3eb0a9dfbe724dfd58e88e1756b34b618df7 100644
--- a/lib/cli.py
+++ b/lib/cli.py
@@ -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)
diff --git a/lib/client/gnt_cluster.py b/lib/client/gnt_cluster.py
index 10b2a1bfcca3575fb1dafd16a8f521b2d3764abd..981696fef7604e88217aa9c1969aaf719a74f4c8 100644
--- a/lib/client/gnt_cluster.py
+++ b/lib/client/gnt_cluster.py
@@ -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",
diff --git a/lib/cmdlib.py b/lib/cmdlib.py
index ef9f1c995a48a2b99e6583a1fd7ec59aeb7d9cd2..2b3f2e0154ef1e047b4900ec452b547d7849c3dd 100644
--- a/lib/cmdlib.py
+++ b/lib/cmdlib.py
@@ -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
@@ -6626,6 +6627,21 @@ def _GenerateDiskTemplate(lu, template_name,
opcodes.RequireFileStorage()
+ 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)
+ 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"],
@@ -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:
diff --git a/lib/config.py b/lib/config.py
index 86b98fc5602b4f9a59d3f268f229c026a881694f..39fca63c141b4440ba072a9517c7eb86be180aa9 100644
--- a/lib/config.py
+++ b/lib/config.py
@@ -877,6 +877,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,
diff --git a/lib/constants.py b/lib/constants.py
index 63c4880b05d42459b6c8a3a6729bb2823ffd19a2..4c55f3ae4c20bb3fbf5edc84de848408cc53542b 100644
--- a/lib/constants.py
+++ b/lib/constants.py
@@ -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"
diff --git a/lib/objects.py b/lib/objects.py
index cc7f25cc7b05ae5c5dad83530fd99642dab2cd1d..50dfe4d7921977eef986fa06b0b284d1fd2b7cb4 100644
--- a/lib/objects.py
+++ b/lib/objects.py
@@ -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",
diff --git a/lib/opcodes.py b/lib/opcodes.py
index 5b16e1823740e0b9f1b349adf29f5550738e6f86..a0530c0c1ba34357ce209f6b823aa333fe960e78 100644
--- a/lib/opcodes.py
+++ b/lib/opcodes.py
@@ -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
diff --git a/lib/ssconf.py b/lib/ssconf.py
index 2eccc59b89187e9b20daa87c219f6b727aa2c2ef..5498ba361d4fe1daf2c3ad64be7a2b662bedcc2a 100644
--- a/lib/ssconf.py
+++ b/lib/ssconf.py
@@ -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.