Statistics
| Branch: | Tag: | Revision:

root / lib / bdev.py @ a0c3fea1

History | View | Annotate | Download (44.4 kB)

1
#!/usr/bin/python
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

    
28
from ganeti import utils
29
from ganeti import logger
30
from ganeti import errors
31

    
32

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

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

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

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

54
  The status of the device can be examined by `GetStatus()`, which
55
  returns a numerical value, depending on the position in the
56
  transition stack of the device.
57

58
  A block device is identified by three items:
59
    - the /dev path of the device (dynamic)
60
    - a unique ID of the device (static)
61
    - it's major/minor pair (dynamic)
62

63
  Not all devices implement both the first two as distinct items. LVM
64
  logical volumes have their unique ID (the pair volume group, logical
65
  volume name) in a 1-to-1 relation to the dev path. For MD devices,
66
  the /dev path is dynamic and the unique ID is the UUID generated at
67
  array creation plus the slave list. For DRBD devices, the /dev path
68
  is again dynamic and the unique id is the pair (host1, dev1),
69
  (host2, dev2).
70

71
  You can get to a device in two ways:
72
    - creating the (real) device, which returns you
73
      an attached instance (lvcreate, mdadm --create)
74
    - attaching of a python instance to an existing (real) device
75

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

82
  """
83
  STATUS_UNKNOWN = 0
84
  STATUS_EXISTING = 1
85
  STATUS_STANDBY = 2
86
  STATUS_ONLINE = 3
87

    
88
  STATUS_MAP = {
89
    STATUS_UNKNOWN: "unknown",
90
    STATUS_EXISTING: "existing",
91
    STATUS_STANDBY: "ready for use",
92
    STATUS_ONLINE: "online",
93
    }
94

    
95

    
96
  def __init__(self, unique_id, children):
97
    self._children = children
98
    self.dev_path = None
99
    self.unique_id = unique_id
100
    self.major = None
101
    self.minor = None
102

    
103

    
104
  def Assemble(self):
105
    """Assemble the device from its components.
106

107
    If this is a plain block device (e.g. LVM) than assemble does
108
    nothing, as the LVM has no children and we don't put logical
109
    volumes offline.
110

111
    One guarantee is that after the device has been assembled, it
112
    knows its major/minor numbers. This allows other devices (usually
113
    parents) to probe correctly for their children.
114

115
    """
116
    status = True
117
    for child in self._children:
118
      if not isinstance(child, BlockDev):
119
        raise TypeError("Invalid child passed of type '%s'" % type(child))
120
      if not status:
121
        break
122
      status = status and child.Assemble()
123
      if not status:
124
        break
125
      status = status and child.Open()
126

    
127
    if not status:
128
      for child in self._children:
129
        child.Shutdown()
130
    return status
131

    
132

    
133
  def Attach(self):
134
    """Find a device which matches our config and attach to it.
135

136
    """
137
    raise NotImplementedError
138

    
139

    
140
  def Close(self):
141
    """Notifies that the device will no longer be used for I/O.
142

143
    """
144
    raise NotImplementedError
145

    
146

    
147
  @classmethod
148
  def Create(cls, unique_id, children, size):
149
    """Create the device.
150

151
    If the device cannot be created, it will return None
152
    instead. Error messages go to the logging system.
153

154
    Note that for some devices, the unique_id is used, and for other,
155
    the children. The idea is that these two, taken together, are
156
    enough for both creation and assembly (later).
157

158
    """
159
    raise NotImplementedError
160

    
161

    
162
  def Remove(self):
163
    """Remove this device.
164

165
    This makes sense only for some of the device types: LV and to a
166
    lesser degree, md devices. Also note that if the device can't
167
    attach, the removal can't be completed.
168

169
    """
170
    raise NotImplementedError
171

    
172

    
173
  def GetStatus(self):
174
    """Return the status of the device.
175

176
    """
177
    raise NotImplementedError
178

    
179

    
180
  def Open(self, force=False):
181
    """Make the device ready for use.
182

183
    This makes the device ready for I/O. For now, just the DRBD
184
    devices need this.
185

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

189
    """
190
    raise NotImplementedError
191

    
192

    
193
  def Shutdown(self):
194
    """Shut down the device, freeing its children.
195

196
    This undoes the `Assemble()` work, except for the child
197
    assembling; as such, the children on the device are still
198
    assembled after this call.
199

200
    """
201
    raise NotImplementedError
202

    
203

    
204
  def SetSyncSpeed(self, speed):
205
    """Adjust the sync speed of the mirror.
206

207
    In case this is not a mirroring device, this is no-op.
208

209
    """
210
    result = True
211
    if self._children:
212
      for child in self._children:
213
        result = result and child.SetSyncSpeed(speed)
214
    return result
215

    
216

    
217
  def GetSyncStatus(self):
218
    """Returns the sync status of the device.
219

220
    If this device is a mirroring device, this function returns the
221
    status of the mirror.
222

223
    Returns:
224
     (sync_percent, estimated_time, is_degraded)
225

226
    If sync_percent is None, it means all is ok
227
    If estimated_time is None, it means we can't estimate
228
    the time needed, otherwise it's the time left in seconds
229
    If is_degraded is True, it means the device is missing
230
    redundancy. This is usually a sign that something went wrong in
231
    the device setup, if sync_percent is None.
232

233
    """
234
    return None, None, False
235

    
236

    
237
  def CombinedSyncStatus(self):
238
    """Calculate the mirror status recursively for our children.
239

240
    The return value is the same as for `GetSyncStatus()` except the
241
    minimum percent and maximum time are calculated across our
242
    children.
243

244
    """
245
    min_percent, max_time, is_degraded = self.GetSyncStatus()
246
    if self._children:
247
      for child in self._children:
248
        c_percent, c_time, c_degraded = child.GetSyncStatus()
249
        if min_percent is None:
250
          min_percent = c_percent
251
        elif c_percent is not None:
252
          min_percent = min(min_percent, c_percent)
253
        if max_time is None:
254
          max_time = c_time
255
        elif c_time is not None:
256
          max_time = max(max_time, c_time)
257
        is_degraded = is_degraded or c_degraded
258
    return min_percent, max_time, is_degraded
259

    
260

    
261
  def SetInfo(self, text):
262
    """Update metadata with info text.
