Statistics
| Branch: | Tag: | Revision:

root / lib / bdev.py @ 153d9724

History | View | Annotate | Download (64.2 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
"""Block device abstraction"""
23

    
24
import re
25
import time
26
import errno
27
import pyparsing as pyp
28

    
29
from ganeti import utils
30
from ganeti import logger
31
from ganeti import errors
32
from ganeti import constants
33

    
34

    
35
class BlockDev(object):
36
  """Block device abstract class.
37

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

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

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

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

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

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

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

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

84
  """
85
  STATUS_UNKNOWN = 0
86
  STATUS_EXISTING = 1
87
  STATUS_STANDBY = 2
88
  STATUS_ONLINE = 3
89

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

    
97

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

    
105

    
106
  def Assemble(self):
107
    """Assemble the device from its components.
108

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

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

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

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

    
134

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

138
    """
139
    raise NotImplementedError
140

    
141

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

145
    """
146
    raise NotImplementedError
147

    
148

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

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

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

160
    """
161
    raise NotImplementedError
162

    
163

    
164
  def Remove(self):
165
    """Remove this device.
166

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

171
    """
172
    raise NotImplementedError
173

    
174

    
175
  def GetStatus(self):
176
    """Return the status of the device.
177

178
    """
179
    raise NotImplementedError
180

    
181

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

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

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

191
    """
192
    raise NotImplementedError
193

    
194

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

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

202
    """
203
    raise NotImplementedError
204

    
205

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

209
    In case this is not a mirroring device, this is no-op.
210

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

    
218

    
219
  def GetSyncStatus(self):
220
    """Returns the sync status of the device.
221

222
    If this device is a mirroring device, this function returns the
223
    status of the mirror.
224

225
    Returns:
226
     (sync_percent, estimated_time, is_degraded)
227

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

235
    """
236
    return None, None, False
237

    
238

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

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

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

    
262

    
263
  def SetInfo(self, text):
264
    """Update metadata with info text.
265

266
    Only supported for some device types.
267

268
    """
269
    for child in self._children:
270
      child.SetInfo(text)
271

    
272

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

    
278

    
279
class LogicalVolume(BlockDev):
280
  """Logical Volume block device.
281

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

286
    The unique_id is a tuple (vg_name, lv_name)
287

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

    
296

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

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

    
312
    pvlist = [ pv[1] for pv in pvs_info ]
313
    free_size = sum([ pv[0] for pv in pvs_info ])
314

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

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

330
    Args:
331
      vg_name: the volume group name
332

333
    Returns:
334
      list of (free_space, name) with free_space in mebibytes
335

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

    
355
    return data
356

    
357
  def Remove(self):
358
    """Remove this logical volume.
359

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

    
369
    return not result.failed
370

    
371

    
372
  def Attach(self):
373
    """Attach to an existing LV.
374

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

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

    
394

    
395
  def Assemble(self):
396
    """Assemble the device.
397

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

401
    """
402
    return True
403

    
404

    
405
  def Shutdown(self):
406
    """Shutdown the device.
407

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

411
    """
412
    return True
413

    
414

    
415
  def GetStatus(self):
416
    """Return the status of the device.
417

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

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

    
441
    return retval
442

    
443

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

447
    This is a no-op for the LV device type.
448

449
    """
450
    return True
451

    
452

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

456
    This is a no-op for the LV device type.
457

458
    """
459
    return True
460

    
461

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

465
    """
466
    snap_name = self._lv_name + ".snap"
467

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

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

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

    
489
    return snap_name
490

    
491

    
492
  def SetInfo(self, text):
493
    """Update metadata with info text.
494

495
    """
496
    BlockDev.SetInfo(self, text)
497

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

    
502
    # Only up to 128 characters are allowed
503
    text = text[:128]
504

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

    
511

    
512
class MDRaid1(BlockDev):
513
  """raid1 device implemented via md.
514

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

    
521

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

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

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

    
535
    return (minor is not None)
536

    
537

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

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

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

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

    
558
    return used_md
559

    
560

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

565
    Currently only uuid is returned.
566

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

    
583

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

588
    This code assumes that there are 256 minors only.
589

590
    """
591
    used_md = MDRaid1._GetUsedDevs()
592
    i = 0
593
    while i < 256:
594
      if i not in used_md:
595
        break
596
      i += 1
597
    if i == 256:
598
      logger.Error("Critical: Out of md minor numbers.")
599
      raise errors.BlockDeviceError("Can't find a free MD minor")
600
    return i
601

    
602

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

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

    
615

    
616
  @staticmethod
617
  def _ZeroSuperblock(dev_path):
618
    """Zero the possible locations for an MD superblock.
619

620
    The zero-ing can't be done via ``mdadm --zero-superblock`` as that
621
    fails in versions 2.x with the same error code as non-writable
622
    device.
623

624
    The superblocks are located at (negative values are relative to
625
    the end of the block device):
626
      - -128k to end for version 0.90 superblock
627
      - -8k to -12k for version 1.0 superblock (included in the above)
628
      - 0k to 4k for version 1.1 superblock
629
      - 4k to 8k for version 1.2 superblock
630

631
    To cover all situations, the zero-ing will be:
632
      - 0k to 128k
633
      - -128k to end
634

635
    As such, the minimum device size must be 128k, otherwise we'll get
636
    I/O errors.
637

638
    Note that this function depends on the fact that one can open,
639
    read and write block devices normally.
640

641
    """
642
    overwrite_size = 128 * 1024
643
    empty_buf = '\0' * overwrite_size
644
    fd = open(dev_path, "r+")
645
    try:
646
      fd.seek(0, 0)
647
      p1 = fd.tell()
648
      fd.write(empty_buf)
649
      p2 = fd.tell()
650
      logger.Debug("Zeroed %s from %d to %d" % (dev_path, p1, p2))
651
      fd.seek(-overwrite_size, 2)
652
      p1 = fd.tell()
653
      fd.write(empty_buf)
654
      p2 = fd.tell()
655
      logger.Debug("Zeroed %s from %d to %d" % (dev_path, p1, p2))
656
    finally:
657
      fd.close()
658

    
659
  @classmethod
660
  def Create(cls, unique_id, children, size):
661
    """Create a new MD raid1 array.
662

663
    """
664
    if not isinstance(children, (tuple, list)):
665
      raise ValueError("Invalid setup data for MDRaid1 dev: %s" %
666
                       str(children))
667
    for i in children:
668
      if not isinstance(i, BlockDev):
669
        raise ValueError("Invalid member in MDRaid1 dev: %s" % type(i))
670
    for i in children:
671
      try:
672
        cls._ZeroSuperblock(i.dev_path)
673
      except EnvironmentError, err:
674
        logger.Error("Can't zero superblock for %s: %s" %
675
                     (i.dev_path, str(err)))
676
        return None
677
    minor = cls._FindUnusedMinor()
678
    result = utils.RunCmd(["mdadm", "--create", "/dev/md%d" % minor,
679
                           "--auto=yes", "--force", "-l1",
680
                           "-n%d" % len(children)] +
681
                          [dev.dev_path for dev in children])
682

    
683
    if result.failed:
684
      logger.Error("Can't create md: %s: %s" % (result.fail_reason,
685
                                                result.output))
686
      return None
687
    info = cls._GetDevInfo(minor)
688
    if not info or not "uuid" in info:
689
      logger.Error("Wrong information returned from mdadm -D: %s" % str(info))
690
      return None
691
    return MDRaid1(info["uuid"], children)
692

    
693

    
694
  def Remove(self):
695
    """Stub remove function for MD RAID 1 arrays.
696

697
    We don't remove the superblock right now. Mark a to do.
698

699
    """
700
    #TODO: maybe zero superblock on child devices?
701
    return self.Shutdown()
702

    
703

    
704
  def AddChildren(self, devices):
705
    """Add new member(s) to the md raid1.
706

707
    """
708
    if self.minor is None and not self.Attach():
709
      raise errors.BlockDeviceError("Can't attach to device")
710

    
711
    args = ["mdadm", "-a", self.dev_path]
712
    for dev in devices:
713
      if dev.dev_path is None:
714
        raise errors.BlockDeviceError("Child '%s' is not initialised" % dev)
715
      dev.Open()
716
      args.append(dev.dev_path)
717
    result = utils.RunCmd(args)
718
    if result.failed:
719
      raise errors.BlockDeviceError("Failed to add new device to array: %s" %
720
                                    result.output)
721
    new_len = len(self._children) + len(devices)
722
    result = utils.RunCmd(["mdadm", "--grow", self.dev_path, "-n", new_len])
723
    if result.failed:
724
      raise errors.BlockDeviceError("Can't grow md array: %s" %
725
                                    result.output)
726
    self._children.extend(devices)
727

    
728

    
729
  def RemoveChildren(self, devices):
730
    """Remove member(s) from the md raid1.
731

732
    """
733
    if self.minor is None and not self.Attach():
734
      raise errors.BlockDeviceError("Can't attach to device")
735
    new_len = len(self._children) - len(devices)
736
    if new_len < 1:
737
      raise errors.BlockDeviceError("Can't reduce to less than one child")
738
    args = ["mdadm", "-f", self.dev_path]
739
    orig_devs = []
740
    for dev in devices:
741
      args.append(dev.dev_path)
742
      for c in self._children:
743
        if c.dev_path == dev.dev_path:
744
          orig_devs.append(c)
745
          break
746
      else:
747
        raise errors.BlockDeviceError("Can't find device '%s' for removal" %
748
                                      dev)
749
    result = utils.RunCmd(args)
750
    if result.failed:
751
      raise errors.BlockDeviceError("Failed to mark device(s) as failed: %s" %
752
                                    result.output)
753

    
754
    # it seems here we need a short delay for MD to update its
755
    # superblocks
756
    time.sleep(0.5)
757
    args[1] = "-r"
758
    result = utils.RunCmd(args)
759
    if result.failed:
760
      raise errors.BlockDeviceError("Failed to remove device(s) from array:"
761
                                    " %s" % result.output)
762
    result = utils.RunCmd(["mdadm", "--grow", "--force", self.dev_path,
763
                           "-n", new_len])
764
    if result.failed:
765
      raise errors.BlockDeviceError("Can't shrink md array: %s" %
766
                                    result.output)
767
    for dev in orig_devs:
768
      self._children.remove(dev)
769

    
770

    
771
  def GetStatus(self):
772
    """Return the status of the device.
773

774
    """
775
    self.Attach()
776
    if self.minor is None:
777
      retval = self.STATUS_UNKNOWN
778
    else:
779
      retval = self.STATUS_ONLINE
780
    return retval
781

    
782

    
783
  def _SetFromMinor(self, minor):
784
    """Set our parameters based on the given minor.
785

786
    This sets our minor variable and our dev_path.
787

788
    """
789
    self.minor = minor
790
    self.dev_path = "/dev/md%d" % minor
791

    
792

    
793
  def Assemble(self):
794
    """Assemble the MD device.
795

796
    At this point we should have:
797
      - list of children devices
798
      - uuid
799

800
    """
801
    result = super(MDRaid1, self).Assemble()
802
    if not result:
803
      return result
804
    md_list = self._GetUsedDevs()
805
    for minor in md_list:
806
      info = self._GetDevInfo(minor)
807
      if info and info["uuid"] == self.unique_id:
808
        self._SetFromMinor(minor)
809
        logger.Info("MD array %s already started" % str(self))
810
        return True
811
    free_minor = self._FindUnusedMinor()
812
    result = utils.RunCmd(["mdadm", "-A", "--auto=yes", "--uuid",
813
                           self.unique_id, "/dev/md%d" % free_minor] +
814
                          [bdev.dev_path for bdev in self._children])
815
    if result.failed:
816
      logger.Error("Can't assemble MD array: %s: %s" %
817
                   (result.fail_reason, result.output))
818
      self.minor = None
819
    else:
820
      self.minor = free_minor
821
    return not result.failed
822

    
823

    
824
  def Shutdown(self):
825
    """Tear down the MD array.
826

827
    This does a 'mdadm --stop' so after this command, the array is no
828
    longer available.
829

830
    """
831
    if self.minor is None and not self.Attach():
832
      logger.Info("MD object not attached to a device")
833
      return True
834

    
835
    result = utils.RunCmd(["mdadm", "--stop", "/dev/md%d" % self.minor])
836
    if result.failed:
837
      logger.Error("Can't stop MD array: %s" % result.fail_reason)
838
      return False
839
    self.minor = None
840
    self.dev_path = None
841
    return True
842

    
843

    
844
  def SetSyncSpeed(self, kbytes):
845
    """Set the maximum sync speed for the MD array.
846

847
    """
848
    result = super(MDRaid1, self).SetSyncSpeed(kbytes)
849
    if self.minor is None:
850
      logger.Error("MD array not attached to a device")
851
      return False
852
    f = open("/sys/block/md%d/md/sync_speed_max" % self.minor, "w")
853
    try:
854
      f.write("%d" % kbytes)
855
    finally:
856
      f.close()
857
    f = open("/sys/block/md%d/md/sync_speed_min" % self.minor, "w")
858
    try:
859
      f.write("%d" % (kbytes/2))
860
    finally:
861
      f.close()
862
    return result
863

    
864

    
865
  def GetSyncStatus(self):
866
    """Returns the sync status of the device.
867

868
    Returns:
869
     (sync_percent, estimated_time)
870

871
    If sync_percent is None, it means all is ok
872
    If estimated_time is None, it means we can't esimate
873
    the time needed, otherwise it's the time left in seconds
874

875
    """
876
    if self.minor is None and not self.Attach():
877
      raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
878
    dev_info = self._GetDevInfo(self.minor)
879
    is_clean = ("state" in dev_info and
880
                len(dev_info["state"]) == 1 and
881
                dev_info["state"][0] in ("clean", "active"))
882
    sys_path = "/sys/block/md%s/md/" % self.minor
883
    f = file(sys_path + "sync_action")
884
    sync_status = f.readline().strip()
885
    f.close()
886
    if sync_status == "idle":
887
      return None, None, not is_clean
888
    f = file(sys_path + "sync_completed")
889
    sync_completed = f.readline().strip().split(" / ")
890
    f.close()
891
    if len(sync_completed) != 2:
892
      return 0, None, not is_clean
893
    sync_done, sync_total = [float(i) for i in sync_completed]
894
    sync_percent = 100.0*sync_done/sync_total
895
    f = file(sys_path + "sync_speed")
896
    sync_speed_k = int(f.readline().strip())
897
    if sync_speed_k == 0:
898
      time_est = None
899
    else:
900
      time_est = (sync_total - sync_done) / 2 / sync_speed_k
901
    return sync_percent, time_est, not is_clean
902

    
903

    
904
  def Open(self, force=False):
905
    """Make the device ready for I/O.
906

907
    This is a no-op for the MDRaid1 device type, although we could use
908
    the 2.6.18's new array_state thing.
909

910
    """
911
    return True
912

    
913

    
914
  def Close(self):
915
    """Notifies that the device will no longer be used for I/O.
916

917
    This is a no-op for the MDRaid1 device type, but see comment for
918
    `Open()`.
919

920
    """
921
    return True
922

    
923

    
924
class BaseDRBD(BlockDev):
925
  """Base DRBD class.
926

927
  This class contains a few bits of common functionality between the
928
  0.7 and 8.x versions of DRBD.
929

930
  """
931
  _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
932
                           r" \(api:(\d+)/proto:(\d+)\)")
933
  _DRBD_MAJOR = 147
934
  _ST_UNCONFIGURED = "Unconfigured"
935
  _ST_WFCONNECTION = "WFConnection"
936
  _ST_CONNECTED = "Connected"
937

    
938
  @staticmethod
939
  def _GetProcData():
940
    """Return data from /proc/drbd.
941

942
    """
943
    stat = open("/proc/drbd", "r")
944
    try:
945
      data = stat.read().splitlines()
946
    finally:
947
      stat.close()
948
    if not data:
949
      raise errors.BlockDeviceError("Can't read any data from /proc/drbd")
950
    return data
951

    
952
  @staticmethod
953
  def _MassageProcData(data):
954
    """Transform the output of _GetProdData into a nicer form.
955

956
    Returns:
957
      a dictionary of minor: joined lines from /proc/drbd for that minor
958

959
    """
960
    lmatch = re.compile("^ *([0-9]+):.*$")
961
    results = {}
962
    old_minor = old_line = None
963
    for line in data:
964
      lresult = lmatch.match(line)
965
      if lresult is not None:
966
        if old_minor is not None:
967
          results[old_minor] = old_line
968
        old_minor = int(lresult.group(1))
969
        old_line = line
970
      else:
971
        if old_minor is not None:
972
          old_line += " " + line.strip()
973
    # add last line
974
    if old_minor is not None:
975
      results[old_minor] = old_line
976
    return results
977

    
978
  @classmethod
979
  def _GetVersion(cls):
980
    """Return the DRBD version.
981

982
    This will return a list [k_major, k_minor, k_point, api, proto].
983

984
    """
985
    proc_data = cls._GetProcData()
986
    first_line = proc_data[0].strip()
987
    version = cls._VERSION_RE.match(first_line)
988
    if not version:
989
      raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
990
                                    first_line)
991
    return [int(val) for val in version.groups()]
992

    
993
  @staticmethod
994
  def _DevPath(minor):
995
    """Return the path to a drbd device for a given minor.
996

997
    """
998
    return "/dev/drbd%d" % minor
999

    
1000
  @classmethod
1001
  def _GetUsedDevs(cls):
1002
    """Compute the list of used DRBD devices.
1003

1004
    """
1005
    data = cls._GetProcData()
1006

    
1007
    used_devs = {}
1008
    valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
1009
    for line in data:
1010
      match = valid_line.match(line)
1011
      if not match:
1012
        continue
1013
      minor = int(match.group(1))
1014
      state = match.group(2)
1015
      if state == cls._ST_UNCONFIGURED:
1016
        continue
1017
      used_devs[minor] = state, line
1018

    
1019
    return used_devs
1020

    
1021
  def _SetFromMinor(self, minor):
1022
    """Set our parameters based on the given minor.
1023

1024
    This sets our minor variable and our dev_path.
1025

1026
    """
1027
    if minor is None:
1028
      self.minor = self.dev_path = None
1029
    else:
1030
      self.minor = minor
1031
      self.dev_path = self._DevPath(minor)
1032

    
1033
  @staticmethod
1034
  def _CheckMetaSize(meta_device):
1035
    """Check if the given meta device looks like a valid one.
1036

1037
    This currently only check the size, which must be around
1038
    128MiB.
1039

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

    
1059

    
1060
class DRBDev(BaseDRBD):
1061
  """DRBD block device.
1062

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

1067
  The unique_id for the drbd device is the (local_ip, local_port,
1068
  remote_ip, remote_port) tuple, and it must have two children: the
1069
  data device and the meta_device. The meta device is checked for
1070
  valid size and is zeroed on create.
1071

1072
  """
1073
  def __init__(self, unique_id, children):
1074
    super(DRBDev, self).__init__(unique_id, children)
1075
    self.major = self._DRBD_MAJOR
1076
    [kmaj, kmin, kfix, api, proto] = self._GetVersion()
1077
    if kmaj != 0 and kmin != 7:
1078
      raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1079
                                    " requested ganeti usage: kernel is"
1080
                                    " %s.%s, ganeti wants 0.7" % (kmaj, kmin))
