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.
29 from ganeti import errors
30 from ganeti import constants
31 from ganeti import utils
34 def _ParseSize(value):
35 return int(round(float(value), 0))
39 """Base class for storage abstraction.
42 def List(self, name, fields):
43 """Returns a list of all entities within the storage unit.
45 @type name: string or None
46 @param name: Entity name or None for all
48 @param fields: List with all requested result fields (order is preserved)
51 raise NotImplementedError()
53 def Modify(self, name, changes):
54 """Modifies an entity within the storage unit.
57 @param name: Entity name
59 @param changes: New field values
62 # Don't raise an error if no changes are requested
64 raise errors.ProgrammerError("Unable to modify the following"
65 "fields: %r" % (changes.keys(), ))
67 def Execute(self, name, op):
68 """Executes an operation on an entity within the storage unit.
71 @param name: Entity name
73 @param op: Operation name
76 raise NotImplementedError()
79 class FileStorage(_Base):
83 def __init__(self, paths):
84 """Initializes this class.
87 @param paths: List of file storage paths
92 def List(self, name, fields):
93 """Returns a list of all entities within the storage unit.
106 rows.append(self._ListInner(path, fields))
111 def _ListInner(path, fields):
112 """Gathers requested information from directory.
115 @param path: Path to directory
117 @param fields: Requested fields
122 # Pre-calculate information in case it's requested more than once
123 if constants.SF_USED in fields:
124 dirsize = utils.CalculateDirectorySize(path)
128 if constants.SF_FREE in fields:
129 fsfree = utils.GetFreeFilesystemSpace(path)
133 # Make sure to update constants.VALID_STORAGE_FIELDS when changing fields.
134 for field_name in fields:
135 if field_name == constants.SF_NAME:
138 elif field_name == constants.SF_USED:
139 values.append(dirsize)
141 elif field_name == constants.SF_FREE:
142 values.append(fsfree)
145 raise errors.StorageError("Unknown field: %r" % field_name)
150 class _LvmBase(_Base):
151 """Base class for LVM storage containers.
158 def List(self, name, wanted_field_names):
159 """Returns a list of all entities within the storage unit.
164 # Get needed LVM fields
165 lvm_fields = self._GetLvmFields(self.LIST_FIELDS, wanted_field_names)
168 cmd_args = self._BuildListCommand(self.LIST_COMMAND, self.LIST_SEP,
172 cmd_result = self._RunListCommand(cmd_args)
174 # Split and rearrange LVM command output
175 return self._BuildList(self._SplitList(cmd_result, self.LIST_SEP,
182 def _GetLvmFields(fields_def, wanted_field_names):
183 """Returns unique list of fields wanted from LVM command.
185 @type fields_def: list
186 @param fields_def: Field definitions
187 @type wanted_field_names: list
188 @param wanted_field_names: List of requested fields
191 field_to_idx = dict([(field_name, idx)
192 for (idx, (field_name, _, _)) in
193 enumerate(fields_def)])
197 for field_name in wanted_field_names:
199 idx = field_to_idx[field_name]
201 raise errors.StorageError("Unknown field: %r" % field_name)
203 (_, lvm_name, _) = fields_def[idx]
205 lvm_fields.append(lvm_name)
207 return utils.UniqueSequence(lvm_fields)
210 def _BuildList(cls, cmd_result, fields_def, wanted_field_names, lvm_fields):
211 """Builds the final result list.
213 @type cmd_result: iterable
214 @param cmd_result: Iterable of LVM command output (iterable of lists)
215 @type fields_def: list
216 @param fields_def: Field definitions
217 @type wanted_field_names: list
218 @param wanted_field_names: List of requested fields
219 @type lvm_fields: list
220 @param lvm_fields: LVM fields
223 lvm_name_to_idx = dict([(lvm_name, idx)
224 for (idx, lvm_name) in enumerate(lvm_fields)])
225 field_to_idx = dict([(field_name, idx)
226 for (idx, (field_name, _, _)) in
227 enumerate(fields_def)])
230 for raw_data in cmd_result:
233 for field_name in wanted_field_names:
234 (_, lvm_name, convert_fn) = fields_def[field_to_idx[field_name]]
236 value = raw_data[lvm_name_to_idx[lvm_name]]
239 value = convert_fn(value)
248 def _BuildListCommand(cmd, sep, options, name):
249 """Builds LVM command line.
252 @param cmd: Command name
254 @param sep: Field separator character
255 @type options: list of strings
256 @param options: Wanted LVM fields
257 @type name: name or None
258 @param name: Name of requested entity
262 "--noheadings", "--units=m", "--nosuffix",
264 "--options", ",".join(options)]
272 def _RunListCommand(args):
276 result = utils.RunCmd(args)
279 raise errors.StorageError("Failed to run %r, command output: %s" %
280 (args[0], result.output))
285 def _SplitList(data, sep, fieldcount):
286 """Splits LVM command output into rows and fields.
289 @param data: LVM command output
291 @param sep: Field separator character
292 @type fieldcount: int
293 @param fieldcount: Expected number of fields
296 for line in data.splitlines():
297 fields = line.strip().split(sep)
299 if len(fields) != fieldcount:
305 class LvmPvStorage(_LvmBase):
306 """LVM Physical Volume storage unit.
309 def _GetAllocatable(attr):
311 return (attr[0] == "a")
313 logging.warning("Invalid PV attribute: %r", attr)
318 # Make sure to update constants.VALID_STORAGE_FIELDS when changing field
321 (constants.SF_NAME, "pv_name", None),
322 (constants.SF_SIZE, "pv_size", _ParseSize),
323 (constants.SF_USED, "pv_used", _ParseSize),
324 (constants.SF_FREE, "pv_free", _ParseSize),
325 (constants.SF_ALLOCATABLE, "pv_attr", _GetAllocatable),
328 def _SetAllocatable(self, name, allocatable):
329 """Sets the "allocatable" flag on a physical volume.
332 @param name: Physical volume name
333 @type allocatable: bool
334 @param allocatable: Whether to set the "allocatable" flag
337 args = ["pvchange", "--allocatable"]
346 result = utils.RunCmd(args)
348 raise errors.StorageError("Failed to modify physical volume,"
349 " pvchange output: %s" %
352 def Modify(self, name, changes):
353 """Modifies flags on a physical volume.
358 if constants.SF_ALLOCATABLE in changes:
359 self._SetAllocatable(name, changes[constants.SF_ALLOCATABLE])
360 del changes[constants.SF_ALLOCATABLE]
362 # Other changes will be handled (and maybe refused) by the base class.
363 return _LvmBase.Modify(self, name, changes)
366 class LvmVgStorage(_LvmBase):
367 """LVM Volume Group storage unit.
372 # Make sure to update constants.VALID_STORAGE_FIELDS when changing field
375 (constants.SF_NAME, "vg_name", None),
376 (constants.SF_SIZE, "vg_size", _ParseSize),
379 def _RemoveMissing(self, name):
380 """Runs "vgreduce --removemissing" on a volume group.
383 @param name: Volume group name
386 # Ignoring vgreduce exit code. Older versions exit with an error even tough
387 # the VG is already consistent. This was fixed in later versions, but we
388 # cannot depend on it.
389 result = utils.RunCmd(["vgreduce", "--removemissing", name])
391 # Keep output in case something went wrong
392 vgreduce_output = result.output
394 result = utils.RunCmd(["vgs", "--noheadings", "--nosuffix", name])
396 raise errors.StorageError(("Volume group '%s' still not consistent,"
397 " 'vgreduce' output: %r,"
398 " 'vgs' output: %r") %
399 (name, vgreduce_output, result.output))
401 def Execute(self, name, op):
402 """Executes an operation on a virtual volume.
404 See L{_Base.Execute}.
407 if op == constants.SO_FIX_CONSISTENCY:
408 return self._RemoveMissing(name)
410 return _LvmBase.Execute(self, name, op)
413 # Lookup table for storage types
415 constants.ST_FILE: FileStorage,
416 constants.ST_LVM_PV: LvmPvStorage,
417 constants.ST_LVM_VG: LvmVgStorage,
421 def GetStorageClass(name):
422 """Returns the class for a storage type.
425 @param name: Storage type
429 return _STORAGE_TYPES[name]
431 raise errors.StorageError("Unknown storage type: %r" % name)
434 def GetStorage(name, *args):
435 """Factory function for storage methods.
438 @param name: Storage type
441 return GetStorageClass(name)(*args)