Revision cde49218

b/Makefile.am
41 41
# the directory + 'dir' suffix
42 42
clientdir = $(pkgpythondir)/client
43 43
hypervisordir = $(pkgpythondir)/hypervisor
44
blockdir = $(pkgpythondir)/block
44
storagedir = $(pkgpythondir)/storage
45 45
httpdir = $(pkgpythondir)/http
46 46
masterddir = $(pkgpythondir)/masterd
47 47
confddir = $(pkgpythondir)/confd
......
109 109
	lib/confd \
110 110
	lib/http \
111 111
	lib/hypervisor \
112
	lib/block \
113 112
	lib/impexpd \
114 113
	lib/masterd \
115 114
	lib/rapi \
116 115
	lib/server \
116
	lib/storage \
117 117
	lib/tools \
118 118
	lib/utils \
119 119
	lib/watcher \
......
316 316
	lib/hypervisor/hv_lxc.py \
317 317
	lib/hypervisor/hv_xen.py
318 318

  
319
block_PYTHON = \
320
	lib/block/__init__.py \
321
	lib/block/bdev.py \
322
	lib/block/base.py \
323
	lib/block/drbd.py \
324
	lib/block/drbd_info.py \
325
	lib/block/drbd_cmdgen.py
319
storage_PYTHON = \
320
	lib/storage/__init__.py \
321
	lib/storage/bdev.py \
322
	lib/storage/base.py \
323
	lib/storage/drbd.py \
324
	lib/storage/drbd_info.py \
325
	lib/storage/drbd_cmdgen.py
326 326

  
327 327
rapi_PYTHON = \
328 328
	lib/rapi/__init__.py \
......
1142 1142
	test/py/ganeti.asyncnotifier_unittest.py \
1143 1143
	test/py/ganeti.backend_unittest-runasroot.py \
1144 1144
	test/py/ganeti.backend_unittest.py \
1145
	test/py/ganeti.block.bdev_unittest.py \
1146
	test/py/ganeti.block.drbd_unittest.py \
1145
	test/py/ganeti.storage.bdev_unittest.py \
1146
	test/py/ganeti.storage.drbd_unittest.py \
1147 1147
	test/py/ganeti.cli_unittest.py \
1148 1148
	test/py/ganeti.client.gnt_cluster_unittest.py \
1149 1149
	test/py/ganeti.client.gnt_instance_unittest.py \
......
1263 1263
	$(pkgpython_PYTHON) \
1264 1264
	$(client_PYTHON) \
1265 1265
	$(hypervisor_PYTHON) \
1266
	$(block_PYTHON) \
1266
	$(storage_PYTHON) \
1267 1267
	$(rapi_PYTHON) \
1268 1268
	$(server_PYTHON) \
1269 1269
	$(pytools_PYTHON) \
b/lib/backend.py
54 54
from ganeti import ssh
55 55
from ganeti import hypervisor
56 56
from ganeti import constants
57
from ganeti.block import bdev
58
from ganeti.block import drbd
57
from ganeti.storage import bdev
58
from ganeti.storage import drbd
59 59
from ganeti import objects
60 60
from ganeti import ssconf
61 61
from ganeti import serializer
......
65 65
from ganeti import pathutils
66 66
from ganeti import vcluster
67 67
from ganeti import ht
68
from ganeti.block.base import BlockDev
69
from ganeti.block.drbd import DRBD8
68
from ganeti.storage.base import BlockDev
69
from ganeti.storage.drbd import DRBD8
70 70
from ganeti import hooksmaster
71 71

  
72 72

  
/dev/null
1
#
2
#
3

  
4
# Copyright (C) 2006, 2007, 2008 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
"""Block device abstraction
23

  
24
"""
/dev/null
1
#
2
#
3

  
4
# Copyright (C) 2006, 2007, 2010, 2011, 2012, 2013 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
"""Block device abstraction - base class and utility functions"""
23

  
24
import logging
25

  
26
from ganeti import objects
27
from ganeti import constants
28
from ganeti import utils
29
from ganeti import errors
30

  
31

  
32
class BlockDev(object):
33
  """Block device abstract class.
34

  
35
  A block device can be in the following states:
36
    - not existing on the system, and by `Create()` it goes into:
37
    - existing but not setup/not active, and by `Assemble()` goes into:
38
    - active read-write and by `Open()` it goes into
39
    - online (=used, or ready for use)
40

  
41
  A device can also be online but read-only, however we are not using
42
  the readonly state (LV has it, if needed in the future) and we are
43
  usually looking at this like at a stack, so it's easier to
44
  conceptualise the transition from not-existing to online and back
45
  like a linear one.
46

  
47
  The many different states of the device are due to the fact that we
48
  need to cover many device types:
49
    - logical volumes are created, lvchange -a y $lv, and used
50
    - drbd devices are attached to a local disk/remote peer and made primary
51

  
52
  A block device is identified by three items:
53
    - the /dev path of the device (dynamic)
54
    - a unique ID of the device (static)
55
    - it's major/minor pair (dynamic)
56

  
57
  Not all devices implement both the first two as distinct items. LVM
58
  logical volumes have their unique ID (the pair volume group, logical
59
  volume name) in a 1-to-1 relation to the dev path. For DRBD devices,
60
  the /dev path is again dynamic and the unique id is the pair (host1,
61
  dev1), (host2, dev2).
62

  
63
  You can get to a device in two ways:
64
    - creating the (real) device, which returns you
65
      an attached instance (lvcreate)
66
    - attaching of a python instance to an existing (real) device
67

  
68
  The second point, the attachment to a device, is different
69
  depending on whether the device is assembled or not. At init() time,
70
  we search for a device with the same unique_id as us. If found,
71
  good. It also means that the device is already assembled. If not,
72
  after assembly we'll have our correct major/minor.
73

  
74
  """
75
  def __init__(self, unique_id, children, size, params):
76
    self._children = children
77
    self.dev_path = None
78
    self.unique_id = unique_id
79
    self.major = None
80
    self.minor = None
81
    self.attached = False
82
    self.size = size
83
    self.params = params
84

  
85
  def Assemble(self):
86
    """Assemble the device from its components.
87

  
88
    Implementations of this method by child classes must ensure that:
89
      - after the device has been assembled, it knows its major/minor
90
        numbers; this allows other devices (usually parents) to probe
91
        correctly for their children
92
      - calling this method on an existing, in-use device is safe
93
      - if the device is already configured (and in an OK state),
94
        this method is idempotent
95

  
96
    """
97
    pass
98

  
99
  def Attach(self):
100
    """Find a device which matches our config and attach to it.
101

  
102
    """
103
    raise NotImplementedError
104

  
105
  def Close(self):
106
    """Notifies that the device will no longer be used for I/O.
107

  
108
    """
109
    raise NotImplementedError
110

  
111
  @classmethod
112
  def Create(cls, unique_id, children, size, params, excl_stor):
113
    """Create the device.
114

  
115
    If the device cannot be created, it will return None
116
    instead. Error messages go to the logging system.
117

  
118
    Note that for some devices, the unique_id is used, and for other,
119
    the children. The idea is that these two, taken together, are
120
    enough for both creation and assembly (later).
121

  
122
    """
123
    raise NotImplementedError
124

  
125
  def Remove(self):
126
    """Remove this device.
127

  
128
    This makes sense only for some of the device types: LV and file
129
    storage. Also note that if the device can't attach, the removal
130
    can't be completed.
131

  
132
    """
133
    raise NotImplementedError
134

  
135
  def Rename(self, new_id):
136
    """Rename this device.
137

  
138
    This may or may not make sense for a given device type.
139

  
140
    """
141
    raise NotImplementedError
142

  
143
  def Open(self, force=False):
144
    """Make the device ready for use.
145

  
146
    This makes the device ready for I/O. For now, just the DRBD
147
    devices need this.
148

  
149
    The force parameter signifies that if the device has any kind of
150
    --force thing, it should be used, we know what we are doing.
151

  
152
    @type force: boolean
153

  
154
    """
155
    raise NotImplementedError
156

  
157
  def Shutdown(self):
158
    """Shut down the device, freeing its children.
159

  
160
    This undoes the `Assemble()` work, except for the child
161
    assembling; as such, the children on the device are still
162
    assembled after this call.
163

  
164
    """
165
    raise NotImplementedError
166

  
167
  def SetSyncParams(self, params):
168
    """Adjust the synchronization parameters of the mirror.
169

  
170
    In case this is not a mirroring device, this is no-op.
171

  
172
    @param params: dictionary of LD level disk parameters related to the
173
    synchronization.
174
    @rtype: list
175
    @return: a list of error messages, emitted both by the current node and by
176
    children. An empty list means no errors.
177

  
178
    """
179
    result = []
180
    if self._children:
181
      for child in self._children:
182
        result.extend(child.SetSyncParams(params))
183
    return result
184

  
185
  def PauseResumeSync(self, pause):
186
    """Pause/Resume the sync of the mirror.
187

  
188
    In case this is not a mirroring device, this is no-op.
189

  
190
    @type pause: boolean
191
    @param pause: Whether to pause or resume
192

  
193
    """
194
    result = True
195
    if self._children:
196
      for child in self._children:
197
        result = result and child.PauseResumeSync(pause)
198
    return result
199

  
200
  def GetSyncStatus(self):
201
    """Returns the sync status of the device.
202

  
203
    If this device is a mirroring device, this function returns the
204
    status of the mirror.
205

  
206
    If sync_percent is None, it means the device is not syncing.
207

  
208
    If estimated_time is None, it means we can't estimate
209
    the time needed, otherwise it's the time left in seconds.
210

  
211
    If is_degraded is True, it means the device is missing
212
    redundancy. This is usually a sign that something went wrong in
213
    the device setup, if sync_percent is None.
214

  
215
    The ldisk parameter represents the degradation of the local
216
    data. This is only valid for some devices, the rest will always
217
    return False (not degraded).
218

  
219
    @rtype: objects.BlockDevStatus
220

  
221
    """
222
    return objects.BlockDevStatus(dev_path=self.dev_path,
223
                                  major=self.major,
224
                                  minor=self.minor,
225
                                  sync_percent=None,
226
                                  estimated_time=None,
227
                                  is_degraded=False,
228
                                  ldisk_status=constants.LDS_OKAY)
229

  
230
  def CombinedSyncStatus(self):
231
    """Calculate the mirror status recursively for our children.
232

  
233
    The return value is the same as for `GetSyncStatus()` except the
234
    minimum percent and maximum time are calculated across our
235
    children.
236

  
237
    @rtype: objects.BlockDevStatus
238

  
239
    """
240
    status = self.GetSyncStatus()
241

  
242
    min_percent = status.sync_percent
243
    max_time = status.estimated_time
244
    is_degraded = status.is_degraded
245
    ldisk_status = status.ldisk_status
246

  
247
    if self._children:
248
      for child in self._children:
249
        child_status = child.GetSyncStatus()
250

  
251
        if min_percent is None:
252
          min_percent = child_status.sync_percent
253
        elif child_status.sync_percent is not None:
254
          min_percent = min(min_percent, child_status.sync_percent)
255

  
256
        if max_time is None:
257
          max_time = child_status.estimated_time
258
        elif child_status.estimated_time is not None:
259
          max_time = max(max_time, child_status.estimated_time)
260

  
261
        is_degraded = is_degraded or child_status.is_degraded
262

  
263
        if ldisk_status is None:
264
          ldisk_status = child_status.ldisk_status
265
        elif child_status.ldisk_status is not None:
266
          ldisk_status = max(ldisk_status, child_status.ldisk_status)
267

  
268
    return objects.BlockDevStatus(dev_path=self.dev_path,
269
                                  major=self.major,
270
                                  minor=self.minor,
271
                                  sync_percent=min_percent,
272
                                  estimated_time=max_time,
273
                                  is_degraded=is_degraded,
274
                                  ldisk_status=ldisk_status)
275

  
276
  def SetInfo(self, text):
277
    """Update metadata with info text.
278

  
279
    Only supported for some device types.
280

  
281
    """
282
    for child in self._children:
283
      child.SetInfo(text)
284

  
285
  def Grow(self, amount, dryrun, backingstore):
