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.