1081

    
1082
    if len(children) != 2:
1083
      raise ValueError("Invalid configuration data %s" % str(children))
1084
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1085
      raise ValueError("Invalid configuration data %s" % str(unique_id))
1086
    self._lhost, self._lport, self._rhost, self._rport = unique_id
1087
    self.Attach()
1088

    
1089
  @classmethod
1090
  def _FindUnusedMinor(cls):
1091
    """Find an unused DRBD device.
1092

1093
    """
1094
    data = cls._GetProcData()
1095

    
1096
    valid_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1097
    for line in data:
1098
      match = valid_line.match(line)
1099
      if match:
1100
        return int(match.group(1))
1101
    logger.Error("Error: no free drbd minors!")
1102
    raise errors.BlockDeviceError("Can't find a free DRBD minor")
1103

    
1104
  @classmethod
1105
  def _GetDevInfo(cls, minor):
1106
    """Get details about a given DRBD minor.
1107

1108
    This return, if available, the local backing device in (major,
1109
    minor) formant and the local and remote (ip, port) information.
1110

1111
    """
1112
    data = {}
1113
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1114
    if result.failed:
1115
      logger.Error("Can't display the drbd config: %s" % result.fail_reason)
1116
      return data
1117
    out = result.stdout
1118
    if out == "Not configured\n":
