import pyparsing as pyp
import os
import logging
+import math
from ganeti import utils
from ganeti import errors
paths.update(map(lambda s: "%s/%s" % (prefix, s),
["bin", "lib", "lib32", "lib64", "sbin"]))
- return frozenset(map(os.path.normpath, paths))
+ return compat.UniqueFrozenset(map(os.path.normpath, paths))
def _ComputeWrongFileStoragePaths(paths,
raise NotImplementedError
@classmethod
- def Create(cls, unique_id, children, size, params):
+ def Create(cls, unique_id, children, size, params, excl_stor):
"""Create the device.
If the device cannot be created, it will return None
"""
_VALID_NAME_RE = re.compile("^[a-zA-Z0-9+_.-]*$")
- _INVALID_NAMES = frozenset([".", "..", "snapshot", "pvmove"])
- _INVALID_SUBSTRINGS = frozenset(["_mlog", "_mimage"])
+ _INVALID_NAMES = compat.UniqueFrozenset([".", "..", "snapshot", "pvmove"])
+ _INVALID_SUBSTRINGS = compat.UniqueFrozenset(["_mlog", "_mimage"])
def __init__(self, unique_id, children, size, params):
"""Attaches to a LV device.
self.major = self.minor = self.pe_size = self.stripe_count = None
self.Attach()
+ @staticmethod
+ def _GetStdPvSize(pvs_info):
+ """Return the the standard PV size (used with exclusive storage).
+
+ @param pvs_info: list of objects.LvmPvInfo, cannot be empty
+ @rtype: float
+ @return: size in MiB
+
+ """
+ assert len(pvs_info) > 0
+ smallest = min([pv.size for pv in pvs_info])
+ return smallest / (1 + constants.PART_MARGIN + constants.PART_RESERVED)
+
+ @staticmethod
+ def _ComputeNumPvs(size, pvs_info):
+ """Compute the number of PVs needed for an LV (with exclusive storage).
+
+ @type size: float
+ @param size: LV size in MiB
+ @param pvs_info: list of objects.LvmPvInfo, cannot be empty
+ @rtype: integer
+ @return: number of PVs needed
+ """
+ assert len(pvs_info) > 0
+ pv_size = float(LogicalVolume._GetStdPvSize(pvs_info))
+ return int(math.ceil(float(size) / pv_size))
+
+ @staticmethod
+ def _GetEmptyPvNames(pvs_info, max_pvs=None):
+ """Return a list of empty PVs, by name.
+
+ """
+ empty_pvs = filter(objects.LvmPvInfo.IsEmpty, pvs_info)
+ if max_pvs is not None:
+ empty_pvs = empty_pvs[:max_pvs]
+ return map((lambda pv: pv.name), empty_pvs)
+
@classmethod
- def Create(cls, unique_id, children, size, params):
+ def Create(cls, unique_id, children, size, params, excl_stor):
"""Create a new logical volume.
"""
cls._ValidateName(lv_name)
pvs_info = cls.GetPVInfo([vg_name])
if not pvs_info:
- _ThrowError("Can't compute PV info for vg %s", vg_name)
- pvs_info.sort()
- pvs_info.reverse()
+ if excl_stor:
+ msg = "No (empty) PVs found"
+ else:
+ msg = "Can't compute PV info for vg %s" % vg_name
+ _ThrowError(msg)
+ pvs_info.sort(key=(lambda pv: pv.free), reverse=True)
- pvlist = [pv[1] for pv in pvs_info]
+ pvlist = [pv.name for pv in pvs_info]
if compat.any(":" in v for v in pvlist):
_ThrowError("Some of your PVs have the invalid character ':' in their"
" name, this is not supported - please filter them out"
" in lvm.conf using either 'filter' or 'preferred_names'")
- free_size = sum([pv[0] for pv in pvs_info])
+
current_pvs = len(pvlist)
desired_stripes = params[constants.LDP_STRIPES]
stripes = min(current_pvs, desired_stripes)
- if stripes < desired_stripes:
- logging.warning("Could not use %d stripes for VG %s, as only %d PVs are"
- " available.", desired_stripes, vg_name, current_pvs)
- # The size constraint should have been checked from the master before
- # calling the create function.
- if free_size < size:
- _ThrowError("Not enough free space: required %s,"
- " available %s", size, free_size)
- cmd = ["lvcreate", "-L%dm" % size, "-n%s" % lv_name]
+ if excl_stor:
+ (err_msgs, _) = utils.LvmExclusiveCheckNodePvs(pvs_info)
+ if err_msgs:
+ for m in err_msgs:
+ logging.warning(m)
+ req_pvs = cls._ComputeNumPvs(size, pvs_info)
+ pvlist = cls._GetEmptyPvNames(pvs_info, req_pvs)
+ current_pvs = len(pvlist)
+ if current_pvs < req_pvs:
+ _ThrowError("Not enough empty PVs to create a disk of %d MB:"
+ " %d available, %d needed", size, current_pvs, req_pvs)
+ assert current_pvs == len(pvlist)
+ if stripes > current_pvs:
+ # No warning issued for this, as it's no surprise
+ stripes = current_pvs
+
+ else:
+ if stripes < desired_stripes:
+ logging.warning("Could not use %d stripes for VG %s, as only %d PVs are"
+ " available.", desired_stripes, vg_name, current_pvs)
+ free_size = sum([pv.free for pv in pvs_info])
+ # The size constraint should have been checked from the master before
+ # calling the create function.
+ if free_size < size:
+ _ThrowError("Not enough free space: required %s,"
+ " available %s", size, free_size)
+
# If the free space is not well distributed, we won't be able to
# create an optimally-striped volume; in that case, we want to try
# with N, N-1, ..., 2, and finally 1 (non-stripped) number of
# stripes
+ cmd = ["lvcreate", "-L%dm" % size, "-n%s" % lv_name]
for stripes_arg in range(stripes, 0, -1):
result = utils.RunCmd(cmd + ["-i%d" % stripes_arg] + [vg_name] + pvlist)
if not result.failed:
return data
@classmethod
- def GetPVInfo(cls, vg_names, filter_allocatable=True):
+ def GetPVInfo(cls, vg_names, filter_allocatable=True, include_lvs=False):
"""Get the free space info for PVs in a volume group.
@param vg_names: list of volume group names, if empty all will be returned
@param filter_allocatable: whether to skip over unallocatable PVs
+ @param include_lvs: whether to include a list of LVs hosted on each PV
@rtype: list
- @return: list of tuples (free_space, name) with free_space in mebibytes
+ @return: list of objects.LvmPvInfo objects
"""
+ # We request "lv_name" field only if we care about LVs, so we don't get
+ # a long list of entries with many duplicates unless we really have to.
+ # The duplicate "pv_name" field will be ignored.
+ if include_lvs:
+ lvfield = "lv_name"
+ else:
+ lvfield = "pv_name"
try:
info = cls._GetVolumeInfo("pvs", ["pv_name", "vg_name", "pv_free",
- "pv_attr"])
+ "pv_attr", "pv_size", lvfield])
except errors.GenericError, err:
logging.error("Can't get PV information: %s", err)
return None
+ # When asked for LVs, "pvs" may return multiple entries for the same PV-LV
+ # pair. We sort entries by PV name and then LV name, so it's easy to weed
+ # out duplicates.
+ if include_lvs:
+ info.sort(key=(lambda i: (i[0], i[5])))
data = []
- for pv_name, vg_name, pv_free, pv_attr in info:
+ lastpvi = None
+ for (pv_name, vg_name, pv_free, pv_attr, pv_size, lv_name) in info:
# (possibly) skip over pvs which are not allocatable
if filter_allocatable and pv_attr[0] != "a":
continue
# (possibly) skip over pvs which are not in the right volume group(s)
if vg_names and vg_name not in vg_names:
continue
- data.append((float(pv_free), pv_name, vg_name))
+ # Beware of duplicates (check before inserting)
+ if lastpvi and lastpvi.name == pv_name:
+ if include_lvs and lv_name:
+ if not lastpvi.lv_list or lastpvi.lv_list[-1] != lv_name:
+ lastpvi.lv_list.append(lv_name)
+ else:
+ if include_lvs and lv_name:
+ lvl = [lv_name]
+ else:
+ lvl = []
+ lastpvi = objects.LvmPvInfo(name=pv_name, vg_name=vg_name,
+ size=float(pv_size), free=float(pv_free),
+ attributes=pv_attr, lv_list=lvl)
+ data.append(lastpvi)
return data
@classmethod
- def GetVGInfo(cls, vg_names, filter_readonly=True):
+ def _GetExclusiveStorageVgFree(cls, vg_name):
+ """Return the free disk space in the given VG, in exclusive storage mode.
+
+ @type vg_name: string
+ @param vg_name: VG name
+ @rtype: float
+ @return: free space in MiB
+ """
+ pvs_info = cls.GetPVInfo([vg_name])
+ if not pvs_info:
+ return 0.0
+ pv_size = cls._GetStdPvSize(pvs_info)
+ num_pvs = len(cls._GetEmptyPvNames(pvs_info))
+ return pv_size * num_pvs
+
+ @classmethod
+ def GetVGInfo(cls, vg_names, excl_stor, filter_readonly=True):
"""Get the free space info for specific VGs.
@param vg_names: list of volume group names, if empty all will be returned
+ @param excl_stor: whether exclusive_storage is enabled
@param filter_readonly: whether to skip over readonly VGs
@rtype: list
# (possibly) skip over vgs which are not in the right volume group(s)
if vg_names and vg_name not in vg_names:
continue
+ # Exclusive storage needs a different concept of free space
+ if excl_stor:
+ es_free = cls._GetExclusiveStorageVgFree(vg_name)
+ assert es_free <= vg_free
+ vg_free = es_free
data.append((float(vg_free), float(vg_size), vg_name))
return data
snap = LogicalVolume((self._vg_name, snap_name), None, size, self.params)
_IgnoreError(snap.Remove)
- vg_info = self.GetVGInfo([self._vg_name])
+ vg_info = self.GetVGInfo([self._vg_name], False)
if not vg_info:
_ThrowError("Can't compute VG info for vg %s", self._vg_name)
free_size, _, _ = vg_info[0]
CS_SYNCTARGET = "SyncTarget"
CS_PAUSEDSYNCS = "PausedSyncS"
CS_PAUSEDSYNCT = "PausedSyncT"
- CSET_SYNC = frozenset([
+ CSET_SYNC = compat.UniqueFrozenset([
CS_WFREPORTPARAMS,
CS_STARTINGSYNCS,
CS_STARTINGSYNCT,
self.Shutdown()
@classmethod
- def Create(cls, unique_id, children, size, params):
+ def Create(cls, unique_id, children, size, params, excl_stor):
"""Create a new DRBD8 device.
Since DRBD devices are not created per se, just assembled, this
"""
if len(children) != 2:
raise errors.ProgrammerError("Invalid setup for the drbd device")
+ if excl_stor:
+ raise errors.ProgrammerError("DRBD device requested with"
+ " exclusive_storage")
# check that the minor is unused
aminor = unique_id[4]
proc_info = cls._MassageProcData(cls._GetProcData())
_ThrowError("Can't stat %s: %s", self.dev_path, err)
@classmethod
- def Create(cls, unique_id, children, size, params):
+ def Create(cls, unique_id, children, size, params, excl_stor):
"""Create a new file.
@param size: the size of file in MiB
@return: an instance of FileStorage
"""
+ if excl_stor:
+ raise errors.ProgrammerError("FileStorage device requested with"
+ " exclusive_storage")
if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
raise ValueError("Invalid configuration data %s" % str(unique_id))
self.Attach()
@classmethod
- def Create(cls, unique_id, children, size, params):
+ def Create(cls, unique_id, children, size, params, excl_stor):
"""Create a new device
This is a noop, we only return a PersistentBlockDevice instance
"""
+ if excl_stor:
+ raise errors.ProgrammerError("Persistent block device requested with"
+ " exclusive_storage")
return PersistentBlockDevice(unique_id, children, 0, params)
def Remove(self):
self.Attach()
@classmethod
- def Create(cls, unique_id, children, size, params):
+ def Create(cls, unique_id, children, size, params, excl_stor):
"""Create a new rbd device.
Provision a new rbd volume inside a RADOS pool.
if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
raise errors.ProgrammerError("Invalid configuration data %s" %
str(unique_id))
+ if excl_stor:
+ raise errors.ProgrammerError("RBD device requested with"
+ " exclusive_storage")
rbd_pool = params[constants.LDP_POOL]
rbd_name = unique_id[1]
result.fail_reason, result.output)
+class ExtStorageDevice(BlockDev):
+ """A block device provided by an ExtStorage Provider.
+
+ This class implements the External Storage Interface, which means
+ handling of the externally provided block devices.
+
+ """
+ def __init__(self, unique_id, children, size, params):
+ """Attaches to an extstorage block device.
+
+ """
+ super(ExtStorageDevice, self).__init__(unique_id, children, size, params)
+ if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
+ raise ValueError("Invalid configuration data %s" % str(unique_id))
+
+ self.driver, self.vol_name = unique_id
+ self.ext_params = params
+
+ self.major = self.minor = None
+ self.Attach()
+
+ @classmethod
+ def Create(cls, unique_id, children, size, params, excl_stor):
+ """Create a new extstorage device.
+
+ Provision a new volume using an extstorage provider, which will
+ then be mapped to a block device.
+
+ """
+ if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
+ raise errors.ProgrammerError("Invalid configuration data %s" %
+ str(unique_id))
+ if excl_stor:
+ raise errors.ProgrammerError("extstorage device requested with"
+ " exclusive_storage")
+
+ # Call the External Storage's create script,
+ # to provision a new Volume inside the External Storage
+ _ExtStorageAction(constants.ES_ACTION_CREATE, unique_id,
+ params, str(size))
+
+ return ExtStorageDevice(unique_id, children, size, params)
+
+ def Remove(self):
+ """Remove the extstorage device.
+
+ """
+ if not self.minor and not self.Attach():
+ # The extstorage device doesn't exist.
+ return
+
+ # First shutdown the device (remove mappings).
+ self.Shutdown()
+
+ # Call the External Storage's remove script,
+ # to remove the Volume from the External Storage
+ _ExtStorageAction(constants.ES_ACTION_REMOVE, self.unique_id,
+ self.ext_params)
+
+ def Rename(self, new_id):
+ """Rename this device.
+
+ """
+ pass
+
+ def Attach(self):
+ """Attach to an existing extstorage device.
+
+ This method maps the extstorage volume that matches our name with
+ a corresponding block device and then attaches to this device.
+
+ """
+ self.attached = False
+
+ # Call the External Storage's attach script,
+ # to attach an existing Volume to a block device under /dev
+ self.dev_path = _ExtStorageAction(constants.ES_ACTION_ATTACH,
+ self.unique_id, self.ext_params)
+
+ try:
+ st = os.stat(self.dev_path)
+ except OSError, err:
+ logging.error("Error stat()'ing %s: %s", self.dev_path, str(err))
+ return False
+
+ if not stat.S_ISBLK(st.st_mode):
+ logging.error("%s is not a block device", self.dev_path)
+ return False
+
+ self.major = os.major(st.st_rdev)
+ self.minor = os.minor(st.st_rdev)
+ self.attached = True
+
+ return True
+
+ def Assemble(self):
+ """Assemble the device.
+
+ """
+ pass
+
+ def Shutdown(self):
+ """Shutdown the device.
+
+ """
+ if not self.minor and not self.Attach():
+ # The extstorage device doesn't exist.
+ return
+
+ # Call the External Storage's detach script,
+ # to detach an existing Volume from it's block device under /dev
+ _ExtStorageAction(constants.ES_ACTION_DETACH, self.unique_id,
+ self.ext_params)
+
+ self.minor = None
+ self.dev_path = None
+
+ def Open(self, force=False):
+ """Make the device ready for I/O.
+
+ """
+ pass
+
+ def Close(self):
+ """Notifies that the device will no longer be used for I/O.
+
+ """
+ pass
+
+ def Grow(self, amount, dryrun, backingstore):
+ """Grow the Volume.
+
+ @type amount: integer
+ @param amount: the amount (in mebibytes) to grow with
+ @type dryrun: boolean
+ @param dryrun: whether to execute the operation in simulation mode
+ only, without actually increasing the size
+
+ """
+ if not backingstore:
+ return
+ if not self.Attach():
+ _ThrowError("Can't attach to extstorage device during Grow()")
+
+ if dryrun:
+ # we do not support dry runs of resize operations for now.
+ return
+
+ new_size = self.size + amount
+
+ # Call the External Storage's grow script,
+ # to grow an existing Volume inside the External Storage
+ _ExtStorageAction(constants.ES_ACTION_GROW, self.unique_id,
+ self.ext_params, str(self.size), grow=str(new_size))
+
+ def SetInfo(self, text):
+ """Update metadata with info text.
+
+ """
+ # Replace invalid characters
+ text = re.sub("^[^A-Za-z0-9_+.]", "_", text)
+ text = re.sub("[^-A-Za-z0-9_+.]", "_", text)
+
+ # Only up to 128 characters are allowed
+ text = text[:128]
+
+ # Call the External Storage's setinfo script,
+ # to set metadata for an existing Volume inside the External Storage
+ _ExtStorageAction(constants.ES_ACTION_SETINFO, self.unique_id,
+ self.ext_params, metadata=text)
+
+
+def _ExtStorageAction(action, unique_id, ext_params,
+ size=None, grow=None, metadata=None):
+ """Take an External Storage action.
+
+ Take an External Storage action concerning or affecting
+ a specific Volume inside the External Storage.
+
+ @type action: string
+ @param action: which action to perform. One of:
+ create / remove / grow / attach / detach
+ @type unique_id: tuple (driver, vol_name)
+ @param unique_id: a tuple containing the type of ExtStorage (driver)
+ and the Volume name
+ @type ext_params: dict
+ @param ext_params: ExtStorage parameters
+ @type size: integer
+ @param size: the size of the Volume in mebibytes
+ @type grow: integer
+ @param grow: the new size in mebibytes (after grow)
+ @type metadata: string
+ @param metadata: metadata info of the Volume, for use by the provider
+ @rtype: None or a block device path (during attach)
+
+ """
+ driver, vol_name = unique_id
+
+ # Create an External Storage instance of type `driver'
+ status, inst_es = ExtStorageFromDisk(driver)
+ if not status:
+ _ThrowError("%s" % inst_es)
+
+ # Create the basic environment for the driver's scripts
+ create_env = _ExtStorageEnvironment(unique_id, ext_params, size,
+ grow, metadata)
+
+ # Do not use log file for action `attach' as we need
+ # to get the output from RunResult
+ # TODO: find a way to have a log file for attach too
+ logfile = None
+ if action is not constants.ES_ACTION_ATTACH:
+ logfile = _VolumeLogName(action, driver, vol_name)
+
+ # Make sure the given action results in a valid script
+ if action not in constants.ES_SCRIPTS:
+ _ThrowError("Action '%s' doesn't result in a valid ExtStorage script" %
+ action)
+
+ # Find out which external script to run according the given action
+ script_name = action + "_script"
+ script = getattr(inst_es, script_name)
+
+ # Run the external script
+ result = utils.RunCmd([script], env=create_env,
+ cwd=inst_es.path, output=logfile,)
+ if result.failed:
+ logging.error("External storage's %s command '%s' returned"
+ " error: %s, logfile: %s, output: %s",
+ action, result.cmd, result.fail_reason,
+ logfile, result.output)
+
+ # If logfile is 'None' (during attach), it breaks TailFile
+ # TODO: have a log file for attach too
+ if action is not constants.ES_ACTION_ATTACH:
+ lines = [utils.SafeEncode(val)
+ for val in utils.TailFile(logfile, lines=20)]
+ else:
+ lines = result.output[-20:]
+
+ _ThrowError("External storage's %s script failed (%s), last"
+ " lines of output:\n%s",
+ action, result.fail_reason, "\n".join(lines))
+
+ if action == constants.ES_ACTION_ATTACH:
+ return result.stdout
+
+
+def ExtStorageFromDisk(name, base_dir=None):
+ """Create an ExtStorage instance from disk.
+
+ This function will return an ExtStorage instance
+ if the given name is a valid ExtStorage name.
+
+ @type base_dir: string
+ @keyword base_dir: Base directory containing ExtStorage installations.
+ Defaults to a search in all the ES_SEARCH_PATH dirs.
+ @rtype: tuple
+ @return: True and the ExtStorage instance if we find a valid one, or
+ False and the diagnose message on error
+
+ """
+ if base_dir is None:
+ es_base_dir = pathutils.ES_SEARCH_PATH
+ else:
+ es_base_dir = [base_dir]
+
+ es_dir = utils.FindFile(name, es_base_dir, os.path.isdir)
+
+ if es_dir is None:
+ return False, ("Directory for External Storage Provider %s not"
+ " found in search path" % name)
+
+ # ES Files dictionary, we will populate it with the absolute path
+ # names; if the value is True, then it is a required file, otherwise
+ # an optional one
+ es_files = dict.fromkeys(constants.ES_SCRIPTS, True)
+
+ es_files[constants.ES_PARAMETERS_FILE] = True
+
+ for (filename, _) in es_files.items():
+ es_files[filename] = utils.PathJoin(es_dir, filename)
+
+ try:
+ st = os.stat(es_files[filename])
+ except EnvironmentError, err:
+ return False, ("File '%s' under path '%s' is missing (%s)" %
+ (filename, es_dir, utils.ErrnoOrStr(err)))
+
+ if not stat.S_ISREG(stat.S_IFMT(st.st_mode)):
+ return False, ("File '%s' under path '%s' is not a regular file" %
+ (filename, es_dir))
+
+ if filename in constants.ES_SCRIPTS:
+ if stat.S_IMODE(st.st_mode) & stat.S_IXUSR != stat.S_IXUSR:
+ return False, ("File '%s' under path '%s' is not executable" %
+ (filename, es_dir))
+
+ parameters = []
+ if constants.ES_PARAMETERS_FILE in es_files:
+ parameters_file = es_files[constants.ES_PARAMETERS_FILE]
+ try:
+ parameters = utils.ReadFile(parameters_file).splitlines()
+ except EnvironmentError, err:
+ return False, ("Error while reading the EXT parameters file at %s: %s" %
+ (parameters_file, utils.ErrnoOrStr(err)))
+ parameters = [v.split(None, 1) for v in parameters]
+
+ es_obj = \
+ objects.ExtStorage(name=name, path=es_dir,
+ create_script=es_files[constants.ES_SCRIPT_CREATE],
+ remove_script=es_files[constants.ES_SCRIPT_REMOVE],
+ grow_script=es_files[constants.ES_SCRIPT_GROW],
+ attach_script=es_files[constants.ES_SCRIPT_ATTACH],
+ detach_script=es_files[constants.ES_SCRIPT_DETACH],
+ setinfo_script=es_files[constants.ES_SCRIPT_SETINFO],
+ verify_script=es_files[constants.ES_SCRIPT_VERIFY],
+ supported_parameters=parameters)
+ return True, es_obj
+
+
+def _ExtStorageEnvironment(unique_id, ext_params,
+ size=None, grow=None, metadata=None):
+ """Calculate the environment for an External Storage script.
+
+ @type unique_id: tuple (driver, vol_name)
+ @param unique_id: ExtStorage pool and name of the Volume
+ @type ext_params: dict
+ @param ext_params: the EXT parameters
+ @type size: string
+ @param size: size of the Volume (in mebibytes)
+ @type grow: string
+ @param grow: new size of Volume after grow (in mebibytes)
+ @type metadata: string
+ @param metadata: metadata info of the Volume
+ @rtype: dict
+ @return: dict of environment variables
+
+ """
+ vol_name = unique_id[1]
+
+ result = {}
+ result["VOL_NAME"] = vol_name
+
+ # EXT params
+ for pname, pvalue in ext_params.items():
+ result["EXTP_%s" % pname.upper()] = str(pvalue)
+
+ if size is not None:
+ result["VOL_SIZE"] = size
+
+ if grow is not None:
+ result["VOL_NEW_SIZE"] = grow
+
+ if metadata is not None:
+ result["VOL_METADATA"] = metadata
+
+ return result
+
+
+def _VolumeLogName(kind, es_name, volume):
+ """Compute the ExtStorage log filename for a given Volume and operation.
+
+ @type kind: string
+ @param kind: the operation type (e.g. create, remove etc.)
+ @type es_name: string
+ @param es_name: the ExtStorage name
+ @type volume: string
+ @param volume: the name of the Volume inside the External Storage
+
+ """
+ # Check if the extstorage log dir is a valid dir
+ if not os.path.isdir(pathutils.LOG_ES_DIR):
+ _ThrowError("Cannot find log directory: %s", pathutils.LOG_ES_DIR)
+
+ # TODO: Use tempfile.mkstemp to create unique filename
+ base = ("%s-%s-%s-%s.log" %
+ (kind, es_name, volume, utils.TimestampForFilename()))
+ return utils.PathJoin(pathutils.LOG_ES_DIR, base)
+
+
DEV_MAP = {
constants.LD_LV: LogicalVolume,
constants.LD_DRBD8: DRBD8,
constants.LD_BLOCKDEV: PersistentBlockDevice,
constants.LD_RBD: RADOSBlockDevice,
+ constants.LD_EXT: ExtStorageDevice,
}
if constants.ENABLE_FILE_STORAGE or constants.ENABLE_SHARED_FILE_STORAGE:
return device
-def Create(disk, children):
+def Create(disk, children, excl_stor):
"""Create a device.
@type disk: L{objects.Disk}
@type children: list of L{bdev.BlockDev}
@param children: the list of block devices that are children of the device
represented by the disk parameter
+ @type excl_stor: boolean
+ @param excl_stor: Whether exclusive_storage is active
"""
_VerifyDiskType(disk.dev_type)
_VerifyDiskParams(disk)
device = DEV_MAP[disk.dev_type].Create(disk.physical_id, children, disk.size,
- disk.params)
+ disk.params, excl_stor)
return device