Statistics
| Branch: | Tag: | Revision:

root / lib / bdev.py @ 098c0958

History | View | Annotate | Download (43.6 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 __repr__(self):
262
    return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
263
            (self.__class__, self.unique_id, self._children,
264
             self.major, self.minor, self.dev_path))
265

    
266

    
267
class LogicalVolume(BlockDev):
268
  """Logical Volume block device.
269

270
  """
271
  def __init__(self, unique_id, children):
272
    """Attaches to a LV device.
273

274
    The unique_id is a tuple (vg_name, lv_name)
275

276
    """
277
    super(LogicalVolume, self).__init__(unique_id, children)
278
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
279
      raise ValueError("Invalid configuration data %s" % str(unique_id))
280
    self._vg_name, self._lv_name = unique_id
281
    self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
282
    self.Attach()
283

    
284

    
285
  @classmethod
286
  def Create(cls, unique_id, children, size):
287
    """Create a new logical volume.
288

289
    """
290
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
291
      raise ValueError("Invalid configuration data %s" % str(unique_id))
292
    vg_name, lv_name = unique_id
293
    pvs_info = cls.GetPVInfo(vg_name)
294
    if not pvs_info:
295
      raise errors.BlockDeviceError, ("Can't compute PV info for vg %s" %
296
                                      vg_name)
297
    pvs_info.sort()
298
    pvs_info.reverse()
299

    
300
    pvlist = [ pv[1] for pv in pvs_info ]
301
    free_size = sum([ pv[0] for pv in pvs_info ])
302

    
303
    # The size constraint should have been checked from the master before
304
    # calling the create function.
305
    if free_size < size:
306
      raise errors.BlockDeviceError, ("Not enough free space: required %s,"
307
                                      " available %s" % (size, free_size))
308
    result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-n%s" % lv_name,
309
                           vg_name] + pvlist)
310
    if result.failed:
311
      raise errors.BlockDeviceError(result.fail_reason)
312
    return LogicalVolume(unique_id, children)
313

    
314
  @staticmethod
315
  def GetPVInfo(vg_name):
316
    """Get the free space info for PVs in a volume group.
317

318
    Args:
319
      vg_name: the volume group name
320

321
    Returns:
322
      list of (free_space, name) with free_space in mebibytes
323

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

    
343
    return data
344

    
345
  def Remove(self):
346
    """Remove this logical volume.
347

348
    """
349
    if not self.minor and not self.Attach():
350
      # the LV does not exist
351
      return True
352
    result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
353
                           (self._vg_name, self._lv_name)])
354
    if result.failed:
355
      logger.Error("Can't lvremove: %s" % result.fail_reason)
356

    
357
    return not result.failed
358

    
359

    
360
  def Attach(self):
361
    """Attach to an existing LV.
362

363
    This method will try to see if an existing and active LV exists
364
    which matches the our name. If so, its major/minor will be
365
    recorded.
366

367
    """
368
    result = utils.RunCmd(["lvdisplay", self.dev_path])
369
    if result.failed:
370
      logger.Error("Can't find LV %s: %s" %
371
                   (self.dev_path, result.fail_reason))
372
      return False
373
    match = re.compile("^ *Block device *([0-9]+):([0-9]+).*$")
374
    for line in result.stdout.splitlines():
375
      match_result = match.match(line)
376
      if match_result:
377
        self.major = int(match_result.group(1))
378
        self.minor = int(match_result.group(2))
379
        return True
380
    return False
381

    
382

    
383
  def Assemble(self):
384
    """Assemble the device.
385

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

389
    """
390
    return True
391

    
392

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

396
    This is a no-op for the LV device type, as we don't deactivate the
397
    volumes on shutdown.
398

399
    """
400
    return True
401

    
402

    
403
  def GetStatus(self):
404
    """Return the status of the device.
405

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

410
    """
411
    result = utils.RunCmd(["lvs", "--noheadings", "-olv_attr", self.dev_path])
412
    if result.failed:
413
      logger.Error("Can't display lv: %s" % result.fail_reason)
414
      return self.STATUS_UNKNOWN
415
    out = result.stdout.strip()
416
    # format: type/permissions/alloc/fixed_minor/state/open
417
    if len(out) != 6:
418
      return self.STATUS_UNKNOWN
419
    #writable = (out[1] == "w")
420
    active = (out[4] == "a")
421
    online = (out[5] == "o")
422
    if online:
423
      retval = self.STATUS_ONLINE
424
    elif active:
425
      retval = self.STATUS_STANDBY
426
    else:
427
      retval = self.STATUS_EXISTING
428

    
429
    return retval
430

    
431

    
432
  def Open(self, force=False):
433
    """Make the device ready for I/O.
434

435
    This is a no-op for the LV device type.
436

437
    """
438
    return True
439

    
440

    
441
  def Close(self):
442
    """Notifies that the device will no longer be used for I/O.
443

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

446
    """
447
    return True