1119
      return data
1120
    for line in out.splitlines():
1121
      if "local_dev" not in data:
1122
        match = re.match("^Lower device: ([0-9]+):([0-9]+) .*$", line)
1123
        if match:
1124
          data["local_dev"] = (int(match.group(1)), int(match.group(2)))
1125
          continue
1126
      if "meta_dev" not in data:
1127
        match = re.match("^Meta device: (([0-9]+):([0-9]+)|internal).*$", line)
1128
        if match:
1129
          if match.group(2) is not None and match.group(3) is not None:
1130
            # matched on the major/minor
1131
            data["meta_dev"] = (int(match.group(2)), int(match.group(3)))
1132
          else:
1133
            # matched on the "internal" string
1134
            data["meta_dev"] = match.group(1)
1135
            # in this case, no meta_index is in the output
1136
            data["meta_index"] = -1
1137
          continue
1138
      if "meta_index" not in data:
1139
        match = re.match("^Meta index: ([0-9]+).*$", line)
1140
        if match:
1141
          data["meta_index"] = int(match.group(1))
1142
          continue
1143
      if "local_addr" not in data:
1144
        match = re.match("^Local address: ([0-9.]+):([0-9]+)$", line)
1145
        if match:
1146
          data["local_addr"] = (match.group(1), int(match.group(2)))
