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