263

264
    Only supported for some device types.
265

266
    """
267
    for child in self._children:
268
      child.SetInfo(text)
269

    
270

    
271
  def __repr__(self):
272
    return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
273
            (self.__class__, self.unique_id, self._children,
274
             self.major, self.minor, self.dev_path))
275

    
276

    
277
class LogicalVolume(BlockDev):
278
  """Logical Volume block device.
279

280
  """
281
  def __init__(self, unique_id, children):
282
    """Attaches to a LV device.
283

284
    The unique_id is a tuple (vg_name, lv_name)
285

286
    """
287
    super(LogicalVolume, self).__init__(unique_id, children)
288
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
289
      raise ValueError("Invalid configuration data %s" % str(unique_id))
290
    self._vg_name, self._lv_name = unique_id
291
    self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
292
    self.Attach()
293

    
294

    
295
  @classmethod
296
  def Create(cls, unique_id, children, size):
297
    """Create a new logical volume.
298

299
    """
300
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
301
      raise ValueError("Invalid configuration data %s" % str(unique_id))
302
    vg_name, lv_name = unique_id
303
    pvs_info = cls.GetPVInfo(vg_name)
304
    if not pvs_info:
305
      raise errors.BlockDeviceError, ("Can't compute PV info for vg %s" %
306
                                      vg_name)
307
    pvs_info.sort()
308
    pvs_info.reverse()
309

    
310
    pvlist = [ pv[1] for pv in pvs_info ]
311
    free_size = sum([ pv[0] for pv in pvs_info ])
312

    
313
    # The size constraint should have been checked from the master before
314
    # calling the create function.
315
    if free_size < size:
316
      raise errors.BlockDeviceError, ("Not enough free space: required %s,"
317
                                      " available %s" % (size, free_size))
318
    result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-n%s" % lv_name,
319
                           vg_name] + pvlist)
320
    if result.failed:
321
      raise errors.BlockDeviceError(result.fail_reason)
322
    return LogicalVolume(unique_id, children)
323

    
324
  @staticmethod
325
  def GetPVInfo(vg_name):
326
    """Get the free space info for PVs in a volume group.
327

328
    Args:
329
      vg_name: the volume group name
330

331
    Returns:
332
      list of (free_space, name) with free_space in mebibytes
333

334
    """
335
    command = ["pvs", "--noheadings", "--nosuffix", "--units=m",
336
               "-opv_name,vg_name,pv_free,pv_attr", "--unbuffered",
337
               "--separator=:"]
338
    result = utils.RunCmd(command)
339
    if result.failed:
340
      logger.Error("Can't get the PV information: %s" % result.fail_reason)
341
      return None
342
    data = []
343
    for line in result.stdout.splitlines():
344
      fields = line.strip().split(':')
345
      if len(fields) != 4:
346
        logger.Error("Can't parse pvs output: line '%s'" % line)
347
        return None
348
      # skip over pvs from another vg or ones which are not allocatable
349
      if fields[1] != vg_name or fields[3][0] != 'a':
350
        continue
351
      data.append((float(fields[2]), fields[0]))
352

    
353
    return data
354

    
355
  def Remove(self):
356
    """Remove this logical volume.
357

358
    """
359
    if not self.minor and not self.Attach():
360
      # the LV does not exist
361
      return True
362
    result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
363
                           (self._vg_name, self._lv_name)])
364
    if result.failed:
365
      logger.Error("Can't lvremove: %s" % result.fail_reason)
366

    
367
    return not result.failed
368

    
369

    
370
  def Attach(self):
371
    """Attach to an existing LV.
372

373
    This method will try to see if an existing and active LV exists
374
    which matches the our name. If so, its major/minor will be
375
    recorded.
376

377
    """
378
    result = utils.RunCmd(["lvdisplay", self.dev_path])
379
    if result.failed:
380
      logger.Error("Can't find LV %s: %s" %
381
                   (self.dev_path, result.fail_reason))
382
      return False
383
    match = re.compile("^ *Block device *([0-9]+):([0-9]+).*$")
384
    for line in result.stdout.splitlines():
385
      match_result = match.match(line)
386
      if match_result:
387
        self.major = int(match_result.group(1))
388
        self.minor = int(match_result.group(2))
389
        return True
390
    return False
391

    
392

    
393
  def Assemble(self):
394
    """Assemble the device.
395

396
    This is a no-op for the LV device type. Eventually, we could
397
    lvchange -ay here if we see that the LV is not active.
398

399
    """
400
    return True
401

    
402

    
403
  def Shutdown(self):
404
    """Shutdown the device.
405

406
    This is a no-op for the LV device type, as we don't deactivate the
407
    volumes on shutdown.
408

409
    """
410
    return True
411

    
412

    
413
  def GetStatus(self):
414
    """Return the status of the device.
415

416
    Logical volumes will can be in all four states, although we don't
417
    deactivate (lvchange -an) them when shutdown, so STATUS_EXISTING
418
    should not be seen for our devices.
419

420
    """
421
    result = utils.RunCmd(["lvs", "--noheadings", "-olv_attr", self.dev_path])
422
    if result.failed:
423
      logger.Error("Can't display lv: %s" % result.fail_reason)
424
      return self.STATUS_UNKNOWN
425
    out = result.stdout.strip()
426
    # format: type/permissions/alloc/fixed_minor/state/open
427
    if len(out) != 6:
428
      return self.STATUS_UNKNOWN
429
    #writable = (out[1] == "w")
430
    active = (out[4] == "a")
431
    online = (out[5] == "o")
432
    if online:
433
      retval = self.STATUS_ONLINE
434
    elif active:
435
      retval = self.STATUS_STANDBY
436
    else:
437
      retval = self.STATUS_EXISTING
438

    
439
    return retval
440

    
441

    
442
  def Open(self, force=False):
443
    """Make the device ready for I/O.