1147
          continue
1148
      if "remote_addr" not in data:
1149
        match = re.match("^Remote address: ([0-9.]+):([0-9]+)$", line)
1150
        if match:
1151
          data["remote_addr"] = (match.group(1), int(match.group(2)))
1152
          continue
1153
    return data
1154

    
1155

    
1156
  def _MatchesLocal(self, info):
1157
    """Test if our local config matches with an existing device.
1158

1159
    The parameter should be as returned from `_GetDevInfo()`. This
1160
    method tests if our local backing device is the same as the one in
1161
    the info parameter, in effect testing if we look like the given
1162
    device.
1163

1164
    """
1165
    if not ("local_dev" in info and "meta_dev" in info and
1166
            "meta_index" in info):
1167
      return False
1168

    
1169
    backend = self._children[0]
1170
    if backend is not None:
1171
      retval = (info["local_dev"] == (backend.major, backend.minor))
1172
    else:
1173
      retval = (info["local_dev"] == (0, 0))
1174
    meta = self._children[1]
1175
    if meta is not None:
1176
      retval = retval and (info["meta_dev"] == (meta.major, meta.minor))
1177
      retval = retval and (info["meta_index"] == 0)
1178
    else:
1179
      retval = retval and (info["meta_dev"] == "internal" and
1180
                           info["meta_index"] == -1)
1181
    return retval
1182

    
1183

    
1184
  def _MatchesNet(self, info):
1185
    """Test if our network config matches with an existing device.
1186

1187
    The parameter should be as returned from `_GetDevInfo()`. This
1188
    method tests if our network configuration is the same as the one
1189
    in the info parameter, in effect testing if we look like the given
1190
    device.
1191

1192
    """
1193
    if (((self._lhost is None and not ("local_addr" in info)) and
1194
         (self._rhost is None and not ("remote_addr" in info)))):
1195
      return True
1196

    
1197
    if self._lhost is None:
1198
      return False
1199

    
1200
    if not ("local_addr" in info and
1201
            "remote_addr" in info):
1202
      return False
1203

    
1204
    retval = (info["local_addr"] == (self._lhost, self._lport))
1205
    retval = (retval and
1206
              info["remote_addr"] == (self._rhost, self._rport))
1207
    return retval
1208

    
1209

    
1210
  @classmethod
1211
  def _AssembleLocal(cls, minor, backend, meta):
1212
    """Configure the local part of a DRBD device.
1213

1214
    This is the first thing that must be done on an unconfigured DRBD
1215
    device. And it must be done only once.
1216

1217
    """
1218
    if not cls._CheckMetaSize(meta):
1219
      return False
1220
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1221
                           backend, meta, "0", "-e", "detach"])
1222
    if result.failed:
1223
      logger.Error("Can't attach local disk: %s" % result.output)
1224
    return not result.failed
1225

    
1226

    
1227
  @classmethod
1228
  def _ShutdownLocal(cls, minor):
1229
    """Detach from the local device.
1230

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

1234
    """
1235
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1236
    if result.failed:
1237
      logger.Error("Can't detach local device: %s" % result.output)
1238
    return not result.failed
1239

    
1240

    
1241
  @staticmethod
1242
  def _ShutdownAll(minor):
1243
    """Deactivate the device.
1244

1245
    This will, of course, fail if the device is in use.
1246

1247
    """
1248
    result = utils.RunCmd(["drbdsetup", DRBDev._DevPath(minor), "down"])
1249
    if result.failed:
1250
      logger.Error("Can't shutdown drbd device: %s" % result.output)
1251
    return not result.failed
1252

    
1253

    
1254
  @classmethod
1255
  def _AssembleNet(cls, minor, net_info, protocol):
1256
    """Configure the network part of the device.
1257

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

1265
    """
1266
    lhost, lport, rhost, rport = net_info
1267
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "net",
1268
                           "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport),
1269
                           protocol])
1270
    if result.failed:
1271
      logger.Error("Can't setup network for dbrd device: %s" %
1272
                   result.fail_reason)
1273
      return False
1274

    
1275
    timeout = time.time() + 10
1276
    ok = False
1277
    while time.time() < timeout:
1278
      info = cls._GetDevInfo(minor)
1279
      if not "local_addr" in info or not "remote_addr" in info:
1280
        time.sleep(1)
1281
        continue
1282
      if (info["local_addr"] != (lhost, lport) or
1283
          info["remote_addr"] != (rhost, rport)):
1284
        time.sleep(1)
1285
        continue
1286
      ok = True
1287
      break
1288
    if not ok:
1289
      logger.Error("Timeout while configuring network")
1290
      return False
1291
    return True
1292

    
1293

    
1294
  @classmethod
1295
  def _ShutdownNet(cls, minor):
1296
    """Disconnect from the remote peer.
1297

1298
    This fails if we don't have a local device.
1299

1300
    """
1301
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1302
    logger.Error("Can't shutdown network: %s" % result.output)
1303
    return not result.failed
1304

    
1305

    
1306
  def Assemble(self):
1307
    """Assemble the drbd.
1308

1309
    Method:
1310
      - if we have a local backing device, we bind to it by:
1311
        - checking the list of used drbd devices
1312
        - check if the local minor use of any of them is our own device
1313
        - if yes, abort?
1314
        - if not, bind
1315
      - if we have a local/remote net info:
1316
        - redo the local backing device step for the remote device
1317
        - check if any drbd device is using the local port,
1318
          if yes abort
1319
        - check if any remote drbd device is using the remote
1320
          port, if yes abort (for now)
1321
        - bind our net port
1322
        - bind the remote net port
1323

1324
    """
1325
    self.Attach()
1326
    if self.minor is not None:
1327
      logger.Info("Already assembled")
1328
      return True
1329

    
1330
    result = super(DRBDev, self).Assemble()
1331
    if not result:
1332
      return result
