serializer.DumpSignedJson
[ganeti-local] / lib / storage.py
1 #
2 #
3
4 # Copyright (C) 2009 Google Inc.
5 #
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.
10 #
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.
15 #
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
19 # 02110-1301, USA.
20
21
22 """Storage container abstraction.
23
24 """
25
26
27 import logging
28
29 from ganeti import errors
30 from ganeti import constants
31 from ganeti import utils
32
33
34 def _ParseSize(value):
35   return int(round(float(value), 0))
36
37
38 class _Base:
39   """Base class for storage abstraction.
40
41   """
42   def List(self, name, fields):
43     """Returns a list of all entities within the storage unit.
44
45     @type name: string or None
46     @param name: Entity name or None for all
47     @type fields: list
48     @param fields: List with all requested result fields (order is preserved)
49
50     """
51     raise NotImplementedError()
52
53   def Modify(self, name, changes):
54     """Modifies an entity within the storage unit.
55
56     @type name: string
57     @param name: Entity name
58     @type changes: dict
59     @param changes: New field values
60
61     """
62     # Don't raise an error if no changes are requested
63     if changes:
64       raise errors.ProgrammerError("Unable to modify the following"
65                                    "fields: %r" % (changes.keys(), ))
66
67
68 class FileStorage(_Base):
69   """File storage unit.
70
71   """
72   def __init__(self, paths):
73     """Initializes this class.
74
75     @type paths: list
76     @param paths: List of file storage paths
77
78     """
79     self._paths = paths
80
81   def List(self, name, fields):
82     """Returns a list of all entities within the storage unit.
83
84     See L{_Base.List}.
85
86     """
87     rows = []
88
89     if name is None:
90       paths = self._paths
91     else:
92       paths = [name]
93
94     for path in paths:
95       rows.append(self._ListInner(path, fields))
96
97     return rows
98
99   @staticmethod
100   def _ListInner(path, fields):
101     """Gathers requested information from directory.
102
103     @type path: string
104     @param path: Path to directory
105     @type fields: list
106     @param fields: Requested fields
107
108     """
109     values = []
110
111     # Pre-calculate information in case it's requested more than once
112     if constants.SF_USED in fields:
113       dirsize = utils.CalculateDirectorySize(path)
114     else:
115       dirsize = None
116
117     if constants.SF_FREE in fields:
118       fsfree = utils.GetFreeFilesystemSpace(path)
119     else:
120       fsfree = None
121
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:
125         values.append(path)
126
127       elif field_name == constants.SF_USED:
128         values.append(dirsize)
129
130       elif field_name == constants.SF_FREE:
131         values.append(fsfree)
132
133       else:
134         raise errors.StorageError("Unknown field: %r" % field_name)
135
136     return values
137
138
139 class _LvmBase(_Base):
140   """Base class for LVM storage containers.
141
142   """
143   LIST_SEP = "|"
144   LIST_COMMAND = None
145   LIST_FIELDS = None
146
147   def List(self, name, wanted_field_names):
148     """Returns a list of all entities within the storage unit.
149
150     See L{_Base.List}.
151
152     """
153     # Get needed LVM fields
154     lvm_fields = self._GetLvmFields(self.LIST_FIELDS, wanted_field_names)
155
156     # Build LVM command
157     cmd_args = self._BuildListCommand(self.LIST_COMMAND, self.LIST_SEP,
158                                       lvm_fields, name)
159
160     # Run LVM command
161     cmd_result = self._RunListCommand(cmd_args)
162
163     # Split and rearrange LVM command output
164     return self._BuildList(self._SplitList(cmd_result, self.LIST_SEP,
165                                            len(lvm_fields)),
166                            self.LIST_FIELDS,
167                            wanted_field_names,
168                            lvm_fields)
169
170   @staticmethod
171   def _GetLvmFields(fields_def, wanted_field_names):
172     """Returns unique list of fields wanted from LVM command.
173
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
178
179     """
180     field_to_idx = dict([(field_name, idx)
181                          for (idx, (field_name, _, _)) in enumerate(fields_def)])
182
183     lvm_fields = []
184
185     for field_name in wanted_field_names:
186       try:
187         idx = field_to_idx[field_name]
188       except IndexError:
189         raise errors.StorageError("Unknown field: %r" % field_name)
190
191       (_, lvm_name, _) = fields_def[idx]
192
193       lvm_fields.append(lvm_name)
194
195     return utils.UniqueSequence(lvm_fields)
196
197   @classmethod
198   def _BuildList(cls, cmd_result, fields_def, wanted_field_names, lvm_fields):
199     """Builds the final result list.
200
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
209
210     """
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)])
215
216     data = []
217     for raw_data in cmd_result:
218       row = []
219
220       for field_name in wanted_field_names:
221         (_, lvm_name, convert_fn) = fields_def[field_to_idx[field_name]]
222
223         value = raw_data[lvm_name_to_idx[lvm_name]]
224
225         if convert_fn:
226           value = convert_fn(value)
227
228         row.append(value)
229
230       data.append(row)
231
232     return data
233
234   @staticmethod
235   def _BuildListCommand(cmd, sep, options, name):
236     """Builds LVM command line.
237
238     @type cmd: string
239     @param cmd: Command name
240     @type sep: string
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
246
247     """
248     args = [cmd,
249             "--noheadings", "--units=m", "--nosuffix",
250             "--separator", sep,
251             "--options", ",".join(options)]
252
253     if name is not None:
254       args.append(name)
255
256     return args
257
258   @staticmethod
259   def _RunListCommand(args):
260     """Run LVM command.
261
262     """
263     result = utils.RunCmd(args)
264
265     if result.failed:
266       raise errors.StorageError("Failed to run %r, command output: %s" %
267                                 (args[0], result.output))
268
269     return result.stdout
270
271   @staticmethod
272   def _SplitList(data, sep, fieldcount):
273     """Splits LVM command output into rows and fields.
274
275     @type data: string
276     @param data: LVM command output
277     @type sep: string
278     @param sep: Field separator character
279     @type fieldcount: int
280     @param fieldcount: Expected number of fields
281
282     """
283     for line in data.splitlines():
284       fields = line.strip().split(sep)
285
286       if len(fields) != fieldcount:
287         continue
288
289       yield fields
290
291
292 class LvmPvStorage(_LvmBase):
293   """LVM Physical Volume storage unit.
294
295   """
296   def _GetAllocatable(attr):
297     if attr:
298       return (attr[0] == "a")
299     else:
300       logging.warning("Invalid PV attribute: %r", attr)
301       return False
302
303   LIST_COMMAND = "pvs"
304
305   # Make sure to update constants.VALID_STORAGE_FIELDS when changing field
306   # definitions.
307   LIST_FIELDS = [
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),
313     ]
314
315   def _SetAllocatable(self, name, allocatable):
316     """Sets the "allocatable" flag on a physical volume.
317
318     @type name: string
319     @param name: Physical volume name
320     @type allocatable: bool
321     @param allocatable: Whether to set the "allocatable" flag
322
323     """
324     args = ["pvchange", "--allocatable"]
325
326     if allocatable:
327       args.append("y")
328     else:
329       args.append("n")
330
331     args.append(name)
332
333     result = utils.RunCmd(args)
334     if result.failed:
335       raise errors.StorageError("Failed to modify physical volume,"
336                                 " pvchange output: %s" %
337                                 result.output)
338
339   def Modify(self, name, changes):
340     """Modifies flags on a physical volume.
341
342     See L{_Base.Modify}.
343
344     """
345     if constants.SF_ALLOCATABLE in changes:
346       self._SetAllocatable(name, changes[constants.SF_ALLOCATABLE])
347       del changes[constants.SF_ALLOCATABLE]
348
349     # Other changes will be handled (and maybe refused) by the base class.
350     return _LvmBase.Modify(self, name, changes)
351
352
353 class LvmVgStorage(_LvmBase):
354   """LVM Volume Group storage unit.
355
356   """
357   LIST_COMMAND = "vgs"
358
359   # Make sure to update constants.VALID_STORAGE_FIELDS when changing field
360   # definitions.
361   LIST_FIELDS = [
362     (constants.SF_NAME, "vg_name", None),
363     (constants.SF_SIZE, "vg_size", _ParseSize),
364     ]
365
366
367 # Lookup table for storage types
368 _STORAGE_TYPES = {
369   constants.ST_FILE: FileStorage,
370   constants.ST_LVM_PV: LvmPvStorage,
371   constants.ST_LVM_VG: LvmVgStorage,
372   }
373
374
375 def GetStorageClass(name):
376   """Returns the class for a storage type.
377
378   @type name: string
379   @param name: Storage type
380
381   """
382   try:
383     return _STORAGE_TYPES[name]
384   except KeyError:
385     raise errors.StorageError("Unknown storage type: %r" % name)
386
387
388 def GetStorage(name, *args):
389   """Factory function for storage methods.
390
391   @type name: string
392   @param name: Storage type
393
394   """
395   return GetStorageClass(name)(*args)