448

    
449

    
450
  def Snapshot(self, size):
451
    """Create a snapshot copy of an lvm block device.
452

453
    """
454
    snap_name = self._lv_name + ".snap"
455

    
456
    # remove existing snapshot if found
457
    snap = LogicalVolume((self._vg_name, snap_name), None)
458
    snap.Remove()
459

    
460
    pvs_info = self.GetPVInfo(self._vg_name)
461
    if not pvs_info:
462
      raise errors.BlockDeviceError, ("Can't compute PV info for vg %s" %
463
                                      self._vg_name)
464
    pvs_info.sort()
465
    pvs_info.reverse()
466
    free_size, pv_name = pvs_info[0]
467
    if free_size < size:
468
      raise errors.BlockDeviceError, ("Not enough free space: required %s,"
469
                                      " available %s" % (size, free_size))
470

    
471
    result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
472
                           "-n%s" % snap_name, self.dev_path])
473
    if result.failed:
474
      raise errors.BlockDeviceError, ("command: %s error: %s" %
475
                                      (result.cmd, result.fail_reason))
476

    
477
    return snap_name
478

    
479

    
480
class MDRaid1(BlockDev):
481
  """raid1 device implemented via md.
482

483
  """
484
  def __init__(self, unique_id, children):
485
    super(MDRaid1, self).__init__(unique_id, children)
486
    self.major = 9
487
    self.Attach()
488

    
489

    
490
  def Attach(self):
491
    """Find an array which matches our config and attach to it.
492

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

495
    """
496
    minor = self._FindMDByUUID(self.unique_id)
497
    if minor is not None:
498
      self._SetFromMinor(minor)
499
    else:
500
      self.minor = None
501
      self.dev_path = None
502

    
503
    return (minor is not None)
504

    
505

    
506
  @staticmethod
507
  def _GetUsedDevs():
508
    """Compute the list of in-use MD devices.
509

510
    It doesn't matter if the used device have other raid level, just
511
    that they are in use.
512

513
    """
514
    mdstat = open("/proc/mdstat", "r")
515
    data = mdstat.readlines()
516
    mdstat.close()
517

    
518
    used_md = {}
519
    valid_line = re.compile("^md([0-9]+) : .*$")
520
    for line in data:
521
      match = valid_line.match(line)
522
      if match:
523
        md_no = int(match.group(1))
524
        used_md[md_no] = line
525

    
526
    return used_md
527

    
528

    
529
  @staticmethod
530
  def _GetDevInfo(minor):
531
    """Get info about a MD device.
532

533
    Currently only uuid is returned.
534

535
    """
536
    result = utils.RunCmd(["mdadm", "-D", "/dev/md%d" % minor])
537
    if result.failed:
538
      logger.Error("Can't display md: %s" % result.fail_reason)
539
      return None
540
    retval = {}
541
    for line in result.stdout.splitlines():
542
      line = line.strip()
543
      kv = line.split(" : ", 1)
544
      if kv:
545
        if kv[0] == "UUID":
546
          retval["uuid"] = kv[1]
547
        elif kv[0] == "State":
548
          retval["state"] = kv[1].split(", ")
549
    return retval
550

    
551

    
552
  @staticmethod
553
  def _FindUnusedMinor():
554
    """Compute an unused MD minor.
555

556
    This code assumes that there are 256 minors only.
557

558
    """
559
    used_md = MDRaid1._GetUsedDevs()
560
    i = 0
561
    while i < 256:
562
      if i not in used_md:
563
        break
564
      i += 1
565
    if i == 256:
566
      logger.Error("Critical: Out of md minor numbers.")
567
      return None
568
    return i
569

    
570

    
571
  @classmethod
572
  def _FindMDByUUID(cls, uuid):
573
    """Find the minor of an MD array with a given UUID.
574

575
    """
576
    md_list = cls._GetUsedDevs()
577
    for minor in md_list:
578
      info = cls._GetDevInfo(minor)
579
      if info and info["uuid"] == uuid:
580
        return minor
581
    return None
582

    
583

    
584
  @classmethod
585
  def Create(cls, unique_id, children, size):
586
    """Create a new MD raid1 array.
587

588
    """
589
    if not isinstance(children, (tuple, list)):
590
      raise ValueError("Invalid setup data for MDRaid1 dev: %s" %
591
                       str(children))
592
    for i in children:
593
      if not isinstance(i, BlockDev):
594
        raise ValueError("Invalid member in MDRaid1 dev: %s" % type(i))
595
    for i in children:
596
      result = utils.RunCmd(["mdadm", "--zero-superblock", "--force",
597
                             i.dev_path])
598
      if result.failed:
599
        logger.Error("Can't zero superblock: %s" % result.fail_reason)
600
        return None
601
    minor = cls._FindUnusedMinor()
602
    result = utils.RunCmd(["mdadm", "--create", "/dev/md%d" % minor,
603
                           "--auto=yes", "--force", "-l1",
604
                           "-n%d" % len(children)] +
605
                          [dev.dev_path for dev in children])
606

    
607
    if result.failed:
608
      logger.Error("Can't create md: %s" % result.fail_reason)
609
      return None
610
    info = cls._GetDevInfo(minor)
611
    if not info or not "uuid" in info:
612
      logger.Error("Wrong information returned from mdadm -D: %s" % str(info))
613
      return None
614
    return MDRaid1(info["uuid"], children)
615

    
616

    
617
  def Remove(self):
618
    """Stub remove function for MD RAID 1 arrays.
619

620
    We don't remove the superblock right now. Mark a to do.
621

622
    """
623
    #TODO: maybe zero superblock on child devices?
624
    return self.Shutdown()
625

    
626

    
627
  def AddChild(self, device):
628
    """Add a new member to the md raid1.
629

630
    """
631
    if self.minor is None and not self.Attach():
632
      raise errors.BlockDeviceError, "Can't attach to device"
633
    if device.dev_path is None:
634
      raise errors.BlockDeviceError, "New child is not initialised"
635
    result = utils.RunCmd(["mdadm", "-a", self.dev_path, device.dev_path])
636
    if result.failed:
637
      raise errors.BlockDeviceError, ("Failed to add new device to array: %s" %
638
                                      result.output)
639
    new_len = len(self._children) + 1
640
    result = utils.RunCmd(["mdadm", "--grow", self.dev_path, "-n", new_len])
641
    if result.failed:
642
      raise errors.BlockDeviceError, ("Can't grow md array: %s" %
643
                                      result.output)
644
    self._children.append(device)
645

    
646

    
647
  def RemoveChild(self, dev_path):
648
    """Remove member from the md raid1.
649

650
    """
651
    if self.minor is None and not self.Attach():
652
      raise errors.BlockDeviceError, "Can't attach to device"
653
    if len(self._children) == 1:
654
      raise errors.BlockDeviceError, ("Can't reduce member when only one"
655
                                      " child left")
656
    for device in self._children:
657
      if device.dev_path == dev_path:
658
        break
659
    else:
660
      raise errors.BlockDeviceError, "Can't find child with this path"
661
    new_len = len(self._children) - 1
662
    result = utils.RunCmd(["mdadm", "-f", self.dev_path, dev_path])
663
    if result.failed:
664
      raise errors.BlockDeviceError, ("Failed to mark device as failed: %s" %
665
                                      result.output)
666

    
667
    # it seems here we need a short delay for MD to update its
668
    # superblocks
669
    time.sleep(0.5)
670
    result = utils.RunCmd(["mdadm", "-r", self.dev_path, dev_path])
671
    if result.failed:
672
      raise errors.BlockDeviceError, ("Failed to remove device from array:"
673
                                      " %s" % result.output)
674
    result = utils.RunCmd(["mdadm", "--grow", "--force", self.dev_path,
675
                           "-n", new_len])
676
    if result.failed:
677
      raise errors.BlockDeviceError, ("Can't shrink md array: %s" %
678
                                      result.output)
679
    self._children.remove(device)
680

    
681

    
682
  def GetStatus(self):
683
    """Return the status of the device.
684

685
    """
686
    self.Attach()
687
    if self.minor is None:
688
      retval = self.STATUS_UNKNOWN
689
    else:
690
      retval = self.STATUS_ONLINE
691
    return retval
692

    
693

    
694
  def _SetFromMinor(self, minor):
695
    """Set our parameters based on the given minor.
696

697
    This sets our minor variable and our dev_path.
698

699
    """
700
    self.minor = minor
701
    self.dev_path = "/dev/md%d" % minor
702

    
703

    
704
  def Assemble(self):
705
    """Assemble the MD device.
706

707
    At this point we should have:
708
      - list of children devices
709
      - uuid
710

711
    """
712
    result = super(MDRaid1, self).Assemble()
713
    if not result:
714
      return result
715
    md_list = self._GetUsedDevs()
716
    for minor in md_list:
717
      info = self._GetDevInfo(minor)
718
      if info and info["uuid"] == self.unique_id:
719
        self._SetFromMinor(minor)
720
        logger.Info("MD array %s already started" % str(self))
721
        return True
722
    free_minor = self._FindUnusedMinor()
723
    result = utils.RunCmd(["mdadm", "-A", "--auto=yes", "--uuid",
724
                           self.unique_id, "/dev/md%d" % free_minor] +
725
                          [bdev.dev_path for bdev in self._children])
726
    if result.failed:
727
      logger.Error("Can't assemble MD array: %s" % result.fail_reason)
728
      self.minor = None
729
    else:
730
      self.minor = free_minor
731
    return not result.failed
732

    
733

    
734
  def Shutdown(self):
735
    """Tear down the MD array.
736

737
    This does a 'mdadm --stop' so after this command, the array is no
738
    longer available.
739

740
    """
741
    if self.minor is None and not self.Attach():
742
      logger.Info("MD object not attached to a device")
743
      return True
744

    
745
    result = utils.RunCmd(["mdadm", "--stop", "/dev/md%d" % self.minor])
746
    if result.failed:
747
      logger.Error("Can't stop MD array: %s" % result.fail_reason)
748
      return False
749
    self.minor = None
750
    self.dev_path = None
751
    return True
752

    
753

    
754
  def SetSyncSpeed(self, kbytes):
755
    """Set the maximum sync speed for the MD array.
756

757
    """
758
    result = super(MDRaid1, self).SetSyncSpeed(kbytes)
759
    if self.minor is None:
760
      logger.Error("MD array not attached to a device")
761
      return False
762
    f = open("/sys/block/md%d/md/sync_speed_max" % self.minor, "w")
763
    try:
764
      f.write("%d" % kbytes)
765
    finally:
766
      f.close()
767
    f = open("/sys/block/md%d/md/sync_speed_min" % self.minor, "w")
768
    try:
769
      f.write("%d" % (kbytes/2))
770
    finally:
771
      f.close()
772
    return result
773

    
774

    
775
  def GetSyncStatus(self):
776
    """Returns the sync status of the device.
777

778
    Returns:
779
     (sync_percent, estimated_time)
780

781
    If sync_percent is None, it means all is ok
782
    If estimated_time is None, it means we can't esimate
783
    the time needed, otherwise it's the time left in seconds
784

785
    """
786
    if self.minor is None and not self.Attach():
787
      raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
788
    dev_info = self._GetDevInfo(self.minor)
789
    is_clean = ("state" in dev_info and
790
                len(dev_info["state"]) == 1 and
791
                dev_info["state"][0] in ("clean", "active"))
792
    sys_path = "/sys/block/md%s/md/" % self.minor
793
    f = file(sys_path + "sync_action")
794
    sync_status = f.readline().strip()
795
    f.close()
796
    if sync_status == "idle":
797
      return None, None, not is_clean
798
    f = file(sys_path + "sync_completed")
799
    sync_completed = f.readline().strip().split(" / ")
800
    f.close()
801
    if len(sync_completed) != 2:
802
      return 0, None, not is_clean
803
    sync_done, sync_total = [float(i) for i in sync_completed]
804
    sync_percent = 100.0*sync_done/sync_total
805
    f = file(sys_path + "sync_speed")
806
    sync_speed_k = int(f.readline().strip())
807
    if sync_speed_k == 0:
808
      time_est = None
809
    else:
810
      time_est = (sync_total - sync_done) / 2 / sync_speed_k
811
    return sync_percent, time_est, not is_clean
812

    
813

    
814
  def Open(self, force=False):
815
    """Make the device ready for I/O.
816

817
    This is a no-op for the MDRaid1 device type, although we could use
818
    the 2.6.18's new array_state thing.
819

820
    """
821
    return True
822

    
823

    
824
  def Close(self):
825
    """Notifies that the device will no longer be used for I/O.
826

827
    This is a no-op for the MDRaid1 device type, but see comment for
828
    `Open()`.
829

830
    """
831
    return True
832

    
833

    
834

    
835
class DRBDev(BlockDev):
836
  """DRBD block device.
837

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

842
  The unique_id for the drbd device is the (local_ip, local_port,
843
  remote_ip, remote_port) tuple, and it must have two children: the
844
  data device and the meta_device. The meta device is checked for
845
  valid size and is zeroed on create.
846

847
  """
848
  _DRBD_MAJOR = 147
849
  _ST_UNCONFIGURED = "Unconfigured"
850
  _ST_WFCONNECTION = "WFConnection"
851
  _ST_CONNECTED = "Connected"
852

    
853
  def __init__(self, unique_id, children):
854
    super(DRBDev, self).__init__(unique_id, children)
855
    self.major = self._DRBD_MAJOR
856
    if len(children) != 2:
857
      raise ValueError("Invalid configuration data %s" % str(children))
858
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
859
      raise ValueError("Invalid configuration data %s" % str(unique_id))
860
    self._lhost, self._lport, self._rhost, self._rport = unique_id
861
    self.Attach()
862

    
863
  @staticmethod
864
  def _DevPath(minor):
865
    """Return the path to a drbd device for a given minor.
866

867
    """
868
    return "/dev/drbd%d" % minor
869

    
870
  @staticmethod
871
  def _GetProcData():
872
    """Return data from /proc/drbd.
873

874
    """
875
    stat = open("/proc/drbd", "r")
876
    data = stat.read().splitlines()
877
    stat.close()
878
    return data
879

    
880

    
881
  @classmethod
882
  def _GetUsedDevs(cls):
883
    """Compute the list of used DRBD devices.
884

885
    """
886
    data = cls._GetProcData()
887

    
888
    used_devs = {}
889
    valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
890
    for line in data:
891
      match = valid_line.match(line)
892
      if not match:
893
        continue
894
      minor = int(match.group(1))
895
      state = match.group(2)
896
      if state == cls._ST_UNCONFIGURED:
897
        continue
898
      used_devs[minor] = state, line
899

    
900
    return used_devs
901

    
902

    
903
  @classmethod
904
  def _FindUnusedMinor(cls):
905
    """Find an unused DRBD device.
906

907
    """
908
    data = cls._GetProcData()
909

    
910
    valid_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
911
    for line in data:
912
      match = valid_line.match(line)
913
      if match:
914
        return int(match.group(1))
915
    logger.Error("Error: no free drbd minors!")
916
    return None
917

    
918

    
919
  @classmethod
920
  def _GetDevInfo(cls, minor):
921
    """Get details about a given DRBD minor.
922

923
    This return, if available, the local backing device in (major,
924
    minor) formant and the local and remote (ip, port) information.
925

926
    """
927
    data = {}
928
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
929
    if result.failed:
930
      logger.Error("Can't display the drbd config: %s" % result.fail_reason)
931
      return data
932
    out = result.stdout
933
    if out == "Not configured\n":
934
      return data
935
    for line in out.splitlines():
936
      if "local_dev" not in data:
937
        match = re.match("^Lower device: ([0-9]+):([0-9]+) .*$", line)
938
        if match:
939
          data["local_dev"] = (int(match.group(1)), int(match.group(2)))
940
          continue
941
      if "meta_dev" not in data:
942
        match = re.match("^Meta device: (([0-9]+):([0-9]+)|internal).*$", line)
943
        if match:
944
          if match.group(2) is not None and match.group(3) is not None:
945
            # matched on the major/minor
946
            data["meta_dev"] = (int(match.group(2)), int(match.group(3)))
947
          else:
948
            # matched on the "internal" string
949
            data["meta_dev"] = match.group(1)
950
            # in this case, no meta_index is in the output
951
            data["meta_index"] = -1
952
          continue
953
      if "meta_index" not in data:
954
        match = re.match("^Meta index: ([0-9]+).*$", line)
955
        if match:
956
          data["meta_index"] = int(match.group(1))
957
          continue
958
      if "local_addr" not in data:
959
        match = re.match("^Local address: ([0-9.]+):([0-9]+)$", line)
960
        if match:
961
          data["local_addr"] = (match.group(1), int(match.group(2)))
962
          continue
963
      if "remote_addr" not in data:
964
        match = re.match("^Remote address: ([0-9.]+):([0-9]+)$", line)
965
        if match:
966
          data["remote_addr"] = (match.group(1), int(match.group(2)))
967
          continue
968
    return data
969

    
970

    
971
  def _MatchesLocal(self, info):
972
    """Test if our local config matches with an existing device.
973

974
    The parameter should be as returned from `_GetDevInfo()`. This
975
    method tests if our local backing device is the same as the one in
976
    the info parameter, in effect testing if we look like the given
977
    device.
978

979
    """
980
    if not ("local_dev" in info and "meta_dev" in info and
981
            "meta_index" in info):
982
      return False
983

    
984
    backend = self._children[0]
985
    if backend is not None:
986
      retval = (info["local_dev"] == (backend.major, backend.minor))
987
    else:
988
      retval = (info["local_dev"] == (0, 0))
989
    meta = self._children[1]
990
    if meta is not None:
991
      retval = retval and (info["meta_dev"] == (meta.major, meta.minor))
992
      retval = retval and (info["meta_index"] == 0)
993
    else:
994
      retval = retval and (info["meta_dev"] == "internal" and
995
                           info["meta_index"] == -1)
996
    return retval
997

    
998

    
999
  def _MatchesNet(self, info):
1000
    """Test if our network config matches with an existing device.
1001

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

1007
    """
1008
    if (((self._lhost is None and not ("local_addr" in info)) and
1009
         (self._rhost is None and not ("remote_addr" in info)))):
1010
      return True
1011

    
1012
    if self._lhost is None:
1013
      return False
1014

    
1015
    if not ("local_addr" in info and
1016
            "remote_addr" in info):
1017
      return False
1018

    
1019
    retval = (info["local_addr"] == (self._lhost, self._lport))
1020
    retval = (retval and
1021
              info["remote_addr"] == (self._rhost, self._rport))
1022
    return retval
1023

    
1024

    
1025
  @staticmethod
1026
  def _IsValidMeta(meta_device):
1027
    """Check if the given meta device looks like a valid one.
1028

1029
    This currently only check the size, which must be around
1030
    128MiB.
1031

1032
    """
1033
    result = utils.RunCmd(["blockdev", "--getsize", meta_device])
1034
    if result.failed:
1035
      logger.Error("Failed to get device size: %s" % result.fail_reason)
1036
      return False
1037
    try:
1038
      sectors = int(result.stdout)
1039
    except ValueError:
1040
      logger.Error("Invalid output from blockdev: '%s'" % result.stdout)
1041
      return False
1042
    bytes = sectors * 512
1043
    if bytes < 128*1024*1024: # less than 128MiB
1044
      logger.Error("Meta device too small (%.2fMib)" % (bytes/1024/1024))
1045
      return False
1046
    if bytes > (128+32)*1024*1024: # account for an extra (big) PE on LVM
1047
      logger.Error("Meta device too big (%.2fMiB)" % (bytes/1024/1024))
1048
      return False
1049
    return True
1050

    
1051

    
1052
  @classmethod
1053
  def _AssembleLocal(cls, minor, backend, meta):
1054
    """Configure the local part of a DRBD device.
1055

1056
    This is the first thing that must be done on an unconfigured DRBD
1057
    device. And it must be done only once.
1058

1059
    """
1060
    if not cls._IsValidMeta(meta):
1061
      return False
1062
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1063
                           backend, meta, "0", "-e", "detach"])
1064
    if result.failed:
1065
      logger.Error("Can't attach local disk: %s" % result.output)
1066
    return not result.failed
1067

    
1068

    
1069
  @classmethod
1070
  def _ShutdownLocal(cls, minor):
1071
    """Detach from the local device.
1072

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

1076
    """
1077
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1078
    if result.failed:
1079
      logger.Error("Can't detach local device: %s" % result.output)
1080
    return not result.failed
1081

    
1082

    
1083
  @staticmethod
1084
  def _ShutdownAll(minor):
1085
    """Deactivate the device.
1086

1087
    This will, of course, fail if the device is in use.
1088

1089
    """
1090
    result = utils.RunCmd(["drbdsetup", DRBDev._DevPath(minor), "down"])
1091
    if result.failed:
1092
      logger.Error("Can't shutdown drbd device: %s" % result.output)
1093
    return not result.failed
1094

    
1095

    
1096
  @classmethod
1097
  def _AssembleNet(cls, minor, net_info, protocol):
1098
    """Configure the network part of the device.
1099

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

1107
    """
1108
    lhost, lport, rhost, rport = net_info
1109
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "net",
1110
                           "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport),
1111
                           protocol])
1112
    if result.failed:
1113
      logger.Error("Can't setup network for dbrd device: %s" %
1114
                   result.fail_reason)
1115
      return False
1116

    
1117
    timeout = time.time() + 10
1118
    ok = False
1119
    while time.time() < timeout:
1120
      info = cls._GetDevInfo(minor)
1121
      if not "local_addr" in info or not "remote_addr" in info:
1122
        time.sleep(1)
1123
        continue
1124
      if (info["local_addr"] != (lhost, lport) or
1125
          info["remote_addr"] != (rhost, rport)):
1126
        time.sleep(1)
1127
        continue
1128
      ok = True
1129
      break
1130
    if not ok:
1131
      logger.Error("Timeout while configuring network")
1132
      return False
1133
    return True
1134

    
1135

    
1136
  @classmethod
1137
  def _ShutdownNet(cls, minor):
1138
    """Disconnect from the remote peer.
1139

1140
    This fails if we don't have a local device.
1141

1142
    """
1143
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1144
    logger.Error("Can't shutdown network: %s" % result.output)
1145
    return not result.failed
1146

    
1147

    
1148
  def _SetFromMinor(self, minor):
1149
    """Set our parameters based on the given minor.
1150

1151
    This sets our minor variable and our dev_path.
1152

1153
    """
1154
    if minor is None:
1155
      self.minor = self.dev_path = None
1156
    else:
1157
      self.minor = minor
1158
      self.dev_path = self._DevPath(minor)
1159

    
1160

    
1161
  def Assemble(self):
1162
    """Assemble the drbd.
1163

1164
    Method:
1165
      - if we have a local backing device, we bind to it by:
1166
        - checking the list of used drbd devices
1167
        - check if the local minor use of any of them is our own device
1168
        - if yes, abort?
1169
        - if not, bind
1170
      - if we have a local/remote net info:
1171
        - redo the local backing device step for the remote device
1172
        - check if any drbd device is using the local port,
1173
          if yes abort
1174
        - check if any remote drbd device is using the remote
1175
          port, if yes abort (for now)
1176
        - bind our net port
1177
        - bind the remote net port
1178

1179
    """
1180
    self.Attach()
1181
    if self.minor is not None:
1182
      logger.Info("Already assembled")
1183
      return True
1184

    
1185
    result = super(DRBDev, self).Assemble()
1186
    if not result:
1187
      return result
1188

    
1189
    minor = self._FindUnusedMinor()
1190
    if minor is None:
1191
      raise errors.BlockDeviceError, "Not enough free minors for DRBD!"
1192
    need_localdev_teardown = False
1193
    if self._children[0]:
1194
      result = self._AssembleLocal(minor, self._children[0].dev_path,
1195
                                   self._children[1].dev_path)
1196
      if not result:
1197
        return False
