Statistics
| Branch: | Tag: | Revision:

root / lib / bdev.py @ c7c6606d

History | View | Annotate | Download (86.2 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2010, 2011, 2012 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 time
26
import errno
27
import shlex
28
import stat
29
import pyparsing as pyp
30
import os
31
import logging
32

    
33
from ganeti import utils
34
from ganeti import errors
35
from ganeti import constants
36
from ganeti import objects
37
from ganeti import compat
38
from ganeti import netutils
39

    
40

    
41
# Size of reads in _CanReadDevice
42
_DEVICE_READ_SIZE = 128 * 1024
43

    
44

    
45
def _IgnoreError(fn, *args, **kwargs):
46
  """Executes the given function, ignoring BlockDeviceErrors.
47

48
  This is used in order to simplify the execution of cleanup or
49
  rollback functions.
50

51
  @rtype: boolean
52
  @return: True when fn didn't raise an exception, False otherwise
53

54
  """
55
  try:
56
    fn(*args, **kwargs)
57
    return True
58
  except errors.BlockDeviceError, err:
59
    logging.warning("Caught BlockDeviceError but ignoring: %s", str(err))
60
    return False
61

    
62

    
63
def _ThrowError(msg, *args):
64
  """Log an error to the node daemon and the raise an exception.
65

66
  @type msg: string
67
  @param msg: the text of the exception
68
  @raise errors.BlockDeviceError
69

70
  """
71
  if args:
72
    msg = msg % args
73
  logging.error(msg)
74
  raise errors.BlockDeviceError(msg)
75

    
76

    
77
def _CanReadDevice(path):
78
  """Check if we can read from the given device.
79

80
  This tries to read the first 128k of the device.
81

82
  """
83
  try:
84
    utils.ReadFile(path, size=_DEVICE_READ_SIZE)
85
    return True
86
  except EnvironmentError:
87
    logging.warning("Can't read from device %s", path, exc_info=True)
88
    return False
89

    
90

    
91
class BlockDev(object):
92
  """Block device abstract class.
93

94
  A block device can be in the following states:
95
    - not existing on the system, and by `Create()` it goes into:
96
    - existing but not setup/not active, and by `Assemble()` goes into:
97
    - active read-write and by `Open()` it goes into
98
    - online (=used, or ready for use)
99

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

106
  The many different states of the device are due to the fact that we
107
  need to cover many device types:
108
    - logical volumes are created, lvchange -a y $lv, and used
109
    - drbd devices are attached to a local disk/remote peer and made primary
110

111
  A block device is identified by three items:
112
    - the /dev path of the device (dynamic)
113
    - a unique ID of the device (static)
114
    - it's major/minor pair (dynamic)
115

116
  Not all devices implement both the first two as distinct items. LVM
117
  logical volumes have their unique ID (the pair volume group, logical
118
  volume name) in a 1-to-1 relation to the dev path. For DRBD devices,
119
  the /dev path is again dynamic and the unique id is the pair (host1,
120
  dev1), (host2, dev2).
121

122
  You can get to a device in two ways:
123
    - creating the (real) device, which returns you
124
      an attached instance (lvcreate)
125
    - attaching of a python instance to an existing (real) device
126

127
  The second point, the attachement to a device, is different
128
  depending on whether the device is assembled or not. At init() time,
129
  we search for a device with the same unique_id as us. If found,
130
  good. It also means that the device is already assembled. If not,
131
  after assembly we'll have our correct major/minor.
132

133
  """
134
  def __init__(self, unique_id, children, size, params):
135
    self._children = children
136
    self.dev_path = None
137
    self.unique_id = unique_id
138
    self.major = None
139
    self.minor = None
140
    self.attached = False
141
    self.size = size
142
    self.params = params
143

    
144
  def Assemble(self):
145
    """Assemble the device from its components.
146

147
    Implementations of this method by child classes must ensure that:
148
      - after the device has been assembled, it knows its major/minor
149
        numbers; this allows other devices (usually parents) to probe
150
        correctly for their children
151
      - calling this method on an existing, in-use device is safe
152
      - if the device is already configured (and in an OK state),
153
        this method is idempotent
154

155
    """
156
    pass
157

    
158
  def Attach(self):
159
    """Find a device which matches our config and attach to it.
160

161
    """
162
    raise NotImplementedError
163

    
164
  def Close(self):
165
    """Notifies that the device will no longer be used for I/O.
166

167
    """
168
    raise NotImplementedError
169

    
170
  @classmethod
171
  def Create(cls, unique_id, children, size, params):
172
    """Create the device.
173

174
    If the device cannot be created, it will return None
175
    instead. Error messages go to the logging system.
176

177
    Note that for some devices, the unique_id is used, and for other,
178
    the children. The idea is that these two, taken together, are
179
    enough for both creation and assembly (later).
180

181
    """
182
    raise NotImplementedError
183

    
184
  def Remove(self):
185
    """Remove this device.
186

187
    This makes sense only for some of the device types: LV and file
188
    storage. Also note that if the device can't attach, the removal
189
    can't be completed.
190

191
    """
192
    raise NotImplementedError
193

    
194
  def Rename(self, new_id):
195
    """Rename this device.
196

197
    This may or may not make sense for a given device type.
198

199
    """
200
    raise NotImplementedError
201

    
202
  def Open(self, force=False):
203
    """Make the device ready for use.
204

205
    This makes the device ready for I/O. For now, just the DRBD
206
    devices need this.
207

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

211
    """
212
    raise NotImplementedError
213

    
214
  def Shutdown(self):
215
    """Shut down the device, freeing its children.
216

217
    This undoes the `Assemble()` work, except for the child
218
    assembling; as such, the children on the device are still
219
    assembled after this call.
220

221
    """
222
    raise NotImplementedError
223

    
224
  def SetSyncParams(self, params):
225
    """Adjust the synchronization parameters of the mirror.
226

227
    In case this is not a mirroring device, this is no-op.
228

229
    @param params: dictionary of LD level disk parameters related to the
230
    synchronization.
231
    @rtype: list
232
    @return: a list of error messages, emitted both by the current node and by
233
    children. An empty list means no errors.
234

235
    """
236
    result = []
237
    if self._children:
238
      for child in self._children:
239
        result.extend(child.SetSyncParams(params))
240
    return result
241

    
242
  def PauseResumeSync(self, pause):
243
    """Pause/Resume the sync of the mirror.
244

245
    In case this is not a mirroring device, this is no-op.
246

247
    @param pause: Whether to pause or resume
248

249
    """
250
    result = True
251
    if self._children:
252
      for child in self._children:
253
        result = result and child.PauseResumeSync(pause)
254
    return result
255

    
256
  def GetSyncStatus(self):
257
    """Returns the sync status of the device.
258

259
    If this device is a mirroring device, this function returns the
260
    status of the mirror.
261

262
    If sync_percent is None, it means the device is not syncing.
263

264
    If estimated_time is None, it means we can't estimate
265
    the time needed, otherwise it's the time left in seconds.
266

267
    If is_degraded is True, it means the device is missing
268
    redundancy. This is usually a sign that something went wrong in
269
    the device setup, if sync_percent is None.
270

271
    The ldisk parameter represents the degradation of the local
272
    data. This is only valid for some devices, the rest will always
273
    return False (not degraded).
274

275
    @rtype: objects.BlockDevStatus
276

277
    """
278
    return objects.BlockDevStatus(dev_path=self.dev_path,
279
                                  major=self.major,
280
                                  minor=self.minor,
281
                                  sync_percent=None,
282
                                  estimated_time=None,
283
                                  is_degraded=False,
284
                                  ldisk_status=constants.LDS_OKAY)
285

    
286
  def CombinedSyncStatus(self):
287
    """Calculate the mirror status recursively for our children.
288

289
    The return value is the same as for `GetSyncStatus()` except the
290
    minimum percent and maximum time are calculated across our
291
    children.
292

293
    @rtype: objects.BlockDevStatus
294

295
    """
296
    status = self.GetSyncStatus()
297

    
298
    min_percent = status.sync_percent
299
    max_time = status.estimated_time
300
    is_degraded = status.is_degraded
301
    ldisk_status = status.ldisk_status
302

    
303
    if self._children:
304
      for child in self._children:
305
        child_status = child.GetSyncStatus()
306

    
307
        if min_percent is None:
308
          min_percent = child_status.sync_percent
309
        elif child_status.sync_percent is not None:
310
          min_percent = min(min_percent, child_status.sync_percent)
311

    
312
        if max_time is None:
313
          max_time = child_status.estimated_time
314
        elif child_status.estimated_time is not None:
315
          max_time = max(max_time, child_status.estimated_time)
316

    
317
        is_degraded = is_degraded or child_status.is_degraded
318

    
319
        if ldisk_status is None:
320
          ldisk_status = child_status.ldisk_status
321
        elif child_status.ldisk_status is not None:
322
          ldisk_status = max(ldisk_status, child_status.ldisk_status)
323

    
324
    return objects.BlockDevStatus(dev_path=self.dev_path,
325
                                  major=self.major,
326
                                  minor=self.minor,
327
                                  sync_percent=min_percent,
328
                                  estimated_time=max_time,
329
                                  is_degraded=is_degraded,
330
                                  ldisk_status=ldisk_status)
331

    
332
  def SetInfo(self, text):
333
    """Update metadata with info text.
334

335
    Only supported for some device types.
336

337
    """
338
    for child in self._children:
339
      child.SetInfo(text)
340

    
341
  def Grow(self, amount, dryrun):
342
    """Grow the block device.
343

344
    @type amount: integer
345
    @param amount: the amount (in mebibytes) to grow with
346
    @type dryrun: boolean
347
    @param dryrun: whether to execute the operation in simulation mode
348
        only, without actually increasing the size
349

350
    """
351
    raise NotImplementedError
352

    
353
  def GetActualSize(self):
354
    """Return the actual disk size.
355

356
    @note: the device needs to be active when this is called
357

358
    """
359
    assert self.attached, "BlockDevice not attached in GetActualSize()"
360
    result = utils.RunCmd(["blockdev", "--getsize64", self.dev_path])
361
    if result.failed:
362
      _ThrowError("blockdev failed (%s): %s",
363
                  result.fail_reason, result.output)
364
    try:
365
      sz = int(result.output.strip())
366
    except (ValueError, TypeError), err:
367
      _ThrowError("Failed to parse blockdev output: %s", str(err))
368
    return sz
369

    
370
  def __repr__(self):
371
    return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
372
            (self.__class__, self.unique_id, self._children,
373
             self.major, self.minor, self.dev_path))
374

    
375

    
376
class LogicalVolume(BlockDev):
377
  """Logical Volume block device.
378

379
  """
380
  _VALID_NAME_RE = re.compile("^[a-zA-Z0-9+_.-]*$")
381
  _INVALID_NAMES = frozenset([".", "..", "snapshot", "pvmove"])
382
  _INVALID_SUBSTRINGS = frozenset(["_mlog", "_mimage"])
383

    
384
  def __init__(self, unique_id, children, size, params):
385
    """Attaches to a LV device.
386

387
    The unique_id is a tuple (vg_name, lv_name)
388

389
    """
390
    super(LogicalVolume, self).__init__(unique_id, children, size, params)
391
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
392
      raise ValueError("Invalid configuration data %s" % str(unique_id))
393
    self._vg_name, self._lv_name = unique_id
394
    self._ValidateName(self._vg_name)
395
    self._ValidateName(self._lv_name)
396
    self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
397
    self._degraded = True
398
    self.major = self.minor = self.pe_size = self.stripe_count = None
399
    self.Attach()
400

    
401
  @classmethod
402
  def Create(cls, unique_id, children, size, params):
403
    """Create a new logical volume.
404

405
    """
406
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
407
      raise errors.ProgrammerError("Invalid configuration data %s" %
408
                                   str(unique_id))
409
    vg_name, lv_name = unique_id
410
    cls._ValidateName(vg_name)
411
    cls._ValidateName(lv_name)
412
    pvs_info = cls.GetPVInfo([vg_name])
413
    if not pvs_info:
414
      _ThrowError("Can't compute PV info for vg %s", vg_name)
415
    pvs_info.sort()
416
    pvs_info.reverse()
417

    
418
    pvlist = [pv[1] for pv in pvs_info]
419
    if compat.any(":" in v for v in pvlist):
420
      _ThrowError("Some of your PVs have the invalid character ':' in their"
421
                  " name, this is not supported - please filter them out"
422
                  " in lvm.conf using either 'filter' or 'preferred_names'")
423
    free_size = sum([pv[0] for pv in pvs_info])
424
    current_pvs = len(pvlist)
425
    desired_stripes = params[constants.LDP_STRIPES]
426
    stripes = min(current_pvs, desired_stripes)
427
    if stripes < desired_stripes:
428
      logging.warning("Could not use %d stripes for VG %s, as only %d PVs are"
429
                      " available.", desired_stripes, vg_name, current_pvs)
430

    
431
    # The size constraint should have been checked from the master before
432
    # calling the create function.
433
    if free_size < size:
434
      _ThrowError("Not enough free space: required %s,"
435
                  " available %s", size, free_size)
436
    cmd = ["lvcreate", "-L%dm" % size, "-n%s" % lv_name]
437
    # If the free space is not well distributed, we won't be able to
438
    # create an optimally-striped volume; in that case, we want to try
439
    # with N, N-1, ..., 2, and finally 1 (non-stripped) number of
440
    # stripes
441
    for stripes_arg in range(stripes, 0, -1):
442
      result = utils.RunCmd(cmd + ["-i%d" % stripes_arg] + [vg_name] + pvlist)
443
      if not result.failed:
444
        break
445
    if result.failed:
446
      _ThrowError("LV create failed (%s): %s",
447
                  result.fail_reason, result.output)
448
    return LogicalVolume(unique_id, children, size, params)
449

    
450
  @staticmethod
451
  def _GetVolumeInfo(lvm_cmd, fields):
452
    """Returns LVM Volumen infos using lvm_cmd
453

454
    @param lvm_cmd: Should be one of "pvs", "vgs" or "lvs"
455
    @param fields: Fields to return
456
    @return: A list of dicts each with the parsed fields
457

458
    """
459
    if not fields:
460
      raise errors.ProgrammerError("No fields specified")
461

    
462
    sep = "|"
463
    cmd = [lvm_cmd, "--noheadings", "--nosuffix", "--units=m", "--unbuffered",
464
           "--separator=%s" % sep, "-o%s" % ",".join(fields)]
465

    
466
    result = utils.RunCmd(cmd)
467
    if result.failed:
468
      raise errors.CommandError("Can't get the volume information: %s - %s" %
469
                                (result.fail_reason, result.output))
470

    
471
    data = []
472
    for line in result.stdout.splitlines():
473
      splitted_fields = line.strip().split(sep)
474

    
475
      if len(fields) != len(splitted_fields):
476
        raise errors.CommandError("Can't parse %s output: line '%s'" %
477
                                  (lvm_cmd, line))
478

    
479
      data.append(splitted_fields)
480

    
481
    return data
482

    
483
  @classmethod
484
  def GetPVInfo(cls, vg_names, filter_allocatable=True):
485
    """Get the free space info for PVs in a volume group.
486

487
    @param vg_names: list of volume group names, if empty all will be returned
488
    @param filter_allocatable: whether to skip over unallocatable PVs
489

490
    @rtype: list
491
    @return: list of tuples (free_space, name) with free_space in mebibytes
492

493
    """
494
    try:
495
      info = cls._GetVolumeInfo("pvs", ["pv_name", "vg_name", "pv_free",
496
                                        "pv_attr"])
497
    except errors.GenericError, err:
498
      logging.error("Can't get PV information: %s", err)
499
      return None
500

    
501
    data = []
502
    for pv_name, vg_name, pv_free, pv_attr in info:
503
      # (possibly) skip over pvs which are not allocatable
504
      if filter_allocatable and pv_attr[0] != "a":
505
        continue
506
      # (possibly) skip over pvs which are not in the right volume group(s)
507
      if vg_names and vg_name not in vg_names:
508
        continue
509
      data.append((float(pv_free), pv_name, vg_name))
510

    
511
    return data
512

    
513
  @classmethod
514
  def GetVGInfo(cls, vg_names, filter_readonly=True):
515
    """Get the free space info for specific VGs.
516

517
    @param vg_names: list of volume group names, if empty all will be returned
518
    @param filter_readonly: whether to skip over readonly VGs
519

520
    @rtype: list
521
    @return: list of tuples (free_space, total_size, name) with free_space in
522
             MiB
523

524
    """
525
    try:
526
      info = cls._GetVolumeInfo("vgs", ["vg_name", "vg_free", "vg_attr",
527
                                        "vg_size"])
528
    except errors.GenericError, err:
529
      logging.error("Can't get VG information: %s", err)
530
      return None
531

    
532
    data = []
533
    for vg_name, vg_free, vg_attr, vg_size in info:
534
      # (possibly) skip over vgs which are not writable
535
      if filter_readonly and vg_attr[0] == "r":
536
        continue
537
      # (possibly) skip over vgs which are not in the right volume group(s)
538
      if vg_names and vg_name not in vg_names:
539
        continue
540
      data.append((float(vg_free), float(vg_size), vg_name))
541

    
542
    return data
543

    
544
  @classmethod
545
  def _ValidateName(cls, name):
546
    """Validates that a given name is valid as VG or LV name.
547

548
    The list of valid characters and restricted names is taken out of
549
    the lvm(8) manpage, with the simplification that we enforce both
550
    VG and LV restrictions on the names.
551

552
    """
553
    if (not cls._VALID_NAME_RE.match(name) or
554
        name in cls._INVALID_NAMES or
555
        compat.any(substring in name for substring in cls._INVALID_SUBSTRINGS)):
556
      _ThrowError("Invalid LVM name '%s'", name)
557

    
558
  def Remove(self):
559
    """Remove this logical volume.
560

561
    """
562
    if not self.minor and not self.Attach():
563
      # the LV does not exist
564
      return
565
    result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
566
                           (self._vg_name, self._lv_name)])
567
    if result.failed:
568
      _ThrowError("Can't lvremove: %s - %s", result.fail_reason, result.output)
569

    
570
  def Rename(self, new_id):
571
    """Rename this logical volume.
572

573
    """
574
    if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
575
      raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
576
    new_vg, new_name = new_id
577
    if new_vg != self._vg_name:
578
      raise errors.ProgrammerError("Can't move a logical volume across"
579
                                   " volume groups (from %s to to %s)" %
580
                                   (self._vg_name, new_vg))
581
    result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
582
    if result.failed:
583
      _ThrowError("Failed to rename the logical volume: %s", result.output)
584
    self._lv_name = new_name
585
    self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
586

    
587
  def Attach(self):
588
    """Attach to an existing LV.
589

590
    This method will try to see if an existing and active LV exists
591
    which matches our name. If so, its major/minor will be
592
    recorded.
593

594
    """
595
    self.attached = False
596
    result = utils.RunCmd(["lvs", "--noheadings", "--separator=,",
597
                           "--units=m", "--nosuffix",
598
                           "-olv_attr,lv_kernel_major,lv_kernel_minor,"
599
                           "vg_extent_size,stripes", self.dev_path])
600
    if result.failed:
601
      logging.error("Can't find LV %s: %s, %s",
602
                    self.dev_path, result.fail_reason, result.output)
603
      return False
604
    # the output can (and will) have multiple lines for multi-segment
605
    # LVs, as the 'stripes' parameter is a segment one, so we take
606
    # only the last entry, which is the one we're interested in; note
607
    # that with LVM2 anyway the 'stripes' value must be constant
608
    # across segments, so this is a no-op actually
609
    out = result.stdout.splitlines()
610
    if not out: # totally empty result? splitlines() returns at least
611
                # one line for any non-empty string
612
      logging.error("Can't parse LVS output, no lines? Got '%s'", str(out))
613
      return False
614
    out = out[-1].strip().rstrip(",")
615
    out = out.split(",")
616
    if len(out) != 5:
617
      logging.error("Can't parse LVS output, len(%s) != 5", str(out))
618
      return False
619

    
620
    status, major, minor, pe_size, stripes = out
621
    if len(status) < 6:
622
      logging.error("lvs lv_attr is not at least 6 characters (%s)", status)
623
      return False
624

    
625
    try:
626
      major = int(major)
627
      minor = int(minor)
628
    except (TypeError, ValueError), err:
629
      logging.error("lvs major/minor cannot be parsed: %s", str(err))
630

    
631
    try:
632
      pe_size = int(float(pe_size))
633
    except (TypeError, ValueError), err:
634
      logging.error("Can't parse vg extent size: %s", err)
635
      return False
636

    
637
    try:
638
      stripes = int(stripes)
639
    except (TypeError, ValueError), err:
640
      logging.error("Can't parse the number of stripes: %s", err)
641
      return False
642

    
643
    self.major = major
644
    self.minor = minor
645
    self.pe_size = pe_size
646
    self.stripe_count = stripes
647
    self._degraded = status[0] == "v" # virtual volume, i.e. doesn't backing
648
                                      # storage
649
    self.attached = True
650
    return True
651

    
652
  def Assemble(self):
653
    """Assemble the device.
654

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

659
    """
660
    result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
661
    if result.failed:
662
      _ThrowError("Can't activate lv %s: %s", self.dev_path, result.output)
663

    
664
  def Shutdown(self):
665
    """Shutdown the device.
666

667
    This is a no-op for the LV device type, as we don't deactivate the
668
    volumes on shutdown.
669

670
    """
671
    pass
672

    
673
  def GetSyncStatus(self):
674
    """Returns the sync status of the device.
675

676
    If this device is a mirroring device, this function returns the
677
    status of the mirror.
678

679
    For logical volumes, sync_percent and estimated_time are always
680
    None (no recovery in progress, as we don't handle the mirrored LV
681
    case). The is_degraded parameter is the inverse of the ldisk
682
    parameter.
683

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

690
    The status was already read in Attach, so we just return it.
691

692
    @rtype: objects.BlockDevStatus
693

694
    """
695
    if self._degraded:
696
      ldisk_status = constants.LDS_FAULTY
697
    else:
698
      ldisk_status = constants.LDS_OKAY
699

    
700
    return objects.BlockDevStatus(dev_path=self.dev_path,
701
                                  major=self.major,
702
                                  minor=self.minor,
703
                                  sync_percent=None,
704
                                  estimated_time=None,
705
                                  is_degraded=self._degraded,
706
                                  ldisk_status=ldisk_status)
707

    
708
  def Open(self, force=False):
709
    """Make the device ready for I/O.
710

711
    This is a no-op for the LV device type.
712

713
    """
714
    pass
715

    
716
  def Close(self):
717
    """Notifies that the device will no longer be used for I/O.
718

719
    This is a no-op for the LV device type.
720

721
    """
722
    pass
723

    
724
  def Snapshot(self, size):
725
    """Create a snapshot copy of an lvm block device.
726

727
    @returns: tuple (vg, lv)
728

729
    """
730
    snap_name = self._lv_name + ".snap"
731

    
732
    # remove existing snapshot if found
733
    snap = LogicalVolume((self._vg_name, snap_name), None, size, self.params)
734
    _IgnoreError(snap.Remove)
735

    
736
    vg_info = self.GetVGInfo([self._vg_name])
737
    if not vg_info:
738
      _ThrowError("Can't compute VG info for vg %s", self._vg_name)
739
    free_size, _, _ = vg_info[0]
740
    if free_size < size:
741
      _ThrowError("Not enough free space: required %s,"
742
                  " available %s", size, free_size)
743

    
744
    result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
745
                           "-n%s" % snap_name, self.dev_path])
746
    if result.failed:
747
      _ThrowError("command: %s error: %s - %s",
748
                  result.cmd, result.fail_reason, result.output)
749

    
750
    return (self._vg_name, snap_name)
751

    
752
  def SetInfo(self, text):
753
    """Update metadata with info text.
754

755
    """
756
    BlockDev.SetInfo(self, text)
757

    
758
    # Replace invalid characters
759
    text = re.sub("^[^A-Za-z0-9_+.]", "_", text)
760
    text = re.sub("[^-A-Za-z0-9_+.]", "_", text)
761

    
762
    # Only up to 128 characters are allowed
763
    text = text[:128]
764

    
765
    result = utils.RunCmd(["lvchange", "--addtag", text,
766
                           self.dev_path])
767
    if result.failed:
768
      _ThrowError("Command: %s error: %s - %s", result.cmd, result.fail_reason,
769
                  result.output)
770

    
771
  def Grow(self, amount, dryrun):
772
    """Grow the logical volume.
773

774
    """
775
    if self.pe_size is None or self.stripe_count is None:
776
      if not self.Attach():
777
        _ThrowError("Can't attach to LV during Grow()")
778
    full_stripe_size = self.pe_size * self.stripe_count
779
    rest = amount % full_stripe_size
780
    if rest != 0:
781
      amount += full_stripe_size - rest
782
    cmd = ["lvextend", "-L", "+%dm" % amount]
783
    if dryrun:
784
      cmd.append("--test")
785
    # we try multiple algorithms since the 'best' ones might not have
786
    # space available in the right place, but later ones might (since
787
    # they have less constraints); also note that only recent LVM
788
    # supports 'cling'
789
    for alloc_policy in "contiguous", "cling", "normal":
790
      result = utils.RunCmd(cmd + ["--alloc", alloc_policy, self.dev_path])
791
      if not result.failed:
792
        return
793
    _ThrowError("Can't grow LV %s: %s", self.dev_path, result.output)
794

    
795

    
796
class DRBD8Status(object):
797
  """A DRBD status representation class.
798

799
  Note that this doesn't support unconfigured devices (cs:Unconfigured).
800

801
  """
802
  UNCONF_RE = re.compile(r"\s*[0-9]+:\s*cs:Unconfigured$")
803
  LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+(?:st|ro):([^/]+)/(\S+)"
804
                       "\s+ds:([^/]+)/(\S+)\s+.*$")
805
  SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*"
806
                       # Due to a bug in drbd in the kernel, introduced in
807
                       # commit 4b0715f096 (still unfixed as of 2011-08-22)
808
                       "(?:\s|M)"
809
                       "finish: ([0-9]+):([0-9]+):([0-9]+)\s.*$")
810

    
811
  CS_UNCONFIGURED = "Unconfigured"
812
  CS_STANDALONE = "StandAlone"
813
  CS_WFCONNECTION = "WFConnection"
814
  CS_WFREPORTPARAMS = "WFReportParams"
815
  CS_CONNECTED = "Connected"
816
  CS_STARTINGSYNCS = "StartingSyncS"
817
  CS_STARTINGSYNCT = "StartingSyncT"
818
  CS_WFBITMAPS = "WFBitMapS"
819
  CS_WFBITMAPT = "WFBitMapT"
820
  CS_WFSYNCUUID = "WFSyncUUID"
821
  CS_SYNCSOURCE = "SyncSource"
822
  CS_SYNCTARGET = "SyncTarget"
823
  CS_PAUSEDSYNCS = "PausedSyncS"
824
  CS_PAUSEDSYNCT = "PausedSyncT"
825
  CSET_SYNC = frozenset([
826
    CS_WFREPORTPARAMS,
827
    CS_STARTINGSYNCS,
828
    CS_STARTINGSYNCT,
829
    CS_WFBITMAPS,
830
    CS_WFBITMAPT,
831
    CS_WFSYNCUUID,
832
    CS_SYNCSOURCE,
833
    CS_SYNCTARGET,
834
    CS_PAUSEDSYNCS,
835
    CS_PAUSEDSYNCT,
836
    ])
837

    
838
  DS_DISKLESS = "Diskless"
839
  DS_ATTACHING = "Attaching" # transient state
840
  DS_FAILED = "Failed" # transient state, next: diskless
841
  DS_NEGOTIATING = "Negotiating" # transient state
842
  DS_INCONSISTENT = "Inconsistent" # while syncing or after creation
843
  DS_OUTDATED = "Outdated"
844
  DS_DUNKNOWN = "DUnknown" # shown for peer disk when not connected
845
  DS_CONSISTENT = "Consistent"
846
  DS_UPTODATE = "UpToDate" # normal state
847

    
848
  RO_PRIMARY = "Primary"
849
  RO_SECONDARY = "Secondary"
850
  RO_UNKNOWN = "Unknown"
851

    
852
  def __init__(self, procline):
853
    u = self.UNCONF_RE.match(procline)
854
    if u:
855
      self.cstatus = self.CS_UNCONFIGURED
856
      self.lrole = self.rrole = self.ldisk = self.rdisk = None
857
    else:
858
      m = self.LINE_RE.match(procline)
859
      if not m:
860
        raise errors.BlockDeviceError("Can't parse input data '%s'" % procline)
861
      self.cstatus = m.group(1)
862
      self.lrole = m.group(2)
863
      self.rrole = m.group(3)
864
      self.ldisk = m.group(4)
865
      self.rdisk = m.group(5)
866

    
867
    # end reading of data from the LINE_RE or UNCONF_RE
868

    
869
    self.is_standalone = self.cstatus == self.CS_STANDALONE
870
    self.is_wfconn = self.cstatus == self.CS_WFCONNECTION
871
    self.is_connected = self.cstatus == self.CS_CONNECTED
872
    self.is_primary = self.lrole == self.RO_PRIMARY
873
    self.is_secondary = self.lrole == self.RO_SECONDARY
874
    self.peer_primary = self.rrole == self.RO_PRIMARY
875
    self.peer_secondary = self.rrole == self.RO_SECONDARY
876
    self.both_primary = self.is_primary and self.peer_primary
877
    self.both_secondary = self.is_secondary and self.peer_secondary
878

    
879
    self.is_diskless = self.ldisk == self.DS_DISKLESS
880
    self.is_disk_uptodate = self.ldisk == self.DS_UPTODATE
881

    
882
    self.is_in_resync = self.cstatus in self.CSET_SYNC
883
    self.is_in_use = self.cstatus != self.CS_UNCONFIGURED
884

    
885
    m = self.SYNC_RE.match(procline)
886
    if m:
887
      self.sync_percent = float(m.group(1))
888
      hours = int(m.group(2))
889
      minutes = int(m.group(3))
890
      seconds = int(m.group(4))
891
      self.est_time = hours * 3600 + minutes * 60 + seconds
892
    else:
893
      # we have (in this if branch) no percent information, but if
894
      # we're resyncing we need to 'fake' a sync percent information,
895
      # as this is how cmdlib determines if it makes sense to wait for
896
      # resyncing or not
897
      if self.is_in_resync:
898
        self.sync_percent = 0
899
      else:
900
        self.sync_percent = None
901
      self.est_time = None
902

    
903

    
904
class BaseDRBD(BlockDev): # pylint: disable=W0223
905
  """Base DRBD class.
906

907
  This class contains a few bits of common functionality between the
908
  0.7 and 8.x versions of DRBD.
909

910
  """
911
  _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)(?:\.\d+)?"
912
                           r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
913
  _VALID_LINE_RE = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
914
  _UNUSED_LINE_RE = re.compile("^ *([0-9]+): cs:Unconfigured$")
915

    
916
  _DRBD_MAJOR = 147
917
  _ST_UNCONFIGURED = "Unconfigured"
918
  _ST_WFCONNECTION = "WFConnection"
919
  _ST_CONNECTED = "Connected"
920

    
921
  _STATUS_FILE = "/proc/drbd"
922
  _USERMODE_HELPER_FILE = "/sys/module/drbd/parameters/usermode_helper"
923

    
924
  @staticmethod
925
  def _GetProcData(filename=_STATUS_FILE):
926
    """Return data from /proc/drbd.
927

928
    """
929
    try:
930
      data = utils.ReadFile(filename).splitlines()
931
    except EnvironmentError, err:
932
      if err.errno == errno.ENOENT:
933
        _ThrowError("The file %s cannot be opened, check if the module"
934
                    " is loaded (%s)", filename, str(err))
935
      else:
936
        _ThrowError("Can't read the DRBD proc file %s: %s", filename, str(err))
937
    if not data:
938
      _ThrowError("Can't read any data from %s", filename)
939
    return data
940

    
941
  @classmethod
942
  def _MassageProcData(cls, data):
943
    """Transform the output of _GetProdData into a nicer form.
944

945
    @return: a dictionary of minor: joined lines from /proc/drbd
946
        for that minor
947

948
    """
949
    results = {}
950
    old_minor = old_line = None
951
    for line in data:
952
      if not line: # completely empty lines, as can be returned by drbd8.0+
953
        continue
954
      lresult = cls._VALID_LINE_RE.match(line)
955
      if lresult is not None:
956
        if old_minor is not None:
957
          results[old_minor] = old_line
958
        old_minor = int(lresult.group(1))
959
        old_line = line
960
      else:
961
        if old_minor is not None:
962
          old_line += " " + line.strip()
963
    # add last line
964
    if old_minor is not None:
965
      results[old_minor] = old_line
966
    return results
967

    
968
  @classmethod
969
  def _GetVersion(cls, proc_data):
970
    """Return the DRBD version.
971

972
    This will return a dict with keys:
973
      - k_major
974
      - k_minor
975
      - k_point
976
      - api
977
      - proto
978
      - proto2 (only on drbd > 8.2.X)
979

980
    """
981
    first_line = proc_data[0].strip()
982
    version = cls._VERSION_RE.match(first_line)
983
    if not version:
984
      raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
985
                                    first_line)
986

    
987
    values = version.groups()
988
    retval = {"k_major": int(values[0]),
989
              "k_minor": int(values[1]),
990
              "k_point": int(values[2]),
991
              "api": int(values[3]),
992
              "proto": int(values[4]),
993
             }
994
    if values[5] is not None:
995
      retval["proto2"] = values[5]
996

    
997
    return retval
998

    
999
  @staticmethod
1000
  def GetUsermodeHelper(filename=_USERMODE_HELPER_FILE):
1001
    """Returns DRBD usermode_helper currently set.
1002

1003
    """
1004
    try:
1005
      helper = utils.ReadFile(filename).splitlines()[0]
1006
    except EnvironmentError, err:
1007
      if err.errno == errno.ENOENT:
1008
        _ThrowError("The file %s cannot be opened, check if the module"
1009
                    " is loaded (%s)", filename, str(err))
1010
      else:
1011
        _ThrowError("Can't read DRBD helper file %s: %s", filename, str(err))
1012
    if not helper:
1013
      _ThrowError("Can't read any data from %s", filename)
1014
    return helper
1015

    
1016
  @staticmethod
1017
  def _DevPath(minor):
1018
    """Return the path to a drbd device for a given minor.
1019

1020
    """
1021
    return "/dev/drbd%d" % minor
1022

    
1023
  @classmethod
1024
  def GetUsedDevs(cls):
1025
    """Compute the list of used DRBD devices.
1026

1027
    """
1028
    data = cls._GetProcData()
1029

    
1030
    used_devs = {}
1031
    for line in data:
1032
      match = cls._VALID_LINE_RE.match(line)
1033
      if not match:
1034
        continue
1035
      minor = int(match.group(1))
1036
      state = match.group(2)
1037
      if state == cls._ST_UNCONFIGURED:
1038
        continue
1039
      used_devs[minor] = state, line
1040

    
1041
    return used_devs
1042

    
1043
  def _SetFromMinor(self, minor):
1044
    """Set our parameters based on the given minor.
1045

1046
    This sets our minor variable and our dev_path.
1047

1048
    """
1049
    if minor is None:
1050
      self.minor = self.dev_path = None
1051
      self.attached = False
1052
    else:
1053
      self.minor = minor
1054
      self.dev_path = self._DevPath(minor)
1055
      self.attached = True
1056

    
1057
  @staticmethod
1058
  def _CheckMetaSize(meta_device):
1059
    """Check if the given meta device looks like a valid one.
1060

1061
    This currently only check the size, which must be around
1062
    128MiB.
1063

1064
    """
1065
    result = utils.RunCmd(["blockdev", "--getsize", meta_device])
1066
    if result.failed:
1067
      _ThrowError("Failed to get device size: %s - %s",
1068
                  result.fail_reason, result.output)
1069
    try:
1070
      sectors = int(result.stdout)
1071
    except (TypeError, ValueError):
1072
      _ThrowError("Invalid output from blockdev: '%s'", result.stdout)
1073
    num_bytes = sectors * 512
1074
    if num_bytes < 128 * 1024 * 1024: # less than 128MiB
1075
      _ThrowError("Meta device too small (%.2fMib)", (num_bytes / 1024 / 1024))
1076
    # the maximum *valid* size of the meta device when living on top
1077
    # of LVM is hard to compute: it depends on the number of stripes
1078
    # and the PE size; e.g. a 2-stripe, 64MB PE will result in a 128MB
1079
    # (normal size), but an eight-stripe 128MB PE will result in a 1GB
1080
    # size meta device; as such, we restrict it to 1GB (a little bit
1081
    # too generous, but making assumptions about PE size is hard)
1082
    if num_bytes > 1024 * 1024 * 1024:
1083
      _ThrowError("Meta device too big (%.2fMiB)", (num_bytes / 1024 / 1024))
1084

    
1085
  def Rename(self, new_id):
1086
    """Rename a device.
1087

1088
    This is not supported for drbd devices.
1089

1090
    """
1091
    raise errors.ProgrammerError("Can't rename a drbd device")
1092

    
1093

    
1094
class DRBD8(BaseDRBD):
1095
  """DRBD v8.x block device.
1096

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

1101
  The unique_id for the drbd device is the (local_ip, local_port,
1102
  remote_ip, remote_port) tuple, and it must have two children: the
1103
  data device and the meta_device. The meta device is checked for
1104
  valid size and is zeroed on create.
1105

1106
  """
1107
  _MAX_MINORS = 255
1108
  _PARSE_SHOW = None
1109

    
1110
  # timeout constants
1111
  _NET_RECONFIG_TIMEOUT = 60
1112

    
1113
  # command line options for barriers
1114
  _DISABLE_DISK_OPTION = "--no-disk-barrier"  # -a
1115
  _DISABLE_DRAIN_OPTION = "--no-disk-drain"   # -D
1116
  _DISABLE_FLUSH_OPTION = "--no-disk-flushes" # -i
1117
  _DISABLE_META_FLUSH_OPTION = "--no-md-flushes"  # -m
1118

    
1119
  def __init__(self, unique_id, children, size, params):
1120
    if children and children.count(None) > 0:
1121
      children = []
1122
    if len(children) not in (0, 2):
1123
      raise ValueError("Invalid configuration data %s" % str(children))
1124
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6:
1125
      raise ValueError("Invalid configuration data %s" % str(unique_id))
1126
    (self._lhost, self._lport,
1127
     self._rhost, self._rport,
1128
     self._aminor, self._secret) = unique_id
1129
    if children:
1130
      if not _CanReadDevice(children[1].dev_path):
1131
        logging.info("drbd%s: Ignoring unreadable meta device", self._aminor)
1132
        children = []
1133
    super(DRBD8, self).__init__(unique_id, children, size, params)
1134
    self.major = self._DRBD_MAJOR
1135
    version = self._GetVersion(self._GetProcData())
1136
    if version["k_major"] != 8:
1137
      _ThrowError("Mismatch in DRBD kernel version and requested ganeti"
1138
                  " usage: kernel is %s.%s, ganeti wants 8.x",
1139
                  version["k_major"], version["k_minor"])
1140

    
1141
    if (self._lhost is not None and self._lhost == self._rhost and
1142
        self._lport == self._rport):
1143
      raise ValueError("Invalid configuration data, same local/remote %s" %
1144
                       (unique_id,))
1145
    self.Attach()
1146

    
1147
  @classmethod
1148
  def _InitMeta(cls, minor, dev_path):
1149
    """Initialize a meta device.
1150

1151
    This will not work if the given minor is in use.
1152

1153
    """
1154
    # Zero the metadata first, in order to make sure drbdmeta doesn't
1155
    # try to auto-detect existing filesystems or similar (see
1156
    # http://code.google.com/p/ganeti/issues/detail?id=182); we only
1157
    # care about the first 128MB of data in the device, even though it
1158
    # can be bigger
1159
    result = utils.RunCmd([constants.DD_CMD,
1160
                           "if=/dev/zero", "of=%s" % dev_path,
1161
                           "bs=1048576", "count=128", "oflag=direct"])
1162
    if result.failed:
1163
      _ThrowError("Can't wipe the meta device: %s", result.output)
1164

    
1165
    result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
1166
                           "v08", dev_path, "0", "create-md"])
1167
    if result.failed:
1168
      _ThrowError("Can't initialize meta device: %s", result.output)
1169

    
1170
  @classmethod
1171
  def _FindUnusedMinor(cls):
1172
    """Find an unused DRBD device.
1173

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

1177
    """
1178
    data = cls._GetProcData()
1179

    
1180
    highest = None
1181
    for line in data:
1182
      match = cls._UNUSED_LINE_RE.match(line)
1183
      if match:
1184
        return int(match.group(1))
1185
      match = cls._VALID_LINE_RE.match(line)
1186
      if match:
1187
        minor = int(match.group(1))
1188
        highest = max(highest, minor)
1189
    if highest is None: # there are no minors in use at all
1190
      return 0
1191
    if highest >= cls._MAX_MINORS:
1192
      logging.error("Error: no free drbd minors!")
1193
      raise errors.BlockDeviceError("Can't find a free DRBD minor")
1194
    return highest + 1
1195

    
1196
  @classmethod
1197
  def _GetShowParser(cls):
1198
    """Return a parser for `drbd show` output.
1199

1200
    This will either create or return an already-create parser for the
1201
    output of the command `drbd show`.
1202

1203
    """
1204
    if cls._PARSE_SHOW is not None:
1205
      return cls._PARSE_SHOW
1206

    
1207
    # pyparsing setup
1208
    lbrace = pyp.Literal("{").suppress()
1209
    rbrace = pyp.Literal("}").suppress()
1210
    lbracket = pyp.Literal("[").suppress()
1211
    rbracket = pyp.Literal("]").suppress()
1212
    semi = pyp.Literal(";").suppress()
1213
    colon = pyp.Literal(":").suppress()
1214
    # this also converts the value to an int
1215
    number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
1216

    
1217
    comment = pyp.Literal("#") + pyp.Optional(pyp.restOfLine)
1218
    defa = pyp.Literal("_is_default").suppress()
1219
    dbl_quote = pyp.Literal('"').suppress()
1220

    
1221
    keyword = pyp.Word(pyp.alphanums + "-")
1222

    
1223
    # value types
1224
    value = pyp.Word(pyp.alphanums + "_-/.:")
1225
    quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1226
    ipv4_addr = (pyp.Optional(pyp.Literal("ipv4")).suppress() +
1227
                 pyp.Word(pyp.nums + ".") + colon + number)
1228
    ipv6_addr = (pyp.Optional(pyp.Literal("ipv6")).suppress() +
1229
                 pyp.Optional(lbracket) + pyp.Word(pyp.hexnums + ":") +
1230
                 pyp.Optional(rbracket) + colon + number)
1231
    # meta device, extended syntax
1232
    meta_value = ((value ^ quoted) + lbracket + number + rbracket)
1233
    # device name, extended syntax
1234
    device_value = pyp.Literal("minor").suppress() + number
1235

    
1236
    # a statement
1237
    stmt = (~rbrace + keyword + ~lbrace +
1238
            pyp.Optional(ipv4_addr ^ ipv6_addr ^ value ^ quoted ^ meta_value ^
1239
                         device_value) +
1240
            pyp.Optional(defa) + semi +
1241
            pyp.Optional(pyp.restOfLine).suppress())
1242

    
1243
    # an entire section
1244
    section_name = pyp.Word(pyp.alphas + "_")
1245
    section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1246

    
1247
    bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1248
    bnf.ignore(comment)
1249

    
1250
    cls._PARSE_SHOW = bnf
1251

    
1252
    return bnf
1253

    
1254
  @classmethod
1255
  def _GetShowData(cls, minor):
1256
    """Return the `drbdsetup show` data for a minor.
1257

1258
    """
1259
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1260
    if result.failed:
1261
      logging.error("Can't display the drbd config: %s - %s",
1262
                    result.fail_reason, result.output)
1263
      return None
1264
    return result.stdout
1265

    
1266
  @classmethod
1267
  def _GetDevInfo(cls, out):
1268
    """Parse details about a given DRBD minor.
1269

1270
    This return, if available, the local backing device (as a path)
1271
    and the local and remote (ip, port) information from a string
1272
    containing the output of the `drbdsetup show` command as returned
1273
    by _GetShowData.
1274

1275
    """
1276
    data = {}
1277
    if not out:
1278
      return data
1279

    
1280
    bnf = cls._GetShowParser()
1281
    # run pyparse
1282

    
1283
    try:
1284
      results = bnf.parseString(out)
1285
    except pyp.ParseException, err:
1286
      _ThrowError("Can't parse drbdsetup show output: %s", str(err))
1287

    
1288
    # and massage the results into our desired format
1289
    for section in results:
1290
      sname = section[0]
1291
      if sname == "_this_host":
1292
        for lst in section[1:]:
1293
          if lst[0] == "disk":
1294
            data["local_dev"] = lst[1]
1295
          elif lst[0] == "meta-disk":
1296
            data["meta_dev"] = lst[1]
1297
            data["meta_index"] = lst[2]
1298
          elif lst[0] == "address":
1299
            data["local_addr"] = tuple(lst[1:])
1300
      elif sname == "_remote_host":
1301
        for lst in section[1:]:
1302
          if lst[0] == "address":
1303
            data["remote_addr"] = tuple(lst[1:])
1304
    return data
1305

    
1306
  def _MatchesLocal(self, info):
1307
    """Test if our local config matches with an existing device.
1308

1309
    The parameter should be as returned from `_GetDevInfo()`. This
1310
    method tests if our local backing device is the same as the one in
1311
    the info parameter, in effect testing if we look like the given
1312
    device.
1313

1314
    """
1315
    if self._children:
1316
      backend, meta = self._children
1317
    else:
1318
      backend = meta = None
1319

    
1320
    if backend is not None:
1321
      retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1322
    else:
1323
      retval = ("local_dev" not in info)
1324

    
1325
    if meta is not None:
1326
      retval = retval and ("meta_dev" in info and
1327
                           info["meta_dev"] == meta.dev_path)
1328
      retval = retval and ("meta_index" in info and
1329
                           info["meta_index"] == 0)
1330
    else:
1331
      retval = retval and ("meta_dev" not in info and
1332
                           "meta_index" not in info)
1333
    return retval
1334

    
1335
  def _MatchesNet(self, info):
1336
    """Test if our network config matches with an existing device.
1337

1338
    The parameter should be as returned from `_GetDevInfo()`. This
1339
    method tests if our network configuration is the same as the one
1340
    in the info parameter, in effect testing if we look like the given
1341
    device.
1342

1343
    """
1344
    if (((self._lhost is None and not ("local_addr" in info)) and
1345
         (self._rhost is None and not ("remote_addr" in info)))):
1346
      return True
1347

    
1348
    if self._lhost is None:
1349
      return False
1350

    
1351
    if not ("local_addr" in info and
1352
            "remote_addr" in info):
1353
      return False
1354

    
1355
    retval = (info["local_addr"] == (self._lhost, self._lport))
1356
    retval = (retval and
1357
              info["remote_addr"] == (self._rhost, self._rport))
1358
    return retval
1359

    
1360
  def _AssembleLocal(self, minor, backend, meta, size):
1361
    """Configure the local part of a DRBD device.
1362

1363
    """
1364
    args = ["drbdsetup", self._DevPath(minor), "disk",
1365
            backend, meta, "0",
1366
            "-e", "detach",
1367
            "--create-device"]
1368
    if size:
1369
      args.extend(["-d", "%sm" % size])
1370

    
1371
    version = self._GetVersion(self._GetProcData())
1372
    vmaj = version["k_major"]
1373
    vmin = version["k_minor"]
1374
    vrel = version["k_point"]
1375

    
1376
    barrier_args = \
1377
      self._ComputeDiskBarrierArgs(vmaj, vmin, vrel,
1378
                                   self.params[constants.LDP_BARRIERS],
1379
                                   self.params[constants.LDP_NO_META_FLUSH])
1380
    args.extend(barrier_args)
1381

    
1382
    if self.params[constants.LDP_DISK_CUSTOM]:
1383
      args.extend(shlex.split(self.params[constants.LDP_DISK_CUSTOM]))
1384

    
1385
    result = utils.RunCmd(args)
1386
    if result.failed:
1387
      _ThrowError("drbd%d: can't attach local disk: %s", minor, result.output)
1388

    
1389
  @classmethod
1390
  def _ComputeDiskBarrierArgs(cls, vmaj, vmin, vrel, disabled_barriers,
1391
      disable_meta_flush):
1392
    """Compute the DRBD command line parameters for disk barriers
1393

1394
    Returns a list of the disk barrier parameters as requested via the
1395
    disabled_barriers and disable_meta_flush arguments, and according to the
1396
    supported ones in the DRBD version vmaj.vmin.vrel
1397

1398
    If the desired option is unsupported, raises errors.BlockDeviceError.
1399

1400
    """
1401
    disabled_barriers_set = frozenset(disabled_barriers)
1402
    if not disabled_barriers_set in constants.DRBD_VALID_BARRIER_OPT:
1403
      raise errors.BlockDeviceError("%s is not a valid option set for DRBD"
1404
                                    " barriers" % disabled_barriers)
1405

    
1406
    args = []
1407

    
1408
    # The following code assumes DRBD 8.x, with x < 4 and x != 1 (DRBD 8.1.x
1409
    # does not exist)
1410
    if not vmaj == 8 and vmin in (0, 2, 3):
1411
      raise errors.BlockDeviceError("Unsupported DRBD version: %d.%d.%d" %
1412
                                    (vmaj, vmin, vrel))
1413

    
1414
    def _AppendOrRaise(option, min_version):
1415
      """Helper for DRBD options"""
1416
      if min_version is not None and vrel >= min_version:
1417
        args.append(option)
1418
      else:
1419
        raise errors.BlockDeviceError("Could not use the option %s as the"
1420
                                      " DRBD version %d.%d.%d does not support"
1421
                                      " it." % (option, vmaj, vmin, vrel))
1422

    
1423
    # the minimum version for each feature is encoded via pairs of (minor
1424
    # version -> x) where x is version in which support for the option was
1425
    # introduced.
1426
    meta_flush_supported = disk_flush_supported = {
1427
      0: 12,
1428
      2: 7,
1429
      3: 0,
1430
      }
1431

    
1432
    disk_drain_supported = {
1433
      2: 7,
1434
      3: 0,
1435
      }
1436

    
1437
    disk_barriers_supported = {
1438
      3: 0,
1439
      }
1440

    
1441
    # meta flushes
1442
    if disable_meta_flush:
1443
      _AppendOrRaise(cls._DISABLE_META_FLUSH_OPTION,
1444
                     meta_flush_supported.get(vmin, None))
1445

    
1446
    # disk flushes
1447
    if constants.DRBD_B_DISK_FLUSH in disabled_barriers_set:
1448
      _AppendOrRaise(cls._DISABLE_FLUSH_OPTION,
1449
                     disk_flush_supported.get(vmin, None))
1450

    
1451
    # disk drain
1452
    if constants.DRBD_B_DISK_DRAIN in disabled_barriers_set:
1453
      _AppendOrRaise(cls._DISABLE_DRAIN_OPTION,
1454
                     disk_drain_supported.get(vmin, None))
1455

    
1456
    # disk barriers
1457
    if constants.DRBD_B_DISK_BARRIERS in disabled_barriers_set:
1458
      _AppendOrRaise(cls._DISABLE_DISK_OPTION,
1459
                     disk_barriers_supported.get(vmin, None))
1460

    
1461
    return args
1462

    
1463
  def _AssembleNet(self, minor, net_info, protocol,
1464
                   dual_pri=False, hmac=None, secret=None):
1465
    """Configure the network part of the device.
1466

1467
    """
1468
    lhost, lport, rhost, rport = net_info
1469
    if None in net_info:
1470
      # we don't want network connection and actually want to make
1471
      # sure its shutdown
1472
      self._ShutdownNet(minor)
1473
      return
1474

    
1475
    # Workaround for a race condition. When DRBD is doing its dance to
1476
    # establish a connection with its peer, it also sends the
1477
    # synchronization speed over the wire. In some cases setting the
1478
    # sync speed only after setting up both sides can race with DRBD
1479
    # connecting, hence we set it here before telling DRBD anything
1480
    # about its peer.
1481
    sync_errors = self._SetMinorSyncParams(minor, self.params)
1482
    if sync_errors:
1483
      _ThrowError("drbd%d: can't set the synchronization parameters: %s" %
1484
                  (minor, utils.CommaJoin(sync_errors)))
1485

    
1486
    if netutils.IP6Address.IsValid(lhost):
1487
      if not netutils.IP6Address.IsValid(rhost):
1488
        _ThrowError("drbd%d: can't connect ip %s to ip %s" %
1489
                    (minor, lhost, rhost))
1490
      family = "ipv6"
1491
    elif netutils.IP4Address.IsValid(lhost):
1492
      if not netutils.IP4Address.IsValid(rhost):
1493
        _ThrowError("drbd%d: can't connect ip %s to ip %s" %
1494
                    (minor, lhost, rhost))
1495
      family = "ipv4"
1496
    else:
1497
      _ThrowError("drbd%d: Invalid ip %s" % (minor, lhost))
1498

    
1499
    args = ["drbdsetup", self._DevPath(minor), "net",
1500
            "%s:%s:%s" % (family, lhost, lport),
1501
            "%s:%s:%s" % (family, rhost, rport), protocol,
1502
            "-A", "discard-zero-changes",
1503
            "-B", "consensus",
1504
            "--create-device",
1505
            ]
1506
    if dual_pri:
1507
      args.append("-m")
1508
    if hmac and secret:
1509
      args.extend(["-a", hmac, "-x", secret])
1510

    
1511
    if self.params[constants.LDP_NET_CUSTOM]:
1512
      args.extend(shlex.split(self.params[constants.LDP_NET_CUSTOM]))
1513

    
1514
    result = utils.RunCmd(args)
1515
    if result.failed:
1516
      _ThrowError("drbd%d: can't setup network: %s - %s",
1517
                  minor, result.fail_reason, result.output)
1518

    
1519
    def _CheckNetworkConfig():
1520
      info = self._GetDevInfo(self._GetShowData(minor))
1521
      if not "local_addr" in info or not "remote_addr" in info:
1522
        raise utils.RetryAgain()
1523

    
1524
      if (info["local_addr"] != (lhost, lport) or
1525
          info["remote_addr"] != (rhost, rport)):
1526
        raise utils.RetryAgain()
1527

    
1528
    try:
1529
      utils.Retry(_CheckNetworkConfig, 1.0, 10.0)
1530
    except utils.RetryTimeout:
1531
      _ThrowError("drbd%d: timeout while configuring network", minor)
1532

    
1533
  def AddChildren(self, devices):
1534
    """Add a disk to the DRBD device.
1535

1536
    """
1537
    if self.minor is None:
1538
      _ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
1539
                  self._aminor)
1540
    if len(devices) != 2:
1541
      _ThrowError("drbd%d: need two devices for AddChildren", self.minor)
1542
    info = self._GetDevInfo(self._GetShowData(self.minor))
1543
    if "local_dev" in info:
1544
      _ThrowError("drbd%d: already attached to a local disk", self.minor)
1545
    backend, meta = devices
1546
    if backend.dev_path is None or meta.dev_path is None:
1547
      _ThrowError("drbd%d: children not ready during AddChildren", self.minor)
1548
    backend.Open()
1549
    meta.Open()
1550
    self._CheckMetaSize(meta.dev_path)
1551
    self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1552

    
1553
    self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
1554
    self._children = devices
1555

    
1556
  def RemoveChildren(self, devices):
1557
    """Detach the drbd device from local storage.
1558

1559
    """
1560
    if self.minor is None:
1561
      _ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
1562
                  self._aminor)
1563
    # early return if we don't actually have backing storage
1564
    info = self._GetDevInfo(self._GetShowData(self.minor))
1565
    if "local_dev" not in info:
1566
      return
1567
    if len(self._children) != 2:
1568
      _ThrowError("drbd%d: we don't have two children: %s", self.minor,
1569
                  self._children)
1570
    if self._children.count(None) == 2: # we don't actually have children :)
1571
      logging.warning("drbd%d: requested detach while detached", self.minor)
1572
      return
1573
    if len(devices) != 2:
1574
      _ThrowError("drbd%d: we need two children in RemoveChildren", self.minor)
1575
    for child, dev in zip(self._children, devices):
1576
      if dev != child.dev_path:
1577
        _ThrowError("drbd%d: mismatch in local storage (%s != %s) in"
1578
                    " RemoveChildren", self.minor, dev, child.dev_path)
1579

    
1580
    self._ShutdownLocal(self.minor)
1581
    self._children = []
1582

    
1583
  @classmethod
1584
  def _SetMinorSyncParams(cls, minor, params):
1585
    """Set the parameters of the DRBD syncer.
1586

1587
    This is the low-level implementation.
1588

1589
    @type minor: int
1590
    @param minor: the drbd minor whose settings we change
1591
    @type params: dict
1592
    @param params: LD level disk parameters related to the synchronization
1593
    @rtype: list
1594
    @return: a list of error messages
1595

1596
    """
1597

    
1598
    args = ["drbdsetup", cls._DevPath(minor), "syncer"]
1599
    if params[constants.LDP_DYNAMIC_RESYNC]:
1600
      version = cls._GetVersion(cls._GetProcData())
1601
      vmin = version["k_minor"]
1602
      vrel = version["k_point"]
1603

    
1604
      # By definition we are using 8.x, so just check the rest of the version
1605
      # number
1606
      if vmin != 3 or vrel < 9:
1607
        msg = ("The current DRBD version (8.%d.%d) does not support the "
1608
               "dynamic resync speed controller" % (vmin, vrel))
1609
        logging.error(msg)
1610
        return [msg]
1611

    
1612
      if params[constants.LDP_PLAN_AHEAD] == 0:
1613
        msg = ("A value of 0 for c-plan-ahead disables the dynamic sync speed"
1614
               " controller at DRBD level. If you want to disable it, please"
1615
               " set the dynamic-resync disk parameter to False.")
1616
        logging.error(msg)
1617
        return [msg]
1618

    
1619
      # add the c-* parameters to args
1620
      args.extend(["--c-plan-ahead", params[constants.LDP_PLAN_AHEAD],
1621
                   "--c-fill-target", params[constants.LDP_FILL_TARGET],
1622
                   "--c-delay-target", params[constants.LDP_DELAY_TARGET],
1623
                   "--c-max-rate", params[constants.LDP_MAX_RATE],
1624
                   "--c-min-rate", params[constants.LDP_MIN_RATE],
1625
                  ])
1626

    
1627
    else:
1628
      args.extend(["-r", "%d" % params[constants.LDP_RESYNC_RATE]])
1629

    
1630
    args.append("--create-device")
1631
    result = utils.RunCmd(args)
1632
    if result.failed:
1633
      msg = ("Can't change syncer rate: %s - %s" %
1634
             (result.fail_reason, result.output))
1635
      logging.error(msg)
1636
      return msg
1637

    
1638
    return []
1639

    
1640
  def SetSyncParams(self, params):
1641
    """Set the synchronization parameters of the DRBD syncer.
1642

1643
    @type params: dict
1644
    @param params: LD level disk parameters related to the synchronization
1645
    @rtype: list
1646
    @return: a list of error messages, emitted both by the current node and by
1647
    children. An empty list means no errors
1648

1649
    """
1650
    if self.minor is None:
1651
      err = "Not attached during SetSyncParams"
1652
      logging.info(err)
1653
      return [err]
1654

    
1655
    children_result = super(DRBD8, self).SetSyncParams(params)
1656
    children_result.extend(self._SetMinorSyncParams(self.minor, params))
1657
    return children_result
1658

    
1659
  def PauseResumeSync(self, pause):
1660
    """Pauses or resumes the sync of a DRBD device.
1661

1662
    @param pause: Wether to pause or resume
1663
    @return: the success of the operation
1664

1665
    """
1666
    if self.minor is None:
1667
      logging.info("Not attached during PauseSync")
1668
      return False
1669

    
1670
    children_result = super(DRBD8, self).PauseResumeSync(pause)
1671

    
1672
    if pause:
1673
      cmd = "pause-sync"
1674
    else:
1675
      cmd = "resume-sync"
1676

    
1677
    result = utils.RunCmd(["drbdsetup", self.dev_path, cmd])
1678
    if result.failed:
1679
      logging.error("Can't %s: %s - %s", cmd,
1680
                    result.fail_reason, result.output)
1681
    return not result.failed and children_result
1682

    
1683
  def GetProcStatus(self):
1684
    """Return device data from /proc.
1685

1686
    """
1687
    if self.minor is None:
1688
      _ThrowError("drbd%d: GetStats() called while not attached", self._aminor)
1689
    proc_info = self._MassageProcData(self._GetProcData())
1690
    if self.minor not in proc_info:
1691
      _ThrowError("drbd%d: can't find myself in /proc", self.minor)
1692
    return DRBD8Status(proc_info[self.minor])
1693

    
1694
  def GetSyncStatus(self):
1695
    """Returns the sync status of the device.
1696

1697

1698
    If sync_percent is None, it means all is ok
1699
    If estimated_time is None, it means we can't estimate
1700
    the time needed, otherwise it's the time left in seconds.
1701

1702

1703
    We set the is_degraded parameter to True on two conditions:
1704
    network not connected or local disk missing.
1705

1706
    We compute the ldisk parameter based on whether we have a local
1707
    disk or not.
1708

1709
    @rtype: objects.BlockDevStatus
1710

1711
    """
1712
    if self.minor is None and not self.Attach():
1713
      _ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
1714

    
1715
    stats = self.GetProcStatus()
1716
    is_degraded = not stats.is_connected or not stats.is_disk_uptodate
1717

    
1718
    if stats.is_disk_uptodate:
1719
      ldisk_status = constants.LDS_OKAY
1720
    elif stats.is_diskless:
1721
      ldisk_status = constants.LDS_FAULTY
1722
    else:
1723
      ldisk_status = constants.LDS_UNKNOWN
1724

    
1725
    return objects.BlockDevStatus(dev_path=self.dev_path,
1726
                                  major=self.major,
1727
                                  minor=self.minor,
1728
                                  sync_percent=stats.sync_percent,
1729
                                  estimated_time=stats.est_time,
1730
                                  is_degraded=is_degraded,
1731
                                  ldisk_status=ldisk_status)
1732

    
1733
  def Open(self, force=False):
1734
    """Make the local state primary.
1735

1736
    If the 'force' parameter is given, the '-o' option is passed to
1737
    drbdsetup. Since this is a potentially dangerous operation, the
1738
    force flag should be only given after creation, when it actually
1739
    is mandatory.
1740

1741
    """
1742
    if self.minor is None and not self.Attach():
1743
      logging.error("DRBD cannot attach to a device during open")
1744
      return False
1745
    cmd = ["drbdsetup", self.dev_path, "primary"]
1746
    if force:
1747
      cmd.append("-o")
1748
    result = utils.RunCmd(cmd)
1749
    if result.failed:
1750
      _ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
1751
                  result.output)
1752

    
1753
  def Close(self):
1754
    """Make the local state secondary.
1755

1756
    This will, of course, fail if the device is in use.
1757

1758
    """
1759
    if self.minor is None and not self.Attach():
1760
      _ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
1761
    result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1762
    if result.failed:
1763
      _ThrowError("drbd%d: can't switch drbd device to secondary: %s",
1764
                  self.minor, result.output)
1765

    
1766
  def DisconnectNet(self):
1767
    """Removes network configuration.
1768

1769
    This method shutdowns the network side of the device.
1770

1771
    The method will wait up to a hardcoded timeout for the device to
1772
    go into standalone after the 'disconnect' command before
1773
    re-configuring it, as sometimes it takes a while for the
1774
    disconnect to actually propagate and thus we might issue a 'net'
1775
    command while the device is still connected. If the device will
1776
    still be attached to the network and we time out, we raise an
1777
    exception.
1778

1779
    """
1780
    if self.minor is None:
1781
      _ThrowError("drbd%d: disk not attached in re-attach net", self._aminor)
1782

    
1783
    if None in (self._lhost, self._lport, self._rhost, self._rport):
1784
      _ThrowError("drbd%d: DRBD disk missing network info in"
1785
                  " DisconnectNet()", self.minor)
1786

    
1787
    class _DisconnectStatus:
1788
      def __init__(self, ever_disconnected):
1789
        self.ever_disconnected = ever_disconnected
1790

    
1791
    dstatus = _DisconnectStatus(_IgnoreError(self._ShutdownNet, self.minor))
1792

    
1793
    def _WaitForDisconnect():
1794
      if self.GetProcStatus().is_standalone:
1795
        return
1796

    
1797
      # retry the disconnect, it seems possible that due to a well-time
1798
      # disconnect on the peer, my disconnect command might be ignored and
1799
      # forgotten
1800
      dstatus.ever_disconnected = \
1801
        _IgnoreError(self._ShutdownNet, self.minor) or dstatus.ever_disconnected
1802

    
1803
      raise utils.RetryAgain()
1804

    
1805
    # Keep start time
1806
    start_time = time.time()
1807

    
1808
    try:
1809
      # Start delay at 100 milliseconds and grow up to 2 seconds
1810
      utils.Retry(_WaitForDisconnect, (0.1, 1.5, 2.0),
1811
                  self._NET_RECONFIG_TIMEOUT)
1812
    except utils.RetryTimeout:
1813
      if dstatus.ever_disconnected:
1814
        msg = ("drbd%d: device did not react to the"
1815
               " 'disconnect' command in a timely manner")
1816
      else:
1817
        msg = "drbd%d: can't shutdown network, even after multiple retries"
1818

    
1819
      _ThrowError(msg, self.minor)
1820

    
1821
    reconfig_time = time.time() - start_time
1822
    if reconfig_time > (self._NET_RECONFIG_TIMEOUT * 0.25):
1823
      logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
1824
                   self.minor, reconfig_time)
1825

    
1826
  def AttachNet(self, multimaster):
1827
    """Reconnects the network.
1828

1829
    This method connects the network side of the device with a
1830
    specified multi-master flag. The device needs to be 'Standalone'
1831
    but have valid network configuration data.
1832

1833
    Args:
1834
      - multimaster: init the network in dual-primary mode
1835

1836
    """
1837
    if self.minor is None:
1838
      _ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
1839

    
1840
    if None in (self._lhost, self._lport, self._rhost, self._rport):
1841
      _ThrowError("drbd%d: missing network info in AttachNet()", self.minor)
1842

    
1843
    status = self.GetProcStatus()
1844

    
1845
    if not status.is_standalone:
1846
      _ThrowError("drbd%d: device is not standalone in AttachNet", self.minor)
1847

    
1848
    self._AssembleNet(self.minor,
1849
                      (self._lhost, self._lport, self._rhost, self._rport),
1850
                      constants.DRBD_NET_PROTOCOL, dual_pri=multimaster,
1851
                      hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1852

    
1853
  def Attach(self):
1854
    """Check if our minor is configured.
1855

1856
    This doesn't do any device configurations - it only checks if the
1857
    minor is in a state different from Unconfigured.
1858

1859
    Note that this function will not change the state of the system in
1860
    any way (except in case of side-effects caused by reading from
1861
    /proc).
1862

1863
    """
1864
    used_devs = self.GetUsedDevs()
1865
    if self._aminor in used_devs:
1866
      minor = self._aminor
1867
    else:
1868
      minor = None
1869

    
1870
    self._SetFromMinor(minor)
1871
    return minor is not None
1872

    
1873
  def Assemble(self):
1874
    """Assemble the drbd.
1875

1876
    Method:
1877
      - if we have a configured device, we try to ensure that it matches
1878
        our config
1879
      - if not, we create it from zero
1880
      - anyway, set the device parameters
1881

1882
    """
1883
    super(DRBD8, self).Assemble()
1884

    
1885
    self.Attach()
1886
    if self.minor is None:
1887
      # local device completely unconfigured
1888
      self._FastAssemble()
1889
    else:
1890
      # we have to recheck the local and network status and try to fix
1891
      # the device
1892
      self._SlowAssemble()
1893

    
1894
    sync_errors = self.SetSyncParams(self.params)
1895
    if sync_errors:
1896
      _ThrowError("drbd%d: can't set the synchronization parameters: %s" %
1897
                  (self.minor, utils.CommaJoin(sync_errors)))
1898

    
1899
  def _SlowAssemble(self):
1900
    """Assembles the DRBD device from a (partially) configured device.
1901

1902
    In case of partially attached (local device matches but no network
1903
    setup), we perform the network attach. If successful, we re-test
1904
    the attach if can return success.
1905

1906
    """
1907
    # TODO: Rewrite to not use a for loop just because there is 'break'
1908
    # pylint: disable=W0631
1909
    net_data = (self._lhost, self._lport, self._rhost, self._rport)
1910
    for minor in (self._aminor,):
1911
      info = self._GetDevInfo(self._GetShowData(minor))
1912
      match_l = self._MatchesLocal(info)
1913
      match_r = self._MatchesNet(info)
1914

    
1915
      if match_l and match_r:
1916
        # everything matches
1917
        break
1918

    
1919
      if match_l and not match_r and "local_addr" not in info:
1920
        # disk matches, but not attached to network, attach and recheck
1921
        self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1922
                          hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1923
        if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1924
          break
1925
        else:
1926
          _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1927
                      " show' disagrees", minor)
1928

    
1929
      if match_r and "local_dev" not in info:
1930
        # no local disk, but network attached and it matches
1931
        self._AssembleLocal(minor, self._children[0].dev_path,
1932
                            self._children[1].dev_path, self.size)
1933
        if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1934
          break
1935
        else:
1936
          _ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
1937
                      " show' disagrees", minor)
1938

    
1939
      # this case must be considered only if we actually have local
1940
      # storage, i.e. not in diskless mode, because all diskless
1941
      # devices are equal from the point of view of local
1942
      # configuration
1943
      if (match_l and "local_dev" in info and
1944
          not match_r and "local_addr" in info):
1945
        # strange case - the device network part points to somewhere
1946
        # else, even though its local storage is ours; as we own the
1947
        # drbd space, we try to disconnect from the remote peer and
1948
        # reconnect to our correct one
1949
        try:
1950
          self._ShutdownNet(minor)
1951
        except errors.BlockDeviceError, err:
1952
          _ThrowError("drbd%d: device has correct local storage, wrong"
1953
                      " remote peer and is unable to disconnect in order"
1954
                      " to attach to the correct peer: %s", minor, str(err))
1955
        # note: _AssembleNet also handles the case when we don't want
1956
        # local storage (i.e. one or more of the _[lr](host|port) is
1957
        # None)
1958
        self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1959
                          hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1960
        if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1961
          break
1962
        else:
1963
          _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1964
                      " show' disagrees", minor)
1965

    
1966
    else:
1967
      minor = None
1968

    
1969
    self._SetFromMinor(minor)
1970
    if minor is None:
1971
      _ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
1972
                  self._aminor)
1973

    
1974
  def _FastAssemble(self):
1975
    """Assemble the drbd device from zero.
1976

1977
    This is run when in Assemble we detect our minor is unused.
1978

1979
    """
1980
    minor = self._aminor
1981
    if self._children and self._children[0] and self._children[1]:
1982
      self._AssembleLocal(minor, self._children[0].dev_path,
1983
                          self._children[1].dev_path, self.size)
1984
    if self._lhost and self._lport and self._rhost and self._rport:
1985
      self._AssembleNet(minor,
1986
                        (self._lhost, self._lport, self._rhost, self._rport),
1987
                        constants.DRBD_NET_PROTOCOL,
1988
                        hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1989
    self._SetFromMinor(minor)
1990

    
1991
  @classmethod
1992
  def _ShutdownLocal(cls, minor):
1993
    """Detach from the local device.
1994

1995
    I/Os will continue to be served from the remote device. If we
1996
    don't have a remote device, this operation will fail.
1997

1998
    """
1999
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
2000
    if result.failed:
2001
      _ThrowError("drbd%d: can't detach local disk: %s", minor, result.output)
2002

    
2003
  @classmethod
2004
  def _ShutdownNet(cls, minor):
2005
    """Disconnect from the remote peer.
2006

2007
    This fails if we don't have a local device.
2008

2009
    """
2010
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
2011
    if result.failed:
2012
      _ThrowError("drbd%d: can't shutdown network: %s", minor, result.output)
2013

    
2014
  @classmethod
2015
  def _ShutdownAll(cls, minor):
2016
    """Deactivate the device.
2017

2018
    This will, of course, fail if the device is in use.
2019

2020
    """
2021
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
2022
    if result.failed:
2023
      _ThrowError("drbd%d: can't shutdown drbd device: %s",
2024
                  minor, result.output)
2025

    
2026
  def Shutdown(self):
2027
    """Shutdown the DRBD device.
2028

2029
    """
2030
    if self.minor is None and not self.Attach():
2031
      logging.info("drbd%d: not attached during Shutdown()", self._aminor)
2032
      return
2033
    minor = self.minor
2034
    self.minor = None
2035
    self.dev_path = None
2036
    self._ShutdownAll(minor)
2037

    
2038
  def Remove(self):
2039
    """Stub remove for DRBD devices.
2040

2041
    """
2042
    self.Shutdown()
2043

    
2044
  @classmethod
2045
  def Create(cls, unique_id, children, size, params):
2046
    """Create a new DRBD8 device.
2047

2048
    Since DRBD devices are not created per se, just assembled, this
2049
    function only initializes the metadata.
2050

2051
    """
2052
    if len(children) != 2:
2053
      raise errors.ProgrammerError("Invalid setup for the drbd device")
2054
    # check that the minor is unused
2055
    aminor = unique_id[4]
2056
    proc_info = cls._MassageProcData(cls._GetProcData())
2057
    if aminor in proc_info:
2058
      status = DRBD8Status(proc_info[aminor])
2059
      in_use = status.is_in_use
2060
    else:
2061
      in_use = False
2062
    if in_use:
2063
      _ThrowError("drbd%d: minor is already in use at Create() time", aminor)
2064
    meta = children[1]
2065
    meta.Assemble()
2066
    if not meta.Attach():
2067
      _ThrowError("drbd%d: can't attach to meta device '%s'",
2068
                  aminor, meta)
2069
    cls._CheckMetaSize(meta.dev_path)
2070
    cls._InitMeta(aminor, meta.dev_path)
2071
    return cls(unique_id, children, size, params)
2072

    
2073
  def Grow(self, amount, dryrun):
2074
    """Resize the DRBD device and its backing storage.
2075

2076
    """
2077
    if self.minor is None:
2078
      _ThrowError("drbd%d: Grow called while not attached", self._aminor)
2079
    if len(self._children) != 2 or None in self._children:
2080
      _ThrowError("drbd%d: cannot grow diskless device", self.minor)
2081
    self._children[0].Grow(amount, dryrun)
2082
    if dryrun:
2083
      # DRBD does not support dry-run mode, so we'll return here
2084
      return
2085
    result = utils.RunCmd(["drbdsetup", self.dev_path, "resize", "-s",
2086
                           "%dm" % (self.size + amount)])
2087
    if result.failed:
2088
      _ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
2089

    
2090

    
2091
class FileStorage(BlockDev):
2092
  """File device.
2093

2094
  This class represents the a file storage backend device.
2095

2096
  The unique_id for the file device is a (file_driver, file_path) tuple.
2097

2098
  """
2099
  def __init__(self, unique_id, children, size, params):
2100
    """Initalizes a file device backend.
2101

2102
    """
2103
    if children:
2104
      raise errors.BlockDeviceError("Invalid setup for file device")
2105
    super(FileStorage, self).__init__(unique_id, children, size, params)
2106
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
2107
      raise ValueError("Invalid configuration data %s" % str(unique_id))
2108
    self.driver = unique_id[0]
2109
    self.dev_path = unique_id[1]
2110
    self.Attach()
2111

    
2112
  def Assemble(self):
2113
    """Assemble the device.
2114

2115
    Checks whether the file device exists, raises BlockDeviceError otherwise.
2116

2117
    """
2118
    if not os.path.exists(self.dev_path):
2119
      _ThrowError("File device '%s' does not exist" % self.dev_path)
2120

    
2121
  def Shutdown(self):
2122
    """Shutdown the device.
2123

2124
    This is a no-op for the file type, as we don't deactivate
2125
    the file on shutdown.
2126

2127
    """
2128
    pass
2129

    
2130
  def Open(self, force=False):
2131
    """Make the device ready for I/O.
2132

2133
    This is a no-op for the file type.
2134

2135
    """
2136
    pass
2137

    
2138
  def Close(self):
2139
    """Notifies that the device will no longer be used for I/O.
2140

2141
    This is a no-op for the file type.
2142

2143
    """
2144
    pass
2145

    
2146
  def Remove(self):
2147
    """Remove the file backing the block device.
2148

2149
    @rtype: boolean
2150
    @return: True if the removal was successful
2151

2152
    """
2153
    try:
2154
      os.remove(self.dev_path)
2155
    except OSError, err:
2156
      if err.errno != errno.ENOENT:
2157
        _ThrowError("Can't remove file '%s': %s", self.dev_path, err)
2158

    
2159
  def Rename(self, new_id):
2160
    """Renames the file.
2161

2162
    """
2163
    # TODO: implement rename for file-based storage
2164
    _ThrowError("Rename is not supported for file-based storage")
2165

    
2166
  def Grow(self, amount, dryrun):
2167
    """Grow the file
2168

2169
    @param amount: the amount (in mebibytes) to grow with
2170

2171
    """
2172
    # Check that the file exists
2173
    self.Assemble()
2174
    current_size = self.GetActualSize()
2175
    new_size = current_size + amount * 1024 * 1024
2176
    assert new_size > current_size, "Cannot Grow with a negative amount"
2177
    # We can't really simulate the growth
2178
    if dryrun:
2179
      return
2180
    try:
2181
      f = open(self.dev_path, "a+")
2182
      f.truncate(new_size)
2183
      f.close()
2184
    except EnvironmentError, err:
2185
      _ThrowError("Error in file growth: %", str(err))
2186

    
2187
  def Attach(self):
2188
    """Attach to an existing file.
2189

2190
    Check if this file already exists.
2191

2192
    @rtype: boolean
2193
    @return: True if file exists
2194

2195
    """
2196
    self.attached = os.path.exists(self.dev_path)
2197
    return self.attached
2198

    
2199
  def GetActualSize(self):
2200
    """Return the actual disk size.
2201

2202
    @note: the device needs to be active when this is called
2203

2204
    """
2205
    assert self.attached, "BlockDevice not attached in GetActualSize()"
2206
    try:
2207
      st = os.stat(self.dev_path)
2208
      return st.st_size
2209
    except OSError, err:
2210
      _ThrowError("Can't stat %s: %s", self.dev_path, err)
2211

    
2212
  @classmethod
2213
  def Create(cls, unique_id, children, size, params):
2214
    """Create a new file.
2215

2216
    @param size: the size of file in MiB
2217

2218
    @rtype: L{bdev.FileStorage}
2219
    @return: an instance of FileStorage
2220

2221
    """
2222
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
2223
      raise ValueError("Invalid configuration data %s" % str(unique_id))
2224
    dev_path = unique_id[1]
2225
    try:
2226
      fd = os.open(dev_path, os.O_RDWR | os.O_CREAT | os.O_EXCL)
2227
      f = os.fdopen(fd, "w")
2228
      f.truncate(size * 1024 * 1024)
2229
      f.close()
2230
    except EnvironmentError, err:
2231
      if err.errno == errno.EEXIST:
2232
        _ThrowError("File already existing: %s", dev_path)
2233
      _ThrowError("Error in file creation: %", str(err))
2234

    
2235
    return FileStorage(unique_id, children, size, params)
2236

    
2237

    
2238
class PersistentBlockDevice(BlockDev):
2239
  """A block device with persistent node
2240

2241
  May be either directly attached, or exposed through DM (e.g. dm-multipath).
2242
  udev helpers are probably required to give persistent, human-friendly
2243
  names.
2244

2245
  For the time being, pathnames are required to lie under /dev.
2246

2247
  """
2248
  def __init__(self, unique_id, children, size, params):
2249
    """Attaches to a static block device.
2250

2251
    The unique_id is a path under /dev.
2252

2253
    """
2254
    super(PersistentBlockDevice, self).__init__(unique_id, children, size,
2255
                                                params)
2256
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
2257
      raise ValueError("Invalid configuration data %s" % str(unique_id))
2258
    self.dev_path = unique_id[1]
2259
    if not os.path.realpath(self.dev_path).startswith("/dev/"):
2260
      raise ValueError("Full path '%s' lies outside /dev" %
2261
                              os.path.realpath(self.dev_path))
2262
    # TODO: this is just a safety guard checking that we only deal with devices
2263
    # we know how to handle. In the future this will be integrated with
2264
    # external storage backends and possible values will probably be collected
2265
    # from the cluster configuration.
2266
    if unique_id[0] != constants.BLOCKDEV_DRIVER_MANUAL:
2267
      raise ValueError("Got persistent block device of invalid type: %s" %
2268
                       unique_id[0])
2269

    
2270
    self.major = self.minor = None
2271
    self.Attach()
2272

    
2273
  @classmethod
2274
  def Create(cls, unique_id, children, size, params):
2275
    """Create a new device
2276

2277
    This is a noop, we only return a PersistentBlockDevice instance
2278

2279
    """
2280
    return PersistentBlockDevice(unique_id, children, 0, params)
2281

    
2282
  def Remove(self):
2283
    """Remove a device
2284

2285
    This is a noop
2286

2287
    """
2288
    pass
2289

    
2290
  def Rename(self, new_id):
2291
    """Rename this device.
2292

2293
    """
2294
    _ThrowError("Rename is not supported for PersistentBlockDev storage")
2295

    
2296
  def Attach(self):
2297
    """Attach to an existing block device.
2298

2299

2300
    """
2301
    self.attached = False
2302
    try:
2303
      st = os.stat(self.dev_path)
2304
    except OSError, err:
2305
      logging.error("Error stat()'ing %s: %s", self.dev_path, str(err))
2306
      return False
2307

    
2308
    if not stat.S_ISBLK(st.st_mode):
2309
      logging.error("%s is not a block device", self.dev_path)
2310
      return False
2311

    
2312
    self.major = os.major(st.st_rdev)
2313
    self.minor = os.minor(st.st_rdev)
2314
    self.attached = True
2315

    
2316
    return True
2317

    
2318
  def Assemble(self):
2319
    """Assemble the device.
2320

2321
    """
2322
    pass
2323

    
2324
  def Shutdown(self):
2325
    """Shutdown the device.
2326

2327
    """
2328
    pass
2329

    
2330
  def Open(self, force=False):
2331
    """Make the device ready for I/O.
2332

2333
    """
2334
    pass
2335

    
2336
  def Close(self):
2337
    """Notifies that the device will no longer be used for I/O.
2338

2339
    """
2340
    pass
2341

    
2342
  def Grow(self, amount, dryrun):
2343
    """Grow the logical volume.
2344

2345
    """
2346
    _ThrowError("Grow is not supported for PersistentBlockDev storage")
2347

    
2348

    
2349
class RADOSBlockDevice(BlockDev):
2350
  """A RADOS Block Device (rbd).
2351

2352
  This class implements the RADOS Block Device for the backend. You need
2353
  the rbd kernel driver, the RADOS Tools and a working RADOS cluster for
2354
  this to be functional.
2355

2356
  """
2357
  def __init__(self, unique_id, children, size, params):
2358
    """Attaches to an rbd device.
2359

2360
    """
2361
    super(RADOSBlockDevice, self).__init__(unique_id, children, size, params)
2362
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
2363
      raise ValueError("Invalid configuration data %s" % str(unique_id))
2364

    
2365
    self.driver, self.rbd_name = unique_id
2366

    
2367
    self.major = self.minor = None
2368
    self.Attach()
2369

    
2370
  @classmethod
2371
  def Create(cls, unique_id, children, size, params):
2372
    """Create a new rbd device.
2373

2374
    Provision a new rbd volume inside a RADOS pool.
2375

2376
    """
2377
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
2378
      raise errors.ProgrammerError("Invalid configuration data %s" %
2379
                                   str(unique_id))
2380
    rbd_pool = params[constants.LDP_POOL]
2381
    rbd_name = unique_id[1]
2382

    
2383
    # Provision a new rbd volume (Image) inside the RADOS cluster.
2384
    cmd = [constants.RBD_CMD, "create", "-p", rbd_pool,
2385
           rbd_name, "--size", "%s" % size]
2386
    result = utils.RunCmd(cmd)
2387
    if result.failed:
2388
      _ThrowError("rbd creation failed (%s): %s",
2389
                  result.fail_reason, result.output)
2390

    
2391
    return RADOSBlockDevice(unique_id, children, size, params)
2392

    
2393
  def Remove(self):
2394
    """Remove the rbd device.
2395

2396
    """
2397
    rbd_pool = self.params[constants.LDP_POOL]
2398
    rbd_name = self.unique_id[1]
2399

    
2400
    if not self.minor and not self.Attach():
2401
      # The rbd device doesn't exist.
2402
      return
2403

    
2404
    # First shutdown the device (remove mappings).
2405
    self.Shutdown()
2406

    
2407
    # Remove the actual Volume (Image) from the RADOS cluster.
2408
    cmd = [constants.RBD_CMD, "rm", "-p", rbd_pool, rbd_name]
2409
    result = utils.RunCmd(cmd)
2410
    if result.failed:
2411
      _ThrowError("Can't remove Volume from cluster with rbd rm: %s - %s",
2412
                  result.fail_reason, result.output)
2413

    
2414
  def Rename(self, new_id):
2415
    """Rename this device.
2416

2417
    """
2418
    pass
2419

    
2420
  def Attach(self):
2421
    """Attach to an existing rbd device.
2422

2423
    This method maps the rbd volume that matches our name with
2424
    an rbd device and then attaches to this device.
2425

2426
    """
2427
    self.attached = False
2428

    
2429
    # Map the rbd volume to a block device under /dev
2430
    self.dev_path = self._MapVolumeToBlockdev(self.unique_id)
2431

    
2432
    try:
2433
      st = os.stat(self.dev_path)
2434
    except OSError, err:
2435
      logging.error("Error stat()'ing %s: %s", self.dev_path, str(err))
2436
      return False
2437

    
2438
    if not stat.S_ISBLK(st.st_mode):
2439
      logging.error("%s is not a block device", self.dev_path)
2440
      return False
2441

    
2442
    self.major = os.major(st.st_rdev)
2443
    self.minor = os.minor(st.st_rdev)
2444
    self.attached = True
2445

    
2446
    return True
2447

    
2448
  def _MapVolumeToBlockdev(self, unique_id):
2449
    """Maps existing rbd volumes to block devices.
2450

2451
    This method should be idempotent if the mapping already exists.
2452

2453
    @rtype: string
2454
    @return: the block device path that corresponds to the volume
2455

2456
    """
2457
    pool = self.params[constants.LDP_POOL]
2458
    name = unique_id[1]
2459

    
2460
    # Check if the mapping already exists.
2461
    showmap_cmd = [constants.RBD_CMD, "showmapped", "-p", pool]
2462
    result = utils.RunCmd(showmap_cmd)
2463
    if result.failed:
2464
      _ThrowError("rbd showmapped failed (%s): %s",
2465
                  result.fail_reason, result.output)
2466

    
2467
    rbd_dev = self._ParseRbdShowmappedOutput(result.output, name)
2468

    
2469
    if rbd_dev:
2470
      # The mapping exists. Return it.
2471
      return rbd_dev
2472

    
2473
    # The mapping doesn't exist. Create it.
2474
    map_cmd = [constants.RBD_CMD, "map", "-p", pool, name]
2475
    result = utils.RunCmd(map_cmd)
2476
    if result.failed:
2477
      _ThrowError("rbd map failed (%s): %s",
2478
                  result.fail_reason, result.output)
2479

    
2480
    # Find the corresponding rbd device.
2481
    showmap_cmd = [constants.RBD_CMD, "showmapped", "-p", pool]
2482
    result = utils.RunCmd(showmap_cmd)
2483
    if result.failed:
2484
      _ThrowError("rbd map succeeded, but showmapped failed (%s): %s",
2485
                  result.fail_reason, result.output)
2486

    
2487
    rbd_dev = self._ParseRbdShowmappedOutput(result.output, name)
2488

    
2489
    if not rbd_dev:
2490
      _ThrowError("rbd map succeeded, but could not find the rbd block"
2491
                  " device in output of showmapped, for volume: %s", name)
2492

    
2493
    # The device was successfully mapped. Return it.
2494
    return rbd_dev
2495

    
2496
  @staticmethod
2497
  def _ParseRbdShowmappedOutput(output, volume_name):
2498
    """Parse the output of `rbd showmapped'.
2499

2500
    This method parses the output of `rbd showmapped' and returns
2501
    the rbd block device path (e.g. /dev/rbd0) that matches the
2502
    given rbd volume.
2503

2504
    @type output: string
2505
    @param output: the whole output of `rbd showmapped'
2506
    @type volume_name: string
2507
    @param volume_name: the name of the volume whose device we search for
2508
    @rtype: string or None
2509
    @return: block device path if the volume is mapped, else None
2510

2511
    """
2512
    allfields = 5
2513
    volumefield = 2
2514
    devicefield = 4
2515

    
2516
    field_sep = "\t"
2517

    
2518
    lines = output.splitlines()
2519
    splitted_lines = map(lambda l: l.split(field_sep), lines)
2520

    
2521
    # Check empty output.
2522
    if not splitted_lines:
2523
      _ThrowError("rbd showmapped returned empty output")
2524

    
2525
    # Check showmapped header line, to determine number of fields.
2526
    field_cnt = len(splitted_lines[0])
2527
    if field_cnt != allfields:
2528
      _ThrowError("Cannot parse rbd showmapped output because its format"
2529
                  " seems to have changed; expected %s fields, found %s",
2530
                  allfields, field_cnt)
2531

    
2532
    matched_lines = \
2533
      filter(lambda l: len(l) == allfields and l[volumefield] == volume_name,
2534
             splitted_lines)
2535

    
2536
    if len(matched_lines) > 1:
2537
      _ThrowError("The rbd volume %s is mapped more than once."
2538
                  " This shouldn't happen, try to unmap the extra"
2539
                  " devices manually.", volume_name)
2540

    
2541
    if matched_lines:
2542
      # rbd block device found. Return it.
2543
      rbd_dev = matched_lines[0][devicefield]
2544
      return rbd_dev
2545

    
2546
    # The given volume is not mapped.
2547
    return None
2548

    
2549
  def Assemble(self):
2550
    """Assemble the device.
2551

2552
    """
2553
    pass
2554

    
2555
  def Shutdown(self):
2556
    """Shutdown the device.
2557

2558
    """
2559
    if not self.minor and not self.Attach():
2560
      # The rbd device doesn't exist.
2561
      return
2562

    
2563
    # Unmap the block device from the Volume.
2564
    self._UnmapVolumeFromBlockdev(self.unique_id)
2565

    
2566
    self.minor = None
2567
    self.dev_path = None
2568

    
2569
  def _UnmapVolumeFromBlockdev(self, unique_id):
2570
    """Unmaps the rbd device from the Volume it is mapped.
2571

2572
    Unmaps the rbd device from the Volume it was previously mapped to.
2573
    This method should be idempotent if the Volume isn't mapped.
2574

2575
    """
2576
    pool = self.params[constants.LDP_POOL]
2577
    name = unique_id[1]
2578

    
2579
    # Check if the mapping already exists.
2580
    showmap_cmd = [constants.RBD_CMD, "showmapped", "-p", pool]
2581
    result = utils.RunCmd(showmap_cmd)
2582
    if result.failed:
2583
      _ThrowError("rbd showmapped failed [during unmap](%s): %s",
2584
                  result.fail_reason, result.output)
2585

    
2586
    rbd_dev = self._ParseRbdShowmappedOutput(result.output, name)
2587

    
2588
    if rbd_dev:
2589
      # The mapping exists. Unmap the rbd device.
2590
      unmap_cmd = [constants.RBD_CMD, "unmap", "%s" % rbd_dev]
2591
      result = utils.RunCmd(unmap_cmd)
2592
      if result.failed:
2593
        _ThrowError("rbd unmap failed (%s): %s",
2594
                    result.fail_reason, result.output)
2595

    
2596
  def Open(self, force=False):
2597
    """Make the device ready for I/O.
2598

2599
    """
2600
    pass
2601

    
2602
  def Close(self):
2603
    """Notifies that the device will no longer be used for I/O.
2604

2605
    """
2606
    pass
2607

    
2608
  def Grow(self, amount, dryrun):
2609
    """Grow the Volume.
2610

2611
    @type amount: integer
2612
    @param amount: the amount (in mebibytes) to grow with
2613
    @type dryrun: boolean
2614
    @param dryrun: whether to execute the operation in simulation mode
2615
        only, without actually increasing the size
2616

2617
    """
2618
    if not self.Attach():
2619
      _ThrowError("Can't attach to rbd device during Grow()")
2620

    
2621
    if dryrun:
2622
      # the rbd tool does not support dry runs of resize operations.
2623
      # Since rbd volumes are thinly provisioned, we assume
2624
      # there is always enough free space for the operation.
2625
      return
2626

    
2627
    rbd_pool = self.params[constants.LDP_POOL]
2628
    rbd_name = self.unique_id[1]
2629
    new_size = self.size + amount
2630

    
2631
    # Resize the rbd volume (Image) inside the RADOS cluster.
2632
    cmd = [constants.RBD_CMD, "resize", "-p", rbd_pool,
2633
           rbd_name, "--size", "%s" % new_size]
2634
    result = utils.RunCmd(cmd)
2635
    if result.failed:
2636
      _ThrowError("rbd resize failed (%s): %s",
2637
                  result.fail_reason, result.output)
2638

    
2639

    
2640
DEV_MAP = {
2641
  constants.LD_LV: LogicalVolume,
2642
  constants.LD_DRBD8: DRBD8,
2643
  constants.LD_BLOCKDEV: PersistentBlockDevice,
2644
  constants.LD_RBD: RADOSBlockDevice,
2645
  }
2646

    
2647
if constants.ENABLE_FILE_STORAGE or constants.ENABLE_SHARED_FILE_STORAGE:
2648
  DEV_MAP[constants.LD_FILE] = FileStorage
2649

    
2650

    
2651
def _VerifyDiskType(dev_type):
2652
  if dev_type not in DEV_MAP:
2653
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2654

    
2655

    
2656
def FindDevice(disk, children):
2657
  """Search for an existing, assembled device.
2658

2659
  This will succeed only if the device exists and is assembled, but it
2660
  does not do any actions in order to activate the device.
2661

2662
  @type disk: L{objects.Disk}
2663
  @param disk: the disk object to find
2664
  @type children: list of L{bdev.BlockDev}
2665
  @param children: the list of block devices that are children of the device
2666
                  represented by the disk parameter
2667

2668
  """
2669
  _VerifyDiskType(disk.dev_type)
2670
  device = DEV_MAP[disk.dev_type](disk.physical_id, children, disk.size,
2671
                                  disk.params)
2672
  if not device.attached:
2673
    return None
2674
  return device
2675

    
2676

    
2677
def Assemble(disk, children):
2678
  """Try to attach or assemble an existing device.
2679

2680
  This will attach to assemble the device, as needed, to bring it
2681
  fully up. It must be safe to run on already-assembled devices.
2682

2683
  @type disk: L{objects.Disk}
2684
  @param disk: the disk object to assemble
2685
  @type children: list of L{bdev.BlockDev}
2686
  @param children: the list of block devices that are children of the device
2687
                  represented by the disk parameter
2688

2689
  """
2690
  _VerifyDiskType(disk.dev_type)
2691
  device = DEV_MAP[disk.dev_type](disk.physical_id, children, disk.size,
2692
                                  disk.params)
2693
  device.Assemble()
2694
  return device
2695

    
2696

    
2697
def Create(disk, children):
2698
  """Create a device.
2699

2700
  @type disk: L{objects.Disk}
2701
  @param disk: the disk object to create
2702
  @type children: list of L{bdev.BlockDev}
2703
  @param children: the list of block devices that are children of the device
2704
                  represented by the disk parameter
2705

2706
  """
2707
  _VerifyDiskType(disk.dev_type)
2708
  device = DEV_MAP[disk.dev_type].Create(disk.physical_id, children, disk.size,
2709
                                         disk.params)
2710
  return device