Revision ac2d0fe4

b/Makefile.am
85 85
	lib/serializer.py \
86 86
	lib/ssconf.py \
87 87
	lib/ssh.py \
88
	lib/storage.py \
88 89
	lib/utils.py \
89 90
	lib/workerpool.py
90 91

  
b/lib/constants.py
182 182
HKR_FAIL = 1
183 183
HKR_SUCCESS = 2
184 184

  
185
# Storage types
186
ST_FILE = "file"
187
ST_LVM_PV = "lvm-pv"
188
ST_LVM_VG = "lvm-vg"
189

  
185 190
# disk template types
186 191
DT_DISKLESS = "diskless"
187 192
DT_PLAIN = "plain"
b/lib/errors.py
195 195

  
196 196
  """
197 197

  
198

  
198 199
class TypeEnforcementError(GenericError):
199 200
  """Unable to enforce data type.
200 201

  
201 202
  """
202 203

  
204

  
203 205
class SshKeyError(GenericError):
204 206
  """Invalid SSH key.
205 207

  
......
220 222
  """
221 223

  
222 224

  
225
class StorageError(GenericError):
226
  """Storage-related exception.
227

  
228
  """
229

  
230

  
223 231
class QuitGanetiException(Exception):
224 232
  """Signal that Ganeti that it must quit.
225 233

  
b/lib/storage.py
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
COL_NAME = "name"
35
COL_SIZE = "size"
36
COL_FREE = "free"
37
COL_USED = "used"
38
COL_ALLOCATABLE = "allocatable"
39

  
40

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

  
44

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

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

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

  
57
    """
58
    raise NotImplementedError()
59

  
60

  
61
class FileStorage(_Base):
62
  """File storage unit.
63

  
64
  """
65
  def __init__(self, paths):
66
    """Initializes this class.
67

  
68
    @type paths: list
69
    @param paths: List of file storage paths
70

  
71
    """
72
    self._paths = paths
73

  
74
  def List(self, name, fields):
75
    """Returns a list of all entities within the storage unit.
76

  
77
    See L{_Base.List}.
78

  
79
    """
80
    rows = []
81

  
82
    if name is None:
83
      paths = self._paths
84
    else:
85
      paths = [name]
86

  
87
    for path in paths:
88
      rows.append(self._ListInner(path, fields))
89

  
90
    return rows
91

  
92
  @staticmethod
93
  def _ListInner(path, fields):
94
    """Gathers requested information from directory.
95

  
96
    @type path: string
97
    @param path: Path to directory
98
    @type fields: list
99
    @param fields: Requested fields
100

  
101
    """
102
    values = []
103

  
104
    # Pre-calculate information in case it's requested more than once
105
    if COL_SIZE in fields:
106
      dirsize = utils.CalculateDirectorySize(path)
107
    else:
108
      dirsize = None
109

  
110
    if COL_FREE in fields:
111
      fsfree = utils.GetFreeFilesystemSpace(path)
112
    else:
113
      fsfree = None
114

  
115
    for field_name in fields:
116
      if field_name == COL_NAME:
117
        values.append(path)
118

  
119
      elif field_name == COL_SIZE:
120
        values.append(dirsize)
121

  
122
      elif field_name == COL_FREE:
123
        values.append(fsfree)
124

  
125
      else:
126
        raise errors.StorageError("Unknown field: %r" % field_name)
127

  
128
    return values
129

  
130

  
131
class _LvmBase(_Base):
132
  """Base class for LVM storage containers.
133

  
134
  """
135
  LIST_SEP = "|"
136
  LIST_COMMAND = None
137
  LIST_FIELDS = None
138

  
139
  def List(self, name, wanted_field_names):
140
    """Returns a list of all entities within the storage unit.
141

  
142
    See L{_Base.List}.
143

  
144
    """
145
    # Get needed LVM fields
146
    lvm_fields = self._GetLvmFields(self.LIST_FIELDS, wanted_field_names)
147

  
148
    # Build LVM command
149
    cmd_args = self._BuildListCommand(self.LIST_COMMAND, self.LIST_SEP,
150
                                      lvm_fields, name)
151

  
152
    # Run LVM command
153
    cmd_result = self._RunListCommand(cmd_args)
154

  
155
    # Split and rearrange LVM command output
156
    return self._BuildList(self._SplitList(cmd_result, self.LIST_SEP,
157
                                           len(lvm_fields)),
158
                           self.LIST_FIELDS,
159
                           wanted_field_names,
160
                           lvm_fields)
161

  
162
  @staticmethod
163
  def _GetLvmFields(fields_def, wanted_field_names):
164
    """Returns unique list of fields wanted from LVM command.
165

  
166
    @type fields_def: list
167
    @param fields_def: Field definitions
168
    @type wanted_field_names: list
169
    @param wanted_field_names: List of requested fields