1333

    
1334
    minor = self._FindUnusedMinor()
1335
    need_localdev_teardown = False
1336
    if self._children[0]:
1337
      result = self._AssembleLocal(minor, self._children[0].dev_path,
1338
                                   self._children[1].dev_path)
1339
      if not result:
1340
        return False
1341
      need_localdev_teardown = True
1342
    if self._lhost and self._lport and self._rhost and self._rport:
1343
      result = self._AssembleNet(minor,
1344
                                 (self._lhost, self._lport,
1345
                                  self._rhost, self._rport),
1346
                                 "C")
1347
      if not result:
1348
        if need_localdev_teardown:
1349
          # we will ignore failures from this
1350
          logger.Error("net setup failed, tearing down local device")
1351
          self._ShutdownAll(minor)
1352
        return False
1353
    self._SetFromMinor(minor)
1354
    return True
1355

    
1356

    
1357
  def Shutdown(self):
1358
    """Shutdown the DRBD device.
1359

1360
    """
1361
    if self.minor is None and not self.Attach():
1362
      logger.Info("DRBD device not attached to a device during Shutdown")
1363
      return True
1364
    if not self._ShutdownAll(self.minor):
1365
      return False
1366
    self.minor = None
1367
    self.dev_path = None
1368
    return True
1369

    
1370

    
1371
  def Attach(self):
1372
    """Find a DRBD device which matches our config and attach to it.
1373

1374
    In case of partially attached (local device matches but no network
1375
    setup), we perform the network attach. If successful, we re-test
1376
    the attach if can return success.
1377

1378
    """
1379
    for minor in self._GetUsedDevs():
1380
      info = self._GetDevInfo(minor)
1381
      match_l = self._MatchesLocal(info)
1382
      match_r = self._MatchesNet(info)
1383
      if match_l and match_r:
1384
        break
1385
      if match_l and not match_r and "local_addr" not in info:
1386
        res_r = self._AssembleNet(minor,
1387
                                  (self._lhost, self._lport,
1388
                                   self._rhost, self._rport),
1389
                                  "C")
1390
        if res_r and self._MatchesNet(self._GetDevInfo(minor)):
1391
          break
1392
    else:
1393
      minor = None
1394

    
1395
    self._SetFromMinor(minor)
1396
    return minor is not None
1397

    
1398

    
1399
  def Open(self, force=False):
1400
    """Make the local state primary.
1401

1402
    If the 'force' parameter is given, the '--do-what-I-say' parameter
1403
    is given. Since this is a pottentialy dangerous operation, the
1404
    force flag should be only given after creation, when it actually
1405
    has to be given.
1406

1407
    """
1408
    if self.minor is None and not self.Attach():
1409
      logger.Error("DRBD cannot attach to a device during open")
1410
      return False
1411
    cmd = ["drbdsetup", self.dev_path, "primary"]
1412
    if force:
1413
      cmd.append("--do-what-I-say")
1414
    result = utils.RunCmd(cmd)
1415
    if result.failed:
1416
      logger.Error("Can't make drbd device primary: %s" % result.output)
1417
      return False
1418
    return True
1419

    
1420

    
1421
  def Close(self):
1422
    """Make the local state secondary.
1423

1424
    This will, of course, fail if the device is in use.
1425

1426
    """
1427
    if self.minor is None and not self.Attach():
1428
      logger.Info("Instance not attached to a device")
1429
      raise errors.BlockDeviceError("Can't find device")
1430
    result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1431
    if result.failed:
1432
      logger.Error("Can't switch drbd device to secondary: %s" % result.output)
1433
      raise errors.BlockDeviceError("Can't switch drbd device to secondary")
1434

    
1435

    
1436
  def SetSyncSpeed(self, kbytes):
1437
    """Set the speed of the DRBD syncer.
1438

1439
    """
1440
    children_result = super(DRBDev, self).SetSyncSpeed(kbytes)
1441
    if self.minor is None:
1442
      logger.Info("Instance not attached to a device")
1443
      return False
1444
    result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1445
                           kbytes])
1446
    if result.failed:
1447
      logger.Error("Can't change syncer rate: %s " % result.fail_reason)
1448
    return not result.failed and children_result
1449

    
1450

    
1451
  def GetSyncStatus(self):
1452
    """Returns the sync status of the device.
1453

1454
    Returns:
1455
     (sync_percent, estimated_time)
1456

1457
    If sync_percent is None, it means all is ok
1458
    If estimated_time is None, it means we can't esimate
1459
    the time needed, otherwise it's the time left in seconds
1460

1461
    """
1462
    if self.minor is None and not self.Attach():
1463
      raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1464
    proc_info = self._MassageProcData(self._GetProcData())
1465
    if self.minor not in proc_info:
1466
      raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1467
                                    self.minor)
1468
    line = proc_info[self.minor]
1469
    match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1470
                     " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1471
    if match:
1472
      sync_percent = float(match.group(1))
1473
      hours = int(match.group(2))
1474
      minutes = int(match.group(3))
1475
      seconds = int(match.group(4))
1476
      est_time = hours * 3600 + minutes * 60 + seconds
1477
    else:
1478
      sync_percent = None
1479
      est_time = None
1480
    match = re.match("^ *[0-9]+: cs:([^ ]+).*$", line)
1481
    if not match:
1482
      raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
1483
                                    self.minor)
1484
    client_state = match.group(1)
1485
    is_degraded = client_state != "Connected"
1486
    return sync_percent, est_time, is_degraded
1487

    
1488

    
1489

    
1490

    
1491
  def GetStatus(self):
1492
    """Compute the status of the DRBD device
1493

1494
    Note that DRBD devices don't have the STATUS_EXISTING state.
1495

1496
    """
1497
    if self.minor is None and not self.Attach():
1498
      return self.STATUS_UNKNOWN
1499

    
1500
    data = self._GetProcData()
1501
    match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
1502
                       self.minor)
1503
    for line in data:
1504
      mresult = match.match(line)
1505
      if mresult:
1506
        break
1507
    else:
1508
      logger.Error("Can't find myself!")
1509
      return self.STATUS_UNKNOWN
1510

    
1511
    state = mresult.group(2)
1512
    if state == "Primary":
1513
      result = self.STATUS_ONLINE
1514
    else:
1515
      result = self.STATUS_STANDBY
1516

    
1517
    return result
1518

    
1519

    
1520
  @staticmethod
1521
  def _ZeroDevice(device):
1522
    """Zero a device.
1523

1524
    This writes until we get ENOSPC.
1525

1526
    """
1527
    f = open(device, "w")
1528
    buf = "\0" * 1048576
1529
    try:
1530
      while True:
1531
        f.write(buf)
1532
    except IOError, err:
1533
      if err.errno != errno.ENOSPC:
1534
        raise
1535

    
1536

    
1537
  @classmethod
1538
  def Create(cls, unique_id, children, size):
1539
    """Create a new DRBD device.
1540

1541
    Since DRBD devices are not created per se, just assembled, this
1542
    function just zeroes the meta device.
1543

1544
    """
1545
    if len(children) != 2:
1546
      raise errors.ProgrammerError("Invalid setup for the drbd device")
1547
    meta = children[1]
1548
    meta.Assemble()
1549
    if not meta.Attach():
1550
      raise errors.BlockDeviceError("Can't attach to meta device")
1551
    if not cls._CheckMetaSize(meta.dev_path):
1552
      raise errors.BlockDeviceError("Invalid meta device")
1553
    logger.Info("Started zeroing device %s" % meta.dev_path)
1554
    cls._ZeroDevice(meta.dev_path)
1555
    logger.Info("Done zeroing device %s" % meta.dev_path)
1556
    return cls(unique_id, children)
1557

    
1558

    
1559
  def Remove(self):
1560
    """Stub remove for DRBD devices.
1561

1562
    """
1563
    return self.Shutdown()
1564

    
1565
class DRBD8(BaseDRBD):
1566
  """DRBD v8.x block device.
1567

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

1572
  The unique_id for the drbd device is the (local_ip, local_port,
1573
  remote_ip, remote_port) tuple, and it must have two children: the
1574
  data device and the meta_device. The meta device is checked for
1575
  valid size and is zeroed on create.
1576

1577
  """
1578
  _DRBD_MAJOR = 147
1579
  _MAX_MINORS = 255
1580
  _PARSE_SHOW = None
1581

    
1582
  def __init__(self, unique_id, children):
1583
    super(DRBD8, self).__init__(unique_id, children)
1584
    self.major = self._DRBD_MAJOR
1585
    [kmaj, kmin, kfix, api, proto] = self._GetVersion()
1586
    if kmaj != 8:
1587
      raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1588
                                    " requested ganeti usage: kernel is"
1589
                                    " %s.%s, ganeti wants 8.x" % (kmaj, kmin))
1590

    
1591
    if len(children) != 2:
1592
      raise ValueError("Invalid configuration data %s" % str(children))
1593
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1594
      raise ValueError("Invalid configuration data %s" % str(unique_id))
1595
    self._lhost, self._lport, self._rhost, self._rport = unique_id
1596
    self.Attach()
1597

    
1598
  @classmethod
1599
  def _InitMeta(cls, minor, dev_path):
1600
    """Initialize a meta device.
1601

1602
    This will not work if the given minor is in use.
1603

1604
    """
1605
    result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
1606
                           "v08", dev_path, "0", "create-md"])
1607
    if result.failed:
1608
      raise errors.BlockDeviceError("Can't initialize meta device: %s" %
1609
                                    result.output)
1610

    
1611
  @classmethod
1612
  def _FindUnusedMinor(cls):
1613
    """Find an unused DRBD device.
1614

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

1618
    """
1619
    data = cls._GetProcData()
1620

    
1621
    unused_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1622
    used_line = re.compile("^ *([0-9]+): cs:")
1623
    highest = None
1624
    for line in data:
1625
      match = unused_line.match(line)
1626
      if match:
1627
        return int(match.group(1))
1628
      match = used_line.match(line)
1629
      if match:
1630
        minor = int(match.group(1))
1631
        highest = max(highest, minor)
1632
    if highest is None: # there are no minors in use at all
1633
      return 0
1634
    if highest >= cls._MAX_MINORS:
1635
      logger.Error("Error: no free drbd minors!")
1636
      raise errors.BlockDeviceError("Can't find a free DRBD minor")
1637
    return highest + 1
1638

    
1639
  @classmethod
1640
  def _IsValidMeta(cls, meta_device):
1641
    """Check if the given meta device looks like a valid one.
1642

1643
    """
1644
    minor = cls._FindUnusedMinor()
1645
    minor_path = cls._DevPath(minor)
1646
    result = utils.RunCmd(["drbdmeta", minor_path,
1647
                           "v08", meta_device, "0",
1648
                           "dstate"])
1649
    if result.failed:
1650
      logger.Error("Invalid meta device %s: %s" % (meta_device, result.output))
1651
      return False
1652
    return True
1653

    
1654
  @classmethod
1655
  def _GetShowParser(cls):
1656
    """Return a parser for `drbd show` output.
1657

1658
    This will either create or return an already-create parser for the
1659
    output of the command `drbd show`.
1660

1661
    """
1662
    if cls._PARSE_SHOW is not None:
1663
      return cls._PARSE_SHOW
1664

    
1665
    # pyparsing setup
1666
    lbrace = pyp.Literal("{").suppress()
1667
    rbrace = pyp.Literal("}").suppress()
1668
    semi = pyp.Literal(";").suppress()
1669
    # this also converts the value to an int
1670
    number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t:(l, [int(t[0])]))
1671

    
1672
    comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
1673
    defa = pyp.Literal("_is_default").suppress()
1674
    dbl_quote = pyp.Literal('"').suppress()
1675

    
1676
    keyword = pyp.Word(pyp.alphanums + '-')
1677

    
1678
    # value types
1679
    value = pyp.Word(pyp.alphanums + '_-/.:')
1680
    quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1681
    addr_port = (pyp.Word(pyp.nums + '.') + pyp.Literal(':').suppress() +
1682
                 number)
1683
    # meta device, extended syntax
1684
    meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
1685
                  number + pyp.Word(']').suppress())
1686

    
1687
    # a statement
1688
    stmt = (~rbrace + keyword + ~lbrace +
1689
            (addr_port ^ value ^ quoted ^ meta_value) +
1690
            pyp.Optional(defa) + semi +
1691
            pyp.Optional(pyp.restOfLine).suppress())
1692

    
1693
    # an entire section
1694
    section_name = pyp.Word(pyp.alphas + '_')
1695
    section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1696

    
1697
    bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1698
    bnf.ignore(comment)
1699

    
1700
    cls._PARSE_SHOW = bnf
1701

    
1702
    return bnf
1703

    
1704
  @classmethod
1705
  def _GetDevInfo(cls, minor):
1706
    """Get details about a given DRBD minor.
1707

1708
    This return, if available, the local backing device (as a path)
1709
    and the local and remote (ip, port) information.
1710

1711
    """
1712
    data = {}
1713
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1714
    if result.failed:
1715
      logger.Error("Can't display the drbd config: %s" % result.fail_reason)
1716
      return data
1717
    out = result.stdout
1718
    if not out:
1719
      return data
1720

    
1721
    bnf = cls._GetShowParser()
1722
    # run pyparse
1723

    
1724
    try:
1725
      results = bnf.parseString(out)
1726
    except pyp.ParseException, err:
1727
      raise errors.BlockDeviceError("Can't parse drbdsetup show output: %s" %
1728
                                    str(err))
1729

    
1730
    # and massage the results into our desired format
1731
    for section in results:
1732
      sname = section[0]
1733
      if sname == "_this_host":
1734
        for lst in section[1:]:
1735
          if lst[0] == "disk":
1736
            data["local_dev"] = lst[1]
1737
          elif lst[0] == "meta-disk":
1738
            data["meta_dev"] = lst[1]
1739
            data["meta_index"] = lst[2]
1740
          elif lst[0] == "address":
1741
            data["local_addr"] = tuple(lst[1:])
1742
      elif sname == "_remote_host":
1743
        for lst in section[1:]:
1744
          if lst[0] == "address":
1745
            data["remote_addr"] = tuple(lst[1:])
1746
    return data
1747

    
1748
  def _MatchesLocal(self, info):
1749
    """Test if our local config matches with an existing device.
1750

1751
    The parameter should be as returned from `_GetDevInfo()`. This
1752
    method tests if our local backing device is the same as the one in
1753
    the info parameter, in effect testing if we look like the given
1754
    device.
1755

1756
    """
1757
    backend = self._children[0]
1758
    if backend is not None:
1759
      retval = (info["local_dev"] == backend.dev_path)
1760
    else:
1761
      retval = ("local_dev" not in info)
1762
    meta = self._children[1]
1763
    if meta is not None:
1764
      retval = retval and (info["meta_dev"] == meta.dev_path)
1765
      retval = retval and (info["meta_index"] == 0)
1766
    else:
1767
      retval = retval and ("meta_dev" not in info and
1768
                           "meta_index" not in info)
1769
    return retval
1770

    
1771
  def _MatchesNet(self, info):
1772
    """Test if our network config matches with an existing device.
1773

1774
    The parameter should be as returned from `_GetDevInfo()`. This
1775
    method tests if our network configuration is the same as the one
1776
    in the info parameter, in effect testing if we look like the given
1777
    device.
1778

1779
    """
1780
    if (((self._lhost is None and not ("local_addr" in info)) and
1781
         (self._rhost is None and not ("remote_addr" in info)))):
1782
      return True
1783

    
1784
    if self._lhost is None:
1785
      return False
1786

    
1787
    if not ("local_addr" in info and
1788
            "remote_addr" in info):
1789
      return False
1790

    
1791
    retval = (info["local_addr"] == (self._lhost, self._lport))
1792
    retval = (retval and
1793
              info["remote_addr"] == (self._rhost, self._rport))
1794
    return retval
1795

    
1796
  @classmethod
1797
  def _AssembleLocal(cls, minor, backend, meta):
1798
    """Configure the local part of a DRBD device.
1799

1800
    This is the first thing that must be done on an unconfigured DRBD
1801
    device. And it must be done only once.
1802

1803
    """
1804
    if not cls._IsValidMeta(meta):
1805
      return False
1806
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1807
                           backend, meta, "0", "-e", "detach",
1808
                           "--create-device"])
1809
    if result.failed:
1810
      logger.Error("Can't attach local disk: %s" % result.output)
1811
    return not result.failed
1812

    
1813
  @classmethod
1814
  def _AssembleNet(cls, minor, net_info, protocol,
1815
                   dual_pri=False, hmac=None, secret=None):
1816
    """Configure the network part of the device.
1817

1818
    """
1819
    lhost, lport, rhost, rport = net_info
1820
    args = ["drbdsetup", cls._DevPath(minor), "net",
1821
            "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1822
            "-A", "discard-zero-changes",
1823
            "-B", "consensus",
1824
            ]
1825
    if dual_pri:
1826
      args.append("-m")
1827
    if hmac and secret:
1828
      args.extend(["-a", hmac, "-x", secret])
1829
    result = utils.RunCmd(args)
1830
    if result.failed:
1831
      logger.Error("Can't setup network for dbrd device: %s" %
1832
                   result.fail_reason)
1833
      return False
1834

    
1835
    timeout = time.time() + 10
1836
    ok = False
1837
    while time.time() < timeout:
1838
      info = cls._GetDevInfo(minor)
1839
      if not "local_addr" in info or not "remote_addr" in info:
1840
        time.sleep(1)
1841
        continue
1842
      if (info["local_addr"] != (lhost, lport) or
1843
          info["remote_addr"] != (rhost, rport)):
1844
        time.sleep(1)
1845
        continue
1846
      ok = True
1847
      break
1848
    if not ok:
1849
      logger.Error("Timeout while configuring network")
1850
      return False
1851
    return True
1852

    
1853
  def SetSyncSpeed(self, kbytes):
1854
    """Set the speed of the DRBD syncer.
1855

1856
    """
1857
    children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1858
    if self.minor is None:
1859
      logger.Info("Instance not attached to a device")
1860
      return False
1861
    result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1862
                           kbytes])
1863
    if result.failed:
1864
      logger.Error("Can't change syncer rate: %s " % result.fail_reason)
1865
    return not result.failed and children_result
1866

    
1867
  def GetSyncStatus(self):
1868
    """Returns the sync status of the device.
1869

1870
    Returns:
1871
     (sync_percent, estimated_time)
1872

1873
    If sync_percent is None, it means all is ok
1874
    If estimated_time is None, it means we can't esimate
1875
    the time needed, otherwise it's the time left in seconds
1876

1877
    """
1878
    if self.minor is None and not self.Attach():
1879
      raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1880
    proc_info = self._MassageProcData(self._GetProcData())
1881
    if self.minor not in proc_info:
1882
      raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1883
                                    self.minor)
1884
    line = proc_info[self.minor]
1885
    match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1886
                     " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1887
    if match:
1888
      sync_percent = float(match.group(1))
1889
      hours = int(match.group(2))
1890
      minutes = int(match.group(3))
1891
      seconds = int(match.group(4))
1892
      est_time = hours * 3600 + minutes * 60 + seconds
1893
    else:
1894
      sync_percent = None
1895
      est_time = None
1896
    match = re.match("^ *[0-9]+: cs:([^ ]+).*$", line)
1897
    if not match:
1898
      raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
1899
                                    self.minor)
1900
    client_state = match.group(1)
1901
    is_degraded = client_state != "Connected"
1902
    return sync_percent, est_time, is_degraded
1903

    
1904
  def GetStatus(self):
1905
    """Compute the status of the DRBD device
1906

1907
    Note that DRBD devices don't have the STATUS_EXISTING state.
1908

1909
    """
1910
    if self.minor is None and not self.Attach():
1911
      return self.STATUS_UNKNOWN
1912

    
1913
    data = self._GetProcData()
1914
    match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
1915
                       self.minor)
1916
    for line in data:
1917
      mresult = match.match(line)
1918
      if mresult:
1919
        break
1920
    else:
1921
      logger.Error("Can't find myself!")
1922
      return self.STATUS_UNKNOWN
1923

    
1924
    state = mresult.group(2)
1925
    if state == "Primary":
1926
      result = self.STATUS_ONLINE
1927
    else:
1928
      result = self.STATUS_STANDBY
1929

    
1930
    return result
1931

    
1932
  def Open(self, force=False):
1933
    """Make the local state primary.
1934

1935
    If the 'force' parameter is given, the '--do-what-I-say' parameter
1936
    is given. Since this is a pottentialy dangerous operation, the
1937
    force flag should be only given after creation, when it actually
1938
    has to be given.
1939

1940
    """
1941
    if self.minor is None and not self.Attach():
1942
      logger.Error("DRBD cannot attach to a device during open")
1943
      return False
