X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/4b37cac57356e1959664777acfddd0f3efb23cca..b76d4aaf4b54b049658cbf3dbd762431ecfcc51d:/lib/storage.py?ds=sidebyside diff --git a/lib/storage.py b/lib/storage.py index 19563bf..d77d80b 100644 --- a/lib/storage.py +++ b/lib/storage.py @@ -1,7 +1,7 @@ # # -# Copyright (C) 2009 Google Inc. +# Copyright (C) 2009, 2011, 2012 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 @@ -23,6 +23,14 @@ """ +# 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 @@ -50,7 +58,7 @@ class _Base: """ raise NotImplementedError() - def Modify(self, name, changes): + def Modify(self, name, changes): # pylint: disable=W0613 """Modifies an entity within the storage unit. @type name: string @@ -76,7 +84,7 @@ class _Base: raise NotImplementedError() -class FileStorage(_Base): +class FileStorage(_Base): # pylint: disable=W0223 """File storage unit. """ @@ -125,10 +133,10 @@ class FileStorage(_Base): else: dirsize = None - if constants.SF_FREE in fields: - fsfree = utils.GetFreeFilesystemSpace(path) + if constants.SF_FREE in fields or constants.SF_SIZE in fields: + fsstats = utils.GetFilesystemStats(path) else: - fsfree = None + fsstats = None # Make sure to update constants.VALID_STORAGE_FIELDS when changing fields. for field_name in fields: @@ -139,7 +147,13 @@ class FileStorage(_Base): values.append(dirsize) elif field_name == constants.SF_FREE: - values.append(fsfree) + 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) @@ -147,9 +161,14 @@ class FileStorage(_Base): return values -class _LvmBase(_Base): +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 @@ -189,7 +208,8 @@ class _LvmBase(_Base): """ field_to_idx = dict([(field_name, idx) - for (idx, (field_name, _, _)) in enumerate(fields_def)]) + for (idx, (field_name, _, _)) in + enumerate(fields_def)]) lvm_fields = [] @@ -199,9 +219,9 @@ class _LvmBase(_Base): except IndexError: raise errors.StorageError("Unknown field: %r" % field_name) - (_, lvm_name, _) = fields_def[idx] + (_, lvm_names, _) = fields_def[idx] - lvm_fields.append(lvm_name) + lvm_fields.extend(lvm_names) return utils.UniqueSequence(lvm_fields) @@ -222,21 +242,34 @@ class _LvmBase(_Base): 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)]) + for (idx, (field_name, _, _)) in + enumerate(fields_def)]) data = [] for raw_data in cmd_result: row = [] for field_name in wanted_field_names: - (_, lvm_name, convert_fn) = fields_def[field_to_idx[field_name]] - - value = raw_data[lvm_name_to_idx[lvm_name]] - - if convert_fn: - value = convert_fn(value) - - row.append(value) + (_, 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) @@ -295,32 +328,39 @@ class _LvmBase(_Base): fields = line.strip().split(sep) if len(fields) != fieldcount: + logging.warning("Invalid line returned from lvm command: %s", line) continue yield fields -class LvmPvStorage(_LvmBase): - """LVM Physical Volume storage unit. +def _LvmPvGetAllocatable(attr): + """Determines whether LVM PV is allocatable. + + @rtype: bool """ - def _GetAllocatable(attr): - if attr: - return (attr[0] == "a") - else: - logging.warning("Invalid PV attribute: %r", attr) - return False + 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", _GetAllocatable), + (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): @@ -366,14 +406,62 @@ class LvmVgStorage(_LvmBase): """ 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_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 = {