-
Dimitris Bliablias authored
This patch, adds the 'gluster' storage type to the set of storage types for which full node storage reporting is available. This set is used by the 'LUNodeQueryStorage' logical unit for getting information on storage units on node(s). Signed-off-by:
Dimitris Bliablias <bl.dimitris@gmail.com> Signed-off-by:
Petr Pudlak <pudlak@google.com> Reviewed-by:
Petr Pudlak <pudlak@google.com> Cherry-picked from 7ec47185 Signed-off-by:
Lisa Velden <velden@google.com> Signed-off-by:
Klaus Aehlig <aehlig@google.com> Reviewed-by:
Klaus Aehlig <aehlig@google.com>
f0517981
container.py 13.95 KiB
#
#
# Copyright (C) 2009, 2011, 2012 Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Storage container abstraction.
"""
# pylint: disable=W0232,R0201
# W0232, since we use these as singletons rather than object holding
# data
# R0201, for the same reason
# TODO: FileStorage initialised with paths whereas the others not
import logging
from ganeti import errors
from ganeti import constants
from ganeti import utils
def _ParseSize(value):
return int(round(float(value), 0))
class _Base(object):
"""Base class for storage abstraction.
"""
def List(self, name, fields):
"""Returns a list of all entities within the storage unit.
@type name: string or None
@param name: Entity name or None for all
@type fields: list
@param fields: List with all requested result fields (order is preserved)
"""
raise NotImplementedError()
def Modify(self, name, changes): # pylint: disable=W0613
"""Modifies an entity within the storage unit.
@type name: string
@param name: Entity name
@type changes: dict
@param changes: New field values
"""
# Don't raise an error if no changes are requested
if changes:
raise errors.ProgrammerError("Unable to modify the following"
"fields: %r" % (changes.keys(), ))
def Execute(self, name, op):
"""Executes an operation on an entity within the storage unit.
@type name: string
@param name: Entity name
@type op: string
@param op: Operation name
"""
raise NotImplementedError()
class FileStorage(_Base): # pylint: disable=W0223
"""File storage unit.
"""
def __init__(self, paths):
"""Initializes this class.
@type paths: list
@param paths: List of file storage paths
"""
super(FileStorage, self).__init__()
self._paths = paths
def List(self, name, fields):
"""Returns a list of all entities within the storage unit.
See L{_Base.List}.
"""
rows = []
if name is None:
paths = self._paths
else:
paths = [name]
for path in paths:
rows.append(self._ListInner(path, fields))
return rows
@staticmethod
def _ListInner(path, fields):
"""Gathers requested information from directory.
@type path: string
@param path: Path to directory
@type fields: list
@param fields: Requested fields
"""
values = []
# Pre-calculate information in case it's requested more than once
if constants.SF_USED in fields:
dirsize = utils.CalculateDirectorySize(path)
else:
dirsize = None
if constants.SF_FREE in fields or constants.SF_SIZE in fields:
fsstats = utils.GetFilesystemStats(path)
else:
fsstats = None
# Make sure to update constants.VALID_STORAGE_FIELDS when changing fields.
for field_name in fields:
if field_name == constants.SF_NAME:
values.append(path)
elif field_name == constants.SF_USED:
values.append(dirsize)
elif field_name == constants.SF_FREE:
values.append(fsstats[1])
elif field_name == constants.SF_SIZE:
values.append(fsstats[0])
elif field_name == constants.SF_ALLOCATABLE:
values.append(True)
else:
raise errors.StorageError("Unknown field: %r" % field_name)
return values
class _LvmBase(_Base): # pylint: disable=W0223
"""Base class for LVM storage containers.
@cvar LIST_FIELDS: list of tuples consisting of three elements: SF_*
constants, lvm command output fields (list), and conversion
function or static value (for static value, the lvm output field
can be an empty list)
"""
LIST_SEP = "|"
LIST_COMMAND = None
LIST_FIELDS = None
def List(self, name, wanted_field_names):
"""Returns a list of all entities within the storage unit.
See L{_Base.List}.
"""
# Get needed LVM fields
lvm_fields = self._GetLvmFields(self.LIST_FIELDS, wanted_field_names)
# Build LVM command
cmd_args = self._BuildListCommand(self.LIST_COMMAND, self.LIST_SEP,
lvm_fields, name)
# Run LVM command
cmd_result = self._RunListCommand(cmd_args)
# Split and rearrange LVM command output
return self._BuildList(self._SplitList(cmd_result, self.LIST_SEP,
len(lvm_fields)),
self.LIST_FIELDS,
wanted_field_names,
lvm_fields)
@staticmethod
def _GetLvmFields(fields_def, wanted_field_names):
"""Returns unique list of fields wanted from LVM command.
@type fields_def: list
@param fields_def: Field definitions
@type wanted_field_names: list
@param wanted_field_names: List of requested fields
"""
field_to_idx = dict([(field_name, idx)
for (idx, (field_name, _, _)) in
enumerate(fields_def)])
lvm_fields = []
for field_name in wanted_field_names:
try:
idx = field_to_idx[field_name]
except IndexError:
raise errors.StorageError("Unknown field: %r" % field_name)
(_, lvm_names, _) = fields_def[idx]
lvm_fields.extend(lvm_names)
return utils.UniqueSequence(lvm_fields)
@classmethod
def _BuildList(cls, cmd_result, fields_def, wanted_field_names, lvm_fields):
"""Builds the final result list.
@type cmd_result: iterable
@param cmd_result: Iterable of LVM command output (iterable of lists)
@type fields_def: list
@param fields_def: Field definitions
@type wanted_field_names: list
@param wanted_field_names: List of requested fields
@type lvm_fields: list
@param lvm_fields: LVM fields
"""
lvm_name_to_idx = dict([(lvm_name, idx)
for (idx, lvm_name) in enumerate(lvm_fields)])
field_to_idx = dict([(field_name, idx)
for (idx, (field_name, _, _)) in
enumerate(fields_def)])
data = []
for raw_data in cmd_result:
row = []
for field_name in wanted_field_names:
(_, lvm_names, mapper) = fields_def[field_to_idx[field_name]]
values = [raw_data[lvm_name_to_idx[i]] for i in lvm_names]
if callable(mapper):
# we got a function, call it with all the declared fields
val = mapper(*values) # pylint: disable=W0142
elif len(values) == 1:
assert mapper is None, ("Invalid mapper value (neither callable"
" nor None) for one-element fields")
# we don't have a function, but we had a single field
# declared, pass it unchanged
val = values[0]
else:
# let's make sure there are no fields declared (cannot map >
# 1 field without a function)
assert not values, "LVM storage has multi-fields without a function"
val = mapper
row.append(val)
data.append(row)
return data
@staticmethod
def _BuildListCommand(cmd, sep, options, name):
"""Builds LVM command line.
@type cmd: string
@param cmd: Command name
@type sep: string
@param sep: Field separator character
@type options: list of strings
@param options: Wanted LVM fields
@type name: name or None
@param name: Name of requested entity
"""
args = [cmd,
"--noheadings", "--units=m", "--nosuffix",
"--separator", sep,
"--options", ",".join(options)]
if name is not None:
args.append(name)
return args
@staticmethod
def _RunListCommand(args):
"""Run LVM command.
"""
result = utils.RunCmd(args)
if result.failed:
raise errors.StorageError("Failed to run %r, command output: %s" %
(args[0], result.output))
return result.stdout
@staticmethod
def _SplitList(data, sep, fieldcount):
"""Splits LVM command output into rows and fields.
@type data: string
@param data: LVM command output
@type sep: string
@param sep: Field separator character
@type fieldcount: int
@param fieldcount: Expected number of fields
"""
for line in data.splitlines():
fields = line.strip().split(sep)
if len(fields) != fieldcount:
logging.warning("Invalid line returned from lvm command: %s", line)
continue
yield fields
def _LvmPvGetAllocatable(attr):
"""Determines whether LVM PV is allocatable.
@rtype: bool
"""
if attr:
return (attr[0] == "a")
else:
logging.warning("Invalid PV attribute: %r", attr)
return False
class LvmPvStorage(_LvmBase): # pylint: disable=W0223
"""LVM Physical Volume storage unit.
"""
LIST_COMMAND = "pvs"
# Make sure to update constants.VALID_STORAGE_FIELDS when changing field
# definitions.
LIST_FIELDS = [
(constants.SF_NAME, ["pv_name"], None),
(constants.SF_SIZE, ["pv_size"], _ParseSize),
(constants.SF_USED, ["pv_used"], _ParseSize),
(constants.SF_FREE, ["pv_free"], _ParseSize),
(constants.SF_ALLOCATABLE, ["pv_attr"], _LvmPvGetAllocatable),
]
def _SetAllocatable(self, name, allocatable):
"""Sets the "allocatable" flag on a physical volume.
@type name: string
@param name: Physical volume name
@type allocatable: bool
@param allocatable: Whether to set the "allocatable" flag
"""
args = ["pvchange", "--allocatable"]
if allocatable:
args.append("y")
else:
args.append("n")
args.append(name)
result = utils.RunCmd(args)
if result.failed:
raise errors.StorageError("Failed to modify physical volume,"
" pvchange output: %s" %
result.output)
def Modify(self, name, changes):
"""Modifies flags on a physical volume.
See L{_Base.Modify}.
"""
if constants.SF_ALLOCATABLE in changes:
self._SetAllocatable(name, changes[constants.SF_ALLOCATABLE])
del changes[constants.SF_ALLOCATABLE]
# Other changes will be handled (and maybe refused) by the base class.
return _LvmBase.Modify(self, name, changes)
class LvmVgStorage(_LvmBase):
"""LVM Volume Group storage unit.
"""
LIST_COMMAND = "vgs"
VGREDUCE_COMMAND = "vgreduce"
# Make sure to update constants.VALID_STORAGE_FIELDS when changing field
# definitions.
LIST_FIELDS = [
(constants.SF_NAME, ["vg_name"], None),
(constants.SF_SIZE, ["vg_size"], _ParseSize),
(constants.SF_FREE, ["vg_free"], _ParseSize),
(constants.SF_USED, ["vg_size", "vg_free"],
lambda x, y: _ParseSize(x) - _ParseSize(y)),
(constants.SF_ALLOCATABLE, [], True),
]
def _RemoveMissing(self, name, _runcmd_fn=utils.RunCmd):
"""Runs "vgreduce --removemissing" on a volume group.
@type name: string
@param name: Volume group name
"""
# Ignoring vgreduce exit code. Older versions exit with an error even tough
# the VG is already consistent. This was fixed in later versions, but we
# cannot depend on it.
result = _runcmd_fn([self.VGREDUCE_COMMAND, "--removemissing", name])
# Keep output in case something went wrong
vgreduce_output = result.output
# work around newer LVM version
if ("Wrote out consistent volume group" not in vgreduce_output or
"vgreduce --removemissing --force" in vgreduce_output):
# we need to re-run with --force
result = _runcmd_fn([self.VGREDUCE_COMMAND, "--removemissing",
"--force", name])
vgreduce_output += "\n" + result.output
result = _runcmd_fn([self.LIST_COMMAND, "--noheadings",
"--nosuffix", name])
# we also need to check the output
if result.failed or "Couldn't find device with uuid" in result.output:
raise errors.StorageError(("Volume group '%s' still not consistent,"
" 'vgreduce' output: %r,"
" 'vgs' output: %r") %
(name, vgreduce_output, result.output))
def Execute(self, name, op):
"""Executes an operation on a virtual volume.
See L{_Base.Execute}.
"""
if op == constants.SO_FIX_CONSISTENCY:
return self._RemoveMissing(name)
return _LvmBase.Execute(self, name, op)
# Lookup table for storage types
_STORAGE_TYPES = {
constants.ST_FILE: FileStorage,
constants.ST_LVM_PV: LvmPvStorage,
constants.ST_LVM_VG: LvmVgStorage,
constants.ST_SHARED_FILE: FileStorage,
constants.ST_GLUSTER: FileStorage,
}
def GetStorageClass(name):
"""Returns the class for a storage type.
@type name: string
@param name: Storage type
"""
try:
return _STORAGE_TYPES[name]
except KeyError:
raise errors.StorageError("Unknown storage type: %r" % name)
def GetStorage(name, *args):
"""Factory function for storage methods.
@type name: string
@param name: Storage type
"""
return GetStorageClass(name)(*args)