Statistics
| Branch: | Tag: | Revision:

root / lib / storage.py @ 54878c54

History | View | Annotate | Download (8 kB)

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_USED 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_USED:
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)