170

  
171
    """
172
    field_to_idx = dict([(field_name, idx)
173
                         for (idx, (field_name, _, _)) in enumerate(fields_def)])
174

  
175
    lvm_fields = []
176

  
177
    for field_name in wanted_field_names:
178
      try:
179
        idx = field_to_idx[field_name]
180
      except IndexError:
181
        raise errors.StorageError("Unknown field: %r" % field_name)
182

  
183
      (_, lvm_name, _) = fields_def[idx]
184

  
185
      lvm_fields.append(lvm_name)
186

  
187
    return utils.UniqueSequence(lvm_fields)
188

  
189
  @classmethod
190
  def _BuildList(cls, cmd_result, fields_def, wanted_field_names, lvm_fields):
191
    """Builds the final result list.
192

  
193
    @type cmd_result: iterable
194
    @param cmd_result: Iterable of LVM command output (iterable of lists)
195
    @type fields_def: list
196
    @param fields_def: Field definitions
197
    @type wanted_field_names: list
198
    @param wanted_field_names: List of requested fields
199
    @type lvm_fields: list
200
    @param lvm_fields: LVM fields
201

  
202
    """
203
    lvm_name_to_idx = dict([(lvm_name, idx)
204
                           for (idx, lvm_name) in enumerate(lvm_fields)])
205
    field_to_idx = dict([(field_name, idx)
206
                         for (idx, (field_name, _, _)) in enumerate(fields_def)])
207

  
208
    data = []
209
    for raw_data in cmd_result:
210
      row = []
211

  
212
      for field_name in wanted_field_names:
213
        (_, lvm_name, convert_fn) = fields_def[field_to_idx[field_name]]
214

  
215
        value = raw_data[lvm_name_to_idx[lvm_name]]
216

  
217
        if convert_fn:
218
          value = convert_fn(value)
219

  
220
        row.append(value)
221

  
222
      data.append(row)
223

  
224
    return data
225

  
226
  @staticmethod
227
  def _BuildListCommand(cmd, sep, options, name):
228
    """Builds LVM command line.
229

  
230
    @type cmd: string
231
    @param cmd: Command name
232
    @type sep: string
233
    @param sep: Field separator character
234
    @type options: list of strings
235
    @param options: Wanted LVM fields
236
    @type name: name or None
237
    @param name: Name of requested entity
238

  
239
    """
240
    args = [cmd,
241
            "--noheadings", "--units=m", "--nosuffix",
242
            "--separator", sep,
243
            "--options", ",".join(options)]
244

  
245
    if name is not None:
246
      args.append(name)
247

  
248
    return args
249

  
250
  @staticmethod
251
  def _RunListCommand(args):
252
    """Run LVM command.
253

  
254
    """
255
    result = utils.RunCmd(args)
256

  
257
    if result.failed:
258
      raise errors.StorageError("Failed to run %r, command output: %s" %
259
                                (args[0], result.output))
260

  
261
    return result.stdout
262

  
263
  @staticmethod
264
  def _SplitList(data, sep, fieldcount):
265
    """Splits LVM command output into rows and fields.
266

  
267
    @type data: string
268
    @param data: LVM command output
269
    @type sep: string
270
    @param sep: Field separator character
271
    @type fieldcount: int
272
    @param fieldcount: Expected number of fields
273

  
274
    """
275
    for line in data.splitlines():
276
      fields = line.strip().split(sep)
277

  
278
      if len(fields) != fieldcount:
279
        continue
280

  
281
      yield fields
282

  
283

  
284
class LvmPvStorage(_LvmBase):
285
  """LVM Physical Volume storage unit.
286

  
287
  """
288
  def _GetAllocatable(attr):
289
    if attr:
290
      return (attr[0] == "a")
291
    else:
292
      logging.warning("Invalid PV attribute: %r", attr)
293
      return False
294

  
295
  LIST_COMMAND = "pvs"
296
  LIST_FIELDS = [
297
    (COL_NAME, "pv_name", None),
298
    (COL_SIZE, "pv_size", _ParseSize),
299
    (COL_USED, "pv_used", _ParseSize),
300
    (COL_FREE, "pv_free", _ParseSize),
301
    (COL_ALLOCATABLE, "pv_attr", _GetAllocatable),
302
    ]
303

  
304

  
305
class LvmVgStorage(_LvmBase):
306
  """LVM Volume Group storage unit.
307

  
308
  """
309
  LIST_COMMAND = "vgs"
310
  LIST_FIELDS = [
311
    (COL_NAME, "vg_name", None),
312
    (COL_SIZE, "vg_size", _ParseSize),
313
    ]
314

  
315

  
316
# Lookup table for storage types
317
_STORAGE_TYPES = {
318
  constants.ST_FILE: FileStorage,
319
  constants.ST_LVM_PV: LvmPvStorage,
320
  constants.ST_LVM_VG: LvmVgStorage,
321
  }
322

  
323

  
324
def GetStorageClass(name):
325
  """Returns the class for a storage type.
326

  
327
  @type name: string
328
  @param name: Storage type
329

  
330
  """
331
  try:
332
    return _STORAGE_TYPES[name]
333
  except KeyError:
334
    raise errors.StorageError("Unknown storage type: %r" % name)
335

  
336

  
337
def GetStorage(name, *args):
338
  """Factory function for storage methods.
339

  
340
  @type name: string
341
  @param name: Storage type
342

  
343
  """
344
  return GetStorageClass(name)(*args)

Also available in: Unified diff