286
    """Grow the block device.
287

  
288
    @type amount: integer
289
    @param amount: the amount (in mebibytes) to grow with
290
    @type dryrun: boolean
291
    @param dryrun: whether to execute the operation in simulation mode
292
        only, without actually increasing the size
293
    @param backingstore: whether to execute the operation on backing storage
294
        only, or on "logical" storage only; e.g. DRBD is logical storage,
295
        whereas LVM, file, RBD are backing storage
296

  
297
    """
298
    raise NotImplementedError
299

  
300
  def GetActualSize(self):
301
    """Return the actual disk size.
302

  
303
    @note: the device needs to be active when this is called
304

  
305
    """
306
    assert self.attached, "BlockDevice not attached in GetActualSize()"
307
    result = utils.RunCmd(["blockdev", "--getsize64", self.dev_path])
308
    if result.failed:
309
      ThrowError("blockdev failed (%s): %s",
310
                  result.fail_reason, result.output)
311
    try:
312
      sz = int(result.output.strip())
313
    except (ValueError, TypeError), err:
314
      ThrowError("Failed to parse blockdev output: %s", str(err))
315
    return sz
316

  
317
  def __repr__(self):
318
    return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
319
            (self.__class__, self.unique_id, self._children,
320
             self.major, self.minor, self.dev_path))
321

  
322

  
323
def ThrowError(msg, *args):
324
  """Log an error to the node daemon and the raise an exception.
325

  
326
  @type msg: string
327
  @param msg: the text of the exception
328
  @raise errors.BlockDeviceError
329

  
330
  """
331
  if args:
332
    msg = msg % args
333
  logging.error(msg)
334
  raise errors.BlockDeviceError(msg)
335

  
336

  
337
def IgnoreError(fn, *args, **kwargs):
338
  """Executes the given function, ignoring BlockDeviceErrors.
339

  
340
  This is used in order to simplify the execution of cleanup or
341
  rollback functions.
342

  
343
  @rtype: boolean
344
  @return: True when fn didn't raise an exception, False otherwise
345

  
346
  """
347
  try:
348
    fn(*args, **kwargs)
349
    return True
350
  except errors.BlockDeviceError, err:
351
    logging.warning("Caught BlockDeviceError but ignoring: %s", str(err))
352
    return False
/dev/null
1
#
2
#
3

  
4
# Copyright (C) 2006, 2007, 2010, 2011, 2012, 2013 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
"""Block device abstraction"""
23

  
24
import re
25
import errno
26
import stat
27
import os
28
import logging
29
import math
30

  
31
from ganeti import utils
32
from ganeti import errors
33
from ganeti import constants
34
from ganeti import objects
35
from ganeti import compat
36
from ganeti import pathutils
37
from ganeti import serializer
38
from ganeti.block import drbd
39
from ganeti.block import base
40

  
41

  
42
class RbdShowmappedJsonError(Exception):
43
  """`rbd showmmapped' JSON formatting error Exception class.
44

  
45
  """
46
  pass
47

  
48

  
49
def _CheckResult(result):
50
  """Throws an error if the given result is a failed one.
51

  
52
  @param result: result from RunCmd
53

  
54
  """
55
  if result.failed:
56
    base.ThrowError("Command: %s error: %s - %s",
57
                    result.cmd, result.fail_reason, result.output)
58

  
59

  
60
def _GetForbiddenFileStoragePaths():
61
  """Builds a list of path prefixes which shouldn't be used for file storage.
62

  
63
  @rtype: frozenset
64

  
65
  """
66
  paths = set([
67
    "/boot",
68
    "/dev",
69
    "/etc",
70
    "/home",
71
    "/proc",
72
    "/root",
73
    "/sys",
74
    ])
75

  
76
  for prefix in ["", "/usr", "/usr/local"]:
77
    paths.update(map(lambda s: "%s/%s" % (prefix, s),
78
                     ["bin", "lib", "lib32", "lib64", "sbin"]))
79

  
80
  return compat.UniqueFrozenset(map(os.path.normpath, paths))
81

  
82

  
83
def _ComputeWrongFileStoragePaths(paths,
84
                                  _forbidden=_GetForbiddenFileStoragePaths()):
85
  """Cross-checks a list of paths for prefixes considered bad.
86

  
87
  Some paths, e.g. "/bin", should not be used for file storage.
88

  
89
  @type paths: list
90
  @param paths: List of paths to be checked
91
  @rtype: list
92
  @return: Sorted list of paths for which the user should be warned
93

  
94
  """
95
  def _Check(path):
96
    return (not os.path.isabs(path) or
97
            path in _forbidden or
98
            filter(lambda p: utils.IsBelowDir(p, path), _forbidden))
99

  
100
  return utils.NiceSort(filter(_Check, map(os.path.normpath, paths)))
101

  
102

  
103
def ComputeWrongFileStoragePaths(_filename=pathutils.FILE_STORAGE_PATHS_FILE):
104
  """Returns a list of file storage paths whose prefix is considered bad.
105

  
106
  See L{_ComputeWrongFileStoragePaths}.
107

  
108
  """
109
  return _ComputeWrongFileStoragePaths(_LoadAllowedFileStoragePaths(_filename))
110

  
111

  
112
def _CheckFileStoragePath(path, allowed):
113
  """Checks if a path is in a list of allowed paths for file storage.
114

  
115
  @type path: string
116
  @param path: Path to check
117
  @type allowed: list
118
  @param allowed: List of allowed paths
119
  @raise errors.FileStoragePathError: If the path is not allowed
120

  
121
  """
122
  if not os.path.isabs(path):
123
    raise errors.FileStoragePathError("File storage path must be absolute,"
124
                                      " got '%s'" % path)
125

  
126
  for i in allowed:
127
    if not os.path.isabs(i):
128
      logging.info("Ignoring relative path '%s' for file storage", i)
129
      continue
130

  
131
    if utils.IsBelowDir(i, path):
132
      break
133
  else:
134
    raise errors.FileStoragePathError("Path '%s' is not acceptable for file"
135
                                      " storage" % path)
136

  
137

  
138
def _LoadAllowedFileStoragePaths(filename):
139
  """Loads file containing allowed file storage paths.
140

  
141
  @rtype: list
142
  @return: List of allowed paths (can be an empty list)
143

  
144
  """
145
  try:
146
    contents = utils.ReadFile(filename)
147
  except EnvironmentError:
148
    return []
149
  else:
150
    return utils.FilterEmptyLinesAndComments(contents)
151

  
152

  
153
def CheckFileStoragePath(path, _filename=pathutils.FILE_STORAGE_PATHS_FILE):
154
  """Checks if a path is allowed for file storage.
155

  
156
  @type path: string
157
  @param path: Path to check
158
  @raise errors.FileStoragePathError: If the path is not allowed
159

  
160
  """
161
  allowed = _LoadAllowedFileStoragePaths(_filename)
162

  
163
  if _ComputeWrongFileStoragePaths([path]):
164
    raise errors.FileStoragePathError("Path '%s' uses a forbidden prefix" %
165
                                      path)
166

  
167
  _CheckFileStoragePath(path, allowed)
168

  
169

  
170
class LogicalVolume(base.BlockDev):
171
  """Logical Volume block device.
172

  
173
  """
174
  _VALID_NAME_RE = re.compile("^[a-zA-Z0-9+_.-]*$")
175
  _INVALID_NAMES = compat.UniqueFrozenset([".", "..", "snapshot", "pvmove"])
176
  _INVALID_SUBSTRINGS = compat.UniqueFrozenset(["_mlog", "_mimage"])
177

  
178
  def __init__(self, unique_id, children, size, params):
179
    """Attaches to a LV device.
180

  
181
    The unique_id is a tuple (vg_name, lv_name)
182

  
183
    """
184
    super(LogicalVolume, self).__init__(unique_id, children, size, params)
185
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
186
      raise ValueError("Invalid configuration data %s" % str(unique_id))
187
    self._vg_name, self._lv_name = unique_id
188
    self._ValidateName(self._vg_name)
189
    self._ValidateName(self._lv_name)
190
    self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
191
    self._degraded = True
192
    self.major = self.minor = self.pe_size = self.stripe_count = None
193
    self.Attach()
194

  
195
  @staticmethod
196
  def _GetStdPvSize(pvs_info):
197
    """Return the the standard PV size (used with exclusive storage).
198

  
199
    @param pvs_info: list of objects.LvmPvInfo, cannot be empty
200
    @rtype: float
201
    @return: size in MiB
202

  
203
    """
204
    assert len(pvs_info) > 0
205
    smallest = min([pv.size for pv in pvs_info])
206
    return smallest / (1 + constants.PART_MARGIN + constants.PART_RESERVED)
207

  
208
  @staticmethod
209
  def _ComputeNumPvs(size, pvs_info):
210
    """Compute the number of PVs needed for an LV (with exclusive storage).
211

  
212
    @type size: float
213
    @param size: LV size in MiB
214
    @param pvs_info: list of objects.LvmPvInfo, cannot be empty
215
    @rtype: integer
216
    @return: number of PVs needed
217
    """
218
    assert len(pvs_info) > 0
219
    pv_size = float(LogicalVolume._GetStdPvSize(pvs_info))
220
    return int(math.ceil(float(size) / pv_size))
221

  
222
  @staticmethod
223
  def _GetEmptyPvNames(pvs_info, max_pvs=None):
224
    """Return a list of empty PVs, by name.
225

  
226
    """
227
    empty_pvs = filter(objects.LvmPvInfo.IsEmpty, pvs_info)
228
    if max_pvs is not None:
229
      empty_pvs = empty_pvs[:max_pvs]
230
    return map((lambda pv: pv.name), empty_pvs)
231

  
232
  @classmethod
233
  def Create(cls, unique_id, children, size, params, excl_stor):
234
    """Create a new logical volume.
235

  
236
    """
237
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
238
      raise errors.ProgrammerError("Invalid configuration data %s" %
239
                                   str(unique_id))
240
    vg_name, lv_name = unique_id
241
    cls._ValidateName(vg_name)
242
    cls._ValidateName(lv_name)
243
    pvs_info = cls.GetPVInfo([vg_name])
244
    if not pvs_info:
245
      if excl_stor:
246
        msg = "No (empty) PVs found"
247
      else:
248
        msg = "Can't compute PV info for vg %s" % vg_name
249
      base.ThrowError(msg)
250
    pvs_info.sort(key=(lambda pv: pv.free), reverse=True)
251

  
252
    pvlist = [pv.name for pv in pvs_info]
253
    if compat.any(":" in v for v in pvlist):
254
      base.ThrowError("Some of your PVs have the invalid character ':' in their"
255
                      " name, this is not supported - please filter them out"
256
                      " in lvm.conf using either 'filter' or 'preferred_names'")
257

  
258
    current_pvs = len(pvlist)
259
    desired_stripes = params[constants.LDP_STRIPES]
260
    stripes = min(current_pvs, desired_stripes)
261

  
262
    if excl_stor:
263
      (err_msgs, _) = utils.LvmExclusiveCheckNodePvs(pvs_info)
264
      if err_msgs:
265
        for m in err_msgs:
266
          logging.warning(m)
267
      req_pvs = cls._ComputeNumPvs(size, pvs_info)
268
      pvlist = cls._GetEmptyPvNames(pvs_info, req_pvs)
269
      current_pvs = len(pvlist)
270
      if current_pvs < req_pvs:
271
        base.ThrowError("Not enough empty PVs to create a disk of %d MB:"
272
                        " %d available, %d needed", size, current_pvs, req_pvs)
273
      assert current_pvs == len(pvlist)
274
      if stripes > current_pvs:
275
        # No warning issued for this, as it's no surprise
276
        stripes = current_pvs
277

  
278
    else:
279
      if stripes < desired_stripes:
280
        logging.warning("Could not use %d stripes for VG %s, as only %d PVs are"
281
                        " available.", desired_stripes, vg_name, current_pvs)
282
      free_size = sum([pv.free for pv in pvs_info])
283
      # The size constraint should have been checked from the master before
284
      # calling the create function.
285
      if free_size < size:
286
        base.ThrowError("Not enough free space: required %s,"
287
                        " available %s", size, free_size)
288

  
289
    # If the free space is not well distributed, we won't be able to
290
    # create an optimally-striped volume; in that case, we want to try
291
    # with N, N-1, ..., 2, and finally 1 (non-stripped) number of
292
    # stripes
293
    cmd = ["lvcreate", "-L%dm" % size, "-n%s" % lv_name]
294
    for stripes_arg in range(stripes, 0, -1):
295
      result = utils.RunCmd(cmd + ["-i%d" % stripes_arg] + [vg_name] + pvlist)
296
      if not result.failed:
297
        break
298
    if result.failed:
299
      base.ThrowError("LV create failed (%s): %s",
300
                      result.fail_reason, result.output)
301
    return LogicalVolume(unique_id, children, size, params)
302

  
303
  @staticmethod
304
  def _GetVolumeInfo(lvm_cmd, fields):
305
    """Returns LVM Volume infos using lvm_cmd
306

  
307
    @param lvm_cmd: Should be one of "pvs", "vgs" or "lvs"
308
    @param fields: Fields to return
309
    @return: A list of dicts each with the parsed fields
310

  
311
    """
312
    if not fields:
313
      raise errors.ProgrammerError("No fields specified")
314

  
315
    sep = "|"
316
    cmd = [lvm_cmd, "--noheadings", "--nosuffix", "--units=m", "--unbuffered",
317
           "--separator=%s" % sep, "-o%s" % ",".join(fields)]
318

  
319
    result = utils.RunCmd(cmd)
320
    if result.failed:
321
      raise errors.CommandError("Can't get the volume information: %s - %s" %
322
                                (result.fail_reason, result.output))
323

  
324
    data = []
325
    for line in result.stdout.splitlines():
326
      splitted_fields = line.strip().split(sep)
327

  
328
      if len(fields) != len(splitted_fields):
329
        raise errors.CommandError("Can't parse %s output: line '%s'" %
330
                                  (lvm_cmd, line))
331

  
332
      data.append(splitted_fields)
333

  
334
    return data
335

  
336
  @classmethod
337
  def GetPVInfo(cls, vg_names, filter_allocatable=True, include_lvs=False):
338
    """Get the free space info for PVs in a volume group.
339

  
340
    @param vg_names: list of volume group names, if empty all will be returned
341
    @param filter_allocatable: whether to skip over unallocatable PVs
342
    @param include_lvs: whether to include a list of LVs hosted on each PV
343

  
344
    @rtype: list
345
    @return: list of objects.LvmPvInfo objects
346

  
347
    """
348
    # We request "lv_name" field only if we care about LVs, so we don't get
349
    # a long list of entries with many duplicates unless we really have to.
350
    # The duplicate "pv_name" field will be ignored.
351
    if include_lvs:
352
      lvfield = "lv_name"
353
    else:
354
      lvfield = "pv_name"
355
    try:
356
      info = cls._GetVolumeInfo("pvs", ["pv_name", "vg_name", "pv_free",
357
                                        "pv_attr", "pv_size", lvfield])
358
    except errors.GenericError, err:
359
      logging.error("Can't get PV information: %s", err)
360
      return None
361

  
362
    # When asked for LVs, "pvs" may return multiple entries for the same PV-LV
363
    # pair. We sort entries by PV name and then LV name, so it's easy to weed
364
    # out duplicates.
365
    if include_lvs:
366
      info.sort(key=(lambda i: (i[0], i[5])))
367
    data = []
368
    lastpvi = None
369
    for (pv_name, vg_name, pv_free, pv_attr, pv_size, lv_name) in info:
370
      # (possibly) skip over pvs which are not allocatable
371
      if filter_allocatable and pv_attr[0] != "a":
372
        continue
373
      # (possibly) skip over pvs which are not in the right volume group(s)
374
      if vg_names and vg_name not in vg_names:
375
        continue
376
      # Beware of duplicates (check before inserting)
377
      if lastpvi and lastpvi.name == pv_name:
378
        if include_lvs and lv_name:
379
          if not lastpvi.lv_list or lastpvi.lv_list[-1] != lv_name:
380
            lastpvi.lv_list.append(lv_name)
381
      else:
382
        if include_lvs and lv_name:
383
          lvl = [lv_name]
384
        else:
385
          lvl = []
386
        lastpvi = objects.LvmPvInfo(name=pv_name, vg_name=vg_name,
387
                                    size=float(pv_size), free=float(pv_free),
388
                                    attributes=pv_attr, lv_list=lvl)
389
        data.append(lastpvi)
390

  
391
    return data
392

  
393
  @classmethod
394
  def _GetExclusiveStorageVgFree(cls, vg_name):
395
    """Return the free disk space in the given VG, in exclusive storage mode.
396

  
397
    @type vg_name: string
398
    @param vg_name: VG name
399
    @rtype: float
400
    @return: free space in MiB
401
    """
402
    pvs_info = cls.GetPVInfo([vg_name])
403
    if not pvs_info:
404
      return 0.0
405
    pv_size = cls._GetStdPvSize(pvs_info)
406
    num_pvs = len(cls._GetEmptyPvNames(pvs_info))
407
    return pv_size * num_pvs
408

  
409
  @classmethod
410
  def GetVGInfo(cls, vg_names, excl_stor, filter_readonly=True):
411
    """Get the free space info for specific VGs.
412

  
413
    @param vg_names: list of volume group names, if empty all will be returned
414
    @param excl_stor: whether exclusive_storage is enabled
415
    @param filter_readonly: whether to skip over readonly VGs
416

  
417
    @rtype: list
418
    @return: list of tuples (free_space, total_size, name) with free_space in
419
             MiB
420

  
421
    """
422
    try:
423
      info = cls._GetVolumeInfo("vgs", ["vg_name", "vg_free", "vg_attr",
424
                                        "vg_size"])
425
    except errors.GenericError, err:
426
      logging.error("Can't get VG information: %s", err)
427
      return None
428

  
429
    data = []
430
    for vg_name, vg_free, vg_attr, vg_size in info:
431
      # (possibly) skip over vgs which are not writable
432
      if filter_readonly and vg_attr[0] == "r":
433
        continue
434
      # (possibly) skip over vgs which are not in the right volume group(s)
435
      if vg_names and vg_name not in vg_names:
436
        continue
437
      # Exclusive storage needs a different concept of free space
438
      if excl_stor:
439
        es_free = cls._GetExclusiveStorageVgFree(vg_name)
440
        assert es_free <= vg_free
441
        vg_free = es_free
