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 enumerate(fields_def)])
196 for field_name in wanted_field_names:
198 idx = field_to_idx[field_name]
200 raise errors.StorageError("Unknown field: %r" % field_name)
202 (_, lvm_name, _) = fields_def[idx]
204 lvm_fields.append(lvm_name)
206 return utils.UniqueSequence(lvm_fields)
209 def _BuildList(cls, cmd_result, fields_def, wanted_field_names, lvm_fields):
210 """Builds the final result list.
212 @type cmd_result: iterable
213 @param cmd_result: Iterable of LVM command output (iterable of lists)
214 @type fields_def: list
215 @param fields_def: Field definitions
216 @type wanted_field_names: list
217 @param wanted_field_names: List of requested fields
218 @type lvm_fields: list
219 @param lvm_fields: LVM fields
222 lvm_name_to_idx = dict([(lvm_name, idx)
223 for (idx, lvm_name) in enumerate(lvm_fields)])
224 field_to_idx = dict([(field_name, idx)
225 for (idx, (field_name, _, _)) in enumerate(fields_def)])
228 for raw_data in cmd_result:
231 for field_name in wanted_field_names:
232 (_, lvm_name, convert_fn) = fields_def[field_to_idx[field_name]]
234 value = raw_data[lvm_name_to_idx[lvm_name]]
237 value = convert_fn(value)
246 def _BuildListCommand(cmd, sep, options, name):
247 """Builds LVM command line.
250 @param cmd: Command name
252 @param sep: Field separator character
253 @type options: list of strings
254 @param options: Wanted LVM fields
255 @type name: name or None
256 @param name: Name of requested entity
260 "--noheadings", "--units=m", "--nosuffix",
262 "--options", ",".join(options)]
270 def _RunListCommand(args):
274 result = utils.RunCmd(args)
277 raise errors.StorageError("Failed to run %r, command output: %s" %
278 (args[0], result.output))
283 def _SplitList(data, sep, fieldcount):
284 """Splits LVM command output into rows and fields.
287 @param data: LVM command output
289 @param sep: Field separator character
290 @type fieldcount: int
291 @param fieldcount: Expected number of fields
294 for line in data.splitlines():
295 fields = line.strip().split(sep)
297 if len(fields) != fieldcount:
303 class LvmPvStorage(_LvmBase):
304 """LVM Physical Volume storage unit.
307 def _GetAllocatable(attr):
309 return (attr[0] == "a")
311 logging.warning("Invalid PV attribute: %r", attr)
316 # Make sure to update constants.VALID_STORAGE_FIELDS when changing field
319 (constants.SF_NAME, "pv_name", None),
320 (constants.SF_SIZE, "pv_size", _ParseSize),
321 (constants.SF_USED, "pv_used", _ParseSize),
322 (constants.SF_FREE, "pv_free", _ParseSize),
323 (constants.SF_ALLOCATABLE, "pv_attr", _GetAllocatable),
326 def _SetAllocatable(self, name, allocatable):
327 """Sets the "allocatable" flag on a physical volume.
330 @param name: Physical volume name
331 @type allocatable: bool
332 @param allocatable: Whether to set the "allocatable" flag
335 args = ["pvchange", "--allocatable"]
344 result = utils.RunCmd(args)
346 raise errors.StorageError("Failed to modify physical volume,"
347 " pvchange output: %s" %
350 def Modify(self, name, changes):
351 """Modifies flags on a physical volume.
356 if constants.SF_ALLOCATABLE in changes:
357 self._SetAllocatable(name, changes[constants.SF_ALLOCATABLE])
358 del changes[constants.SF_ALLOCATABLE]
360 # Other changes will be handled (and maybe refused) by the base class.
361 return _LvmBase.Modify(self, name, changes)
364 class LvmVgStorage(_LvmBase):
365 """LVM Volume Group storage unit.
370 # Make sure to update constants.VALID_STORAGE_FIELDS when changing field
373 (constants.SF_NAME, "vg_name", None),
374 (constants.SF_SIZE, "vg_size", _ParseSize),
378 # Lookup table for storage types
380 constants.ST_FILE: FileStorage,
381 constants.ST_LVM_PV: LvmPvStorage,
382 constants.ST_LVM_VG: LvmVgStorage,
386 def GetStorageClass(name):
387 """Returns the class for a storage type.
390 @param name: Storage type
394 return _STORAGE_TYPES[name]
396 raise errors.StorageError("Unknown storage type: %r" % name)
399 def GetStorage(name, *args):
400 """Factory function for storage methods.
403 @param name: Storage type
406 return GetStorageClass(name)(*args)