444

445
    This is a no-op for the LV device type.
446

447
    """
448
    return True
449

    
450

    
451
  def Close(self):
452
    """Notifies that the device will no longer be used for I/O.
453

454
    This is a no-op for the LV device type.
455

456
    """
457
    return True
458

    
459

    
460
  def Snapshot(self, size):
461
    """Create a snapshot copy of an lvm block device.
462

463
    """
464
    snap_name = self._lv_name + ".snap"
465

    
466
    # remove existing snapshot if found
467
    snap = LogicalVolume((self._vg_name, snap_name), None)
468
    snap.Remove()
469

    
470
    pvs_info = self.GetPVInfo(self._vg_name)
471
    if not pvs_info:
472
      raise errors.BlockDeviceError, ("Can't compute PV info for vg %s" %
473
                                      self._vg_name)
474
    pvs_info.sort()
475
    pvs_info.reverse()
476
    free_size, pv_name = pvs_info[0]
477
    if free_size < size:
478
      raise errors.BlockDeviceError, ("Not enough free space: required %s,"
479
                                      " available %s" % (size, free_size))
480

    
481
    result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
482
                           "-n%s" % snap_name, self.dev_path])
483
    if result.failed:
484
      raise errors.BlockDeviceError, ("command: %s error: %s" %
485
                                      (result.cmd, result.fail_reason))
486

    
487
    return snap_name
488

    
489

    
490
  def SetInfo(self, text):
491
    """Update metadata with info text.
492

493
    """
494
    BlockDev.SetInfo(self, text)
495

    
496
    # Replace invalid characters
497
    text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
498
    text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
499

    
500
    # Only up to 128 characters are allowed
501
    text = text[:128]
502

    
503
    result = utils.RunCmd(["lvchange", "--addtag", text,
504
                           self.dev_path])
505
    if result.failed:
506
      raise errors.BlockDeviceError, ("Command: %s error: %s" %
507
                                      (result.cmd, result.fail_reason))
508

    
509

    
510
class MDRaid1(BlockDev):
511
  """raid1 device implemented via md.
512

513
  """
514
  def __init__(self, unique_id, children):
515
    super(MDRaid1, self).__init__(unique_id, children)
516
    self.major = 9
517
    self.Attach()
518

    
519

    
520
  def Attach(self):
521
    """Find an array which matches our config and attach to it.
522

523
    This tries to find a MD array which has the same UUID as our own.
524

525
    """
526
    minor = self._FindMDByUUID(self.unique_id)
527
    if minor is not None:
528
      self._SetFromMinor(minor)
529
    else:
530
      self.minor = None
531
      self.dev_path = None
532

    
533
    return (minor is not None)
534

    
535

    
536
  @staticmethod
537
  def _GetUsedDevs():
538
    """Compute the list of in-use MD devices.
539

540
    It doesn't matter if the used device have other raid level, just
541
    that they are in use.
542

543
    """
544
    mdstat = open("/proc/mdstat", "r")
545
    data = mdstat.readlines()
546
    mdstat.close()
547

    
548
    used_md = {}
549
    valid_line = re.compile("^md([0-9]+) : .*$")
550
    for line in data:
551
      match = valid_line.match(line)
552
      if match:
553
        md_no = int(match.group(1))
554
        used_md[md_no] = line
555

    
556
    return used_md
557

    
558

    
559
  @staticmethod
560
  def _GetDevInfo(minor):
561
    """Get info about a MD device.
562

563
    Currently only uuid is returned.
564

565
    """
566
    result = utils.RunCmd(["mdadm", "-D", "/dev/md%d" % minor])
567
    if result.failed:
568
      logger.Error("Can't display md: %s" % result.fail_reason)
569
      return None
570
    retval = {}
571
    for line in result.stdout.splitlines():
572
      line = line.strip()
573
      kv = line.split(" : ", 1)
574
      if kv:
575
        if kv[0] == "UUID":
576
          retval["uuid"] = kv[1]
577
        elif kv[0] == "State":
578
          retval["state"] = kv[1].split(", ")
579
    return retval
580

    
581

    
582
  @staticmethod
583
  def _FindUnusedMinor():
584
    """Compute an unused MD minor.
585

586
    This code assumes that there are 256 minors only.
587

588
    """
589
    used_md = MDRaid1._GetUsedDevs()
590
    i = 0
591
    while i < 256:
592
      if i not in used_md:
593
        break
594
      i += 1
595
    if i == 256:
596
      logger.Error("Critical: Out of md minor numbers.")
597
      return None
598
    return i
599

    
600

    
601
  @classmethod
602
  def _FindMDByUUID(cls, uuid):
603
    """Find the minor of an MD array with a given UUID.
604

605
    """
606
    md_list = cls._GetUsedDevs()
607
    for minor in md_list:
608
      info = cls._GetDevInfo(minor)
609
      if info and info["uuid"] == uuid:
610
        return minor
611
    return None
612

    
613

    
614
  @classmethod
615
  def Create(cls, unique_id, children, size):
616
    """Create a new MD raid1 array.
617

618
    """
619
    if not isinstance(children, (tuple, list)):
620
      raise ValueError("Invalid setup data for MDRaid1 dev: %s" %
621
                       str(children))
622
    for i in children:
623
      if not isinstance(i, BlockDev):
624
        raise ValueError("Invalid member in MDRaid1 dev: %s" % type(i))
625
    for i in children:
626
      result = utils.RunCmd(["mdadm", "--zero-superblock", "--force",
627
                             i.dev_path])
628
      if result.failed:
629
        logger.Error("Can't zero superblock: %s" % result.fail_reason)
630
        return None
631
    minor = cls._FindUnusedMinor()
632
    result = utils.RunCmd(["mdadm", "--create", "/dev/md%d" % minor,
633
                           "--auto=yes", "--force", "-l1",
634
                           "-n%d" % len(children)] +
635
                          [dev.dev_path for dev in children])
636

    
637
    if result.failed:
638
      logger.Error("Can't create md: %s" % result.fail_reason)
639
      return None
640
    info = cls._GetDevInfo(minor)
641
    if not info or not "uuid" in info:
642
      logger.Error("Wrong information returned from mdadm -D: %s" % str(info))
643
      return None
644
    return MDRaid1(info["uuid"], children)
645

    
646

    
647
  def Remove(self):
648
    """Stub remove function for MD RAID 1 arrays.
649

650
    We don't remove the superblock right now. Mark a to do.
651

652
    """
653
    #TODO: maybe zero superblock on child devices?
654
    return self.Shutdown()
655

    
656

    
657
  def AddChild(self, device):
658
    """Add a new member to the md raid1.
659

660
    """
661
    if self.minor is None and not self.Attach():
662
      raise errors.BlockDeviceError, "Can't attach to device"
663
    if device.dev_path is None:
664
      raise errors.BlockDeviceError, "New child is not initialised"
665
    result = utils.RunCmd(["mdadm", "-a", self.dev_path, device.dev_path])
666
    if result.failed:
667
      raise errors.BlockDeviceError, ("Failed to add new device to array: %s" %
668
                                      result.output)
669
    new_len = len(self._children) + 1
670
    result = utils.RunCmd(["mdadm", "--grow", self.dev_path, "-n", new_len])
671
    if result.failed:
672
      raise errors.BlockDeviceError, ("Can't grow md array: %s" %
673
                                      result.output)
674
    self._children.append(device)
675

    
676

    
677
  def RemoveChild(self, dev_path):
678
    """Remove member from the md raid1.
679

680
    """
681
    if self.minor is None and not self.Attach():
682
      raise errors.BlockDeviceError, "Can't attach to device"
683
    if len(self._children) == 1:
684
      raise errors.BlockDeviceError, ("Can't reduce member when only one"
685
                                      " child left")
686
    for device in self._children:
687
      if device.dev_path == dev_path:
688
        break
689
    else:
690
      raise errors.BlockDeviceError, "Can't find child with this path"
691
    new_len = len(self._children) - 1
692
    result = utils.RunCmd(["mdadm", "-f", self.dev_path, dev_path])
693
    if result.failed:
694
      raise errors.BlockDeviceError, ("Failed to mark device as failed: %s" %
695
                                      result.output)
696

    
697
    # it seems here we need a short delay for MD to update its
698
    # superblocks
699
    time.sleep(0.5)
700
    result = utils.RunCmd(["mdadm", "-r", self.dev_path, dev_path])
701
    if result.failed:
702
      raise errors.BlockDeviceError, ("Failed to remove device from array:"
703
                                      " %s" % result.output)
704
    result = utils.RunCmd(["mdadm", "--grow", "--force", self.dev_path,
705
                           "-n", new_len])
706
    if result.failed:
707
      raise errors.BlockDeviceError, ("Can't shrink md array: %s" %
708
                                      result.output)
709
    self._children.remove(device)
710

    
711

    
712
  def GetStatus(self):
713
    """Return the status of the device.
714

715
    """
716
    self.Attach()
717
    if self.minor is None:
718
      retval = self.STATUS_UNKNOWN
719
    else:
720
      retval = self.STATUS_ONLINE
721
    return retval
722

    
723

    
724
  def _SetFromMinor(self, minor):
725
    """Set our parameters based on the given minor.
726

727
    This sets our minor variable and our dev_path.
728

729
    """
730
    self.minor = minor
731
    self.dev_path = "/dev/md%d" % minor
732

    
733

    
734
  def Assemble(self):
735
    """Assemble the MD device.
736

737
    At this point we should have:
738
      - list of children devices
739
      - uuid
740

741
    """
742
    result = super(MDRaid1, self).Assemble()
743
    if not result:
744
      return result
745
    md_list = self._GetUsedDevs()
746
    for minor in md_list:
747
      info = self._GetDevInfo(minor)
748
      if info and info["uuid"] == self.unique_id:
749
        self._SetFromMinor(minor)
750
        logger.Info("MD array %s already started" % str(self))
751
        return True
752
    free_minor = self._FindUnusedMinor()
753
    result = utils.RunCmd(["mdadm", "-A", "--auto=yes", "--uuid",
754
                           self.unique_id, "/dev/md%d" % free_minor] +
755
                          [bdev.dev_path for bdev in self._children])
756
    if result.failed:
757
      logger.Error("Can't assemble MD array: %s" % result.fail_reason)
758
      self.minor = None
759
    else:
760
      self.minor = free_minor
761
    return not result.failed
762

    
763

    
764
  def Shutdown(self):
765
    """Tear down the MD array.
766

767
    This does a 'mdadm --stop' so after this command, the array is no
768
    longer available.
769

770
    """
771
    if self.minor is None and not self.Attach():
772
      logger.Info("MD object not attached to a device")
773
      return True
774

    
775
    result = utils.RunCmd(["mdadm", "--stop", "/dev/md%d" % self.minor])
776
    if result.failed:
777
      logger.Error("Can't stop MD array: %s" % result.fail_reason)
778
      return False
779
    self.minor = None
780
    self.dev_path = None
781
    return True
782

    
783

    
784
  def SetSyncSpeed(self, kbytes):
785
    """Set the maximum sync speed for the MD array.
786

787
    """
788
    result = super(MDRaid1, self).SetSyncSpeed(kbytes)
789
    if self.minor is None:
790
      logger.Error("MD array not attached to a device")
791
      return False
792
    f = open("/sys/block/md%d/md/sync_speed_max" % self.minor, "w")
793
    try:
794
      f.write("%d" % kbytes)
795
    finally:
796
      f.close()
797
    f = open("/sys/block/md%d/md/sync_speed_min" % self.minor, "w")
798
    try:
799
      f.write("%d" % (kbytes/2))
800
    finally:
801
      f.close()
802
    return result
803

    
804

    
805
  def GetSyncStatus(self):
806
    """Returns the sync status of the device.