1944
    cmd = ["drbdsetup", self.dev_path, "primary"]
1945
    if force:
1946
      cmd.append("-o")
1947
    result = utils.RunCmd(cmd)
1948
    if result.failed:
1949
      logger.Error("Can't make drbd device primary: %s" % result.output)
1950
      return False
1951
    return True
1952

    
1953
  def Close(self):
1954
    """Make the local state secondary.
1955

1956
    This will, of course, fail if the device is in use.
1957

1958
    """
1959
    if self.minor is None and not self.Attach():
1960
      logger.Info("Instance not attached to a device")
1961
      raise errors.BlockDeviceError("Can't find device")
1962
    result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1963
    if result.failed:
1964
      logger.Error("Can't switch drbd device to secondary: %s" % result.output)
1965
      raise errors.BlockDeviceError("Can't switch drbd device to secondary")
1966

    
1967
  def Attach(self):
1968
    """Find a DRBD device which matches our config and attach to it.
1969

1970
    In case of partially attached (local device matches but no network
1971
    setup), we perform the network attach. If successful, we re-test
1972
    the attach if can return success.
1973

1974
    """
1975
    for minor in self._GetUsedDevs():
1976
      info = self._GetDevInfo(minor)
1977
      match_l = self._MatchesLocal(info)
1978
      match_r = self._MatchesNet(info)
1979
      if match_l and match_r:
1980
        break
1981
      if match_l and not match_r and "local_addr" not in info:
1982
        res_r = self._AssembleNet(minor,
1983
                                  (self._lhost, self._lport,
1984
                                   self._rhost, self._rport),
1985
                                  "C")
1986
        if res_r and self._MatchesNet(self._GetDevInfo(minor)):
1987
          break
1988
    else:
1989
      minor = None
1990

    
1991
    self._SetFromMinor(minor)
1992
    return minor is not None
1993

    
1994
  def Assemble(self):
1995
    """Assemble the drbd.
1996

1997
    Method:
1998
      - if we have a local backing device, we bind to it by:
1999
        - checking the list of used drbd devices
2000
        - check if the local minor use of any of them is our own device
2001
        - if yes, abort?
2002
        - if not, bind
2003
      - if we have a local/remote net info:
2004
        - redo the local backing device step for the remote device
2005
        - check if any drbd device is using the local port,
2006
          if yes abort
2007
        - check if any remote drbd device is using the remote
2008
          port, if yes abort (for now)
2009
        - bind our net port
2010
        - bind the remote net port
2011

2012
    """
2013
    self.Attach()
2014
    if self.minor is not None:
2015
      logger.Info("Already assembled")
2016
      return True
2017

    
2018
    result = super(DRBD8, self).Assemble()
2019
    if not result:
2020
      return result
2021

    
2022
    minor = self._FindUnusedMinor()
2023
    need_localdev_teardown = False
2024
    if self._children[0]:
2025
      result = self._AssembleLocal(minor, self._children[0].dev_path,
2026
                                   self._children[1].dev_path)
2027
      if not result:
2028
        return False
2029
      need_localdev_teardown = True
2030
    if self._lhost and self._lport and self._rhost and self._rport:
2031
      result = self._AssembleNet(minor,
2032
                                 (self._lhost, self._lport,
2033
                                  self._rhost, self._rport),
2034
                                 "C")
2035
      if not result:
2036
        if need_localdev_teardown:
2037
          # we will ignore failures from this
2038
          logger.Error("net setup failed, tearing down local device")
2039
          self._ShutdownAll(minor)
2040
        return False
2041
    self._SetFromMinor(minor)
2042
    return True
2043

    
2044
  @classmethod
2045
  def _ShutdownAll(cls, minor):
2046
    """Deactivate the device.
2047

2048
    This will, of course, fail if the device is in use.
2049

2050
    """
2051
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
2052
    if result.failed:
2053
      logger.Error("Can't shutdown drbd device: %s" % result.output)
2054
    return not result.failed
2055

    
2056
  def Shutdown(self):
2057
    """Shutdown the DRBD device.
2058

2059
    """
2060
    if self.minor is None and not self.Attach():
2061
      logger.Info("DRBD device not attached to a device during Shutdown")
2062
      return True
2063
    if not self._ShutdownAll(self.minor):
2064
      return False
2065
    self.minor = None
2066
    self.dev_path = None
2067
    return True
2068

    
2069
  def Remove(self):
2070
    """Stub remove for DRBD devices.
2071

2072
    """
2073
    return self.Shutdown()
2074

    
2075
  @classmethod
2076
  def Create(cls, unique_id, children, size):
2077
    """Create a new DRBD8 device.
2078

2079
    Since DRBD devices are not created per se, just assembled, this
2080
    function only initializes the metadata.
2081

2082
    """
2083
    if len(children) != 2:
2084
      raise errors.ProgrammerError("Invalid setup for the drbd device")
2085
    meta = children[1]
2086
    meta.Assemble()
2087
    if not meta.Attach():
2088
      raise errors.BlockDeviceError("Can't attach to meta device")
2089
    if not cls._CheckMetaSize(meta.dev_path):
2090
      raise errors.BlockDeviceError("Invalid meta device size")
2091
    cls._InitMeta(cls._FindUnusedMinor(), meta.dev_path)
2092
    if not cls._IsValidMeta(meta.dev_path):
2093
      raise errors.BlockDeviceError("Cannot initalize meta device")
2094
    return cls(unique_id, children)
2095

    
2096

    
2097
DEV_MAP = {
2098
  constants.LD_LV: LogicalVolume,
2099
  constants.LD_MD_R1: MDRaid1,
2100
  constants.LD_DRBD7: DRBDev,
2101
  constants.LD_DRBD8: DRBD8,
2102
  }
2103

    
2104

    
2105
def FindDevice(dev_type, unique_id, children):
2106
  """Search for an existing, assembled device.
2107

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

2111
  """
2112
  if dev_type not in DEV_MAP:
2113
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2114
  device = DEV_MAP[dev_type](unique_id, children)
2115
  if not device.Attach():
2116
    return None
2117
  return  device
2118

    
2119

    
2120
def AttachOrAssemble(dev_type, unique_id, children):
2121
  """Try to attach or assemble an existing device.
2122

2123
  This will attach to an existing assembled device or will assemble
2124
  the device, as needed, to bring it fully up.
2125

2126
  """
2127
  if dev_type not in DEV_MAP:
2128
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2129
  device = DEV_MAP[dev_type](unique_id, children)
2130
  if not device.Attach():
2131
    device.Assemble()
2132
  if not device.Attach():
2133
    raise errors.BlockDeviceError("Can't find a valid block device for"
2134
                                  " %s/%s/%s" %
2135
                                  (dev_type, unique_id, children))
2136
  return device
2137

    
2138

    
2139
def Create(dev_type, unique_id, children, size):
2140
  """Create a device.
2141

2142
  """
2143
  if dev_type not in DEV_MAP:
2144
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
2145
  device = DEV_MAP[dev_type].Create(unique_id, children, size)
2146
  return device