from ganeti import objects
from ganeti import compat
from ganeti import netutils
+from ganeti import pathutils
# Size of reads in _CanReadDevice
raise errors.BlockDeviceError(msg)
+def _CheckResult(result):
+ """Throws an error if the given result is a failed one.
+
+ @param result: result from RunCmd
+
+ """
+ if result.failed:
+ _ThrowError("Command: %s error: %s - %s", result.cmd, result.fail_reason,
+ result.output)
+
+
def _CanReadDevice(path):
"""Check if we can read from the given device.
return False
+def _GetForbiddenFileStoragePaths():
+ """Builds a list of path prefixes which shouldn't be used for file storage.
+
+ @rtype: frozenset
+
+ """
+ paths = set([
+ "/boot",
+ "/dev",
+ "/etc",
+ "/home",
+ "/proc",
+ "/root",
+ "/sys",
+ ])
+
+ for prefix in ["", "/usr", "/usr/local"]:
+ paths.update(map(lambda s: "%s/%s" % (prefix, s),
+ ["bin", "lib", "lib32", "lib64", "sbin"]))
+
+ return frozenset(map(os.path.normpath, paths))
+
+
+def _ComputeWrongFileStoragePaths(paths,
+ _forbidden=_GetForbiddenFileStoragePaths()):
+ """Cross-checks a list of paths for prefixes considered bad.
+
+ Some paths, e.g. "/bin", should not be used for file storage.
+
+ @type paths: list
+ @param paths: List of paths to be checked
+ @rtype: list
+ @return: Sorted list of paths for which the user should be warned
+
+ """
+ def _Check(path):
+ return (not os.path.isabs(path) or
+ path in _forbidden or
+ filter(lambda p: utils.IsBelowDir(p, path), _forbidden))
+
+ return utils.NiceSort(filter(_Check, map(os.path.normpath, paths)))
+
+
+def ComputeWrongFileStoragePaths(_filename=pathutils.FILE_STORAGE_PATHS_FILE):
+ """Returns a list of file storage paths whose prefix is considered bad.
+
+ See L{_ComputeWrongFileStoragePaths}.
+
+ """
+ return _ComputeWrongFileStoragePaths(_LoadAllowedFileStoragePaths(_filename))
+
+
+def _CheckFileStoragePath(path, allowed):
+ """Checks if a path is in a list of allowed paths for file storage.
+
+ @type path: string
+ @param path: Path to check
+ @type allowed: list
+ @param allowed: List of allowed paths
+ @raise errors.FileStoragePathError: If the path is not allowed
+
+ """
+ if not os.path.isabs(path):
+ raise errors.FileStoragePathError("File storage path must be absolute,"
+ " got '%s'" % path)
+
+ for i in allowed:
+ if not os.path.isabs(i):
+ logging.info("Ignoring relative path '%s' for file storage", i)
+ continue
+
+ if utils.IsBelowDir(i, path):
+ break
+ else:
+ raise errors.FileStoragePathError("Path '%s' is not acceptable for file"
+ " storage" % path)
+
+
+def _LoadAllowedFileStoragePaths(filename):
+ """Loads file containing allowed file storage paths.
+
+ @rtype: list
+ @return: List of allowed paths (can be an empty list)
+
+ """
+ try:
+ contents = utils.ReadFile(filename)
+ except EnvironmentError:
+ return []
+ else:
+ return utils.FilterEmptyLinesAndComments(contents)
+
+
+def CheckFileStoragePath(path, _filename=pathutils.FILE_STORAGE_PATHS_FILE):
+ """Checks if a path is allowed for file storage.
+
+ @type path: string
+ @param path: Path to check
+ @raise errors.FileStoragePathError: If the path is not allowed
+
+ """
+ allowed = _LoadAllowedFileStoragePaths(_filename)
+
+ if _ComputeWrongFileStoragePaths([path]):
+ raise errors.FileStoragePathError("Path '%s' uses a forbidden prefix" %
+ path)
+
+ _CheckFileStoragePath(path, allowed)
+
+
class BlockDev(object):
"""Block device abstract class.
for child in self._children:
child.SetInfo(text)
- def Grow(self, amount, dryrun):
+ def Grow(self, amount, dryrun, backingstore):
"""Grow the block device.
@type amount: integer
@type dryrun: boolean
@param dryrun: whether to execute the operation in simulation mode
only, without actually increasing the size
+ @param backingstore: whether to execute the operation on backing storage
+ only, or on "logical" storage only; e.g. DRBD is logical storage,
+ whereas LVM, file, RBD are backing storage
"""
raise NotImplementedError
_ThrowError("Not enough free space: required %s,"
" available %s", size, free_size)
- result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
- "-n%s" % snap_name, self.dev_path])
- if result.failed:
- _ThrowError("command: %s error: %s - %s",
- result.cmd, result.fail_reason, result.output)
+ _CheckResult(utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
+ "-n%s" % snap_name, self.dev_path]))
return (self._vg_name, snap_name)
+ def _RemoveOldInfo(self):
+ """Try to remove old tags from the lv.
+
+ """
+ result = utils.RunCmd(["lvs", "-o", "tags", "--noheadings", "--nosuffix",
+ self.dev_path])
+ _CheckResult(result)
+
+ raw_tags = result.stdout.strip()
+ if raw_tags:
+ for tag in raw_tags.split(","):
+ _CheckResult(utils.RunCmd(["lvchange", "--deltag",
+ tag.strip(), self.dev_path]))
+
def SetInfo(self, text):
"""Update metadata with info text.
"""
BlockDev.SetInfo(self, text)
+ self._RemoveOldInfo()
+
# 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]
- result = utils.RunCmd(["lvchange", "--addtag", text,
- self.dev_path])
- if result.failed:
- _ThrowError("Command: %s error: %s - %s", result.cmd, result.fail_reason,
- result.output)
+ _CheckResult(utils.RunCmd(["lvchange", "--addtag", text, self.dev_path]))
- def Grow(self, amount, dryrun):
+ def Grow(self, amount, dryrun, backingstore):
"""Grow the logical volume.
"""
+ if not backingstore:
+ return
if self.pe_size is None or self.stripe_count is None:
if not self.Attach():
_ThrowError("Can't attach to LV during Grow()")
first_line)
values = version.groups()
- retval = {"k_major": int(values[0]),
- "k_minor": int(values[1]),
- "k_point": int(values[2]),
- "api": int(values[3]),
- "proto": int(values[4]),
- }
+ retval = {
+ "k_major": int(values[0]),
+ "k_minor": int(values[1]),
+ "k_point": int(values[2]),
+ "api": int(values[3]),
+ "proto": int(values[4]),
+ }
if values[5] is not None:
retval["proto2"] = values[5]
def _CheckMetaSize(meta_device):
"""Check if the given meta device looks like a valid one.
- This currently only check the size, which must be around
+ This currently only checks the size, which must be around
128MiB.
"""
doesn't do anything to the supposed peer. If you need a fully
connected DRBD pair, you need to use this class on both hosts.
- The unique_id for the drbd device is the (local_ip, local_port,
- remote_ip, remote_port) tuple, and it must have two children: the
- data device and the meta_device. The meta device is checked for
- valid size and is zeroed on create.
+ The unique_id for the drbd device is a (local_ip, local_port,
+ remote_ip, remote_port, local_minor, secret) tuple, and it must have
+ two children: the data device and the meta_device. The meta device
+ is checked for valid size and is zeroed on create.
"""
_MAX_MINORS = 255
def _GetShowParser(cls):
"""Return a parser for `drbd show` output.
- This will either create or return an already-create parser for the
+ This will either create or return an already-created parser for the
output of the command `drbd show`.
"""
@classmethod
def _ComputeDiskBarrierArgs(cls, vmaj, vmin, vrel, disabled_barriers,
- disable_meta_flush):
+ disable_meta_flush):
"""Compute the DRBD command line parameters for disk barriers
Returns a list of the disk barrier parameters as requested via the
"--c-delay-target", params[constants.LDP_DELAY_TARGET],
"--c-max-rate", params[constants.LDP_MAX_RATE],
"--c-min-rate", params[constants.LDP_MIN_RATE],
- ])
+ ])
else:
args.extend(["-r", "%d" % params[constants.LDP_RESYNC_RATE]])
msg = ("Can't change syncer rate: %s - %s" %
(result.fail_reason, result.output))
logging.error(msg)
- return msg
+ return [msg]
return []
cls._InitMeta(aminor, meta.dev_path)
return cls(unique_id, children, size, params)
- def Grow(self, amount, dryrun):
+ def Grow(self, amount, dryrun, backingstore):
"""Resize the DRBD device and its backing storage.
"""
_ThrowError("drbd%d: Grow called while not attached", self._aminor)
if len(self._children) != 2 or None in self._children:
_ThrowError("drbd%d: cannot grow diskless device", self.minor)
- self._children[0].Grow(amount, dryrun)
- if dryrun:
- # DRBD does not support dry-run mode, so we'll return here
+ self._children[0].Grow(amount, dryrun, backingstore)
+ if dryrun or backingstore:
+ # DRBD does not support dry-run mode and is not backing storage,
+ # so we'll return here
return
result = utils.RunCmd(["drbdsetup", self.dev_path, "resize", "-s",
"%dm" % (self.size + amount)])
raise ValueError("Invalid configuration data %s" % str(unique_id))
self.driver = unique_id[0]
self.dev_path = unique_id[1]
+
+ CheckFileStoragePath(self.dev_path)
+
self.Attach()
def Assemble(self):
# TODO: implement rename for file-based storage
_ThrowError("Rename is not supported for file-based storage")
- def Grow(self, amount, dryrun):
+ def Grow(self, amount, dryrun, backingstore):
"""Grow the file
@param amount: the amount (in mebibytes) to grow with
"""
+ if not backingstore:
+ return
# Check that the file exists
self.Assemble()
current_size = self.GetActualSize()
"""
if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
raise ValueError("Invalid configuration data %s" % str(unique_id))
+
dev_path = unique_id[1]
+
+ CheckFileStoragePath(dev_path)
+
try:
fd = os.open(dev_path, os.O_RDWR | os.O_CREAT | os.O_EXCL)
f = os.fdopen(fd, "w")
"""
pass
- def Grow(self, amount, dryrun):
+ def Grow(self, amount, dryrun, backingstore):
"""Grow the logical volume.
"""
"""
pass
- def Grow(self, amount, dryrun):
+ def Grow(self, amount, dryrun, backingstore):
"""Grow the Volume.
@type amount: integer
only, without actually increasing the size
"""
+ if not backingstore:
+ return
if not self.Attach():
_ThrowError("Can't attach to rbd device during Grow()")
raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
+def _VerifyDiskParams(disk):
+ """Verifies if all disk parameters are set.
+
+ """
+ missing = set(constants.DISK_LD_DEFAULTS[disk.dev_type]) - set(disk.params)
+ if missing:
+ raise errors.ProgrammerError("Block device is missing disk parameters: %s" %
+ missing)
+
+
def FindDevice(disk, children):
"""Search for an existing, assembled device.
"""
_VerifyDiskType(disk.dev_type)
+ _VerifyDiskParams(disk)
device = DEV_MAP[disk.dev_type](disk.physical_id, children, disk.size,
disk.params)
device.Assemble()
"""
_VerifyDiskType(disk.dev_type)
+ _VerifyDiskParams(disk)
device = DEV_MAP[disk.dev_type].Create(disk.physical_id, children, disk.size,
disk.params)
return device