Statistics
| Branch: | Tag: | Revision:

root / lib / storage.py @ 7260cfbe

History | View | Annotate | Download (12.5 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
# pylint: disable-msg=W0232,R0201
27

    
28
# W0232, since we use these as singletons rather than object holding
29
# data
30

    
31
# R0201, for the same reason
32

    
33
# TODO: FileStorage initialised with paths whereas the others not
34

    
35
import logging
36

    
37
from ganeti import errors
38
from ganeti import constants
39
from ganeti import utils
40

    
41

    
42
def _ParseSize(value):
43
  return int(round(float(value), 0))
44

    
45

    
46
class _Base:
47
  """Base class for storage abstraction.
48

49
  """
50
  def List(self, name, fields):
51
    """Returns a list of all entities within the storage unit.
52

53
    @type name: string or None
54
    @param name: Entity name or None for all
55
    @type fields: list
56
    @param fields: List with all requested result fields (order is preserved)
57

58
    """
59
    raise NotImplementedError()
60

    
61
  def Modify(self, name, changes): # pylint: disable-msg=W0613
62
    """Modifies an entity within the storage unit.
63

64
    @type name: string
65
    @param name: Entity name
66
    @type changes: dict
67
    @param changes: New field values
68

69
    """
70
    # Don't raise an error if no changes are requested
71
    if changes:
72
      raise errors.ProgrammerError("Unable to modify the following"
73
                                   "fields: %r" % (changes.keys(), ))
74

    
75
  def Execute(self, name, op):
76
    """Executes an operation on an entity within the storage unit.
77

78
    @type name: string
79
    @param name: Entity name
80
    @type op: string
81
    @param op: Operation name
82

83
    """
84
    raise NotImplementedError()
85

    
86

    
87
class FileStorage(_Base): # pylint: disable-msg=W0223
88
  """File storage unit.
89

90
  """
91
  def __init__(self, paths):
92
    """Initializes this class.
93

94
    @type paths: list
95
    @param paths: List of file storage paths
96

97
    """
98
    self._paths = paths
99

    
100
  def List(self, name, fields):
101
    """Returns a list of all entities within the storage unit.
102

103
    See L{_Base.List}.
104

105
    """
106
    rows = []
107

    
108
    if name is None:
109
      paths = self._paths
110
    else:
111
      paths = [name]
112

    
113
    for path in paths:
114
      rows.append(self._ListInner(path, fields))
115

    
116
    return rows
117

    
118
  @staticmethod
119
  def _ListInner(path, fields):
120
    """Gathers requested information from directory.
121

122
    @type path: string
123
    @param path: Path to directory
124
    @type fields: list
125
    @param fields: Requested fields
126

127
    """
128
    values = []
129

    
130
    # Pre-calculate information in case it's requested more than once
131
    if constants.SF_USED in fields:
132
      dirsize = utils.CalculateDirectorySize(path)
133
    else:
134
      dirsize = None
135

    
136
    if constants.SF_FREE in fields or constants.SF_SIZE in fields:
137
      fsstats = utils.GetFilesystemStats(path)
138
    else:
139
      fsstats = None
140

    
141
    # Make sure to update constants.VALID_STORAGE_FIELDS when changing fields.
142
    for field_name in fields:
143
      if field_name == constants.SF_NAME:
144
        values.append(path)
145

    
146
      elif field_name == constants.SF_USED:
147
        values.append(dirsize)
148

    
149
      elif field_name == constants.SF_FREE:
150
        values.append(fsstats[1])
151

    
152
      elif field_name == constants.SF_SIZE:
153
        values.append(fsstats[0])
154

    
155
      elif field_name == constants.SF_ALLOCATABLE:
156
        values.append(True)
157

    
158
      else:
159
        raise errors.StorageError("Unknown field: %r" % field_name)
160

    
161
    return values
162

    
163

    
164
class _LvmBase(_Base): # pylint: disable-msg=W0223
165
  """Base class for LVM storage containers.
166

167
  @cvar LIST_FIELDS: list of tuples consisting of three elements: SF_*
168
      constants, lvm command output fields (list), and conversion
169
      function or static value (for static value, the lvm output field
170
      can be an empty list)
171

172
  """
173
  LIST_SEP = "|"
174
  LIST_COMMAND = None
175
  LIST_FIELDS = None
176

    
177
  def List(self, name, wanted_field_names):
178
    """Returns a list of all entities within the storage unit.
179

180
    See L{_Base.List}.
181

182
    """
183
    # Get needed LVM fields
184
    lvm_fields = self._GetLvmFields(self.LIST_FIELDS, wanted_field_names)
185

    
186
    # Build LVM command
187
    cmd_args = self._BuildListCommand(self.LIST_COMMAND, self.LIST_SEP,
188
                                      lvm_fields, name)
189

    
190
    # Run LVM command
191
    cmd_result = self._RunListCommand(cmd_args)
192

    
193
    # Split and rearrange LVM command output
194
    return self._BuildList(self._SplitList(cmd_result, self.LIST_SEP,
195
                                           len(lvm_fields)),
196
                           self.LIST_FIELDS,
197
                           wanted_field_names,
198
                           lvm_fields)
199

    
200
  @staticmethod
201
  def _GetLvmFields(fields_def, wanted_field_names):
202
    """Returns unique list of fields wanted from LVM command.
203

204
    @type fields_def: list
205
    @param fields_def: Field definitions
206
    @type wanted_field_names: list
207
    @param wanted_field_names: List of requested fields
208

209
    """
210
    field_to_idx = dict([(field_name, idx)
211
                         for (idx, (field_name, _, _)) in
212
                         enumerate(fields_def)])
213

    
214
    lvm_fields = []
215

    
216
    for field_name in wanted_field_names:
217
      try:
218
        idx = field_to_idx[field_name]
219
      except IndexError:
220
        raise errors.StorageError("Unknown field: %r" % field_name)
221

    
222
      (_, lvm_names, _) = fields_def[idx]
223

    
224
      lvm_fields.extend(lvm_names)
225

    
226
    return utils.UniqueSequence(lvm_fields)
227

    
228
  @classmethod
229
  def _BuildList(cls, cmd_result, fields_def, wanted_field_names, lvm_fields):
230
    """Builds the final result list.
231

232
    @type cmd_result: iterable
233
    @param cmd_result: Iterable of LVM command output (iterable of lists)
234
    @type fields_def: list
235
    @param fields_def: Field definitions
236
    @type wanted_field_names: list
237
    @param wanted_field_names: List of requested fields
238
    @type lvm_fields: list
239
    @param lvm_fields: LVM fields
240

241
    """
242
    lvm_name_to_idx = dict([(lvm_name, idx)
243
                           for (idx, lvm_name) in enumerate(lvm_fields)])
244
    field_to_idx = dict([(field_name, idx)
245
                         for (idx, (field_name, _, _)) in
246
                         enumerate(fields_def)])
247

    
248
    data = []
249
    for raw_data in cmd_result:
250
      row = []
251

    
252
      for field_name in wanted_field_names:
253
        (_, lvm_names, mapper) = fields_def[field_to_idx[field_name]]
254

    
255
        values = [raw_data[lvm_name_to_idx[i]] for i in lvm_names]
256

    
257
        if callable(mapper):
258
          # we got a function, call it with all the declared fields
259
          val = mapper(*values) # pylint: disable-msg=W0142
260
        elif len(values) == 1:
261
          # we don't have a function, but we had a single field
262
          # declared, pass it unchanged
263
          val = values[0]
264
        else:
265
          # let's make sure there are no fields declared (cannot map >
266
          # 1 field without a function)
267
          assert not values, "LVM storage has multi-fields without a function"
268
          val = mapper
269

    
270
        row.append(val)
271

    
272
      data.append(row)
273

    
274
    return data
275

    
276
  @staticmethod
277
  def _BuildListCommand(cmd, sep, options, name):
278
    """Builds LVM command line.
279

280
    @type cmd: string
281
    @param cmd: Command name
282
    @type sep: string
283
    @param sep: Field separator character
284
    @type options: list of strings
285
    @param options: Wanted LVM fields
286
    @type name: name or None
287
    @param name: Name of requested entity
288

289
    """
290
    args = [cmd,
291
            "--noheadings", "--units=m", "--nosuffix",
292
            "--separator", sep,
293
            "--options", ",".join(options)]
294

    
295
    if name is not None:
296
      args.append(name)
297

    
298
    return args
299

    
300
  @staticmethod
301
  def _RunListCommand(args):
302
    """Run LVM command.
303

304
    """
305
    result = utils.RunCmd(args)
306

    
307
    if result.failed:
308
      raise errors.StorageError("Failed to run %r, command output: %s" %
309
                                (args[0], result.output))
310

    
311
    return result.stdout
312

    
313
  @staticmethod
314
  def _SplitList(data, sep, fieldcount):
315
    """Splits LVM command output into rows and fields.
316

317
    @type data: string
318
    @param data: LVM command output
319
    @type sep: string
320
    @param sep: Field separator character
321
    @type fieldcount: int
322
    @param fieldcount: Expected number of fields
323

324
    """
325
    for line in data.splitlines():
326
      fields = line.strip().split(sep)
327

    
328
      if len(fields) != fieldcount:
329
        logging.warning("Invalid line returned from lvm command: %s", line)
330
        continue
331

    
332
      yield fields
333

    
334

    
335
class LvmPvStorage(_LvmBase): # pylint: disable-msg=W0223
336
  """LVM Physical Volume storage unit.
337

338
  """
339
  @staticmethod
340
  def _GetAllocatable(attr):
341
    if attr:
342
      return (attr[0] == "a")
343
    else:
344
      logging.warning("Invalid PV attribute: %r", attr)
345
      return False
346

    
347
  LIST_COMMAND = "pvs"
348

    
349
  # Make sure to update constants.VALID_STORAGE_FIELDS when changing field
350
  # definitions.
351
  LIST_FIELDS = [
352
    (constants.SF_NAME, ["pv_name"], None),
353
    (constants.SF_SIZE, ["pv_size"], _ParseSize),
354
    (constants.SF_USED, ["pv_used"], _ParseSize),
355
    (constants.SF_FREE, ["pv_free"], _ParseSize),
356
    (constants.SF_ALLOCATABLE, ["pv_attr"], _GetAllocatable),
357
    ]
358

    
359
  def _SetAllocatable(self, name, allocatable):
360
    """Sets the "allocatable" flag on a physical volume.
361

362
    @type name: string
363
    @param name: Physical volume name
364
    @type allocatable: bool
365
    @param allocatable: Whether to set the "allocatable" flag
366

367
    """
368
    args = ["pvchange", "--allocatable"]
369

    
370
    if allocatable:
371
      args.append("y")
372
    else:
373
      args.append("n")
374

    
375
    args.append(name)
376

    
377
    result = utils.RunCmd(args)
378
    if result.failed:
379
      raise errors.StorageError("Failed to modify physical volume,"
380
                                " pvchange output: %s" %
381
                                result.output)
382

    
383
  def Modify(self, name, changes):
384
    """Modifies flags on a physical volume.
385

386
    See L{_Base.Modify}.
387

388
    """
389
    if constants.SF_ALLOCATABLE in changes:
390
      self._SetAllocatable(name, changes[constants.SF_ALLOCATABLE])
391
      del changes[constants.SF_ALLOCATABLE]
392

    
393
    # Other changes will be handled (and maybe refused) by the base class.
394
    return _LvmBase.Modify(self, name, changes)
395

    
396

    
397
class LvmVgStorage(_LvmBase):
398
  """LVM Volume Group storage unit.
399

400
  """
401
  LIST_COMMAND = "vgs"
402

    
403
  # Make sure to update constants.VALID_STORAGE_FIELDS when changing field
404
  # definitions.
405
  LIST_FIELDS = [
406
    (constants.SF_NAME, ["vg_name"], None),
407
    (constants.SF_SIZE, ["vg_size"], _ParseSize),
408
    (constants.SF_FREE, ["vg_free"], _ParseSize),
409
    (constants.SF_USED, ["vg_size", "vg_free"],
410
     lambda x, y: _ParseSize(x) - _ParseSize(y)),
411
    (constants.SF_ALLOCATABLE, [], True),
412
    ]
413

    
414
  def _RemoveMissing(self, name):
415
    """Runs "vgreduce --removemissing" on a volume group.
416

417
    @type name: string
418
    @param name: Volume group name
419

420
    """
421
    # Ignoring vgreduce exit code. Older versions exit with an error even tough
422
    # the VG is already consistent. This was fixed in later versions, but we
423
    # cannot depend on it.
424
    result = utils.RunCmd(["vgreduce", "--removemissing", name])
425

    
426
    # Keep output in case something went wrong
427
    vgreduce_output = result.output
428

    
429
    result = utils.RunCmd(["vgs", "--noheadings", "--nosuffix", name])
430
    if result.failed:
431
      raise errors.StorageError(("Volume group '%s' still not consistent,"
432
                                 " 'vgreduce' output: %r,"
433
                                 " 'vgs' output: %r") %
434
                                (name, vgreduce_output, result.output))
435

    
436
  def Execute(self, name, op):
437
    """Executes an operation on a virtual volume.
438

439
    See L{_Base.Execute}.
440

441
    """
442
    if op == constants.SO_FIX_CONSISTENCY:
443
      return self._RemoveMissing(name)
444

    
445
    return _LvmBase.Execute(self, name, op)
446

    
447

    
448
# Lookup table for storage types
449
_STORAGE_TYPES = {
450
  constants.ST_FILE: FileStorage,
451
  constants.ST_LVM_PV: LvmPvStorage,
452
  constants.ST_LVM_VG: LvmVgStorage,
453
  }
454

    
455

    
456
def GetStorageClass(name):
457
  """Returns the class for a storage type.
458

459
  @type name: string
460
  @param name: Storage type
461

462
  """
463
  try:
464
    return _STORAGE_TYPES[name]
465
  except KeyError:
466
    raise errors.StorageError("Unknown storage type: %r" % name)
467

    
468

    
469
def GetStorage(name, *args):
470
  """Factory function for storage methods.
471

472
  @type name: string
473
  @param name: Storage type
474

475
  """
476
  return GetStorageClass(name)(*args)