4 # Copyright (C) 2009 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-msg=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-msg=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-msg=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-msg=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-msg=W0142
260 elif len(values) == 1:
261 # we don't have a function, but we had a single field
262 # declared, pass it unchanged
265 # let's make sure there are no fields declared (cannot map >
266 # 1 field without a function)
267 assert not values, "LVM storage has multi-fields without a function"
277 def _BuildListCommand(cmd, sep, options, name):
278 """Builds LVM command line.
281 @param cmd: Command name
283 @param sep: Field separator character
284 @type options: list of strings
285 @param options: Wanted LVM fields
286 @type name: name or None
287 @param name: Name of requested entity
291 "--noheadings", "--units=m", "--nosuffix",
293 "--options", ",".join(options)]
301 def _RunListCommand(args):
305 result = utils.RunCmd(args)
308 raise errors.StorageError("Failed to run %r, command output: %s" %
309 (args[0], result.output))
314 def _SplitList(data, sep, fieldcount):
315 """Splits LVM command output into rows and fields.
318 @param data: LVM command output
320 @param sep: Field separator character
321 @type fieldcount: int
322 @param fieldcount: Expected number of fields
325 for line in data.splitlines():
326 fields = line.strip().split(sep)
328 if len(fields) != fieldcount:
329 logging.warning("Invalid line returned from lvm command: %s", line)
335 class LvmPvStorage(_LvmBase): # pylint: disable-msg=W0223
336 """LVM Physical Volume storage unit.
340 def _GetAllocatable(attr):
342 return (attr[0] == "a")
344 logging.warning("Invalid PV attribute: %r", attr)
349 # Make sure to update constants.VALID_STORAGE_FIELDS when changing field
352 (constants.SF_NAME, ["pv_name"], None),
353 (constants.SF_SIZE, ["pv_size"], _ParseSize),
354 (constants.SF_USED, ["pv_used"], _ParseSize),
355 (constants.SF_FREE, ["pv_free"], _ParseSize),
356 (constants.SF_ALLOCATABLE, ["pv_attr"], _GetAllocatable),
359 def _SetAllocatable(self, name, allocatable):
360 """Sets the "allocatable" flag on a physical volume.
363 @param name: Physical volume name
364 @type allocatable: bool
365 @param allocatable: Whether to set the "allocatable" flag
368 args = ["pvchange", "--allocatable"]
377 result = utils.RunCmd(args)
379 raise errors.StorageError("Failed to modify physical volume,"
380 " pvchange output: %s" %
383 def Modify(self, name, changes):
384 """Modifies flags on a physical volume.
389 if constants.SF_ALLOCATABLE in changes:
390 self._SetAllocatable(name, changes[constants.SF_ALLOCATABLE])
391 del changes[constants.SF_ALLOCATABLE]
393 # Other changes will be handled (and maybe refused) by the base class.
394 return _LvmBase.Modify(self, name, changes)
397 class LvmVgStorage(_LvmBase):
398 """LVM Volume Group storage unit.
403 # Make sure to update constants.VALID_STORAGE_FIELDS when changing field
406 (constants.SF_NAME, ["vg_name"], None),
407 (constants.SF_SIZE, ["vg_size"], _ParseSize),
408 (constants.SF_FREE, ["vg_free"], _ParseSize),
409 (constants.SF_USED, ["vg_size", "vg_free"],
410 lambda x, y: _ParseSize(x) - _ParseSize(y)),
411 (constants.SF_ALLOCATABLE, [], True),
414 def _RemoveMissing(self, name):
415 """Runs "vgreduce --removemissing" on a volume group.
418 @param name: Volume group name
421 # Ignoring vgreduce exit code. Older versions exit with an error even tough
422 # the VG is already consistent. This was fixed in later versions, but we
423 # cannot depend on it.
424 result = utils.RunCmd(["vgreduce", "--removemissing", name])
426 # Keep output in case something went wrong
427 vgreduce_output = result.output
429 result = utils.RunCmd(["vgs", "--noheadings", "--nosuffix", name])
431 raise errors.StorageError(("Volume group '%s' still not consistent,"
432 " 'vgreduce' output: %r,"
433 " 'vgs' output: %r") %
434 (name, vgreduce_output, result.output))
436 def Execute(self, name, op):
437 """Executes an operation on a virtual volume.
439 See L{_Base.Execute}.
442 if op == constants.SO_FIX_CONSISTENCY:
443 return self._RemoveMissing(name)
445 return _LvmBase.Execute(self, name, op)
448 # Lookup table for storage types
450 constants.ST_FILE: FileStorage,
451 constants.ST_LVM_PV: LvmPvStorage,
452 constants.ST_LVM_VG: LvmVgStorage,
456 def GetStorageClass(name):
457 """Returns the class for a storage type.
460 @param name: Storage type
464 return _STORAGE_TYPES[name]
466 raise errors.StorageError("Unknown storage type: %r" % name)
469 def GetStorage(name, *args):
470 """Factory function for storage methods.
473 @param name: Storage type
476 return GetStorageClass(name)(*args)