Statistics
| Branch: | Tag: | Revision:

root / lib / storage.py @ b8716596

History | View | Annotate | Download (12.2 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
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 or constants.SF_SIZE in fields:
129
      fsstats = utils.GetFilesystemStats(path)
130
    else:
131
      fsstats = 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(fsstats[1])
143

    
144
      elif field_name == constants.SF_SIZE:
145
        values.append(fsstats[0])
146

    
147
      elif field_name == constants.SF_ALLOCATABLE:
148
        values.append(True)
149

    
150
      else:
151
        raise errors.StorageError("Unknown field: %r" % field_name)
152

    
153
    return values
154

    
155

    
156
class _LvmBase(_Base):
157
  """Base class for LVM storage containers.
158

159
  @cvar LIST_FIELDS: list of tuples consisting of three elements: SF_*
160
      constants, lvm command output fields (list), and conversion
161
      function or static value (for static value, the lvm output field
162
      can be an empty list)
163

164
  """
165
  LIST_SEP = "|"
166
  LIST_COMMAND = None
167
  LIST_FIELDS = None
168

    
169
  def List(self, name, wanted_field_names):
170
    """Returns a list of all entities within the storage unit.
171

172
    See L{_Base.List}.
173

174
    """
175
    # Get needed LVM fields
176
    lvm_fields = self._GetLvmFields(self.LIST_FIELDS, wanted_field_names)
177

    
178
    # Build LVM command
179
    cmd_args = self._BuildListCommand(self.LIST_COMMAND, self.LIST_SEP,
180
                                      lvm_fields, name)
181

    
182
    # Run LVM command
183
    cmd_result = self._RunListCommand(cmd_args)
184

    
185
    # Split and rearrange LVM command output
186
    return self._BuildList(self._SplitList(cmd_result, self.LIST_SEP,
187
                                           len(lvm_fields)),
188
                           self.LIST_FIELDS,
189
                           wanted_field_names,
190
                           lvm_fields)
191

    
192
  @staticmethod
193
  def _GetLvmFields(fields_def, wanted_field_names):
194
    """Returns unique list of fields wanted from LVM command.
195

196
    @type fields_def: list
197
    @param fields_def: Field definitions
198
    @type wanted_field_names: list
199
    @param wanted_field_names: List of requested fields
200

201
    """
202
    field_to_idx = dict([(field_name, idx)
203
                         for (idx, (field_name, _, _)) in
204
                         enumerate(fields_def)])
205

    
206
    lvm_fields = []
207

    
208
    for field_name in wanted_field_names:
209
      try:
210
        idx = field_to_idx[field_name]
211
      except IndexError:
212
        raise errors.StorageError("Unknown field: %r" % field_name)
213

    
214
      (_, lvm_names, _) = fields_def[idx]
215

    
216
      lvm_fields.extend(lvm_names)
217

    
218
    return utils.UniqueSequence(lvm_fields)
219

    
220
  @classmethod
221
  def _BuildList(cls, cmd_result, fields_def, wanted_field_names, lvm_fields):
222
    """Builds the final result list.
223

224
    @type cmd_result: iterable
225
    @param cmd_result: Iterable of LVM command output (iterable of lists)
226
    @type fields_def: list
227
    @param fields_def: Field definitions
228
    @type wanted_field_names: list
229
    @param wanted_field_names: List of requested fields
230
    @type lvm_fields: list
231
    @param lvm_fields: LVM fields
232

233
    """
234
    lvm_name_to_idx = dict([(lvm_name, idx)
235
                           for (idx, lvm_name) in enumerate(lvm_fields)])
236
    field_to_idx = dict([(field_name, idx)
237
                         for (idx, (field_name, _, _)) in
238
                         enumerate(fields_def)])
239

    
240
    data = []
241
    for raw_data in cmd_result:
242
      row = []
243

    
244
      for field_name in wanted_field_names:
245
        (_, lvm_names, mapper) = fields_def[field_to_idx[field_name]]
246

    
247
        values = [raw_data[lvm_name_to_idx[i]] for i in lvm_names]
248

    
249
        if callable(mapper):
250
          # we got a function, call it with all the declared fields
251
          val = mapper(*values)
252
        elif len(values) == 1:
253
          # we don't have a function, but we had a single field
254
          # declared, pass it unchanged
255
          val = values[0]
256
        else:
257
          # let's make sure there are no fields declared (cannot map >
258
          # 1 field without a function)
259
          assert not values, "LVM storage has multi-fields without a function"
260
          val = mapper
261

    
262
        row.append(val)
263

    
264
      data.append(row)
265

    
266
    return data
267

    
268
  @staticmethod
269
  def _BuildListCommand(cmd, sep, options, name):
270
    """Builds LVM command line.
271

272
    @type cmd: string
273
    @param cmd: Command name
274
    @type sep: string
275
    @param sep: Field separator character
276
    @type options: list of strings
277
    @param options: Wanted LVM fields
278
    @type name: name or None
279
    @param name: Name of requested entity
280

281
    """
282
    args = [cmd,
283
            "--noheadings", "--units=m", "--nosuffix",
284
            "--separator", sep,
285
            "--options", ",".join(options)]
286

    
287
    if name is not None:
288
      args.append(name)
289

    
290
    return args
291

    
292
  @staticmethod
293
  def _RunListCommand(args):
294
    """Run LVM command.
295

296
    """
297
    result = utils.RunCmd(args)
298

    
299
    if result.failed:
300
      raise errors.StorageError("Failed to run %r, command output: %s" %
301
                                (args[0], result.output))
302

    
303
    return result.stdout
304

    
305
  @staticmethod
306
  def _SplitList(data, sep, fieldcount):
307
    """Splits LVM command output into rows and fields.
308

309
    @type data: string
310
    @param data: LVM command output
311
    @type sep: string
312
    @param sep: Field separator character
313
    @type fieldcount: int
314
    @param fieldcount: Expected number of fields
315

316
    """
317
    for line in data.splitlines():
318
      fields = line.strip().split(sep)
319

    
320
      if len(fields) != fieldcount:
321
        logging.warning("Invalid line returned from lvm command: %s", line)
322
        continue
323

    
324
      yield fields
325

    
326

    
327
class LvmPvStorage(_LvmBase):
328
  """LVM Physical Volume storage unit.
329

330
  """
331
  @staticmethod
332
  def _GetAllocatable(attr):
333
    if attr:
334
      return (attr[0] == "a")
335
    else:
336
      logging.warning("Invalid PV attribute: %r", attr)
337
      return False
338

    
339
  LIST_COMMAND = "pvs"
340

    
341
  # Make sure to update constants.VALID_STORAGE_FIELDS when changing field
342
  # definitions.
343
  LIST_FIELDS = [
344
    (constants.SF_NAME, ["pv_name"], None),
345
    (constants.SF_SIZE, ["pv_size"], _ParseSize),
346
    (constants.SF_USED, ["pv_used"], _ParseSize),
347
    (constants.SF_FREE, ["pv_free"], _ParseSize),
348
    (constants.SF_ALLOCATABLE, ["pv_attr"], _GetAllocatable),
349
    ]
350

    
351
  def _SetAllocatable(self, name, allocatable):
352
    """Sets the "allocatable" flag on a physical volume.
353

354
    @type name: string
355
    @param name: Physical volume name
356
    @type allocatable: bool
357
    @param allocatable: Whether to set the "allocatable" flag
358

359
    """
360
    args = ["pvchange", "--allocatable"]
361

    
362
    if allocatable:
363
      args.append("y")
364
    else:
365
      args.append("n")
366

    
367
    args.append(name)
368

    
369
    result = utils.RunCmd(args)
370
    if result.failed:
371
      raise errors.StorageError("Failed to modify physical volume,"
372
                                " pvchange output: %s" %
373
                                result.output)
374

    
375
  def Modify(self, name, changes):
376
    """Modifies flags on a physical volume.
377

378
    See L{_Base.Modify}.
379

380
    """
381
    if constants.SF_ALLOCATABLE in changes:
382
      self._SetAllocatable(name, changes[constants.SF_ALLOCATABLE])
383
      del changes[constants.SF_ALLOCATABLE]
384

    
385
    # Other changes will be handled (and maybe refused) by the base class.
386
    return _LvmBase.Modify(self, name, changes)
387

    
388

    
389
class LvmVgStorage(_LvmBase):
390
  """LVM Volume Group storage unit.
391

392
  """
393
  LIST_COMMAND = "vgs"
394

    
395
  # Make sure to update constants.VALID_STORAGE_FIELDS when changing field
396
  # definitions.
397
  LIST_FIELDS = [
398
    (constants.SF_NAME, ["vg_name"], None),
399
    (constants.SF_SIZE, ["vg_size"], _ParseSize),
400
    (constants.SF_FREE, ["vg_free"], _ParseSize),
401
    (constants.SF_USED, ["vg_size", "vg_free"],
402
     lambda x, y: _ParseSize(x) - _ParseSize(y)),
403
    (constants.SF_ALLOCATABLE, [], True),
404
    ]
405

    
406
  def _RemoveMissing(self, name):
407
    """Runs "vgreduce --removemissing" on a volume group.
408

409
    @type name: string
410
    @param name: Volume group name
411

412
    """
413
    # Ignoring vgreduce exit code. Older versions exit with an error even tough
414
    # the VG is already consistent. This was fixed in later versions, but we
415
    # cannot depend on it.
416
    result = utils.RunCmd(["vgreduce", "--removemissing", name])
417

    
418
    # Keep output in case something went wrong
419
    vgreduce_output = result.output
420

    
421
    result = utils.RunCmd(["vgs", "--noheadings", "--nosuffix", name])
422
    if result.failed:
423
      raise errors.StorageError(("Volume group '%s' still not consistent,"
424
                                 " 'vgreduce' output: %r,"
425
                                 " 'vgs' output: %r") %
426
                                (name, vgreduce_output, result.output))
427

    
428
  def Execute(self, name, op):
429
    """Executes an operation on a virtual volume.
430

431
    See L{_Base.Execute}.
432

433
    """
434
    if op == constants.SO_FIX_CONSISTENCY:
435
      return self._RemoveMissing(name)
436

    
437
    return _LvmBase.Execute(self, name, op)
438

    
439

    
440
# Lookup table for storage types
441
_STORAGE_TYPES = {
442
  constants.ST_FILE: FileStorage,
443
  constants.ST_LVM_PV: LvmPvStorage,
444
  constants.ST_LVM_VG: LvmVgStorage,
445
  }
446

    
447

    
448
def GetStorageClass(name):
449
  """Returns the class for a storage type.
450

451
  @type name: string
452
  @param name: Storage type
453

454
  """
455
  try:
456
    return _STORAGE_TYPES[name]
457
  except KeyError:
458
    raise errors.StorageError("Unknown storage type: %r" % name)
459

    
460

    
461
def GetStorage(name, *args):
462
  """Factory function for storage methods.
463

464
  @type name: string
465
  @param name: Storage type
466

467
  """
468
  return GetStorageClass(name)(*args)