storage: Add new function to execute operations
[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 enumerate(fields_def)])
193
194     lvm_fields = []
195
196     for field_name in wanted_field_names:
197       try:
198         idx = field_to_idx[field_name]
199       except IndexError:
200         raise errors.StorageError("Unknown field: %r" % field_name)
201
202       (_, lvm_name, _) = fields_def[idx]
203
204       lvm_fields.append(lvm_name)
205
206     return utils.UniqueSequence(lvm_fields)
207
208   @classmethod
209   def _BuildList(cls, cmd_result, fields_def, wanted_field_names, lvm_fields):
210     """Builds the final result list.
211
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
220
221     """
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)])
226
227     data = []
228     for raw_data in cmd_result:
229       row = []
230
231       for field_name in wanted_field_names:
232         (_, lvm_name, convert_fn) = fields_def[field_to_idx[field_name]]
233
234         value = raw_data[lvm_name_to_idx[lvm_name]]
235
236         if convert_fn:
237           value = convert_fn(value)
238
239         row.append(value)
240
241       data.append(row)
242
243     return data
244
245   @staticmethod
246   def _BuildListCommand(cmd, sep, options, name):
247     """Builds LVM command line.
248
249     @type cmd: string
250     @param cmd: Command name
251     @type sep: string
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
257
258     """
259     args = [cmd,
260             "--noheadings", "--units=m", "--nosuffix",
261             "--separator", sep,
262             "--options", ",".join(options)]
263
264     if name is not None:
265       args.append(name)
266
267     return args
268
269   @staticmethod
270   def _RunListCommand(args):
271     """Run LVM command.
272
273     """
274     result = utils.RunCmd(args)
275
276     if result.failed:
277       raise errors.StorageError("Failed to run %r, command output: %s" %
278                                 (args[0], result.output))
279
280     return result.stdout
281
282   @staticmethod
283   def _SplitList(data, sep, fieldcount):
284     """Splits LVM command output into rows and fields.
285
286     @type data: string
287     @param data: LVM command output
288     @type sep: string
289     @param sep: Field separator character
290     @type fieldcount: int
291     @param fieldcount: Expected number of fields
292
293     """
294     for line in data.splitlines():
295       fields = line.strip().split(sep)
296
297       if len(fields) != fieldcount:
298         continue
299
300       yield fields
301
302
303 class LvmPvStorage(_LvmBase):
304   """LVM Physical Volume storage unit.
305
306   """
307   def _GetAllocatable(attr):
308     if attr:
309       return (attr[0] == "a")
310     else:
311       logging.warning("Invalid PV attribute: %r", attr)
312       return False
313
314   LIST_COMMAND = "pvs"
315
316   # Make sure to update constants.VALID_STORAGE_FIELDS when changing field
317   # definitions.
318   LIST_FIELDS = [
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),
324     ]
325
326   def _SetAllocatable(self, name, allocatable):
327     """Sets the "allocatable" flag on a physical volume.
328
329     @type name: string
330     @param name: Physical volume name
331     @type allocatable: bool
332     @param allocatable: Whether to set the "allocatable" flag
333
334     """
335     args = ["pvchange", "--allocatable"]
336
337     if allocatable:
338       args.append("y")
339     else:
340       args.append("n")
341
342     args.append(name)
343
344     result = utils.RunCmd(args)
345     if result.failed:
346       raise errors.StorageError("Failed to modify physical volume,"
347                                 " pvchange output: %s" %
348                                 result.output)
349
350   def Modify(self, name, changes):
351     """Modifies flags on a physical volume.
352
353     See L{_Base.Modify}.
354
355     """
356     if constants.SF_ALLOCATABLE in changes:
357       self._SetAllocatable(name, changes[constants.SF_ALLOCATABLE])
358       del changes[constants.SF_ALLOCATABLE]
359
360     # Other changes will be handled (and maybe refused) by the base class.
361     return _LvmBase.Modify(self, name, changes)
362
363
364 class LvmVgStorage(_LvmBase):
365   """LVM Volume Group storage unit.
366
367   """
368   LIST_COMMAND = "vgs"
369
370   # Make sure to update constants.VALID_STORAGE_FIELDS when changing field
371   # definitions.
372   LIST_FIELDS = [
373     (constants.SF_NAME, "vg_name", None),
374     (constants.SF_SIZE, "vg_size", _ParseSize),
375     ]
376
377
378 # Lookup table for storage types
379 _STORAGE_TYPES = {
380   constants.ST_FILE: FileStorage,
381   constants.ST_LVM_PV: LvmPvStorage,
382   constants.ST_LVM_VG: LvmVgStorage,
383   }
384
385
386 def GetStorageClass(name):
387   """Returns the class for a storage type.
388
389   @type name: string
390   @param name: Storage type
391
392   """
393   try:
394     return _STORAGE_TYPES[name]
395   except KeyError:
396     raise errors.StorageError("Unknown storage type: %r" % name)
397
398
399 def GetStorage(name, *args):
400   """Factory function for storage methods.
401
402   @type name: string
403   @param name: Storage type
404
405   """
406   return GetStorageClass(name)(*args)