1198
      need_localdev_teardown = True
1199
    if self._lhost and self._lport and self._rhost and self._rport:
1200
      result = self._AssembleNet(minor,
1201
                                 (self._lhost, self._lport,
1202
                                  self._rhost, self._rport),
1203
                                 "C")
1204
      if not result:
1205
        if need_localdev_teardown:
1206
          # we will ignore failures from this
1207
          logger.Error("net setup failed, tearing down local device")
1208
          self._ShutdownAll(minor)
1209
        return False
1210
    self._SetFromMinor(minor)
1211
    return True
1212

    
1213

    
1214
  def Shutdown(self):
1215
    """Shutdown the DRBD device.
1216

1217
    """
1218
    if self.minor is None and not self.Attach():
1219
      logger.Info("DRBD device not attached to a device during Shutdown")
1220
      return True
1221
    if not self._ShutdownAll(self.minor):
1222
      return False
1223
    self.minor = None
1224
    self.dev_path = None
1225
    return True
1226

    
1227

    
1228
  def Attach(self):
1229
    """Find a DRBD device which matches our config and attach to it.
1230

1231
    In case of partially attached (local device matches but no network
1232
    setup), we perform the network attach. If successful, we re-test
1233
    the attach if can return success.
1234

1235
    """
1236
    for minor in self._GetUsedDevs():
1237
      info = self._GetDevInfo(minor)
1238
      match_l = self._MatchesLocal(info)
1239
      match_r = self._MatchesNet(info)
1240
      if match_l and match_r:
1241
        break
1242
      if match_l and not match_r and "local_addr" not in info:
1243
        res_r = self._AssembleNet(minor,
1244
                                  (self._lhost, self._lport,
1245
                                   self._rhost, self._rport),
1246
                                  "C")
1247
        if res_r and self._MatchesNet(self._GetDevInfo(minor)):
1248
          break
1249
    else:
1250
      minor = None
1251

    
1252
    self._SetFromMinor(minor)
1253
    return minor is not None
1254

    
1255

    
1256
  def Open(self, force=False):
1257
    """Make the local state primary.
1258

1259
    If the 'force' parameter is given, the '--do-what-I-say' parameter
1260
    is given. Since this is a pottentialy dangerous operation, the
1261
    force flag should be only given after creation, when it actually
1262
    has to be given.
1263

1264
    """
1265
    if self.minor is None and not self.Attach():
1266
      logger.Error("DRBD cannot attach to a device during open")
1267
      return False
1268
    cmd = ["drbdsetup", self.dev_path, "primary"]
1269
    if force:
1270
      cmd.append("--do-what-I-say")
1271
    result = utils.RunCmd(cmd)
1272
    if result.failed:
1273
      logger.Error("Can't make drbd device primary: %s" % result.output)
1274
      return False
1275
    return True
1276

    
1277

    
1278
  def Close(self):
1279
    """Make the local state secondary.
1280

1281
    This will, of course, fail if the device is in use.
1282

1283
    """
1284
    if self.minor is None and not self.Attach():
1285
      logger.Info("Instance not attached to a device")
1286
      raise errors.BlockDeviceError("Can't find device")
1287
    result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1288
    if result.failed:
1289
      logger.Error("Can't switch drbd device to secondary: %s" % result.output)
1290
      raise errors.BlockDeviceError("Can't switch drbd device to secondary")
1291

    
1292

    
1293
  def SetSyncSpeed(self, kbytes):
1294
    """Set the speed of the DRBD syncer.
1295

1296
    """
1297
    children_result = super(DRBDev, self).SetSyncSpeed(kbytes)
1298
    if self.minor is None:
1299
      logger.Info("Instance not attached to a device")
1300
      return False
1301
    result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1302
                           kbytes])
1303
    if result.failed:
1304
      logger.Error("Can't change syncer rate: %s " % result.fail_reason)
1305
    return not result.failed and children_result
1306

    
1307

    
1308
  def GetSyncStatus(self):
1309
    """Returns the sync status of the device.
1310

1311
    Returns:
1312
     (sync_percent, estimated_time)
1313

1314
    If sync_percent is None, it means all is ok
1315
    If estimated_time is None, it means we can't esimate
1316
    the time needed, otherwise it's the time left in seconds
1317

1318
    """
1319
    if self.minor is None and not self.Attach():
1320
      raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1321
    proc_info = self._MassageProcData(self._GetProcData())
1322
    if self.minor not in proc_info:
1323
      raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1324
                                    self.minor)
1325
    line = proc_info[self.minor]
1326
    match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1327
                     " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1328
    if match:
1329
      sync_percent = float(match.group(1))
1330
      hours = int(match.group(2))
1331
      minutes = int(match.group(3))
1332
      seconds = int(match.group(4))
1333
      est_time = hours * 3600 + minutes * 60 + seconds
1334
    else:
1335
      sync_percent = None
1336
      est_time = None
1337
    match = re.match("^ *[0-9]+: cs:([^ ]+).*$", line)
1338
    if not match:
1339
      raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
1340
                                    self.minor)
1341
    client_state = match.group(1)
1342
    is_degraded = client_state != "Connected"
1343
    return sync_percent, est_time, is_degraded
1344

    
1345

    
1346
  @staticmethod
1347
  def _MassageProcData(data):
1348
    """Transform the output of _GetProdData into a nicer form.
1349

1350
    Returns:
1351
      a dictionary of minor: joined lines from /proc/drbd for that minor
1352

1353
    """
1354
    lmatch = re.compile("^ *([0-9]+):.*$")
1355
    results = {}
1356
    old_minor = old_line = None
1357
    for line in data:
1358
      lresult = lmatch.match(line)
1359
      if lresult is not None:
1360
        if old_minor is not None:
1361
          results[old_minor] = old_line
1362
        old_minor = int(lresult.group(1))
1363
        old_line = line
1364
      else:
1365
        if old_minor is not None:
1366
          old_line += " " + line.strip()
1367
    # add last line
1368
    if old_minor is not None:
1369
      results[old_minor] = old_line
1370
    return results
1371

    
1372

    
1373
  def GetStatus(self):
1374
    """Compute the status of the DRBD device
1375

1376
    Note that DRBD devices don't have the STATUS_EXISTING state.
1377

1378
    """
1379
    if self.minor is None and not self.Attach():
1380
      return self.STATUS_UNKNOWN
1381

    
1382
    data = self._GetProcData()
1383
    match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
1384
                       self.minor)
1385
    for line in data:
1386
      mresult = match.match(line)
1387
      if mresult:
1388
        break
1389
    else:
1390
      logger.Error("Can't find myself!")
1391
      return self.STATUS_UNKNOWN
1392

    
1393
    state = mresult.group(2)
1394
    if state == "Primary":
1395
      result = self.STATUS_ONLINE
1396
    else:
1397
      result = self.STATUS_STANDBY
1398

    
1399
    return result
1400

    
1401

    
1402
  @staticmethod
1403
  def _ZeroDevice(device):
1404
    """Zero a device.
1405

1406
    This writes until we get ENOSPC.
1407

1408
    """
1409
    f = open(device, "w")
1410
    buf = "\0" * 1048576
1411
    try:
1412
      while True:
1413
        f.write(buf)
1414
    except IOError, err:
1415
      if err.errno != errno.ENOSPC:
1416
        raise
1417

    
1418

    
1419
  @classmethod
1420
  def Create(cls, unique_id, children, size):
1421
    """Create a new DRBD device.
1422

1423
    Since DRBD devices are not created per se, just assembled, this
1424
    function just zeroes the meta device.
1425

1426
    """
1427
    if len(children) != 2:
1428
      raise errors.ProgrammerError("Invalid setup for the drbd device")
1429
    meta = children[1]
1430
    meta.Assemble()
1431
    if not meta.Attach():
1432
      raise errors.BlockDeviceError("Can't attach to meta device")
1433
    if not cls._IsValidMeta(meta.dev_path):
1434
      raise errors.BlockDeviceError("Invalid meta device")
1435
    logger.Info("Started zeroing device %s" % meta.dev_path)
1436
    cls._ZeroDevice(meta.dev_path)
1437
    logger.Info("Done zeroing device %s" % meta.dev_path)
1438
    return cls(unique_id, children)
1439

    
1440

    
1441
  def Remove(self):
1442
    """Stub remove for DRBD devices.
1443

1444
    """
1445
    return self.Shutdown()
1446

    
1447

    
1448
DEV_MAP = {
1449
  "lvm": LogicalVolume,
1450
  "md_raid1": MDRaid1,
1451
  "drbd": DRBDev,
1452
  }
1453

    
1454

    
1455
def FindDevice(dev_type, unique_id, children):
1456
  """Search for an existing, assembled device.
1457

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

1461
  """
1462
  if dev_type not in DEV_MAP:
1463
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1464
  device = DEV_MAP[dev_type](unique_id, children)
1465
  if not device.Attach():
1466
    return None
1467
  return  device
1468

    
1469

    
1470
def AttachOrAssemble(dev_type, unique_id, children):
1471
  """Try to attach or assemble an existing device.
1472

1473
  This will attach to an existing assembled device or will assemble
1474
  the device, as needed, to bring it fully up.
1475

1476
  """
1477
  if dev_type not in DEV_MAP:
1478
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1479
  device = DEV_MAP[dev_type](unique_id, children)
1480
  if not device.Attach():
1481
    device.Assemble()
1482
  if not device.Attach():
1483
    raise errors.BlockDeviceError("Can't find a valid block device for"
1484
                                  " %s/%s/%s" %
1485
                                  (dev_type, unique_id, children))
1486
  return device
1487

    
1488

    
1489
def Create(dev_type, unique_id, children, size):
1490
  """Create a device.
1491

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