807

808
    Returns:
809
     (sync_percent, estimated_time)
810

811
    If sync_percent is None, it means all is ok
812
    If estimated_time is None, it means we can't esimate
813
    the time needed, otherwise it's the time left in seconds
814

815
    """
816
    if self.minor is None and not self.Attach():
817
      raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
818
    dev_info = self._GetDevInfo(self.minor)
819
    is_clean = ("state" in dev_info and
820
                len(dev_info["state"]) == 1 and
821
                dev_info["state"][0] in ("clean", "active"))
822
    sys_path = "/sys/block/md%s/md/" % self.minor
823
    f = file(sys_path + "sync_action")
824
    sync_status = f.readline().strip()
825
    f.close()
826
    if sync_status == "idle":
827
      return None, None, not is_clean
828
    f = file(sys_path + "sync_completed")
829
    sync_completed = f.readline().strip().split(" / ")
830
    f.close()
831
    if len(sync_completed) != 2:
832
      return 0, None, not is_clean
833
    sync_done, sync_total = [float(i) for i in sync_completed]
834
    sync_percent = 100.0*sync_done/sync_total
835
    f = file(sys_path + "sync_speed")
836
    sync_speed_k = int(f.readline().strip())
837
    if sync_speed_k == 0:
838
      time_est = None
839
    else:
840
      time_est = (sync_total - sync_done) / 2 / sync_speed_k
841
    return sync_percent, time_est, not is_clean
842

    
843

    
844
  def Open(self, force=False):
845
    """Make the device ready for I/O.
846

847
    This is a no-op for the MDRaid1 device type, although we could use
848
    the 2.6.18's new array_state thing.
849

850
    """
851
    return True
852

    
853

    
854
  def Close(self):
855
    """Notifies that the device will no longer be used for I/O.
856

857
    This is a no-op for the MDRaid1 device type, but see comment for
858
    `Open()`.
859

860
    """
861
    return True
862

    
863

    
864

    
865
class DRBDev(BlockDev):
866
  """DRBD block device.
867

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

872
  The unique_id for the drbd device is the (local_ip, local_port,
873
  remote_ip, remote_port) tuple, and it must have two children: the
874
  data device and the meta_device. The meta device is checked for
875
  valid size and is zeroed on create.
876

877
  """
878
  _DRBD_MAJOR = 147
879
  _ST_UNCONFIGURED = "Unconfigured"
880
  _ST_WFCONNECTION = "WFConnection"
881
  _ST_CONNECTED = "Connected"
882

    
883
  def __init__(self, unique_id, children):
884
    super(DRBDev, self).__init__(unique_id, children)
885
    self.major = self._DRBD_MAJOR
886
    if len(children) != 2:
887
      raise ValueError("Invalid configuration data %s" % str(children))
888
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
889
      raise ValueError("Invalid configuration data %s" % str(unique_id))
890
    self._lhost, self._lport, self._rhost, self._rport = unique_id
891
    self.Attach()
892

    
893
  @staticmethod
894
  def _DevPath(minor):
895
    """Return the path to a drbd device for a given minor.
896

897
    """
898
    return "/dev/drbd%d" % minor
899

    
900
  @staticmethod
901
  def _GetProcData():
902
    """Return data from /proc/drbd.
903

904
    """
905
    stat = open("/proc/drbd", "r")
906
    data = stat.read().splitlines()
907
    stat.close()
908
    return data
909

    
910

    
911
  @classmethod
912
  def _GetUsedDevs(cls):
913
    """Compute the list of used DRBD devices.
914

915
    """
916
    data = cls._GetProcData()
917

    
918
    used_devs = {}
919
    valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
920
    for line in data:
921
      match = valid_line.match(line)
922
      if not match:
923
        continue
924
      minor = int(match.group(1))
925
      state = match.group(2)
926
      if state == cls._ST_UNCONFIGURED:
927
        continue
928
      used_devs[minor] = state, line
929

    
930
    return used_devs
931

    
932

    
933
  @classmethod
934
  def _FindUnusedMinor(cls):
935
    """Find an unused DRBD device.
936

937
    """
938
    data = cls._GetProcData()
939

    
940
    valid_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
941
    for line in data:
942
      match = valid_line.match(line)
943
      if match:
944
        return int(match.group(1))
945
    logger.Error("Error: no free drbd minors!")
946
    return None
947

    
948

    
949
  @classmethod
950
  def _GetDevInfo(cls, minor):
951
    """Get details about a given DRBD minor.
952

953
    This return, if available, the local backing device in (major,
954
    minor) formant and the local and remote (ip, port) information.
955

956
    """
957
    data = {}
958
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
959
    if result.failed:
960
      logger.Error("Can't display the drbd config: %s" % result.fail_reason)
961
      return data
962
    out = result.stdout
963
    if out == "Not configured\n":
964
      return data
965
    for line in out.splitlines():
966
      if "local_dev" not in data:
967
        match = re.match("^Lower device: ([0-9]+):([0-9]+) .*$", line)
968
        if match:
969
          data["local_dev"] = (int(match.group(1)), int(match.group(2)))
970
          continue
971
      if "meta_dev" not in data:
972
        match = re.match("^Meta device: (([0-9]+):([0-9]+)|internal).*$", line)
973
        if match:
974
          if match.group(2) is not None and match.group(3) is not None:
975
            # matched on the major/minor
976
            data["meta_dev"] = (int(match.group(2)), int(match.group(3)))
977
          else:
978
            # matched on the "internal" string
979
            data["meta_dev"] = match.group(1)
980
            # in this case, no meta_index is in the output
981
            data["meta_index"] = -1
982
          continue
983
      if "meta_index" not in data:
984
        match = re.match("^Meta index: ([0-9]+).*$", line)
985
        if match:
986
          data["meta_index"] = int(match.group(1))
987
          continue
988
      if "local_addr" not in data:
989
        match = re.match("^Local address: ([0-9.]+):([0-9]+)$", line)
990
        if match:
991
          data["local_addr"] = (match.group(1), int(match.group(2)))
992
          continue
993
      if "remote_addr" not in data:
994
        match = re.match("^Remote address: ([0-9.]+):([0-9]+)$", line)
995
        if match:
996
          data["remote_addr"] = (match.group(1), int(match.group(2)))
997
          continue
998
    return data
999

    
1000

    
1001
  def _MatchesLocal(self, info):
1002
    """Test if our local config matches with an existing device.
1003

1004
    The parameter should be as returned from `_GetDevInfo()`. This
1005
    method tests if our local backing device is the same as the one in
1006
    the info parameter, in effect testing if we look like the given
1007
    device.
1008

1009
    """
1010
    if not ("local_dev" in info and "meta_dev" in info and
1011
            "meta_index" in info):
1012
      return False
1013

    
1014
    backend = self._children[0]
1015
    if backend is not None:
1016
      retval = (info["local_dev"] == (backend.major, backend.minor))
1017
    else:
1018
      retval = (info["local_dev"] == (0, 0))
1019
    meta = self._children[1]
1020
    if meta is not None:
1021
      retval = retval and (info["meta_dev"] == (meta.major, meta.minor))
1022
      retval = retval and (info["meta_index"] == 0)
1023
    else:
1024
      retval = retval and (info["meta_dev"] == "internal" and
1025
                           info["meta_index"] == -1)
1026
    return retval
1027

    
1028

    
1029
  def _MatchesNet(self, info):
1030
    """Test if our network config matches with an existing device.
1031

1032
    The parameter should be as returned from `_GetDevInfo()`. This
1033
    method tests if our network configuration is the same as the one
1034
    in the info parameter, in effect testing if we look like the given
1035
    device.
1036

1037
    """
1038
    if (((self._lhost is None and not ("local_addr" in info)) and
1039
         (self._rhost is None and not ("remote_addr" in info)))):
1040
      return True
1041

    
1042
    if self._lhost is None:
1043
      return False
1044

    
1045
    if not ("local_addr" in info and
1046
            "remote_addr" in info):
1047
      return False
1048

    
1049
    retval = (info["local_addr"] == (self._lhost, self._lport))
1050
    retval = (retval and
1051
              info["remote_addr"] == (self._rhost, self._rport))
1052
    return retval
1053

    
1054

    
1055
  @staticmethod
1056
  def _IsValidMeta(meta_device):
1057
    """Check if the given meta device looks like a valid one.
1058

1059
    This currently only check the size, which must be around
1060
    128MiB.
1061

1062
    """
1063
    result = utils.RunCmd(["blockdev", "--getsize", meta_device])
1064
    if result.failed:
1065
      logger.Error("Failed to get device size: %s" % result.fail_reason)
1066
      return False
1067
    try:
1068
      sectors = int(result.stdout)
1069
    except ValueError:
1070
      logger.Error("Invalid output from blockdev: '%s'" % result.stdout)
1071
      return False
1072
    bytes = sectors * 512
1073
    if bytes < 128*1024*1024: # less than 128MiB
1074
      logger.Error("Meta device too small (%.2fMib)" % (bytes/1024/1024))
1075
      return False
1076
    if bytes > (128+32)*1024*1024: # account for an extra (big) PE on LVM
1077
      logger.Error("Meta device too big (%.2fMiB)" % (bytes/1024/1024))
1078
      return False
1079
    return True
1080

    
1081

    
1082
  @classmethod
1083
  def _AssembleLocal(cls, minor, backend, meta):
1084
    """Configure the local part of a DRBD device.
1085

1086
    This is the first thing that must be done on an unconfigured DRBD
1087
    device. And it must be done only once.
1088

1089
    """
1090
    if not cls._IsValidMeta(meta):
1091
      return False
1092
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1093
                           backend, meta, "0", "-e", "detach"])
1094
    if result.failed:
1095
      logger.Error("Can't attach local disk: %s" % result.output)
1096
    return not result.failed
1097

    
1098

    
1099
  @classmethod
1100
  def _ShutdownLocal(cls, minor):
1101
    """Detach from the local device.
1102

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

1106
    """
1107
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1108
    if result.failed:
1109
      logger.Error("Can't detach local device: %s" % result.output)
1110
    return not result.failed
1111

    
1112

    
1113
  @staticmethod
1114
  def _ShutdownAll(minor):
1115
    """Deactivate the device.
1116

1117
    This will, of course, fail if the device is in use.
1118

1119
    """
1120
    result = utils.RunCmd(["drbdsetup", DRBDev._DevPath(minor), "down"])
1121
    if result.failed:
1122
      logger.Error("Can't shutdown drbd device: %s" % result.output)
1123
    return not result.failed
1124

    
1125

    
1126
  @classmethod
1127
  def _AssembleNet(cls, minor, net_info, protocol):
1128
    """Configure the network part of the device.
1129

1130
    This operation can be, in theory, done multiple times, but there
1131
    have been cases (in lab testing) in which the network part of the
1132
    device had become stuck and couldn't be shut down because activity
1133
    from the new peer (also stuck) triggered a timer re-init and
1134
    needed remote peer interface shutdown in order to clear. So please
1135
    don't change online the net config.
1136

1137
    """
1138
    lhost, lport, rhost, rport = net_info
1139
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "net",
1140
                           "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport),
1141
                           protocol])
1142
    if result.failed:
1143
      logger.Error("Can't setup network for dbrd device: %s" %
1144
                   result.fail_reason)
1145
      return False
1146

    
1147
    timeout = time.time() + 10
1148
    ok = False
1149
    while time.time() < timeout:
1150
      info = cls._GetDevInfo(minor)
1151
      if not "local_addr" in info or not "remote_addr" in info:
1152
        time.sleep(1)
1153
        continue
1154
      if (info["local_addr"] != (lhost, lport) or
1155
          info["remote_addr"] != (rhost, rport)):
1156
        time.sleep(1)
1157
        continue
1158
      ok = True
1159
      break
1160
    if not ok:
1161
      logger.Error("Timeout while configuring network")
1162
      return False
1163
    return True
1164

    
1165

    
1166
  @classmethod
1167
  def _ShutdownNet(cls, minor):
1168
    """Disconnect from the remote peer.
1169

1170
    This fails if we don't have a local device.
1171

1172
    """
1173
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1174
    logger.Error("Can't shutdown network: %s" % result.output)
1175
    return not result.failed
1176

    
1177

    
1178
  def _SetFromMinor(self, minor):
1179
    """Set our parameters based on the given minor.
1180

1181
    This sets our minor variable and our dev_path.
1182

1183
    """
1184
    if minor is None:
1185
      self.minor = self.dev_path = None
1186
    else:
1187
      self.minor = minor
1188
      self.dev_path = self._DevPath(minor)
1189

    
1190

    
1191
  def Assemble(self):
1192
    """Assemble the drbd.
1193

1194
    Method:
1195
      - if we have a local backing device, we bind to it by:
1196
        - checking the list of used drbd devices
1197
        - check if the local minor use of any of them is our own device
1198
        - if yes, abort?
1199
        - if not, bind
1200
      - if we have a local/remote net info:
1201
        - redo the local backing device step for the remote device
1202
        - check if any drbd device is using the local port,
1203
          if yes abort
1204
        - check if any remote drbd device is using the remote
1205
          port, if yes abort (for now)
1206
        - bind our net port
1207
        - bind the remote net port
1208

1209
    """
1210
    self.Attach()
1211
    if self.minor is not None:
1212
      logger.Info("Already assembled")
1213
      return True
1214

    
1215
    result = super(DRBDev, self).Assemble()
1216
    if not result:
1217
      return result
1218

    
1219
    minor = self._FindUnusedMinor()
1220
    if minor is None:
1221
      raise errors.BlockDeviceError, "Not enough free minors for DRBD!"
1222
    need_localdev_teardown = False
1223
    if self._children[0]:
1224
      result = self._AssembleLocal(minor, self._children[0].dev_path,
1225
                                   self._children[1].dev_path)
1226
      if not result:
1227
        return False
1228
      need_localdev_teardown = True
1229
    if self._lhost and self._lport and self._rhost and self._rport:
1230
      result = self._AssembleNet(minor,
1231
                                 (self._lhost, self._lport,
1232
                                  self._rhost, self._rport),
1233
                                 "C")
1234
      if not result:
1235
        if need_localdev_teardown:
1236
          # we will ignore failures from this
1237
          logger.Error("net setup failed, tearing down local device")
1238
          self._ShutdownAll(minor)
1239
        return False
1240
    self._SetFromMinor(minor)
1241
    return True
1242

    
1243

    
1244
  def Shutdown(self):
1245
    """Shutdown the DRBD device.
1246

1247
    """
1248
    if self.minor is None and not self.Attach():
1249
      logger.Info("DRBD device not attached to a device during Shutdown")
1250
      return True
1251
    if not self._ShutdownAll(self.minor):
1252
      return False
1253
    self.minor = None
1254
    self.dev_path = None
1255
    return True
1256

    
1257

    
1258
  def Attach(self):
1259
    """Find a DRBD device which matches our config and attach to it.
1260

1261
    In case of partially attached (local device matches but no network
1262
    setup), we perform the network attach. If successful, we re-test
1263
    the attach if can return success.
1264

1265
    """
1266
    for minor in self._GetUsedDevs():
1267
      info = self._GetDevInfo(minor)
1268
      match_l = self._MatchesLocal(info)
1269
      match_r = self._MatchesNet(info)
1270
      if match_l and match_r:
1271
        break
1272
      if match_l and not match_r and "local_addr" not in info:
1273
        res_r = self._AssembleNet(minor,
1274
                                  (self._lhost, self._lport,
1275
                                   self._rhost, self._rport),
1276
                                  "C")
1277
        if res_r and self._MatchesNet(self._GetDevInfo(minor)):
1278
          break
1279
    else:
1280
      minor = None
1281

    
1282
    self._SetFromMinor(minor)
1283
    return minor is not None
1284

    
1285

    
1286
  def Open(self, force=False):
1287
    """Make the local state primary.
1288

1289
    If the 'force' parameter is given, the '--do-what-I-say' parameter
1290
    is given. Since this is a pottentialy dangerous operation, the
1291
    force flag should be only given after creation, when it actually
1292
    has to be given.
1293

1294
    """
1295
    if self.minor is None and not self.Attach():
1296
      logger.Error("DRBD cannot attach to a device during open")
1297
      return False
1298
    cmd = ["drbdsetup", self.dev_path, "primary"]
1299
    if force:
1300
      cmd.append("--do-what-I-say")
1301
    result = utils.RunCmd(cmd)
1302
    if result.failed:
1303
      logger.Error("Can't make drbd device primary: %s" % result.output)
1304
      return False
1305
    return True
1306

    
1307

    
1308
  def Close(self):
1309
    """Make the local state secondary.
1310

1311
    This will, of course, fail if the device is in use.
1312

1313
    """
1314
    if self.minor is None and not self.Attach():
1315
      logger.Info("Instance not attached to a device")
1316
      raise errors.BlockDeviceError("Can't find device")
1317
    result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1318
    if result.failed:
1319
      logger.Error("Can't switch drbd device to secondary: %s" % result.output)
1320
      raise errors.BlockDeviceError("Can't switch drbd device to secondary")
1321

    
1322

    
1323
  def SetSyncSpeed(self, kbytes):
1324
    """Set the speed of the DRBD syncer.
1325

1326
    """
1327
    children_result = super(DRBDev, self).SetSyncSpeed(kbytes)
1328
    if self.minor is None:
1329
      logger.Info("Instance not attached to a device")
1330
      return False
1331
    result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1332
                           kbytes])
1333
    if result.failed:
1334
      logger.Error("Can't change syncer rate: %s " % result.fail_reason)
1335
    return not result.failed and children_result
1336

    
1337

    
1338
  def GetSyncStatus(self):
1339
    """Returns the sync status of the device.
1340

1341
    Returns:
1342
     (sync_percent, estimated_time)
1343

1344
    If sync_percent is None, it means all is ok
1345
    If estimated_time is None, it means we can't esimate
1346
    the time needed, otherwise it's the time left in seconds
1347

1348
    """
1349
    if self.minor is None and not self.Attach():
1350
      raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1351
    proc_info = self._MassageProcData(self._GetProcData())
1352
    if self.minor not in proc_info:
1353
      raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1354
                                    self.minor)
1355
    line = proc_info[self.minor]
1356
    match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1357
                     " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1358
    if match:
1359
      sync_percent = float(match.group(1))
1360
      hours = int(match.group(2))
1361
      minutes = int(match.group(3))
1362
      seconds = int(match.group(4))
1363
      est_time = hours * 3600 + minutes * 60 + seconds
1364
    else:
1365
      sync_percent = None
1366
      est_time = None
1367
    match = re.match("^ *[0-9]+: cs:([^ ]+).*$", line)
1368
    if not match:
1369
      raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
1370
                                    self.minor)
1371
    client_state = match.group(1)
1372
    is_degraded = client_state != "Connected"
1373
    return sync_percent, est_time, is_degraded
1374

    
1375

    
1376
  @staticmethod
1377
  def _MassageProcData(data):
1378
    """Transform the output of _GetProdData into a nicer form.
1379

1380
    Returns:
1381
      a dictionary of minor: joined lines from /proc/drbd for that minor
1382

1383
    """
1384
    lmatch = re.compile("^ *([0-9]+):.*$")
1385
    results = {}
1386
    old_minor = old_line = None
1387
    for line in data:
1388
      lresult = lmatch.match(line)
1389
      if lresult is not None:
1390
        if old_minor is not None:
1391
          results[old_minor] = old_line
1392
        old_minor = int(lresult.group(1))
1393
        old_line = line
1394
      else:
1395
        if old_minor is not None:
1396
          old_line += " " + line.strip()
1397
    # add last line
1398
    if old_minor is not None:
1399
      results[old_minor] = old_line
1400
    return results
1401

    
1402

    
1403
  def GetStatus(self):
1404
    """Compute the status of the DRBD device
1405

1406
    Note that DRBD devices don't have the STATUS_EXISTING state.
1407

1408
    """
1409
    if self.minor is None and not self.Attach():
1410
      return self.STATUS_UNKNOWN
1411

    
1412
    data = self._GetProcData()
1413
    match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
1414
                       self.minor)
1415
    for line in data:
1416
      mresult = match.match(line)
1417
      if mresult:
1418
        break
1419
    else:
1420
      logger.Error("Can't find myself!")
1421
      return self.STATUS_UNKNOWN
1422

    
1423
    state = mresult.group(2)
1424
    if state == "Primary":
1425
      result = self.STATUS_ONLINE
1426
    else:
1427
      result = self.STATUS_STANDBY
1428

    
1429
    return result
1430

    
1431

    
1432
  @staticmethod
1433
  def _ZeroDevice(device):
1434
    """Zero a device.
1435

1436
    This writes until we get ENOSPC.
1437

1438
    """
1439
    f = open(device, "w")
1440
    buf = "\0" * 1048576
1441
    try:
1442
      while True:
1443
        f.write(buf)
1444
    except IOError, err:
1445
      if err.errno != errno.ENOSPC:
1446
        raise
1447

    
1448

    
1449
  @classmethod
1450
  def Create(cls, unique_id, children, size):
1451
    """Create a new DRBD device.
1452

1453
    Since DRBD devices are not created per se, just assembled, this
1454
    function just zeroes the meta device.
1455

1456
    """
1457
    if len(children) != 2:
1458
      raise errors.ProgrammerError("Invalid setup for the drbd device")
1459
    meta = children[1]
1460
    meta.Assemble()
1461
    if not meta.Attach():
1462
      raise errors.BlockDeviceError("Can't attach to meta device")
1463
    if not cls._IsValidMeta(meta.dev_path):
1464
      raise errors.BlockDeviceError("Invalid meta device")
1465
    logger.Info("Started zeroing device %s" % meta.dev_path)
1466
    cls._ZeroDevice(meta.dev_path)
1467
    logger.Info("Done zeroing device %s" % meta.dev_path)
1468
    return cls(unique_id, children)
1469

    
1470

    
1471
  def Remove(self):
1472
    """Stub remove for DRBD devices.
1473

1474
    """
1475
    return self.Shutdown()
1476

    
1477

    
1478
DEV_MAP = {
1479
  "lvm": LogicalVolume,
1480
  "md_raid1": MDRaid1,
1481
  "drbd": DRBDev,
1482
  }
1483

    
1484

    
1485
def FindDevice(dev_type, unique_id, children):
1486
  """Search for an existing, assembled device.
1487

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

1491
  """
1492
  if dev_type not in DEV_MAP:
1493
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1494
  device = DEV_MAP[dev_type](unique_id, children)
1495
  if not device.Attach():
1496
    return None
1497
  return  device
1498

    
1499

    
1500
def AttachOrAssemble(dev_type, unique_id, children):
1501
  """Try to attach or assemble an existing device.
1502

1503
  This will attach to an existing assembled device or will assemble
1504
  the device, as needed, to bring it fully up.
1505

1506
  """
1507
  if dev_type not in DEV_MAP:
1508
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1509
  device = DEV_MAP[dev_type](unique_id, children)
1510
  if not device.Attach():
1511
    device.Assemble()
1512
  if not device.Attach():
1513
    raise errors.BlockDeviceError("Can't find a valid block device for"
1514
                                  " %s/%s/%s" %
1515
                                  (dev_type, unique_id, children))
1516
  return device
1517

    
1518

    
1519
def Create(dev_type, unique_id, children, size):
1520
  """Create a device.
1521

1522
  """
1523
  if dev_type not in DEV_MAP:
1524
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1525
  device = DEV_MAP[dev_type].Create(unique_id, children, size)
1526
  return device