Statistics
| Branch: | Tag: | Revision:

root / lib / bdev.py @ aa9f8167

History | View | Annotate | Download (64.9 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007 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+)"
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):
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
    proc_data = cls._GetProcData()
889
    first_line = proc_data[0].strip()
890
    version = cls._VERSION_RE.match(first_line)
891
    if not version:
892
      raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
893
                                    first_line)
894

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

    
905
    return retval
906

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

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

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

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

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

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

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

    
949
    return used_devs
950

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

954
    This sets our minor variable and our dev_path.
955

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

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

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

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

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

996
    This is not supported for drbd devices.
997

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

    
1001

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

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

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

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

    
1018
  # timeout constants
1019
  _NET_RECONFIG_TIMEOUT = 60
1020

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1141
    cls._PARSE_SHOW = bnf
1142

    
1143
    return bnf
1144

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1402
    This is the low-level implementation.
1403

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

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

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

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

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

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

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

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

1448

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

1453

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

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

1460
    @rtype: objects.BlockDevStatus
1461

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1554
      raise utils.RetryAgain()
1555

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

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

    
1570
      _ThrowError(msg, self.minor)
1571

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

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

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

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

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

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

    
1594
    status = self.GetProcStatus()
1595

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1711
    else:
1712
      minor = None
1713

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1786
    """
1787
    self.Shutdown()
1788

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

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

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

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

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

    
1832

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

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

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

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

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

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

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

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

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

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

1869
    """
1870
    pass
1871

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

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

1877
    """
1878
    pass
1879

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

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

1885
    """
1886
    pass
1887

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

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

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

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

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

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

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

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

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

1929
    Check if this file already exists.
1930

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

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

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

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

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

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

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

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

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

    
1974
    return FileStorage(unique_id, children, size)
1975

    
1976

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

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

    
1985

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

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

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

    
2000

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

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

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

    
2014

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

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