Automatically cleanup _temporary_ids at save
[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   def Execute(self, name, op):
68     """Executes an operation on an entity within the storage unit.
69
70     @type name: string
71     @param name: Entity name
72     @type op: string
73     @param op: Operation name
74
75     """
76     raise NotImplementedError()
77
78
79 class FileStorage(_Base):
80   """File storage unit.
81
82   """
83   def __init__(self, paths):
84     """Initializes this class.
85
86     @type paths: list
87     @param paths: List of file storage paths
88
89     """
90     self._paths = paths
91
92   def List(self, name, fields):
93     """Returns a list of all entities within the storage unit.
94
95     See L{_Base.List}.
96
97     """
98     rows = []
99
100     if name is None:
101       paths = self._paths
102     else:
103       paths = [name]
104
105     for path in paths:
106       rows.append(self._ListInner(path, fields))
107
108     return rows
109
110   @staticmethod
111   def _ListInner(path, fields):
112     """Gathers requested information from directory.
113
114     @type path: string
115     @param path: Path to directory
116     @type fields: list
117     @param fields: Requested fields
118
119     """
120     values = []
121
122     # Pre-calculate information in case it's requested more than once
123     if constants.SF_USED in fields:
124       dirsize = utils.CalculateDirectorySize(path)
125     else:
126       dirsize = None
127
128     if constants.SF_FREE in fields:
129       fsfree = utils.GetFreeFilesystemSpace(path)
130     else:
131       fsfree = None
132
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:
136         values.append(path)
137
138       elif field_name == constants.SF_USED:
139         values.append(dirsize)
140
141       elif field_name == constants.SF_FREE:
142         values.append(fsfree)
143
144       else:
145         raise errors.StorageError("Unknown field: %r" % field_name)
146
147     return values
148
149
150 class _LvmBase(_Base):
151   """Base class for LVM storage containers.
152
153   """
154   LIST_SEP = "|"
155   LIST_COMMAND = None
156   LIST_FIELDS = None
157
158   def List(self, name, wanted_field_names):
159     """Returns a list of all entities within the storage unit.
160
161     See L{_Base.List}.
162
163     """
164     # Get needed LVM fields
165     lvm_fields = self._GetLvmFields(self.LIST_FIELDS, wanted_field_names)
166
167     # Build LVM command
168     cmd_args = self._BuildListCommand(self.LIST_COMMAND, self.LIST_SEP,
169                                       lvm_fields, name)
170
171     # Run LVM command
172     cmd_result = self._RunListCommand(cmd_args)
173
174     # Split and rearrange LVM command output
175     return self._BuildList(self._SplitList(cmd_result, self.LIST_SEP,
176                                            len(lvm_fields)),
177                            self.LIST_FIELDS,
178                            wanted_field_names,
179                            lvm_fields)
180
181   @staticmethod
182   def _GetLvmFields(fields_def, wanted_field_names):
183     """Returns unique list of fields wanted from LVM command.
184
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
189
190     """
191     field_to_idx = dict([(field_name, idx)
192                          for (idx, (field_name, _, _)) in
193                          enumerate(fields_def)])
194
195     lvm_fields = []
196
197     for field_name in wanted_field_names:
198       try:
199         idx = field_to_idx[field_name]
200       except IndexError:
201         raise errors.StorageError("Unknown field: %r" % field_name)
202
203       (_, lvm_name, _) = fields_def[idx]
204
205       lvm_fields.append(lvm_name)
206
207     return utils.UniqueSequence(lvm_fields)
208
209   @classmethod
210   def _BuildList(cls, cmd_result, fields_def, wanted_field_names, lvm_fields):
211     """Builds the final result list.
212
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
221
222     """
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)])
228
229     data = []
230     for raw_data in cmd_result:
231       row = []
232
233       for field_name in wanted_field_names:
234         (_, lvm_name, convert_fn) = fields_def[field_to_idx[field_name]]
235
236         value = raw_data[lvm_name_to_idx[lvm_name]]
237
238         if convert_fn:
239           value = convert_fn(value)
240
241         row.append(value)
242
243       data.append(row)
244
245     return data
246
247   @staticmethod
248   def _BuildListCommand(cmd, sep, options, name):
249     """Builds LVM command line.
250
251     @type cmd: string
252     @param cmd: Command name
253     @type sep: string
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
259
260     """
261     args = [cmd,
262             "--noheadings", "--units=m", "--nosuffix",
263             "--separator", sep,
264             "--options", ",".join(options)]
265
266     if name is not None:
267       args.append(name)
268
269     return args
270
271   @staticmethod
272   def _RunListCommand(args):
273     """Run LVM command.
274
275     """
276     result = utils.RunCmd(args)
277
278     if result.failed:
279       raise errors.StorageError("Failed to run %r, command output: %s" %
280                                 (args[0], result.output))
281
282     return result.stdout
283
284   @staticmethod
285   def _SplitList(data, sep, fieldcount):
286     """Splits LVM command output into rows and fields.
287
288     @type data: string
289     @param data: LVM command output
290     @type sep: string
291     @param sep: Field separator character
292     @type fieldcount: int
293     @param fieldcount: Expected number of fields
294
295     """
296     for line in data.splitlines():
297       fields = line.strip().split(sep)
298
299       if len(fields) != fieldcount:
300         continue
301
302       yield fields
303
304
305 class LvmPvStorage(_LvmBase):
306   """LVM Physical Volume storage unit.
307
308   """
309   def _GetAllocatable(attr):
310     if attr:
311       return (attr[0] == "a")
312     else:
313       logging.warning("Invalid PV attribute: %r", attr)
314       return False
315
316   LIST_COMMAND = "pvs"
317
318   # Make sure to update constants.VALID_STORAGE_FIELDS when changing field
319   # definitions.
320   LIST_FIELDS = [
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),
326     ]
327
328   def _SetAllocatable(self, name, allocatable):
329     """Sets the "allocatable" flag on a physical volume.
330
331     @type name: string
332     @param name: Physical volume name
333     @type allocatable: bool
334     @param allocatable: Whether to set the "allocatable" flag
335
336     """
337     args = ["pvchange", "--allocatable"]
338
339     if allocatable:
340       args.append("y")
341     else:
342       args.append("n")
343
344     args.append(name)
345
346     result = utils.RunCmd(args)
347     if result.failed:
348       raise errors.StorageError("Failed to modify physical volume,"
349                                 " pvchange output: %s" %
350                                 result.output)
351
352   def Modify(self, name, changes):
353     """Modifies flags on a physical volume.
354
355     See L{_Base.Modify}.
356
357     """
358     if constants.SF_ALLOCATABLE in changes:
359       self._SetAllocatable(name, changes[constants.SF_ALLOCATABLE])
360       del changes[constants.SF_ALLOCATABLE]
361
362     # Other changes will be handled (and maybe refused) by the base class.
363     return _LvmBase.Modify(self, name, changes)
364
365
366 class LvmVgStorage(_LvmBase):
367   """LVM Volume Group storage unit.
368
369   """
370   LIST_COMMAND = "vgs"
371
372   # Make sure to update constants.VALID_STORAGE_FIELDS when changing field
373   # definitions.
374   LIST_FIELDS = [
375     (constants.SF_NAME, "vg_name", None),
376     (constants.SF_SIZE, "vg_size", _ParseSize),
377     ]
378
379   def _RemoveMissing(self, name):
380     """Runs "vgreduce --removemissing" on a volume group.
381
382     @type name: string
383     @param name: Volume group name
384
385     """
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])
390
391     # Keep output in case something went wrong
392     vgreduce_output = result.output
393
394     result = utils.RunCmd(["vgs", "--noheadings", "--nosuffix", name])
395     if result.failed:
396       raise errors.StorageError(("Volume group '%s' still not consistent,"
397                                  " 'vgreduce' output: %r,"
398                                  " 'vgs' output: %r") %
399                                 (name, vgreduce_output, result.output))
400
401   def Execute(self, name, op):
402     """Executes an operation on a virtual volume.
403
404     See L{_Base.Execute}.
405
406     """
407     if op == constants.SO_FIX_CONSISTENCY:
408       return self._RemoveMissing(name)
409
410     return _LvmBase.Execute(self, name, op)
411
412
413 # Lookup table for storage types
414 _STORAGE_TYPES = {
415   constants.ST_FILE: FileStorage,
416   constants.ST_LVM_PV: LvmPvStorage,
417   constants.ST_LVM_VG: LvmVgStorage,
418   }
419
420
421 def GetStorageClass(name):
422   """Returns the class for a storage type.
423
424   @type name: string
425   @param name: Storage type
426
427   """
428   try:
429     return _STORAGE_TYPES[name]
430   except KeyError:
431     raise errors.StorageError("Unknown storage type: %r" % name)
432
433
434 def GetStorage(name, *args):
435   """Factory function for storage methods.
436
437   @type name: string
438   @param name: Storage type
439
440   """
441   return GetStorageClass(name)(*args)