Revision 89ff748d

b/Makefile.am
317 317

  
318 318
block_PYTHON = \
319 319
	lib/block/__init__.py \
320
	lib/block/bdev.py
320
	lib/block/bdev.py \
321
	lib/block/base.py \
322
	lib/block/drbd.py
321 323

  
322 324
rapi_PYTHON = \
323 325
	lib/rapi/__init__.py \
b/lib/backend.py
55 55
from ganeti import hypervisor
56 56
from ganeti import constants
57 57
from ganeti.block import bdev
58
from ganeti.block import drbd
58 59
from ganeti import objects
59 60
from ganeti import ssconf
60 61
from ganeti import serializer
......
65 66
from ganeti import pathutils
66 67
from ganeti import vcluster
67 68
from ganeti import ht
69
from ganeti.block.base import BlockDev
68 70

  
69 71

  
70 72
_BOOT_ID_PATH = "/proc/sys/kernel/random/boot_id"
......
830 832

  
831 833
  if constants.NV_DRBDLIST in what and vm_capable:
832 834
    try:
833
      used_minors = bdev.DRBD8.GetUsedDevs().keys()
835
      used_minors = drbd.DRBD8.GetUsedDevs().keys()
834 836
    except errors.BlockDeviceError, err:
835 837
      logging.warning("Can't get used minors list", exc_info=True)
836 838
      used_minors = str(err)
......
839 841
  if constants.NV_DRBDHELPER in what and vm_capable:
840 842
    status = True
841 843
    try:
842
      payload = bdev.BaseDRBD.GetUsermodeHelper()
844
      payload = drbd.BaseDRBD.GetUsermodeHelper()
843 845
    except errors.BlockDeviceError, err:
844 846
      logging.error("Can't get DRBD usermode helper: %s", str(err))
845 847
      status = False
......
1872 1874
  """
1873 1875
  try:
1874 1876
    result = _RecursiveAssembleBD(disk, owner, as_primary)
1875
    if isinstance(result, bdev.BlockDev):
1877
    if isinstance(result, BlockDev):
1876 1878
      # pylint: disable=E1103
1877 1879
      result = result.dev_path
1878 1880
      if as_primary:
......
3669 3671

  
3670 3672
  """
3671 3673
  try:
3672
    return bdev.BaseDRBD.GetUsermodeHelper()
3674
    return drbd.BaseDRBD.GetUsermodeHelper()
3673 3675
  except errors.BlockDeviceError, err:
3674 3676
    _Fail(str(err))
3675 3677

  
b/lib/block/base.py
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
    """
153
    raise NotImplementedError
154

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

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

  
162
    """
163
    raise NotImplementedError
164

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

  
168
    In case this is not a mirroring device, this is no-op.
169

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

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

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

  
186
    In case this is not a mirroring device, this is no-op.
187

  
188
    @param pause: Whether to pause or resume
189

  
190
    """
191
    result = True
192
    if self._children:
193
      for child in self._children:
194
        result = result and child.PauseResumeSync(pause)
195
    return result
196

  
197
  def GetSyncStatus(self):
198
    """Returns the sync status of the device.
199

  
200
    If this device is a mirroring device, this function returns the
201
    status of the mirror.
202

  
203
    If sync_percent is None, it means the device is not syncing.
204

  
205
    If estimated_time is None, it means we can't estimate
206
    the time needed, otherwise it's the time left in seconds.
207

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

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

  
216
    @rtype: objects.BlockDevStatus
217

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

  
227
  def CombinedSyncStatus(self):
228
    """Calculate the mirror status recursively for our children.
229

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

  
234
    @rtype: objects.BlockDevStatus
235

  
236
    """
237
    status = self.GetSyncStatus()
238

  
239
    min_percent = status.sync_percent
240
    max_time = status.estimated_time
241
    is_degraded = status.is_degraded
242
    ldisk_status = status.ldisk_status
243

  
244
    if self._children:
245
      for child in self._children:
246
        child_status = child.GetSyncStatus()
247

  
248
        if min_percent is None:
249
          min_percent = child_status.sync_percent
250
        elif child_status.sync_percent is not None:
251
          min_percent = min(min_percent, child_status.sync_percent)
252

  
253
        if max_time is None:
254
          max_time = child_status.estimated_time
255
        elif child_status.estimated_time is not None:
256
          max_time = max(max_time, child_status.estimated_time)
257

  
258
        is_degraded = is_degraded or child_status.is_degraded
259

  
260
        if ldisk_status is None:
261
          ldisk_status = child_status.ldisk_status
262
        elif child_status.ldisk_status is not None:
263
          ldisk_status = max(ldisk_status, child_status.ldisk_status)
264

  
265
    return objects.BlockDevStatus(dev_path=self.dev_path,
266
                                  major=self.major,
267
                                  minor=self.minor,
268
                                  sync_percent=min_percent,
269
                                  estimated_time=max_time,
270
                                  is_degraded=is_degraded,
271
                                  ldisk_status=ldisk_status)
272

  
273
  def SetInfo(self, text):
274
    """Update metadata with info text.
275

  
276
    Only supported for some device types.
277

  
278
    """
279
    for child in self._children:
280
      child.SetInfo(text)
281

  
282
  def Grow(self, amount, dryrun, backingstore):
283
    """Grow the block device.
284

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

  
294
    """
295
    raise NotImplementedError
296

  
297
  def GetActualSize(self):
298
    """Return the actual disk size.
299

  
300
    @note: the device needs to be active when this is called
301

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

  
314
  def __repr__(self):
315
    return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
316
            (self.__class__, self.unique_id, self._children,
317
             self.major, self.minor, self.dev_path))
318

  
319

  
320
def ThrowError(msg, *args):
321
  """Log an error to the node daemon and the raise an exception.