442
      data.append((float(vg_free), float(vg_size), vg_name))
443

  
444
    return data
445

  
446
  @classmethod
447
  def _ValidateName(cls, name):
448
    """Validates that a given name is valid as VG or LV name.
449

  
450
    The list of valid characters and restricted names is taken out of
451
    the lvm(8) manpage, with the simplification that we enforce both
452
    VG and LV restrictions on the names.
453

  
454
    """
455
    if (not cls._VALID_NAME_RE.match(name) or
456
        name in cls._INVALID_NAMES or
457
        compat.any(substring in name for substring in cls._INVALID_SUBSTRINGS)):
458
      base.ThrowError("Invalid LVM name '%s'", name)
459

  
460
  def Remove(self):
461
    """Remove this logical volume.
462

  
463
    """
464
    if not self.minor and not self.Attach():
465
      # the LV does not exist
466
      return
467
    result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
468
                           (self._vg_name, self._lv_name)])
469
    if result.failed:
470
      base.ThrowError("Can't lvremove: %s - %s",
471
                      result.fail_reason, result.output)
472

  
473
  def Rename(self, new_id):
474
    """Rename this logical volume.
475

  
476
    """
477
    if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
478
      raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
479
    new_vg, new_name = new_id
480
    if new_vg != self._vg_name:
481
      raise errors.ProgrammerError("Can't move a logical volume across"
482
                                   " volume groups (from %s to to %s)" %
483
                                   (self._vg_name, new_vg))
484
    result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
485
    if result.failed:
486
      base.ThrowError("Failed to rename the logical volume: %s", result.output)
487
    self._lv_name = new_name
488
    self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
489

  
490
  def Attach(self):
491
    """Attach to an existing LV.
492

  
493
    This method will try to see if an existing and active LV exists
494
    which matches our name. If so, its major/minor will be
495
    recorded.
496

  
497
    """
498
    self.attached = False
499
    result = utils.RunCmd(["lvs", "--noheadings", "--separator=,",
500
                           "--units=k", "--nosuffix",
501
                           "-olv_attr,lv_kernel_major,lv_kernel_minor,"
502
                           "vg_extent_size,stripes", self.dev_path])
503
    if result.failed:
504
      logging.error("Can't find LV %s: %s, %s",
505
                    self.dev_path, result.fail_reason, result.output)
506
      return False
507
    # the output can (and will) have multiple lines for multi-segment
508
    # LVs, as the 'stripes' parameter is a segment one, so we take
509
    # only the last entry, which is the one we're interested in; note
510
    # that with LVM2 anyway the 'stripes' value must be constant
511
    # across segments, so this is a no-op actually
512
    out = result.stdout.splitlines()
513
    if not out: # totally empty result? splitlines() returns at least
514
                # one line for any non-empty string
515
      logging.error("Can't parse LVS output, no lines? Got '%s'", str(out))
516
      return False
517
    out = out[-1].strip().rstrip(",")
518
    out = out.split(",")
519
    if len(out) != 5:
520
      logging.error("Can't parse LVS output, len(%s) != 5", str(out))
521
      return False
522

  
523
    status, major, minor, pe_size, stripes = out
524
    if len(status) < 6:
525
      logging.error("lvs lv_attr is not at least 6 characters (%s)", status)
526
      return False
527

  
528
    try:
529
      major = int(major)
530
      minor = int(minor)
531
    except (TypeError, ValueError), err:
532
      logging.error("lvs major/minor cannot be parsed: %s", str(err))
533

  
534
    try:
535
      pe_size = int(float(pe_size))
536
    except (TypeError, ValueError), err:
537
      logging.error("Can't parse vg extent size: %s", err)
538
      return False
539

  
540
    try:
541
      stripes = int(stripes)
542
    except (TypeError, ValueError), err:
543
      logging.error("Can't parse the number of stripes: %s", err)
544
      return False
545

  
546
    self.major = major
547
    self.minor = minor
548
    self.pe_size = pe_size
549
    self.stripe_count = stripes
550
    self._degraded = status[0] == "v" # virtual volume, i.e. doesn't backing
551
                                      # storage
552
    self.attached = True
553
    return True
554

  
555
  def Assemble(self):
556
    """Assemble the device.
557

  
558
    We always run `lvchange -ay` on the LV to ensure it's active before
559
    use, as there were cases when xenvg was not active after boot
560
    (also possibly after disk issues).
561

  
562
    """
563
    result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
564
    if result.failed:
565
      base.ThrowError("Can't activate lv %s: %s", self.dev_path, result.output)
566

  
567
  def Shutdown(self):
568
    """Shutdown the device.
569

  
570
    This is a no-op for the LV device type, as we don't deactivate the
571
    volumes on shutdown.
572

  
573
    """
574
    pass
575

  
576
  def GetSyncStatus(self):
577
    """Returns the sync status of the device.
578

  
579
    If this device is a mirroring device, this function returns the
580
    status of the mirror.
581

  
582
    For logical volumes, sync_percent and estimated_time are always
583
    None (no recovery in progress, as we don't handle the mirrored LV
584
    case). The is_degraded parameter is the inverse of the ldisk
585
    parameter.
586

  
587
    For the ldisk parameter, we check if the logical volume has the
588
    'virtual' type, which means it's not backed by existing storage
589
    anymore (read from it return I/O error). This happens after a
590
    physical disk failure and subsequent 'vgreduce --removemissing' on
591
    the volume group.
592

  
593
    The status was already read in Attach, so we just return it.
594

  
595
    @rtype: objects.BlockDevStatus
596

  
597
    """
598
    if self._degraded:
599
      ldisk_status = constants.LDS_FAULTY
600
    else:
601
      ldisk_status = constants.LDS_OKAY
602

  
603
    return objects.BlockDevStatus(dev_path=self.dev_path,
604
                                  major=self.major,
605
                                  minor=self.minor,
606
                                  sync_percent=None,
607
                                  estimated_time=None,
608
                                  is_degraded=self._degraded,
609
                                  ldisk_status=ldisk_status)
610

  
611
  def Open(self, force=False):
612
    """Make the device ready for I/O.
613

  
614
    This is a no-op for the LV device type.
615

  
616
    """
617
    pass
618

  
619
  def Close(self):
