Statistics
| Branch: | Tag: | Revision:

root / lib / storage.py @ 9b648ee7

History | View | Annotate | Download (9.7 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

    
68
class FileStorage(_Base):
69
  """File storage unit.
70

71
  """
72
  def __init__(self, paths):
73
    """Initializes this class.
74

75
    @type paths: list
76
    @param paths: List of file storage paths
77

78
    """
79
    self._paths = paths
80

    
81
  def List(self, name, fields):
82
    """Returns a list of all entities within the storage unit.
83

84
    See L{_Base.List}.
85

86
    """
87
    rows = []
88

    
89
    if name is None:
90
      paths = self._paths
91
    else:
92
      paths = [name]
93

    
94
    for path in paths:
95
      rows.append(self._ListInner(path, fields))
96

    
97
    return rows
98

    
99
  @staticmethod
100
  def _ListInner(path, fields):
101
    """Gathers requested information from directory.
102

103
    @type path: string
104
    @param path: Path to directory
105
    @type fields: list
106
    @param fields: Requested fields
107

108
    """
109
    values = []
110

    
111
    # Pre-calculate information in case it's requested more than once
112
    if constants.SF_USED in fields:
113
      dirsize = utils.CalculateDirectorySize(path)
114
    else:
115
      dirsize = None
116

    
117
    if constants.SF_FREE in fields:
118
      fsfree = utils.GetFreeFilesystemSpace(path)
119
    else:
120
      fsfree = None
121

    
122
    # Make sure to update constants.VALID_STORAGE_FIELDS when changing fields.
123
    for field_name in fields:
124
      if field_name == constants.SF_NAME:
125
        values.append(path)
126

    
127
      elif field_name == constants.SF_USED:
128
        values.append(dirsize)
129

    
130
      elif field_name == constants.SF_FREE:
131
        values.append(fsfree)
132

    
133
      else:
134
        raise errors.StorageError("Unknown field: %r" % field_name)
135

    
136
    return values
137

    
138

    
139
class _LvmBase(_Base):
140
  """Base class for LVM storage containers.
141

142
  """
143
  LIST_SEP = "|"
144
  LIST_COMMAND = None
145
  LIST_FIELDS = None
146

    
147
  def List(self, name, wanted_field_names):
148
    """Returns a list of all entities within the storage unit.
149

150
    See L{_Base.List}.
151

152
    """
153
    # Get needed LVM fields
154
    lvm_fields = self._GetLvmFields(self.LIST_FIELDS, wanted_field_names)
155

    
156
    # Build LVM command
157
    cmd_args = self._BuildListCommand(self.LIST_COMMAND, self.LIST_SEP,
158
                                      lvm_fields, name)
159

    
160
    # Run LVM command
161
    cmd_result = self._RunListCommand(cmd_args)
162

    
163
    # Split and rearrange LVM command output
164
    return self._BuildList(self._SplitList(cmd_result, self.LIST_SEP,
165
                                           len(lvm_fields)),
166
                           self.LIST_FIELDS,
167
                           wanted_field_names,
168
                           lvm_fields)
169

    
170
  @staticmethod
171
  def _GetLvmFields(fields_def, wanted_field_names):
172
    """Returns unique list of fields wanted from LVM command.
173

174
    @type fields_def: list
175
    @param fields_def: Field definitions
176
    @type wanted_field_names: list
177
    @param wanted_field_names: List of requested fields
178

179
    """
180
    field_to_idx = dict([(field_name, idx)
181
                         for (idx, (field_name, _, _)) in enumerate(fields_def)])
182

    
183
    lvm_fields = []
184

    
185
    for field_name in wanted_field_names:
186
      try:
187
        idx = field_to_idx[field_name]
188
      except IndexError:
189
        raise errors.StorageError("Unknown field: %r" % field_name)
190

    
191
      (_, lvm_name, _) = fields_def[idx]
192

    
193
      lvm_fields.append(lvm_name)
194

    
195
    return utils.UniqueSequence(lvm_fields)
196

    
197
  @classmethod
198
  def _BuildList(cls, cmd_result, fields_def, wanted_field_names, lvm_fields):
199
    """Builds the final result list.
200

201
    @type cmd_result: iterable
202
    @param cmd_result: Iterable of LVM command output (iterable of lists)
203
    @type fields_def: list
204
    @param fields_def: Field definitions
205
    @type wanted_field_names: list
206
    @param wanted_field_names: List of requested fields
207
    @type lvm_fields: list
208
    @param lvm_fields: LVM fields
209

210
    """
211
    lvm_name_to_idx = dict([(lvm_name, idx)
212
                           for (idx, lvm_name) in enumerate(lvm_fields)])
213
    field_to_idx = dict([(field_name, idx)
214
                         for (idx, (field_name, _, _)) in enumerate(fields_def)])
215

    
216
    data = []
217
    for raw_data in cmd_result:
218
      row = []
219

    
220
      for field_name in wanted_field_names:
221
        (_, lvm_name, convert_fn) = fields_def[field_to_idx[field_name]]
222

    
223
        value = raw_data[lvm_name_to_idx[lvm_name]]
224

    
225
        if convert_fn:
226
          value = convert_fn(value)
227

    
228
        row.append(value)
229

    
230
      data.append(row)
231

    
232
    return data
233

    
234
  @staticmethod
235
  def _BuildListCommand(cmd, sep, options, name):
236
    """Builds LVM command line.
237

238
    @type cmd: string
239
    @param cmd: Command name
240
    @type sep: string
241
    @param sep: Field separator character
242
    @type options: list of strings
243
    @param options: Wanted LVM fields
244
    @type name: name or None
245
    @param name: Name of requested entity
246

247
    """
248
    args = [cmd,
249
            "--noheadings", "--units=m", "--nosuffix",
250
            "--separator", sep,
251
            "--options", ",".join(options)]
252

    
253
    if name is not None:
254
      args.append(name)
255

    
256
    return args
257

    
258
  @staticmethod
259
  def _RunListCommand(args):
260
    """Run LVM command.
261

262
    """
263
    result = utils.RunCmd(args)
264

    
265
    if result.failed:
266
      raise errors.StorageError("Failed to run %r, command output: %s" %
267
                                (args[0], result.output))
268

    
269
    return result.stdout
270

    
271
  @staticmethod
272
  def _SplitList(data, sep, fieldcount):
273
    """Splits LVM command output into rows and fields.
274

275
    @type data: string
276
    @param data: LVM command output
277
    @type sep: string
278
    @param sep: Field separator character
279
    @type fieldcount: int
280
    @param fieldcount: Expected number of fields
281

282
    """
283
    for line in data.splitlines():
284
      fields = line.strip().split(sep)
285

    
286
      if len(fields) != fieldcount:
287
        continue
288

    
289
      yield fields
290

    
291

    
292
class LvmPvStorage(_LvmBase):
293
  """LVM Physical Volume storage unit.
294

295
  """
296
  def _GetAllocatable(attr):
297
    if attr:
298
      return (attr[0] == "a")
299
    else:
300
      logging.warning("Invalid PV attribute: %r", attr)
301
      return False
302

    
303
  LIST_COMMAND = "pvs"
304

    
305
  # Make sure to update constants.VALID_STORAGE_FIELDS when changing field
306
  # definitions.
307
  LIST_FIELDS = [
308
    (constants.SF_NAME, "pv_name", None),
309
    (constants.SF_SIZE, "pv_size", _ParseSize),
310
    (constants.SF_USED, "pv_used", _ParseSize),
311
    (constants.SF_FREE, "pv_free", _ParseSize),
312
    (constants.SF_ALLOCATABLE, "pv_attr", _GetAllocatable),
313
    ]
314

    
315
  def _SetAllocatable(self, name, allocatable):
316
    """Sets the "allocatable" flag on a physical volume.
317

318
    @type name: string
319
    @param name: Physical volume name
320
    @type allocatable: bool
321
    @param allocatable: Whether to set the "allocatable" flag
322

323
    """
324
    args = ["pvchange", "--allocatable"]
325

    
326
    if allocatable:
327
      args.append("y")
328
    else:
329
      args.append("n")
330

    
331
    args.append(name)
332

    
333
    result = utils.RunCmd(args)
334
    if result.failed:
335
      raise errors.StorageError("Failed to modify physical volume,"
336
                                " pvchange output: %s" %
337
                                result.output)
338

    
339
  def Modify(self, name, changes):
340
    """Modifies flags on a physical volume.
341

342
    See L{_Base.Modify}.
343

344
    """
345
    if constants.SF_ALLOCATABLE in changes:
346
      self._SetAllocatable(name, changes[constants.SF_ALLOCATABLE])
347
      del changes[constants.SF_ALLOCATABLE]
348

    
349
    # Other changes will be handled (and maybe refused) by the base class.
350
    return _LvmBase.Modify(self, name, changes)
351

    
352

    
353
class LvmVgStorage(_LvmBase):
354
  """LVM Volume Group storage unit.
355

356
  """
357
  LIST_COMMAND = "vgs"
358

    
359
  # Make sure to update constants.VALID_STORAGE_FIELDS when changing field
360
  # definitions.
361
  LIST_FIELDS = [
362
    (constants.SF_NAME, "vg_name", None),
363
    (constants.SF_SIZE, "vg_size", _ParseSize),
364
    ]
365

    
366

    
367
# Lookup table for storage types
368
_STORAGE_TYPES = {
369
  constants.ST_FILE: FileStorage,
370
  constants.ST_LVM_PV: LvmPvStorage,
371
  constants.ST_LVM_VG: LvmVgStorage,
372
  }
373

    
374

    
375
def GetStorageClass(name):
376
  """Returns the class for a storage type.
377

378
  @type name: string
379
  @param name: Storage type
380

381
  """
382
  try:
383
    return _STORAGE_TYPES[name]
384
  except KeyError:
385
    raise errors.StorageError("Unknown storage type: %r" % name)
386

    
387

    
388
def GetStorage(name, *args):
389
  """Factory function for storage methods.
390

391
  @type name: string
392
  @param name: Storage type
393

394
  """
395
  return GetStorageClass(name)(*args)