322

  
323
  @type msg: string
324
  @param msg: the text of the exception
325
  @raise errors.BlockDeviceError
326

  
327
  """
328
  if args:
329
    msg = msg % args
330
  logging.error(msg)
331
  raise errors.BlockDeviceError(msg)
332

  
333

  
334
def IgnoreError(fn, *args, **kwargs):
335
  """Executes the given function, ignoring BlockDeviceErrors.
336

  
337
  This is used in order to simplify the execution of cleanup or
338
  rollback functions.
339

  
340
  @rtype: boolean
341
  @return: True when fn didn't raise an exception, False otherwise
342

  
343
  """
344
  try:
345
    fn(*args, **kwargs)
346
    return True
347
  except errors.BlockDeviceError, err:
348
    logging.warning("Caught BlockDeviceError but ignoring: %s", str(err))
349
    return False
b/lib/block/bdev.py
22 22
"""Block device abstraction"""
23 23

  
24 24
import re
25
import time
26 25
import errno
27
import shlex
28 26
import stat
29
import pyparsing as pyp
30 27
import os
31 28
import logging
32 29
import math
......
36 33
from ganeti import constants
37 34
from ganeti import objects
38 35
from ganeti import compat
39
from ganeti import netutils
40 36
from ganeti import pathutils
41 37
from ganeti import serializer
42

  
43

  
44
# Size of reads in _CanReadDevice
45
_DEVICE_READ_SIZE = 128 * 1024
38
from ganeti.block import drbd
39
from ganeti.block import base
46 40

  
47 41

  
48 42
class RbdShowmappedJsonError(Exception):
......
52 46
  pass
53 47

  
54 48

  
55
def _IgnoreError(fn, *args, **kwargs):
56
  """Executes the given function, ignoring BlockDeviceErrors.
57

  
58
  This is used in order to simplify the execution of cleanup or
59
  rollback functions.
60

  
61
  @rtype: boolean
62
  @return: True when fn didn't raise an exception, False otherwise
63

  
64
  """
65
  try:
66
    fn(*args, **kwargs)
67
    return True
68
  except errors.BlockDeviceError, err:
69
    logging.warning("Caught BlockDeviceError but ignoring: %s", str(err))
70
    return False
71

  
72

  
73
def _ThrowError(msg, *args):
74
  """Log an error to the node daemon and the raise an exception.
75

  
76
  @type msg: string
77
  @param msg: the text of the exception
78
  @raise errors.BlockDeviceError
79

  
80
  """
81
  if args:
82
    msg = msg % args
83
  logging.error(msg)
84
  raise errors.BlockDeviceError(msg)
85

  
86

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

  
......
91 53

  
92 54
  """
93 55
  if result.failed:
94
    _ThrowError("Command: %s error: %s - %s", result.cmd, result.fail_reason,
95
                result.output)
96

  
97

  
98
def _CanReadDevice(path):
99
  """Check if we can read from the given device.
100

  
101
  This tries to read the first 128k of the device.
102

  
103
  """
104
  try:
105
    utils.ReadFile(path, size=_DEVICE_READ_SIZE)
106
    return True
107
  except EnvironmentError:
108
    logging.warning("Can't read from device %s", path, exc_info=True)
109
    return False
56
    base.ThrowError("Command: %s error: %s - %s",
57
                    result.cmd, result.fail_reason, result.output)
110 58

  
111 59

  
112 60
def _GetForbiddenFileStoragePaths():
......
219 167
  _CheckFileStoragePath(path, allowed)
220 168

  
221 169

  
222
class BlockDev(object):
223
  """Block device abstract class.
224

  
225
  A block device can be in the following states:
226
    - not existing on the system, and by `Create()` it goes into:
227
    - existing but not setup/not active, and by `Assemble()` goes into:
228
    - active read-write and by `Open()` it goes into
229
    - online (=used, or ready for use)
230

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

  
237
  The many different states of the device are due to the fact that we
238
  need to cover many device types:
239
    - logical volumes are created, lvchange -a y $lv, and used
240
    - drbd devices are attached to a local disk/remote peer and made primary
241

  
242
  A block device is identified by three items:
243
    - the /dev path of the device (dynamic)
244
    - a unique ID of the device (static)
245
    - it's major/minor pair (dynamic)
246

  
247
  Not all devices implement both the first two as distinct items. LVM
248
  logical volumes have their unique ID (the pair volume group, logical
249
  volume name) in a 1-to-1 relation to the dev path. For DRBD devices,
250
  the /dev path is again dynamic and the unique id is the pair (host1,
251
  dev1), (host2, dev2).
252

  
253
  You can get to a device in two ways:
254
    - creating the (real) device, which returns you
255
      an attached instance (lvcreate)
256
    - attaching of a python instance to an existing (real) device
257

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

  
264
  """
265
  def __init__(self, unique_id, children, size, params):
266
    self._children = children
267
    self.dev_path = None
268
    self.unique_id = unique_id
269
    self.major = None
270
    self.minor = None
271
    self.attached = False
272
    self.size = size
273
    self.params = params
274

  
275
  def Assemble(self):
276
    """Assemble the device from its components.
277

  
278
    Implementations of this method by child classes must ensure that:
279
      - after the device has been assembled, it knows its major/minor
280
        numbers; this allows other devices (usually parents) to probe
281
        correctly for their children
282
      - calling this method on an existing, in-use device is safe
283
      - if the device is already configured (and in an OK state),
284
        this method is idempotent
285

  
286
    """
287
    pass
288

  
289
  def Attach(self):