620
    """Notifies that the device will no longer be used for I/O.
621

  
622
    This is a no-op for the LV device type.
623

  
624
    """
625
    pass
626

  
627
  def Snapshot(self, size):
628
    """Create a snapshot copy of an lvm block device.
629

  
630
    @returns: tuple (vg, lv)
631

  
632
    """
633
    snap_name = self._lv_name + ".snap"
634

  
635
    # remove existing snapshot if found
636
    snap = LogicalVolume((self._vg_name, snap_name), None, size, self.params)
637
    base.IgnoreError(snap.Remove)
638

  
639
    vg_info = self.GetVGInfo([self._vg_name], False)
640
    if not vg_info:
641
      base.ThrowError("Can't compute VG info for vg %s", self._vg_name)
642
    free_size, _, _ = vg_info[0]
643
    if free_size < size:
644
      base.ThrowError("Not enough free space: required %s,"
645
                      " available %s", size, free_size)
646

  
647
    _CheckResult(utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
648
                               "-n%s" % snap_name, self.dev_path]))
649

  
650
    return (self._vg_name, snap_name)
651

  
652
  def _RemoveOldInfo(self):
653
    """Try to remove old tags from the lv.
654

  
655
    """
656
    result = utils.RunCmd(["lvs", "-o", "tags", "--noheadings", "--nosuffix",
657
                           self.dev_path])
658
    _CheckResult(result)
659

  
660
    raw_tags = result.stdout.strip()
661
    if raw_tags:
662
      for tag in raw_tags.split(","):
663
        _CheckResult(utils.RunCmd(["lvchange", "--deltag",
664
                                   tag.strip(), self.dev_path]))
665

  
666
  def SetInfo(self, text):
667
    """Update metadata with info text.
668

  
669
    """
670
    base.BlockDev.SetInfo(self, text)
671

  
672
    self._RemoveOldInfo()
673

  
674
    # Replace invalid characters
675
    text = re.sub("^[^A-Za-z0-9_+.]", "_", text)
676
    text = re.sub("[^-A-Za-z0-9_+.]", "_", text)
677

  
678
    # Only up to 128 characters are allowed
679
    text = text[:128]
680

  
681
    _CheckResult(utils.RunCmd(["lvchange", "--addtag", text, self.dev_path]))
682

  
683
  def Grow(self, amount, dryrun, backingstore):
684
    """Grow the logical volume.
685

  
686
    """
687
    if not backingstore:
688
      return
689
    if self.pe_size is None or self.stripe_count is None:
690
      if not self.Attach():
691
        base.ThrowError("Can't attach to LV during Grow()")
692
    full_stripe_size = self.pe_size * self.stripe_count
693
    # pe_size is in KB
694
    amount *= 1024
695
    rest = amount % full_stripe_size
696
    if rest != 0:
697
      amount += full_stripe_size - rest
698
    cmd = ["lvextend", "-L", "+%dk" % amount]
699
    if dryrun:
700
      cmd.append("--test")
701
    # we try multiple algorithms since the 'best' ones might not have
702
    # space available in the right place, but later ones might (since
703
    # they have less constraints); also note that only recent LVM
704
    # supports 'cling'
705
    for alloc_policy in "contiguous", "cling", "normal":
706
      result = utils.RunCmd(cmd + ["--alloc", alloc_policy, self.dev_path])
707
      if not result.failed:
708
        return
709
    base.ThrowError("Can't grow LV %s: %s", self.dev_path, result.output)
710

  
711

  
712
class FileStorage(base.BlockDev):
713
  """File device.
714

  
715
  This class represents the a file storage backend device.
716

  
717
  The unique_id for the file device is a (file_driver, file_path) tuple.
718

  
719
  """
720
  def __init__(self, unique_id, children, size, params):
721
    """Initalizes a file device backend.
722

  
723
    """
724
    if children:
725
      raise errors.BlockDeviceError("Invalid setup for file device")
726
    super(FileStorage, self).__init__(unique_id, children, size, params)
727
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
728
      raise ValueError("Invalid configuration data %s" % str(unique_id))
729
    self.driver = unique_id[0]
730
    self.dev_path = unique_id[1]
731

  
732
    CheckFileStoragePath(self.dev_path)
733

  
734
    self.Attach()
735

  
736
  def Assemble(self):
737
    """Assemble the device.
738

  
739
    Checks whether the file device exists, raises BlockDeviceError otherwise.
740

  
741
    """
742
    if not os.path.exists(self.dev_path):
743
      base.ThrowError("File device '%s' does not exist" % self.dev_path)
744

  
745
  def Shutdown(self):
746
    """Shutdown the device.
747

  
748
    This is a no-op for the file type, as we don't deactivate
749
    the file on shutdown.
750

  
751
    """
752
    pass
753

  
754
  def Open(self, force=False):
755
    """Make the device ready for I/O.
756

  
757
    This is a no-op for the file type.
758

  
759
    """
760
    pass
761

  
762
  def Close(self):
763
    """Notifies that the device will no longer be used for I/O.
764

  
765
    This is a no-op for the file type.
766

  
767
    """
768
    pass
769

  
770
  def Remove(self):
771
    """Remove the file backing the block device.
772

  
773
    @rtype: boolean
774
    @return: True if the removal was successful
775

  
776
    """
777
    try:
778
      os.remove(self.dev_path)
779
    except OSError, err:
780
      if err.errno != errno.ENOENT:
781
        base.ThrowError("Can't remove file '%s': %s", self.dev_path, err)
782

  
783
  def Rename(self, new_id):
784
    """Renames the file.
785

  
786
    """
787
    # TODO: implement rename for file-based storage
788
    base.ThrowError("Rename is not supported for file-based storage")
789

  
790
  def Grow(self, amount, dryrun, backingstore):
791
    """Grow the file
792

  
793
    @param amount: the amount (in mebibytes) to grow with
794

  
795
    """
796
    if not backingstore:
797
      return
798
    # Check that the file exists
799
    self.Assemble()
800
    current_size = self.GetActualSize()
801
    new_size = current_size + amount * 1024 * 1024
802
    assert new_size > current_size, "Cannot Grow with a negative amount"
803
    # We can't really simulate the growth
804
    if dryrun:
805
      return
806
    try:
807
      f = open(self.dev_path, "a+")
808
      f.truncate(new_size)
809
      f.close()
810
    except EnvironmentError, err:
811
      base.ThrowError("Error in file growth: %", str(err))
812

  
813
  def Attach(self):
814
    """Attach to an existing file.
815

  
816
    Check if this file already exists.
817

  
818
    @rtype: boolean
819
    @return: True if file exists
820

  
821
    """
822
    self.attached = os.path.exists(self.dev_path)
823
    return self.attached
824

  
825
  def GetActualSize(self):
826
    """Return the actual disk size.
827

  
828
    @note: the device needs to be active when this is called
829

  
830
    """
831
    assert self.attached, "BlockDevice not attached in GetActualSize()"
832
    try:
833
      st = os.stat(self.dev_path)
834
      return st.st_size
835
    except OSError, err:
836
      base.ThrowError("Can't stat %s: %s", self.dev_path, err)
837

  
838
  @classmethod
839
  def Create(cls, unique_id, children, size, params, excl_stor):
840
    """Create a new file.
841

  
842
    @param size: the size of file in MiB
843

  
844
    @rtype: L{bdev.FileStorage}
845
    @return: an instance of FileStorage
846

  
847
    """
848
    if excl_stor:
849
      raise errors.ProgrammerError("FileStorage device requested with"
850
                                   " exclusive_storage")
851
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
852
      raise ValueError("Invalid configuration data %s" % str(unique_id))
853

  
854
    dev_path = unique_id[1]
855

  
856
    CheckFileStoragePath(dev_path)
857

  
858
    try:
859
      fd = os.open(dev_path, os.O_RDWR | os.O_CREAT | os.O_EXCL)
860
      f = os.fdopen(fd, "w")
861
      f.truncate(size * 1024 * 1024)
862
      f.close()
863
    except EnvironmentError, err:
864
      if err.errno == errno.EEXIST:
865
        base.ThrowError("File already existing: %s", dev_path)
866
      base.ThrowError("Error in file creation: %", str(err))
867

  
868
    return FileStorage(unique_id, children, size, params)
869

  
870

  
871
class PersistentBlockDevice(base.BlockDev):
872
  """A block device with persistent node
873

  
874
  May be either directly attached, or exposed through DM (e.g. dm-multipath).
875
  udev helpers are probably required to give persistent, human-friendly
876
  names.
877

  
878
  For the time being, pathnames are required to lie under /dev.
879

  
880
  """
881
  def __init__(self, unique_id, children, size, params):
882
    """Attaches to a static block device.
883

  
884
    The unique_id is a path under /dev.
885

  
886
    """
887
    super(PersistentBlockDevice, self).__init__(unique_id, children, size,
888
                                                params)
889
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
890
      raise ValueError("Invalid configuration data %s" % str(unique_id))
891
    self.dev_path = unique_id[1]
892
    if not os.path.realpath(self.dev_path).startswith("/dev/"):
893
      raise ValueError("Full path '%s' lies outside /dev" %
894
                              os.path.realpath(self.dev_path))
895
    # TODO: this is just a safety guard checking that we only deal with devices
896
    # we know how to handle. In the future this will be integrated with
897
    # external storage backends and possible values will probably be collected
898
    # from the cluster configuration.
899
    if unique_id[0] != constants.BLOCKDEV_DRIVER_MANUAL:
900
      raise ValueError("Got persistent block device of invalid type: %s" %
901
                       unique_id[0])
902

  
903
    self.major = self.minor = None
904
    self.Attach()
905

  
906
  @classmethod
907
  def Create(cls, unique_id, children, size, params, excl_stor):
908
    """Create a new device
909

  
910
    This is a noop, we only return a PersistentBlockDevice instance
911

  
912
    """
913
    if excl_stor:
914
      raise errors.ProgrammerError("Persistent block device requested with"
915
                                   " exclusive_storage")
916
    return PersistentBlockDevice(unique_id, children, 0, params)
917

  
918
  def Remove(self):
919
    """Remove a device
920

  
921
    This is a noop
922

  
923
    """
924
    pass
925

  
926
  def Rename(self, new_id):
927
    """Rename this device.
928

  
929
    """
930
    base.ThrowError("Rename is not supported for PersistentBlockDev storage")
931

  
932
  def Attach(self):
933
    """Attach to an existing block device.
934

  
935

  
936
    """
937
    self.attached = False
938
    try:
939
      st = os.stat(self.dev_path)
940
    except OSError, err:
941
      logging.error("Error stat()'ing %s: %s", self.dev_path, str(err))
942
      return False
943

  
944
    if not stat.S_ISBLK(st.st_mode):
945
      logging.error("%s is not a block device", self.dev_path)
946
      return False
947

  
948
    self.major = os.major(st.st_rdev)
949
    self.minor = os.minor(st.st_rdev)
950
    self.attached = True
951

  
952
    return True
953

  
954
  def Assemble(self):
955
    """Assemble the device.
956

  
957
    """
958
    pass
959

  
960
  def Shutdown(self):
961
    """Shutdown the device.
962

  
963
    """
964
    pass
965

  
966
  def Open(self, force=False):
967
    """Make the device ready for I/O.
968

  
969
    """
970
    pass
971

  
972
  def Close(self):
973
    """Notifies that the device will no longer be used for I/O.
974

  
975
    """
976
    pass
977

  
978
  def Grow(self, amount, dryrun, backingstore):
979
    """Grow the logical volume.
980

  
981
    """
982
    base.ThrowError("Grow is not supported for PersistentBlockDev storage")
983

  
984

  
985
class RADOSBlockDevice(base.BlockDev):
986
  """A RADOS Block Device (rbd).
987

  
988
  This class implements the RADOS Block Device for the backend. You need
989
  the rbd kernel driver, the RADOS Tools and a working RADOS cluster for
990
  this to be functional.
991

  
992
  """
993
  def __init__(self, unique_id, children, size, params):
994
    """Attaches to an rbd device.
995

  
996
    """
997
    super(RADOSBlockDevice, self).__init__(unique_id, children, size, params)
998
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
999
      raise ValueError("Invalid configuration data %s" % str(unique_id))
1000

  
1001
    self.driver, self.rbd_name = unique_id
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff