Statistics
| Branch: | Tag: | Revision:

root / lib / bdev.py @ 4008c8ed

History | View | Annotate | Download (64.9 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2010 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 pyparsing as pyp
28
import os
29
import logging
30

    
31
from ganeti import utils
32
from ganeti import errors
33
from ganeti import constants
34
from ganeti import objects
35
from ganeti import compat
36
from ganeti import netutils
37

    
38

    
39
# Size of reads in _CanReadDevice
40
_DEVICE_READ_SIZE = 128 * 1024
41

    
42

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

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

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

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

    
60

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

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

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

    
74

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

78
  This tries to read the first 128k of the device.
79

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

    
88

    
89
class BlockDev(object):
90
  """Block device abstract class.
91

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

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

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

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

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

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

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

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

    
141
  def Assemble(self):
142
    """Assemble the device from its components.
143

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

152
    """
153
    pass
154

    
155
  def Attach(self):
156
    """Find a device which matches our config and attach to it.
157

158
    """
159
    raise NotImplementedError
160

    
161
  def Close(self):
162
    """Notifies that the device will no longer be used for I/O.
163

164
    """
165
    raise NotImplementedError
166

    
167
  @classmethod
168
  def Create(cls, unique_id, children, size):
169
    """Create the device.
170

171
    If the device cannot be created, it will return None
172
    instead. Error messages go to the logging system.
173

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

178
    """
179
    raise NotImplementedError
180

    
181
  def Remove(self):
182
    """Remove this device.
183

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

188
    """
189
    raise NotImplementedError
190

    
191
  def Rename(self, new_id):
192
    """Rename this device.
193

194
    This may or may not make sense for a given device type.
195

196
    """
197
    raise NotImplementedError
198

    
199
  def Open(self, force=False):
200
    """Make the device ready for use.
201

202
    This makes the device ready for I/O. For now, just the DRBD
203
    devices need this.
204

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

208
    """
209
    raise NotImplementedError
210

    
211
  def Shutdown(self):
212
    """Shut down the device, freeing its children.
213

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

218
    """
219
    raise NotImplementedError
220

    
221
  def SetSyncSpeed(self, speed):
222
    """Adjust the sync speed of the mirror.
223

224
    In case this is not a mirroring device, this is no-op.
225

226
    """
227
    result = True
228
    if self._children:
229
      for child in self._children:
230
        result = result and child.SetSyncSpeed(speed)
231
    return result
232

    
233
  def GetSyncStatus(self):
234
    """Returns the sync status of the device.
235

236
    If this device is a mirroring device, this function returns the
237
    status of the mirror.
238

239
    If sync_percent is None, it means the device is not syncing.
240

241
    If estimated_time is None, it means we can't estimate
242
    the time needed, otherwise it's the time left in seconds.
243

244
    If is_degraded is True, it means the device is missing
245
    redundancy. This is usually a sign that something went wrong in
246
    the device setup, if sync_percent is None.
247

248
    The ldisk parameter represents the degradation of the local
249
    data. This is only valid for some devices, the rest will always
250
    return False (not degraded).
251

252
    @rtype: objects.BlockDevStatus
253

254
    """
255
    return objects.BlockDevStatus(dev_path=self.dev_path,
256
                                  major=self.major,
257
                                  minor=self.minor,
258
                                  sync_percent=None,
259
                                  estimated_time=None,
260
                                  is_degraded=False,
261
                                  ldisk_status=constants.LDS_OKAY)
262

    
263
  def CombinedSyncStatus(self):
264
    """Calculate the mirror status recursively for our children.
265

266
    The return value is the same as for `GetSyncStatus()` except the
267
    minimum percent and maximum time are calculated across our
268
    children.
269

270
    @rtype: objects.BlockDevStatus
271

272
    """
273
    status = self.GetSyncStatus()
274

    
275
    min_percent = status.sync_percent
276
    max_time = status.estimated_time
277
    is_degraded = status.is_degraded
278
    ldisk_status = status.ldisk_status
279

    
280
    if self._children:
281
      for child in self._children:
282
        child_status = child.GetSyncStatus()
283

    
284
        if min_percent is None:
285
          min_percent = child_status.sync_percent
286
        elif child_status.sync_percent is not None:
287
          min_percent = min(min_percent, child_status.sync_percent)
288

    
289
        if max_time is None:
290
          max_time = child_status.estimated_time
291
        elif child_status.estimated_time is not None:
292
          max_time = max(max_time, child_status.estimated_time)
293

    
294
        is_degraded = is_degraded or child_status.is_degraded
295

    
296
        if ldisk_status is None:
297
          ldisk_status = child_status.ldisk_status
298
        elif child_status.ldisk_status is not None:
299
          ldisk_status = max(ldisk_status, child_status.ldisk_status)
300

    
301
    return objects.BlockDevStatus(dev_path=self.dev_path,
302
                                  major=self.major,
303
                                  minor=self.minor,
304
                                  sync_percent=min_percent,
305
                                  estimated_time=max_time,
306
                                  is_degraded=is_degraded,
307
                                  ldisk_status=ldisk_status)
308

    
309

    
310
  def SetInfo(self, text):
311
    """Update metadata with info text.
312

313
    Only supported for some device types.
314

315
    """
316
    for child in self._children:
317
      child.SetInfo(text)
318

    
319
  def Grow(self, amount):
320
    """Grow the block device.
321

322
    @param amount: the amount (in mebibytes) to grow with
323

324
    """
325
    raise NotImplementedError
326

    
327
  def GetActualSize(self):
328
    """Return the actual disk size.
329

330
    @note: the device needs to be active when this is called
331

332
    """
333
    assert self.attached, "BlockDevice not attached in GetActualSize()"
334
    result = utils.RunCmd(["blockdev", "--getsize64", self.dev_path])
335
    if result.failed:
336
      _ThrowError("blockdev failed (%s): %s",
337
                  result.fail_reason, result.output)
338
    try:
339
      sz = int(result.output.strip())
340
    except (ValueError, TypeError), err:
341
      _ThrowError("Failed to parse blockdev output: %s", str(err))
342
    return sz
343

    
344
  def __repr__(self):
345
    return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
346
            (self.__class__, self.unique_id, self._children,
347
             self.major, self.minor, self.dev_path))
348

    
349

    
350
class LogicalVolume(BlockDev):
351
  """Logical Volume block device.
352

353
  """
354
  _VALID_NAME_RE = re.compile("^[a-zA-Z0-9+_.-]*$")
355
  _INVALID_NAMES = frozenset([".", "..", "snapshot", "pvmove"])
356
  _INVALID_SUBSTRINGS = frozenset(["_mlog", "_mimage"])
357

    
358
  def __init__(self, unique_id, children, size):
359
    """Attaches to a LV device.
360

361
    The unique_id is a tuple (vg_name, lv_name)
362

363
    """
364
    super(LogicalVolume, self).__init__(unique_id, children, size)
365
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
366
      raise ValueError("Invalid configuration data %s" % str(unique_id))
367
    self._vg_name, self._lv_name = unique_id
368
    self._ValidateName(self._vg_name)
369
    self._ValidateName(self._lv_name)
370
    self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
371
    self._degraded = True
372
    self.major = self.minor = self.pe_size = self.stripe_count = None
373
    self.Attach()
374

    
375
  @classmethod
376
  def Create(cls, unique_id, children, size):
377
    """Create a new logical volume.
378

379
    """
380
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
381
      raise errors.ProgrammerError("Invalid configuration data %s" %
382
                                   str(unique_id))
383
    vg_name, lv_name = unique_id
384
    cls._ValidateName(vg_name)
385
    cls._ValidateName(lv_name)
386
    pvs_info = cls.GetPVInfo([vg_name])
387
    if not pvs_info:
388
      _ThrowError("Can't compute PV info for vg %s", vg_name)
389
    pvs_info.sort()
390
    pvs_info.reverse()
391

    
392
    pvlist = [ pv[1] for pv in pvs_info ]
393
    if compat.any(":" in v for v in pvlist):
394
      _ThrowError("Some of your PVs have the invalid character ':' in their"
395
                  " name, this is not supported - please filter them out"
396
                  " in lvm.conf using either 'filter' or 'preferred_names'")
397
    free_size = sum([ pv[0] for pv in pvs_info ])
398
    current_pvs = len(pvlist)
399
    stripes = min(current_pvs, constants.LVM_STRIPECOUNT)
400

    
401
    # The size constraint should have been checked from the master before
402
    # calling the create function.
403
    if free_size < size:
404
      _ThrowError("Not enough free space: required %s,"
405
                  " available %s", size, free_size)
406
    cmd = ["lvcreate", "-L%dm" % size, "-n%s" % lv_name]
407
    # If the free space is not well distributed, we won't be able to
408
    # create an optimally-striped volume; in that case, we want to try
409
    # with N, N-1, ..., 2, and finally 1 (non-stripped) number of
410
    # stripes
411
    for stripes_arg in range(stripes, 0, -1):
412
      result = utils.RunCmd(cmd + ["-i%d" % stripes_arg] + [vg_name] + pvlist)
413
      if not result.failed:
414
        break
415
    if result.failed:
416
      _ThrowError("LV create failed (%s): %s",
417
                  result.fail_reason, result.output)
418
    return LogicalVolume(unique_id, children, size)
419

    
420
  @staticmethod
421
  def GetPVInfo(vg_names, filter_allocatable=True):
422
    """Get the free space info for PVs in a volume group.
423

424
    @param vg_names: list of volume group names, if empty all will be returned
425
    @param filter_allocatable: whether to skip over unallocatable PVs
426

427
    @rtype: list
428
    @return: list of tuples (free_space, name) with free_space in mebibytes
429

430
    """
431
    sep = "|"
432
    command = ["pvs", "--noheadings", "--nosuffix", "--units=m",
433
               "-opv_name,vg_name,pv_free,pv_attr", "--unbuffered",
434
               "--separator=%s" % sep ]
435
    result = utils.RunCmd(command)
436
    if result.failed:
437
      logging.error("Can't get the PV information: %s - %s",
438
                    result.fail_reason, result.output)
439
      return None
440
    data = []
441
    for line in result.stdout.splitlines():
442
      fields = line.strip().split(sep)
443
      if len(fields) != 4:
444
        logging.error("Can't parse pvs output: line '%s'", line)
445
        return None
446
      # (possibly) skip over pvs which are not allocatable
447
      if filter_allocatable and fields[3][0] != 'a':
448
        continue
449
      # (possibly) skip over pvs which are not in the right volume group(s)
450
      if vg_names and fields[1] not in vg_names:
451
        continue
452
      data.append((float(fields[2]), fields[0], fields[1]))
453

    
454
    return data
455

    
456
  @classmethod
457
  def _ValidateName(cls, name):
458
    """Validates that a given name is valid as VG or LV name.
459

460
    The list of valid characters and restricted names is taken out of
461
    the lvm(8) manpage, with the simplification that we enforce both
462
    VG and LV restrictions on the names.
463

464
    """
465
    if (not cls._VALID_NAME_RE.match(name) or
466
        name in cls._INVALID_NAMES or
467
        compat.any(substring in name for substring in cls._INVALID_SUBSTRINGS)):
468
      _ThrowError("Invalid LVM name '%s'", name)
469

    
470
  def Remove(self):
471
    """Remove this logical volume.
472

473
    """
474
    if not self.minor and not self.Attach():
475
      # the LV does not exist
476
      return
477
    result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
478
                           (self._vg_name, self._lv_name)])
479
    if result.failed:
480
      _ThrowError("Can't lvremove: %s - %s", result.fail_reason, result.output)
481

    
482
  def Rename(self, new_id):
483
    """Rename this logical volume.
484

485
    """
486
    if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
487
      raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
488
    new_vg, new_name = new_id
489
    if new_vg != self._vg_name:
490
      raise errors.ProgrammerError("Can't move a logical volume across"
491
                                   " volume groups (from %s to to %s)" %
492
                                   (self._vg_name, new_vg))
493
    result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
494
    if result.failed:
495
      _ThrowError("Failed to rename the logical volume: %s", result.output)
496
    self._lv_name = new_name
497
    self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
498

    
499
  def Attach(self):
500
    """Attach to an existing LV.
501

502
    This method will try to see if an existing and active LV exists
503
    which matches our name. If so, its major/minor will be
504
    recorded.
505

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

    
532
    status, major, minor, pe_size, stripes = out
533
    if len(status) != 6:
534
      logging.error("lvs lv_attr is not 6 characters (%s)", status)
535
      return False
536

    
537
    try:
538
      major = int(major)
539
      minor = int(minor)
540
    except (TypeError, ValueError), err:
541
      logging.error("lvs major/minor cannot be parsed: %s", str(err))
542

    
543
    try:
544
      pe_size = int(float(pe_size))
545
    except (TypeError, ValueError), err:
546
      logging.error("Can't parse vg extent size: %s", err)
547
      return False
548

    
549
    try:
550
      stripes = int(stripes)
551
    except (TypeError, ValueError), err:
552
      logging.error("Can't parse the number of stripes: %s", err)
553
      return False
554

    
555
    self.major = major
556
    self.minor = minor
557
    self.pe_size = pe_size
558
    self.stripe_count = stripes
559
    self._degraded = status[0] == 'v' # virtual volume, i.e. doesn't backing
560
                                      # storage
561
    self.attached = True
562
    return True
563

    
564
  def Assemble(self):
565
    """Assemble the device.
566

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

571
    """
572
    result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
573
    if result.failed:
574
      _ThrowError("Can't activate lv %s: %s", self.dev_path, result.output)
575

    
576
  def Shutdown(self):
577
    """Shutdown the device.
578

579
    This is a no-op for the LV device type, as we don't deactivate the
580
    volumes on shutdown.
581

582
    """
583
    pass
584

    
585
  def GetSyncStatus(self):
586
    """Returns the sync status of the device.
587

588
    If this device is a mirroring device, this function returns the
589
    status of the mirror.
590

591
    For logical volumes, sync_percent and estimated_time are always
592
    None (no recovery in progress, as we don't handle the mirrored LV
593
    case). The is_degraded parameter is the inverse of the ldisk
594
    parameter.
595

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

602
    The status was already read in Attach, so we just return it.
603

604
    @rtype: objects.BlockDevStatus
605

606
    """
607
    if self._degraded:
608
      ldisk_status = constants.LDS_FAULTY
609
    else:
610
      ldisk_status = constants.LDS_OKAY
611

    
612
    return objects.BlockDevStatus(dev_path=self.dev_path,
613
                                  major=self.major,
614
                                  minor=self.minor,
615
                                  sync_percent=None,
616
                                  estimated_time=None,
617
                                  is_degraded=self._degraded,
618
                                  ldisk_status=ldisk_status)
619

    
620
  def Open(self, force=False):
621
    """Make the device ready for I/O.
622

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

625
    """
626
    pass
627

    
628
  def Close(self):
629
    """Notifies that the device will no longer be used for I/O.
630

631
    This is a no-op for the LV device type.
632

633
    """
634
    pass
635

    
636
  def Snapshot(self, size):
637
    """Create a snapshot copy of an lvm block device.
638

639
    """
640
    snap_name = self._lv_name + ".snap"
641

    
642
    # remove existing snapshot if found
643
    snap = LogicalVolume((self._vg_name, snap_name), None, size)
644
    _IgnoreError(snap.Remove)
645

    
646
    pvs_info = self.GetPVInfo([self._vg_name])
647
    if not pvs_info:
648
      _ThrowError("Can't compute PV info for vg %s", self._vg_name)
649
    pvs_info.sort()
650
    pvs_info.reverse()
651
    free_size, _, _ = pvs_info[0]
652
    if free_size < size:
653
      _ThrowError("Not enough free space: required %s,"
654
                  " available %s", size, free_size)
655

    
656
    result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
657
                           "-n%s" % snap_name, self.dev_path])
658
    if result.failed:
659
      _ThrowError("command: %s error: %s - %s",
660
                  result.cmd, result.fail_reason, result.output)
661

    
662
    return snap_name
663

    
664
  def SetInfo(self, text):
665
    """Update metadata with info text.
666

667
    """
668
    BlockDev.SetInfo(self, text)
669

    
670
    # Replace invalid characters
671
    text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
672
    text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
673

    
674
    # Only up to 128 characters are allowed
675
    text = text[:128]
676

    
677
    result = utils.RunCmd(["lvchange", "--addtag", text,
678
                           self.dev_path])
679
    if result.failed:
680
      _ThrowError("Command: %s error: %s - %s", result.cmd, result.fail_reason,
681
                  result.output)
682

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

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

    
705

    
706
class DRBD8Status(object):
707
  """A DRBD status representation class.
708

709
  Note that this doesn't support unconfigured devices (cs:Unconfigured).
710

711
  """
712
  UNCONF_RE = re.compile(r"\s*[0-9]+:\s*cs:Unconfigured$")
713
  LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+(?:st|ro):([^/]+)/(\S+)"
714
                       "\s+ds:([^/]+)/(\S+)\s+.*$")
715
  SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*"
716
                       "\sfinish: ([0-9]+):([0-9]+):([0-9]+)\s.*$")
717

    
718
  CS_UNCONFIGURED = "Unconfigured"
719
  CS_STANDALONE = "StandAlone"
720
  CS_WFCONNECTION = "WFConnection"
721
  CS_WFREPORTPARAMS = "WFReportParams"
722
  CS_CONNECTED = "Connected"
723
  CS_STARTINGSYNCS = "StartingSyncS"
724
  CS_STARTINGSYNCT = "StartingSyncT"
725
  CS_WFBITMAPS = "WFBitMapS"
726
  CS_WFBITMAPT = "WFBitMapT"
727
  CS_WFSYNCUUID = "WFSyncUUID"
728
  CS_SYNCSOURCE = "SyncSource"
729
  CS_SYNCTARGET = "SyncTarget"
730
  CS_PAUSEDSYNCS = "PausedSyncS"
731
  CS_PAUSEDSYNCT = "PausedSyncT"
732
  CSET_SYNC = frozenset([
733
    CS_WFREPORTPARAMS,
734
    CS_STARTINGSYNCS,
735
    CS_STARTINGSYNCT,
736
    CS_WFBITMAPS,
737
    CS_WFBITMAPT,
738
    CS_WFSYNCUUID,
739
    CS_SYNCSOURCE,
740
    CS_SYNCTARGET,
741
    CS_PAUSEDSYNCS,
742
    CS_PAUSEDSYNCT,
743
    ])
744

    
745
  DS_DISKLESS = "Diskless"
746
  DS_ATTACHING = "Attaching" # transient state
747
  DS_FAILED = "Failed" # transient state, next: diskless
748
  DS_NEGOTIATING = "Negotiating" # transient state
749
  DS_INCONSISTENT = "Inconsistent" # while syncing or after creation
750
  DS_OUTDATED = "Outdated"
751
  DS_DUNKNOWN = "DUnknown" # shown for peer disk when not connected
752
  DS_CONSISTENT = "Consistent"
753
  DS_UPTODATE = "UpToDate" # normal state
754

    
755
  RO_PRIMARY = "Primary"
756
  RO_SECONDARY = "Secondary"
757
  RO_UNKNOWN = "Unknown"
758

    
759
  def __init__(self, procline):
760
    u = self.UNCONF_RE.match(procline)
761
    if u:
762
      self.cstatus = self.CS_UNCONFIGURED
763
      self.lrole = self.rrole = self.ldisk = self.rdisk = None
764
    else:
765
      m = self.LINE_RE.match(procline)
766
      if not m:
767
        raise errors.BlockDeviceError("Can't parse input data '%s'" % procline)
768
      self.cstatus = m.group(1)
769
      self.lrole = m.group(2)
770
      self.rrole = m.group(3)
771
      self.ldisk = m.group(4)
772
      self.rdisk = m.group(5)
773

    
774
    # end reading of data from the LINE_RE or UNCONF_RE
775

    
776
    self.is_standalone = self.cstatus == self.CS_STANDALONE
777
    self.is_wfconn = self.cstatus == self.CS_WFCONNECTION
778
    self.is_connected = self.cstatus == self.CS_CONNECTED
779
    self.is_primary = self.lrole == self.RO_PRIMARY
780
    self.is_secondary = self.lrole == self.RO_SECONDARY
781
    self.peer_primary = self.rrole == self.RO_PRIMARY
782
    self.peer_secondary = self.rrole == self.RO_SECONDARY
783
    self.both_primary = self.is_primary and self.peer_primary
784
    self.both_secondary = self.is_secondary and self.peer_secondary
785

    
786
    self.is_diskless = self.ldisk == self.DS_DISKLESS
787
    self.is_disk_uptodate = self.ldisk == self.DS_UPTODATE
788

    
789
    self.is_in_resync = self.cstatus in self.CSET_SYNC
790
    self.is_in_use = self.cstatus != self.CS_UNCONFIGURED
791

    
792
    m = self.SYNC_RE.match(procline)
793
    if m:
794
      self.sync_percent = float(m.group(1))
795
      hours = int(m.group(2))
796
      minutes = int(m.group(3))
797
      seconds = int(m.group(4))
798
      self.est_time = hours * 3600 + minutes * 60 + seconds
799
    else:
800
      # we have (in this if branch) no percent information, but if
801
      # we're resyncing we need to 'fake' a sync percent information,
802
      # as this is how cmdlib determines if it makes sense to wait for
803
      # resyncing or not
804
      if self.is_in_resync:
805
        self.sync_percent = 0
806
      else:
807
        self.sync_percent = None
808
      self.est_time = None
809

    
810

    
811
class BaseDRBD(BlockDev): # pylint: disable-msg=W0223
812
  """Base DRBD class.
813

814
  This class contains a few bits of common functionality between the
815
  0.7 and 8.x versions of DRBD.
816

817
  """
818
  _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)(?:\.\d+)?"
819
                           r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
820
  _VALID_LINE_RE = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
821
  _UNUSED_LINE_RE = re.compile("^ *([0-9]+): cs:Unconfigured$")
822

    
823
  _DRBD_MAJOR = 147
824
  _ST_UNCONFIGURED = "Unconfigured"
825
  _ST_WFCONNECTION = "WFConnection"
826
  _ST_CONNECTED = "Connected"
827

    
828
  _STATUS_FILE = "/proc/drbd"
829
  _USERMODE_HELPER_FILE = "/sys/module/drbd/parameters/usermode_helper"
830

    
831
  @staticmethod
832
  def _GetProcData(filename=_STATUS_FILE):
833
    """Return data from /proc/drbd.
834

835
    """
836
    try:
837
      data = utils.ReadFile(filename).splitlines()
838
    except EnvironmentError, err:
839
      if err.errno == errno.ENOENT:
840
        _ThrowError("The file %s cannot be opened, check if the module"
841
                    " is loaded (%s)", filename, str(err))
842
      else:
843
        _ThrowError("Can't read the DRBD proc file %s: %s", filename, str(err))
844
    if not data:
845
      _ThrowError("Can't read any data from %s", filename)
846
    return data
847

    
848
  @classmethod
849
  def _MassageProcData(cls, data):
850
    """Transform the output of _GetProdData into a nicer form.
851

852
    @return: a dictionary of minor: joined lines from /proc/drbd
853
        for that minor
854

855
    """
856
    results = {}
857
    old_minor = old_line = None
858
    for line in data:
859
      if not line: # completely empty lines, as can be returned by drbd8.0+
860
        continue
861
      lresult = cls._VALID_LINE_RE.match(line)
862
      if lresult is not None:
863
        if old_minor is not None:
864
          results[old_minor] = old_line
865
        old_minor = int(lresult.group(1))
866
        old_line = line
867
      else:
868
        if old_minor is not None:
869
          old_line += " " + line.strip()
870
    # add last line
871
    if old_minor is not None:
872
      results[old_minor] = old_line
873
    return results
874

    
875
  @classmethod
876
  def _GetVersion(cls, proc_data):
877
    """Return the DRBD version.
878

879
    This will return a dict with keys:
880
      - k_major
881
      - k_minor
882
      - k_point
883
      - api
884
      - proto
885
      - proto2 (only on drbd > 8.2.X)
886

887
    """
888
    first_line = proc_data[0].strip()
889
    version = cls._VERSION_RE.match(first_line)
890
    if not version:
891
      raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
892
                                    first_line)
893

    
894
    values = version.groups()
895
    retval = {'k_major': int(values[0]),
896
              'k_minor': int(values[1]),
897
              'k_point': int(values[2]),
898
              'api': int(values[3]),
899
              'proto': int(values[4]),
900
             }
901
    if values[5] is not None:
902
      retval['proto2'] = values[5]
903

    
904
    return retval
905

    
906
  @staticmethod
907
  def GetUsermodeHelper(filename=_USERMODE_HELPER_FILE):
908
    """Returns DRBD usermode_helper currently set.
909

910
    """
911
    try:
912
      helper = utils.ReadFile(filename).splitlines()[0]
913
    except EnvironmentError, err:
914
      if err.errno == errno.ENOENT:
915
        _ThrowError("The file %s cannot be opened, check if the module"
916
                    " is loaded (%s)", filename, str(err))
917
      else:
918
        _ThrowError("Can't read DRBD helper file %s: %s", filename, str(err))
919
    if not helper:
920
      _ThrowError("Can't read any data from %s", filename)
921
    return helper
922

    
923
  @staticmethod
924
  def _DevPath(minor):
925
    """Return the path to a drbd device for a given minor.
926

927
    """
928
    return "/dev/drbd%d" % minor
929

    
930
  @classmethod
931
  def GetUsedDevs(cls):
932
    """Compute the list of used DRBD devices.
933

934
    """
935
    data = cls._GetProcData()
936

    
937
    used_devs = {}
938
    for line in data:
939
      match = cls._VALID_LINE_RE.match(line)
940
      if not match:
941
        continue
942
      minor = int(match.group(1))
943
      state = match.group(2)
944
      if state == cls._ST_UNCONFIGURED:
945
        continue
946
      used_devs[minor] = state, line
947

    
948
    return used_devs
949

    
950
  def _SetFromMinor(self, minor):
951
    """Set our parameters based on the given minor.
952

953
    This sets our minor variable and our dev_path.
954

955
    """
956
    if minor is None:
957
      self.minor = self.dev_path = None
958
      self.attached = False
959
    else:
960
      self.minor = minor
961
      self.dev_path = self._DevPath(minor)
962
      self.attached = True
963

    
964
  @staticmethod
965
  def _CheckMetaSize(meta_device):
966
    """Check if the given meta device looks like a valid one.
967

968
    This currently only check the size, which must be around
969
    128MiB.
970

971
    """
972
    result = utils.RunCmd(["blockdev", "--getsize", meta_device])
973
    if result.failed:
974
      _ThrowError("Failed to get device size: %s - %s",
975
                  result.fail_reason, result.output)
976
    try:
977
      sectors = int(result.stdout)
978
    except (TypeError, ValueError):
979
      _ThrowError("Invalid output from blockdev: '%s'", result.stdout)
980
    num_bytes = sectors * 512
981
    if num_bytes < 128 * 1024 * 1024: # less than 128MiB
982
      _ThrowError("Meta device too small (%.2fMib)", (num_bytes / 1024 / 1024))
983
    # the maximum *valid* size of the meta device when living on top
984
    # of LVM is hard to compute: it depends on the number of stripes
985
    # and the PE size; e.g. a 2-stripe, 64MB PE will result in a 128MB
986
    # (normal size), but an eight-stripe 128MB PE will result in a 1GB
987
    # size meta device; as such, we restrict it to 1GB (a little bit
988
    # too generous, but making assumptions about PE size is hard)
989
    if num_bytes > 1024 * 1024 * 1024:
990
      _ThrowError("Meta device too big (%.2fMiB)", (num_bytes / 1024 / 1024))
991

    
992
  def Rename(self, new_id):
993
    """Rename a device.
994

995
    This is not supported for drbd devices.
996

997
    """
998
    raise errors.ProgrammerError("Can't rename a drbd device")
999

    
1000

    
1001
class DRBD8(BaseDRBD):
1002
  """DRBD v8.x block device.
1003

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

1008
  The unique_id for the drbd device is the (local_ip, local_port,
1009
  remote_ip, remote_port) tuple, and it must have two children: the
1010
  data device and the meta_device. The meta device is checked for
1011
  valid size and is zeroed on create.
1012

1013
  """
1014
  _MAX_MINORS = 255
1015
  _PARSE_SHOW = None
1016

    
1017
  # timeout constants
1018
  _NET_RECONFIG_TIMEOUT = 60
1019

    
1020
  def __init__(self, unique_id, children, size):
1021
    if children and children.count(None) > 0:
1022
      children = []
1023
    if len(children) not in (0, 2):
1024
      raise ValueError("Invalid configuration data %s" % str(children))
1025
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6:
1026
      raise ValueError("Invalid configuration data %s" % str(unique_id))
1027
    (self._lhost, self._lport,
1028
     self._rhost, self._rport,
1029
     self._aminor, self._secret) = unique_id
1030
    if children:
1031
      if not _CanReadDevice(children[1].dev_path):
1032
        logging.info("drbd%s: Ignoring unreadable meta device", self._aminor)
1033
        children = []
1034
    super(DRBD8, self).__init__(unique_id, children, size)
1035
    self.major = self._DRBD_MAJOR
1036
    version = self._GetVersion(self._GetProcData())
1037
    if version['k_major'] != 8 :
1038
      _ThrowError("Mismatch in DRBD kernel version and requested ganeti"
1039
                  " usage: kernel is %s.%s, ganeti wants 8.x",
1040
                  version['k_major'], version['k_minor'])
1041

    
1042
    if (self._lhost is not None and self._lhost == self._rhost and
1043
        self._lport == self._rport):
1044
      raise ValueError("Invalid configuration data, same local/remote %s" %
1045
                       (unique_id,))
1046
    self.Attach()
1047

    
1048
  @classmethod
1049
  def _InitMeta(cls, minor, dev_path):
1050
    """Initialize a meta device.
1051

1052
    This will not work if the given minor is in use.
1053

1054
    """
1055
    result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
1056
                           "v08", dev_path, "0", "create-md"])
1057
    if result.failed:
1058
      _ThrowError("Can't initialize meta device: %s", result.output)
1059

    
1060
  @classmethod
1061
  def _FindUnusedMinor(cls):
1062
    """Find an unused DRBD device.
1063

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

1067
    """
1068
    data = cls._GetProcData()
1069

    
1070
    highest = None
1071
    for line in data:
1072
      match = cls._UNUSED_LINE_RE.match(line)
1073
      if match:
1074
        return int(match.group(1))
1075
      match = cls._VALID_LINE_RE.match(line)
1076
      if match:
1077
        minor = int(match.group(1))
1078
        highest = max(highest, minor)
1079
    if highest is None: # there are no minors in use at all
1080
      return 0
1081
    if highest >= cls._MAX_MINORS:
1082
      logging.error("Error: no free drbd minors!")
1083
      raise errors.BlockDeviceError("Can't find a free DRBD minor")
1084
    return highest + 1
1085

    
1086
  @classmethod
1087
  def _GetShowParser(cls):
1088
    """Return a parser for `drbd show` output.
1089

1090
    This will either create or return an already-create parser for the
1091
    output of the command `drbd show`.
1092

1093
    """
1094
    if cls._PARSE_SHOW is not None:
1095
      return cls._PARSE_SHOW
1096

    
1097
    # pyparsing setup
1098
    lbrace = pyp.Literal("{").suppress()
1099
    rbrace = pyp.Literal("}").suppress()
1100
    lbracket = pyp.Literal("[").suppress()
1101
    rbracket = pyp.Literal("]").suppress()
1102
    semi = pyp.Literal(";").suppress()
1103
    colon = pyp.Literal(":").suppress()
1104
    # this also converts the value to an int
1105
    number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
1106

    
1107
    comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
1108
    defa = pyp.Literal("_is_default").suppress()
1109
    dbl_quote = pyp.Literal('"').suppress()
1110

    
1111
    keyword = pyp.Word(pyp.alphanums + '-')
1112

    
1113
    # value types
1114
    value = pyp.Word(pyp.alphanums + '_-/.:')
1115
    quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1116
    ipv4_addr = (pyp.Optional(pyp.Literal("ipv4")).suppress() +
1117
                 pyp.Word(pyp.nums + ".") + colon + number)
1118
    ipv6_addr = (pyp.Optional(pyp.Literal("ipv6")).suppress() +
1119
                 pyp.Optional(lbracket) + pyp.Word(pyp.hexnums + ":") +
1120
                 pyp.Optional(rbracket) + colon + number)
1121
    # meta device, extended syntax
1122
    meta_value = ((value ^ quoted) + lbracket + number + rbracket)
1123
    # device name, extended syntax
1124
    device_value = pyp.Literal("minor").suppress() + number
1125

    
1126
    # a statement
1127
    stmt = (~rbrace + keyword + ~lbrace +
1128
            pyp.Optional(ipv4_addr ^ ipv6_addr ^ value ^ quoted ^ meta_value ^
1129
                         device_value) +
1130
            pyp.Optional(defa) + semi +
1131
            pyp.Optional(pyp.restOfLine).suppress())
1132

    
1133
    # an entire section
1134
    section_name = pyp.Word(pyp.alphas + '_')
1135
    section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1136

    
1137
    bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1138
    bnf.ignore(comment)
1139

    
1140
    cls._PARSE_SHOW = bnf
1141

    
1142
    return bnf
1143

    
1144
  @classmethod
1145
  def _GetShowData(cls, minor):
1146
    """Return the `drbdsetup show` data for a minor.
1147

1148
    """
1149
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1150
    if result.failed:
1151
      logging.error("Can't display the drbd config: %s - %s",
1152
                    result.fail_reason, result.output)
1153
      return None
1154
    return result.stdout
1155

    
1156
  @classmethod
1157
  def _GetDevInfo(cls, out):
1158
    """Parse details about a given DRBD minor.
1159

1160
    This return, if available, the local backing device (as a path)
1161
    and the local and remote (ip, port) information from a string
1162
    containing the output of the `drbdsetup show` command as returned
1163
    by _GetShowData.
1164

1165
    """
1166
    data = {}
1167
    if not out:
1168
      return data
1169

    
1170
    bnf = cls._GetShowParser()
1171
    # run pyparse
1172

    
1173
    try:
1174
      results = bnf.parseString(out)
1175
    except pyp.ParseException, err:
1176
      _ThrowError("Can't parse drbdsetup show output: %s", str(err))
1177

    
1178
    # and massage the results into our desired format
1179
    for section in results:
1180
      sname = section[0]
1181
      if sname == "_this_host":
1182
        for lst in section[1:]:
1183
          if lst[0] == "disk":
1184
            data["local_dev"] = lst[1]
1185
          elif lst[0] == "meta-disk":
1186
            data["meta_dev"] = lst[1]
1187
            data["meta_index"] = lst[2]
1188
          elif lst[0] == "address":
1189
            data["local_addr"] = tuple(lst[1:])
1190
      elif sname == "_remote_host":
1191
        for lst in section[1:]:
1192
          if lst[0] == "address":
1193
            data["remote_addr"] = tuple(lst[1:])
1194
    return data
1195

    
1196
  def _MatchesLocal(self, info):
1197
    """Test if our local config matches with an existing device.
1198

1199
    The parameter should be as returned from `_GetDevInfo()`. This
1200
    method tests if our local backing device is the same as the one in
1201
    the info parameter, in effect testing if we look like the given
1202
    device.
1203

1204
    """
1205
    if self._children:
1206
      backend, meta = self._children
1207
    else:
1208
      backend = meta = None
1209

    
1210
    if backend is not None:
1211
      retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1212
    else:
1213
      retval = ("local_dev" not in info)
1214

    
1215
    if meta is not None:
1216
      retval = retval and ("meta_dev" in info and
1217
                           info["meta_dev"] == meta.dev_path)
1218
      retval = retval and ("meta_index" in info and
1219
                           info["meta_index"] == 0)
1220
    else:
1221
      retval = retval and ("meta_dev" not in info and
1222
                           "meta_index" not in info)
1223
    return retval
1224

    
1225
  def _MatchesNet(self, info):
1226
    """Test if our network config matches with an existing device.
1227

1228
    The parameter should be as returned from `_GetDevInfo()`. This
1229
    method tests if our network configuration is the same as the one
1230
    in the info parameter, in effect testing if we look like the given
1231
    device.
1232

1233
    """
1234
    if (((self._lhost is None and not ("local_addr" in info)) and
1235
         (self._rhost is None and not ("remote_addr" in info)))):
1236
      return True
1237

    
1238
    if self._lhost is None:
1239
      return False
1240

    
1241
    if not ("local_addr" in info and
1242
            "remote_addr" in info):
1243
      return False
1244

    
1245
    retval = (info["local_addr"] == (self._lhost, self._lport))
1246
    retval = (retval and
1247
              info["remote_addr"] == (self._rhost, self._rport))
1248
    return retval
1249

    
1250
  @classmethod
1251
  def _AssembleLocal(cls, minor, backend, meta, size):
1252
    """Configure the local part of a DRBD device.
1253

1254
    """
1255
    args = ["drbdsetup", cls._DevPath(minor), "disk",
1256
            backend, meta, "0",
1257
            "-e", "detach",
1258
            "--create-device"]
1259
    if size:
1260
      args.extend(["-d", "%sm" % size])
1261
    if not constants.DRBD_BARRIERS: # disable barriers, if configured so
1262
      version = cls._GetVersion(cls._GetProcData())
1263
      # various DRBD versions support different disk barrier options;
1264
      # what we aim here is to revert back to the 'drain' method of
1265
      # disk flushes and to disable metadata barriers, in effect going
1266
      # back to pre-8.0.7 behaviour
1267
      vmaj = version['k_major']
1268
      vmin = version['k_minor']
1269
      vrel = version['k_point']
1270
      assert vmaj == 8
1271
      if vmin == 0: # 8.0.x
1272
        if vrel >= 12:
1273
          args.extend(['-i', '-m'])
1274
      elif vmin == 2: # 8.2.x
1275
        if vrel >= 7:
1276
          args.extend(['-i', '-m'])
1277
      elif vmaj >= 3: # 8.3.x or newer
1278
        args.extend(['-i', '-a', 'm'])
1279
    result = utils.RunCmd(args)
1280
    if result.failed:
1281
      _ThrowError("drbd%d: can't attach local disk: %s", minor, result.output)
1282

    
1283
  @classmethod
1284
  def _AssembleNet(cls, minor, net_info, protocol,
1285
                   dual_pri=False, hmac=None, secret=None):
1286
    """Configure the network part of the device.
1287

1288
    """
1289
    lhost, lport, rhost, rport = net_info
1290
    if None in net_info:
1291
      # we don't want network connection and actually want to make
1292
      # sure its shutdown
1293
      cls._ShutdownNet(minor)
1294
      return
1295

    
1296
    # Workaround for a race condition. When DRBD is doing its dance to
1297
    # establish a connection with its peer, it also sends the
1298
    # synchronization speed over the wire. In some cases setting the
1299
    # sync speed only after setting up both sides can race with DRBD
1300
    # connecting, hence we set it here before telling DRBD anything
1301
    # about its peer.
1302
    cls._SetMinorSyncSpeed(minor, constants.SYNC_SPEED)
1303

    
1304
    if netutils.IsValidIP6(lhost):
1305
      if not netutils.IsValidIP6(rhost):
1306
        _ThrowError("drbd%d: can't connect ip %s to ip %s" %
1307
                    (minor, lhost, rhost))
1308
      family = "ipv6"
1309
    elif netutils.IsValidIP4(lhost):
1310
      if not netutils.IsValidIP4(rhost):
1311
        _ThrowError("drbd%d: can't connect ip %s to ip %s" %
1312
                    (minor, lhost, rhost))
1313
      family = "ipv4"
1314
    else:
1315
      _ThrowError("drbd%d: Invalid ip %s" % (minor, lhost))
1316

    
1317
    args = ["drbdsetup", cls._DevPath(minor), "net",
1318
            "%s:%s:%s" % (family, lhost, lport),
1319
            "%s:%s:%s" % (family, rhost, rport), protocol,
1320
            "-A", "discard-zero-changes",
1321
            "-B", "consensus",
1322
            "--create-device",
1323
            ]
1324
    if dual_pri:
1325
      args.append("-m")
1326
    if hmac and secret:
1327
      args.extend(["-a", hmac, "-x", secret])
1328
    result = utils.RunCmd(args)
1329
    if result.failed:
1330
      _ThrowError("drbd%d: can't setup network: %s - %s",
1331
                  minor, result.fail_reason, result.output)
1332

    
1333
    def _CheckNetworkConfig():
1334
      info = cls._GetDevInfo(cls._GetShowData(minor))
1335
      if not "local_addr" in info or not "remote_addr" in info:
1336
        raise utils.RetryAgain()
1337

    
1338
      if (info["local_addr"] != (lhost, lport) or
1339
          info["remote_addr"] != (rhost, rport)):
1340
        raise utils.RetryAgain()
1341

    
1342
    try:
1343
      utils.Retry(_CheckNetworkConfig, 1.0, 10.0)
1344
    except utils.RetryTimeout:
1345
      _ThrowError("drbd%d: timeout while configuring network", minor)
1346

    
1347
  def AddChildren(self, devices):
1348
    """Add a disk to the DRBD device.
1349

1350
    """
1351
    if self.minor is None:
1352
      _ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
1353
                  self._aminor)
1354
    if len(devices) != 2:
1355
      _ThrowError("drbd%d: need two devices for AddChildren", self.minor)
1356
    info = self._GetDevInfo(self._GetShowData(self.minor))
1357
    if "local_dev" in info:
1358
      _ThrowError("drbd%d: already attached to a local disk", self.minor)
1359
    backend, meta = devices
1360
    if backend.dev_path is None or meta.dev_path is None:
1361
      _ThrowError("drbd%d: children not ready during AddChildren", self.minor)
1362
    backend.Open()
1363
    meta.Open()
1364
    self._CheckMetaSize(meta.dev_path)
1365
    self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1366

    
1367
    self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
1368
    self._children = devices
1369

    
1370
  def RemoveChildren(self, devices):
1371
    """Detach the drbd device from local storage.
1372

1373
    """
1374
    if self.minor is None:
1375
      _ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
1376
                  self._aminor)
1377
    # early return if we don't actually have backing storage
1378
    info = self._GetDevInfo(self._GetShowData(self.minor))
1379
    if "local_dev" not in info:
1380
      return
1381
    if len(self._children) != 2:
1382
      _ThrowError("drbd%d: we don't have two children: %s", self.minor,
1383
                  self._children)
1384
    if self._children.count(None) == 2: # we don't actually have children :)
1385
      logging.warning("drbd%d: requested detach while detached", self.minor)
1386
      return
1387
    if len(devices) != 2:
1388
      _ThrowError("drbd%d: we need two children in RemoveChildren", self.minor)
1389
    for child, dev in zip(self._children, devices):
1390
      if dev != child.dev_path:
1391
        _ThrowError("drbd%d: mismatch in local storage (%s != %s) in"
1392
                    " RemoveChildren", self.minor, dev, child.dev_path)
1393

    
1394
    self._ShutdownLocal(self.minor)
1395
    self._children = []
1396

    
1397
  @classmethod
1398
  def _SetMinorSyncSpeed(cls, minor, kbytes):
1399
    """Set the speed of the DRBD syncer.
1400

1401
    This is the low-level implementation.
1402

1403
    @type minor: int
1404
    @param minor: the drbd minor whose settings we change
1405
    @type kbytes: int
1406
    @param kbytes: the speed in kbytes/second
1407
    @rtype: boolean
1408
    @return: the success of the operation
1409

1410
    """
1411
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "syncer",
1412
                           "-r", "%d" % kbytes, "--create-device"])
1413
    if result.failed:
1414
      logging.error("Can't change syncer rate: %s - %s",
1415
                    result.fail_reason, result.output)
1416
    return not result.failed
1417

    
1418
  def SetSyncSpeed(self, kbytes):
1419
    """Set the speed of the DRBD syncer.
1420

1421
    @type kbytes: int
1422
    @param kbytes: the speed in kbytes/second
1423
    @rtype: boolean
1424
    @return: the success of the operation
1425

1426
    """
1427
    if self.minor is None:
1428
      logging.info("Not attached during SetSyncSpeed")
1429
      return False
1430
    children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1431
    return self._SetMinorSyncSpeed(self.minor, kbytes) and children_result
1432

    
1433
  def GetProcStatus(self):
1434
    """Return device data from /proc.
1435

1436
    """
1437
    if self.minor is None:
1438
      _ThrowError("drbd%d: GetStats() called while not attached", self._aminor)
1439
    proc_info = self._MassageProcData(self._GetProcData())
1440
    if self.minor not in proc_info:
1441
      _ThrowError("drbd%d: can't find myself in /proc", self.minor)
1442
    return DRBD8Status(proc_info[self.minor])
1443

    
1444
  def GetSyncStatus(self):
1445
    """Returns the sync status of the device.
1446

1447

1448
    If sync_percent is None, it means all is ok
1449
    If estimated_time is None, it means we can't estimate
1450
    the time needed, otherwise it's the time left in seconds.
1451

1452

1453
    We set the is_degraded parameter to True on two conditions:
1454
    network not connected or local disk missing.
1455

1456
    We compute the ldisk parameter based on whether we have a local
1457
    disk or not.
1458

1459
    @rtype: objects.BlockDevStatus
1460

1461
    """
1462
    if self.minor is None and not self.Attach():
1463
      _ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
1464

    
1465
    stats = self.GetProcStatus()
1466
    is_degraded = not stats.is_connected or not stats.is_disk_uptodate
1467

    
1468
    if stats.is_disk_uptodate:
1469
      ldisk_status = constants.LDS_OKAY
1470
    elif stats.is_diskless:
1471
      ldisk_status = constants.LDS_FAULTY
1472
    else:
1473
      ldisk_status = constants.LDS_UNKNOWN
1474

    
1475
    return objects.BlockDevStatus(dev_path=self.dev_path,
1476
                                  major=self.major,
1477
                                  minor=self.minor,
1478
                                  sync_percent=stats.sync_percent,
1479
                                  estimated_time=stats.est_time,
1480
                                  is_degraded=is_degraded,
1481
                                  ldisk_status=ldisk_status)
1482

    
1483
  def Open(self, force=False):
1484
    """Make the local state primary.
1485

1486
    If the 'force' parameter is given, the '-o' option is passed to
1487
    drbdsetup. Since this is a potentially dangerous operation, the
1488
    force flag should be only given after creation, when it actually
1489
    is mandatory.
1490

1491
    """
1492
    if self.minor is None and not self.Attach():
1493
      logging.error("DRBD cannot attach to a device during open")
1494
      return False
1495
    cmd = ["drbdsetup", self.dev_path, "primary"]
1496
    if force:
1497
      cmd.append("-o")
1498
    result = utils.RunCmd(cmd)
1499
    if result.failed:
1500
      _ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
1501
                  result.output)
1502

    
1503
  def Close(self):
1504
    """Make the local state secondary.
1505

1506
    This will, of course, fail if the device is in use.
1507

1508
    """
1509
    if self.minor is None and not self.Attach():
1510
      _ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
1511
    result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1512
    if result.failed:
1513
      _ThrowError("drbd%d: can't switch drbd device to secondary: %s",
1514
                  self.minor, result.output)
1515

    
1516
  def DisconnectNet(self):
1517
    """Removes network configuration.
1518

1519
    This method shutdowns the network side of the device.
1520

1521
    The method will wait up to a hardcoded timeout for the device to
1522
    go into standalone after the 'disconnect' command before
1523
    re-configuring it, as sometimes it takes a while for the
1524
    disconnect to actually propagate and thus we might issue a 'net'
1525
    command while the device is still connected. If the device will
1526
    still be attached to the network and we time out, we raise an
1527
    exception.
1528

1529
    """
1530
    if self.minor is None:
1531
      _ThrowError("drbd%d: disk not attached in re-attach net", self._aminor)
1532

    
1533
    if None in (self._lhost, self._lport, self._rhost, self._rport):
1534
      _ThrowError("drbd%d: DRBD disk missing network info in"
1535
                  " DisconnectNet()", self.minor)
1536

    
1537
    class _DisconnectStatus:
1538
      def __init__(self, ever_disconnected):
1539
        self.ever_disconnected = ever_disconnected
1540

    
1541
    dstatus = _DisconnectStatus(_IgnoreError(self._ShutdownNet, self.minor))
1542

    
1543
    def _WaitForDisconnect():
1544
      if self.GetProcStatus().is_standalone:
1545
        return
1546

    
1547
      # retry the disconnect, it seems possible that due to a well-time
1548
      # disconnect on the peer, my disconnect command might be ignored and
1549
      # forgotten
1550
      dstatus.ever_disconnected = \
1551
        _IgnoreError(self._ShutdownNet, self.minor) or dstatus.ever_disconnected
1552

    
1553
      raise utils.RetryAgain()
1554

    
1555
    # Keep start time
1556
    start_time = time.time()
1557

    
1558
    try:
1559
      # Start delay at 100 milliseconds and grow up to 2 seconds
1560
      utils.Retry(_WaitForDisconnect, (0.1, 1.5, 2.0),
1561
                  self._NET_RECONFIG_TIMEOUT)
1562
    except utils.RetryTimeout:
1563
      if dstatus.ever_disconnected:
1564
        msg = ("drbd%d: device did not react to the"
1565
               " 'disconnect' command in a timely manner")
1566
      else:
1567
        msg = "drbd%d: can't shutdown network, even after multiple retries"
1568

    
1569
      _ThrowError(msg, self.minor)
1570

    
1571
    reconfig_time = time.time() - start_time
1572
    if reconfig_time > (self._NET_RECONFIG_TIMEOUT * 0.25):
1573
      logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
1574
                   self.minor, reconfig_time)
1575

    
1576
  def AttachNet(self, multimaster):
1577
    """Reconnects the network.
1578

1579
    This method connects the network side of the device with a
1580
    specified multi-master flag. The device needs to be 'Standalone'
1581
    but have valid network configuration data.
1582

1583
    Args:
1584
      - multimaster: init the network in dual-primary mode
1585

1586
    """
1587
    if self.minor is None:
1588
      _ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
1589

    
1590
    if None in (self._lhost, self._lport, self._rhost, self._rport):
1591
      _ThrowError("drbd%d: missing network info in AttachNet()", self.minor)
1592

    
1593
    status = self.GetProcStatus()
1594

    
1595
    if not status.is_standalone:
1596
      _ThrowError("drbd%d: device is not standalone in AttachNet", self.minor)
1597

    
1598
    self._AssembleNet(self.minor,
1599
                      (self._lhost, self._lport, self._rhost, self._rport),
1600
                      constants.DRBD_NET_PROTOCOL, dual_pri=multimaster,
1601
                      hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1602

    
1603
  def Attach(self):
1604
    """Check if our minor is configured.
1605

1606
    This doesn't do any device configurations - it only checks if the
1607
    minor is in a state different from Unconfigured.
1608

1609
    Note that this function will not change the state of the system in
1610
    any way (except in case of side-effects caused by reading from
1611
    /proc).
1612

1613
    """
1614
    used_devs = self.GetUsedDevs()
1615
    if self._aminor in used_devs:
1616
      minor = self._aminor
1617
    else:
1618
      minor = None
1619

    
1620
    self._SetFromMinor(minor)
1621
    return minor is not None
1622

    
1623
  def Assemble(self):
1624
    """Assemble the drbd.
1625

1626
    Method:
1627
      - if we have a configured device, we try to ensure that it matches
1628
        our config
1629
      - if not, we create it from zero
1630

1631
    """
1632
    super(DRBD8, self).Assemble()
1633

    
1634
    self.Attach()
1635
    if self.minor is None:
1636
      # local device completely unconfigured
1637
      self._FastAssemble()
1638
    else:
1639
      # we have to recheck the local and network status and try to fix
1640
      # the device
1641
      self._SlowAssemble()
1642

    
1643
  def _SlowAssemble(self):
1644
    """Assembles the DRBD device from a (partially) configured device.
1645

1646
    In case of partially attached (local device matches but no network
1647
    setup), we perform the network attach. If successful, we re-test
1648
    the attach if can return success.
1649

1650
    """
1651
    # TODO: Rewrite to not use a for loop just because there is 'break'
1652
    # pylint: disable-msg=W0631
1653
    net_data = (self._lhost, self._lport, self._rhost, self._rport)
1654
    for minor in (self._aminor,):
1655
      info = self._GetDevInfo(self._GetShowData(minor))
1656
      match_l = self._MatchesLocal(info)
1657
      match_r = self._MatchesNet(info)
1658

    
1659
      if match_l and match_r:
1660
        # everything matches
1661
        break
1662

    
1663
      if match_l and not match_r and "local_addr" not in info:
1664
        # disk matches, but not attached to network, attach and recheck
1665
        self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1666
                          hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1667
        if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1668
          break
1669
        else:
1670
          _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1671
                      " show' disagrees", minor)
1672

    
1673
      if match_r and "local_dev" not in info:
1674
        # no local disk, but network attached and it matches
1675
        self._AssembleLocal(minor, self._children[0].dev_path,
1676
                            self._children[1].dev_path, self.size)
1677
        if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1678
          break
1679
        else:
1680
          _ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
1681
                      " show' disagrees", minor)
1682

    
1683
      # this case must be considered only if we actually have local
1684
      # storage, i.e. not in diskless mode, because all diskless
1685
      # devices are equal from the point of view of local
1686
      # configuration
1687
      if (match_l and "local_dev" in info and
1688
          not match_r and "local_addr" in info):
1689
        # strange case - the device network part points to somewhere
1690
        # else, even though its local storage is ours; as we own the
1691
        # drbd space, we try to disconnect from the remote peer and
1692
        # reconnect to our correct one
1693
        try:
1694
          self._ShutdownNet(minor)
1695
        except errors.BlockDeviceError, err:
1696
          _ThrowError("drbd%d: device has correct local storage, wrong"
1697
                      " remote peer and is unable to disconnect in order"
1698
                      " to attach to the correct peer: %s", minor, str(err))
1699
        # note: _AssembleNet also handles the case when we don't want
1700
        # local storage (i.e. one or more of the _[lr](host|port) is
1701
        # None)
1702
        self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1703
                          hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1704
        if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1705
          break
1706
        else:
1707
          _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1708
                      " show' disagrees", minor)
1709

    
1710
    else:
1711
      minor = None
1712

    
1713
    self._SetFromMinor(minor)
1714
    if minor is None:
1715
      _ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
1716
                  self._aminor)
1717

    
1718
  def _FastAssemble(self):
1719
    """Assemble the drbd device from zero.
1720

1721
    This is run when in Assemble we detect our minor is unused.
1722

1723
    """
1724
    minor = self._aminor
1725
    if self._children and self._children[0] and self._children[1]:
1726
      self._AssembleLocal(minor, self._children[0].dev_path,
1727
                          self._children[1].dev_path, self.size)
1728
    if self._lhost and self._lport and self._rhost and self._rport:
1729
      self._AssembleNet(minor,
1730
                        (self._lhost, self._lport, self._rhost, self._rport),
1731
                        constants.DRBD_NET_PROTOCOL,
1732
                        hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1733
    self._SetFromMinor(minor)
1734

    
1735
  @classmethod
1736
  def _ShutdownLocal(cls, minor):
1737
    """Detach from the local device.
1738

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

1742
    """
1743
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1744
    if result.failed:
1745
      _ThrowError("drbd%d: can't detach local disk: %s", minor, result.output)
1746

    
1747
  @classmethod
1748
  def _ShutdownNet(cls, minor):
1749
    """Disconnect from the remote peer.
1750

1751
    This fails if we don't have a local device.
1752

1753
    """
1754
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1755
    if result.failed:
1756
      _ThrowError("drbd%d: can't shutdown network: %s", minor, result.output)
1757

    
1758
  @classmethod
1759
  def _ShutdownAll(cls, minor):
1760
    """Deactivate the device.
1761

1762
    This will, of course, fail if the device is in use.
1763

1764
    """
1765
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
1766
    if result.failed:
1767
      _ThrowError("drbd%d: can't shutdown drbd device: %s",
1768
                  minor, result.output)
1769

    
1770
  def Shutdown(self):
1771
    """Shutdown the DRBD device.
1772

1773
    """
1774
    if self.minor is None and not self.Attach():
1775
      logging.info("drbd%d: not attached during Shutdown()", self._aminor)
1776
      return
1777
    minor = self.minor
1778
    self.minor = None
1779
    self.dev_path = None
1780
    self._ShutdownAll(minor)
1781

    
1782
  def Remove(self):
1783
    """Stub remove for DRBD devices.
1784

1785
    """
1786
    self.Shutdown()
1787

    
1788
  @classmethod
1789
  def Create(cls, unique_id, children, size):
1790
    """Create a new DRBD8 device.
1791

1792
    Since DRBD devices are not created per se, just assembled, this
1793
    function only initializes the metadata.
1794

1795
    """
1796
    if len(children) != 2:
1797
      raise errors.ProgrammerError("Invalid setup for the drbd device")
1798
    # check that the minor is unused
1799
    aminor = unique_id[4]
1800
    proc_info = cls._MassageProcData(cls._GetProcData())
1801
    if aminor in proc_info:
1802
      status = DRBD8Status(proc_info[aminor])
1803
      in_use = status.is_in_use
1804
    else:
1805
      in_use = False
1806
    if in_use:
1807
      _ThrowError("drbd%d: minor is already in use at Create() time", aminor)
1808
    meta = children[1]
1809
    meta.Assemble()
1810
    if not meta.Attach():
1811
      _ThrowError("drbd%d: can't attach to meta device '%s'",
1812
                  aminor, meta)
1813
    cls._CheckMetaSize(meta.dev_path)
1814
    cls._InitMeta(aminor, meta.dev_path)
1815
    return cls(unique_id, children, size)
1816

    
1817
  def Grow(self, amount):
1818
    """Resize the DRBD device and its backing storage.
1819

1820
    """
1821
    if self.minor is None:
1822
      _ThrowError("drbd%d: Grow called while not attached", self._aminor)
1823
    if len(self._children) != 2 or None in self._children:
1824
      _ThrowError("drbd%d: cannot grow diskless device", self.minor)
1825
    self._children[0].Grow(amount)
1826
    result = utils.RunCmd(["drbdsetup", self.dev_path, "resize", "-s",
1827
                           "%dm" % (self.size + amount)])
1828
    if result.failed:
1829
      _ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
1830

    
1831

    
1832
class FileStorage(BlockDev):
1833
  """File device.
1834

1835
  This class represents the a file storage backend device.
1836

1837
  The unique_id for the file device is a (file_driver, file_path) tuple.
1838

1839
  """
1840
  def __init__(self, unique_id, children, size):
1841
    """Initalizes a file device backend.
1842

1843
    """
1844
    if children:
1845
      raise errors.BlockDeviceError("Invalid setup for file device")
1846
    super(FileStorage, self).__init__(unique_id, children, size)
1847
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1848
      raise ValueError("Invalid configuration data %s" % str(unique_id))
1849
    self.driver = unique_id[0]
1850
    self.dev_path = unique_id[1]
1851
    self.Attach()
1852

    
1853
  def Assemble(self):
1854
    """Assemble the device.
1855

1856
    Checks whether the file device exists, raises BlockDeviceError otherwise.
1857

1858
    """
1859
    if not os.path.exists(self.dev_path):
1860
      _ThrowError("File device '%s' does not exist" % self.dev_path)
1861

    
1862
  def Shutdown(self):
1863
    """Shutdown the device.
1864

1865
    This is a no-op for the file type, as we don't deactivate
1866
    the file on shutdown.
1867

1868
    """
1869
    pass
1870

    
1871
  def Open(self, force=False):
1872
    """Make the device ready for I/O.
1873

1874
    This is a no-op for the file type.
1875

1876
    """
1877
    pass
1878

    
1879
  def Close(self):
1880
    """Notifies that the device will no longer be used for I/O.
1881

1882
    This is a no-op for the file type.
1883

1884
    """
1885
    pass
1886

    
1887
  def Remove(self):
1888
    """Remove the file backing the block device.
1889

1890
    @rtype: boolean
1891
    @return: True if the removal was successful
1892

1893
    """
1894
    try:
1895
      os.remove(self.dev_path)
1896
    except OSError, err:
1897
      if err.errno != errno.ENOENT:
1898
        _ThrowError("Can't remove file '%s': %s", self.dev_path, err)
1899

    
1900
  def Rename(self, new_id):
1901
    """Renames the file.
1902

1903
    """
1904
    # TODO: implement rename for file-based storage
1905
    _ThrowError("Rename is not supported for file-based storage")
1906

    
1907
  def Grow(self, amount):
1908
    """Grow the file
1909

1910
    @param amount: the amount (in mebibytes) to grow with
1911

1912
    """
1913
    # Check that the file exists
1914
    self.Assemble()
1915
    current_size = self.GetActualSize()
1916
    new_size = current_size + amount * 1024 * 1024
1917
    assert new_size > current_size, "Cannot Grow with a negative amount"
1918
    try:
1919
      f = open(self.dev_path, "a+")
1920
      f.truncate(new_size)
1921
      f.close()
1922
    except EnvironmentError, err:
1923
      _ThrowError("Error in file growth: %", str(err))
1924

    
1925
  def Attach(self):
1926
    """Attach to an existing file.
1927

1928
    Check if this file already exists.
1929

1930
    @rtype: boolean
1931
    @return: True if file exists
1932

1933
    """
1934
    self.attached = os.path.exists(self.dev_path)
1935
    return self.attached
1936

    
1937
  def GetActualSize(self):
1938
    """Return the actual disk size.
1939

1940
    @note: the device needs to be active when this is called
1941

1942
    """
1943
    assert self.attached, "BlockDevice not attached in GetActualSize()"
1944
    try:
1945
      st = os.stat(self.dev_path)
1946
      return st.st_size
1947
    except OSError, err:
1948
      _ThrowError("Can't stat %s: %s", self.dev_path, err)
1949

    
1950
  @classmethod
1951
  def Create(cls, unique_id, children, size):
1952
    """Create a new file.
1953

1954
    @param size: the size of file in MiB
1955

1956
    @rtype: L{bdev.FileStorage}
1957
    @return: an instance of FileStorage
1958

1959
    """
1960
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1961
      raise ValueError("Invalid configuration data %s" % str(unique_id))
1962
    dev_path = unique_id[1]
1963
    try:
1964
      fd = os.open(dev_path, os.O_RDWR | os.O_CREAT | os.O_EXCL)
1965
      f = os.fdopen(fd, "w")
1966
      f.truncate(size * 1024 * 1024)
1967
      f.close()
1968
    except EnvironmentError, err:
1969
      if err.errno == errno.EEXIST:
1970
        _ThrowError("File already existing: %s", dev_path)
1971
      _ThrowError("Error in file creation: %", str(err))
1972

    
1973
    return FileStorage(unique_id, children, size)
1974

    
1975

    
1976
DEV_MAP = {
1977
  constants.LD_LV: LogicalVolume,
1978
  constants.LD_DRBD8: DRBD8,
1979
  }
1980

    
1981
if constants.ENABLE_FILE_STORAGE:
1982
  DEV_MAP[constants.LD_FILE] = FileStorage
1983

    
1984

    
1985
def FindDevice(dev_type, unique_id, children, size):
1986
  """Search for an existing, assembled device.
1987

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

1991
  """
1992
  if dev_type not in DEV_MAP:
1993
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1994
  device = DEV_MAP[dev_type](unique_id, children, size)
1995
  if not device.attached:
1996
    return None
1997
  return device
1998

    
1999

    
2000
def Assemble(dev_type, unique_id, children, size):
2001
  """Try to attach or assemble an existing device.
2002

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

2006
  """
2007
  if dev_type not in DEV_MAP:
2008
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2009
  device = DEV_MAP[dev_type](unique_id, children, size)
2010
  device.Assemble()
2011
  return device
2012

    
2013

    
2014
def Create(dev_type, unique_id, children, size):
2015
  """Create a device.
2016

2017
  """
2018
  if dev_type not in DEV_MAP:
2019
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2020
  device = DEV_MAP[dev_type].Create(unique_id, children, size)
2021
  return device