Add first implementation of generic storage unit framework
[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 COL_NAME = "name"
35 COL_SIZE = "size"
36 COL_FREE = "free"
37 COL_USED = "used"
38 COL_ALLOCATABLE = "allocatable"
39
40
41 def _ParseSize(value):
42   return int(round(float(value), 0))
43
44
45 class _Base:
46   """Base class for storage abstraction.
47
48   """
49   def List(self, name, fields):
50     """Returns a list of all entities within the storage unit.
51
52     @type name: string or None
53     @param name: Entity name or None for all
54     @type fields: list
55     @param fields: List with all requested result fields (order is preserved)
56
57     """
58     raise NotImplementedError()
59
60
61 class FileStorage(_Base):
62   """File storage unit.
63
64   """
65   def __init__(self, paths):
66     """Initializes this class.
67
68     @type paths: list
69     @param paths: List of file storage paths
70
71     """
72     self._paths = paths
73
74   def List(self, name, fields):
75     """Returns a list of all entities within the storage unit.
76
77     See L{_Base.List}.
78
79     """
80     rows = []
81
82     if name is None:
83       paths = self._paths
84     else:
85       paths = [name]
86
87     for path in paths:
88       rows.append(self._ListInner(path, fields))
89
90     return rows
91
92   @staticmethod
93   def _ListInner(path, fields):
94     """Gathers requested information from directory.
95
96     @type path: string
97     @param path: Path to directory
98     @type fields: list
99     @param fields: Requested fields
100
101     """
102     values = []
103
104     # Pre-calculate information in case it's requested more than once
105     if COL_SIZE in fields:
106       dirsize = utils.CalculateDirectorySize(path)
107     else:
108       dirsize = None
109
110     if COL_FREE in fields:
111       fsfree = utils.GetFreeFilesystemSpace(path)
112     else:
113       fsfree = None
114
115     for field_name in fields:
116       if field_name == COL_NAME:
117         values.append(path)
118
119       elif field_name == COL_SIZE:
120         values.append(dirsize)
121
122       elif field_name == COL_FREE:
123         values.append(fsfree)
124
125       else:
126         raise errors.StorageError("Unknown field: %r" % field_name)
127
128     return values
129
130
131 class _LvmBase(_Base):
132   """Base class for LVM storage containers.
133
134   """
135   LIST_SEP = "|"
136   LIST_COMMAND = None
137   LIST_FIELDS = None
138
139   def List(self, name, wanted_field_names):
140     """Returns a list of all entities within the storage unit.
141
142     See L{_Base.List}.
143
144     """
145     # Get needed LVM fields
146     lvm_fields = self._GetLvmFields(self.LIST_FIELDS, wanted_field_names)
147
148     # Build LVM command
149     cmd_args = self._BuildListCommand(self.LIST_COMMAND, self.LIST_SEP,
150                                       lvm_fields, name)
151
152     # Run LVM command
153     cmd_result = self._RunListCommand(cmd_args)
154
155     # Split and rearrange LVM command output
156     return self._BuildList(self._SplitList(cmd_result, self.LIST_SEP,
157                                            len(lvm_fields)),
158                            self.LIST_FIELDS,
159                            wanted_field_names,
160                            lvm_fields)
161
162   @staticmethod
163   def _GetLvmFields(fields_def, wanted_field_names):
164     """Returns unique list of fields wanted from LVM command.
165
166     @type fields_def: list
167     @param fields_def: Field definitions
168     @type wanted_field_names: list
169     @param wanted_field_names: List of requested fields
170
171     """
172     field_to_idx = dict([(field_name, idx)
173                          for (idx, (field_name, _, _)) in enumerate(fields_def)])
174
175     lvm_fields = []
176
177     for field_name in wanted_field_names:
178       try:
179         idx = field_to_idx[field_name]
180       except IndexError:
181         raise errors.StorageError("Unknown field: %r" % field_name)
182
183       (_, lvm_name, _) = fields_def[idx]
184
185       lvm_fields.append(lvm_name)
186
187     return utils.UniqueSequence(lvm_fields)
188
189   @classmethod
190   def _BuildList(cls, cmd_result, fields_def, wanted_field_names, lvm_fields):
191     """Builds the final result list.
192
193     @type cmd_result: iterable
194     @param cmd_result: Iterable of LVM command output (iterable of lists)
195     @type fields_def: list
196     @param fields_def: Field definitions
197     @type wanted_field_names: list
198     @param wanted_field_names: List of requested fields
199     @type lvm_fields: list
200     @param lvm_fields: LVM fields
201
202     """
203     lvm_name_to_idx = dict([(lvm_name, idx)
204                            for (idx, lvm_name) in enumerate(lvm_fields)])
205     field_to_idx = dict([(field_name, idx)
206                          for (idx, (field_name, _, _)) in enumerate(fields_def)])
207
208     data = []
209     for raw_data in cmd_result:
210       row = []
211
212       for field_name in wanted_field_names:
213         (_, lvm_name, convert_fn) = fields_def[field_to_idx[field_name]]
214
215         value = raw_data[lvm_name_to_idx[lvm_name]]
216
217         if convert_fn:
218           value = convert_fn(value)
219
220         row.append(value)
221
222       data.append(row)
223
224     return data
225
226   @staticmethod
227   def _BuildListCommand(cmd, sep, options, name):
228     """Builds LVM command line.
229
230     @type cmd: string
231     @param cmd: Command name
232     @type sep: string
233     @param sep: Field separator character
234     @type options: list of strings
235     @param options: Wanted LVM fields
236     @type name: name or None
237     @param name: Name of requested entity
238
239     """
240     args = [cmd,
241             "--noheadings", "--units=m", "--nosuffix",
242             "--separator", sep,
243             "--options", ",".join(options)]
244
245     if name is not None:
246       args.append(name)
247
248     return args
249
250   @staticmethod
251   def _RunListCommand(args):
252     """Run LVM command.
253
254     """
255     result = utils.RunCmd(args)
256
257     if result.failed:
258       raise errors.StorageError("Failed to run %r, command output: %s" %
259                                 (args[0], result.output))
260
261     return result.stdout
262
263   @staticmethod
264   def _SplitList(data, sep, fieldcount):
265     """Splits LVM command output into rows and fields.
266
267     @type data: string
268     @param data: LVM command output
269     @type sep: string
270     @param sep: Field separator character
271     @type fieldcount: int
272     @param fieldcount: Expected number of fields
273
274     """
275     for line in data.splitlines():
276       fields = line.strip().split(sep)
277
278       if len(fields) != fieldcount:
279         continue
280
281       yield fields
282
283
284 class LvmPvStorage(_LvmBase):
285   """LVM Physical Volume storage unit.
286
287   """
288   def _GetAllocatable(attr):
289     if attr:
290       return (attr[0] == "a")
291     else:
292       logging.warning("Invalid PV attribute: %r", attr)
293       return False
294
295   LIST_COMMAND = "pvs"
296   LIST_FIELDS = [
297     (COL_NAME, "pv_name", None),
298     (COL_SIZE, "pv_size", _ParseSize),
299     (COL_USED, "pv_used", _ParseSize),
300     (COL_FREE, "pv_free", _ParseSize),
301     (COL_ALLOCATABLE, "pv_attr", _GetAllocatable),
302     ]
303
304
305 class LvmVgStorage(_LvmBase):
306   """LVM Volume Group storage unit.
307
308   """
309   LIST_COMMAND = "vgs"
310   LIST_FIELDS = [
311     (COL_NAME, "vg_name", None),
312     (COL_SIZE, "vg_size", _ParseSize),
313     ]
314
315
316 # Lookup table for storage types
317 _STORAGE_TYPES = {
318   constants.ST_FILE: FileStorage,
319   constants.ST_LVM_PV: LvmPvStorage,
320   constants.ST_LVM_VG: LvmVgStorage,
321   }
322
323
324 def GetStorageClass(name):
325   """Returns the class for a storage type.
326
327   @type name: string
328   @param name: Storage type
329
330   """
331   try:
332     return _STORAGE_TYPES[name]
333   except KeyError:
334     raise errors.StorageError("Unknown storage type: %r" % name)
335
336
337 def GetStorage(name, *args):
338   """Factory function for storage methods.
339
340   @type name: string
341   @param name: Storage type
342
343   """
344   return GetStorageClass(name)(*args)