Statistics
| Branch: | Tag: | Revision:

root / lib / storage.py @ d3833ebd

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
  def _GetAllocatable(attr):
332
    if attr:
333
      return (attr[0] == "a")
334
    else:
335
      logging.warning("Invalid PV attribute: %r", attr)
336
      return False
337

    
338
  LIST_COMMAND = "pvs"
339

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

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

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

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

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

    
366
    args.append(name)
367

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

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

377
    See L{_Base.Modify}.
378

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

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

    
387

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

391
  """
392
  LIST_COMMAND = "vgs"
393

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

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

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

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

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

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

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

430
    See L{_Base.Execute}.
431

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

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

    
438

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

    
446

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

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

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

    
459

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

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

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