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 or constants.SF_SIZE in fields:
129 fsstats = utils.GetFilesystemStats(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(fsstats[1])
144 elif field_name == constants.SF_SIZE:
145 values.append(fsstats[0])
147 elif field_name == constants.SF_ALLOCATABLE:
151 raise errors.StorageError("Unknown field: %r" % field_name)
156 class _LvmBase(_Base):
157 """Base class for LVM storage containers.
159 @cvar LIST_FIELDS: list of tuples consisting of three elements: SF_*
160 constants, lvm command output fields (list), and conversion
161 function or static value (for static value, the lvm output field
162 can be an empty list)
169 def List(self, name, wanted_field_names):
170 """Returns a list of all entities within the storage unit.
175 # Get needed LVM fields
176 lvm_fields = self._GetLvmFields(self.LIST_FIELDS, wanted_field_names)
179 cmd_args = self._BuildListCommand(self.LIST_COMMAND, self.LIST_SEP,
183 cmd_result = self._RunListCommand(cmd_args)
185 # Split and rearrange LVM command output
186 return self._BuildList(self._SplitList(cmd_result, self.LIST_SEP,
193 def _GetLvmFields(fields_def, wanted_field_names):
194 """Returns unique list of fields wanted from LVM command.
196 @type fields_def: list
197 @param fields_def: Field definitions
198 @type wanted_field_names: list
199 @param wanted_field_names: List of requested fields
202 field_to_idx = dict([(field_name, idx)
203 for (idx, (field_name, _, _)) in
204 enumerate(fields_def)])
208 for field_name in wanted_field_names:
210 idx = field_to_idx[field_name]
212 raise errors.StorageError("Unknown field: %r" % field_name)
214 (_, lvm_names, _) = fields_def[idx]
216 lvm_fields.extend(lvm_names)
218 return utils.UniqueSequence(lvm_fields)
221 def _BuildList(cls, cmd_result, fields_def, wanted_field_names, lvm_fields):
222 """Builds the final result list.
224 @type cmd_result: iterable
225 @param cmd_result: Iterable of LVM command output (iterable of lists)
226 @type fields_def: list
227 @param fields_def: Field definitions
228 @type wanted_field_names: list
229 @param wanted_field_names: List of requested fields
230 @type lvm_fields: list
231 @param lvm_fields: LVM fields
234 lvm_name_to_idx = dict([(lvm_name, idx)
235 for (idx, lvm_name) in enumerate(lvm_fields)])
236 field_to_idx = dict([(field_name, idx)
237 for (idx, (field_name, _, _)) in
238 enumerate(fields_def)])
241 for raw_data in cmd_result:
244 for field_name in wanted_field_names:
245 (_, lvm_names, mapper) = fields_def[field_to_idx[field_name]]
247 values = [raw_data[lvm_name_to_idx[i]] for i in lvm_names]
250 # we got a function, call it with all the declared fields
251 val = mapper(*values)
252 elif len(values) == 1:
253 # we don't have a function, but we had a single field
254 # declared, pass it unchanged
257 # let's make sure there are no fields declared (cannot map >
258 # 1 field without a function)
259 assert not values, "LVM storage has multi-fields without a function"
269 def _BuildListCommand(cmd, sep, options, name):
270 """Builds LVM command line.
273 @param cmd: Command name
275 @param sep: Field separator character
276 @type options: list of strings
277 @param options: Wanted LVM fields
278 @type name: name or None
279 @param name: Name of requested entity
283 "--noheadings", "--units=m", "--nosuffix",
285 "--options", ",".join(options)]
293 def _RunListCommand(args):
297 result = utils.RunCmd(args)
300 raise errors.StorageError("Failed to run %r, command output: %s" %
301 (args[0], result.output))
306 def _SplitList(data, sep, fieldcount):
307 """Splits LVM command output into rows and fields.
310 @param data: LVM command output
312 @param sep: Field separator character
313 @type fieldcount: int
314 @param fieldcount: Expected number of fields
317 for line in data.splitlines():
318 fields = line.strip().split(sep)
320 if len(fields) != fieldcount:
321 logging.warning("Invalid line returned from lvm command: %s", line)
327 class LvmPvStorage(_LvmBase):
328 """LVM Physical Volume storage unit.
331 def _GetAllocatable(attr):
333 return (attr[0] == "a")
335 logging.warning("Invalid PV attribute: %r", attr)
340 # Make sure to update constants.VALID_STORAGE_FIELDS when changing field
343 (constants.SF_NAME, ["pv_name"], None),
344 (constants.SF_SIZE, ["pv_size"], _ParseSize),
345 (constants.SF_USED, ["pv_used"], _ParseSize),
346 (constants.SF_FREE, ["pv_free"], _ParseSize),
347 (constants.SF_ALLOCATABLE, ["pv_attr"], _GetAllocatable),
350 def _SetAllocatable(self, name, allocatable):
351 """Sets the "allocatable" flag on a physical volume.
354 @param name: Physical volume name
355 @type allocatable: bool
356 @param allocatable: Whether to set the "allocatable" flag
359 args = ["pvchange", "--allocatable"]
368 result = utils.RunCmd(args)
370 raise errors.StorageError("Failed to modify physical volume,"
371 " pvchange output: %s" %
374 def Modify(self, name, changes):
375 """Modifies flags on a physical volume.
380 if constants.SF_ALLOCATABLE in changes:
381 self._SetAllocatable(name, changes[constants.SF_ALLOCATABLE])
382 del changes[constants.SF_ALLOCATABLE]
384 # Other changes will be handled (and maybe refused) by the base class.
385 return _LvmBase.Modify(self, name, changes)
388 class LvmVgStorage(_LvmBase):
389 """LVM Volume Group storage unit.
394 # Make sure to update constants.VALID_STORAGE_FIELDS when changing field
397 (constants.SF_NAME, ["vg_name"], None),
398 (constants.SF_SIZE, ["vg_size"], _ParseSize),
399 (constants.SF_FREE, ["vg_free"], _ParseSize),
400 (constants.SF_USED, ["vg_size", "vg_free"],
401 lambda x, y: _ParseSize(x) - _ParseSize(y)),
402 (constants.SF_ALLOCATABLE, [], True),
405 def _RemoveMissing(self, name):
406 """Runs "vgreduce --removemissing" on a volume group.
409 @param name: Volume group name
412 # Ignoring vgreduce exit code. Older versions exit with an error even tough
413 # the VG is already consistent. This was fixed in later versions, but we
414 # cannot depend on it.
415 result = utils.RunCmd(["vgreduce", "--removemissing", name])
417 # Keep output in case something went wrong
418 vgreduce_output = result.output
420 result = utils.RunCmd(["vgs", "--noheadings", "--nosuffix", name])
422 raise errors.StorageError(("Volume group '%s' still not consistent,"
423 " 'vgreduce' output: %r,"
424 " 'vgs' output: %r") %
425 (name, vgreduce_output, result.output))
427 def Execute(self, name, op):
428 """Executes an operation on a virtual volume.
430 See L{_Base.Execute}.
433 if op == constants.SO_FIX_CONSISTENCY:
434 return self._RemoveMissing(name)
436 return _LvmBase.Execute(self, name, op)
439 # Lookup table for storage types
441 constants.ST_FILE: FileStorage,
442 constants.ST_LVM_PV: LvmPvStorage,
443 constants.ST_LVM_VG: LvmVgStorage,
447 def GetStorageClass(name):
448 """Returns the class for a storage type.
451 @param name: Storage type
455 return _STORAGE_TYPES[name]
457 raise errors.StorageError("Unknown storage type: %r" % name)
460 def GetStorage(name, *args):
461 """Factory function for storage methods.
464 @param name: Storage type
467 return GetStorageClass(name)(*args)