290
    """Find a device which matches our config and attach to it.
291

  
292
    """
293
    raise NotImplementedError
294

  
295
  def Close(self):
296
    """Notifies that the device will no longer be used for I/O.
297

  
298
    """
299
    raise NotImplementedError
300

  
301
  @classmethod
302
  def Create(cls, unique_id, children, size, params, excl_stor):
303
    """Create the device.
304

  
305
    If the device cannot be created, it will return None
306
    instead. Error messages go to the logging system.
307

  
308
    Note that for some devices, the unique_id is used, and for other,
309
    the children. The idea is that these two, taken together, are
310
    enough for both creation and assembly (later).
311

  
312
    """
313
    raise NotImplementedError
314

  
315
  def Remove(self):
316
    """Remove this device.
317

  
318
    This makes sense only for some of the device types: LV and file
319
    storage. Also note that if the device can't attach, the removal
320
    can't be completed.
321

  
322
    """
323
    raise NotImplementedError
324

  
325
  def Rename(self, new_id):
326
    """Rename this device.
327

  
328
    This may or may not make sense for a given device type.
329

  
330
    """
331
    raise NotImplementedError
332

  
333
  def Open(self, force=False):
334
    """Make the device ready for use.
335

  
336
    This makes the device ready for I/O. For now, just the DRBD
337
    devices need this.
338

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

  
342
    """
343
    raise NotImplementedError
344

  
345
  def Shutdown(self):
346
    """Shut down the device, freeing its children.
347

  
348
    This undoes the `Assemble()` work, except for the child
349
    assembling; as such, the children on the device are still
350
    assembled after this call.
351

  
352
    """
353
    raise NotImplementedError
354

  
355
  def SetSyncParams(self, params):
356
    """Adjust the synchronization parameters of the mirror.
357

  
358
    In case this is not a mirroring device, this is no-op.
359

  
360
    @param params: dictionary of LD level disk parameters related to the
361
    synchronization.
362
    @rtype: list
363
    @return: a list of error messages, emitted both by the current node and by
364
    children. An empty list means no errors.
365

  
366
    """
367
    result = []
368
    if self._children:
369
      for child in self._children:
370
        result.extend(child.SetSyncParams(params))
371
    return result
372

  
373
  def PauseResumeSync(self, pause):
374
    """Pause/Resume the sync of the mirror.
375

  
376
    In case this is not a mirroring device, this is no-op.
377

  
378
    @param pause: Whether to pause or resume
379

  
380
    """
381
    result = True
382
    if self._children:
383
      for child in self._children:
384
        result = result and child.PauseResumeSync(pause)
385
    return result
386

  
387
  def GetSyncStatus(self):
388
    """Returns the sync status of the device.
389

  
390
    If this device is a mirroring device, this function returns the
391
    status of the mirror.
392

  
393
    If sync_percent is None, it means the device is not syncing.
394

  
395
    If estimated_time is None, it means we can't estimate
396
    the time needed, otherwise it's the time left in seconds.
397

  
398
    If is_degraded is True, it means the device is missing
399
    redundancy. This is usually a sign that something went wrong in
400
    the device setup, if sync_percent is None.
401

  
402
    The ldisk parameter represents the degradation of the local
403
    data. This is only valid for some devices, the rest will always
404
    return False (not degraded).
405

  
406
    @rtype: objects.BlockDevStatus
407

  
408
    """
409
    return objects.BlockDevStatus(dev_path=self.dev_path,
410
                                  major=self.major,
411
                                  minor=self.minor,
412
                                  sync_percent=None,
413
                                  estimated_time=None,
414
                                  is_degraded=False,
415
                                  ldisk_status=constants.LDS_OKAY)
416

  
417
  def CombinedSyncStatus(self):
418
    """Calculate the mirror status recursively for our children.
419

  
420
    The return value is the same as for `GetSyncStatus()` except the
421
    minimum percent and maximum time are calculated across our
422
    children.
423

  
424
    @rtype: objects.BlockDevStatus
425

  
426
    """
427
    status = self.GetSyncStatus()
428

  
429
    min_percent = status.sync_percent
430
    max_time = status.estimated_time
431
    is_degraded = status.is_degraded
432
    ldisk_status = status.ldisk_status
433

  
434
    if self._children:
435
      for child in self._children:
436
        child_status = child.GetSyncStatus()
437

  
438
        if min_percent is None:
439
          min_percent = child_status.sync_percent
440
        elif child_status.sync_percent is not None:
441
          min_percent = min(min_percent, child_status.sync_percent)
442

  
443
        if max_time is None:
444
          max_time = child_status.estimated_time
445
        elif child_status.estimated_time is not None:
446
          max_time = max(max_time, child_status.estimated_time)
447

  
448
        is_degraded = is_degraded or child_status.is_degraded
449

  
450
        if ldisk_status is None:
451
          ldisk_status = child_status.ldisk_status
452
        elif child_status.ldisk_status is not None:
453
          ldisk_status = max(ldisk_status, child_status.ldisk_status)
454

  
455
    return objects.BlockDevStatus(dev_path=self.dev_path,
456
                                  major=self.major,
457
                                  minor=self.minor,
458
                                  sync_percent=min_percent,
459
                                  estimated_time=max_time,
460
                                  is_degraded=is_degraded,
461
                                  ldisk_status=ldisk_status)
462

  
463
  def SetInfo(self, text):
464
    """Update metadata with info text.
465

  
466
    Only supported for some device types.
467

  
468
    """
469
    for child in self._children:
470
      child.SetInfo(text)
471

  
472
  def Grow(self, amount, dryrun, backingstore):
473
    """Grow the block device.
474

  
475
    @type amount: integer
476
    @param amount: the amount (in mebibytes) to grow with
477
    @type dryrun: boolean
478
    @param dryrun: whether to execute the operation in simulation mode
479
        only, without actually increasing the size
480
    @param backingstore: whether to execute the operation on backing storage
481
        only, or on "logical" storage only; e.g. DRBD is logical storage,
482
        whereas LVM, file, RBD are backing storage
483

  
484
    """
485
    raise NotImplementedError
486

  
487
  def GetActualSize(self):
488
    """Return the actual disk size.
489

  
490
    @note: the device needs to be active when this is called
491

  
492
    """
493
    assert self.attached, "BlockDevice not attached in GetActualSize()"
494
    result = utils.RunCmd(["blockdev", "--getsize64", self.dev_path])
495
    if result.failed:
496
      _ThrowError("blockdev failed (%s): %s",
497
                  result.fail_reason, result.output)
498
    try:
499
      sz = int(result.output.strip())
500
    except (ValueError, TypeError), err:
501
      _ThrowError("Failed to parse blockdev output: %s", str(err))
502
    return sz
503

  
504
  def __repr__(self):
505
    return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
506
            (self.__class__, self.unique_id, self._children,
507
             self.major, self.minor, self.dev_path))
508

  
509

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

  
513 173
  """
......
586 246
        msg = "No (empty) PVs found"
587 247
      else:
588 248
        msg = "Can't compute PV info for vg %s" % vg_name
589
      _ThrowError(msg)
249
      base.ThrowError(msg)
590 250
    pvs_info.sort(key=(lambda pv: pv.free), reverse=True)
591 251

  
592 252
    pvlist = [pv.name for pv in pvs_info]
593 253
    if compat.any(":" in v for v in pvlist):
594
      _ThrowError("Some of your PVs have the invalid character ':' in their"
595
                  " name, this is not supported - please filter them out"
596
                  " in lvm.conf using either 'filter' or 'preferred_names'")
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'")
597 257

  
598 258
    current_pvs = len(pvlist)
599 259
    desired_stripes = params[constants.LDP_STRIPES]
......
608 268
      pvlist = cls._GetEmptyPvNames(pvs_info, req_pvs)
609 269
      current_pvs = len(pvlist)
610 270
      if current_pvs < req_pvs:
611
        _ThrowError("Not enough empty PVs to create a disk of %d MB:"
612
                    " %d available, %d needed", size, 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)
613 273
      assert current_pvs == len(pvlist)
614 274
      if stripes > current_pvs:
615 275
        # No warning issued for this, as it's no surprise
......
623 283
      # The size constraint should have been checked from the master before
624 284
      # calling the create function.
625 285
      if free_size < size:
626
        _ThrowError("Not enough free space: required %s,"
627
                    " available %s", size, free_size)
286
        base.ThrowError("Not enough free space: required %s,"
287
                        " available %s", size, free_size)
628 288

  
629 289
    # If the free space is not well distributed, we won't be able to
630 290
    # create an optimally-striped volume; in that case, we want to try
......
636 296
      if not result.failed:
637 297
        break
638 298
    if result.failed:
639
      _ThrowError("LV create failed (%s): %s",
640
                  result.fail_reason, result.output)
299
      base.ThrowError("LV create failed (%s): %s",
300
                      result.fail_reason, result.output)
641 301
    return LogicalVolume(unique_id, children, size, params)
642 302

  
643 303
  @staticmethod
......
795 455
    if (not cls._VALID_NAME_RE.match(name) or
796 456
        name in cls._INVALID_NAMES or
797 457
        compat.any(substring in name for substring in cls._INVALID_SUBSTRINGS)):
798
      _ThrowError("Invalid LVM name '%s'", name)
458
      base.ThrowError("Invalid LVM name '%s'", name)
799 459

  
800 460
  def Remove(self):
801 461
    """Remove this logical volume.
......
807 467
    result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
808 468
                           (self._vg_name, self._lv_name)])
809 469
    if result.failed:
810
      _ThrowError("Can't lvremove: %s - %s", result.fail_reason, result.output)
470
      base.ThrowError("Can't lvremove: %s - %s",
471
                      result.fail_reason, result.output)
811 472

  
812 473
  def Rename(self, new_id):
813 474
    """Rename this logical volume.
......
822 483
                                   (self._vg_name, new_vg))
823 484
    result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
824 485
    if result.failed:
825
      _ThrowError("Failed to rename the logical volume: %s", result.output)
486
      base.ThrowError("Failed to rename the logical volume: %s", result.output)
826 487
    self._lv_name = new_name
827 488
    self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
828 489

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

  
906 567
  def Shutdown(self):
907 568
    """Shutdown the device.
......
973 634

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

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

  
986 647
    _CheckResult(utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
987 648
                               "-n%s" % snap_name, self.dev_path]))
......
1006 667
    """Update metadata with info text.
1007 668

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

  
1011 672
    self._RemoveOldInfo()
1012 673

  
......
1027 688
      return
1028 689
    if self.pe_size is None or self.stripe_count is None:
1029 690
      if not self.Attach():
1030
        _ThrowError("Can't attach to LV during Grow()")
691
        base.ThrowError("Can't attach to LV during Grow()")
1031 692
    full_stripe_size = self.pe_size * self.stripe_count
1032 693
    rest = amount % full_stripe_size
1033 694
    if rest != 0:
......
1043 704
      result = utils.RunCmd(cmd + ["--alloc", alloc_policy, self.dev_path])
1044 705
      if not result.failed:
1045 706
        return
1046
    _ThrowError("Can't grow LV %s: %s", self.dev_path, result.output)
1047

  
1048

  
1049
class DRBD8Status(object):
1050
  """A DRBD status representation class.
1051

  
1052
  Note that this doesn't support unconfigured devices (cs:Unconfigured).
1053

  
1054
  """
1055
  UNCONF_RE = re.compile(r"\s*[0-9]+:\s*cs:Unconfigured$")
1056
  LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+(?:st|ro):([^/]+)/(\S+)"
1057
                       "\s+ds:([^/]+)/(\S+)\s+.*$")
1058
  SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*"
1059
                       # Due to a bug in drbd in the kernel, introduced in
1060
                       # commit 4b0715f096 (still unfixed as of 2011-08-22)
1061
                       "(?:\s|M)"
1062
                       "finish: ([0-9]+):([0-9]+):([0-9]+)\s.*$")
1063

  
1064
  CS_UNCONFIGURED = "Unconfigured"
1065
  CS_STANDALONE = "StandAlone"
1066
  CS_WFCONNECTION = "WFConnection"
1067
  CS_WFREPORTPARAMS = "WFReportParams"
1068
  CS_CONNECTED = "Connected"
1069
  CS_STARTINGSYNCS = "StartingSyncS"
1070
  CS_STARTINGSYNCT = "StartingSyncT"
1071
  CS_WFBITMAPS = "WFBitMapS"
1072
  CS_WFBITMAPT = "WFBitMapT"
1073
  CS_WFSYNCUUID = "WFSyncUUID"
1074
  CS_SYNCSOURCE = "SyncSource"
1075
  CS_SYNCTARGET = "SyncTarget"
1076
  CS_PAUSEDSYNCS = "PausedSyncS"
1077
  CS_PAUSEDSYNCT = "PausedSyncT"
1078
  CSET_SYNC = compat.UniqueFrozenset([
1079
    CS_WFREPORTPARAMS,
1080
    CS_STARTINGSYNCS,
1081
    CS_STARTINGSYNCT,
1082
    CS_WFBITMAPS,
1083
    CS_WFBITMAPT,
1084
    CS_WFSYNCUUID,
1085
    CS_SYNCSOURCE,
1086
    CS_SYNCTARGET,
1087
    CS_PAUSEDSYNCS,
1088
    CS_PAUSEDSYNCT,
1089
    ])
1090

  
1091
  DS_DISKLESS = "Diskless"
1092
  DS_ATTACHING = "Attaching" # transient state
1093
  DS_FAILED = "Failed" # transient state, next: diskless
1094
  DS_NEGOTIATING = "Negotiating" # transient state
1095
  DS_INCONSISTENT = "Inconsistent" # while syncing or after creation
1096
  DS_OUTDATED = "Outdated"
1097
  DS_DUNKNOWN = "DUnknown" # shown for peer disk when not connected
1098
  DS_CONSISTENT = "Consistent"
1099
  DS_UPTODATE = "UpToDate" # normal state
1100

  
1101
  RO_PRIMARY = "Primary"
1102
  RO_SECONDARY = "Secondary"
1103
  RO_UNKNOWN = "Unknown"
1104

  
1105
  def __init__(self, procline):
1106
    u = self.UNCONF_RE.match(procline)
1107
    if u:
1108
      self.cstatus = self.CS_UNCONFIGURED
1109
      self.lrole = self.rrole = self.ldisk = self.rdisk = None
1110
    else:
1111
      m = self.LINE_RE.match(procline)
1112
      if not m:
1113
        raise errors.BlockDeviceError("Can't parse input data '%s'" % procline)
1114
      self.cstatus = m.group(1)
1115
      self.lrole = m.group(2)
1116
      self.rrole = m.group(3)
1117
      self.ldisk = m.group(4)
1118
      self.rdisk = m.group(5)
1119

  
1120
    # end reading of data from the LINE_RE or UNCONF_RE
1121

  
1122
    self.is_standalone = self.cstatus == self.CS_STANDALONE
1123
    self.is_wfconn = self.cstatus == self.CS_WFCONNECTION
1124
    self.is_connected = self.cstatus == self.CS_CONNECTED
1125
    self.is_primary = self.lrole == self.RO_PRIMARY
1126
    self.is_secondary = self.lrole == self.RO_SECONDARY
1127
    self.peer_primary = self.rrole == self.RO_PRIMARY
1128
    self.peer_secondary = self.rrole == self.RO_SECONDARY
1129
    self.both_primary = self.is_primary and self.peer_primary
1130
    self.both_secondary = self.is_secondary and self.peer_secondary
1131

  
1132
    self.is_diskless = self.ldisk == self.DS_DISKLESS
1133
    self.is_disk_uptodate = self.ldisk == self.DS_UPTODATE
1134

  
1135
    self.is_in_resync = self.cstatus in self.CSET_SYNC
1136
    self.is_in_use = self.cstatus != self.CS_UNCONFIGURED
1137

  
1138
    m = self.SYNC_RE.match(procline)
1139
    if m:
1140
      self.sync_percent = float(m.group(1))
1141
      hours = int(m.group(2))
1142
      minutes = int(m.group(3))
1143
      seconds = int(m.group(4))
1144
      self.est_time = hours * 3600 + minutes * 60 + seconds
1145
    else:
1146
      # we have (in this if branch) no percent information, but if
1147
      # we're resyncing we need to 'fake' a sync percent information,
1148
      # as this is how cmdlib determines if it makes sense to wait for
1149
      # resyncing or not
1150
      if self.is_in_resync:
1151
        self.sync_percent = 0
1152
      else:
1153
        self.sync_percent = None
1154
      self.est_time = None
1155

  
1156

  
1157
class BaseDRBD(BlockDev): # pylint: disable=W0223
1158
  """Base DRBD class.
1159

  
1160
  This class contains a few bits of common functionality between the
1161
  0.7 and 8.x versions of DRBD.
1162

  
1163
  """
1164
  _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)(?:\.\d+)?"
1165
                           r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
1166
  _VALID_LINE_RE = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
1167
  _UNUSED_LINE_RE = re.compile("^ *([0-9]+): cs:Unconfigured$")
1168

  
1169
  _DRBD_MAJOR = 147
1170
  _ST_UNCONFIGURED = "Unconfigured"
1171
  _ST_WFCONNECTION = "WFConnection"
1172
  _ST_CONNECTED = "Connected"
1173

  
1174
  _STATUS_FILE = constants.DRBD_STATUS_FILE
1175
  _USERMODE_HELPER_FILE = "/sys/module/drbd/parameters/usermode_helper"
1176

  
1177
  @staticmethod
1178
  def _GetProcData(filename=_STATUS_FILE):
1179
    """Return data from /proc/drbd.
1180

  
1181
    """
1182
    try:
1183
      data = utils.ReadFile(filename).splitlines()
1184
    except EnvironmentError, err:
1185
      if err.errno == errno.ENOENT:
1186
        _ThrowError("The file %s cannot be opened, check if the module"
1187
                    " is loaded (%s)", filename, str(err))
1188
      else:
1189
        _ThrowError("Can't read the DRBD proc file %s: %s", filename, str(err))
1190
    if not data:
1191
      _ThrowError("Can't read any data from %s", filename)
1192
    return data
1193

  
1194
  @classmethod
1195
  def _MassageProcData(cls, data):
1196
    """Transform the output of _GetProdData into a nicer form.
1197

  
1198
    @return: a dictionary of minor: joined lines from /proc/drbd
1199
        for that minor
1200

  
1201
    """
1202
    results = {}
1203
    old_minor = old_line = None
1204
    for line in data:
1205
      if not line: # completely empty lines, as can be returned by drbd8.0+
1206
        continue
1207
      lresult = cls._VALID_LINE_RE.match(line)
1208
      if lresult is not None:
1209
        if old_minor is not None:
1210
          results[old_minor] = old_line
1211
        old_minor = int(lresult.group(1))
1212
        old_line = line
1213
      else:
1214
        if old_minor is not None:
1215
          old_line += " " + line.strip()
1216
    # add last line
1217
    if old_minor is not None:
1218
      results[old_minor] = old_line
1219
    return results
707
    base.ThrowError("Can't grow LV %s: %s", self.dev_path, result.output)
1220 708

  
1221
  @classmethod
1222
  def _GetVersion(cls, proc_data):
1223
    """Return the DRBD version.
1224

  
1225
    This will return a dict with keys:
1226
      - k_major
1227
      - k_minor
1228
      - k_point
1229
      - api
1230
      - proto
1231
      - proto2 (only on drbd > 8.2.X)
1232

  
1233
    """
1234
    first_line = proc_data[0].strip()
1235
    version = cls._VERSION_RE.match(first_line)
1236
    if not version:
1237
      raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
1238
                                    first_line)
1239

  
1240
    values = version.groups()
1241
    retval = {
1242
      "k_major": int(values[0]),
1243
      "k_minor": int(values[1]),
1244
      "k_point": int(values[2]),
1245
      "api": int(values[3]),
1246
      "proto": int(values[4]),
1247
      }
1248
    if values[5] is not None:
1249
      retval["proto2"] = values[5]
1250

  
1251
    return retval
1252

  
1253
  @staticmethod
1254
  def GetUsermodeHelper(filename=_USERMODE_HELPER_FILE):
1255
    """Returns DRBD usermode_helper currently set.
1256

  
1257
    """
1258
    try:
1259
      helper = utils.ReadFile(filename).splitlines()[0]
1260
    except EnvironmentError, err:
1261
      if err.errno == errno.ENOENT:
1262
        _ThrowError("The file %s cannot be opened, check if the module"
1263
                    " is loaded (%s)", filename, str(err))
1264
      else:
1265
        _ThrowError("Can't read DRBD helper file %s: %s", filename, str(err))
1266
    if not helper:
1267
      _ThrowError("Can't read any data from %s", filename)
1268
    return helper
1269

  
1270
  @staticmethod
1271
  def _DevPath(minor):
1272
    """Return the path to a drbd device for a given minor.
1273

  
1274
    """
1275
    return "/dev/drbd%d" % minor
1276

  
1277
  @classmethod
1278
  def GetUsedDevs(cls):
1279
    """Compute the list of used DRBD devices.
1280

  
1281
    """
1282
    data = cls._GetProcData()
1283

  
1284
    used_devs = {}
1285
    for line in data:
1286
      match = cls._VALID_LINE_RE.match(line)
1287
      if not match:
1288
        continue
1289
      minor = int(match.group(1))
1290
      state = match.group(2)
1291
      if state == cls._ST_UNCONFIGURED:
1292
        continue
1293
      used_devs[minor] = state, line
1294

  
1295
    return used_devs
1296

  
1297
  def _SetFromMinor(self, minor):
1298
    """Set our parameters based on the given minor.
1299

  
1300
    This sets our minor variable and our dev_path.
1301

  
1302
    """
1303
    if minor is None:
1304
      self.minor = self.dev_path = None
1305
      self.attached = False
1306
    else:
1307
      self.minor = minor
1308
      self.dev_path = self._DevPath(minor)
1309
      self.attached = True
1310

  
1311
  @staticmethod
1312
  def _CheckMetaSize(meta_device):
1313
    """Check if the given meta device looks like a valid one.
1314

  
1315
    This currently only checks the size, which must be around
1316
    128MiB.
1317

  
1318
    """
1319
    result = utils.RunCmd(["blockdev", "--getsize", meta_device])
1320
    if result.failed:
1321
      _ThrowError("Failed to get device size: %s - %s",
1322
                  result.fail_reason, result.output)
1323
    try:
1324
      sectors = int(result.stdout)
1325
    except (TypeError, ValueError):
1326
      _ThrowError("Invalid output from blockdev: '%s'", result.stdout)
1327
    num_bytes = sectors * 512
1328
    if num_bytes < 128 * 1024 * 1024: # less than 128MiB
1329
      _ThrowError("Meta device too small (%.2fMib)", (num_bytes / 1024 / 1024))
1330
    # the maximum *valid* size of the meta device when living on top
1331
    # of LVM is hard to compute: it depends on the number of stripes
1332
    # and the PE size; e.g. a 2-stripe, 64MB PE will result in a 128MB
1333
    # (normal size), but an eight-stripe 128MB PE will result in a 1GB
1334
    # size meta device; as such, we restrict it to 1GB (a little bit
1335
    # too generous, but making assumptions about PE size is hard)
1336
    if num_bytes > 1024 * 1024 * 1024:
1337
      _ThrowError("Meta device too big (%.2fMiB)", (num_bytes / 1024 / 1024))
1338

  
1339
  def Rename(self, new_id):
1340
    """Rename a device.
1341

  
1342
    This is not supported for drbd devices.
1343

  
1344
    """
1345
    raise errors.ProgrammerError("Can't rename a drbd device")
1346

  
1347

  
1348
class DRBD8(BaseDRBD):
1349
  """DRBD v8.x block device.
1350

  
1351
  This implements the local host part of the DRBD device, i.e. it
1352
  doesn't do anything to the supposed peer. If you need a fully
1353
  connected DRBD pair, you need to use this class on both hosts.
1354

  
1355
  The unique_id for the drbd device is a (local_ip, local_port,
1356
  remote_ip, remote_port, local_minor, secret) tuple, and it must have
1357
  two children: the data device and the meta_device. The meta device
1358
  is checked for valid size and is zeroed on create.
1359

  
1360
  """
1361
  _MAX_MINORS = 255
1362
  _PARSE_SHOW = None
1363

  
1364
  # timeout constants
1365
  _NET_RECONFIG_TIMEOUT = 60
1366

  
1367
  # command line options for barriers
1368
  _DISABLE_DISK_OPTION = "--no-disk-barrier"  # -a
1369
  _DISABLE_DRAIN_OPTION = "--no-disk-drain"   # -D
1370
  _DISABLE_FLUSH_OPTION = "--no-disk-flushes" # -i
1371
  _DISABLE_META_FLUSH_OPTION = "--no-md-flushes"  # -m
1372

  
1373
  def __init__(self, unique_id, children, size, params):
1374
    if children and children.count(None) > 0:
1375
      children = []
1376
    if len(children) not in (0, 2):
1377
      raise ValueError("Invalid configuration data %s" % str(children))
1378
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6:
1379
      raise ValueError("Invalid configuration data %s" % str(unique_id))
1380
    (self._lhost, self._lport,
1381
     self._rhost, self._rport,
1382
     self._aminor, self._secret) = unique_id
1383
    if children:
1384
      if not _CanReadDevice(children[1].dev_path):
1385
        logging.info("drbd%s: Ignoring unreadable meta device", self._aminor)
1386
        children = []
1387
    super(DRBD8, self).__init__(unique_id, children, size, params)
1388
    self.major = self._DRBD_MAJOR
1389
    version = self._GetVersion(self._GetProcData())
1390
    if version["k_major"] != 8:
1391
      _ThrowError("Mismatch in DRBD kernel version and requested ganeti"
1392
                  " usage: kernel is %s.%s, ganeti wants 8.x",
1393
                  version["k_major"], version["k_minor"])
1394

  
1395
    if (self._lhost is not None and self._lhost == self._rhost and
1396
        self._lport == self._rport):
1397
      raise ValueError("Invalid configuration data, same local/remote %s" %
1398
                       (unique_id,))
1399
    self.Attach()
1400

  
1401
  @classmethod
1402
  def _InitMeta(cls, minor, dev_path):
1403
    """Initialize a meta device.
1404

  
1405
    This will not work if the given minor is in use.
1406

  
1407
    """
1408
    # Zero the metadata first, in order to make sure drbdmeta doesn't
1409
    # try to auto-detect existing filesystems or similar (see
1410
    # http://code.google.com/p/ganeti/issues/detail?id=182); we only
1411
    # care about the first 128MB of data in the device, even though it
1412
    # can be bigger
1413
    result = utils.RunCmd([constants.DD_CMD,
1414
                           "if=/dev/zero", "of=%s" % dev_path,
1415
                           "bs=1048576", "count=128", "oflag=direct"])
1416
    if result.failed:
1417
      _ThrowError("Can't wipe the meta device: %s", result.output)
1418

  
1419
    result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
1420
                           "v08", dev_path, "0", "create-md"])
1421
    if result.failed:
1422
      _ThrowError("Can't initialize meta device: %s", result.output)
1423

  
1424
  @classmethod
1425
  def _FindUnusedMinor(cls):
1426
    """Find an unused DRBD device.
1427

  
1428
    This is specific to 8.x as the minors are allocated dynamically,
1429
    so non-existing numbers up to a max minor count are actually free.
1430

  
1431
    """
1432
    data = cls._GetProcData()
1433

  
1434
    highest = None
1435
    for line in data:
1436
      match = cls._UNUSED_LINE_RE.match(line)
1437
      if match:
1438
        return int(match.group(1))
1439
      match = cls._VALID_LINE_RE.match(line)
1440
      if match:
1441
        minor = int(match.group(1))
1442
        highest = max(highest, minor)
1443
    if highest is None: # there are no minors in use at all
1444
      return 0
1445
    if highest >= cls._MAX_MINORS:
1446
      logging.error("Error: no free drbd minors!")
1447
      raise errors.BlockDeviceError("Can't find a free DRBD minor")
1448
    return highest + 1
1449 709

  
1450
  @classmethod
1451
  def _GetShowParser(cls):
1452
    """Return a parser for `drbd show` output.
1453

  
1454
    This will either create or return an already-created parser for the
1455
    output of the command `drbd show`.
1456

  
1457
    """
1458
    if cls._PARSE_SHOW is not None:
1459
      return cls._PARSE_SHOW
1460

  
1461
    # pyparsing setup
1462
    lbrace = pyp.Literal("{").suppress()
1463
    rbrace = pyp.Literal("}").suppress()
1464
    lbracket = pyp.Literal("[").suppress()
1465
    rbracket = pyp.Literal("]").suppress()
1466
    semi = pyp.Literal(";").suppress()
1467
    colon = pyp.Literal(":").suppress()
1468
    # this also converts the value to an int
1469
    number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
1470

  
1471
    comment = pyp.Literal("#") + pyp.Optional(pyp.restOfLine)
1472
    defa = pyp.Literal("_is_default").suppress()
1473
    dbl_quote = pyp.Literal('"').suppress()
1474

  
1475
    keyword = pyp.Word(pyp.alphanums + "-")
1476

  
1477
    # value types
1478
    value = pyp.Word(pyp.alphanums + "_-/.:")
1479
    quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1480
    ipv4_addr = (pyp.Optional(pyp.Literal("ipv4")).suppress() +
1481
                 pyp.Word(pyp.nums + ".") + colon + number)
1482
    ipv6_addr = (pyp.Optional(pyp.Literal("ipv6")).suppress() +
1483
                 pyp.Optional(lbracket) + pyp.Word(pyp.hexnums + ":") +
1484
                 pyp.Optional(rbracket) + colon + number)
1485
    # meta device, extended syntax
1486
    meta_value = ((value ^ quoted) + lbracket + number + rbracket)
1487
    # device name, extended syntax
1488
    device_value = pyp.Literal("minor").suppress() + number
1489

  
1490
    # a statement
1491
    stmt = (~rbrace + keyword + ~lbrace +
1492
            pyp.Optional(ipv4_addr ^ ipv6_addr ^ value ^ quoted ^ meta_value ^
1493
                         device_value) +
1494
            pyp.Optional(defa) + semi +
1495
            pyp.Optional(pyp.restOfLine).suppress())
1496

  
1497
    # an entire section
1498
    section_name = pyp.Word(pyp.alphas + "_")
1499
    section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1500

  
1501
    bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1502
    bnf.ignore(comment)
1503

  
1504
    cls._PARSE_SHOW = bnf
1505

  
1506
    return bnf
1507

  
1508
  @classmethod
1509
  def _GetShowData(cls, minor):
1510
    """Return the `drbdsetup show` data for a minor.
1511

  
1512
    """
1513
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1514
    if result.failed:
1515
      logging.error("Can't display the drbd config: %s - %s",
1516
                    result.fail_reason, result.output)
1517
      return None
1518
    return result.stdout
1519

  
1520
  @classmethod
1521
  def _GetDevInfo(cls, out):
1522
    """Parse details about a given DRBD minor.
1523

  
1524
    This return, if available, the local backing device (as a path)
1525
    and the local and remote (ip, port) information from a string
1526
    containing the output of the `drbdsetup show` command as returned
1527
    by _GetShowData.
1528

  
1529
    """
1530
    data = {}
1531
    if not out:
1532
      return data
1533

  
1534
    bnf = cls._GetShowParser()
1535
    # run pyparse
1536

  
1537
    try:
1538
      results = bnf.parseString(out)
1539
    except pyp.ParseException, err:
1540
      _ThrowError("Can't parse drbdsetup show output: %s", str(err))
1541

  
1542
    # and massage the results into our desired format
1543
    for section in results:
1544
      sname = section[0]
1545
      if sname == "_this_host":
1546
        for lst in section[1:]:
1547
          if lst[0] == "disk":
1548
            data["local_dev"] = lst[1]
1549
          elif lst[0] == "meta-disk":
1550
            data["meta_dev"] = lst[1]
1551
            data["meta_index"] = lst[2]
1552
          elif lst[0] == "address":
1553
            data["local_addr"] = tuple(lst[1:])
1554
      elif sname == "_remote_host":
1555
        for lst in section[1:]:
1556
          if lst[0] == "address":
1557
            data["remote_addr"] = tuple(lst[1:])
1558
    return data
1559

  
1560
  def _MatchesLocal(self, info):
1561
    """Test if our local config matches with an existing device.
1562

  
1563
    The parameter should be as returned from `_GetDevInfo()`. This
1564
    method tests if our local backing device is the same as the one in
1565
    the info parameter, in effect testing if we look like the given
1566
    device.
1567

  
1568
    """
1569
    if self._children:
1570
      backend, meta = self._children
1571
    else:
1572
      backend = meta = None
1573

  
1574
    if backend is not None:
1575
      retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1576
    else:
1577
      retval = ("local_dev" not in info)
1578

  
1579
    if meta is not None:
1580
      retval = retval and ("meta_dev" in info and
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff