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(), ))
68 class FileStorage(_Base):
72 def __init__(self, paths):
73 """Initializes this class.
76 @param paths: List of file storage paths
81 def List(self, name, fields):
82 """Returns a list of all entities within the storage unit.
95 rows.append(self._ListInner(path, fields))
100 def _ListInner(path, fields):
101 """Gathers requested information from directory.
104 @param path: Path to directory
106 @param fields: Requested fields
111 # Pre-calculate information in case it's requested more than once
112 if constants.SF_USED in fields:
113 dirsize = utils.CalculateDirectorySize(path)
117 if constants.SF_FREE in fields:
118 fsfree = utils.GetFreeFilesystemSpace(path)
122 # Make sure to update constants.VALID_STORAGE_FIELDS when changing fields.
123 for field_name in fields:
124 if field_name == constants.SF_NAME:
127 elif field_name == constants.SF_USED:
128 values.append(dirsize)
130 elif field_name == constants.SF_FREE:
131 values.append(fsfree)
134 raise errors.StorageError("Unknown field: %r" % field_name)
139 class _LvmBase(_Base):
140 """Base class for LVM storage containers.
147 def List(self, name, wanted_field_names):
148 """Returns a list of all entities within the storage unit.
153 # Get needed LVM fields
154 lvm_fields = self._GetLvmFields(self.LIST_FIELDS, wanted_field_names)
157 cmd_args = self._BuildListCommand(self.LIST_COMMAND, self.LIST_SEP,
161 cmd_result = self._RunListCommand(cmd_args)
163 # Split and rearrange LVM command output
164 return self._BuildList(self._SplitList(cmd_result, self.LIST_SEP,
171 def _GetLvmFields(fields_def, wanted_field_names):
172 """Returns unique list of fields wanted from LVM command.
174 @type fields_def: list
175 @param fields_def: Field definitions
176 @type wanted_field_names: list
177 @param wanted_field_names: List of requested fields
180 field_to_idx = dict([(field_name, idx)
181 for (idx, (field_name, _, _)) in enumerate(fields_def)])
185 for field_name in wanted_field_names:
187 idx = field_to_idx[field_name]
189 raise errors.StorageError("Unknown field: %r" % field_name)
191 (_, lvm_name, _) = fields_def[idx]
193 lvm_fields.append(lvm_name)
195 return utils.UniqueSequence(lvm_fields)
198 def _BuildList(cls, cmd_result, fields_def, wanted_field_names, lvm_fields):
199 """Builds the final result list.
201 @type cmd_result: iterable
202 @param cmd_result: Iterable of LVM command output (iterable of lists)
203 @type fields_def: list
204 @param fields_def: Field definitions
205 @type wanted_field_names: list
206 @param wanted_field_names: List of requested fields
207 @type lvm_fields: list
208 @param lvm_fields: LVM fields
211 lvm_name_to_idx = dict([(lvm_name, idx)
212 for (idx, lvm_name) in enumerate(lvm_fields)])
213 field_to_idx = dict([(field_name, idx)
214 for (idx, (field_name, _, _)) in enumerate(fields_def)])
217 for raw_data in cmd_result:
220 for field_name in wanted_field_names:
221 (_, lvm_name, convert_fn) = fields_def[field_to_idx[field_name]]
223 value = raw_data[lvm_name_to_idx[lvm_name]]
226 value = convert_fn(value)
235 def _BuildListCommand(cmd, sep, options, name):
236 """Builds LVM command line.
239 @param cmd: Command name
241 @param sep: Field separator character
242 @type options: list of strings
243 @param options: Wanted LVM fields
244 @type name: name or None
245 @param name: Name of requested entity
249 "--noheadings", "--units=m", "--nosuffix",
251 "--options", ",".join(options)]
259 def _RunListCommand(args):
263 result = utils.RunCmd(args)
266 raise errors.StorageError("Failed to run %r, command output: %s" %
267 (args[0], result.output))
272 def _SplitList(data, sep, fieldcount):
273 """Splits LVM command output into rows and fields.
276 @param data: LVM command output
278 @param sep: Field separator character
279 @type fieldcount: int
280 @param fieldcount: Expected number of fields
283 for line in data.splitlines():
284 fields = line.strip().split(sep)
286 if len(fields) != fieldcount:
292 class LvmPvStorage(_LvmBase):
293 """LVM Physical Volume storage unit.
296 def _GetAllocatable(attr):
298 return (attr[0] == "a")
300 logging.warning("Invalid PV attribute: %r", attr)
305 # Make sure to update constants.VALID_STORAGE_FIELDS when changing field
308 (constants.SF_NAME, "pv_name", None),
309 (constants.SF_SIZE, "pv_size", _ParseSize),
310 (constants.SF_USED, "pv_used", _ParseSize),
311 (constants.SF_FREE, "pv_free", _ParseSize),
312 (constants.SF_ALLOCATABLE, "pv_attr", _GetAllocatable),
315 def _SetAllocatable(self, name, allocatable):
316 """Sets the "allocatable" flag on a physical volume.
319 @param name: Physical volume name
320 @type allocatable: bool
321 @param allocatable: Whether to set the "allocatable" flag
324 args = ["pvchange", "--allocatable"]
333 result = utils.RunCmd(args)
335 raise errors.StorageError("Failed to modify physical volume,"
336 " pvchange output: %s" %
339 def Modify(self, name, changes):
340 """Modifies flags on a physical volume.
345 if constants.SF_ALLOCATABLE in changes:
346 self._SetAllocatable(name, changes[constants.SF_ALLOCATABLE])
347 del changes[constants.SF_ALLOCATABLE]
349 # Other changes will be handled (and maybe refused) by the base class.
350 return _LvmBase.Modify(self, name, changes)
353 class LvmVgStorage(_LvmBase):
354 """LVM Volume Group storage unit.
359 # Make sure to update constants.VALID_STORAGE_FIELDS when changing field
362 (constants.SF_NAME, "vg_name", None),
363 (constants.SF_SIZE, "vg_size", _ParseSize),
367 # Lookup table for storage types
369 constants.ST_FILE: FileStorage,
370 constants.ST_LVM_PV: LvmPvStorage,
371 constants.ST_LVM_VG: LvmVgStorage,
375 def GetStorageClass(name):
376 """Returns the class for a storage type.
379 @param name: Storage type
383 return _STORAGE_TYPES[name]
385 raise errors.StorageError("Unknown storage type: %r" % name)
388 def GetStorage(name, *args):
389 """Factory function for storage methods.
392 @param name: Storage type
395 return GetStorageClass(name)(*args)