Statistics
| Branch: | Tag: | Revision:

root / lib / storage.py @ e4335b5b

History | View | Annotate | Download (11 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:
129
      fsfree = utils.GetFreeFilesystemSpace(path)
130
    else:
131
      fsfree = 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(fsfree)
143

    
144
      else:
145
        raise errors.StorageError("Unknown field: %r" % field_name)
146

    
147
    return values
148

    
149

    
150
class _LvmBase(_Base):
151
  """Base class for LVM storage containers.
152

153
  """
154
  LIST_SEP = "|"
155
  LIST_COMMAND = None
156
  LIST_FIELDS = None
157

    
158
  def List(self, name, wanted_field_names):
159
    """Returns a list of all entities within the storage unit.
160

161
    See L{_Base.List}.
162

163
    """
164
    # Get needed LVM fields
165
    lvm_fields = self._GetLvmFields(self.LIST_FIELDS, wanted_field_names)
166

    
167
    # Build LVM command
168
    cmd_args = self._BuildListCommand(self.LIST_COMMAND, self.LIST_SEP,
169
                                      lvm_fields, name)
170

    
171
    # Run LVM command
172
    cmd_result = self._RunListCommand(cmd_args)
173

    
174
    # Split and rearrange LVM command output
175
    return self._BuildList(self._SplitList(cmd_result, self.LIST_SEP,
176
                                           len(lvm_fields)),
177
                           self.LIST_FIELDS,
178
                           wanted_field_names,
179
                           lvm_fields)
180

    
181
  @staticmethod
182
  def _GetLvmFields(fields_def, wanted_field_names):
183
    """Returns unique list of fields wanted from LVM command.
184

185
    @type fields_def: list
186
    @param fields_def: Field definitions
187
    @type wanted_field_names: list
188
    @param wanted_field_names: List of requested fields
189

190
    """
191
    field_to_idx = dict([(field_name, idx)
192
                         for (idx, (field_name, _, _)) in
193
                         enumerate(fields_def)])
194

    
195
    lvm_fields = []
196

    
197
    for field_name in wanted_field_names:
198
      try:
199
        idx = field_to_idx[field_name]
200
      except IndexError:
201
        raise errors.StorageError("Unknown field: %r" % field_name)
202

    
203
      (_, lvm_name, _) = fields_def[idx]
204

    
205
      lvm_fields.append(lvm_name)
206

    
207
    return utils.UniqueSequence(lvm_fields)
208

    
209
  @classmethod
210
  def _BuildList(cls, cmd_result, fields_def, wanted_field_names, lvm_fields):
211
    """Builds the final result list.
212

213
    @type cmd_result: iterable
214
    @param cmd_result: Iterable of LVM command output (iterable of lists)
215
    @type fields_def: list
216
    @param fields_def: Field definitions
217
    @type wanted_field_names: list
218
    @param wanted_field_names: List of requested fields
219
    @type lvm_fields: list
220
    @param lvm_fields: LVM fields
221

222
    """
223
    lvm_name_to_idx = dict([(lvm_name, idx)
224
                           for (idx, lvm_name) in enumerate(lvm_fields)])
225
    field_to_idx = dict([(field_name, idx)
226
                         for (idx, (field_name, _, _)) in
227
                         enumerate(fields_def)])
228

    
229
    data = []
230
    for raw_data in cmd_result:
231
      row = []
232

    
233
      for field_name in wanted_field_names:
234
        (_, lvm_name, convert_fn) = fields_def[field_to_idx[field_name]]
235

    
236
        value = raw_data[lvm_name_to_idx[lvm_name]]
237

    
238
        if convert_fn:
239
          value = convert_fn(value)
240

    
241
        row.append(value)
242

    
243
      data.append(row)
244

    
245
    return data
246

    
247
  @staticmethod
248
  def _BuildListCommand(cmd, sep, options, name):
249
    """Builds LVM command line.
250

251
    @type cmd: string
252
    @param cmd: Command name
253
    @type sep: string
254
    @param sep: Field separator character
255
    @type options: list of strings
256
    @param options: Wanted LVM fields
257
    @type name: name or None
258
    @param name: Name of requested entity
259

260
    """
261
    args = [cmd,
262
            "--noheadings", "--units=m", "--nosuffix",
263
            "--separator", sep,
264
            "--options", ",".join(options)]
265

    
266
    if name is not None:
267
      args.append(name)
268

    
269
    return args
270

    
271
  @staticmethod
272
  def _RunListCommand(args):
273
    """Run LVM command.
274

275
    """
276
    result = utils.RunCmd(args)
277

    
278
    if result.failed:
279
      raise errors.StorageError("Failed to run %r, command output: %s" %
280
                                (args[0], result.output))
281

    
282
    return result.stdout
283

    
284
  @staticmethod
285
  def _SplitList(data, sep, fieldcount):
286
    """Splits LVM command output into rows and fields.
287

288
    @type data: string
289
    @param data: LVM command output
290
    @type sep: string
291
    @param sep: Field separator character
292
    @type fieldcount: int
293
    @param fieldcount: Expected number of fields
294

295
    """
296
    for line in data.splitlines():
297
      fields = line.strip().split(sep)
298

    
299
      if len(fields) != fieldcount:
300
        continue
301

    
302
      yield fields
303

    
304

    
305
class LvmPvStorage(_LvmBase):
306
  """LVM Physical Volume storage unit.
307

308
  """
309
  def _GetAllocatable(attr):
310
    if attr:
311
      return (attr[0] == "a")
312
    else:
313
      logging.warning("Invalid PV attribute: %r", attr)
314
      return False
315

    
316
  LIST_COMMAND = "pvs"
317

    
318
  # Make sure to update constants.VALID_STORAGE_FIELDS when changing field
319
  # definitions.
320
  LIST_FIELDS = [
321
    (constants.SF_NAME, "pv_name", None),
322
    (constants.SF_SIZE, "pv_size", _ParseSize),
323
    (constants.SF_USED, "pv_used", _ParseSize),
324
    (constants.SF_FREE, "pv_free", _ParseSize),
325
    (constants.SF_ALLOCATABLE, "pv_attr", _GetAllocatable),
326
    ]
327

    
328
  def _SetAllocatable(self, name, allocatable):
329
    """Sets the "allocatable" flag on a physical volume.
330

331
    @type name: string
332
    @param name: Physical volume name
333
    @type allocatable: bool
334
    @param allocatable: Whether to set the "allocatable" flag
335

336
    """
337
    args = ["pvchange", "--allocatable"]
338

    
339
    if allocatable:
340
      args.append("y")
341
    else:
342
      args.append("n")
343

    
344
    args.append(name)
345

    
346
    result = utils.RunCmd(args)
347
    if result.failed:
348
      raise errors.StorageError("Failed to modify physical volume,"
349
                                " pvchange output: %s" %
350
                                result.output)
351

    
352
  def Modify(self, name, changes):
353
    """Modifies flags on a physical volume.
354

355
    See L{_Base.Modify}.
356

357
    """
358
    if constants.SF_ALLOCATABLE in changes:
359
      self._SetAllocatable(name, changes[constants.SF_ALLOCATABLE])
360
      del changes[constants.SF_ALLOCATABLE]
361

    
362
    # Other changes will be handled (and maybe refused) by the base class.
363
    return _LvmBase.Modify(self, name, changes)
364

    
365

    
366
class LvmVgStorage(_LvmBase):
367
  """LVM Volume Group storage unit.
368

369
  """
370
  LIST_COMMAND = "vgs"
371

    
372
  # Make sure to update constants.VALID_STORAGE_FIELDS when changing field
373
  # definitions.
374
  LIST_FIELDS = [
375
    (constants.SF_NAME, "vg_name", None),
376
    (constants.SF_SIZE, "vg_size", _ParseSize),
377
    ]
378

    
379
  def _RemoveMissing(self, name):
380
    """Runs "vgreduce --removemissing" on a volume group.
381

382
    @type name: string
383
    @param name: Volume group name
384

385
    """
386
    # Ignoring vgreduce exit code. Older versions exit with an error even tough
387
    # the VG is already consistent. This was fixed in later versions, but we
388
    # cannot depend on it.
389
    result = utils.RunCmd(["vgreduce", "--removemissing", name])
390

    
391
    # Keep output in case something went wrong
392
    vgreduce_output = result.output
393

    
394
    result = utils.RunCmd(["vgs", "--noheadings", "--nosuffix", name])
395
    if result.failed:
396
      raise errors.StorageError(("Volume group '%s' still not consistent,"
397
                                 " 'vgreduce' output: %r,"
398
                                 " 'vgs' output: %r") %
399
                                (name, vgreduce_output, result.output))
400

    
401
  def Execute(self, name, op):
402
    """Executes an operation on a virtual volume.
403

404
    See L{_Base.Execute}.
405

406
    """
407
    if op == constants.SO_FIX_CONSISTENCY:
408
      return self._RemoveMissing(name)
409

    
410
    return _LvmBase.Execute(self, name, op)
411

    
412

    
413
# Lookup table for storage types
414
_STORAGE_TYPES = {
415
  constants.ST_FILE: FileStorage,
416
  constants.ST_LVM_PV: LvmPvStorage,
417
  constants.ST_LVM_VG: LvmVgStorage,
418
  }
419

    
420

    
421
def GetStorageClass(name):
422
  """Returns the class for a storage type.
423

424
  @type name: string
425
  @param name: Storage type
426

427
  """
428
  try:
429
    return _STORAGE_TYPES[name]
430
  except KeyError:
431
    raise errors.StorageError("Unknown storage type: %r" % name)
432

    
433

    
434
def GetStorage(name, *args):
435
  """Factory function for storage methods.
436

437
  @type name: string
438
  @param name: Storage type
439

440
  """
441
  return GetStorageClass(name)(*args)