4 # Copyright (C) 2009, 2011 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 """Storage container abstraction.
26 # pylint: disable=W0232,R0201
28 # W0232, since we use these as singletons rather than object holding
31 # R0201, for the same reason
33 # TODO: FileStorage initialised with paths whereas the others not
37 from ganeti import errors
38 from ganeti import constants
39 from ganeti import utils
42 def _ParseSize(value):
43 return int(round(float(value), 0))
47 """Base class for storage abstraction.
50 def List(self, name, fields):
51 """Returns a list of all entities within the storage unit.
53 @type name: string or None
54 @param name: Entity name or None for all
56 @param fields: List with all requested result fields (order is preserved)
59 raise NotImplementedError()
61 def Modify(self, name, changes): # pylint: disable=W0613
62 """Modifies an entity within the storage unit.
65 @param name: Entity name
67 @param changes: New field values
70 # Don't raise an error if no changes are requested
72 raise errors.ProgrammerError("Unable to modify the following"
73 "fields: %r" % (changes.keys(), ))
75 def Execute(self, name, op):
76 """Executes an operation on an entity within the storage unit.
79 @param name: Entity name
81 @param op: Operation name
84 raise NotImplementedError()
87 class FileStorage(_Base): # pylint: disable=W0223
91 def __init__(self, paths):
92 """Initializes this class.
95 @param paths: List of file storage paths
100 def List(self, name, fields):
101 """Returns a list of all entities within the storage unit.
114 rows.append(self._ListInner(path, fields))
119 def _ListInner(path, fields):
120 """Gathers requested information from directory.
123 @param path: Path to directory
125 @param fields: Requested fields
130 # Pre-calculate information in case it's requested more than once
131 if constants.SF_USED in fields:
132 dirsize = utils.CalculateDirectorySize(path)
136 if constants.SF_FREE in fields or constants.SF_SIZE in fields:
137 fsstats = utils.GetFilesystemStats(path)
141 # Make sure to update constants.VALID_STORAGE_FIELDS when changing fields.
142 for field_name in fields:
143 if field_name == constants.SF_NAME:
146 elif field_name == constants.SF_USED:
147 values.append(dirsize)
149 elif field_name == constants.SF_FREE:
150 values.append(fsstats[1])
152 elif field_name == constants.SF_SIZE:
153 values.append(fsstats[0])
155 elif field_name == constants.SF_ALLOCATABLE:
159 raise errors.StorageError("Unknown field: %r" % field_name)
164 class _LvmBase(_Base): # pylint: disable=W0223
165 """Base class for LVM storage containers.
167 @cvar LIST_FIELDS: list of tuples consisting of three elements: SF_*
168 constants, lvm command output fields (list), and conversion
169 function or static value (for static value, the lvm output field
170 can be an empty list)
177 def List(self, name, wanted_field_names):
178 """Returns a list of all entities within the storage unit.
183 # Get needed LVM fields
184 lvm_fields = self._GetLvmFields(self.LIST_FIELDS, wanted_field_names)
187 cmd_args = self._BuildListCommand(self.LIST_COMMAND, self.LIST_SEP,
191 cmd_result = self._RunListCommand(cmd_args)
193 # Split and rearrange LVM command output
194 return self._BuildList(self._SplitList(cmd_result, self.LIST_SEP,
201 def _GetLvmFields(fields_def, wanted_field_names):
202 """Returns unique list of fields wanted from LVM command.
204 @type fields_def: list
205 @param fields_def: Field definitions
206 @type wanted_field_names: list
207 @param wanted_field_names: List of requested fields
210 field_to_idx = dict([(field_name, idx)
211 for (idx, (field_name, _, _)) in
212 enumerate(fields_def)])
216 for field_name in wanted_field_names:
218 idx = field_to_idx[field_name]
220 raise errors.StorageError("Unknown field: %r" % field_name)
222 (_, lvm_names, _) = fields_def[idx]
224 lvm_fields.extend(lvm_names)
226 return utils.UniqueSequence(lvm_fields)
229 def _BuildList(cls, cmd_result, fields_def, wanted_field_names, lvm_fields):
230 """Builds the final result list.
232 @type cmd_result: iterable
233 @param cmd_result: Iterable of LVM command output (iterable of lists)
234 @type fields_def: list
235 @param fields_def: Field definitions
236 @type wanted_field_names: list
237 @param wanted_field_names: List of requested fields
238 @type lvm_fields: list
239 @param lvm_fields: LVM fields
242 lvm_name_to_idx = dict([(lvm_name, idx)
243 for (idx, lvm_name) in enumerate(lvm_fields)])
244 field_to_idx = dict([(field_name, idx)
245 for (idx, (field_name, _, _)) in
246 enumerate(fields_def)])
249 for raw_data in cmd_result:
252 for field_name in wanted_field_names:
253 (_, lvm_names, mapper) = fields_def[field_to_idx[field_name]]
255 values = [raw_data[lvm_name_to_idx[i]] for i in lvm_names]
258 # we got a function, call it with all the declared fields
259 val = mapper(*values) # pylint: disable=W0142
260 elif len(values) == 1:
261 assert mapper is None, ("Invalid mapper value (neither callable"
262 " nor None) for one-element fields")
263 # we don't have a function, but we had a single field
264 # declared, pass it unchanged
267 # let's make sure there are no fields declared (cannot map >
268 # 1 field without a function)
269 assert not values, "LVM storage has multi-fields without a function"
279 def _BuildListCommand(cmd, sep, options, name):
280 """Builds LVM command line.
283 @param cmd: Command name
285 @param sep: Field separator character
286 @type options: list of strings
287 @param options: Wanted LVM fields
288 @type name: name or None
289 @param name: Name of requested entity
293 "--noheadings", "--units=m", "--nosuffix",
295 "--options", ",".join(options)]
303 def _RunListCommand(args):
307 result = utils.RunCmd(args)
310 raise errors.StorageError("Failed to run %r, command output: %s" %
311 (args[0], result.output))
316 def _SplitList(data, sep, fieldcount):
317 """Splits LVM command output into rows and fields.
320 @param data: LVM command output
322 @param sep: Field separator character
323 @type fieldcount: int
324 @param fieldcount: Expected number of fields
327 for line in data.splitlines():
328 fields = line.strip().split(sep)
330 if len(fields) != fieldcount:
331 logging.warning("Invalid line returned from lvm command: %s", line)
337 def _LvmPvGetAllocatable(attr):
338 """Determines whether LVM PV is allocatable.
344 return (attr[0] == "a")
346 logging.warning("Invalid PV attribute: %r", attr)
350 class LvmPvStorage(_LvmBase): # pylint: disable=W0223
351 """LVM Physical Volume storage unit.
356 # Make sure to update constants.VALID_STORAGE_FIELDS when changing field
359 (constants.SF_NAME, ["pv_name"], None),
360 (constants.SF_SIZE, ["pv_size"], _ParseSize),
361 (constants.SF_USED, ["pv_used"], _ParseSize),
362 (constants.SF_FREE, ["pv_free"], _ParseSize),
363 (constants.SF_ALLOCATABLE, ["pv_attr"], _LvmPvGetAllocatable),
366 def _SetAllocatable(self, name, allocatable):
367 """Sets the "allocatable" flag on a physical volume.
370 @param name: Physical volume name
371 @type allocatable: bool
372 @param allocatable: Whether to set the "allocatable" flag
375 args = ["pvchange", "--allocatable"]
384 result = utils.RunCmd(args)
386 raise errors.StorageError("Failed to modify physical volume,"
387 " pvchange output: %s" %
390 def Modify(self, name, changes):
391 """Modifies flags on a physical volume.
396 if constants.SF_ALLOCATABLE in changes:
397 self._SetAllocatable(name, changes[constants.SF_ALLOCATABLE])
398 del changes[constants.SF_ALLOCATABLE]
400 # Other changes will be handled (and maybe refused) by the base class.
401 return _LvmBase.Modify(self, name, changes)
404 class LvmVgStorage(_LvmBase):
405 """LVM Volume Group storage unit.
410 # Make sure to update constants.VALID_STORAGE_FIELDS when changing field
413 (constants.SF_NAME, ["vg_name"], None),
414 (constants.SF_SIZE, ["vg_size"], _ParseSize),
415 (constants.SF_FREE, ["vg_free"], _ParseSize),
416 (constants.SF_USED, ["vg_size", "vg_free"],
417 lambda x, y: _ParseSize(x) - _ParseSize(y)),
418 (constants.SF_ALLOCATABLE, [], True),
421 def _RemoveMissing(self, name):
422 """Runs "vgreduce --removemissing" on a volume group.
425 @param name: Volume group name
428 # Ignoring vgreduce exit code. Older versions exit with an error even tough
429 # the VG is already consistent. This was fixed in later versions, but we
430 # cannot depend on it.
431 result = utils.RunCmd(["vgreduce", "--removemissing", name])
433 # Keep output in case something went wrong
434 vgreduce_output = result.output
436 result = utils.RunCmd(["vgs", "--noheadings", "--nosuffix", name])
438 raise errors.StorageError(("Volume group '%s' still not consistent,"
439 " 'vgreduce' output: %r,"
440 " 'vgs' output: %r") %
441 (name, vgreduce_output, result.output))
443 def Execute(self, name, op):
444 """Executes an operation on a virtual volume.
446 See L{_Base.Execute}.
449 if op == constants.SO_FIX_CONSISTENCY:
450 return self._RemoveMissing(name)
452 return _LvmBase.Execute(self, name, op)
455 # Lookup table for storage types
457 constants.ST_FILE: FileStorage,
458 constants.ST_LVM_PV: LvmPvStorage,
459 constants.ST_LVM_VG: LvmVgStorage,
463 def GetStorageClass(name):
464 """Returns the class for a storage type.
467 @param name: Storage type
471 return _STORAGE_TYPES[name]
473 raise errors.StorageError("Unknown storage type: %r" % name)
476 def GetStorage(name, *args):
477 """Factory function for storage methods.
480 @param name: Storage type
483 return GetStorageClass(name)(*args)