Statistics
| Branch: | Tag: | Revision:

root / lib / storage.py @ 4b37cac5

History | View | Annotate | Download (9.9 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 enumerate(fields_def)])
193

    
194
    lvm_fields = []
195

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

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

    
204
      lvm_fields.append(lvm_name)
205

    
206
    return utils.UniqueSequence(lvm_fields)
207

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

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

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

    
227
    data = []
228
    for raw_data in cmd_result:
229
      row = []
230

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

    
234
        value = raw_data[lvm_name_to_idx[lvm_name]]
235

    
236
        if convert_fn:
237
          value = convert_fn(value)
238

    
239
        row.append(value)
240

    
241
      data.append(row)
242

    
243
    return data
244

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

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

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

    
264
    if name is not None:
265
      args.append(name)
266

    
267
    return args
268

    
269
  @staticmethod
270
  def _RunListCommand(args):
271
    """Run LVM command.
272

273
    """
274
    result = utils.RunCmd(args)
275

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

    
280
    return result.stdout
281

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

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

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

    
297
      if len(fields) != fieldcount:
298
        continue
299

    
300
      yield fields
301

    
302

    
303
class LvmPvStorage(_LvmBase):
304
  """LVM Physical Volume storage unit.
305

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

    
314
  LIST_COMMAND = "pvs"
315

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

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

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

334
    """
335
    args = ["pvchange", "--allocatable"]
336

    
337
    if allocatable:
338
      args.append("y")
339
    else:
340
      args.append("n")
341

    
342
    args.append(name)
343

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

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

353
    See L{_Base.Modify}.
354

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

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

    
363

    
364
class LvmVgStorage(_LvmBase):
365
  """LVM Volume Group storage unit.
366

367
  """
368
  LIST_COMMAND = "vgs"
369

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

    
377

    
378
# Lookup table for storage types
379
_STORAGE_TYPES = {
380
  constants.ST_FILE: FileStorage,
381
  constants.ST_LVM_PV: LvmPvStorage,
382
  constants.ST_LVM_VG: LvmVgStorage,
383
  }
384

    
385

    
386
def GetStorageClass(name):
387
  """Returns the class for a storage type.
388

389
  @type name: string
390
  @param name: Storage type
391

392
  """
393
  try:
394
    return _STORAGE_TYPES[name]
395
  except KeyError:
396
    raise errors.StorageError("Unknown storage type: %r" % name)
397

    
398

    
399
def GetStorage(name, *args):
400
  """Factory function for storage methods.
401

402
  @type name: string
403
  @param name: Storage type
404

405
  """
406
  return GetStorageClass(name)(*args)